Claude-skill-registry generate-form
Generate form component with validation, state management, and submit handling. Use when creating new data entry forms.
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/generate-form" ~/.claude/skills/majiayu000-claude-skill-registry-generate-form && rm -rf "$T"
manifest:
skills/data/generate-form/SKILL.mdsource content
Generate Form
Generate a form component with validation and store integration.
Usage
When user requests to create a form component, ask for:
- Form name (e.g., "WaterIntakeForm", "SleepLogForm")
- Fields needed (name, type, validation rules)
- Store action to call on submit (e.g., "addWaterLog")
- Whether it includes a list (add/remove items like MealLogForm)
Implementation Pattern
Based on
src/components/forms/MealLogForm.tsx pattern.
File Structure
Create file:
src/components/forms/{FormName}.tsx
'use client'; import { useState } from 'react'; import { Card, CardContent, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { Trash2, Plus, Loader2 } from 'lucide-react'; import { useHealthStore } from '@/lib/store/healthStore'; import { toast } from 'sonner'; export function FormName() { const [field1, setField1] = useState(''); const [field2, setField2] = useState(''); const { addItem, isLoading } = useHealthStore(); const [errors, setErrors] = useState<Record<string, string>>({}); const validate = () => { const newErrors: Record<string, string> = {}; if (!field1.trim()) { newErrors.field1 = 'Field 1 is required'; } if (!field2.trim()) { newErrors.field2 = 'Field 2 is required'; } setErrors(newErrors); return Object.keys(newErrors).length === 0; }; const handleSubmit = async () => { if (!validate()) return; const today = new Date().toISOString().split('T')[0]; try { await addItem({ date: today, field1, field2, }); setField1(''); setField2(''); setErrors({}); } catch (error: any) { toast.error(error.message || 'Failed to submit form'); } }; return ( <Card> <CardHeader> <CardTitle>Form Title</CardTitle> </CardHeader> <CardContent className="space-y-4"> <div className="space-y-2"> <Label htmlFor="field1">Field 1 Label</Label> <Input id="field1" type="text" placeholder="Enter value" value={field1} onChange={(e) => setField1(e.target.value)} disabled={isLoading} /> {errors.field1 && <p className="text-xs text-red-500">{errors.field1}</p>} </div> <div className="space-y-2"> <Label htmlFor="field2">Field 2 Label</Label> <Input id="field2" type="number" placeholder="0" value={field2} onChange={(e) => setField2(e.target.value)} disabled={isLoading} /> {errors.field2 && <p className="text-xs text-red-500">{errors.field2}</p>} </div> </CardContent> <CardFooter> <Button onClick={handleSubmit} disabled={isLoading} className="w-full"> {isLoading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />} Submit </Button> </CardFooter> </Card> ); }
With List Management (Optional)
For forms that add/remove items from a list:
'use client'; import { useState } from 'react'; import { Trash2, Plus } from 'lucide-react'; export function FormWithList() { const [items, setItems] = useState<any[]>([]); const [currentItem, setCurrentItem] = useState(''); const addItem = () => { if (!currentItem.trim()) { toast.error('Item cannot be empty'); return; } setItems([...items, { id: Date.now(), name: currentItem }]); setCurrentItem(''); toast.success(`Added ${currentItem}`); }; const removeItem = (index: number) => { const itemName = items[index].name; setItems(items.filter((_, i) => i !== index)); toast.info(`Removed ${itemName}`); }; return ( <> <div className="flex gap-2"> <Input value={currentItem} onChange={(e) => setCurrentItem(e.target.value)} placeholder="Enter item" /> <Button onClick={addItem} variant="outline" size="sm"> <Plus className="h-4 w-4" /> </Button> </div> <div className="space-y-2 mt-4"> {items.map((item, index) => ( <div key={item.id} className="flex items-center justify-between bg-muted p-2 rounded"> <span>{item.name}</span> <Button onClick={() => removeItem(index)} variant="ghost" size="sm" > <Trash2 className="h-4 w-4 text-destructive" /> </Button> </div> ))} </div> </> ); }
Key Conventions
- Use
directive at top'use client' - useState for all form fields
- useHealthStore hook for store actions
- Validation function before submit
- Try-catch-toast error handling
- Set isLoading state on button during submission
- Clear form fields after successful submit
- Use shadcn/ui components (Input, Button, Label, etc.)
- Show validation errors below each field
- Disable inputs while loading
- Use Loader2 icon for loading state
- Toast notifications for all user actions
Steps
- Ask user for form name, fields, store action, and list management
- Create file:
src/components/forms/{FormName}.tsx - Generate component with Card wrapper
- Add useState for each field
- Add useHealthStore hook integration
- Add validation function
- Add handleSubmit with try-catch-toast
- Add form fields with shadcn/ui components
- Add list management if requested
- Format with Prettier
Implementation Checklist
- Component exports correctly
- 'use client' directive present
- useState for all fields
- useHealthStore hook imported
- Validation function implemented
- Error state management
- handleSubmit with try-catch
- Toast notifications added
- isLoading state used
- shadcn/ui components used
- Error messages displayed
- List management (if applicable)