{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"date-picker","type":"registry:ui","title":"Date Picker","description":"A button that opens a calendar in a popover.","dependencies":["lucide-react"],"registryDependencies":["https://dashral.com/r/utils.json","https://dashral.com/r/lib-locale.json","https://dashral.com/r/button.json","https://dashral.com/r/calendar.json","https://dashral.com/r/field.json","https://dashral.com/r/input.json","https://dashral.com/r/popover.json"],"files":[{"path":"registry/dashral/date-picker.tsx","type":"registry:file","target":"components/dashral/date-picker.tsx","content":"\"use client\"\n\nimport * as React from \"react\"\nimport { CalendarIcon, ClockIcon } from \"lucide-react\"\n\nimport { useLocale } from \"./lib/locale\"\nimport { cn } from \"./lib/utils\"\nimport { FieldWrapper, splitFieldProps, useFieldControl, type FieldClassNames, type FieldControlProps, type FieldOwnProps } from \"./field\"\nimport { Button } from \"./button\"\nimport { Calendar } from \"./calendar\"\nimport { Input } from \"./input\"\nimport { PopoverRoot, PopoverContent, PopoverTrigger } from \"./popover\"\n\ntype DatePickerProps = FieldOwnProps & {\n  /** Controlled value. Omit it (and use defaultValue) for an uncontrolled picker. */\n  value?: Date\n  defaultValue?: Date\n  onValueChange?: (date: Date | undefined) => void\n  placeholder?: string\n  /** BCP 47 locale for the button label, e.g. \"id-ID\". */\n  locale?: string\n  formatOptions?: Intl.DateTimeFormatOptions\n  /** Days that can't be picked, e.g. `{ before: new Date() }` — any DayPicker matcher. */\n  disabledDays?: React.ComponentProps<typeof Calendar>[\"disabled\"]\n  /**\n   * A time too: a field for it under the calendar and a Done button. Picking a day keeps the panel open; the value is\n   * the day at that time, and the label shows both.\n   */\n  withTime?: boolean\n  /** With withTime: the time a day gets when none was set yet, \"HH:MM\". */\n  defaultTime?: string\n  /** The whole picker can't be used. */\n  disabled?: boolean\n  required?: boolean\n  id?: string\n  /** The trigger's classes. */\n  className?: string\n  \"aria-label\"?: string\n  \"aria-invalid\"?: React.AriaAttributes[\"aria-invalid\"]\n  classNames?: FieldClassNames & { trigger?: string; content?: string }\n}\n\nfunction DatePicker(props: DatePickerProps) {\n  const [field, { required: _required, ...control }] = splitFieldProps(props)\n  return (\n    <FieldWrapper {...field}>\n      <DatePickerControl {...control} />\n    </FieldWrapper>\n  )\n}\n\nfunction DatePickerControl({\n  value,\n  defaultValue,\n  onValueChange,\n  placeholder: placeholderProp,\n  locale: localeProp,\n  formatOptions: formatOptionsProp,\n  disabledDays,\n  withTime = false,\n  defaultTime = \"09:00\",\n  classNames,\n  className,\n  \"aria-label\": ariaLabel,\n  ...rest\n}: Omit<DatePickerProps, keyof FieldOwnProps | \"required\">) {\n  const { t, locale: contextLocale } = useLocale()\n  const locale = localeProp ?? contextLocale\n  const placeholder = placeholderProp ?? t(\"pickDate\")\n  const { id, disabled, ...aria } = useFieldControl<FieldControlProps>({ id: rest.id, disabled: rest.disabled, \"aria-invalid\": rest[\"aria-invalid\"] })\n  const [open, setOpen] = React.useState(false)\n  const [internal, setInternal] = React.useState<Date | undefined>(defaultValue)\n  const current = value === undefined && onValueChange === undefined ? internal : value\n  const formatOptions = formatOptionsProp ?? (withTime ? { dateStyle: \"medium\", timeStyle: \"short\" } : { dateStyle: \"medium\" })\n  // The time field's text while it's typed: \"HH:MM\", 24-hour, as the value is stored.\n  const timeOf = (date: Date | undefined) =>\n    date ? `${String(date.getHours()).padStart(2, \"0\")}:${String(date.getMinutes()).padStart(2, \"0\")}` : defaultTime\n  const [time, setTime] = React.useState(() => timeOf(current))\n\n  function commit(date: Date | undefined) {\n    setInternal(date)\n    onValueChange?.(date)\n  }\n  /** The day at the time in the field (\"14:30\"), or the day's own time when the field doesn't hold one yet. */\n  function at(day: Date, hhmm: string) {\n    const match = hhmm.match(/^(\\d{1,2}):(\\d{2})$/)\n    const next = new Date(day)\n    if (match && Number(match[1]) < 24 && Number(match[2]) < 60) next.setHours(Number(match[1]), Number(match[2]), 0, 0)\n    return next\n  }\n\n  function select(date: Date | undefined) {\n    if (withTime) {\n      // The day changes; the time stays what it was (or the field's), and the panel stays open for it.\n      commit(date && at(date, time))\n      return\n    }\n    commit(date)\n    setOpen(false)\n  }\n\n  return (\n    <PopoverRoot\n      open={open}\n      onOpenChange={(next) => {\n        setOpen(next)\n        // Opening, the time field starts from the value's time.\n        if (next) setTime(timeOf(current))\n      }}\n    >\n      <PopoverTrigger asChild>\n        <Button\n          id={id}\n          variant=\"outline\"\n          disabled={disabled}\n          aria-label={ariaLabel}\n          aria-describedby={aria[\"aria-describedby\"]}\n          aria-invalid={aria[\"aria-invalid\"]}\n          className={cn(\n            \"w-full justify-start font-normal aria-invalid:border-destructive\",\n            // Looks like the other fields (Input, Select), not like a button.\n            \"border-input bg-transparent hover:bg-transparent focus-visible:border-ring focus-visible:ring-ring/15 motion-safe:enabled:not-aria-busy:active:scale-100\",\n            current ? \"hover:text-foreground\" : \"text-muted-foreground hover:text-muted-foreground\",\n            className,\n            classNames?.trigger\n          )}\n        >\n          <CalendarIcon aria-hidden />\n          {current ? new Intl.DateTimeFormat(locale, formatOptions).format(current) : placeholder}\n        </Button>\n      </PopoverTrigger>\n      <PopoverContent className={cn(\"w-auto p-0\", classNames?.content)} align=\"start\">\n        <Calendar mode=\"single\" selected={current} onSelect={select} defaultMonth={current} disabled={disabledDays} autoFocus />\n        {withTime && (\n          <form\n            className=\"flex items-center gap-2 border-t p-3\"\n            onSubmit={(event) => {\n              event.preventDefault()\n              setOpen(false)\n            }}\n          >\n            <Input\n              aria-label={t(\"time\")}\n              format=\"time\"\n              placeholder=\"HH:MM\"\n              prefix={<ClockIcon />}\n              value={time}\n              onChange={(event) => {\n                setTime(event.target.value)\n                // A whole time, typed: the picked day takes it at once.\n                if (current && /^\\d{2}:\\d{2}$/.test(event.target.value)) commit(at(current, event.target.value))\n              }}\n              className=\"h-8 w-28 tabular-nums\"\n            />\n            <Button type=\"submit\" size=\"sm\" className=\"ms-auto\">\n              {t(\"done\")}\n            </Button>\n          </form>\n        )}\n      </PopoverContent>\n    </PopoverRoot>\n  )\n}\n\nexport { DatePicker }\n"}]}