{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "ink/select",
  "title": "Select",
  "description": "Single-select dropdown with arrow-key navigation",
  "dependencies": [
    "ink"
  ],
  "registryDependencies": [
    "https://termcn.dev/r/ink/interaction.json",
    "https://termcn.dev/r/ink/terminal-symbols.json",
    "https://termcn.dev/r/ink/use-theme.json",
    "https://termcn.dev/r/ink/use-unicode.json"
  ],
  "files": [
    {
      "path": "registry/ui/select.tsx",
      "content": "import { Box, Text } from \"ink\";\nimport React, { useState } from \"react\";\n\nimport { useInteraction } from \"@/hooks/use-interaction\";\nimport { useTheme } from \"@/hooks/use-theme\";\nimport { useUnicode } from \"@/hooks/use-unicode\";\nimport { resolveTerminalSymbol } from \"@/lib/terminal-symbols\";\n\nexport interface SelectOption<T = string> {\n  value: T;\n  label: string;\n  hint?: string;\n  disabled?: boolean;\n}\n\nexport interface SelectProps<T = string> {\n  options: SelectOption<T>[];\n  value?: T;\n  defaultValue?: T;\n  onValueChange?: (value: T) => void;\n  onChange?: (value: T) => void;\n  onSubmit?: (value: T) => void;\n  label?: string;\n  cursor?: string;\n  cursorColor?: string;\n  id?: string;\n  autoFocus?: boolean;\n  isActive?: boolean;\n  disabled?: boolean;\n  \"aria-label\"?: string;\n}\n\nexport const Select = <T = string,>({\n  options,\n  value: controlledValue,\n  defaultValue,\n  onValueChange,\n  onChange,\n  onSubmit,\n  label,\n  cursor,\n  cursorColor,\n  id,\n  autoFocus = false,\n  isActive = true,\n  disabled = false,\n  \"aria-label\": ariaLabel,\n}: SelectProps<T>) => {\n  const theme = useTheme();\n  const unicode = useUnicode();\n  const resolvedCursor = cursor ?? resolveTerminalSymbol(unicode, \"›\", \">\");\n  const selectedIndicator = resolveTerminalSymbol(unicode, \"✓\", \"*\");\n  const firstEnabledIndex = Math.max(\n    0,\n    options.findIndex((option) => !option.disabled)\n  );\n  const [activeIndex, setActiveIndex] = useState(firstEnabledIndex);\n  const [internalValue, setInternalValue] = useState(defaultValue);\n  const selectedValue = controlledValue ?? internalValue;\n\n  const resolvedCursorColor = cursorColor ?? theme.colors.primary;\n\n  const { isFocused } = useInteraction(\n    (input, key) => {\n      if (key.upArrow) {\n        setActiveIndex((i) => {\n          let next = i - 1;\n          while (next >= 0 && options[next]?.disabled) {\n            next -= 1;\n          }\n          return next < 0 ? i : next;\n        });\n      } else if (key.downArrow) {\n        setActiveIndex((i) => {\n          let next = i + 1;\n          while (next < options.length && options[next]?.disabled) {\n            next += 1;\n          }\n          return next >= options.length ? i : next;\n        });\n      } else if (key.home) {\n        setActiveIndex(firstEnabledIndex);\n      } else if (key.end) {\n        const lastEnabledIndex = options.findLastIndex(\n          (option) => !option.disabled\n        );\n        if (lastEnabledIndex !== -1) {\n          setActiveIndex(lastEnabledIndex);\n        }\n      } else if (key.return) {\n        const opt = options[activeIndex];\n        if (opt && !opt.disabled) {\n          if (controlledValue === undefined) {\n            setInternalValue(opt.value);\n          }\n          (onValueChange ?? onChange)?.(opt.value);\n          onSubmit?.(opt.value);\n        }\n      }\n    },\n    { autoFocus, disabled, id, isActive }\n  );\n\n  return (\n    <Box aria-role=\"listbox\" aria-state={{ disabled }} flexDirection=\"column\">\n      {label && (\n        <Text aria-label={ariaLabel ?? label} bold>\n          {label}\n        </Text>\n      )}\n      {options.map((opt, idx) => {\n        const isActive = idx === activeIndex;\n        const isSelected =\n          selectedValue !== undefined && opt.value === selectedValue;\n\n        let optColor: string;\n        if (opt.disabled) {\n          optColor = theme.colors.mutedForeground;\n        } else if (isActive) {\n          optColor = resolvedCursorColor;\n        } else {\n          optColor = theme.colors.foreground;\n        }\n\n        return (\n          <Box\n            key={idx}\n            aria-label={opt.hint ? `${opt.label}: ${opt.hint}` : opt.label}\n            aria-role=\"option\"\n            aria-state={{ disabled: opt.disabled, selected: isSelected }}\n            gap={1}\n          >\n            <Text\n              aria-hidden\n              color={isActive ? resolvedCursorColor : undefined}\n            >\n              {isActive ? resolvedCursor : \" \"}\n            </Text>\n            {isSelected && (\n              <Text aria-hidden color={theme.colors.primary}>\n                {selectedIndicator}\n              </Text>\n            )}\n            <Text\n              aria-hidden\n              color={optColor}\n              bold={isActive || isSelected}\n              dimColor={opt.disabled}\n            >\n              {opt.label}\n            </Text>\n            {opt.hint && (\n              <Text aria-hidden color={theme.colors.mutedForeground} dimColor>\n                {opt.hint}\n              </Text>\n            )}\n          </Box>\n        );\n      })}\n    </Box>\n  );\n};\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "selection"
  ],
  "type": "registry:ui"
}