{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "ink/dialog",
  "title": "Dialog",
  "description": "Confirmation dialog with OK/Cancel actions",
  "dependencies": [
    "ink"
  ],
  "registryDependencies": [
    "https://termcn.dev/r/ink/interaction.json",
    "https://termcn.dev/r/ink/terminal-style.json",
    "https://termcn.dev/r/ink/use-theme.json",
    "https://termcn.dev/r/ink/use-unicode.json"
  ],
  "files": [
    {
      "path": "registry/ui/dialog.tsx",
      "content": "import { Box, Text } from \"ink\";\nimport { useId, useState } from \"react\";\nimport type { ReactNode } from \"react\";\n\nimport {\n  FocusScope,\n  isActivationKey,\n  useInteraction,\n} from \"@/hooks/use-interaction\";\nimport { useTheme } from \"@/hooks/use-theme\";\nimport { useUnicode } from \"@/hooks/use-unicode\";\nimport { resolveBorderStyle } from \"@/lib/terminal-style\";\n\ninterface DialogActionsProps {\n  id: string;\n  confirmLabel: string;\n  cancelLabel: string;\n  confirmColor: string;\n  onConfirm: () => void;\n  onCancel: () => void;\n}\n\nconst DialogActions = ({\n  id,\n  confirmLabel,\n  cancelLabel,\n  confirmColor,\n  onConfirm,\n  onCancel,\n}: DialogActionsProps) => {\n  const theme = useTheme();\n  const [focusedButton, setFocusedButton] = useState<\"cancel\" | \"confirm\">(\n    \"cancel\"\n  );\n  const { isFocused } = useInteraction(\n    (input, key) => {\n      if (key.leftArrow) {\n        setFocusedButton(\"cancel\");\n      } else if (key.rightArrow) {\n        setFocusedButton(\"confirm\");\n      } else if (key.home) {\n        setFocusedButton(\"cancel\");\n      } else if (key.end) {\n        setFocusedButton(\"confirm\");\n      } else if (isActivationKey(input, key)) {\n        if (focusedButton === \"confirm\") {\n          onConfirm();\n        } else {\n          onCancel();\n        }\n      }\n    },\n    { autoFocus: true, id }\n  );\n\n  return (\n    <Box\n      aria-role=\"toolbar\"\n      flexDirection=\"row\"\n      gap={2}\n      justifyContent=\"flex-end\"\n      marginTop={1}\n    >\n      <Box\n        aria-label={cancelLabel}\n        aria-role=\"button\"\n        aria-state={{ selected: focusedButton === \"cancel\" }}\n      >\n        <Text\n          aria-hidden\n          color={\n            focusedButton === \"cancel\"\n              ? theme.colors.foreground\n              : theme.colors.mutedForeground\n          }\n          bold={focusedButton === \"cancel\"}\n          inverse={focusedButton === \"cancel\"}\n        >\n          {` ${cancelLabel} `}\n        </Text>\n      </Box>\n      <Box\n        aria-label={confirmLabel}\n        aria-role=\"button\"\n        aria-state={{ selected: focusedButton === \"confirm\" }}\n      >\n        <Text\n          aria-hidden\n          color={\n            focusedButton === \"confirm\"\n              ? confirmColor\n              : theme.colors.mutedForeground\n          }\n          bold={focusedButton === \"confirm\"}\n          inverse={focusedButton === \"confirm\"}\n        >\n          {` ${confirmLabel} `}\n        </Text>\n      </Box>\n    </Box>\n  );\n};\n\nexport interface DialogProps {\n  title?: string;\n  children: ReactNode;\n  confirmLabel?: string;\n  cancelLabel?: string;\n  onConfirm?: () => void;\n  onCancel?: () => void;\n  variant?: \"default\" | \"danger\";\n  open?: boolean;\n  defaultOpen?: boolean;\n  onOpenChange?: (open: boolean) => void;\n  isOpen?: boolean;\n  initialFocusId?: string;\n  returnFocusId?: string;\n  \"aria-label\"?: string;\n}\n\nexport const Dialog = ({\n  title,\n  children,\n  confirmLabel = \"OK\",\n  cancelLabel = \"Cancel\",\n  onConfirm,\n  onCancel,\n  variant = \"default\",\n  open: controlledOpen,\n  defaultOpen = false,\n  onOpenChange,\n  isOpen,\n  initialFocusId,\n  returnFocusId,\n  \"aria-label\": ariaLabel,\n}: DialogProps) => {\n  const unicode = useUnicode();\n  const theme = useTheme();\n  const generatedId = useId();\n  const actionsId = `dialog-actions-${generatedId}`;\n  const [internalOpen, setInternalOpen] = useState(defaultOpen);\n  const open = controlledOpen ?? isOpen ?? internalOpen;\n\n  const setOpen = (nextOpen: boolean) => {\n    if (controlledOpen === undefined && isOpen === undefined) {\n      setInternalOpen(nextOpen);\n    }\n    onOpenChange?.(nextOpen);\n  };\n\n  const cancel = () => {\n    onCancel?.();\n    setOpen(false);\n  };\n\n  const confirm = () => {\n    onConfirm?.();\n    setOpen(false);\n  };\n\n  if (!open) {\n    return null;\n  }\n\n  const confirmColor =\n    variant === \"danger\" ? theme.colors.error : theme.colors.primary;\n\n  return (\n    <FocusScope\n      active={open}\n      initialFocusId={initialFocusId ?? actionsId}\n      returnFocusId={returnFocusId}\n      onEscapeKey={cancel}\n    >\n      <Box\n        flexDirection=\"column\"\n        borderStyle={resolveBorderStyle(\"round\", unicode)}\n        borderColor={\n          variant === \"danger\" ? theme.colors.error : theme.colors.primary\n        }\n        paddingX={1}\n        paddingY={0}\n      >\n        {(title || ariaLabel) && (\n          <Box marginBottom={1}>\n            <Text\n              aria-label={ariaLabel ?? `Dialog: ${title}`}\n              bold\n              color={\n                variant === \"danger\" ? theme.colors.error : theme.colors.primary\n              }\n            >\n              {title ?? \"\"}\n            </Text>\n          </Box>\n        )}\n        <Box marginBottom={1} flexDirection=\"column\">\n          {children}\n        </Box>\n        <DialogActions\n          id={actionsId}\n          confirmLabel={confirmLabel}\n          cancelLabel={cancelLabel}\n          confirmColor={confirmColor}\n          onConfirm={confirm}\n          onCancel={cancel}\n        />\n      </Box>\n    </FocusScope>\n  );\n};\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "overlays"
  ],
  "type": "registry:ui"
}