{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "date-picker",
  "title": "Date Picker",
  "author": "Ballmac <https://ui.ballmac.com>",
  "description": "A button that opens a calendar popover to choose one date, with presets, month and year selects, min and max dates, clearing, invalid state and form submission.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "utils",
    "https://ui.ballmac.com/r/calendar.json",
    "https://ui.ballmac.com/r/popover.json",
    "https://ui.ballmac.com/r/i18n.json"
  ],
  "files": [
    {
      "path": "registry/ballmac/components/date-picker.tsx",
      "content": "// Ballmac UI: Date Picker. https://ui.ballmac.com/components/date-picker\n\"use client\";\n\nimport * as React from \"react\";\nimport { CalendarDays, X } from \"lucide-react\";\nimport { Calendar } from \"@/components/ballmac/calendar\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"@/components/ballmac/popover\";\nimport { cn } from \"@/lib/utils\";\nimport { useLocale, useMessages } from \"@/lib/ballmac/i18n\";\n\ntype DatePickerPreset = {\n  /** Button label, for example \"Tomorrow\". */\n  label: string;\n  /** The date the preset selects. Build it with `new Date(y, m, d)` so it is local midnight. */\n  date: Date;\n};\n\ntype DatePickerProps = Omit<\n  React.ComponentProps<\"button\">,\n  \"value\" | \"defaultValue\" | \"onChange\"\n> & {\n  /** Controlled date. Pass `null` for a controlled empty value; `undefined` leaves the picker uncontrolled. */\n  value?: Date | null;\n  /** Initial date when uncontrolled. */\n  defaultValue?: Date;\n  /** Called with the new date, or `undefined` when cleared. */\n  onValueChange?: (date: Date | undefined) => void;\n  /** Text on the trigger when no date is chosen. */\n  placeholder?: string;\n  /** Locale for the trigger text and calendar. The default keeps server and browser output identical. */\n  locale?: string;\n  /** Formatting options for the trigger text. */\n  format?: Intl.DateTimeFormatOptions;\n  /** Earliest selectable date. */\n  minDate?: Date;\n  /** Latest selectable date. */\n  maxDate?: Date;\n  /** Quick picks shown beside the calendar. */\n  presets?: DatePickerPreset[];\n  /** Show a control that clears the date. */\n  clearable?: boolean;\n  /** Mark the field invalid (`aria-invalid`, destructive border). */\n  invalid?: boolean;\n  /** Name of a hidden input that submits the date as YYYY-MM-DD. */\n  name?: string;\n  /** Show month and year selects in the calendar header. */\n  dropdowns?: boolean;\n};\n\nfunction toIso(d: Date) {\n  const m = String(d.getMonth() + 1).padStart(2, \"0\");\n  const day = String(d.getDate()).padStart(2, \"0\");\n  return `${d.getFullYear()}-${m}-${day}`;\n}\nconst sameDay = (a?: Date, b?: Date) => !!a && !!b && toIso(a) === toIso(b);\n\n/** A button that opens a calendar popover to choose one date, with optional presets and form submission. */\nfunction DatePicker({\n  value: valueProp,\n  defaultValue,\n  onValueChange,\n  placeholder,\n  locale,\n  format = { dateStyle: \"medium\" },\n  minDate,\n  maxDate,\n  presets,\n  clearable = false,\n  invalid = false,\n  name,\n  dropdowns = false,\n  disabled,\n  className,\n  \"aria-label\": ariaLabel,\n  ...props\n}: DatePickerProps) {\n  const defaultLocale = useLocale()\n  locale ??= defaultLocale\n  const msg = useMessages()\n  placeholder ??= msg(\"date-picker.placeholder\", \"Pick a date\")\n  const [open, setOpen] = React.useState(false);\n  const [inner, setInner] = React.useState<Date | undefined>(defaultValue);\n  const value = valueProp === undefined ? inner : (valueProp ?? undefined);\n  const choose = (next: Date | undefined) => {\n    setInner(next);\n    onValueChange?.(next);\n    if (next) setOpen(false);\n  };\n  const text = value ? new Intl.DateTimeFormat(locale, format).format(value) : placeholder;\n  return (\n    <Popover open={open} onOpenChange={setOpen}>\n      <div className=\"relative w-full\">\n        <PopoverTrigger\n          data-slot=\"date-picker\"\n          disabled={disabled}\n          aria-invalid={invalid || undefined}\n          aria-label={ariaLabel ? `${ariaLabel}${value ? `, ${text}` : \"\"}` : undefined}\n          className={cn(\n            \"flex h-9 w-full min-w-0 items-center gap-2 rounded-md border border-input bg-background px-3 text-start text-sm shadow-xs outline-none transition-[color,border-color,box-shadow] duration-150 hover:bg-accent/40 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:bg-input/30\",\n            !value && \"text-muted-foreground\",\n            clearable && value && \"pe-10\",\n            className,\n          )}\n          {...props}\n        >\n          <CalendarDays aria-hidden=\"true\" className=\"size-4 shrink-0 text-muted-foreground\" />\n          <span className=\"truncate\">{text}</span>\n        </PopoverTrigger>\n        {clearable && value && !disabled && (\n          <button\n            type=\"button\"\n            aria-label={msg(\"date-picker.clearDate\", \"Clear date\")}\n            onClick={() => choose(undefined)}\n            className=\"absolute top-1/2 end-2 inline-flex size-6 -translate-y-1/2 items-center justify-center rounded text-muted-foreground outline-none hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/50\"\n          >\n            <X aria-hidden=\"true\" className=\"size-3.5\" />\n          </button>\n        )}\n        {name && <input type=\"hidden\" name={name} value={value ? toIso(value) : \"\"} />}\n      </div>\n      <PopoverContent\n        label={ariaLabel ?? placeholder}\n        align=\"start\"\n        className=\"w-auto max-w-[calc(100vw-1.5rem)] gap-0 p-0\"\n      >\n        <div className=\"flex flex-col sm:flex-row\">\n          {presets && presets.length > 0 && (\n            <div className=\"flex flex-wrap gap-1.5 border-b p-3 sm:w-36 sm:flex-col sm:flex-nowrap sm:border-e sm:border-b-0\">\n              {presets.map((p) => (\n                <button\n                  key={p.label}\n                  type=\"button\"\n                  aria-pressed={sameDay(p.date, value)}\n                  onClick={() => choose(p.date)}\n                  className=\"h-8 rounded-md px-2.5 text-start text-sm outline-none transition-colors hover:bg-accent focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-pressed:bg-accent aria-pressed:font-medium\"\n                >\n                  {p.label}\n                </button>\n              ))}\n            </div>\n          )}\n          <Calendar\n            className=\"p-3\"\n            mode=\"single\"\n            selected={value}\n            defaultMonth={value}\n            onSelect={choose}\n            disabled={[...(minDate ? [{ before: minDate }] : []), ...(maxDate ? [{ after: maxDate }] : [])]}\n            captionLayout={dropdowns ? \"dropdown\" : \"label\"}\n            startMonth={dropdowns ? (minDate ?? new Date(new Date().getFullYear() - 100, 0)) : undefined}\n            endMonth={dropdowns ? (maxDate ?? new Date(new Date().getFullYear() + 10, 11)) : undefined}\n          />\n        </div>\n      </PopoverContent>\n    </Popover>\n  );\n}\n\nexport { DatePicker, type DatePickerProps, type DatePickerPreset };\n",
      "type": "registry:ui",
      "target": "@components/ballmac/date-picker.tsx"
    }
  ],
  "meta": {
    "schema": 1,
    "tier": "free",
    "tags": [
      "date",
      "input",
      "popover",
      "form"
    ],
    "version": "1.0.0",
    "updated": "2026-09-30",
    "ai": {
      "summary": "Give it a defaultValue or value and onValueChange. presets add quick picks, dropdowns adds month and year selects, name submits YYYY-MM-DD.",
      "whenToUse": [
        "A single date in a form",
        "Deadlines, birthdays and delivery dates",
        "When quick picks like Tomorrow help"
      ],
      "whenNotToUse": [
        "A date range; use date-range-picker",
        "An always-visible calendar; use calendar"
      ],
      "composesWith": [
        "field",
        "calendar",
        "popover"
      ],
      "a11y": [
        {
          "keys": "Enter / Space / ArrowDown",
          "action": "Opens the calendar from the trigger"
        },
        {
          "keys": "Arrow keys",
          "action": "Move between days"
        },
        {
          "keys": "Escape",
          "action": "Closes and returns focus to the trigger"
        }
      ],
      "customization": [
        "presets",
        "dropdowns for distant dates",
        "minDate and maxDate",
        "format (Intl options) and locale",
        "clearable and invalid"
      ]
    },
    "examples": [
      "date-picker-demo",
      "date-picker-states"
    ],
    "url": "https://ui.ballmac.com/components/date-picker"
  },
  "categories": [
    "forms"
  ],
  "type": "registry:ui"
}