{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "date-picker",
  "title": "Date picker",
  "author": "Tori Bryan",
  "description": "A field that opens a calendar to pick a day, or a range with presets and Apply; a popover on desktop, a drawer on phones.",
  "dependencies": [
    "@base-ui/react",
    "lucide-react"
  ],
  "registryDependencies": [
    "https://fibo.toribryan.com/r/button.json",
    "https://fibo.toribryan.com/r/calendar.json",
    "https://fibo.toribryan.com/r/sheet.json",
    "utils"
  ],
  "files": [
    {
      "path": "registry/ui/date-picker.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Popover as PopoverPrimitive } from \"@base-ui/react/popover\"\nimport { CalendarIcon, ChevronDownIcon } from \"lucide-react\"\n\nimport { Button } from \"@/components/ui/button\"\nimport { Calendar, useToday } from \"@/components/ui/calendar\"\nimport {\n  Sheet,\n  SheetBody,\n  SheetClose,\n  SheetContent,\n  SheetTitle,\n  SheetTrigger,\n} from \"@/components/ui/sheet\"\nimport {\n  addDays,\n  addMonths,\n  clampDate,\n  clampRange,\n  countDays,\n  endOfMonth,\n  formatDate,\n  formatDateRange,\n  isSameDay,\n  orderRange,\n  startOfMonth,\n  type DateRange,\n  type DraftRange,\n  type WeekStart,\n} from \"@/lib/dates\"\nimport { useFieldSize, type FieldSize } from \"@/lib/field-size\"\nimport { mergeRefs } from \"@/lib/merge-refs\"\nimport { useMediaQuery } from \"@/lib/use-media-query\"\nimport { cn } from \"@/lib/utils\"\n\n/** A shortcut in the rail or chip row, worked out from today when it opens. */\ntype DateRangePreset = {\n  label: string\n  range: (today: Date) => DateRange\n}\n\n/** A one-day shortcut, worked out from today when it opens. */\ntype DatePreset = {\n  label: string\n  date: (today: Date) => Date\n}\n\ntype DatePickerType = \"auto\" | \"popover\" | \"drawer\"\n\nconst defaultDateRangePresets: DateRangePreset[] = [\n  { label: \"Today\", range: (today) => ({ from: today, to: today }) },\n  {\n    label: \"Yesterday\",\n    range: (today) => ({ from: addDays(today, -1), to: addDays(today, -1) }),\n  },\n  {\n    label: \"Last 7 days\",\n    range: (today) => ({ from: addDays(today, -6), to: today }),\n  },\n  {\n    label: \"Last 30 days\",\n    range: (today) => ({ from: addDays(today, -29), to: today }),\n  },\n  {\n    label: \"This month\",\n    range: (today) => ({ from: startOfMonth(today), to: today }),\n  },\n  {\n    label: \"Last month\",\n    range: (today) => {\n      const month = addMonths(startOfMonth(today), -1)\n      return { from: month, to: endOfMonth(month) }\n    },\n  },\n  {\n    label: \"This quarter\",\n    range: (today) => ({\n      from: new Date(\n        today.getFullYear(),\n        Math.floor(today.getMonth() / 3) * 3,\n        1\n      ),\n      to: today,\n    }),\n  },\n  {\n    label: \"Year to date\",\n    range: (today) => ({\n      from: new Date(today.getFullYear(), 0, 1),\n      to: today,\n    }),\n  },\n]\n\nconst defaultDatePresets: DatePreset[] = [\n  { label: \"Today\", date: (today) => today },\n  { label: \"Tomorrow\", date: (today) => addDays(today, 1) },\n  { label: \"In a week\", date: (today) => addDays(today, 7) },\n  { label: \"In a month\", date: (today) => addMonths(today, 1) },\n]\n\n// Below Tailwind's sm breakpoint the panel opens as a drawer from the bottom.\n// It's decided in script rather than CSS because the two layouts are\n// different primitives, not one element restyled. Between sm and md a\n// popover has room for one month beside the presets, not two.\nconst PHONE_QUERY = \"(max-width: 639.98px)\"\nconst NARROW_QUERY = \"(max-width: 767.98px)\"\n\nfunction usePickerLayout(type: DatePickerType): \"popover\" | \"drawer\" {\n  const phone = useMediaQuery(PHONE_QUERY)\n  if (type !== \"auto\") return type\n  return phone ? \"drawer\" : \"popover\"\n}\n\nfunction focusSelectedDay(popup: HTMLElement | null) {\n  return (\n    popup?.querySelector<HTMLElement>(\n      '[data-slot=\"calendar-day\"][tabindex=\"0\"]'\n    ) ?? true\n  )\n}\n\nconst triggerClassName =\n  \"flex w-fit items-center gap-2 rounded-sm border border-input bg-input-subtle px-3 text-sm whitespace-nowrap transition-colors outline-none hover:bg-input-subtle-hover focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring-subtle disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-[3px] aria-invalid:ring-destructive-ring data-placeholder:text-muted-foreground data-[size=default]:h-9 data-[size=sm]:h-8 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 [&_svg]:text-muted-foreground\"\n\nconst popupClassName =\n  \"flex origin-(--transform-origin) flex-col overflow-hidden rounded-xl border border-border bg-popover text-popover-foreground shadow-lg outline-hidden data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95 motion-reduce:animate-none\"\n\ntype TriggerProps = Omit<\n  React.ComponentProps<\"button\">,\n  \"value\" | \"defaultValue\" | \"onChange\" | \"children\" | \"type\"\n>\n\ntype PickerSharedProps = TriggerProps & {\n  /** The earliest day that can be picked. */\n  minDate?: Date | null\n  /** The latest day that can be picked. */\n  maxDate?: Date | null\n  /** Return true for a day that can't be picked, such as a weekend. */\n  isDateDisabled?: (date: Date) => boolean\n  /** The first column of the calendar: 0 for Sunday, 1 for Monday. */\n  weekStartsOn?: WeekStart\n  /** A BCP 47 locale for the calendar and the formatted value. */\n  locale?: string\n  /** Names the panel and titles the drawer; names the trigger too unless a FieldLabel or aria-labelledby does. */\n  label?: string\n  /** Shown in the trigger while nothing is picked. */\n  placeholder?: string\n  /** \"popover\" on every screen, \"drawer\" on every screen, or \"auto\" for a drawer below the sm breakpoint. */\n  type?: DatePickerType\n  /** Matches Input at 36 pixels tall, or 32 for dense forms. */\n  size?: FieldSize\n  /** Whether the panel is open, when you control it. */\n  open?: boolean\n  /** Opens the panel on first render, when the picker keeps its own state. */\n  defaultOpen?: boolean\n  /** Called when the panel opens or closes. */\n  onOpenChange?: (open: boolean) => void\n  /** The element the panel portals into; the body by default. */\n  container?: PopoverPrimitive.Portal.Props[\"container\"]\n  /** Traps focus, blocks the page and moves focus to the calendar on open. Turn off to show the panel inline, as a preview. */\n  modal?: boolean\n}\n\nfunction useOpenState(\n  open: boolean | undefined,\n  defaultOpen: boolean,\n  onOpenChange: ((open: boolean) => void) | undefined\n) {\n  const [ownOpen, setOwnOpen] = React.useState(defaultOpen)\n  const setOpen = (next: boolean) => {\n    if (open === undefined) setOwnOpen(next)\n    onOpenChange?.(next)\n  }\n  return [open ?? ownOpen, setOpen] as const\n}\n\n/*\n * A trigger in a fibo Field is labeled by its FieldLabel, as Input is. Base\n * UI keeps the label's wiring private, so the trigger finds the label in the\n * DOM: it's named by the label followed by the value, and a click on the\n * label focuses it without opening the panel, as it would an input.\n */\nfunction useFieldLabel() {\n  const [labelId, setLabelId] = React.useState<string | null>(null)\n  const ref = React.useCallback((node: HTMLButtonElement | null) => {\n    if (!node) return\n    const label = node\n      .closest('[data-slot=\"field\"]')\n      ?.querySelector<HTMLLabelElement>('label[data-slot=\"field-label\"]')\n    setLabelId(label?.id || null)\n    if (!label) return\n    const focus = () => node.focus()\n    label.addEventListener(\"click\", focus)\n    return () => label.removeEventListener(\"click\", focus)\n  }, [])\n  return [labelId, ref] as const\n}\n\ntype PickerShellProps = {\n  layout: \"popover\" | \"drawer\"\n  open: boolean\n  onOpenChange: (open: boolean) => void\n  container: PickerSharedProps[\"container\"]\n  modal: boolean\n  label: string\n  slot: \"date-picker\" | \"date-range-picker\"\n  text: string\n  empty: boolean\n  size: FieldSize\n  triggerProps: TriggerProps\n  popover: React.ReactNode\n  drawer: React.ReactNode\n}\n\n/*\n * One trigger, two homes for the panel. The panel only mounts while open,\n * so each opening starts from the committed value.\n */\nfunction PickerShell({\n  layout,\n  open,\n  onOpenChange,\n  container,\n  modal,\n  label,\n  slot,\n  text,\n  empty,\n  size,\n  triggerProps: { className, ref, ...triggerProps },\n  popover,\n  drawer,\n}: PickerShellProps) {\n  const popupRef = React.useRef<HTMLDivElement>(null)\n  const valueId = React.useId()\n  // A non-modal panel never pulls focus: focusing would scroll the page to it.\n  const initialFocus = () => modal && focusSelectedDay(popupRef.current)\n  const [fieldLabelId, fieldRef] = useFieldLabel()\n  const triggerRef = React.useMemo(\n    () => mergeRefs(fieldRef, ref),\n    [fieldRef, ref]\n  )\n\n  const labelledBy = triggerProps[\"aria-labelledby\"] ?? fieldLabelId\n  const trigger = {\n    ...triggerProps,\n    ref: triggerRef,\n    type: \"button\" as const,\n    \"data-slot\": slot,\n    \"data-size\": size,\n    \"data-placeholder\": empty ? \"\" : undefined,\n    \"aria-labelledby\": labelledBy ? `${labelledBy} ${valueId}` : undefined,\n    \"aria-label\": labelledBy\n      ? undefined\n      : (triggerProps[\"aria-label\"] ?? `${label}: ${text}`),\n    className: cn(triggerClassName, className),\n    children: (\n      <>\n        <CalendarIcon aria-hidden=\"true\" />\n        <span\n          id={valueId}\n          data-slot=\"date-picker-value\"\n          className=\"flex-1 text-left\"\n        >\n          {text}\n        </span>\n        <ChevronDownIcon aria-hidden=\"true\" />\n      </>\n    ),\n  }\n\n  if (layout === \"drawer\") {\n    return (\n      <Sheet\n        side=\"bottom\"\n        open={open}\n        onOpenChange={onOpenChange}\n        modal={modal}\n      >\n        <SheetTrigger {...trigger} />\n        <SheetContent\n          ref={popupRef}\n          data-slot=\"date-picker-drawer\"\n          showCloseButton={false}\n          container={container}\n          initialFocus={initialFocus}\n          className=\"h-[85%] gap-0\"\n        >\n          <div\n            aria-hidden=\"true\"\n            data-slot=\"date-picker-handle\"\n            className=\"mx-auto mt-2 h-1 w-10 shrink-0 rounded-full bg-border\"\n          />\n          <div className=\"flex shrink-0 items-center justify-between gap-4 py-2 pr-2 pl-4\">\n            <SheetTitle>{label}</SheetTitle>\n            <SheetClose\n              data-slot=\"date-picker-close\"\n              render={<Button type=\"button\" variant=\"ghost\" size=\"sm\" />}\n            >\n              Cancel\n            </SheetClose>\n          </div>\n          {drawer}\n        </SheetContent>\n      </Sheet>\n    )\n  }\n\n  return (\n    <PopoverPrimitive.Root\n      open={open}\n      onOpenChange={onOpenChange}\n      modal={modal}\n    >\n      <PopoverPrimitive.Trigger {...trigger} />\n      <PopoverPrimitive.Portal container={container}>\n        <PopoverPrimitive.Positioner\n          side=\"bottom\"\n          align=\"start\"\n          sideOffset={6}\n          collisionPadding={8}\n          className=\"isolate z-50\"\n        >\n          <PopoverPrimitive.Popup\n            ref={popupRef}\n            data-slot=\"date-picker-popup\"\n            aria-label={label}\n            initialFocus={initialFocus}\n            className={popupClassName}\n          >\n            {popover}\n            {/* Base UI traps focus in a modal popover only once it holds a\n                Close. Escape closes it from the keyboard, so this one is for\n                screen readers on touch and stays out of the tab order. */}\n            <PopoverPrimitive.Close tabIndex={-1} className=\"sr-only\">\n              Close\n            </PopoverPrimitive.Close>\n          </PopoverPrimitive.Popup>\n        </PopoverPrimitive.Positioner>\n      </PopoverPrimitive.Portal>\n    </PopoverPrimitive.Root>\n  )\n}\n\nfunction PresetButton({\n  layout,\n  active,\n  disabled,\n  onClick,\n  children,\n}: {\n  layout: \"popover\" | \"drawer\"\n  active: boolean\n  disabled: boolean\n  onClick: () => void\n  children: React.ReactNode\n}) {\n  return (\n    <Button\n      type=\"button\"\n      data-slot=\"date-picker-preset\"\n      variant={layout === \"drawer\" ? \"outline\" : \"ghost\"}\n      size=\"sm\"\n      aria-pressed={active}\n      disabled={disabled}\n      onClick={onClick}\n      className={cn(\n        layout === \"drawer\"\n          ? \"shrink-0 aria-pressed:border-primary aria-pressed:bg-primary aria-pressed:text-primary-foreground aria-pressed:hover:bg-primary-hover\"\n          : \"w-full justify-start aria-pressed:bg-muted\"\n      )}\n    >\n      {children}\n    </Button>\n  )\n}\n\nfunction PresetList({\n  layout,\n  children,\n}: {\n  layout: \"popover\" | \"drawer\"\n  children: React.ReactNode\n}) {\n  return (\n    <div\n      role=\"group\"\n      aria-label=\"Presets\"\n      data-slot=\"date-picker-presets\"\n      className={\n        layout === \"drawer\"\n          ? \"flex shrink-0 [scrollbar-width:none] gap-2 overflow-x-auto px-4 pb-3\"\n          : \"flex w-36 shrink-0 flex-col gap-0.5 border-r border-border p-2\"\n      }\n    >\n      {children}\n    </div>\n  )\n}\n\ntype DateRangePickerProps = PickerSharedProps & {\n  /** The applied range, when you control it. */\n  value?: DateRange | null\n  /** The applied range at first, when the picker keeps its own state. */\n  defaultValue?: DateRange | null\n  /** Called with the range when Apply is pressed. */\n  onChange?: (range: DateRange) => void\n  /** Shortcuts beside the calendar. Pass an empty array to leave them out. */\n  presets?: DateRangePreset[]\n  /** Months side by side in the popover; one below the md breakpoint. The drawer always scrolls. */\n  months?: 1 | 2\n}\n\nfunction DateRangePicker({\n  value,\n  defaultValue = null,\n  onChange,\n  presets = defaultDateRangePresets,\n  months = 2,\n  minDate,\n  maxDate,\n  isDateDisabled,\n  weekStartsOn = 0,\n  locale = \"en-US\",\n  label = \"Date range\",\n  placeholder = \"Select dates\",\n  type = \"auto\",\n  size: sizeProp,\n  open: openProp,\n  defaultOpen = false,\n  onOpenChange,\n  container,\n  modal = true,\n  ...props\n}: DateRangePickerProps) {\n  const layout = usePickerLayout(type)\n  const narrow = useMediaQuery(NARROW_QUERY)\n  const size = useFieldSize(sizeProp)\n  const [open, setOpen] = useOpenState(openProp, defaultOpen, onOpenChange)\n  // Counts openings, so a panel reopened mid-way through its exit animation\n  // still starts from the applied range rather than the abandoned draft.\n  const [opening, setOpening] = React.useState(0)\n  const [ownValue, setOwnValue] = React.useState(defaultValue)\n  const applied = value !== undefined ? value : ownValue\n  const text = applied ? formatDateRange(applied, locale) : placeholder\n\n  function apply(range: DateRange) {\n    if (value === undefined) setOwnValue(range)\n    onChange?.(range)\n    setOpen(false)\n  }\n\n  const panel = (\n    <DateRangePanel\n      key={opening}\n      layout={layout}\n      initial={applied}\n      presets={presets}\n      months={narrow ? 1 : months}\n      minDate={minDate}\n      maxDate={maxDate}\n      isDateDisabled={isDateDisabled}\n      weekStartsOn={weekStartsOn}\n      locale={locale}\n      onApply={apply}\n      onCancel={() => setOpen(false)}\n    />\n  )\n\n  return (\n    <PickerShell\n      layout={layout}\n      open={open}\n      container={container}\n      modal={modal}\n      onOpenChange={(next) => {\n        if (next) setOpening((n) => n + 1)\n        setOpen(next)\n      }}\n      label={label}\n      slot=\"date-range-picker\"\n      text={text}\n      empty={!applied}\n      size={size}\n      triggerProps={props}\n      popover={panel}\n      drawer={panel}\n    />\n  )\n}\n\nfunction DateRangePanel({\n  layout,\n  initial,\n  presets,\n  months,\n  minDate,\n  maxDate,\n  isDateDisabled,\n  weekStartsOn,\n  locale,\n  onApply,\n  onCancel,\n}: {\n  layout: \"popover\" | \"drawer\"\n  initial: DateRange | null\n  presets: DateRangePreset[]\n  months: 1 | 2\n  minDate?: Date | null\n  maxDate?: Date | null\n  isDateDisabled?: (date: Date) => boolean\n  weekStartsOn: WeekStart\n  locale: string\n  onApply: (range: DateRange) => void\n  onCancel: () => void\n}) {\n  const today = useToday()\n  const [draft, setDraft] = React.useState<DraftRange | null>(initial)\n  const [month, setMonth] = React.useState<Date | null>(() => {\n    const start =\n      initial?.from ?? (today ? clampDate(today, minDate, maxDate) : null)\n    return start ? startOfMonth(start) : null\n  })\n  // A preset remounts the scrolling calendar so it opens on the new range.\n  const [scrollKey, setScrollKey] = React.useState(0)\n\n  const complete = draft?.to ? orderRange(draft.from, draft.to) : null\n  const days = complete ? countDays(complete) : 0\n\n  const resolved = presets.map((preset) => ({\n    label: preset.label,\n    range: today ? clampRange(preset.range(today), minDate, maxDate) : null,\n  }))\n\n  function choosePreset(range: DateRange) {\n    setDraft(range)\n    setMonth(startOfMonth(range.from))\n    setScrollKey((key) => key + 1)\n  }\n\n  const presetList =\n    resolved.length > 0 ? (\n      <PresetList layout={layout}>\n        {resolved.map(({ label, range }) => (\n          <PresetButton\n            key={label}\n            layout={layout}\n            active={\n              !!range &&\n              !!complete &&\n              isSameDay(range.from, complete.from) &&\n              isSameDay(range.to, complete.to)\n            }\n            disabled={!range}\n            onClick={() => range && choosePreset(range)}\n          >\n            {label}\n          </PresetButton>\n        ))}\n      </PresetList>\n    ) : null\n\n  const summary = (\n    <span data-slot=\"date-picker-summary\" className=\"text-sm\">\n      {complete\n        ? formatDateRange(complete, locale)\n        : draft\n          ? \"Pick an end date\"\n          : \"Pick a start date\"}\n    </span>\n  )\n  const count = complete ? (\n    <span\n      data-slot=\"date-picker-count\"\n      className=\"text-sm text-muted-foreground tabular-nums\"\n    >\n      {days} {days === 1 ? \"day\" : \"days\"}\n    </span>\n  ) : null\n  const applyButton = (\n    <Button\n      type=\"button\"\n      data-slot=\"date-picker-apply\"\n      size={layout === \"drawer\" ? \"lg\" : \"sm\"}\n      disabled={!complete}\n      onClick={() => complete && onApply(complete)}\n      className={layout === \"drawer\" ? \"w-full\" : undefined}\n    >\n      Apply\n    </Button>\n  )\n\n  const calendarProps = {\n    mode: \"range\" as const,\n    value: draft,\n    onChange: setDraft,\n    minDate,\n    maxDate,\n    isDateDisabled,\n    weekStartsOn,\n    locale,\n  }\n\n  if (layout === \"drawer\") {\n    return (\n      <>\n        {presetList}\n        <SheetBody className=\"flex min-h-0 flex-col overflow-hidden px-0\">\n          <Calendar\n            key={scrollKey}\n            {...calendarProps}\n            type=\"scroll\"\n            size=\"lg\"\n            defaultMonth={month ?? undefined}\n            className=\"flex-1\"\n          />\n        </SheetBody>\n        <div\n          data-slot=\"date-picker-footer\"\n          className=\"flex shrink-0 flex-col gap-3 border-t border-border p-4\"\n        >\n          <div className=\"flex items-baseline justify-between gap-4\">\n            {summary}\n            {count}\n          </div>\n          {applyButton}\n        </div>\n      </>\n    )\n  }\n\n  return (\n    <>\n      <div className=\"flex\">\n        {presetList}\n        <Calendar\n          {...calendarProps}\n          months={months}\n          month={month ?? undefined}\n          onMonthChange={setMonth}\n          className=\"p-3\"\n        />\n      </div>\n      <div\n        data-slot=\"date-picker-footer\"\n        className=\"flex items-center justify-between gap-4 border-t border-border px-3 py-2.5\"\n      >\n        <div className=\"flex items-baseline gap-2\">\n          {summary}\n          {count}\n        </div>\n        <div className=\"flex gap-2\">\n          <Button\n            type=\"button\"\n            data-slot=\"date-picker-cancel\"\n            variant=\"outline\"\n            size=\"sm\"\n            onClick={onCancel}\n          >\n            Cancel\n          </Button>\n          {applyButton}\n        </div>\n      </div>\n    </>\n  )\n}\n\ntype DatePickerProps = PickerSharedProps & {\n  /** The picked day, when you control it. */\n  value?: Date | null\n  /** The picked day at first, when the picker keeps its own state. */\n  defaultValue?: Date | null\n  /** Called with the day as soon as it's picked. */\n  onChange?: (date: Date) => void\n  /** Shortcuts beside the calendar, such as Tomorrow. None by default. */\n  presets?: DatePreset[]\n}\n\nfunction DatePicker({\n  value,\n  defaultValue = null,\n  onChange,\n  presets = [],\n  minDate,\n  maxDate,\n  isDateDisabled,\n  weekStartsOn = 0,\n  locale = \"en-US\",\n  label = \"Date\",\n  placeholder = \"Select a date\",\n  type = \"auto\",\n  size: sizeProp,\n  open: openProp,\n  defaultOpen = false,\n  onOpenChange,\n  container,\n  modal = true,\n  ...props\n}: DatePickerProps) {\n  const layout = usePickerLayout(type)\n  const size = useFieldSize(sizeProp)\n  const today = useToday()\n  const [open, setOpen] = useOpenState(openProp, defaultOpen, onOpenChange)\n  const [ownValue, setOwnValue] = React.useState(defaultValue)\n  const picked = value !== undefined ? value : ownValue\n  const text = picked ? formatDate(picked, locale) : placeholder\n\n  function pick(date: Date) {\n    if (value === undefined) setOwnValue(date)\n    onChange?.(date)\n    setOpen(false)\n  }\n\n  const presetList =\n    presets.length > 0 ? (\n      <PresetList layout={layout}>\n        {presets.map((preset) => {\n          const raw = today ? preset.date(today) : null\n          const date =\n            raw && isSameDay(clampDate(raw, minDate, maxDate), raw) ? raw : null\n          return (\n            <PresetButton\n              key={preset.label}\n              layout={layout}\n              active={!!date && !!picked && isSameDay(date, picked)}\n              disabled={!date || !!isDateDisabled?.(date)}\n              onClick={() => date && pick(date)}\n            >\n              {preset.label}\n            </PresetButton>\n          )\n        })}\n      </PresetList>\n    ) : null\n\n  const calendarProps = {\n    value: picked,\n    onChange: pick,\n    minDate,\n    maxDate,\n    isDateDisabled,\n    weekStartsOn,\n    locale,\n  }\n\n  return (\n    <PickerShell\n      layout={layout}\n      open={open}\n      container={container}\n      modal={modal}\n      onOpenChange={setOpen}\n      label={label}\n      slot=\"date-picker\"\n      text={text}\n      empty={!picked}\n      size={size}\n      triggerProps={props}\n      popover={\n        <div className=\"flex\">\n          {presetList}\n          <Calendar {...calendarProps} className=\"p-3\" />\n        </div>\n      }\n      drawer={\n        <>\n          {presetList}\n          <SheetBody className=\"flex min-h-0 flex-col overflow-hidden px-0\">\n            <Calendar\n              {...calendarProps}\n              type=\"scroll\"\n              size=\"lg\"\n              className=\"flex-1\"\n            />\n          </SheetBody>\n        </>\n      }\n    />\n  )\n}\n\nexport {\n  DatePicker,\n  DateRangePicker,\n  defaultDatePresets,\n  defaultDateRangePresets,\n}\nexport type {\n  DatePickerType,\n  DatePickerProps,\n  DatePreset,\n  DateRange,\n  DateRangePickerProps,\n  DateRangePreset,\n}\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/lib/dates.ts",
      "content": "/*\n * Calendar-day arithmetic on plain Dates in the local time zone. Every date\n * here is a local midnight, so two dates are the same day exactly when their\n * year, month and day match, whatever the time zone or daylight saving.\n */\n\n/** A finished range: both ends picked, `from` on or before `to`. */\nexport type DateRange = { from: Date; to: Date }\n\n/** A range being picked: `to` is missing until the second day is chosen. */\nexport type DraftRange = { from: Date; to?: Date }\n\n/** The first column of the week: 0 for Sunday, 1 for Monday. */\nexport type WeekStart = 0 | 1\n\nexport function startOfDay(date: Date): Date {\n  return new Date(date.getFullYear(), date.getMonth(), date.getDate())\n}\n\nexport function startOfMonth(date: Date): Date {\n  return new Date(date.getFullYear(), date.getMonth(), 1)\n}\n\nexport function endOfMonth(date: Date): Date {\n  return new Date(date.getFullYear(), date.getMonth() + 1, 0)\n}\n\n// Built from the parts rather than by adding milliseconds, so a day across a\n// daylight-saving change is still one day.\nexport function addDays(date: Date, days: number): Date {\n  return new Date(date.getFullYear(), date.getMonth(), date.getDate() + days)\n}\n\n/** Moves by whole months, keeping the day but never spilling past the end. */\nexport function addMonths(date: Date, months: number): Date {\n  const target = new Date(date.getFullYear(), date.getMonth() + months, 1)\n  const last = endOfMonth(target).getDate()\n  return new Date(\n    target.getFullYear(),\n    target.getMonth(),\n    Math.min(date.getDate(), last)\n  )\n}\n\nexport function isSameDay(a: Date, b: Date): boolean {\n  return (\n    a.getFullYear() === b.getFullYear() &&\n    a.getMonth() === b.getMonth() &&\n    a.getDate() === b.getDate()\n  )\n}\n\nexport function isSameMonth(a: Date, b: Date): boolean {\n  return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth()\n}\n\n/** Whole calendar days from `a` to `b`, negative when `b` comes first. */\nexport function differenceInDays(b: Date, a: Date): number {\n  const utc = (d: Date) => Date.UTC(d.getFullYear(), d.getMonth(), d.getDate())\n  return Math.round((utc(b) - utc(a)) / 86_400_000)\n}\n\n/** Whole months from the month of `a` to the month of `b`. */\nexport function differenceInMonths(b: Date, a: Date): number {\n  return (\n    (b.getFullYear() - a.getFullYear()) * 12 + (b.getMonth() - a.getMonth())\n  )\n}\n\nexport function startOfWeek(date: Date, weekStartsOn: WeekStart = 0): Date {\n  const offset = (date.getDay() - weekStartsOn + 7) % 7\n  return addDays(date, -offset)\n}\n\nexport function endOfWeek(date: Date, weekStartsOn: WeekStart = 0): Date {\n  return addDays(startOfWeek(date, weekStartsOn), 6)\n}\n\n/** Keeps a day inside the bounds given, either of which may be missing. */\nexport function clampDate(date: Date, min?: Date | null, max?: Date | null) {\n  const day = startOfDay(date)\n  if (min && day < startOfDay(min)) return startOfDay(min)\n  if (max && day > startOfDay(max)) return startOfDay(max)\n  return day\n}\n\n/** Two days as a range, earliest first, whichever order they were picked. */\nexport function orderRange(a: Date, b: Date): DateRange {\n  const [from, to] = a <= b ? [a, b] : [b, a]\n  return { from: startOfDay(from), to: startOfDay(to) }\n}\n\n/**\n * The part of a range inside the bounds, or null when none of it is. A\n * preset such as \"Last 30 days\" still works next to a minimum date by\n * starting at that minimum.\n */\nexport function clampRange(\n  range: DateRange,\n  min?: Date | null,\n  max?: Date | null\n): DateRange | null {\n  if (min && startOfDay(range.to) < startOfDay(min)) return null\n  if (max && startOfDay(range.from) > startOfDay(max)) return null\n  return {\n    from: clampDate(range.from, min, max),\n    to: clampDate(range.to, min, max),\n  }\n}\n\n/** Days in a range, counting both ends: Oct 1 to Oct 7 is 7 days. */\nexport function countDays(range: DateRange): number {\n  return Math.abs(differenceInDays(range.to, range.from)) + 1\n}\n\n/**\n * A month as rows of seven, starting on `weekStartsOn`. Days from the\n * months either side are null, so a grid can leave those cells empty.\n */\nexport function getMonthWeeks(\n  month: Date,\n  weekStartsOn: WeekStart = 0\n): (Date | null)[][] {\n  const first = startOfMonth(month)\n  const last = endOfMonth(month)\n  const weeks: (Date | null)[][] = []\n  let day = startOfWeek(first, weekStartsOn)\n  while (day <= last) {\n    const week: (Date | null)[] = []\n    for (let i = 0; i < 7; i++) {\n      week.push(isSameMonth(day, first) ? day : null)\n      day = addDays(day, 1)\n    }\n    weeks.push(week)\n  }\n  return weeks\n}\n\n/** A stable key for a day, such as `2026-10-05`, for lookups and data attributes. */\nexport function toDateKey(date: Date): string {\n  const pad = (n: number) => String(n).padStart(2, \"0\")\n  return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`\n}\n\n/*\n * ICU builds differ in the spaces they put in dates: Node writes thin spaces\n * around a range's dash where a browser may write plain ones, so markup\n * rendered on the server wouldn't match the client's. Plain spaces read the\n * same and hydrate.\n */\nconst plainSpaces = (text: string) => text.replace(/[\\u2009\\u202f]/g, \" \")\n\n/** One day for a trigger or summary, such as \"Oct 5, 2026\". */\nexport function formatDate(date: Date, locale = \"en-US\"): string {\n  const text = new Intl.DateTimeFormat(locale, {\n    month: \"short\",\n    day: \"numeric\",\n    year: \"numeric\",\n  }).format(date)\n  return plainSpaces(text)\n}\n\n/**\n * A range the way the locale writes one, sharing what both ends have in\n * common: \"Oct 1 – 7, 2026\", or \"Oct 5, 2026\" when it's a single day.\n */\nexport function formatDateRange(range: DateRange, locale = \"en-US\"): string {\n  const text = new Intl.DateTimeFormat(locale, {\n    month: \"short\",\n    day: \"numeric\",\n    year: \"numeric\",\n  }).formatRange(range.from, range.to)\n  return plainSpaces(text)\n}\n\n/** A time of day the way the locale writes one, such as \"9:30 AM\". */\nexport function formatTime(date: Date, locale = \"en-US\"): string {\n  const text = new Intl.DateTimeFormat(locale, {\n    hour: \"numeric\",\n    minute: \"2-digit\",\n  }).format(date)\n  return plainSpaces(text)\n}\n\n/** Two times on one day, sharing what they have in common: \"9:30 – 10:00 AM\". */\nexport function formatTimeRange(\n  from: Date,\n  to: Date,\n  locale = \"en-US\"\n): string {\n  const text = new Intl.DateTimeFormat(locale, {\n    hour: \"numeric\",\n    minute: \"2-digit\",\n  }).formatRange(from, to)\n  return plainSpaces(text)\n}\n",
      "type": "registry:lib"
    },
    {
      "path": "registry/lib/field-size.ts",
      "content": "import * as React from \"react\"\n\nexport type FieldSize = \"sm\" | \"default\"\n\n/**\n * The size a FieldGroup or Field sets for the controls inside it. It lives\n * here rather than in the Field part so a control can read it without\n * installing Field.\n */\nexport const FieldSizeContext = React.createContext<FieldSize | undefined>(\n  undefined\n)\n\n/** A control's own size wins, then the nearest Field's or FieldGroup's. */\nexport function useFieldSize(size?: FieldSize): FieldSize {\n  const inherited = React.useContext(FieldSizeContext)\n  return size ?? inherited ?? \"default\"\n}\n",
      "type": "registry:lib"
    },
    {
      "path": "registry/lib/merge-refs.ts",
      "content": "import * as React from \"react\"\n\n/**\n * One callback ref that sets every ref given, so a part can keep its own\n * ref on an element and still hand the element to a caller's `ref`. Undo\n * runs through React 19's ref cleanup, which calls a function ref with\n * null and empties an object ref.\n */\nexport function mergeRefs<T>(\n  ...refs: (React.Ref<T> | undefined)[]\n): React.RefCallback<T> {\n  return (node) => {\n    const cleanups = refs.map((ref) => {\n      if (typeof ref === \"function\") {\n        const cleanup = ref(node)\n        return typeof cleanup === \"function\" ? cleanup : () => ref(null)\n      }\n      if (ref) {\n        ref.current = node\n        return () => {\n          ref.current = null\n        }\n      }\n      return undefined\n    })\n    return () => {\n      for (const cleanup of cleanups) cleanup?.()\n    }\n  }\n}\n",
      "type": "registry:lib"
    },
    {
      "path": "registry/lib/use-media-query.ts",
      "content": "import * as React from \"react\"\n\n/**\n * Whether a media query matches, kept in step as it changes. False on the\n * server and during hydration, so server markup never depends on it.\n */\nexport function useMediaQuery(query: string) {\n  const subscribe = React.useCallback(\n    (onChange: () => void) => {\n      const list = window.matchMedia(query)\n      list.addEventListener(\"change\", onChange)\n      return () => list.removeEventListener(\"change\", onChange)\n    },\n    [query]\n  )\n  return React.useSyncExternalStore(\n    subscribe,\n    () => window.matchMedia(query).matches,\n    () => false\n  )\n}\n",
      "type": "registry:lib"
    }
  ],
  "cssVars": {
    "theme": {
      "color-primary-hover": "var(--primary-hover)",
      "color-destructive-ring": "var(--destructive-ring)",
      "color-input-subtle": "var(--input-subtle)",
      "color-input-subtle-hover": "var(--input-subtle-hover)",
      "color-ring-subtle": "var(--ring-subtle)",
      "border-color-destructive": "var(--destructive-border)"
    },
    "light": {
      "primary-hover": "color-mix(in oklch, var(--color-neutral-900) 80%, transparent)",
      "destructive-ring": "color-mix(in oklch, var(--color-red-700) 20%, transparent)",
      "input-subtle": "color-mix(in oklch, var(--color-neutral-200) 20%, transparent)",
      "input-subtle-hover": "color-mix(in oklch, var(--color-neutral-200) 50%, transparent)",
      "ring-subtle": "color-mix(in oklch, var(--color-neutral-400) 50%, transparent)",
      "destructive-border": "var(--destructive)"
    },
    "dark": {
      "primary-hover": "color-mix(in oklch, var(--color-neutral-50) 80%, transparent)",
      "destructive-ring": "color-mix(in oklch, var(--color-red-400) 40%, transparent)",
      "input-subtle": "color-mix(in oklch, var(--color-white) 5%, transparent)",
      "input-subtle-hover": "color-mix(in oklch, var(--color-white) 10%, transparent)",
      "ring-subtle": "color-mix(in oklch, var(--color-neutral-500) 50%, transparent)",
      "destructive-border": "var(--destructive)"
    }
  },
  "meta": {
    "tier": "base-components",
    "group": "Forms",
    "docs": "https://fibo.toribryan.com/?path=/docs/base-components-forms-date-picker--docs"
  },
  "docs": "Docs and live examples: https://fibo.toribryan.com/?path=/docs/base-components-forms-date-picker--docs",
  "categories": [
    "base-components",
    "forms"
  ],
  "type": "registry:ui"
}