{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "date-picker",
  "title": "Date Picker",
  "description": "A calendar with no date library: single days or ranges, limits, a full keyboard grid and any locale, as a popover, inline, a typed field or two months with a band.",
  "dependencies": [
    "lucide-react",
    "motion"
  ],
  "registryDependencies": [
    "utils",
    "https://miseui.junsu4621.workers.dev/r/motion.json",
    "https://miseui.junsu4621.workers.dev/r/popover.json",
    "https://miseui.junsu4621.workers.dev/r/input.json"
  ],
  "files": [
    {
      "path": "registry/miseui/ui/date-picker.tsx",
      "content": "import type * as React from \"react\"\r\nimport { useState } from \"react\"\r\nimport { CalendarDays } from \"lucide-react\"\r\nimport { Calendar, type CalendarEffect, type CalendarLabels, type CalendarProps } from \"@/registry/miseui/ui/date-picker-calendar\"\r\nimport { dateKey, type DateRange, type WeekStart } from \"@/registry/miseui/ui/date-picker-math\"\r\nimport { CalendarPopoverContent, TypedDateField } from \"@/registry/miseui/ui/date-picker-typed\"\r\nimport { Popover, PopoverTrigger } from \"@/registry/miseui/ui/popover\"\r\nimport { cn } from \"@/registry/miseui/lib/utils\"\r\n\r\ntype DatePickerVariant = \"plain\" | \"inline\" | \"compact\" | \"range\"\r\n\r\ntype TriggerAttributes = Omit<React.ComponentProps<\"button\">, \"value\" | \"defaultValue\" | \"onChange\" | \"children\">\r\n\r\ninterface DatePickerBaseProps extends TriggerAttributes {\r\n  /** `slide` moves months along a shared axis on a spring, and a quick second press interrupts it. */\r\n  effect?: CalendarEffect\r\n  min?: Date\r\n  max?: Date\r\n  isDateDisabled?: (date: Date) => boolean\r\n  locale?: string\r\n  weekStartsOn?: WeekStart\r\n  defaultMonth?: Date\r\n  today?: Date\r\n  labels?: Partial<CalendarLabels>\r\n  placeholder?: string\r\n  /** Posts YYYY-MM-DD with the surrounding form. `range` posts `name-start` and `name-end`. */\r\n  name?: string\r\n}\r\n\r\ntype DatePickerSingleProps = DatePickerBaseProps & {\r\n  /** `plain` button and popover, `inline` always open, `compact` typed date with a popover, `range` two months and a band. */\r\n  variant?: \"plain\" | \"inline\" | \"compact\"\r\n  value?: Date | null\r\n  defaultValue?: Date | null\r\n  onValueChange?: (value: Date | null) => void\r\n}\r\n\r\ntype DatePickerRangeProps = DatePickerBaseProps & {\r\n  variant: \"range\"\r\n  value?: DateRange | null\r\n  defaultValue?: DateRange | null\r\n  onValueChange?: (value: DateRange | null) => void\r\n}\r\n\r\ntype DatePickerProps = DatePickerSingleProps | DatePickerRangeProps\r\n\r\nconst TRIGGER_CLASS = cn(\r\n  \"flex h-9 w-full items-center gap-2 rounded-md border border-input bg-transparent px-3 text-left text-sm shadow-xs outline-none pointer-coarse:h-10\",\r\n  \"transition-[color,background-color,border-color,box-shadow] duration-(--duration-fast) ease-(--ease-mise) hover:bg-accent\",\r\n  \"focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 data-[state=open]:border-ring\",\r\n  \"aria-invalid:border-destructive disabled:pointer-events-none disabled:opacity-50\",\r\n)\r\n\r\n/** Everything else on the props is a button attribute and goes to the trigger, which is what a Field needs. */\r\nconst OWN_PROPS: ReadonlySet<string> = new Set([\r\n  \"effect\", \"min\", \"max\", \"isDateDisabled\", \"locale\", \"weekStartsOn\", \"defaultMonth\", \"today\", \"labels\", \"placeholder\",\r\n  \"name\", \"variant\", \"value\", \"defaultValue\", \"onValueChange\", \"className\",\r\n])\r\n\r\nfunction triggerAttributes(props: DatePickerProps) {\r\n  return Object.fromEntries(Object.entries(props).filter(([key]) => !OWN_PROPS.has(key))) as TriggerAttributes\r\n}\r\n\r\nfunction formatTrigger(value: Date | DateRange | null, locale?: string) {\r\n  if (!value) return null\r\n  const format = new Intl.DateTimeFormat(locale, { dateStyle: \"medium\" })\r\n  if (value instanceof Date) return format.format(value)\r\n  return value.end ? format.formatRange(value.start, value.end) : `${format.format(value.start)} …`\r\n}\r\n\r\nfunction HiddenInputs({ name, value }: { name?: string; value: Date | DateRange | null }) {\r\n  if (!name) return null\r\n  if (value instanceof Date) return <input type=\"hidden\" name={name} value={dateKey(value)} />\r\n  return (\r\n    <>\r\n      <input type=\"hidden\" name={`${name}-start`} value={value ? dateKey(value.start) : \"\"} />\r\n      <input type=\"hidden\" name={`${name}-end`} value={value?.end ? dateKey(value.end) : \"\"} />\r\n    </>\r\n  )\r\n}\r\n\r\nfunction useDatePickerValue(props: DatePickerProps) {\r\n  const [uncontrolled, setUncontrolled] = useState<Date | DateRange | null>(props.defaultValue ?? null)\r\n  const current = props.value !== undefined ? props.value : uncontrolled\r\n  const change = (next: Date | DateRange | null) => {\r\n    setUncontrolled(next)\r\n    ;(props.onValueChange as ((value: Date | DateRange | null) => void) | undefined)?.(next)\r\n  }\r\n  return [current, change] as const\r\n}\r\n\r\ninterface PopoverPickerProps {\r\n  props: DatePickerProps\r\n  isRange: boolean\r\n  current: Date | DateRange | null\r\n  change: (next: Date | DateRange | null) => void\r\n  calendarProps: Omit<CalendarProps, \"mode\" | \"value\" | \"defaultValue\" | \"onValueChange\">\r\n}\r\n\r\nfunction PopoverPicker({ props, isRange, current, change, calendarProps }: PopoverPickerProps) {\r\n  const [open, setOpen] = useState(false)\r\n  const text = formatTrigger(current, props.locale)\r\n  const placeholder = props.placeholder ?? (isRange ? \"Pick dates\" : \"Pick a date\")\r\n  const onPick = (next: Date | DateRange) => {\r\n    change(next)\r\n    // A range is not finished until its second click.\r\n    if (next instanceof Date || next.end) setOpen(false)\r\n  }\r\n  return (\r\n    <Popover open={open} onOpenChange={setOpen}>\r\n      <PopoverTrigger className={TRIGGER_CLASS} {...triggerAttributes(props)}>\r\n        <CalendarDays aria-hidden=\"true\" className=\"size-4 shrink-0 text-muted-foreground\" />\r\n        <span className={cn(\"min-w-0 truncate tabular-nums\", !text && \"text-muted-foreground\")}>{text ?? placeholder}</span>\r\n      </PopoverTrigger>\r\n      <CalendarPopoverContent align=\"start\">\r\n        {isRange ? (\r\n          <Calendar {...calendarProps} mode=\"range\" months={2} value={current as DateRange | null} onValueChange={onPick} />\r\n        ) : (\r\n          <Calendar {...calendarProps} value={current as Date | null} onValueChange={onPick} />\r\n        )}\r\n      </CalendarPopoverContent>\r\n    </Popover>\r\n  )\r\n}\r\n\r\nfunction DatePicker(props: DatePickerProps) {\r\n  const { variant = \"plain\", effect, min, max, isDateDisabled, locale, weekStartsOn, defaultMonth, today, labels, name, className } = props\r\n  const [current, change] = useDatePickerValue(props)\r\n  const calendarProps = { effect, min, max, isDateDisabled, locale, weekStartsOn, defaultMonth, today, labels }\r\n  return (\r\n    <div data-slot=\"date-picker\" data-variant={variant} data-effect={effect} className={cn(\"w-full min-w-0\", className)}>\r\n      <HiddenInputs name={name} value={current} />\r\n      {variant === \"inline\" ? (\r\n        <div className=\"w-fit max-w-full rounded-lg border bg-card p-3\">\r\n          <Calendar {...calendarProps} value={current as Date | null} onValueChange={change} />\r\n        </div>\r\n      ) : null}\r\n      {variant === \"compact\" ? (\r\n        <TypedDateField\r\n          {...(triggerAttributes(props) as React.ComponentProps<\"input\">)}\r\n          placeholder={props.placeholder}\r\n          value={current as Date | null}\r\n          onCommit={change}\r\n          calendarProps={calendarProps}\r\n        />\r\n      ) : null}\r\n      {variant === \"plain\" || variant === \"range\" ? (\r\n        <PopoverPicker props={props} isRange={variant === \"range\"} current={current} change={change} calendarProps={calendarProps} />\r\n      ) : null}\r\n    </div>\r\n  )\r\n}\r\n\r\nexport {\r\n  Calendar,\r\n  DatePicker,\r\n  type CalendarProps,\r\n  type CalendarEffect,\r\n  type DateRange,\r\n  type DatePickerProps,\r\n  type DatePickerVariant,\r\n  type WeekStart,\r\n}\r\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/miseui/ui/date-picker-calendar.tsx",
      "content": "import type * as React from \"react\"\nimport { useMemo, useRef, useSyncExternalStore } from \"react\"\nimport { ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight } from \"lucide-react\"\nimport { AnimatePresence, motion, useReducedMotion, type Variants } from \"motion/react\"\nimport { SPRING_DEFAULT } from \"@/registry/miseui/lib/motion\"\nimport { addMonths, firstDayOfWeek, monthKey, type DateRange, type WeekStart } from \"@/registry/miseui/ui/date-picker-math\"\nimport { CalendarMonth } from \"@/registry/miseui/ui/date-picker-month\"\nimport { useCalendarState, type CalendarState } from \"@/registry/miseui/ui/date-picker-state\"\nimport { cn } from \"@/registry/miseui/lib/utils\"\n\ntype CalendarEffect = \"slide\"\n\nconst TWO_MONTH_QUERY = \"(min-width: 640px)\"\nconst SLIDE_DISTANCE_PX = 16\nconst MONTHS_IN_YEAR = 12\n/** A phone has no room for four 40 px buttons and a month name, and a year is rarely one press away there anyway. */\nconst YEAR_BUTTON_CLASS = \"max-sm:pointer-coarse:hidden\"\nconst NAV_BUTTON_CLASS = cn(\n  \"flex size-8 items-center justify-center rounded-md text-muted-foreground outline-none pointer-coarse:size-10\",\n  \"transition-colors duration-(--duration-fast) ease-(--ease-mise) hover:bg-accent hover:text-foreground\",\n  \"focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-disabled:pointer-events-none aria-disabled:opacity-40 [&_svg]:size-4\",\n)\n\n/** Shared-axis: the outgoing month leaves the way the new one comes from, so forward always reads as forward. */\nconst SLIDE_VARIANTS: Variants = {\n  enter: (direction: number) => ({ x: direction * SLIDE_DISTANCE_PX, opacity: 0 }),\n  centre: { x: 0, opacity: 1 },\n  exit: (direction: number) => ({ x: -direction * SLIDE_DISTANCE_PX, opacity: 0 }),\n}\n\ninterface CalendarLabels {\n  previousMonth: string\n  nextMonth: string\n  previousYear: string\n  nextYear: string\n}\n\nconst DEFAULT_LABELS: CalendarLabels = {\n  previousMonth: \"Previous month\",\n  nextMonth: \"Next month\",\n  previousYear: \"Previous year\",\n  nextYear: \"Next year\",\n}\n\ninterface CalendarBaseProps {\n  min?: Date\n  max?: Date\n  /** Greys out days the calendar cannot offer, such as weekends. */\n  isDateDisabled?: (date: Date) => boolean\n  /** BCP 47 tag. Names, numbers and the first day of the week all follow it. */\n  locale?: string\n  /** Overrides the locale's first day of the week. 0 is Sunday. */\n  weekStartsOn?: WeekStart\n  /** Two months side by side, from 640 px wide. A phone keeps one, so nothing has to scroll sideways. */\n  months?: 1 | 2\n  /** `slide` moves months along a shared axis on a spring, and a quick second press interrupts it. */\n  effect?: CalendarEffect\n  /** The month shown first when nothing is selected. */\n  defaultMonth?: Date\n  month?: Date\n  onMonthChange?: (month: Date) => void\n  /** Stands in for the clock, for tests and for a server that decides what today is. */\n  today?: Date\n  labels?: Partial<CalendarLabels>\n  className?: string\n}\n\ntype CalendarProps = CalendarBaseProps &\n  (\n    | { mode?: \"single\"; value?: Date | null; defaultValue?: Date | null; onValueChange?: (value: Date) => void }\n    | { mode: \"range\"; value?: DateRange | null; defaultValue?: DateRange | null; onValueChange?: (value: DateRange) => void }\n  )\n\nfunction subscribeToWidth(notify: () => void) {\n  const query = window.matchMedia(TWO_MONTH_QUERY)\n  query.addEventListener(\"change\", notify)\n  return () => query.removeEventListener(\"change\", notify)\n}\n\n/** Worked out in JS and not hidden with CSS, because arrow keys must never walk into a month that is not on screen. */\nfunction useMonthsShown(months: number) {\n  return useSyncExternalStore(\n    subscribeToWidth,\n    () => (months > 1 && window.matchMedia(TWO_MONTH_QUERY).matches ? months : 1),\n    () => 1,\n  )\n}\n\ninterface NavButtonProps {\n  label: string\n  amount: number\n  state: CalendarState\n  className?: string\n  children: React.ReactNode\n}\n\n/** aria-disabled, not disabled: a focused button that went dead would drop the keyboard user back at the top of the page. */\nfunction NavButton({ label, amount, state, className, children }: NavButtonProps) {\n  return (\n    <button\n      type=\"button\"\n      aria-label={label}\n      aria-disabled={!state.canStep(amount) || undefined}\n      className={cn(NAV_BUTTON_CLASS, className)}\n      onClick={() => state.stepMonths(amount)}\n    >\n      {children}\n    </button>\n  )\n}\n\nfunction CalendarNav({ state, labels }: { state: CalendarState; labels: CalendarLabels }) {\n  return (\n    <div className=\"pointer-events-none absolute inset-x-0 top-0 z-10 flex justify-between\">\n      <div className=\"pointer-events-auto flex\">\n        <NavButton label={labels.previousYear} amount={-MONTHS_IN_YEAR} state={state} className={YEAR_BUTTON_CLASS}>\n          <ChevronsLeft aria-hidden=\"true\" />\n        </NavButton>\n        <NavButton label={labels.previousMonth} amount={-1} state={state}>\n          <ChevronLeft aria-hidden=\"true\" />\n        </NavButton>\n      </div>\n      <div className=\"pointer-events-auto flex\">\n        <NavButton label={labels.nextMonth} amount={1} state={state}>\n          <ChevronRight aria-hidden=\"true\" />\n        </NavButton>\n        <NavButton label={labels.nextYear} amount={MONTHS_IN_YEAR} state={state} className={YEAR_BUTTON_CLASS}>\n          <ChevronsRight aria-hidden=\"true\" />\n        </NavButton>\n      </div>\n    </div>\n  )\n}\n\ninterface CalendarPanelProps {\n  state: CalendarState\n  monthsShown: number\n  locale?: string\n  weekStartsOn: WeekStart\n  slide: boolean\n}\n\nfunction CalendarPanel({ state, monthsShown, locale, weekStartsOn, slide }: CalendarPanelProps) {\n  const months = (\n    <div className=\"flex items-start gap-6\">\n      {Array.from({ length: monthsShown }, (_, index) => {\n        const month = addMonths(state.view, index)\n        return (\n          <CalendarMonth key={monthKey(month)} month={month} state={state} locale={locale} weekStartsOn={weekStartsOn} showOutside={monthsShown === 1} />\n        )\n      })}\n    </div>\n  )\n  if (!slide) return months\n  return (\n    <div className=\"relative overflow-hidden\">\n      <AnimatePresence initial={false} mode=\"popLayout\" custom={state.direction}>\n        <motion.div\n          key={monthKey(state.view)}\n          custom={state.direction}\n          variants={SLIDE_VARIANTS}\n          initial=\"enter\"\n          animate=\"centre\"\n          exit=\"exit\"\n          transition={SPRING_DEFAULT}\n        >\n          {months}\n        </motion.div>\n      </AnimatePresence>\n    </div>\n  )\n}\n\nfunction Calendar(props: CalendarProps) {\n  const { effect, locale, labels, months = 1, className } = props\n  const rootRef = useRef<HTMLDivElement>(null)\n  const reduceMotion = useReducedMotion() ?? false\n  const monthsShown = useMonthsShown(months)\n  const weekStartsOn = useMemo(() => props.weekStartsOn ?? firstDayOfWeek(locale), [props.weekStartsOn, locale])\n  const state = useCalendarState({ ...props, mode: props.mode ?? \"single\", monthsShown, weekStartsOn }, rootRef)\n  const announce = useMemo(() => new Intl.DateTimeFormat(locale, { month: \"long\", year: \"numeric\" }).format(state.view), [locale, state.view])\n  return (\n    <div ref={rootRef} data-slot=\"calendar\" data-effect={effect} data-months={monthsShown} className={cn(\"relative w-fit max-w-full\", className)}>\n      <span className=\"sr-only\" aria-live=\"polite\">\n        {announce}\n      </span>\n      <CalendarNav state={state} labels={{ ...DEFAULT_LABELS, ...labels }} />\n      <CalendarPanel state={state} monthsShown={monthsShown} locale={locale} weekStartsOn={weekStartsOn} slide={effect === \"slide\" && !reduceMotion} />\n    </div>\n  )\n}\n\nexport { Calendar, type CalendarProps, type CalendarEffect, type CalendarLabels }\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/miseui/ui/date-picker-month.tsx",
      "content": "import { useId, useMemo } from \"react\"\nimport {\n  buildMonthGrid,\n  dateKey,\n  isSameDay,\n  isSameMonth,\n  monthKey,\n  rangeRole,\n  weekdayLabels,\n  type RangeRole,\n  type WeekStart,\n} from \"@/registry/miseui/ui/date-picker-math\"\nimport type { CalendarState } from \"@/registry/miseui/ui/date-picker-state\"\nimport { cn } from \"@/registry/miseui/lib/utils\"\n\n/** Columns share the width, so a parent narrower than seven full cells shrinks the days and never clips Saturday. */\nconst CELL_SIZE_CLASS = \"aspect-square w-full\"\n\n/** The band sits on the cell and the day on top of it, so a band runs unbroken across a row and rounds at its ends. */\nconst BAND_CLASS: Record<RangeRole, string> = {\n  single: \"\",\n  start: \"rounded-l-md bg-mise-soft last:rounded-r-md\",\n  end: \"rounded-r-md bg-mise-soft first:rounded-l-md\",\n  middle: \"bg-mise-soft first:rounded-l-md last:rounded-r-md\",\n}\n\ninterface DayCellProps {\n  day: Date\n  month: Date\n  state: CalendarState\n  showOutside: boolean\n  dayFormat: Intl.DateTimeFormat\n}\n\nfunction DayCell({ day, month, state, showOutside, dayFormat }: DayCellProps) {\n  const outside = !isSameMonth(day, month)\n  if (outside && !showOutside) return (\n      <td role=\"gridcell\" aria-hidden=\"true\" className=\"p-0\">\n        <span className={cn(\"block\", CELL_SIZE_CLASS)} />\n      </td>\n    )\n  const disabled = state.isDisabled(day)\n  const role = rangeRole(day, state.range, state.preview)\n  const endpoint = role === \"start\" || role === \"end\" || role === \"single\"\n  const selected = isSameDay(day, state.selected) || endpoint\n  const today = isSameDay(day, state.today)\n  return (\n    <td role=\"gridcell\" aria-selected={selected || role === \"middle\"} data-range={role ?? undefined} className={cn(\"p-0\", role && BAND_CLASS[role])}>\n      <button\n        type=\"button\"\n        data-date={dateKey(day)}\n        data-outside={outside ? \"true\" : undefined}\n        tabIndex={!outside && isSameDay(day, state.tabDay) ? 0 : -1}\n        aria-label={dayFormat.format(day)}\n        aria-current={today ? \"date\" : undefined}\n        aria-disabled={disabled || undefined}\n        className={cn(\n          \"flex items-center justify-center rounded-md text-sm tabular-nums outline-none\",\n          \"transition-colors duration-(--duration-fast) ease-(--ease-mise)\",\n          \"focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:ring-inset\",\n          CELL_SIZE_CLASS,\n          selected ? \"bg-mise font-medium text-mise-foreground hover:bg-mise/90\" : \"hover:bg-accent\",\n          !selected && today && \"font-medium ring-1 ring-input ring-inset\",\n          !selected && outside && \"text-muted-foreground\",\n          disabled && \"cursor-not-allowed text-muted-foreground/50 hover:bg-transparent\",\n        )}\n        onClick={() => state.select(day)}\n        onMouseEnter={() => state.previewDay(day)}\n        onFocus={() => state.previewDay(day)}\n      >\n        {day.getDate()}\n      </button>\n    </td>\n  )\n}\n\ninterface CalendarMonthProps {\n  month: Date\n  state: CalendarState\n  locale?: string\n  weekStartsOn: WeekStart\n  /** Off with two months side by side, where the same day would otherwise appear twice. */\n  showOutside: boolean\n}\n\nfunction CalendarMonth({ month, state, locale, weekStartsOn, showOutside }: CalendarMonthProps) {\n  const titleId = useId()\n  const weeks = useMemo(() => buildMonthGrid(month, weekStartsOn), [month, weekStartsOn])\n  const weekdays = useMemo(() => weekdayLabels(locale, weekStartsOn), [locale, weekStartsOn])\n  const dayFormat = useMemo(() => new Intl.DateTimeFormat(locale, { dateStyle: \"full\" }), [locale])\n  const title = useMemo(() => new Intl.DateTimeFormat(locale, { month: \"long\", year: \"numeric\" }).format(month), [locale, month])\n  return (\n    <div data-slot=\"calendar-month\" data-month={monthKey(month)} className=\"grid w-63 max-w-full min-w-0 gap-1 pointer-coarse:w-70\">\n      {/* The side padding keeps a long month name clear of the buttons laid over this row. */}\n      <div id={titleId} className=\"truncate px-16 text-center text-sm leading-8 font-medium max-sm:pointer-coarse:px-11 sm:pointer-coarse:px-20 pointer-coarse:leading-10\">\n        {title}\n      </div>\n      <table role=\"grid\" aria-labelledby={titleId} className=\"w-full table-fixed border-separate border-spacing-0\" onKeyDown={state.onGridKeyDown}>\n        <thead>\n          <tr>\n            {weekdays.map((weekday) => (\n              <th key={weekday.long} scope=\"col\" abbr={weekday.long} className=\"h-8 p-0 text-[13px] font-normal text-muted-foreground\">\n                {weekday.short}\n              </th>\n            ))}\n          </tr>\n        </thead>\n        <tbody onMouseLeave={() => state.previewDay(null)}>\n          {weeks.map((week) => (\n            <tr key={dateKey(week[0])}>\n              {week.map((day) => (\n                <DayCell key={dateKey(day)} day={day} month={month} state={state} showOutside={showOutside} dayFormat={dayFormat} />\n              ))}\n            </tr>\n          ))}\n        </tbody>\n      </table>\n    </div>\n  )\n}\n\nexport { CalendarMonth }\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/miseui/ui/date-picker-typed.tsx",
      "content": "import type * as React from \"react\"\r\nimport { useRef, useState } from \"react\"\r\nimport { CalendarDays } from \"lucide-react\"\r\nimport { Calendar, type CalendarProps } from \"@/registry/miseui/ui/date-picker-calendar\"\r\nimport { datePattern, numericFormat, parseDateText } from \"@/registry/miseui/ui/date-picker-math\"\r\nimport { Input } from \"@/registry/miseui/ui/input\"\r\nimport { Popover, PopoverAnchor, PopoverContent, PopoverTrigger } from \"@/registry/miseui/ui/popover\"\r\n\r\ntype CalendarOptions = Omit<CalendarProps, \"mode\" | \"value\" | \"defaultValue\" | \"onValueChange\">\r\n\r\ntype TypedDateFieldProps = Omit<React.ComponentProps<\"input\">, \"value\" | \"onChange\" | \"min\" | \"max\" | \"defaultValue\"> & {\r\n  value: Date | null\r\n  onCommit: (value: Date | null) => void\r\n  calendarProps: CalendarOptions\r\n  openLabel?: string\r\n}\r\n\r\n/** Keeps wrong text on screen and flagged, because replacing it would throw away what someone is halfway through fixing. */\r\nfunction useTypedDate(value: Date | null, onCommit: (value: Date | null) => void, { locale, min, max, isDateDisabled }: CalendarOptions) {\r\n  const [draft, setDraft] = useState<string | null>(null)\r\n  const parse = (text: string) => {\r\n    const parsed = parseDateText(text, locale)\r\n    if (!parsed || (min && parsed < min) || (max && parsed > max) || isDateDisabled?.(parsed)) return null\r\n    return parsed\r\n  }\r\n  const invalid = draft !== null && draft.trim() !== \"\" && parse(draft) === null\r\n  const commit = () => {\r\n    if (draft === null || invalid) return\r\n    onCommit(draft.trim() === \"\" ? null : parse(draft))\r\n    setDraft(null)\r\n  }\r\n  return { text: draft ?? (value ? numericFormat(locale).format(value) : \"\"), draft, setDraft, invalid, commit }\r\n}\r\n\r\n/** Radix would land on the first button, a year back. The day is where the keyboard wants to be. */\r\nfunction CalendarPopoverContent(props: React.ComponentProps<typeof PopoverContent>) {\r\n  const contentRef = useRef<HTMLDivElement>(null)\r\n  return (\r\n    <PopoverContent\r\n      ref={contentRef}\r\n      className=\"w-auto max-w-(--radix-popover-content-available-width) p-3\"\r\n      onOpenAutoFocus={(event) => {\r\n        event.preventDefault()\r\n        contentRef.current?.querySelector<HTMLElement>(\"button[data-date][tabindex='0']\")?.focus()\r\n      }}\r\n      {...props}\r\n    />\r\n  )\r\n}\r\n\r\nfunction OpenCalendarButton({ label, disabled }: { label: string; disabled?: boolean }) {\r\n  return (\r\n    <PopoverTrigger\r\n      aria-label={label}\r\n      disabled={disabled}\r\n      className=\"absolute inset-y-0 right-0 flex w-9 items-center justify-center rounded-r-md text-muted-foreground outline-none hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none pointer-coarse:w-10\"\r\n    >\r\n      <CalendarDays aria-hidden=\"true\" className=\"size-4\" />\r\n    </PopoverTrigger>\r\n  )\r\n}\r\n\r\nfunction TypedDateField({ value, onCommit, calendarProps, openLabel = \"Open calendar\", disabled, placeholder, onBlur, onKeyDown, ...inputProps }: TypedDateFieldProps) {\r\n  const typed = useTypedDate(value, onCommit, calendarProps)\r\n  const [open, setOpen] = useState(false)\r\n  return (\r\n    <Popover open={open} onOpenChange={setOpen}>\r\n      <PopoverAnchor asChild>\r\n        <div className=\"relative w-full min-w-0\">\r\n          <Input\r\n            {...inputProps}\r\n            aria-invalid={typed.invalid || inputProps[\"aria-invalid\"] || undefined}\r\n            autoComplete=\"off\"\r\n            inputMode=\"numeric\"\r\n            data-slot=\"date-picker-input\"\r\n            disabled={disabled}\r\n            placeholder={placeholder ?? datePattern(calendarProps.locale)}\r\n            value={typed.text}\r\n            className=\"pr-10 tabular-nums pointer-coarse:h-10 pointer-coarse:pr-12\"\r\n            onChange={(event) => typed.setDraft(event.target.value)}\r\n            onBlur={(event) => {\r\n              typed.commit()\r\n              onBlur?.(event)\r\n            }}\r\n            onKeyDown={(event) => {\r\n              if (event.key === \"Enter\") typed.commit()\r\n              if (event.key === \"Escape\" && typed.draft !== null) typed.setDraft(null)\r\n              onKeyDown?.(event)\r\n            }}\r\n          />\r\n          <OpenCalendarButton label={openLabel} disabled={disabled} />\r\n        </div>\r\n      </PopoverAnchor>\r\n      <CalendarPopoverContent align=\"end\">\r\n        <Calendar\r\n          {...calendarProps}\r\n          value={value}\r\n          onValueChange={(next) => {\r\n            typed.setDraft(null)\r\n            onCommit(next)\r\n            setOpen(false)\r\n          }}\r\n        />\r\n      </CalendarPopoverContent>\r\n    </Popover>\r\n  )\r\n}\r\n\r\nexport { CalendarPopoverContent, TypedDateField }\r\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/miseui/ui/date-picker-state.ts",
      "content": "import type * as React from \"react\"\r\nimport { useEffect, useRef, useState } from \"react\"\r\nimport {\r\n  addMonths,\r\n  clampDay,\r\n  dateKey,\r\n  isSameDay,\r\n  isSameMonth,\r\n  nextFocusDate,\r\n  nextRange,\r\n  startOfDay,\r\n  startOfMonth,\r\n  type DateRange,\r\n  type WeekStart,\r\n} from \"@/registry/miseui/ui/date-picker-math\"\r\n\r\ntype CalendarValue = Date | DateRange | null\r\n\r\ninterface CalendarStateOptions {\r\n  mode: \"single\" | \"range\"\r\n  value?: CalendarValue\r\n  defaultValue?: CalendarValue\r\n  onValueChange?: (value: never) => void\r\n  month?: Date\r\n  defaultMonth?: Date\r\n  onMonthChange?: (month: Date) => void\r\n  min?: Date\r\n  max?: Date\r\n  isDateDisabled?: (date: Date) => boolean\r\n  today?: Date\r\n  monthsShown: number\r\n  weekStartsOn: WeekStart\r\n}\r\n\r\nfunction anchorOf(value: CalendarValue) {\r\n  if (!value) return null\r\n  return value instanceof Date ? value : value.start\r\n}\r\n\r\nfunction useCalendarView(options: CalendarStateOptions, start: Date) {\r\n  const { min, max, monthsShown, onMonthChange } = options\r\n  const [uncontrolled, setUncontrolled] = useState(() => startOfMonth(clampDay(start, min, max)))\r\n  const [direction, setDirection] = useState<1 | -1>(1)\r\n  const view = options.month ? startOfMonth(options.month) : uncontrolled\r\n  const setView = (next: Date) => {\r\n    const first = startOfMonth(next)\r\n    if (isSameMonth(first, view)) return\r\n    setDirection(first > view ? 1 : -1)\r\n    setUncontrolled(first)\r\n    onMonthChange?.(first)\r\n  }\r\n  const canStep = (amount: number) => {\r\n    const target = addMonths(view, amount)\r\n    if (amount < 0) return !min || target >= startOfMonth(min)\r\n    return !max || startOfMonth(addMonths(target, monthsShown - 1)) <= max\r\n  }\r\n  return { view, direction, lastVisible: addMonths(view, monthsShown - 1), setView, canStep }\r\n}\r\n\r\ntype CalendarView = ReturnType<typeof useCalendarView>\r\n\r\n/** The grid has one tab stop. It moves with the arrow keys, and the view follows it so it is never off screen. */\r\nfunction useCalendarFocus(options: CalendarStateOptions, calendar: CalendarView, start: Date, rootRef: React.RefObject<HTMLElement | null>) {\r\n  const { min, max, weekStartsOn, monthsShown } = options\r\n  const { view, lastVisible, setView, canStep } = calendar\r\n  const [focused, setFocused] = useState(() => clampDay(start, min, max))\r\n  // The owner can move the month under us, so the tab stop falls back to a day that is really on screen.\r\n  const tabDay = focused >= view && startOfMonth(focused) <= lastVisible ? focused : clampDay(view, min, max)\r\n  const focusRequested = useRef(false)\r\n\r\n  useEffect(() => {\r\n    if (!focusRequested.current) return\r\n    focusRequested.current = false\r\n    const matches = rootRef.current?.querySelectorAll<HTMLElement>(`button[data-date=\"${dateKey(focused)}\"]:not([data-outside])`)\r\n    // A month still sliding out can hold the same date, and the arriving one is always rendered last.\r\n    matches?.[matches.length - 1]?.focus()\r\n  }, [focused, rootRef])\r\n\r\n  const moveFocus = (target: Date) => {\r\n    const next = clampDay(target, min, max)\r\n    if (isSameDay(next, tabDay)) return\r\n    focusRequested.current = true\r\n    setFocused(next)\r\n    if (next < view) setView(next)\r\n    else if (startOfMonth(next) > lastVisible) setView(addMonths(next, 1 - monthsShown))\r\n  }\r\n  const stepMonths = (amount: number) => {\r\n    if (!canStep(amount)) return\r\n    setView(addMonths(view, amount))\r\n    setFocused(clampDay(addMonths(tabDay, amount), min, max))\r\n  }\r\n  const onGridKeyDown = (event: React.KeyboardEvent) => {\r\n    const target = nextFocusDate(event.key, tabDay, event.shiftKey, weekStartsOn)\r\n    if (!target) return\r\n    event.preventDefault()\r\n    moveFocus(target)\r\n  }\r\n  return { tabDay, setFocused, stepMonths, onGridKeyDown }\r\n}\r\n\r\nfunction useCalendarState(options: CalendarStateOptions, rootRef: React.RefObject<HTMLElement | null>) {\r\n  const { mode, value, min, max, isDateDisabled } = options\r\n  const [today] = useState(() => startOfDay(options.today ?? new Date()))\r\n  const [uncontrolled, setUncontrolled] = useState<CalendarValue>(options.defaultValue ?? null)\r\n  const [preview, setPreview] = useState<Date | null>(null)\r\n  const current = value !== undefined ? value : uncontrolled\r\n  const anchor = anchorOf(current)\r\n  const calendar = useCalendarView(options, anchor ?? options.defaultMonth ?? today)\r\n  const focus = useCalendarFocus(options, calendar, anchor ?? (isSameMonth(today, calendar.view) ? today : calendar.view), rootRef)\r\n  const range = mode === \"range\" ? (current as DateRange | null) : null\r\n  const isDisabled = (day: Date) => Boolean((min && day < min) || (max && day > max) || isDateDisabled?.(day))\r\n  const select = (day: Date) => {\r\n    if (isDisabled(day)) return\r\n    focus.setFocused(day)\r\n    if (day < calendar.view || startOfMonth(day) > calendar.lastVisible) calendar.setView(day)\r\n    const next = mode === \"range\" ? nextRange(range, day) : day\r\n    setUncontrolled(next)\r\n    setPreview(null)\r\n    ;(options.onValueChange as ((value: CalendarValue) => void) | undefined)?.(next)\r\n  }\r\n  return {\r\n    today,\r\n    view: calendar.view,\r\n    direction: calendar.direction,\r\n    canStep: calendar.canStep,\r\n    tabDay: focus.tabDay,\r\n    stepMonths: focus.stepMonths,\r\n    onGridKeyDown: focus.onGridKeyDown,\r\n    range,\r\n    selected: mode === \"single\" ? (current as Date | null) : null,\r\n    preview: range && !range.end ? preview : null,\r\n    /** Skips the state update once a range is done, so hovering a finished calendar re-renders nothing. */\r\n    previewDay: (day: Date | null) => {\r\n      if (range && !range.end) setPreview(day)\r\n    },\r\n    isDisabled,\r\n    select,\r\n  }\r\n}\r\n\r\ntype CalendarState = ReturnType<typeof useCalendarState>\r\n\r\nexport { useCalendarState, type CalendarState, type CalendarStateOptions, type CalendarValue }\r\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/miseui/ui/date-picker-math.ts",
      "content": "/** Every date here is local midnight. `toISOString` is avoided throughout because it shifts the day in any zone east of UTC. */\nexport interface DateRange {\n  start: Date\n  /** Null while the second click is still to come. */\n  end: Date | null\n}\n\nexport type WeekStart = 0 | 1 | 2 | 3 | 4 | 5 | 6\nexport type RangeRole = \"single\" | \"start\" | \"end\" | \"middle\"\n\nconst WEEK_ROWS = 6\nconst DAYS_IN_WEEK = 7\nconst SUNDAY_FIRST_REGIONS = new Set([\"US\", \"CA\", \"MX\", \"JP\", \"KR\", \"TW\", \"IL\", \"BR\", \"ZA\", \"PH\", \"IN\", \"AU\", \"HK\", \"SG\", \"TH\"])\n\nfunction startOfDay(date: Date) {\n  return new Date(date.getFullYear(), date.getMonth(), date.getDate())\n}\n\nfunction startOfMonth(date: Date) {\n  return new Date(date.getFullYear(), date.getMonth(), 1)\n}\n\n/** Built from parts and not by adding 24 hours, which breaks on the two days a year with daylight saving changes. */\nfunction addDays(date: Date, amount: number) {\n  return new Date(date.getFullYear(), date.getMonth(), date.getDate() + amount)\n}\n\n/** Jan 31 plus a month is Feb 28, not Mar 3. */\nfunction addMonths(date: Date, amount: number) {\n  const first = new Date(date.getFullYear(), date.getMonth() + amount, 1)\n  const lastDay = new Date(first.getFullYear(), first.getMonth() + 1, 0).getDate()\n  return new Date(first.getFullYear(), first.getMonth(), Math.min(date.getDate(), lastDay))\n}\n\nfunction isSameDay(a: Date | null | undefined, b: Date | null | undefined) {\n  return Boolean(a && b) && a?.getTime() === b?.getTime()\n}\n\nfunction isSameMonth(a: Date, b: Date) {\n  return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth()\n}\n\nfunction pad(value: number) {\n  return String(value).padStart(2, \"0\")\n}\n\nfunction monthKey(date: Date) {\n  return `${date.getFullYear()}-${pad(date.getMonth() + 1)}`\n}\n\nfunction dateKey(date: Date) {\n  return `${monthKey(date)}-${pad(date.getDate())}`\n}\n\nfunction clampDay(date: Date, min?: Date, max?: Date) {\n  if (min && date < min) return min\n  if (max && date > max) return max\n  return date\n}\n\n/** Always six weeks, so the calendar keeps one height and the page does not jump between months. */\nfunction buildMonthGrid(month: Date, weekStartsOn: WeekStart) {\n  const first = startOfMonth(month)\n  const lead = (first.getDay() - weekStartsOn + DAYS_IN_WEEK) % DAYS_IN_WEEK\n  const start = addDays(first, -lead)\n  return Array.from({ length: WEEK_ROWS }, (_, week) =>\n    Array.from({ length: DAYS_IN_WEEK }, (_, day) => addDays(start, week * DAYS_IN_WEEK + day)),\n  )\n}\n\n/** weekInfo where the browser has it, a short list of regions where it does not (Firefox). */\nfunction firstDayOfWeek(locale?: string): WeekStart {\n  try {\n    const tag = locale ?? new Intl.DateTimeFormat().resolvedOptions().locale\n    const info = new Intl.Locale(tag) as Intl.Locale & {\n      getWeekInfo?: () => { firstDay: number }\n      weekInfo?: { firstDay: number }\n    }\n    const firstDay = info.getWeekInfo?.().firstDay ?? info.weekInfo?.firstDay\n    if (firstDay) return (firstDay % DAYS_IN_WEEK) as WeekStart\n    return SUNDAY_FIRST_REGIONS.has(info.maximize().region ?? \"\") ? 0 : 1\n  } catch {\n    return 0\n  }\n}\n\nfunction weekdayLabels(locale: string | undefined, weekStartsOn: WeekStart) {\n  const short = new Intl.DateTimeFormat(locale, { weekday: \"short\" })\n  const long = new Intl.DateTimeFormat(locale, { weekday: \"long\" })\n  // 2023-01-01 was a Sunday, which makes the offset from weekStartsOn direct.\n  return Array.from({ length: DAYS_IN_WEEK }, (_, index) => {\n    const day = new Date(2023, 0, 1 + weekStartsOn + index)\n    return { short: short.format(day), long: long.format(day) }\n  })\n}\n\n/** Where each grid key takes the focus. Null when the key is not a grid key. */\nfunction nextFocusDate(key: string, day: Date, shiftKey: boolean, weekStartsOn: WeekStart) {\n  const intoWeek = (day.getDay() - weekStartsOn + DAYS_IN_WEEK) % DAYS_IN_WEEK\n  const moves: Record<string, () => Date> = {\n    ArrowLeft: () => addDays(day, -1),\n    ArrowRight: () => addDays(day, 1),\n    ArrowUp: () => addDays(day, -DAYS_IN_WEEK),\n    ArrowDown: () => addDays(day, DAYS_IN_WEEK),\n    Home: () => addDays(day, -intoWeek),\n    End: () => addDays(day, DAYS_IN_WEEK - 1 - intoWeek),\n    PageUp: () => addMonths(day, shiftKey ? -12 : -1),\n    PageDown: () => addMonths(day, shiftKey ? 12 : 1),\n  }\n  return moves[key]?.() ?? null\n}\n\n/** First click starts, second click finishes in whichever order, third click starts over. */\nfunction nextRange(current: DateRange | null, day: Date): DateRange {\n  if (!current || current.end) return { start: day, end: null }\n  return day < current.start ? { start: day, end: current.start } : { start: current.start, end: day }\n}\n\n/** `preview` stands in for the missing end while someone is still choosing it. */\nfunction rangeRole(day: Date, range: DateRange | null, preview: Date | null): RangeRole | null {\n  if (!range) return null\n  const other = range.end ?? preview ?? range.start\n  const [low, high] = other < range.start ? [other, range.start] : [range.start, other]\n  if (day < low || day > high) return null\n  if (isSameDay(low, high)) return \"single\"\n  if (isSameDay(day, low)) return \"start\"\n  return isSameDay(day, high) ? \"end\" : \"middle\"\n}\n\nfunction numericFormat(locale?: string) {\n  return new Intl.DateTimeFormat(locale, { year: \"numeric\", month: \"2-digit\", day: \"2-digit\" })\n}\n\n/** The placeholder, in the order this locale writes dates: MM/DD/YYYY, DD/MM/YYYY, YYYY. MM. DD. */\nfunction datePattern(locale?: string) {\n  const names: Record<string, string> = { year: \"YYYY\", month: \"MM\", day: \"DD\" }\n  return numericFormat(locale)\n    .formatToParts(new Date(2000, 0, 31))\n    .map((part) => names[part.type] ?? part.value)\n    .join(\"\")\n}\n\n/** Accepts any separators and 2-digit years. A leading 4-digit number always means year first, as in ISO. */\nfunction parseDateText(text: string, locale?: string) {\n  const numbers = text.match(/\\d+/g)\n  if (numbers?.length !== 3) return null\n  const localeOrder = numericFormat(locale)\n    .formatToParts(new Date(2000, 0, 31))\n    .map((part) => part.type)\n    .filter((type) => type === \"year\" || type === \"month\" || type === \"day\")\n  const order = numbers[0].length === 4 ? [\"year\", \"month\", \"day\"] : localeOrder\n  const parts = Object.fromEntries(order.map((type, index) => [type, Number(numbers[index])]))\n  const year = parts.year < 100 ? parts.year + 2000 : parts.year\n  const date = new Date(year, parts.month - 1, parts.day)\n  // new Date quietly turns Feb 30 into Mar 2, so a date that changed on the way through was never real.\n  return date.getMonth() === parts.month - 1 && date.getDate() === parts.day ? date : null\n}\n\nexport {\n  addDays,\n  addMonths,\n  buildMonthGrid,\n  clampDay,\n  dateKey,\n  datePattern,\n  firstDayOfWeek,\n  isSameDay,\n  isSameMonth,\n  monthKey,\n  nextFocusDate,\n  nextRange,\n  numericFormat,\n  parseDateText,\n  rangeRole,\n  startOfDay,\n  startOfMonth,\n  weekdayLabels,\n}\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "forms"
  ],
  "type": "registry:ui"
}