{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "date-picker",
  "type": "registry:ui",
  "title": "Date Picker",
  "description": "Type “next fri” or pick from the calendar; it reads back before committing.",
  "categories": [
    "dates"
  ],
  "dependencies": [
    "@base-ui/react",
    "motion"
  ],
  "registryDependencies": [
    "https://www.stealth.pm/r/tokens.json",
    "https://www.stealth.pm/r/cn.json",
    "https://www.stealth.pm/r/icons.json",
    "https://www.stealth.pm/r/motion.json",
    "https://www.stealth.pm/r/use-controllable-state.json",
    "https://www.stealth.pm/r/calendar.json"
  ],
  "files": [
    {
      "path": "components/ui/date-picker.tsx",
      "content": "\"use client\";\nimport { Popover } from \"@base-ui/react/popover\";\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\";\nimport { useId, useMemo, useRef, useState } from \"react\";\nimport { Calendar, addDays, addMonths, compareDays, toISODate, useResolvedLocale, useToday } from \"@/components/ui/calendar\";\nimport { cn } from \"@/lib/cn\";\nimport { Calendar as CalendarIcon, X } from \"@/lib/icons\";\nimport { ease } from \"@/lib/motion\";\nimport { useControllableState } from \"@/lib/use-controllable-state\";\n\n/* ------------------------------------------------------------------ */\n/* Parsing what people actually type into a date field.                */\n/* ------------------------------------------------------------------ */\n\nconst EN = \"en-US\";\nconst UNITS: Record<string, \"d\" | \"w\" | \"m\" | \"y\"> = {\n  d: \"d\", day: \"d\", days: \"d\",\n  w: \"w\", wk: \"w\", wks: \"w\", week: \"w\", weeks: \"w\",\n  m: \"m\", mo: \"m\", mos: \"m\", month: \"m\", months: \"m\",\n  y: \"y\", yr: \"y\", yrs: \"y\", year: \"y\", years: \"y\",\n};\n\nfunction names(locale: string, kind: \"month\" | \"weekday\") {\n  const out: string[][] = [];\n  for (const loc of new Set([locale, EN])) {\n    const long = new Intl.DateTimeFormat(loc, { [kind]: \"long\" });\n    const short = new Intl.DateTimeFormat(loc, { [kind]: \"short\" });\n    for (let i = 0; i < (kind === \"month\" ? 12 : 7); i++) {\n      // Jan 4 2026 is a Sunday, so weekday i lands on day 4 + i.\n      const d = kind === \"month\" ? new Date(2026, i, 1) : new Date(2026, 0, 4 + i);\n      (out[i] ??= []).push(long.format(d), short.format(d));\n    }\n  }\n  return out.map((list) => [...new Set(list.map((n) => n.toLowerCase().replace(/\\./g, \"\")))]);\n}\n\nfunction matchName(token: string, table: string[][]) {\n  if (token.length < 2 || /\\d/.test(token)) return -1;\n  return table.findIndex((list) => list.some((n) => n.startsWith(token) || (n.length >= 3 && token.startsWith(n))));\n}\n\n/** The locale's numeric order of day, month and year, e.g. [\"month\", \"day\", \"year\"] for en-US. */\nfunction numericOrder(locale: string) {\n  return new Intl.DateTimeFormat(locale, { day: \"numeric\", month: \"numeric\", year: \"numeric\" })\n    .formatToParts(new Date(2026, 10, 22))\n    .map((p) => p.type)\n    .filter((t): t is \"day\" | \"month\" | \"year\" => t === \"day\" || t === \"month\" || t === \"year\");\n}\n\nfunction build(y: number, m: number, d: number) {\n  if (m < 1 || m > 12 || d < 1 || d > 31 || y < 1 || y > 9999) return null;\n  const date = new Date(y, m - 1, d);\n  date.setFullYear(y);\n  return date.getMonth() === m - 1 && date.getDate() === d ? date : null;\n}\n\nconst fullYear = (s: string, fallback: number) => (!s ? fallback : s.length <= 2 ? 2000 + +s : +s);\n\n/**\n * Reads a typed date: ISO (2026-03-12), the locale's numeric order (03/12, 12.03.26), month names in the\n * locale or English (12 Mar, March 12 2026), and relative words (today, tomorrow, in 3 days, +2w, next fri).\n * Returns null when it can't be read. Relative words are English only.\n */\nexport function parseDateInput(text: string, { locale = EN, today }: { locale?: string; today: Date }): Date | null {\n  const s = text.trim().toLowerCase().replace(/\\s+/g, \" \");\n  if (!s) return null;\n  if (/^(today|tod|now)$/.test(s)) return today;\n  if (/^(tomorrow|tmr|tmrw|tom)$/.test(s)) return addDays(today, 1);\n  if (/^(yesterday|yday)$/.test(s)) return addDays(today, -1);\n\n  let m = /^(in )?([+-])? ?(\\d{1,3}) ?([a-z]+)( ago)?$/.exec(s);\n  if (m && UNITS[m[4]]) {\n    const n = +m[3] * (m[2] === \"-\" || m[5] ? -1 : 1);\n    const unit = UNITS[m[4]];\n    return unit === \"d\" ? addDays(today, n) : unit === \"w\" ? addDays(today, n * 7) : addMonths(today, unit === \"m\" ? n : n * 12);\n  }\n\n  m = /^(next |this |last )?([a-z\\u00c0-\\uffff]+)$/.exec(s);\n  if (m) {\n    const wd = matchName(m[2], names(locale, \"weekday\"));\n    if (wd >= 0) {\n      const ahead = (wd - today.getDay() + 7) % 7 || 7;\n      return m[1] === \"last \" ? addDays(today, ahead - 7 === 0 ? -7 : ahead - 7) : addDays(today, ahead);\n    }\n  }\n\n  m = /^(\\d{4})[-/.](\\d{1,2})[-/.](\\d{1,2})$/.exec(s);\n  if (m) return build(+m[1], +m[2], +m[3]);\n\n  const year = today.getFullYear();\n  const tokens = s.split(/[\\s,/.\\-]+/).filter(Boolean).map((t) => t.replace(/^(\\d+)(st|nd|rd|th|er|e|º)$/, \"$1\"));\n  const monthTable = names(locale, \"month\");\n  const monthAt = tokens.findIndex((t) => matchName(t, monthTable) >= 0);\n  if (monthAt >= 0) {\n    const nums = tokens.filter((_, i) => i !== monthAt);\n    if (nums.length > 2 || nums.some((t) => !/^\\d+$/.test(t))) return null;\n    const dayTok = nums.find((t) => t.length <= 2);\n    const yearTok = nums.find((t) => t !== dayTok);\n    if (!dayTok) return null;\n    return build(fullYear(yearTok ?? \"\", year), matchName(tokens[monthAt], monthTable) + 1, +dayTok);\n  }\n\n  if (tokens.every((t) => /^\\d+$/.test(t))) {\n    const order = numericOrder(locale);\n    if (tokens.length === 1 && tokens[0].length === 8) {\n      // 12032026 in the locale's order.\n      const t = tokens[0];\n      const parts: Record<string, string> = {};\n      let i = 0;\n      for (const k of order) {\n        const w = k === \"year\" ? 4 : 2;\n        parts[k] = t.slice(i, i + w);\n        i += w;\n      }\n      return build(+parts.year, +parts.month, +parts.day);\n    }\n    if (tokens.length === 1 && tokens[0].length <= 2) return build(year, today.getMonth() + 1, +tokens[0]);\n    if (tokens.length === 2) {\n      const dm = order.filter((k) => k !== \"year\");\n      const v = { [dm[0]]: +tokens[0], [dm[1]]: +tokens[1] } as Record<string, number>;\n      return build(year, v.month, v.day);\n    }\n    if (tokens.length === 3) {\n      const v = {} as Record<string, string>;\n      order.forEach((k, i) => (v[k] = tokens[i]));\n      return build(fullYear(v.year, year), +v.month, +v.day);\n    }\n  }\n  return null;\n}\n\n/* ------------------------------------------------------------------ */\n\nexport type DatePickerShortcut = { label: string; date: (today: Date) => Date };\n\nexport type DatePickerProps = Omit<React.ComponentProps<\"div\">, \"defaultValue\" | \"onChange\" | \"children\"> & {\n  value?: Date | null;\n  defaultValue?: Date | null;\n  onValueChange?: (date: Date | null) => void;\n  label?: React.ReactNode;\n  /** A hint under the field. Replaced by the parse preview while typing, and by errors. */\n  description?: React.ReactNode;\n  /** Defaults to the locale's numeric pattern, e.g. mm/dd/yyyy. */\n  placeholder?: string;\n  min?: Date;\n  max?: Date;\n  isDateDisabled?: (date: Date) => boolean;\n  locale?: string;\n  weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6;\n  /** How a committed date is written back into the field. */\n  formatOptions?: Intl.DateTimeFormatOptions;\n  /** Quick picks above the calendar, e.g. Today, Tomorrow, Next Monday. */\n  shortcuts?: DatePickerShortcut[];\n  /** Shows a clear button while there's a value. */\n  clearable?: boolean;\n  disabled?: boolean;\n  readOnly?: boolean;\n  required?: boolean;\n  /** Marks the field invalid from outside, e.g. after a failed submit. */\n  invalid?: boolean;\n  /** Shown under the field while invalid. */\n  errorMessage?: React.ReactNode;\n  /** Submits the value as YYYY-MM-DD under this name. */\n  name?: string;\n  size?: \"sm\" | \"md\";\n};\n\nconst DEFAULT_FORMAT: Intl.DateTimeFormatOptions = { day: \"numeric\", month: \"short\", year: \"numeric\" };\n\nexport function DatePicker({\n  value: valueProp,\n  defaultValue = null,\n  onValueChange,\n  label,\n  description,\n  placeholder,\n  min,\n  max,\n  isDateDisabled,\n  locale: localeProp,\n  weekStartsOn,\n  formatOptions = DEFAULT_FORMAT,\n  shortcuts,\n  clearable = true,\n  disabled = false,\n  readOnly = false,\n  required,\n  invalid: invalidProp = false,\n  errorMessage,\n  name,\n  size = \"md\",\n  className,\n  id: idProp,\n  ...rest\n}: DatePickerProps) {\n  const uid = useId();\n  const inputId = idProp ?? `${uid}-input`;\n  const messageId = `${uid}-message`;\n  const reduce = useReducedMotion();\n  const today = useToday();\n  const locale = useResolvedLocale(localeProp);\n\n  const [value, setValue] = useControllableState<Date | null>({ value: valueProp, defaultValue, onChange: onValueChange });\n  // null: the field shows the committed value. A string: what the person is typing.\n  const [draft, setDraft] = useState<string | null>(null);\n  const [error, setError] = useState<string | null>(null);\n  const [open, setOpen] = useState(false);\n\n  const inputRef = useRef<HTMLInputElement>(null);\n  const anchorRef = useRef<HTMLDivElement>(null);\n  const viaPointer = useRef(false);\n  const closeTimer = useRef(0);\n\n  const fmt = useMemo(() => new Intl.DateTimeFormat(locale, formatOptions), [locale, formatOptions]);\n  const longFmt = useMemo(() => new Intl.DateTimeFormat(locale, { weekday: \"long\", day: \"numeric\", month: \"long\", year: \"numeric\" }), [locale]);\n  const hintFmt = useMemo(() => new Intl.DateTimeFormat(locale, { weekday: \"short\", day: \"numeric\", month: \"short\", year: \"numeric\" }), [locale]);\n  const briefFmt = useMemo(() => new Intl.DateTimeFormat(locale, { day: \"numeric\", month: \"short\" }), [locale]);\n  // Messages sit in a narrow line under the field: drop the year when it's this year.\n  const brief = (d: Date) => (today && d.getFullYear() === today.getFullYear() ? briefFmt.format(d) : fmt.format(d));\n  const pattern = useMemo(\n    () =>\n      new Intl.DateTimeFormat(locale, { day: \"2-digit\", month: \"2-digit\", year: \"numeric\" })\n        .formatToParts(new Date(2026, 10, 22))\n        .map((p) => (p.type === \"day\" ? \"dd\" : p.type === \"month\" ? \"mm\" : p.type === \"year\" ? \"yyyy\" : p.value))\n        .join(\"\"),\n    [locale],\n  );\n\n  const text = draft ?? (value ? fmt.format(value) : \"\");\n  const parsed = draft && today ? parseDateInput(draft, { locale, today }) : null;\n\n  function refusal(d: Date): string | null {\n    if (min && compareDays(d, min) < 0) return `Pick ${brief(min)} or later`;\n    if (max && compareDays(d, max) > 0) return `Pick ${brief(max)} or earlier`;\n    if (isDateDisabled?.(d)) return `${brief(d)} isn’t available. Pick another day.`;\n    return null;\n  }\n\n  /** Reads the draft and either commits it or explains why not. Returns whether the field is settled. */\n  function commit(): boolean {\n    if (draft === null) return true;\n    if (!draft.trim()) {\n      setDraft(null);\n      setError(null);\n      if (value) setValue(null);\n      return true;\n    }\n    if (!parsed) {\n      setError(`Couldn’t read that. Try “${today ? brief(today) : pattern}” or “tomorrow”.`);\n      return false;\n    }\n    const why = refusal(parsed);\n    if (why) {\n      setError(why);\n      return false;\n    }\n    setDraft(null);\n    setError(null);\n    setValue(parsed);\n    return true;\n  }\n\n  function pick(d: Date) {\n    window.clearTimeout(closeTimer.current);\n    setDraft(null);\n    setError(null);\n    setValue(d);\n    // Let a pointer see the day fill before the popover goes; a key press closes on the same frame.\n    if (viaPointer.current && !reduce) closeTimer.current = window.setTimeout(() => setOpen(false), 140);\n    else setOpen(false);\n  }\n\n  function onOpenChange(next: boolean) {\n    window.clearTimeout(closeTimer.current);\n    if (next) {\n      if (disabled || readOnly) return;\n      commit();\n    }\n    setOpen(next);\n  }\n\n  const invalid = invalidProp || !!error;\n  const message: { kind: string; node: React.ReactNode } | null =\n    invalidProp && errorMessage\n      ? { kind: \"error\", node: errorMessage }\n      : error\n        ? { kind: \"error\", node: error }\n        : parsed && draft !== null && !refusal(parsed)\n          ? {\n              kind: \"hint\",\n              node: (\n                <>\n                  {hintFmt.format(parsed)}\n                  <span className=\"sr-only\">. Press Enter to set.</span>\n                  <kbd aria-hidden className=\"ml-1.5 inline-grid h-4 min-w-4 place-items-center rounded-[4px] border border-line-2 px-1 font-mono text-[10px] text-fg-3\">↵</kbd>\n                </>\n              ),\n            }\n          : description\n            ? { kind: \"description\", node: description }\n            : null;\n  const showClear = clearable && !!text && !disabled && !readOnly;\n  const sm = size === \"sm\";\n\n  return (\n    <div\n      data-invalid={invalid || undefined}\n      data-disabled={disabled || undefined}\n      data-open={open || undefined}\n      className={cn(\"group/picker flex w-full min-w-0 flex-col gap-1.5\", className)}\n      {...rest}\n    >\n      {label && (\n        <label htmlFor={inputId} className={cn(\"w-fit text-[12.5px] font-medium leading-4\", disabled ? \"text-fg-3\" : \"text-fg\")}>\n          {label}\n          {required && <span aria-hidden className=\"text-fg-3\"> *</span>}\n        </label>\n      )}\n\n      <Popover.Root open={open} onOpenChange={onOpenChange}>\n        <div\n          ref={anchorRef}\n          data-invalid={invalid || undefined}\n          data-disabled={disabled || undefined}\n          data-readonly={readOnly || undefined}\n          onMouseDown={(e) => {\n            if ((e.target as HTMLElement).closest(\"input, button\") || disabled) return;\n            e.preventDefault();\n            inputRef.current?.focus();\n          }}\n          className={cn(\n            \"group/box relative flex w-full min-w-0 items-center border border-line-2 bg-raised text-fg shadow-[var(--shadow)]\",\n            \"transition-[border-color,box-shadow,background-color] duration-150 ease-out hover:border-fg-4\",\n            \"focus-within:border-fg-3 focus-within:ring-3 focus-within:ring-fg/8 hover:focus-within:border-fg-3\",\n            \"group-data-open/picker:border-fg-3 group-data-open/picker:ring-3 group-data-open/picker:ring-fg/8\",\n            \"data-invalid:border-danger/70 data-invalid:hover:border-danger data-invalid:focus-within:border-danger data-invalid:focus-within:ring-danger/15\",\n            \"data-disabled:cursor-not-allowed data-disabled:opacity-50 data-disabled:shadow-none data-disabled:hover:border-line-2\",\n            \"data-readonly:bg-frame data-readonly:shadow-none data-readonly:hover:border-line-2\",\n            sm ? \"h-7 gap-1 rounded-md pl-2 pr-0.5\" : \"h-8 gap-1 rounded-lg pl-2.5 pr-0.5\",\n          )}\n        >\n          <input\n            ref={inputRef}\n            id={inputId}\n            value={text}\n            placeholder={placeholder ?? pattern}\n            disabled={disabled}\n            readOnly={readOnly}\n            required={required}\n            autoComplete=\"off\"\n            spellCheck={false}\n            enterKeyHint=\"done\"\n            aria-invalid={invalid || undefined}\n            aria-describedby={message ? messageId : undefined}\n            aria-haspopup=\"dialog\"\n            aria-expanded={open}\n            onChange={(e) => {\n              setDraft(e.target.value);\n              if (error) setError(null);\n            }}\n            onBlur={() => commit()}\n            onKeyDown={(e) => {\n              if (e.key === \"Enter\") {\n                // Only swallow Enter when there's something to read, so a settled field still submits its form.\n                if (draft !== null) {\n                  e.preventDefault();\n                  commit();\n                }\n              } else if (e.key === \"Escape\" && (draft !== null || error)) {\n                e.preventDefault();\n                setDraft(null);\n                setError(null);\n              } else if (e.key === \"ArrowDown\" && !readOnly) {\n                e.preventDefault();\n                onOpenChange(true);\n              }\n            }}\n            className={cn(\n              \"h-full min-w-0 flex-1 bg-transparent tabular outline-none placeholder:text-fg-4 disabled:cursor-not-allowed read-only:cursor-default\",\n              sm ? \"text-base sm:text-[12.5px]\" : \"text-base sm:text-[13px]\",\n            )}\n          />\n\n          {clearable && (\n            <button\n              type=\"button\"\n              tabIndex={-1}\n              aria-label=\"Clear date\"\n              inert={!showClear}\n              data-state={showClear ? \"visible\" : \"hidden\"}\n              onClick={() => {\n                setDraft(null);\n                setError(null);\n                setValue(null);\n                inputRef.current?.focus();\n              }}\n              className={cn(\n                \"relative grid size-5 shrink-0 place-items-center rounded-[5px] text-fg-3 outline-none\",\n                \"before:absolute before:-inset-1.5 before:content-[''] pointer-coarse:before:-inset-3\",\n                \"hover:bg-hover hover:text-fg active:scale-[0.88]\",\n                \"transition-[opacity,scale,filter,background-color,color] duration-100 ease-out\",\n                \"pointer-events-none scale-75 opacity-0 blur-[2px] motion-reduce:scale-100 motion-reduce:blur-none\",\n                \"data-[state=visible]:group-hover/box:pointer-events-auto data-[state=visible]:group-hover/box:scale-100 data-[state=visible]:group-hover/box:opacity-100 data-[state=visible]:group-hover/box:blur-none\",\n                \"data-[state=visible]:group-focus-within/box:pointer-events-auto data-[state=visible]:group-focus-within/box:scale-100 data-[state=visible]:group-focus-within/box:opacity-100 data-[state=visible]:group-focus-within/box:blur-none\",\n                \"data-[state=visible]:duration-200 data-[state=visible]:ease-out-expo\",\n              )}\n            >\n              <X size={sm ? 12 : 14} />\n            </button>\n          )}\n\n          <Popover.Trigger\n            disabled={disabled || readOnly}\n            aria-label={value ? `Choose date, ${longFmt.format(value)}` : \"Choose date\"}\n            className={cn(\n              \"group/trigger relative grid shrink-0 place-items-center rounded-md text-fg-3 outline-none\",\n              \"before:absolute before:-inset-1 before:content-[''] pointer-coarse:before:-inset-2\",\n              \"transition-[background-color,color,scale] duration-150 hover:bg-hover hover:text-fg active:scale-[0.92] active:duration-75\",\n              \"focus-visible:outline-solid focus-visible:outline-1 focus-visible:outline-offset-1 focus-visible:outline-fg-3\",\n              \"data-popup-open:bg-hover data-popup-open:text-fg disabled:pointer-events-none disabled:text-fg-4\",\n              sm ? \"size-6\" : \"size-7\",\n            )}\n          >\n            {/* The page of the calendar icon lifts a pixel while its popover is open. */}\n            <CalendarIcon size={sm ? 14 : 16} className=\"transition-transform duration-200 ease-out-expo group-data-popup-open/trigger:-translate-y-px\" />\n          </Popover.Trigger>\n        </div>\n\n        <Popover.Portal>\n          <Popover.Positioner anchor={anchorRef} side=\"bottom\" align=\"start\" sideOffset={6} collisionPadding={8} className=\"z-(--z-popover)\">\n            <Popover.Popup\n              initialFocus={false}\n              finalFocus={(type) => (type === \"touch\" ? false : inputRef.current)}\n              onPointerDownCapture={() => (viaPointer.current = true)}\n              onKeyDownCapture={() => (viaPointer.current = false)}\n              className={cn(\n                \"flex flex-col rounded-xl border border-line-2 bg-raised text-fg shadow-pop outline-none\",\n                \"origin-[var(--transform-origin)] transition-[opacity,scale,translate] duration-200 ease-out-expo\",\n                \"data-starting-style:-translate-y-1 data-starting-style:scale-[0.96] data-starting-style:opacity-0\",\n                \"data-ending-style:scale-[0.98] data-ending-style:opacity-0 data-ending-style:duration-150 data-ending-style:ease-out\",\n                \"motion-reduce:data-starting-style:translate-y-0 motion-reduce:data-starting-style:scale-100\",\n              )}\n            >\n              {shortcuts && shortcuts.length > 0 && today && (\n                <div role=\"group\" aria-label=\"Shortcuts\" className=\"flex flex-wrap gap-1 border-b border-line p-2\">\n                  {shortcuts.map((s) => {\n                    const d = s.date(today);\n                    const why = refusal(d);\n                    const on = !!value && compareDays(value, d) === 0;\n                    return (\n                      <button\n                        key={s.label}\n                        type=\"button\"\n                        disabled={!!why}\n                        aria-pressed={on}\n                        title={why ?? longFmt.format(d)}\n                        onClick={() => pick(d)}\n                        className={cn(\n                          \"h-7 rounded-md px-2 text-[12px] font-medium text-fg-2 outline-none transition-[background-color,color,scale] duration-150\",\n                          \"hover:bg-hover hover:text-fg active:scale-[0.95] active:duration-75 disabled:pointer-events-none disabled:opacity-40\",\n                          \"focus-visible:outline-solid focus-visible:outline-1 focus-visible:outline-offset-1 focus-visible:outline-fg-3\",\n                          \"aria-pressed:bg-fg/[0.08] aria-pressed:text-fg\",\n                        )}\n                      >\n                        {s.label}\n                      </button>\n                    );\n                  })}\n                </div>\n              )}\n              <Calendar\n                bare\n                autoFocus\n                className=\"p-3\"\n                value={value}\n                defaultMonth={value ?? undefined}\n                onValueChange={(d) => d && pick(d)}\n                min={min}\n                max={max}\n                isDateDisabled={isDateDisabled}\n                locale={locale}\n                weekStartsOn={weekStartsOn}\n              />\n            </Popover.Popup>\n          </Popover.Positioner>\n        </Popover.Portal>\n      </Popover.Root>\n\n      {/* One reserved line: the description, the live read of what's typed, or the error, swapped in place. */}\n      {/* A read-only or disabled field with nothing to say doesn't hold the line open. */}\n      <div id={messageId} aria-live=\"polite\" className={cn(\"relative text-[12px] leading-4\", (message || !(readOnly || disabled)) && \"min-h-4\")}>\n        <AnimatePresence initial={false} mode=\"popLayout\">\n          {message && (\n            <motion.p\n              key={message.kind === \"hint\" ? \"hint\" : `${message.kind}:${typeof message.node === \"string\" ? message.node : \"\"}`}\n              initial={reduce ? { opacity: 0 } : { opacity: 0, y: -4 }}\n              animate={{ opacity: 1, y: 0 }}\n              exit={{ opacity: 0, transition: { duration: 0.1 } }}\n              transition={{ duration: 0.2, ease: ease.out }}\n              className={cn(\n                \"flex items-start gap-1.5\",\n                message.kind === \"error\" ? \"text-danger\" : message.kind === \"hint\" ? \"text-fg-2\" : \"text-fg-3\",\n              )}\n            >\n              {message.kind === \"error\" && <ErrorGlyph />}\n              <span className=\"min-w-0\">{message.node}</span>\n            </motion.p>\n          )}\n        </AnimatePresence>\n      </div>\n\n      {name && <input type=\"hidden\" name={name} value={value ? toISODate(value) : \"\"} />}\n    </div>\n  );\n}\n\nfunction ErrorGlyph() {\n  return (\n    <svg width=\"12\" height=\"12\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" aria-hidden className=\"mt-0.5 shrink-0\">\n      <circle cx=\"8\" cy=\"8\" r=\"6\" />\n      <path d=\"M8 5v3.5\" />\n      <circle cx=\"8\" cy=\"11\" r=\".6\" fill=\"currentColor\" stroke=\"none\" />\n    </svg>\n  );\n}\n",
      "type": "registry:ui"
    }
  ],
  "docs": "https://www.stealth.pm/ui/date-picker"
}
