{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "confirmation-dialog",
  "title": "Confirmation dialog",
  "description": "An accessible action confirmation with async progress, duplicate submission protection and retry after failure.",
  "registryDependencies": [
    "Familis-co/registry/alert",
    "Familis-co/registry/alert-dialog",
    "Familis-co/registry/spinner"
  ],
  "files": [
    {
      "path": "registry/familis/blocks/confirmation-dialog/confirmation-dialog.tsx",
      "content": "\"use client\"\n\nimport { useRef, useState, type ReactElement } from \"react\"\nimport { Alert, AlertDescription } from \"@/registry/familis/ui/alert\"\nimport {\n  AlertDialog,\n  AlertDialogAction,\n  AlertDialogCancel,\n  AlertDialogContent,\n  AlertDialogDescription,\n  AlertDialogFooter,\n  AlertDialogHeader,\n  AlertDialogTitle,\n  AlertDialogTrigger,\n} from \"@/registry/familis/ui/alert-dialog\"\nimport { Spinner } from \"@/registry/familis/ui/spinner\"\n\nexport interface ConfirmationDialogProps {\n  trigger: ReactElement\n  title: string\n  description: string\n  onConfirm: () => void | Promise<void>\n  confirmLabel?: string\n  cancelLabel?: string\n  pendingLabel?: string\n  errorMessage?: string\n  variant?: \"default\" | \"destructive\"\n  open?: boolean\n  defaultOpen?: boolean\n  onOpenChange?: (open: boolean) => void\n}\n\n/** Resolves before closing; failed actions keep the dialog open so the user can retry. */\nexport function ConfirmationDialog({\n  trigger,\n  title,\n  description,\n  onConfirm,\n  confirmLabel = \"Confirm\",\n  cancelLabel = \"Cancel\",\n  pendingLabel = \"Confirming…\",\n  errorMessage = \"Could not complete the action. Please try again.\",\n  variant = \"default\",\n  open,\n  defaultOpen = false,\n  onOpenChange,\n}: ConfirmationDialogProps) {\n  const [internalOpen, setInternalOpen] = useState(defaultOpen)\n  const [pending, setPending] = useState(false)\n  const [failed, setFailed] = useState(false)\n  const pendingRef = useRef(false)\n\n  function changeOpen(nextOpen: boolean) {\n    setInternalOpen(nextOpen)\n    setFailed(false)\n    onOpenChange?.(nextOpen)\n  }\n\n  async function confirm() {\n    if (pendingRef.current) return\n    pendingRef.current = true\n    setPending(true)\n    setFailed(false)\n    try {\n      await onConfirm()\n      pendingRef.current = false\n      changeOpen(false)\n    } catch {\n      setFailed(true)\n    } finally {\n      pendingRef.current = false\n      setPending(false)\n    }\n  }\n\n  return (\n    <AlertDialog\n      open={open ?? internalOpen}\n      onOpenChange={(nextOpen, details) => {\n        if (pendingRef.current) {\n          details.cancel()\n          return\n        }\n        changeOpen(nextOpen)\n      }}\n    >\n      <AlertDialogTrigger render={trigger} />\n      <AlertDialogContent aria-busy={pending}>\n        <AlertDialogHeader>\n          <AlertDialogTitle>{title}</AlertDialogTitle>\n          <AlertDialogDescription>{description}</AlertDialogDescription>\n        </AlertDialogHeader>\n        {failed && (\n          <Alert variant=\"destructive\">\n            <AlertDescription>{errorMessage}</AlertDescription>\n          </Alert>\n        )}\n        <AlertDialogFooter>\n          <AlertDialogCancel disabled={pending}>{cancelLabel}</AlertDialogCancel>\n          <AlertDialogAction type=\"button\" variant={variant} disabled={pending} onClick={confirm}>\n            {pending && <Spinner data-icon=\"inline-start\" aria-hidden=\"true\" />}\n            {pending ? pendingLabel : confirmLabel}\n          </AlertDialogAction>\n        </AlertDialogFooter>\n      </AlertDialogContent>\n    </AlertDialog>\n  )\n}\n",
      "type": "registry:component"
    }
  ],
  "meta": {
    "base": "base",
    "storybookId": "ui-blocks-confirmation-dialog--default"
  },
  "type": "registry:block"
}