{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "confirmation-dialog",
  "title": "Confirmation Dialog",
  "description": "A native modal that explains impact before destructive actions, with optional typed confirmation.",
  "dependencies": [
    "class-variance-authority"
  ],
  "registryDependencies": [
    "alert-dialog",
    "button",
    "input",
    "label",
    "https://uaiblocks.dev/r/uai-theme.json",
    "https://uaiblocks.dev/r/uai-utils.json"
  ],
  "files": [
    {
      "path": "src/registry/uai/components/confirmation-dialog.tsx",
      "content": "\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport {\n  type ComponentProps,\n  createContext,\n  type ReactNode,\n  useContext,\n  useId,\n  useLayoutEffect,\n  useRef,\n  useState,\n} from \"react\";\nimport {\n  AlertDialog,\n  AlertDialogContent,\n  AlertDialogDescription,\n  AlertDialogTitle,\n  AlertDialogTrigger,\n} from \"@/components/ui/alert-dialog\";\nimport { Button } from \"@/components/ui/button\";\nimport { Input } from \"@/components/ui/input\";\nimport { Label } from \"@/components/ui/label\";\nimport { cn } from \"@/lib/uai-utils\";\n\nexport const CONFIRMATION_DIALOG_VARIANTS = [\"centered\", \"sheet\", \"compact\"] as const;\nexport type ConfirmationDialogVariant = (typeof CONFIRMATION_DIALOG_VARIANTS)[number];\nexport type ConfirmationDialogProps = {\n  variant?: ConfirmationDialogVariant;\n  open?: boolean;\n  defaultOpen?: boolean;\n  onOpenChange?: (open: boolean) => void;\n  children?: ReactNode;\n};\ntype DialogContext = {\n  id: string;\n  variant: ConfirmationDialogVariant;\n  open: boolean;\n  setOpen: (open: boolean) => void;\n  phrase: string | null;\n  setPhrase: (phrase: string | null) => void;\n  typed: string;\n  setTyped: (value: string) => void;\n  triggerRef: React.RefObject<HTMLButtonElement | null>;\n  cancelRef: React.RefObject<HTMLButtonElement | null>;\n};\nconst Context = createContext<DialogContext | null>(null);\nfunction useConfirmation(part: string) {\n  const context = useContext(Context);\n  if (!context) throw new Error(`${part} must be used within ConfirmationDialog`);\n  return context;\n}\nconst buttonTones = cva(\n  \"cursor-pointer whitespace-nowrap rounded-full border-0 py-0 font-medium transition-[background-color,filter,color,scale] duration-[120ms,120ms,120ms,140ms] ease-[ease-out,ease-out,ease-out,cubic-bezier(0.23,1,0.32,1)] focus-visible:ring-0 focus-visible:outline-2 focus-visible:outline-solid focus-visible:outline-offset-2 focus-visible:outline-ring enabled:active:scale-[0.97] motion-reduce:transition-none motion-reduce:enabled:active:scale-100\",\n  {\n    variants: {\n      tone: {\n        trigger:\n          \"bg-destructive/12 text-destructive-ink hover:bg-destructive/18 hover:text-destructive-ink dark:hover:bg-destructive/18\",\n        secondary:\n          \"bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklab,var(--secondary)_85%,var(--foreground))]\",\n        danger:\n          \"bg-destructive text-[oklch(0.99_0_0)] hover:bg-destructive enabled:hover:brightness-108 disabled:bg-muted disabled:text-subtle-foreground disabled:opacity-100 dark:bg-destructive dark:disabled:bg-muted\",\n      },\n      size: {\n        default: \"h-8 px-3.5 text-[13px]/[18px] has-[>svg]:px-3.5\",\n        compact: \"h-7 px-3 text-[12.5px]/[18px] has-[>svg]:px-3\",\n      },\n    },\n  },\n);\n\nconst dialogVariants = cva(\n  \"box-border border-0 bg-popover text-[13px]/[18px] text-popover-foreground shadow-[0_0_0_1px_var(--border-strong),0_18px_48px_-16px_oklch(0_0_0/0.36)] motion-reduce:data-[state=closed]:animate-none motion-reduce:data-closed:animate-none motion-reduce:data-[state=open]:animate-none motion-reduce:data-open:animate-none\",\n  {\n    variants: {\n      variant: {\n        centered:\n          \"w-[min(100%_-_32px,440px)] gap-4 rounded-[14px] p-5 duration-180 ease-[cubic-bezier(0.16,1,0.3,1)] data-[state=closed]:zoom-out-96 data-closed:zoom-out-96 data-[state=open]:zoom-in-96 data-open:zoom-in-96\",\n        sheet:\n          \"top-auto bottom-0 w-[min(100%,560px)] max-w-full translate-y-0 gap-4 rounded-t-3xl rounded-b-none px-5 pt-5 pb-6 duration-300 ease-out-quint data-[state=closed]:duration-200 data-closed:duration-200 data-[size=default]:sm:max-w-[560px] data-[state=closed]:slide-out-to-bottom-[24px] data-closed:slide-out-to-bottom-[24px] data-[state=open]:slide-in-from-bottom-[24px] data-open:slide-in-from-bottom-[24px] data-[state=closed]:[--tw-exit-scale:1]! data-closed:[--tw-exit-scale:1]! data-[state=open]:[--tw-enter-scale:1]! data-open:[--tw-enter-scale:1]!\",\n        compact:\n          \"w-[min(100%_-_32px,360px)] gap-3 rounded-xl p-4 duration-180 ease-[cubic-bezier(0.16,1,0.3,1)] data-[state=closed]:zoom-out-96 data-closed:zoom-out-96 data-[state=open]:zoom-in-96 data-open:zoom-in-96\",\n      },\n    },\n  },\n);\n\nexport function ConfirmationDialog({\n  variant = \"centered\",\n  open,\n  defaultOpen = false,\n  onOpenChange,\n  children,\n}: ConfirmationDialogProps) {\n  const id = useId();\n  const [internal, setInternal] = useState(defaultOpen);\n  const [phrase, setPhrase] = useState<string | null>(null);\n  const [typed, setTyped] = useState(\"\");\n  const triggerRef = useRef<HTMLButtonElement>(null);\n  const cancelRef = useRef<HTMLButtonElement>(null);\n  const current = open ?? internal;\n  const setOpen = (next: boolean) => {\n    if (next === current) return;\n    if (!next) setTyped(\"\");\n    if (open === undefined) setInternal(next);\n    onOpenChange?.(next);\n  };\n  return (\n    <Context.Provider\n      value={{\n        id,\n        variant,\n        open: current,\n        setOpen,\n        phrase,\n        setPhrase,\n        typed,\n        setTyped,\n        triggerRef,\n        cancelRef,\n      }}\n    >\n      <AlertDialog open={current} onOpenChange={setOpen}>\n        <div data-slot=\"confirmation-dialog\" data-variant={variant} className=\"contents\">\n          {children}\n        </div>\n      </AlertDialog>\n    </Context.Provider>\n  );\n}\n\nexport function ConfirmationDialogTrigger({\n  onClick,\n  className,\n  ...props\n}: ComponentProps<\"button\">) {\n  const context = useConfirmation(\"ConfirmationDialogTrigger\");\n  return (\n    <AlertDialogTrigger asChild onClick={onClick}>\n      <Button\n        type=\"button\"\n        variant=\"ghost\"\n        data-slot=\"confirmation-dialog-trigger\"\n        className={cn(buttonTones({ tone: \"trigger\", size: \"compact\" }), className)}\n        {...props}\n        ref={context.triggerRef}\n        data-tone=\"trigger\"\n      />\n    </AlertDialogTrigger>\n  );\n}\n\nexport function ConfirmationDialogContent({\n  className,\n  children,\n  ...props\n}: Omit<ComponentProps<typeof AlertDialogContent>, \"size\">) {\n  const context = useConfirmation(\"ConfirmationDialogContent\");\n  return (\n    <AlertDialogContent\n      data-slot=\"confirmation-dialog-content\"\n      className={cn(dialogVariants({ variant: context.variant }), className)}\n      {...props}\n      data-variant={context.variant}\n    >\n      {children}\n      <InitialFocus fallback={context.cancelRef} />\n    </AlertDialogContent>\n  );\n}\n\n/**\n * Moves focus into the dialog as it mounts: an element marked `autofocus` or `data-autofocus`,\n * else the cancel button. It runs before the primitive's own initial focus, which Radix and Base UI\n * both skip when focus is already inside, so neither needs primitive-specific props.\n */\nfunction InitialFocus({ fallback }: { fallback: React.RefObject<HTMLElement | null> }) {\n  useLayoutEffect(() => {\n    const content = fallback.current?.closest<HTMLElement>(\"[role=dialog], [role=alertdialog]\");\n    const preferred = content?.querySelector<HTMLElement>(\"[autofocus], [data-autofocus]\");\n    (preferred ?? fallback.current)?.focus();\n  }, [fallback]);\n  return null;\n}\n\nexport function ConfirmationDialogTitle({ className, ...props }: ComponentProps<\"h2\">) {\n  const context = useConfirmation(\"ConfirmationDialogTitle\");\n  return (\n    <AlertDialogTitle\n      data-slot=\"confirmation-dialog-title\"\n      className={cn(\n        \"m-0 font-semibold tracking-[-0.01em]\",\n        context.variant === \"compact\" ? \"text-[14px]/5\" : \"text-[15px]/5\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nexport function ConfirmationDialogDescription({\n  className,\n  children,\n  ...props\n}: ComponentProps<\"div\">) {\n  useConfirmation(\"ConfirmationDialogDescription\");\n  return (\n    <AlertDialogDescription\n      asChild\n      data-slot=\"confirmation-dialog-description\"\n      className={cn(\"grid gap-2.5 text-[13px]/[18px] text-pretty text-muted-foreground\", className)}\n    >\n      <div {...props}>{children}</div>\n    </AlertDialogDescription>\n  );\n}\n\nexport function ConfirmationDialogImpact({ className, ...props }: ComponentProps<\"ul\">) {\n  return (\n    <ul\n      data-slot=\"confirmation-dialog-impact\"\n      className={cn(\n        \"m-0 grid list-disc gap-1 rounded-[10px] bg-muted py-2.5 pr-3 pl-7 text-[12.5px] text-foreground\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nexport function ConfirmationDialogInput({\n  match,\n  children,\n  className,\n  onChange,\n  ...props\n}: Omit<ComponentProps<\"input\">, \"value\" | \"defaultValue\" | \"id\"> & {\n  /** Exact text the person must type before Confirm enables. */\n  match: string;\n}) {\n  const context = useConfirmation(\"ConfirmationDialogInput\");\n  const { setPhrase } = context;\n  useLayoutEffect(() => {\n    setPhrase(match);\n    return () => setPhrase(null);\n  }, [match, setPhrase]);\n  const inputId = `${context.id}-input`;\n  return (\n    <div className=\"grid gap-1.5\">\n      <Label\n        htmlFor={inputId}\n        className=\"block text-[12px]/[18px] font-normal text-muted-foreground select-auto\"\n      >\n        {children ?? (\n          <>\n            Type{\" \"}\n            <strong className=\"rounded-md bg-muted px-1.25 py-px [font-family:ui-monospace,SFMono-Regular,Menlo,monospace] text-[11.5px] font-medium text-foreground\">\n              {match}\n            </strong>{\" \"}\n            to confirm\n          </>\n        )}\n      </Label>\n      <Input\n        autoComplete=\"off\"\n        spellCheck={false}\n        data-slot=\"confirmation-dialog-input\"\n        className={cn(\n          \"h-8 rounded-[10px] border-border bg-background px-2.5 py-0 text-[13px] text-inherit shadow-none transition-[border-color,box-shadow] duration-120 ease-out hover:border-border-strong focus-visible:border-border-strong focus-visible:ring-3 focus-visible:ring-primary/18 md:text-[13px] motion-reduce:transition-none dark:bg-background\",\n          className,\n        )}\n        {...props}\n        id={inputId}\n        value={context.typed}\n        onChange={(event) => {\n          onChange?.(event);\n          if (!event.defaultPrevented) context.setTyped(event.target.value);\n        }}\n      />\n    </div>\n  );\n}\n\nexport function ConfirmationDialogActions({ className, ...props }: ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"confirmation-dialog-actions\"\n      className={cn(\"flex flex-wrap-reverse justify-end gap-2\", className)}\n      {...props}\n    />\n  );\n}\n\nexport function ConfirmationDialogCancel({\n  children = \"Cancel\",\n  onClick,\n  className,\n  ...props\n}: ComponentProps<\"button\">) {\n  const context = useConfirmation(\"ConfirmationDialogCancel\");\n  return (\n    <Button\n      variant=\"secondary\"\n      data-slot=\"confirmation-dialog-cancel\"\n      className={cn(\n        buttonTones({\n          tone: \"secondary\",\n          size: context.variant === \"compact\" ? \"compact\" : \"default\",\n        }),\n        className,\n      )}\n      {...props}\n      ref={context.cancelRef}\n      type=\"button\"\n      data-tone=\"secondary\"\n      onClick={(event) => {\n        onClick?.(event);\n        if (!event.defaultPrevented) context.setOpen(false);\n      }}\n    >\n      {children}\n    </Button>\n  );\n}\n\nexport function ConfirmationDialogConfirm({\n  onClick,\n  disabled,\n  className,\n  ...props\n}: ComponentProps<\"button\">) {\n  const context = useConfirmation(\"ConfirmationDialogConfirm\");\n  const blocked = disabled || (context.phrase !== null && context.typed !== context.phrase);\n  return (\n    <Button\n      variant=\"destructive\"\n      data-slot=\"confirmation-dialog-confirm\"\n      className={cn(\n        buttonTones({\n          tone: \"danger\",\n          size: context.variant === \"compact\" ? \"compact\" : \"default\",\n        }),\n        className,\n      )}\n      {...props}\n      type=\"button\"\n      disabled={blocked}\n      data-tone=\"danger\"\n      onClick={(event) => {\n        onClick?.(event);\n        if (!event.defaultPrevented) context.setOpen(false);\n      }}\n    />\n  );\n}\n",
      "type": "registry:ui",
      "target": "@ui/uai/confirmation-dialog.tsx"
    }
  ],
  "categories": [
    "feedback",
    "dialog"
  ],
  "type": "registry:ui"
}