{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "date-field",
  "title": "Date Field",
  "description": "A segmented date input: day, month and year are separate spinbuttons in the order of the locale, typed or stepped with the keyboard, with an ISO date value.",
  "registryDependencies": [
    "@tealmac/date-time-core",
    "@tealmac/date-time-segments"
  ],
  "files": [
    {
      "path": "registry/tealmac/date-field.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\n\nimport {\n  dateFieldOrder,\n  daysInMonth,\n  isValidDateParts,\n  parseDate,\n  resolveLocale,\n  toDate,\n  type ISODate,\n} from \"@/registry/tealmac/date-time-core\"\nimport {\n  SegmentGroup,\n  type SegmentItem,\n  type SegmentKind,\n  type SegmentValues,\n} from \"@/registry/tealmac/date-time-segments\"\n\nexport interface DateFieldProps extends Omit<\n  React.ComponentProps<\"div\">,\n  \"defaultValue\" | \"onChange\" | \"role\" | \"children\"\n> {\n  /** Controlled value, an ISO date (\"2026-10-25\") or null for empty. */\n  value?: ISODate | null\n  /** Initial value when uncontrolled. */\n  defaultValue?: ISODate | null\n  /** Called with a complete valid ISO date, or null while any segment is empty. */\n  onValueChange?: (value: ISODate | null) => void\n  /** Earliest allowed date; earlier values are marked invalid. */\n  min?: ISODate\n  /** Latest allowed date. */\n  max?: ISODate\n  /** Marks single dates as not available (holidays, booked days). */\n  isDateUnavailable?: (date: ISODate) => boolean\n  /** BCP 47 locale; sets the order and separators of the segments. */\n  locale?: string\n  /** Accessible name of the whole field. */\n  label?: string\n  /** Placeholders per segment. */\n  placeholders?: Partial<Record<\"day\" | \"month\" | \"year\", string>>\n  /** Names for the screen reader per segment. */\n  segmentLabels?: Partial<Record<\"day\" | \"month\" | \"year\", string>>\n  name?: string\n  disabled?: boolean\n  readOnly?: boolean\n  /** Force the invalid state. Out-of-range values are invalid by themselves. */\n  invalid?: boolean\n}\n\nconst PLACEHOLDERS = { day: \"dd\", month: \"mm\", year: \"yyyy\" }\nconst LABELS = { day: \"day\", month: \"month\", year: \"year\" }\n\nfunction partsOf(value: ISODate | null | undefined): SegmentValues {\n  const p = parseDate(value)\n  return p ? { day: p.d, month: p.m, year: p.y } : {}\n}\n\n/** The ISO date of the segments, or null while one is empty. */\nfunction compose(v: SegmentValues): ISODate | null {\n  if (v.day === undefined || v.month === undefined || v.year === undefined)\n    return null\n  const d = Math.min(v.day, daysInMonth(v.year, v.month))\n  return isValidDateParts(v.year, v.month, d)\n    ? toDate(v.year, v.month, d)\n    : null\n}\n\nexport function dateViolation(\n  value: ISODate | null,\n  min?: ISODate,\n  max?: ISODate,\n  unavailable?: (d: ISODate) => boolean\n) {\n  if (!value) return null\n  if (min && value < min) return \"before-min\" as const\n  if (max && value > max) return \"after-max\" as const\n  if (unavailable?.(value)) return \"unavailable\" as const\n  return null\n}\n\nfunction DateField({\n  value,\n  defaultValue = null,\n  onValueChange,\n  min,\n  max,\n  isDateUnavailable,\n  locale,\n  label = \"Date\",\n  placeholders,\n  segmentLabels,\n  name,\n  disabled,\n  readOnly,\n  invalid,\n  ...props\n}: DateFieldProps) {\n  const controlled = value !== undefined\n  const [draft, setDraft] = React.useState<SegmentValues>(() =>\n    partsOf(controlled ? value : defaultValue)\n  )\n  const lastEmitted = React.useRef<ISODate | null>(compose(draft))\n\n  // Follow the controlled value when it changes from outside.\n  const external = controlled ? (value ?? null) : undefined\n  const [seen, setSeen] = React.useState(external)\n  if (controlled && external !== seen) {\n    setSeen(external)\n    if (external !== compose(draft)) setDraft(partsOf(external))\n  }\n\n  const loc = resolveLocale(locale)\n  const items = React.useMemo<SegmentItem[]>(\n    () =>\n      dateFieldOrder(loc).map((p) =>\n        \"kind\" in p\n          ? {\n              type: \"segment\",\n              spec: {\n                kind: p.kind,\n                label: segmentLabels?.[p.kind] ?? LABELS[p.kind],\n                digits: p.kind === \"year\" ? 4 : 2,\n                placeholder: placeholders?.[p.kind] ?? PLACEHOLDERS[p.kind],\n                pageStep: p.kind === \"year\" ? 10 : p.kind === \"month\" ? 3 : 7,\n              },\n            }\n          : { type: \"literal\", text: p.literal }\n      ),\n    [loc, placeholders, segmentLabels]\n  )\n\n  const composed = compose(draft)\n  const violation = dateViolation(composed, min, max, isDateUnavailable)\n\n  function change(kind: SegmentKind, next: number | undefined) {\n    const nextDraft: SegmentValues = { ...draft, [kind]: next }\n    if (nextDraft.day !== undefined && nextDraft.month !== undefined) {\n      const dim = daysInMonth(nextDraft.year ?? 2000, nextDraft.month)\n      if (nextDraft.day > dim) nextDraft.day = dim\n    }\n    setDraft(nextDraft)\n    const out = compose(nextDraft)\n    if (out !== lastEmitted.current) {\n      lastEmitted.current = out\n      onValueChange?.(out)\n    }\n  }\n\n  return (\n    <SegmentGroup\n      aria-label={label}\n      items={items}\n      values={draft}\n      limits={(kind, v) =>\n        kind === \"day\"\n          ? { min: 1, max: v.month ? daysInMonth(v.year ?? 2000, v.month) : 31 }\n          : kind === \"month\"\n            ? { min: 1, max: 12 }\n            : { min: 1, max: 9999 }\n      }\n      onSegmentChange={change}\n      format={(kind, n) => String(n).padStart(kind === \"year\" ? 4 : 2, \"0\")}\n      initial={(kind) => {\n        const now = new Date()\n        return kind === \"day\"\n          ? now.getDate()\n          : kind === \"month\"\n            ? now.getMonth() + 1\n            : now.getFullYear()\n      }}\n      disabled={disabled}\n      readOnly={readOnly}\n      invalid={invalid || violation !== null}\n      data-violation={violation ?? undefined}\n      name={name}\n      formValue={composed ?? \"\"}\n      {...props}\n    />\n  )\n}\n\nexport { DateField }\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}