{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "confirm-dialog", "title": "Confirm Dialog", "description": "Accessible confirmation dialog with controlled state, async pending feedback, and retryable errors.", "registryDependencies": [ "askyourpolicy/ss-registry/stitch-base", "askyourpolicy/ss-registry/alert-dialog", "askyourpolicy/ss-registry/spinner" ], "files": [ { "path": "src/components/ui/confirm-dialog.tsx", "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport {\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogMedia,\n AlertDialogTitle,\n AlertDialogTrigger,\n} from \"@/components/ui/alert-dialog\";\nimport { Spinner } from \"@/components/ui/spinner\";\n\ntype ConfirmDialogVariant = \"default\" | \"destructive\";\n\ntype ConfirmDialogProps = {\n cancelLabel?: React.ReactNode;\n children?: React.ReactNode;\n confirmLabel?: React.ReactNode;\n contentProps?: Omit, \"children\">;\n defaultOpen?: boolean;\n description?: React.ReactNode;\n errorMessage?: (error: unknown) => React.ReactNode;\n media?: React.ReactNode;\n onConfirm: () => Promise | void;\n onOpenChange?: (open: boolean) => void;\n open?: boolean;\n pendingLabel?: React.ReactNode;\n title: React.ReactNode;\n trigger?: React.ReactElement;\n variant?: ConfirmDialogVariant;\n};\n\nfunction getDefaultErrorMessage(error: unknown) {\n if (error instanceof Error && error.message.trim()) {\n return error.message;\n }\n\n if (typeof error === \"string\" && error.trim()) {\n return error;\n }\n\n return \"The action could not be completed. Try again.\";\n}\n\nfunction ConfirmDialog({\n cancelLabel = \"Cancel\",\n children,\n confirmLabel = \"Confirm\",\n contentProps,\n defaultOpen = false,\n description,\n errorMessage = getDefaultErrorMessage,\n media,\n onConfirm,\n onOpenChange,\n open,\n pendingLabel = \"Confirming…\",\n title,\n trigger,\n variant = \"default\",\n}: ConfirmDialogProps) {\n const isControlled = open !== undefined;\n const [internalOpen, setInternalOpen] = React.useState(defaultOpen);\n const [pending, setPending] = React.useState(false);\n const [error, setError] = React.useState(null);\n const pendingRef = React.useRef(false);\n const resolvedOpen = open ?? internalOpen;\n\n React.useEffect(() => {\n if (!resolvedOpen) {\n setError(null);\n }\n }, [resolvedOpen]);\n\n function updateOpen(nextOpen: boolean) {\n if (!isControlled) {\n setInternalOpen(nextOpen);\n }\n\n if (!nextOpen) {\n setError(null);\n }\n\n onOpenChange?.(nextOpen);\n }\n\n function handleOpenChange(nextOpen: boolean) {\n if (pendingRef.current && !nextOpen) {\n return;\n }\n\n if (nextOpen) {\n setError(null);\n }\n\n updateOpen(nextOpen);\n }\n\n async function handleConfirm() {\n if (pendingRef.current) {\n return;\n }\n\n pendingRef.current = true;\n setError(null);\n setPending(true);\n\n try {\n await onConfirm();\n updateOpen(false);\n } catch (confirmError) {\n setError(errorMessage(confirmError));\n } finally {\n pendingRef.current = false;\n setPending(false);\n }\n }\n\n return (\n \n {trigger ? : null}\n \n \n {media != null ? {media} : null}\n {title}\n {description != null ? (\n {description}\n ) : null}\n \n\n {children != null || error != null ? (\n \n {children}\n {error != null ? (\n \n {error}\n \n ) : null}\n \n ) : null}\n\n \n {cancelLabel}\n \n {pending ? (\n <>\n \n {pendingLabel}\n \n ) : (\n confirmLabel\n )}\n \n \n \n \n );\n}\n\nexport {\n ConfirmDialog,\n getDefaultErrorMessage,\n type ConfirmDialogProps,\n type ConfirmDialogVariant,\n};\n", "type": "registry:ui" } ], "type": "registry:ui" }