{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "ink/modal",
  "title": "Modal",
  "description": "Focus-trapped overlay modal, close with Esc",
  "dependencies": [
    "ink"
  ],
  "registryDependencies": [
    "https://termcn.dev/r/ink/interaction.json",
    "https://termcn.dev/r/ink/terminal-style.json",
    "https://termcn.dev/r/ink/types.json",
    "https://termcn.dev/r/ink/use-theme.json",
    "https://termcn.dev/r/ink/use-unicode.json"
  ],
  "files": [
    {
      "path": "registry/ui/modal.tsx",
      "content": "import { Box, Text } from \"ink\";\nimport React, { useState } from \"react\";\nimport type { ReactNode } from \"react\";\n\nimport { FocusScope } from \"@/hooks/use-interaction\";\nimport { useTheme } from \"@/hooks/use-theme\";\nimport { useUnicode } from \"@/hooks/use-unicode\";\nimport { resolveBorderStyle } from \"@/lib/terminal-style\";\nimport type { BorderStyle } from \"@/components/ui/types\";\n\nexport interface ModalProps {\n  open?: boolean;\n  defaultOpen?: boolean;\n  onOpenChange?: (open: boolean) => void;\n  onClose?: () => void;\n  title?: string;\n  width?: number;\n  children?: ReactNode;\n  borderStyle?: BorderStyle;\n  borderColor?: string;\n  paddingX?: number;\n  paddingY?: number;\n  titleBorderStyle?: BorderStyle;\n  closeHint?: string | false;\n  initialFocusId?: string;\n  returnFocusId?: string;\n  \"aria-label\"?: string;\n}\n\nexport const Modal = ({\n  open: controlledOpen,\n  defaultOpen = false,\n  onOpenChange,\n  onClose,\n  title,\n  width = 60,\n  children,\n  borderStyle = \"round\",\n  borderColor,\n  paddingX = 1,\n  paddingY = 0,\n  titleBorderStyle = \"single\",\n  closeHint = \"Press Esc to close\",\n  initialFocusId,\n  returnFocusId,\n  \"aria-label\": ariaLabel,\n}: ModalProps) => {\n  const unicode = useUnicode();\n  const theme = useTheme();\n  const [internalOpen, setInternalOpen] = useState(defaultOpen);\n  const open = controlledOpen ?? internalOpen;\n  const resolvedBorderColor = borderColor ?? theme.colors.primary;\n\n  const close = () => {\n    if (controlledOpen === undefined) {\n      setInternalOpen(false);\n    }\n    onOpenChange?.(false);\n    onClose?.();\n  };\n\n  if (!open) {\n    return null;\n  }\n\n  return (\n    <FocusScope\n      active={open}\n      initialFocusId={initialFocusId}\n      returnFocusId={returnFocusId}\n      onEscapeKey={close}\n    >\n      <Box\n        flexDirection=\"column\"\n        borderStyle={resolveBorderStyle(borderStyle, unicode)}\n        borderColor={resolvedBorderColor}\n        width={width}\n        paddingX={paddingX}\n        paddingY={paddingY}\n      >\n        {(title || ariaLabel) && (\n          <Box\n            marginBottom={1}\n            borderStyle={resolveBorderStyle(titleBorderStyle, unicode)}\n            borderColor={theme.colors.border}\n            paddingX={1}\n          >\n            <Text\n              aria-label={ariaLabel ?? `Modal: ${title}`}\n              bold\n              color={resolvedBorderColor}\n            >\n              {title ?? \"\"}\n            </Text>\n          </Box>\n        )}\n        <Box flexDirection=\"column\">{children}</Box>\n        {closeHint !== false && (\n          <Box aria-hidden marginTop={1}>\n            <Text color={theme.colors.mutedForeground} dimColor>\n              {closeHint}\n            </Text>\n          </Box>\n        )}\n      </Box>\n    </FocusScope>\n  );\n};\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "overlays"
  ],
  "type": "registry:ui"
}