{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "date-picker",
  "title": "Date Picker",
  "description": "A calendar on a ruled grid, inline or in a popover.",
  "dependencies": [
    "@base-ui/react"
  ],
  "registryDependencies": [
    "https://spotcheckui.com/r/spotcheck-base.json"
  ],
  "files": [
    {
      "path": "registry/spotcheck/components/spotcheck/date-picker.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Popover } from \"@base-ui/react/popover\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst DAYS_SHOWN = 42\nconst DAYS_IN_WEEK = 7\n\nexport function addDays(date: Date, days: number) {\n  return new Date(date.getFullYear(), date.getMonth(), date.getDate() + days)\n}\n\nexport function addMonths(date: Date, months: number) {\n  const target = new Date(date.getFullYear(), date.getMonth() + months, 1)\n  const lastDay = new Date(target.getFullYear(), target.getMonth() + 1, 0).getDate()\n  return new Date(target.getFullYear(), target.getMonth(), Math.min(date.getDate(), lastDay))\n}\n\nexport function sameDay(a: Date | null | undefined, b: Date | null | undefined) {\n  return Boolean(a && b) && a?.toDateString() === b?.toDateString()\n}\n\n/** Six weeks starting on the Monday on or before the first of the month. */\nexport function monthGrid(month: Date) {\n  const first = new Date(month.getFullYear(), month.getMonth(), 1)\n  const start = addDays(first, -((first.getDay() + 6) % DAYS_IN_WEEK))\n  return Array.from({ length: DAYS_SHOWN }, (_, index) => addDays(start, index))\n}\n\ntype CalendarProps = {\n  /** The selected date. */\n  value?: Date | null\n  onValueChange?: (date: Date) => void\n  /** The month to open on when nothing is selected. Pass it for stable server rendering. */\n  defaultMonth?: Date\n  locale?: string\n  className?: string\n}\n\nexport function Calendar({ value, onValueChange, defaultMonth, locale = \"en-GB\", className }: CalendarProps) {\n  const [focused, setFocused] = React.useState(() => value ?? defaultMonth ?? new Date())\n  const grid = React.useRef<HTMLDivElement>(null)\n  const moved = React.useRef(false)\n\n  const days = monthGrid(focused)\n  const week = days.slice(0, DAYS_IN_WEEK)\n  const heading = new Intl.DateTimeFormat(locale, { month: \"long\", year: \"numeric\" }).format(focused)\n  const dayName = new Intl.DateTimeFormat(locale, { weekday: \"short\" })\n  const fullDate = new Intl.DateTimeFormat(locale, { dateStyle: \"full\" })\n\n  React.useEffect(() => {\n    if (!moved.current) return\n    moved.current = false\n    grid.current?.querySelector<HTMLElement>('[tabindex=\"0\"]')?.focus()\n  }, [focused])\n\n  const onKeyDown = (event: React.KeyboardEvent) => {\n    const weekday = (focused.getDay() + 6) % DAYS_IN_WEEK\n    const targets: Record<string, Date> = {\n      ArrowLeft: addDays(focused, -1),\n      ArrowRight: addDays(focused, 1),\n      ArrowUp: addDays(focused, -DAYS_IN_WEEK),\n      ArrowDown: addDays(focused, DAYS_IN_WEEK),\n      Home: addDays(focused, -weekday),\n      End: addDays(focused, DAYS_IN_WEEK - 1 - weekday),\n      PageUp: addMonths(focused, -1),\n      PageDown: addMonths(focused, 1),\n    }\n    const target = targets[event.key]\n    if (!target) return\n    event.preventDefault()\n    moved.current = true\n    setFocused(target)\n  }\n\n  return (\n    <div className={cn(\"w-[17.5rem] max-w-full font-(family-name:--f-body)\", className)}>\n      <div className=\"mb-2 flex items-center justify-between\">\n        <button\n          type=\"button\"\n          aria-label=\"Previous month\"\n          onClick={() => setFocused(addMonths(focused, -1))}\n          className=\"spotcheck-focus cursor-pointer px-2 font-(family-name:--f-mono)\"\n        >\n          ←\n        </button>\n        <span aria-live=\"polite\" className=\"spotcheck-heading text-lg leading-none\">\n          {heading}\n        </span>\n        <button\n          type=\"button\"\n          aria-label=\"Next month\"\n          onClick={() => setFocused(addMonths(focused, 1))}\n          className=\"spotcheck-focus cursor-pointer px-2 font-(family-name:--f-mono)\"\n        >\n          →\n        </button>\n      </div>\n      <div ref={grid} role=\"grid\" aria-label={heading} onKeyDown={onKeyDown}>\n        <div role=\"row\" className=\"grid grid-cols-7\">\n          {week.map((day) => (\n            <span key={day.toDateString()} role=\"columnheader\" className=\"spotcheck-label py-1 text-center\">\n              {dayName.format(day)}\n            </span>\n          ))}\n        </div>\n        {Array.from({ length: DAYS_SHOWN / DAYS_IN_WEEK }, (_, row) => (\n          <div key={row} role=\"row\" className=\"grid grid-cols-7\">\n            {days.slice(row * DAYS_IN_WEEK, (row + 1) * DAYS_IN_WEEK).map((day) => {\n              const selected = sameDay(day, value)\n              const inMonth = day.getMonth() === focused.getMonth()\n              return (\n                <button\n                  key={day.toDateString()}\n                  type=\"button\"\n                  role=\"gridcell\"\n                  aria-selected={selected}\n                  aria-label={fullDate.format(day)}\n                  tabIndex={sameDay(day, focused) ? 0 : -1}\n                  onClick={() => {\n                    setFocused(day)\n                    onValueChange?.(day)\n                  }}\n                  className={cn(\n                    \"spotcheck-focus hover:bg-wash aspect-square cursor-pointer font-(family-name:--f-mono) text-xs tabular-nums\",\n                    !inMonth && \"text-mute\",\n                    selected && \"bg-ink text-paper hover:bg-ink\",\n                  )}\n                >\n                  {day.getDate()}\n                </button>\n              )\n            })}\n          </div>\n        ))}\n      </div>\n    </div>\n  )\n}\n\ntype DatePickerProps = {\n  /** Names the control for screen readers. */\n  label: string\n  /** The selected date, when you control it. */\n  value?: Date | null\n  /** The starting date, when it manages itself. */\n  defaultValue?: Date | null\n  onValueChange?: (date: Date) => void\n  /** The month to open on when nothing is selected. */\n  defaultMonth?: Date\n  placeholder?: string\n  locale?: string\n  className?: string\n}\n\nexport function DatePicker({\n  label,\n  value,\n  defaultValue = null,\n  onValueChange,\n  defaultMonth,\n  placeholder = \"Pick a date\",\n  locale = \"en-GB\",\n  className,\n}: DatePickerProps) {\n  const [internal, setInternal] = React.useState(defaultValue)\n  const [open, setOpen] = React.useState(false)\n  const current = value === undefined ? internal : value\n  const text = current ? new Intl.DateTimeFormat(locale, { dateStyle: \"medium\" }).format(current) : placeholder\n\n  return (\n    <Popover.Root open={open} onOpenChange={setOpen}>\n      <Popover.Trigger\n        aria-label={`${label}: ${text}`}\n        className={cn(\n          \"spotcheck-focus border-ink flex min-w-40 cursor-pointer items-center justify-between gap-3 border-(length:--rule) px-3 py-2 text-left font-(family-name:--f-body) text-sm\",\n          className,\n        )}\n      >\n        <span className=\"tabular-nums\">{text}</span>\n        <span aria-hidden className=\"font-(family-name:--f-mono) text-xs\">\n          ↓\n        </span>\n      </Popover.Trigger>\n      <Popover.Portal>\n        <Popover.Positioner sideOffset={6} align=\"start\" className=\"z-50\">\n          <Popover.Popup className=\"border-ink bg-paper text-ink border-(length:--rule) p-3 font-(family-name:--f-body) shadow-[6px_6px_0_var(--ink)] outline-none\">\n            <Calendar\n              value={current}\n              defaultMonth={defaultMonth}\n              locale={locale}\n              onValueChange={(date) => {\n                setInternal(date)\n                onValueChange?.(date)\n                setOpen(false)\n              }}\n            />\n          </Popover.Popup>\n        </Popover.Positioner>\n      </Popover.Portal>\n    </Popover.Root>\n  )\n}\n",
      "type": "registry:component"
    }
  ],
  "categories": [
    "core"
  ],
  "type": "registry:component"
}