{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"date-picker","type":"registry:ui","title":"Date picker","description":"Date and range pickers on a sunken trigger: a popover holds the calendar, presets like Last 7 days and Apply/Cancel, plus a segmented date field whose digits roll as you step them.","author":"GetUrUI <hello@geturui.dev>","dependencies":["motion","lucide-react"],"registryDependencies":["@geturui/calendar","@geturui/field","@geturui/popover","@geturui/button"],"files":[{"path":"registry/geturui/ui/date-picker.tsx","type":"registry:ui","target":"components/ui/date-picker.tsx","content":"\"use client\";\n\nimport * as React from \"react\";\nimport { motion, useReducedMotion } from \"motion/react\";\nimport { CalendarDays, ChevronDown } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\nimport { Button } from \"@/components/ui/button\";\nimport { Popover } from \"@/components/ui/popover\";\nimport { Field, fieldWellClasses, type FieldSize } from \"@/components/ui/field\";\nimport { DateField } from \"@/components/ui/date-field\";\nimport {\n  Calendar,\n  addDays,\n  addMonths,\n  compareDays,\n  daysBetween,\n  formatDate,\n  formatRange,\n  isSameDay,\n  startOfMonth,\n  useToday,\n  type DateRange,\n  type WeekStart,\n} from \"@/components/ui/calendar\";\n\nexport { DateField, type DateFieldProps } from \"@/components/ui/date-field\";\nexport type { DateRange } from \"@/components/ui/calendar\";\n\n/** A named range, computed from today so it stays deterministic. */\nexport interface DatePreset {\n  id: string;\n  label: string;\n  range: (today: Date) => DateRange;\n}\n\n/** Today, Yesterday, Last 7 / 30 / 90 days, This month, Last month, Year to date. */\nexport const defaultRangePresets: DatePreset[] = [\n  { id: \"today\", label: \"Today\", range: (t) => ({ start: t, end: t }) },\n  { id: \"yesterday\", label: \"Yesterday\", range: (t) => ({ start: addDays(t, -1), end: addDays(t, -1) }) },\n  { id: \"7d\", label: \"Last 7 days\", range: (t) => ({ start: addDays(t, -6), end: t }) },\n  { id: \"30d\", label: \"Last 30 days\", range: (t) => ({ start: addDays(t, -29), end: t }) },\n  { id: \"90d\", label: \"Last 90 days\", range: (t) => ({ start: addDays(t, -89), end: t }) },\n  { id: \"month\", label: \"This month\", range: (t) => ({ start: startOfMonth(t), end: t }) },\n  {\n    id: \"last-month\",\n    label: \"Last month\",\n    range: (t) => ({ start: addMonths(startOfMonth(t), -1), end: addDays(startOfMonth(t), -1) }),\n  },\n  { id: \"ytd\", label: \"Year to date\", range: (t) => ({ start: new Date(t.getFullYear(), 0, 1), end: t }) },\n];\n\ninterface PickerBaseProps {\n  label?: React.ReactNode;\n  hint?: React.ReactNode;\n  error?: React.ReactNode;\n  required?: boolean;\n  disabled?: boolean;\n  /** Trigger height: 32 / 40 / 48. @default \"md\" */\n  size?: FieldSize;\n  placeholder?: string;\n  /** The day treated as today (marker, presets, Today button). Omit to read the clock after hydration. */\n  today?: Date;\n  min?: Date;\n  max?: Date;\n  disabledDates?: Date[];\n  isDateDisabled?: (date: Date) => boolean;\n  highlightedDates?: Date[];\n  /** @default 0 (Sunday) */\n  weekStartsOn?: WeekStart;\n  open?: boolean;\n  defaultOpen?: boolean;\n  onOpenChange?: (open: boolean) => void;\n  /** Panel alignment under the trigger. @default \"start\" */\n  align?: \"start\" | \"center\" | \"end\";\n  className?: string;\n}\n\nexport interface DatePickerProps extends PickerBaseProps {\n  value?: Date | null;\n  defaultValue?: Date | null;\n  onValueChange?: (date: Date | null) => void;\n  /** Require Apply to commit. When false, picking a day commits and closes. @default true */\n  confirm?: boolean;\n}\n\nexport interface DateRangePickerProps extends PickerBaseProps {\n  value?: DateRange;\n  defaultValue?: DateRange;\n  onValueChange?: (range: DateRange) => void;\n  /** Quick ranges in a side column. Pass false to hide. @default defaultRangePresets */\n  presets?: DatePreset[] | false;\n  /** Months shown. Defaults to 2 from 640px up, 1 below. */\n  numberOfMonths?: 1 | 2;\n}\n\nconst EMPTY: DateRange = { start: null, end: null };\nconst subscribeWide = (cb: () => void) => {\n  const mq = window.matchMedia(\"(min-width: 640px)\");\n  mq.addEventListener(\"change\", cb);\n  return () => mq.removeEventListener(\"change\", cb);\n};\nconst useWide = () =>\n  React.useSyncExternalStore(\n    subscribeWide,\n    () => window.matchMedia(\"(min-width: 640px)\").matches,\n    () => false,\n  );\n\nfunction useOpen(open: boolean | undefined, defaultOpen: boolean | undefined, onOpenChange?: (v: boolean) => void) {\n  const [own, setOwn] = React.useState(Boolean(defaultOpen));\n  const isOpen = open ?? own;\n  const set = (v: boolean) => {\n    if (open === undefined) setOwn(v);\n    onOpenChange?.(v);\n  };\n  return [isOpen, set] as const;\n}\n\ninterface TriggerProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n  size: FieldSize;\n  invalid: boolean;\n  open: boolean;\n  text: string | null;\n  placeholder: string;\n  valueId: string;\n}\n\n/** Sunken-well trigger button. Popover clones it to add onClick and aria-expanded. */\nconst Trigger = React.forwardRef<HTMLButtonElement, TriggerProps>(function Trigger(\n  { size, invalid, open, text, placeholder, valueId, disabled, className, ...props },\n  ref,\n) {\n  const reduce = useReducedMotion();\n  return (\n    <button\n      ref={ref}\n      type=\"button\"\n      disabled={disabled}\n      className={fieldWellClasses({\n        size,\n        invalid,\n        disabled,\n        className: cn(\n          \"w-full cursor-pointer text-left outline-none focus-visible:border-volt-ink focus-visible:shadow-[0_0_0_3px_var(--volt-soft)]\",\n          open && \"border-volt-ink\",\n          className,\n        ),\n      })}\n      {...props}\n    >\n      <CalendarDays aria-hidden className=\"size-4 shrink-0\" />\n      <span id={valueId} className={cn(\"min-w-0 flex-1 truncate tabular-nums\", text ? \"text-ink\" : \"text-ink-subtle\")}>\n        {text ?? placeholder}\n      </span>\n      <motion.span\n        aria-hidden\n        animate={{ rotate: open && !reduce ? 180 : 0 }}\n        transition={{ type: \"spring\", stiffness: 420, damping: 28 }}\n        className=\"grid\"\n      >\n        <ChevronDown className=\"size-4\" />\n      </motion.span>\n    </button>\n  );\n});\n\n/** Single-date picker: a sunken trigger opens a calendar with a typed date field, Today shortcut and Apply/Cancel. */\nexport function DatePicker({\n  value,\n  defaultValue = null,\n  onValueChange,\n  label,\n  hint,\n  error,\n  required,\n  disabled,\n  size = \"md\",\n  placeholder = \"Select a date\",\n  today: todayProp,\n  min,\n  max,\n  disabledDates,\n  isDateDisabled,\n  highlightedDates,\n  weekStartsOn,\n  open,\n  defaultOpen,\n  onOpenChange,\n  confirm = true,\n  align = \"start\",\n  className,\n}: DatePickerProps) {\n  const today = useToday(todayProp);\n  const valueId = React.useId();\n  const [own, setOwn] = React.useState<Date | null>(defaultValue);\n  const current = value !== undefined ? value : own;\n  const [draft, setDraft] = React.useState<Date | null>(current);\n  const [month, setMonth] = React.useState<Date | undefined>(current ?? todayProp ?? undefined);\n  const [isOpen, setOpen] = useOpen(open, defaultOpen, onOpenChange);\n\n  const commit = (d: Date | null) => {\n    if (value === undefined) setOwn(d);\n    onValueChange?.(d);\n  };\n  const handleOpen = (v: boolean) => {\n    if (v) {\n      setDraft(current);\n      setMonth(current ?? today ?? undefined);\n    }\n    setOpen(v);\n  };\n\n  return (\n    <Field label={label} hint={hint} error={error} required={required} disabled={disabled} className={className}>\n      {(ids) => (\n        <div className=\"grid\">\n          <Popover\n            open={isOpen}\n            onOpenChange={handleOpen}\n            align={align}\n            label=\"Choose a date\"\n            className=\"w-max max-w-[calc(100vw-16px)] p-0\"\n            trigger={\n              <Trigger\n                id={ids.id}\n                size={size}\n                invalid={ids.invalid}\n                open={isOpen}\n                disabled={disabled}\n                text={current ? formatDate(current) : null}\n                placeholder={placeholder}\n                valueId={valueId}\n                aria-labelledby={label ? `${ids.labelId} ${valueId}` : undefined}\n                aria-describedby={ids.describedBy}\n                aria-invalid={ids.invalid || undefined}\n              />\n            }\n          >\n            {({ close }) => (\n              <div className=\"grid w-min gap-3 p-4\">\n                <div className=\"flex items-center gap-2\">\n                  <DateField\n                    bare\n                    size=\"sm\"\n                    aria-label=\"Date\"\n                    value={draft}\n                    min={min}\n                    max={max}\n                    onValueChange={(d) => {\n                      setDraft(d);\n                      if (d) setMonth(d);\n                    }}\n                    className=\"flex-1\"\n                  />\n                  <Button\n                    size=\"sm\"\n                    variant=\"secondary\"\n                    disabled={!today}\n                    onClick={() => {\n                      if (!today) return;\n                      setDraft(today);\n                      setMonth(today);\n                    }}\n                  >\n                    Today\n                  </Button>\n                </div>\n                <Calendar\n                  value={draft}\n                  onValueChange={(d) => {\n                    setDraft(d);\n                    if (!confirm) {\n                      commit(d);\n                      close();\n                    }\n                  }}\n                  month={month}\n                  onMonthChange={setMonth}\n                  today={todayProp}\n                  min={min}\n                  max={max}\n                  disabledDates={disabledDates}\n                  isDateDisabled={isDateDisabled}\n                  highlightedDates={highlightedDates}\n                  weekStartsOn={weekStartsOn}\n                />\n                {confirm && (\n                  <div className=\"flex items-center justify-end gap-2 border-t border-border pt-3\">\n                    <Button size=\"sm\" variant=\"ghost\" onClick={close}>\n                      Cancel\n                    </Button>\n                    <Button\n                      size=\"sm\"\n                      disabled={isSameDay(draft, current) || (!draft && !current)}\n                      onClick={() => {\n                        commit(draft);\n                        close();\n                      }}\n                    >\n                      Apply\n                    </Button>\n                  </div>\n                )}\n              </div>\n            )}\n          </Popover>\n        </div>\n      )}\n    </Field>\n  );\n}\n\n/** Range picker: presets column, one or two months with a sweeping range band, typed start/end fields and Apply/Cancel. */\nexport function DateRangePicker({\n  value,\n  defaultValue = EMPTY,\n  onValueChange,\n  label,\n  hint,\n  error,\n  required,\n  disabled,\n  size = \"md\",\n  placeholder = \"Select dates\",\n  today: todayProp,\n  min,\n  max,\n  disabledDates,\n  isDateDisabled,\n  highlightedDates,\n  weekStartsOn,\n  open,\n  defaultOpen,\n  onOpenChange,\n  align = \"start\",\n  presets = defaultRangePresets,\n  numberOfMonths,\n  className,\n}: DateRangePickerProps) {\n  const reduce = useReducedMotion();\n  const uid = React.useId();\n  const valueId = React.useId();\n  const today = useToday(todayProp);\n  const wide = useWide();\n  const months = numberOfMonths ?? (wide ? 2 : 1);\n  const [own, setOwn] = React.useState<DateRange>(defaultValue);\n  const current = value ?? own;\n  const [draft, setDraft] = React.useState<DateRange>(current);\n  const [month, setMonth] = React.useState<Date | undefined>(current.start ?? todayProp ?? undefined);\n  const [isOpen, setOpen] = useOpen(open, defaultOpen, onOpenChange);\n\n  const monthFor = (r: DateRange) => {\n    const anchor = r.end ?? r.start ?? today;\n    return anchor ? addMonths(startOfMonth(anchor), -(months - 1)) : undefined;\n  };\n  const handleOpen = (v: boolean) => {\n    if (v) {\n      setDraft(current);\n      setMonth(monthFor(current));\n    }\n    setOpen(v);\n  };\n  const commit = (r: DateRange) => {\n    if (value === undefined) setOwn(r);\n    onValueChange?.(r);\n  };\n\n  const sameRange = (a: DateRange, b: DateRange) =>\n    (a.start ? !!b.start && isSameDay(a.start, b.start) : !b.start) && (a.end ? !!b.end && isSameDay(a.end, b.end) : !b.end);\n  const activePreset = presets && today ? presets.find((p) => sameRange(p.range(today), draft))?.id : undefined;\n  const length = draft.start && draft.end ? daysBetween(draft.start, draft.end) + 1 : 0;\n  const complete = !!draft.start && !!draft.end;\n\n  return (\n    <Field label={label} hint={hint} error={error} required={required} disabled={disabled} className={className}>\n      {(ids) => (\n        <div className=\"grid\">\n          <Popover\n            open={isOpen}\n            onOpenChange={handleOpen}\n            align={align}\n            label=\"Choose a date range\"\n            className=\"w-max max-w-[calc(100vw-16px)] overflow-hidden p-0\"\n            trigger={\n              <Trigger\n                id={ids.id}\n                size={size}\n                invalid={ids.invalid}\n                open={isOpen}\n                disabled={disabled}\n                text={current.start ? formatRange(current) : null}\n                placeholder={placeholder}\n                valueId={valueId}\n                aria-labelledby={label ? `${ids.labelId} ${valueId}` : undefined}\n                aria-describedby={ids.describedBy}\n                aria-invalid={ids.invalid || undefined}\n              />\n            }\n          >\n            {({ close }) => (\n              <div className=\"flex flex-col sm:flex-row\">\n                {presets && presets.length > 0 && (\n                  <div\n                    role=\"group\"\n                    aria-label=\"Quick ranges\"\n                    className=\"flex gap-1 overflow-x-auto border-b border-border p-2 sm:w-40 sm:flex-col sm:overflow-visible sm:border-b-0 sm:border-r sm:p-3\"\n                  >\n                    {presets.map((p) => {\n                      const active = activePreset === p.id;\n                      return (\n                        <button\n                          key={p.id}\n                          type=\"button\"\n                          aria-pressed={active}\n                          disabled={!today}\n                          onClick={() => {\n                            if (!today) return;\n                            const r = p.range(today);\n                            setDraft(r);\n                            setMonth(monthFor(r));\n                          }}\n                          className={cn(\n                            \"relative shrink-0 whitespace-nowrap rounded-full px-3 py-1.5 text-left text-[13px] font-medium outline-none transition-colors duration-150 focus-visible:shadow-[var(--focus-ring)] sm:rounded-[10px]\",\n                            active ? \"text-volt-ink\" : \"text-ink-muted hover:bg-surface-hover hover:text-ink\",\n                          )}\n                        >\n                          {active && (\n                            <motion.span\n                              aria-hidden\n                              layoutId={reduce ? undefined : `${uid}-preset`}\n                              transition={{ type: \"spring\", stiffness: 420, damping: 34 }}\n                              className=\"absolute inset-0 rounded-[inherit] bg-volt-soft\"\n                            />\n                          )}\n                          <span className=\"relative\">{p.label}</span>\n                        </button>\n                      );\n                    })}\n                  </div>\n                )}\n                <div className=\"grid w-min gap-3 p-4\">\n                  <Calendar\n                    mode=\"range\"\n                    value={draft}\n                    onValueChange={setDraft}\n                    month={month}\n                    onMonthChange={setMonth}\n                    numberOfMonths={months}\n                    today={todayProp}\n                    min={min}\n                    max={max}\n                    disabledDates={disabledDates}\n                    isDateDisabled={isDateDisabled}\n                    highlightedDates={highlightedDates}\n                    weekStartsOn={weekStartsOn}\n                  />\n                  <div className=\"flex flex-wrap items-center gap-x-3 gap-y-3 border-t border-border pt-3\">\n                    <div className=\"flex items-center gap-2\">\n                      <DateField\n                        bare\n                        size=\"sm\"\n                        icon={null}\n                        aria-label=\"Start date\"\n                        value={draft.start}\n                        min={min}\n                        max={max}\n                        onValueChange={(d) => {\n                          const next = { start: d, end: d && draft.end && compareDays(d, draft.end) <= 0 ? draft.end : null };\n                          setDraft(next);\n                          if (d) setMonth(monthFor({ start: d, end: null }));\n                        }}\n                        className=\"w-[120px]\"\n                      />\n                      <span aria-hidden className=\"text-ink-subtle\">\n                        –\n                      </span>\n                      <DateField\n                        bare\n                        size=\"sm\"\n                        icon={null}\n                        aria-label=\"End date\"\n                        value={draft.end}\n                        min={draft.start ?? min}\n                        max={max}\n                        onValueChange={(d) => {\n                          setDraft({ start: draft.start ?? d, end: d });\n                          if (d) setMonth(monthFor({ start: null, end: d }));\n                        }}\n                        className=\"w-[120px]\"\n                      />\n                    </div>\n                    <p className=\"text-xs text-ink-subtle\" aria-live=\"polite\">\n                      {complete ? `${length} ${length === 1 ? \"day\" : \"days\"}` : draft.start ? \"Pick an end date\" : \"Pick a start date\"}\n                    </p>\n                    <div className=\"ml-auto flex items-center gap-2\">\n                      <Button size=\"sm\" variant=\"ghost\" onClick={close}>\n                        Cancel\n                      </Button>\n                      <Button\n                        size=\"sm\"\n                        disabled={!complete || sameRange(draft, current)}\n                        onClick={() => {\n                          commit(draft);\n                          close();\n                        }}\n                      >\n                        Apply\n                      </Button>\n                    </div>\n                  </div>\n                </div>\n              </div>\n            )}\n          </Popover>\n        </div>\n      )}\n    </Field>\n  );\n}\n"},{"path":"registry/geturui/ui/date-field.tsx","type":"registry:ui","target":"components/ui/date-field.tsx","content":"\"use client\";\n\nimport * as React from \"react\";\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\";\nimport { CalendarDays } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\nimport { Field, fieldWellClasses, type FieldSize } from \"@/components/ui/field\";\nimport { MONTH_NAMES, compareDays, daysInMonth, startOfDay, toISODate } from \"@/components/ui/calendar\";\n\ntype SegKey = \"month\" | \"day\" | \"year\";\ntype Segs = Record<SegKey, number | null>;\n\nexport interface DateFieldProps {\n  value?: Date | null;\n  defaultValue?: Date | null;\n  /** Called with a full date once every segment is filled, and with null when one is cleared. */\n  onValueChange?: (date: Date | null) => void;\n  label?: React.ReactNode;\n  hint?: React.ReactNode;\n  error?: React.ReactNode;\n  required?: boolean;\n  disabled?: boolean;\n  size?: FieldSize;\n  /** Dates before this are clamped up when committed. */\n  min?: Date;\n  /** Dates after this are clamped down when committed. */\n  max?: Date;\n  /** Segment order. @default \"mdy\" */\n  order?: \"mdy\" | \"dmy\" | \"ymd\";\n  /** Leading glyph. Pass null to hide. */\n  icon?: React.ReactNode;\n  /** Right side of the well, e.g. a calendar button. */\n  trailing?: React.ReactNode;\n  /** Submits the value as YYYY-MM-DD under this name. */\n  name?: string;\n  /** Accessible name when there is no visible label. */\n  \"aria-label\"?: string;\n  /** Render only the well, without label and hint (for use inside other controls). */\n  bare?: boolean;\n  className?: string;\n}\n\nconst EMPTY: Segs = { month: null, day: null, year: null };\nconst toSegs = (d: Date | null | undefined): Segs => (d ? { month: d.getMonth() + 1, day: d.getDate(), year: d.getFullYear() } : EMPTY);\nconst META: Record<SegKey, { label: string; placeholder: string; min: number; width: string }> = {\n  month: { label: \"Month\", placeholder: \"mm\", min: 1, width: \"min-w-[2.1ch]\" },\n  day: { label: \"Day\", placeholder: \"dd\", min: 1, width: \"min-w-[2.1ch]\" },\n  year: { label: \"Year\", placeholder: \"yyyy\", min: 1, width: \"min-w-[4.2ch]\" },\n};\n\nfunction maxOf(seg: SegKey, s: Segs) {\n  if (seg === \"month\") return 12;\n  if (seg === \"year\") return 9999;\n  return s.month ? daysInMonth(s.year ?? 2000, s.month - 1) : 31;\n}\n\n/** Typed date input in month, day and year segments: arrows step a segment with a rolling digit, a volt pill slides to the focused one. */\nexport function DateField({\n  value,\n  defaultValue,\n  onValueChange,\n  label,\n  hint,\n  error,\n  required,\n  disabled,\n  size = \"md\",\n  min,\n  max,\n  order = \"mdy\",\n  icon,\n  trailing,\n  name,\n  bare,\n  className,\n  ...rest\n}: DateFieldProps) {\n  const reduce = useReducedMotion();\n  const uid = React.useId();\n  const [segs, setSegs] = React.useState<Segs>(() => toSegs(value !== undefined ? value : defaultValue));\n  const [focused, setFocused] = React.useState<SegKey | null>(null);\n  const [roll, setRoll] = React.useState(1);\n  const typed = React.useRef(\"\");\n  const wellRef = React.useRef<HTMLDivElement>(null);\n\n  const order3: SegKey[] =\n    order === \"dmy\" ? [\"day\", \"month\", \"year\"] : order === \"ymd\" ? [\"year\", \"month\", \"day\"] : [\"month\", \"day\", \"year\"];\n  const sep = order === \"ymd\" ? \"-\" : \"/\";\n\n  const build = (s: Segs): Date | null => {\n    if (!s.month || !s.day || !s.year || s.year < 1000) return null;\n    let d = new Date(s.year, s.month - 1, Math.min(s.day, daysInMonth(s.year, s.month - 1)));\n    if (min && compareDays(d, min) < 0) d = startOfDay(min);\n    if (max && compareDays(d, max) > 0) d = startOfDay(max);\n    return d;\n  };\n\n  // Follow a controlled value without clobbering a half-typed entry.\n  const valueKey = value === undefined ? undefined : value ? toISODate(value) : \"\";\n  const [prevKey, setPrevKey] = React.useState(valueKey);\n  if (valueKey !== prevKey) {\n    setPrevKey(valueKey);\n    const shown = build(segs);\n    // Skip echoes of our own change (including null while a segment is half-typed).\n    if (valueKey !== undefined && valueKey !== (shown ? toISODate(shown) : \"\")) setSegs(toSegs(value));\n  }\n\n  const update = (next: Segs) => {\n    const before = build(segs);\n    const after = build(next);\n    setSegs(after ? toSegs(after) : next);\n    const k = (d: Date | null) => (d ? toISODate(d) : \"\");\n    if (k(before) !== k(after)) onValueChange?.(after);\n  };\n\n  const focusSeg = (seg: SegKey | undefined) => {\n    if (!seg) return;\n    wellRef.current?.querySelector<HTMLElement>(`[data-seg=\"${seg}\"]`)?.focus();\n  };\n  const neighbour = (seg: SegKey, step: 1 | -1) => order3[order3.indexOf(seg) + step];\n\n  const onKeyDown = (seg: SegKey) => (e: React.KeyboardEvent) => {\n    const cur = segs[seg];\n    const hi = maxOf(seg, segs);\n    const lo = META[seg].min;\n    if (e.key === \"ArrowUp\" || e.key === \"ArrowDown\") {\n      e.preventDefault();\n      const step = e.key === \"ArrowUp\" ? 1 : -1;\n      typed.current = \"\";\n      setRoll(step);\n      // Empty segments start from the clock; this only runs in an event, never during render.\n      const now = new Date();\n      const start = seg === \"year\" ? now.getFullYear() : seg === \"month\" ? now.getMonth() + 1 : now.getDate();\n      const nextVal =\n        cur === null\n          ? Math.min(start, hi)\n          : seg === \"year\"\n            ? Math.max(1000, cur + step)\n            : ((cur - lo + step + (hi - lo + 1)) % (hi - lo + 1)) + lo;\n      update({ ...segs, [seg]: nextVal });\n      return;\n    }\n    if (e.key === \"ArrowLeft\" || e.key === \"ArrowRight\") {\n      e.preventDefault();\n      focusSeg(neighbour(seg, e.key === \"ArrowRight\" ? 1 : -1));\n      return;\n    }\n    if (e.key === \"Home\" || e.key === \"End\") {\n      e.preventDefault();\n      setRoll(e.key === \"Home\" ? -1 : 1);\n      update({ ...segs, [seg]: e.key === \"Home\" ? (seg === \"year\" ? 1000 : lo) : seg === \"year\" ? 9999 : hi });\n      return;\n    }\n    if (e.key === \"Backspace\" || e.key === \"Delete\") {\n      e.preventDefault();\n      if (typed.current.length > 1) {\n        typed.current = typed.current.slice(0, -1);\n        update({ ...segs, [seg]: Number(typed.current) });\n      } else if (cur !== null) {\n        typed.current = \"\";\n        update({ ...segs, [seg]: null });\n      } else focusSeg(neighbour(seg, -1));\n      return;\n    }\n    if (e.key === \"/\" || e.key === \"-\" || e.key === \".\") {\n      e.preventDefault();\n      focusSeg(neighbour(seg, 1));\n      return;\n    }\n    if (/^\\d$/.test(e.key)) {\n      e.preventDefault();\n      const digit = Number(e.key);\n      setRoll(1);\n      if (seg === \"year\") {\n        const buf = (typed.current + e.key).slice(-4);\n        typed.current = buf.length === 4 ? \"\" : buf;\n        update({ ...segs, year: Number(buf) });\n        if (buf.length === 4) focusSeg(neighbour(seg, 1));\n        return;\n      }\n      const limit = seg === \"month\" ? 12 : 31;\n      const combined = Number(typed.current + e.key);\n      if (typed.current && combined <= limit && combined >= 1) {\n        typed.current = \"\";\n        update({ ...segs, [seg]: combined });\n        focusSeg(neighbour(seg, 1));\n      } else if (digit * 10 > limit) {\n        typed.current = \"\";\n        update({ ...segs, [seg]: Math.max(digit, 1) });\n        if (digit > 0) focusSeg(neighbour(seg, 1));\n      } else {\n        typed.current = e.key;\n        update({ ...segs, [seg]: digit === 0 ? segs[seg] : digit });\n      }\n    }\n  };\n\n  const describe = (seg: SegKey, v: number | null) => {\n    if (v === null) return \"Empty\";\n    return seg === \"month\" ? MONTH_NAMES[v - 1] : String(v);\n  };\n  const show = (seg: SegKey, v: number | null) =>\n    v === null ? META[seg].placeholder : seg === \"year\" ? String(v) : String(v).padStart(2, \"0\");\n  const current = build(segs);\n\n  const well = (ids?: { id: string; labelId: string; describedBy?: string; invalid: boolean }) => (\n    <div\n      ref={wellRef}\n      role=\"group\"\n      id={ids?.id}\n      aria-labelledby={ids && label ? ids.labelId : undefined}\n      aria-label={ids && label ? undefined : rest[\"aria-label\"]}\n      aria-describedby={ids?.describedBy}\n      aria-disabled={disabled || undefined}\n      onMouseDown={(e) => {\n        if (disabled || (e.target as HTMLElement).closest(\"[data-seg],button\")) return;\n        e.preventDefault();\n        focusSeg(order3.find((s) => segs[s] === null) ?? order3[0]);\n      }}\n      className={fieldWellClasses({\n        size,\n        invalid: ids?.invalid ?? Boolean(error),\n        disabled,\n        className: cn(\"cursor-text\", bare && className),\n      })}\n    >\n      {icon !== null && (icon ?? <CalendarDays aria-hidden className=\"size-4 shrink-0\" />)}\n      <span className=\"flex flex-1 items-center font-mono text-ink\">\n        {order3.map((seg, i) => (\n          <React.Fragment key={seg}>\n            {i > 0 && (\n              <span aria-hidden className=\"px-0.5 text-ink-subtle\">\n                {sep}\n              </span>\n            )}\n            <span\n              role=\"spinbutton\"\n              data-seg={seg}\n              tabIndex={disabled ? -1 : 0}\n              aria-label={META[seg].label}\n              aria-valuemin={seg === \"year\" ? 1000 : META[seg].min}\n              aria-valuemax={maxOf(seg, segs)}\n              aria-valuenow={segs[seg] ?? undefined}\n              aria-valuetext={describe(seg, segs[seg])}\n              aria-required={required || undefined}\n              aria-invalid={(ids?.invalid ?? Boolean(error)) || undefined}\n              onKeyDown={onKeyDown(seg)}\n              onFocus={() => {\n                typed.current = \"\";\n                setFocused(seg);\n              }}\n              onBlur={() => setFocused((f) => (f === seg ? null : f))}\n              className={cn(\n                \"relative inline-flex justify-center rounded-[6px] px-0.5 py-px text-center tabular-nums caret-transparent outline-none focus-visible:shadow-none\",\n                META[seg].width,\n                segs[seg] === null && \"text-ink-subtle\",\n              )}\n            >\n              {focused === seg && (\n                <motion.span\n                  aria-hidden\n                  layoutId={reduce ? undefined : `${uid}-pill`}\n                  transition={{ type: \"spring\", stiffness: 520, damping: 36 }}\n                  className=\"absolute inset-0 rounded-[6px] bg-volt-soft ring-1 ring-volt-ink/40\"\n                />\n              )}\n              <span aria-hidden className=\"relative inline-grid overflow-hidden\">\n                <AnimatePresence mode=\"popLayout\" initial={false} custom={roll}>\n                  <motion.span\n                    key={show(seg, segs[seg])}\n                    custom={roll}\n                    initial={reduce ? { opacity: 0 } : { y: roll * 10, opacity: 0, filter: \"blur(3px)\" }}\n                    animate={{ y: 0, opacity: 1, filter: \"blur(0px)\" }}\n                    exit={reduce ? { opacity: 0 } : { y: roll * -10, opacity: 0, filter: \"blur(3px)\" }}\n                    transition={{ type: \"spring\", stiffness: 520, damping: 34 }}\n                    className={cn(\"block\", focused === seg && segs[seg] !== null && \"text-ink\")}\n                  >\n                    {show(seg, segs[seg])}\n                  </motion.span>\n                </AnimatePresence>\n              </span>\n            </span>\n          </React.Fragment>\n        ))}\n      </span>\n      {trailing}\n      {name && <input type=\"hidden\" name={name} value={current ? toISODate(current) : \"\"} />}\n    </div>\n  );\n\n  if (bare) return well();\n  return (\n    <Field label={label} hint={hint} error={error} required={required} disabled={disabled} className={className}>\n      {(ids) => well(ids)}\n    </Field>\n  );\n}\n"},{"path":"LICENSE-geturui.md","type":"registry:file","target":"LICENSE-geturui.md","content":"MIT License\n\nCopyright (c) 2026 geturui.com\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"}],"categories":["components","date picker","date range","presets","date field","form","calendar"],"meta":{"tier":"free","license":"MIT","docs":"https://geturui.com/docs/date-picker"}}