Claude-skill-registry add-dialog-component
Create React dialog components with forms for the dealflow-network project using Radix UI, tRPC mutations, and proper state management. Use when adding create/edit dialogs, modals, or form-based UI components.
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/add-dialog-component" ~/.claude/skills/majiayu000-claude-skill-registry-add-dialog-component && rm -rf "$T"
manifest:
skills/data/add-dialog-component/SKILL.mdsource content
Add Dialog Component
Create form dialogs following project patterns.
Quick Start
When adding a dialog, I will:
- Create component in
client/src/components/ - Use Radix UI Dialog primitive
- Add form state with useState
- Integrate tRPC mutation
- Handle loading, success, and error states
Template: Create Dialog
// client/src/components/CreateItemDialog.tsx import { useState } from "react"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger, } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { trpc } from "@/lib/trpc"; import { toast } from "sonner"; interface CreateItemDialogProps { trigger?: React.ReactNode; onSuccess?: (item: { id: number; name: string }) => void; } export function CreateItemDialog({ trigger, onSuccess }: CreateItemDialogProps) { const [open, setOpen] = useState(false); const [name, setName] = useState(""); const [description, setDescription] = useState(""); const utils = trpc.useUtils(); const createMutation = trpc.items.create.useMutation({ onSuccess: (item) => { utils.items.list.invalidate(); toast.success("Item created successfully"); setOpen(false); resetForm(); onSuccess?.(item); }, onError: (error) => { toast.error(`Failed to create item: ${error.message}`); }, }); const resetForm = () => { setName(""); setDescription(""); }; const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); if (!name.trim()) { toast.error("Name is required"); return; } createMutation.mutate({ name: name.trim(), description }); }; return ( <Dialog open={open} onOpenChange={setOpen}> <DialogTrigger asChild> {trigger || <Button>Create Item</Button>} </DialogTrigger> <DialogContent> <DialogHeader> <DialogTitle>Create New Item</DialogTitle> </DialogHeader> <form onSubmit={handleSubmit} className="space-y-4"> <div className="space-y-2"> <Label htmlFor="name">Name *</Label> <Input id="name" value={name} onChange={(e) => setName(e.target.value)} placeholder="Enter item name" disabled={createMutation.isPending} /> </div> <div className="space-y-2"> <Label htmlFor="description">Description</Label> <Input id="description" value={description} onChange={(e) => setDescription(e.target.value)} placeholder="Enter description (optional)" disabled={createMutation.isPending} /> </div> <div className="flex justify-end gap-2"> <Button type="button" variant="outline" onClick={() => setOpen(false)} disabled={createMutation.isPending} > Cancel </Button> <Button type="submit" disabled={createMutation.isPending}> {createMutation.isPending ? "Creating..." : "Create"} </Button> </div> </form> </DialogContent> </Dialog> ); }
Template: Edit Dialog
// client/src/components/EditItemDialog.tsx import { useState, useEffect } from "react"; import { Dialog, DialogContent, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { trpc } from "@/lib/trpc"; import { toast } from "sonner"; import type { Item } from "@shared/types"; interface EditItemDialogProps { item: Item | null; open: boolean; onOpenChange: (open: boolean) => void; onSuccess?: () => void; } export function EditItemDialog({ item, open, onOpenChange, onSuccess, }: EditItemDialogProps) { const [name, setName] = useState(""); const [description, setDescription] = useState(""); const utils = trpc.useUtils(); // Populate form when item changes useEffect(() => { if (item) { setName(item.name); setDescription(item.description ?? ""); } }, [item]); const updateMutation = trpc.items.update.useMutation({ onSuccess: () => { utils.items.list.invalidate(); utils.items.get.invalidate({ id: item?.id }); toast.success("Item updated successfully"); onOpenChange(false); onSuccess?.(); }, onError: (error) => { toast.error(`Failed to update: ${error.message}`); }, }); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); if (!item) return; if (!name.trim()) { toast.error("Name is required"); return; } updateMutation.mutate({ id: item.id, name: name.trim(), description, }); }; return ( <Dialog open={open} onOpenChange={onOpenChange}> <DialogContent> <DialogHeader> <DialogTitle>Edit Item</DialogTitle> </DialogHeader> <form onSubmit={handleSubmit} className="space-y-4"> <div className="space-y-2"> <Label htmlFor="edit-name">Name *</Label> <Input id="edit-name" value={name} onChange={(e) => setName(e.target.value)} disabled={updateMutation.isPending} /> </div> <div className="space-y-2"> <Label htmlFor="edit-description">Description</Label> <Input id="edit-description" value={description} onChange={(e) => setDescription(e.target.value)} disabled={updateMutation.isPending} /> </div> <div className="flex justify-end gap-2"> <Button type="button" variant="outline" onClick={() => onOpenChange(false)} disabled={updateMutation.isPending} > Cancel </Button> <Button type="submit" disabled={updateMutation.isPending}> {updateMutation.isPending ? "Saving..." : "Save Changes"} </Button> </div> </form> </DialogContent> </Dialog> ); }
Template: Confirmation Dialog
// client/src/components/DeleteConfirmDialog.tsx import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog"; import { trpc } from "@/lib/trpc"; import { toast } from "sonner"; interface DeleteConfirmDialogProps { itemId: number | null; itemName: string; open: boolean; onOpenChange: (open: boolean) => void; onSuccess?: () => void; } export function DeleteConfirmDialog({ itemId, itemName, open, onOpenChange, onSuccess, }: DeleteConfirmDialogProps) { const utils = trpc.useUtils(); const deleteMutation = trpc.items.delete.useMutation({ onSuccess: () => { utils.items.list.invalidate(); toast.success("Item deleted"); onOpenChange(false); onSuccess?.(); }, onError: (error) => { toast.error(`Failed to delete: ${error.message}`); }, }); const handleConfirm = () => { if (itemId) { deleteMutation.mutate({ id: itemId }); } }; return ( <AlertDialog open={open} onOpenChange={onOpenChange}> <AlertDialogContent> <AlertDialogHeader> <AlertDialogTitle>Delete Item</AlertDialogTitle> <AlertDialogDescription> Are you sure you want to delete "{itemName}"? This action cannot be undone. </AlertDialogDescription> </AlertDialogHeader> <AlertDialogFooter> <AlertDialogCancel disabled={deleteMutation.isPending}> Cancel </AlertDialogCancel> <AlertDialogAction onClick={handleConfirm} disabled={deleteMutation.isPending} className="bg-destructive text-destructive-foreground hover:bg-destructive/90" > {deleteMutation.isPending ? "Deleting..." : "Delete"} </AlertDialogAction> </AlertDialogFooter> </AlertDialogContent> </AlertDialog> ); }
Usage in Page Component
// In a page component import { CreateItemDialog } from "@/components/CreateItemDialog"; import { EditItemDialog } from "@/components/EditItemDialog"; import { DeleteConfirmDialog } from "@/components/DeleteConfirmDialog"; function ItemsPage() { const [editItem, setEditItem] = useState<Item | null>(null); const [deleteItem, setDeleteItem] = useState<{ id: number; name: string } | null>(null); return ( <div> <CreateItemDialog /> <EditItemDialog item={editItem} open={!!editItem} onOpenChange={(open) => !open && setEditItem(null)} /> <DeleteConfirmDialog itemId={deleteItem?.id ?? null} itemName={deleteItem?.name ?? ""} open={!!deleteItem} onOpenChange={(open) => !open && setDeleteItem(null)} /> </div> ); }
Available UI Components
Import from
@/components/ui/:
,Dialog
,DialogContent
,DialogHeader
,DialogTitleDialogTrigger
,AlertDialog
,AlertDialogAction
,AlertDialogCancelAlertDialogContent
(variants: default, outline, destructive, ghost)Button
,Input
,LabelTextarea
,Select
,SelectContent
,SelectItem
,SelectTriggerSelectValue
,CheckboxSwitch
Checklist
- Form state managed with useState
- Form populated from props (for edit dialogs)
- Input validation before mutation
- Loading state on submit button
- Inputs disabled during mutation
- Cache invalidation on success
- Toast notifications for success/error
- Form reset on close/success
- Cancel button available