{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "ink/date-picker",
  "title": "Date Picker",
  "description": "Calendar date picker with keyboard navigation",
  "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/date-picker.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 { resolveBorderStyle } from \"@/lib/terminal-style\";\n\nexport interface DatePickerProps {\n  value?: Date;\n  defaultValue?: Date;\n  onValueChange?: (date: Date) => void;\n  onChange?: (date: Date) => void;\n  onSubmit?: (date: Date) => void;\n  label?: string;\n  minDate?: Date;\n  maxDate?: Date;\n  autoFocus?: boolean;\n  id?: string;\n  isActive?: boolean;\n  disabled?: boolean;\n  required?: boolean;\n  \"aria-label\"?: string;\n}\n\nconst MONTHS = [\n  \"January\",\n  \"February\",\n  \"March\",\n  \"April\",\n  \"May\",\n  \"June\",\n  \"July\",\n  \"August\",\n  \"September\",\n  \"October\",\n  \"November\",\n  \"December\",\n];\n\nconst daysInMonth = (month: number, year: number): number =>\n  new Date(year, month + 1, 0).getDate();\n\nconst clamp = (val: number, min: number, max: number): number =>\n  Math.min(Math.max(val, min), max);\n\nconst buildDate = (m: number, d: number, y: number): Date => new Date(y, m, d);\n\nconst getNextField = (\n  f: \"month\" | \"day\" | \"year\"\n): \"month\" | \"day\" | \"year\" => {\n  if (f === \"month\") {\n    return \"day\";\n  }\n  if (f === \"day\") {\n    return \"year\";\n  }\n  return \"month\";\n};\n\nexport const DatePicker = ({\n  value: controlledValue,\n  defaultValue,\n  onValueChange,\n  onChange,\n  onSubmit,\n  label,\n  minDate,\n  maxDate,\n  autoFocus = false,\n  id,\n  isActive = true,\n  disabled = false,\n  required = false,\n  \"aria-label\": ariaLabel,\n}: DatePickerProps) => {\n  const unicode = useUnicode();\n  const theme = useTheme();\n\n  const now = new Date();\n  const initial = controlledValue ?? defaultValue ?? now;\n\n  const [month, setMonth] = useState(initial.getMonth());\n  const [day, setDay] = useState(initial.getDate());\n  const [year, setYear] = useState(initial.getFullYear());\n  const [field, setField] = useState<\"month\" | \"day\" | \"year\">(\"month\");\n\n  const notify = (m: number, d: number, y: number) => {\n    const date = buildDate(m, d, y);\n    if (minDate && date < minDate) {\n      return;\n    }\n    if (maxDate && date > maxDate) {\n      return;\n    }\n    (onValueChange ?? onChange)?.(date);\n  };\n\n  const { isFocused } = useInteraction(\n    (input, key) => {\n      if (key.leftArrow || key.rightArrow) {\n        setField((currentField) => {\n          if (key.rightArrow) {\n            return getNextField(currentField);\n          }\n          return currentField === \"month\"\n            ? \"year\"\n            : currentField === \"day\"\n              ? \"month\"\n              : \"day\";\n        });\n        return;\n      }\n\n      if (key.return) {\n        const date = buildDate(month, day, year);\n        onSubmit?.(date);\n        return;\n      }\n\n      if (key.upArrow) {\n        if (field === \"month\") {\n          const newMonth = (month + 1) % 12;\n          const maxDay = daysInMonth(newMonth, year);\n          const newDay = clamp(day, 1, maxDay);\n          setMonth(newMonth);\n          setDay(newDay);\n          notify(newMonth, newDay, year);\n        } else if (field === \"day\") {\n          const maxDay = daysInMonth(month, year);\n          const newDay = day >= maxDay ? 1 : day + 1;\n          setDay(newDay);\n          notify(month, newDay, year);\n        } else {\n          const newYear = year + 1;\n          const maxDay = daysInMonth(month, newYear);\n          const newDay = clamp(day, 1, maxDay);\n          setYear(newYear);\n          setDay(newDay);\n          notify(month, newDay, newYear);\n        }\n      } else if (key.downArrow) {\n        if (field === \"month\") {\n          const newMonth = month === 0 ? 11 : month - 1;\n          const maxDay = daysInMonth(newMonth, year);\n          const newDay = clamp(day, 1, maxDay);\n          setMonth(newMonth);\n          setDay(newDay);\n          notify(newMonth, newDay, year);\n        } else if (field === \"day\") {\n          const maxDay = daysInMonth(month, year);\n          const newDay = day <= 1 ? maxDay : day - 1;\n          setDay(newDay);\n          notify(month, newDay, year);\n        } else {\n          const newYear = year - 1;\n          const maxDay = daysInMonth(month, newYear);\n          const newDay = clamp(day, 1, maxDay);\n          setYear(newYear);\n          setDay(newDay);\n          notify(month, newDay, newYear);\n        }\n      }\n    },\n    { autoFocus, disabled, id, isActive }\n  );\n\n  const fieldColor = (f: typeof field): string =>\n    field === f && isFocused ? theme.colors.primary : theme.colors.foreground;\n\n  const fieldBg = (f: typeof field): string | undefined =>\n    field === f && isFocused ? theme.colors.selection : undefined;\n  const incrementIcon = unicode ? \"▲\" : \"^\";\n  const decrementIcon = unicode ? \"▼\" : \"v\";\n\n  return (\n    <Box flexDirection=\"column\">\n      {label && <Text bold>{label}</Text>}\n      <Box\n        aria-label={\n          ariaLabel ??\n          `${label ?? \"Date\"}: ${MONTHS[month]} ${day}, ${year}. Editing ${field}`\n        }\n        aria-role=\"textbox\"\n        aria-state={{ disabled, required }}\n        gap={1}\n        borderStyle={resolveBorderStyle(\"round\", unicode)}\n        borderColor={isFocused ? theme.colors.focusRing : theme.colors.border}\n        paddingX={1}\n      >\n        <Box aria-hidden flexDirection=\"column\" alignItems=\"center\">\n          <Text color={fieldColor(\"month\")}>{incrementIcon}</Text>\n          <Text\n            color={fieldColor(\"month\")}\n            backgroundColor={fieldBg(\"month\")}\n            bold={field === \"month\"}\n          >\n            {(MONTHS[month] ?? \"\").slice(0, 3)}\n          </Text>\n          <Text color={fieldColor(\"month\")}>{decrementIcon}</Text>\n        </Box>\n\n        <Text aria-hidden color={theme.colors.border}>\n          /\n        </Text>\n\n        <Box aria-hidden flexDirection=\"column\" alignItems=\"center\">\n          <Text color={fieldColor(\"day\")}>{incrementIcon}</Text>\n          <Text\n            color={fieldColor(\"day\")}\n            backgroundColor={fieldBg(\"day\")}\n            bold={field === \"day\"}\n          >\n            {String(day).padStart(2, \"0\")}\n          </Text>\n          <Text color={fieldColor(\"day\")}>{decrementIcon}</Text>\n        </Box>\n\n        <Text aria-hidden color={theme.colors.border}>\n          /\n        </Text>\n\n        <Box aria-hidden flexDirection=\"column\" alignItems=\"center\">\n          <Text color={fieldColor(\"year\")}>{incrementIcon}</Text>\n          <Text\n            color={fieldColor(\"year\")}\n            backgroundColor={fieldBg(\"year\")}\n            bold={field === \"year\"}\n          >\n            {year}\n          </Text>\n          <Text color={fieldColor(\"year\")}>{decrementIcon}</Text>\n        </Box>\n      </Box>\n      {isFocused && (\n        <Text aria-hidden color={theme.colors.mutedForeground} dimColor>\n          {unicode\n            ? \"↑↓: change · ←→: field · Enter: confirm\"\n            : \"up/down: change - left/right: field - Enter: confirm\"}\n        </Text>\n      )}\n    </Box>\n  );\n};\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "forms"
  ],
  "type": "registry:ui"
}