Files
gh-jezweb-claude-skills-ski…/templates/shadcn-form.tsx
2025-11-30 08:25:27 +08:00

352 lines
10 KiB
TypeScript

/**
* shadcn/ui Form Component Integration
*
* Demonstrates:
* - shadcn/ui Form component with React Hook Form + Zod
* - FormField, FormItem, FormLabel, FormControl, FormMessage components
* - Type-safe form with proper error handling
* - Accessible form structure
*
* Installation:
* npx shadcn@latest add form input button
*/
import { zodResolver } from '@hookform/resolvers/zod'
import { useForm } from 'react-hook-form'
import { z } from 'zod'
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@/components/ui/form'
import { Input } from '@/components/ui/input'
import { Button } from '@/components/ui/button'
import { Textarea } from '@/components/ui/textarea'
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select'
import { Checkbox } from '@/components/ui/checkbox'
// Define schema
const profileFormSchema = z.object({
username: z.string()
.min(2, { message: 'Username must be at least 2 characters.' })
.max(30, { message: 'Username must not be longer than 30 characters.' }),
email: z.string()
.email({ message: 'Please enter a valid email address.' }),
bio: z.string()
.max(160, { message: 'Bio must not be longer than 160 characters.' })
.optional(),
role: z.enum(['admin', 'user', 'guest'], {
required_error: 'Please select a role.',
}),
notifications: z.boolean().default(false).optional(),
})
type ProfileFormValues = z.infer<typeof profileFormSchema>
export function ShadcnProfileForm() {
const form = useForm<ProfileFormValues>({
resolver: zodResolver(profileFormSchema),
defaultValues: {
username: '',
email: '',
bio: '',
notifications: false,
},
})
function onSubmit(data: ProfileFormValues) {
console.log('Form submitted:', data)
// Make API call
}
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8 max-w-2xl mx-auto">
<h2 className="text-3xl font-bold">Profile Settings</h2>
{/* Username Field */}
<FormField
control={form.control}
name="username"
render={({ field }) => (
<FormItem>
<FormLabel>Username</FormLabel>
<FormControl>
<Input placeholder="shadcn" {...field} />
</FormControl>
<FormDescription>
This is your public display name.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
{/* Email Field */}
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input type="email" placeholder="you@example.com" {...field} />
</FormControl>
<FormDescription>
We'll never share your email with anyone.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
{/* Bio Field (Textarea) */}
<FormField
control={form.control}
name="bio"
render={({ field }) => (
<FormItem>
<FormLabel>Bio</FormLabel>
<FormControl>
<Textarea
placeholder="Tell us a little bit about yourself"
className="resize-none"
{...field}
/>
</FormControl>
<FormDescription>
You can write up to 160 characters.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
{/* Role Field (Select) */}
<FormField
control={form.control}
name="role"
render={({ field }) => (
<FormItem>
<FormLabel>Role</FormLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select a role" />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="admin">Admin</SelectItem>
<SelectItem value="user">User</SelectItem>
<SelectItem value="guest">Guest</SelectItem>
</SelectContent>
</Select>
<FormDescription>
Choose your account type.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
{/* Notifications Field (Checkbox) */}
<FormField
control={form.control}
name="notifications"
render={({ field }) => (
<FormItem className="flex flex-row items-start space-x-3 space-y-0 rounded-md border p-4">
<FormControl>
<Checkbox
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
<div className="space-y-1 leading-none">
<FormLabel>
Email notifications
</FormLabel>
<FormDescription>
Receive email notifications about your account activity.
</FormDescription>
</div>
</FormItem>
)}
/>
<Button type="submit" disabled={form.formState.isSubmitting}>
{form.formState.isSubmitting ? 'Saving...' : 'Update profile'}
</Button>
</form>
</Form>
)
}
/**
* Multiple Field Types Example
*/
const settingsFormSchema = z.object({
name: z.string().min(1, 'Name is required'),
language: z.string(),
theme: z.enum(['light', 'dark', 'system']),
emailPreferences: z.object({
marketing: z.boolean().default(false),
updates: z.boolean().default(true),
security: z.boolean().default(true),
}),
})
type SettingsFormValues = z.infer<typeof settingsFormSchema>
export function ShadcnSettingsForm() {
const form = useForm<SettingsFormValues>({
resolver: zodResolver(settingsFormSchema),
defaultValues: {
name: '',
language: 'en',
theme: 'system',
emailPreferences: {
marketing: false,
updates: true,
security: true,
},
},
})
function onSubmit(data: SettingsFormValues) {
console.log('Settings updated:', data)
}
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8 max-w-2xl mx-auto">
<h2 className="text-3xl font-bold">Settings</h2>
{/* Name */}
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Display Name</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* Theme */}
<FormField
control={form.control}
name="theme"
render={({ field }) => (
<FormItem>
<FormLabel>Theme</FormLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<FormControl>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="light">Light</SelectItem>
<SelectItem value="dark">Dark</SelectItem>
<SelectItem value="system">System</SelectItem>
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
{/* Email Preferences (Nested Object with Checkboxes) */}
<div className="space-y-4">
<h3 className="text-lg font-medium">Email Preferences</h3>
<FormField
control={form.control}
name="emailPreferences.marketing"
render={({ field }) => (
<FormItem className="flex flex-row items-start space-x-3 space-y-0">
<FormControl>
<Checkbox
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
<div className="space-y-1 leading-none">
<FormLabel>Marketing emails</FormLabel>
<FormDescription>
Receive emails about new products and features.
</FormDescription>
</div>
</FormItem>
)}
/>
<FormField
control={form.control}
name="emailPreferences.updates"
render={({ field }) => (
<FormItem className="flex flex-row items-start space-x-3 space-y-0">
<FormControl>
<Checkbox
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
<div className="space-y-1 leading-none">
<FormLabel>Update emails</FormLabel>
<FormDescription>
Receive emails about your account updates.
</FormDescription>
</div>
</FormItem>
)}
/>
<FormField
control={form.control}
name="emailPreferences.security"
render={({ field }) => (
<FormItem className="flex flex-row items-start space-x-3 space-y-0">
<FormControl>
<Checkbox
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
<div className="space-y-1 leading-none">
<FormLabel>Security emails</FormLabel>
<FormDescription>
Receive emails about your account security (recommended).
</FormDescription>
</div>
</FormItem>
)}
/>
</div>
<Button type="submit">Save settings</Button>
</form>
</Form>
)
}
/**
* NOTE: shadcn/ui states "We are not actively developing the Form component anymore."
* They recommend using the Field component for new implementations.
* Check https://ui.shadcn.com/docs/components/form for latest guidance.
*/