{
  "schemaVersion": 1,
  "name": "date-picker",
  "useFor": "Use for a date in a form or filter (a key's expiry, an order's date range). Use DateRangePicker for ranges; give presets for common choices (Today, Last 7 days). Give it a name for FormData; the value posts as YYYY-MM-DD.",
  "constraints": [
    "A free-text date field without a picker",
    "A picker that stays open after a single date is chosen",
    "Presets that do not match the calendar's selection"
  ],
  "documentation": "",
  "source": "Extrapolated from opencommerce (the select field and the floating panel)",
  "exports": [
    "DatePicker",
    "DateRangePicker",
    "formatDate",
    "formatRange",
    "DatePickerProps",
    "DateRangePickerProps"
  ],
  "client": true,
  "docsUrl": "https://opencommerce-ds.vercel.app/components/date-picker",
  "markdownUrl": "https://opencommerce-ds.vercel.app/r/date-picker.md",
  "examples": [
    {
      "path": "examples/date-picker.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { type DateRange } from \"@/components/ui/calendar\";\nimport { DatePicker, DateRangePicker } from \"@/components/ui/date-picker\";\nimport { Label } from \"@/components/ui/label\";\n\nconst TODAY = new Date(2026, 9, 26);\nconst days = (n: number) => new Date(TODAY.getFullYear(), TODAY.getMonth(), TODAY.getDate() + n);\n\nexport function Site() {\n  const [date, setDate] = React.useState<Date | undefined>(days(30));\n  return (\n    <div className=\"grid w-full max-w-72 gap-1.5\">\n      <Label htmlFor=\"key-expiry\">Key expires</Label>\n      <DatePicker id=\"key-expiry\" name=\"expires\" value={date} onValueChange={setDate} calendar={{ today: TODAY, disabled: { before: TODAY } }} />\n    </div>\n  );\n}\n\nexport function Range() {\n  const [range, setRange] = React.useState<DateRange | undefined>({ from: days(-6), to: TODAY });\n  return (\n    <div className=\"grid w-full max-w-80 gap-1.5\">\n      <Label htmlFor=\"orders-range\">Orders placed</Label>\n      <DateRangePicker id=\"orders-range\" name=\"placed\" value={range} onValueChange={setRange} calendar={{ today: TODAY }} />\n    </div>\n  );\n}\n\nexport function Presets() {\n  const [range, setRange] = React.useState<DateRange | undefined>();\n  return (\n    <div className=\"grid w-full max-w-80 gap-1.5\">\n      <Label htmlFor=\"report-range\">Report</Label>\n      <DateRangePicker\n        id=\"report-range\"\n        value={range}\n        onValueChange={setRange}\n        calendar={{ today: TODAY }}\n        presets={[\n          { label: \"Today\", value: { from: TODAY, to: TODAY } },\n          { label: \"Last 7 days\", value: { from: days(-6), to: TODAY } },\n          { label: \"Last 30 days\", value: { from: days(-29), to: TODAY } },\n          { label: \"This month\", value: { from: new Date(2026, 9, 1), to: TODAY } },\n        ]}\n      />\n    </div>\n  );\n}\n\nexport function States() {\n  return (\n    <div className=\"grid w-full max-w-72 gap-3\">\n      <DatePicker aria-label=\"Delivery date\" placeholder=\"Pick a delivery date\" aria-invalid />\n      <p className=\"-mt-2 font-sans text-body-sm text-danger\">Choose a delivery date.</p>\n      <DatePicker aria-label=\"Locked date\" defaultValue={TODAY} disabled />\n    </div>\n  );\n}\n",
      "exports": [
        "Site",
        "Range",
        "Presets",
        "States"
      ],
      "requires": [
        "date-picker",
        "calendar",
        "label"
      ],
      "packages": [],
      "previews": [
        {
          "name": "Site",
          "url": "https://opencommerce-ds.vercel.app/preview/date-picker/Site"
        },
        {
          "name": "Range",
          "url": "https://opencommerce-ds.vercel.app/preview/date-picker/Range"
        },
        {
          "name": "Presets",
          "url": "https://opencommerce-ds.vercel.app/preview/date-picker/Presets"
        },
        {
          "name": "States",
          "url": "https://opencommerce-ds.vercel.app/preview/date-picker/States"
        }
      ]
    }
  ],
  "type": "ui",
  "title": "Date Picker",
  "description": "The select's field with a calendar glyph, opening a calendar in the floating panel, with optional presets.",
  "surface": "site",
  "kinds": [
    "input"
  ],
  "requires": [
    "theme",
    "calendar",
    "popover",
    "utils"
  ],
  "packages": [
    "lucide-react"
  ],
  "files": [
    {
      "path": "components/ui/date-picker.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { CalendarDays } from \"lucide-react\";\n\nimport { Calendar, type CalendarProps, type DateRange } from \"@/components/ui/calendar\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"@/components/ui/popover\";\nimport { cn } from \"@/lib/utils\";\n\n// A date field (extrapolated from the brand's select and floating panel). The trigger is the select's field: 36px, 8px\n// corners, white on the 16 percent ink edge, a 14px calendar glyph at 64 percent ink, the date in 14px Inter (the\n// placeholder faint) and the select's drawn chevron at the right; an open picker turns the edge blue. The calendar opens\n// under it in the popover's white panel, growing from the trigger. Presets sit in a column of 34px rows at the panel's\n// left, the chosen one on the pale blue. A single date closes the panel when picked; a range closes once both\n// ends are set. Opening puts focus on the chosen day (or today), so the arrow keys work at once.\nconst TRIGGER =\n  \"flex h-9 w-full min-w-0 cursor-pointer items-center gap-2.5 rounded-control border border-hairline bg-surface px-3 font-sans text-body-sm leading-body whitespace-nowrap text-fg outline-none transition-[border-color] duration-160 ease-out hover:border-line-field-hover focus-visible:border-line-brand data-[state=open]:border-line-brand disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-danger data-[empty=true]:text-fg-faint motion-reduce:transition-none\";\n\ntype Preset<T> = { label: string; value: T };\n\nfunction formatDate(date: Date, locale?: string, options: Intl.DateTimeFormatOptions = { month: \"short\", day: \"numeric\", year: \"numeric\" }) {\n  return new Intl.DateTimeFormat(locale, options).format(date);\n}\n\nfunction formatRange(range: DateRange | undefined, locale?: string) {\n  if (!range?.from) return undefined;\n  if (!range.to) return `${formatDate(range.from, locale)} -`;\n  const sameYear = range.from.getFullYear() === range.to.getFullYear();\n  return `${formatDate(range.from, locale, sameYear ? { month: \"short\", day: \"numeric\" } : undefined)} - ${formatDate(range.to, locale)}`;\n}\n\nfunction DatePickerTrigger({ empty, label, className, children, ...props }: React.ComponentProps<\"button\"> & { empty: boolean; label?: string }) {\n  return (\n    <PopoverTrigger asChild>\n      <button type=\"button\" data-slot=\"date-picker-trigger\" data-empty={empty} aria-label={label} className={cn(TRIGGER, className)} {...props}>\n        <CalendarDays aria-hidden strokeWidth={1.5} className=\"size-3.75 shrink-0 text-fg-muted\" />\n        <span className=\"min-w-0 flex-1 truncate text-left\">{children}</span>\n        <svg aria-hidden viewBox=\"0 0 10 6\" className=\"h-1.5 w-2.5 shrink-0 fill-none stroke-current text-fg-muted [stroke-linecap:round] [stroke-linejoin:round] [stroke-width:1.5]\">\n          <path d=\"M1 1l4 4 4-4\" />\n        </svg>\n      </button>\n    </PopoverTrigger>\n  );\n}\n\nfunction Presets<T>({ presets, isActive, onPick }: { presets: Preset<T>[]; isActive: (p: Preset<T>) => boolean; onPick: (value: T) => void }) {\n  return (\n    <div data-slot=\"date-picker-presets\" role=\"group\" aria-label=\"Presets\" className=\"flex gap-0.5 overflow-x-auto border-line p-1 max-sm:border-b sm:w-40 sm:flex-col sm:border-r\">\n      {presets.map((p) => (\n        <button\n          key={p.label}\n          type=\"button\"\n          aria-pressed={isActive(p)}\n          onClick={() => onPick(p.value)}\n          className=\"flex h-8.5 shrink-0 items-center rounded-menu px-2.5 text-left font-sans text-body-sm leading-tight whitespace-nowrap text-fg-muted outline-none transition-[background-color,color] duration-160 ease-out hover:bg-wash-hover hover:text-fg focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-solid focus-visible:outline-focus-ring aria-pressed:bg-blue-5 aria-pressed:text-fg-brand motion-reduce:transition-none\"\n        >\n          {p.label}\n        </button>\n      ))}\n    </div>\n  );\n}\n\nconst sameDay = (a?: Date, b?: Date) => !!a && !!b && a.toDateString() === b.toDateString();\n\ntype CalendarPassThrough = Omit<CalendarProps, \"mode\" | \"selected\" | \"onSelect\" | \"required\" | \"min\" | \"max\">;\n\ntype DatePickerProps = {\n  value?: Date;\n  defaultValue?: Date;\n  onValueChange?: (date: Date | undefined) => void;\n  placeholder?: string;\n  disabled?: boolean;\n  /** Shortcuts beside the calendar (Today, In a week). */\n  presets?: Preset<Date>[];\n  /** Props for the calendar inside (disabled days, the first day of the week, today). */\n  calendar?: CalendarPassThrough;\n  locale?: string;\n  id?: string;\n  name?: string;\n  className?: string;\n  \"aria-invalid\"?: boolean;\n  \"aria-label\"?: string;\n};\n\nfunction DatePicker({ value: valueProp, defaultValue, onValueChange, placeholder = \"Pick a date\", disabled, presets, calendar, locale, id, name, className, ...aria }: DatePickerProps) {\n  const [open, setOpen] = React.useState(false);\n  const [inner, setInner] = React.useState(defaultValue);\n  const value = valueProp !== undefined ? valueProp : inner;\n  const set = (d: Date | undefined) => {\n    if (valueProp === undefined) setInner(d);\n    onValueChange?.(d);\n  };\n  return (\n    <Popover open={open} onOpenChange={setOpen}>\n      <DatePickerTrigger id={id} empty={!value} disabled={disabled} className={className} aria-invalid={aria[\"aria-invalid\"]} label={aria[\"aria-label\"]}>\n        {value ? formatDate(value, locale) : placeholder}\n      </DatePickerTrigger>\n      {name && <input type=\"hidden\" name={name} value={value ? value.toISOString().slice(0, 10) : \"\"} />}\n      <PopoverContent data-slot=\"date-picker-content\" align=\"start\" onOpenAutoFocus={(e) => e.preventDefault()} className=\"w-auto gap-0 overflow-hidden p-0\">\n        <div className=\"flex flex-col sm:flex-row\">\n          {presets && (\n            <Presets\n              presets={presets}\n              isActive={(p) => sameDay(p.value, value)}\n              onPick={(d) => {\n                set(d);\n                setOpen(false);\n              }}\n            />\n          )}\n          <Calendar\n            {...calendar}\n            mode=\"single\"\n            selected={value}\n            defaultMonth={value ?? calendar?.defaultMonth}\n            locale={locale}\n            autoFocus\n            onSelect={(d) => {\n              set(d);\n              if (d) setOpen(false);\n            }}\n          />\n        </div>\n      </PopoverContent>\n    </Popover>\n  );\n}\n\ntype DateRangePickerProps = Omit<DatePickerProps, \"value\" | \"defaultValue\" | \"onValueChange\" | \"presets\"> & {\n  value?: DateRange;\n  defaultValue?: DateRange;\n  onValueChange?: (range: DateRange | undefined) => void;\n  presets?: Preset<DateRange>[];\n  /** How many months show side by side (two by default; one on a phone). */\n  numberOfMonths?: number;\n};\n\nfunction DateRangePicker({ value: valueProp, defaultValue, onValueChange, placeholder = \"Pick a date range\", disabled, presets, calendar, locale, numberOfMonths = 2, id, name, className, ...aria }: DateRangePickerProps) {\n  const [open, setOpen] = React.useState(false);\n  const [inner, setInner] = React.useState(defaultValue);\n  const value = valueProp !== undefined ? valueProp : inner;\n  const set = (r: DateRange | undefined) => {\n    if (valueProp === undefined) setInner(r);\n    onValueChange?.(r);\n  };\n  const [narrow, setNarrow] = React.useState(false);\n  React.useEffect(() => {\n    const query = window.matchMedia(\"(max-width: 39.99rem)\");\n    const update = () => setNarrow(query.matches);\n    update();\n    query.addEventListener(\"change\", update);\n    return () => query.removeEventListener(\"change\", update);\n  }, []);\n  const label = formatRange(value, locale);\n  return (\n    <Popover open={open} onOpenChange={setOpen}>\n      <DatePickerTrigger id={id} empty={!label} disabled={disabled} className={className} aria-invalid={aria[\"aria-invalid\"]} label={aria[\"aria-label\"]}>\n        {label ?? placeholder}\n      </DatePickerTrigger>\n      {name && (\n        <>\n          <input type=\"hidden\" name={`${name}From`} value={value?.from ? value.from.toISOString().slice(0, 10) : \"\"} />\n          <input type=\"hidden\" name={`${name}To`} value={value?.to ? value.to.toISOString().slice(0, 10) : \"\"} />\n        </>\n      )}\n      <PopoverContent data-slot=\"date-range-picker-content\" align=\"start\" onOpenAutoFocus={(e) => e.preventDefault()} className=\"w-auto max-w-[calc(100vw-2rem)] gap-0 overflow-hidden p-0\">\n        <div className=\"flex flex-col sm:flex-row\">\n          {presets && (\n            <Presets\n              presets={presets}\n              isActive={(p) => sameDay(p.value.from, value?.from) && sameDay(p.value.to, value?.to)}\n              onPick={(r) => {\n                set(r);\n                setOpen(false);\n              }}\n            />\n          )}\n          <Calendar\n            {...calendar}\n            mode=\"range\"\n            selected={value}\n            defaultMonth={value?.from ?? calendar?.defaultMonth}\n            numberOfMonths={narrow ? 1 : numberOfMonths}\n            locale={locale}\n            autoFocus\n            onSelect={(r) => {\n              set(r);\n              if (r?.from && r.to) setOpen(false);\n            }}\n          />\n        </div>\n      </PopoverContent>\n    </Popover>\n  );\n}\n\nexport { DatePicker, DateRangePicker, formatDate, formatRange };\nexport type { DatePickerProps, DateRangePickerProps };\n"
    }
  ]
}
