{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "date-picker",
  "title": "Date-picker",
  "description": "A date-picker component.",
  "dependencies": [
    "react-day-picker",
    "class-variance-authority",
    "@hugeicons/react",
    "@hugeicons/core-free-icons"
  ],
  "registryDependencies": [
    "@cubby-ui/calendar",
    "@cubby-ui/input",
    "@cubby-ui/popover",
    "@cubby-ui/input-group",
    "@cubby-ui/use-controllable-state",
    "@cubby-ui/date-utils",
    "@cubby-ui/parse-date"
  ],
  "files": [
    {
      "path": "registry/default/date-picker/date-picker.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport {\n  dateMatchModifiers,\n  type Matcher,\n  type PropsBase,\n} from \"react-day-picker\";\nimport type { VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \"@/lib/utils\";\nimport {\n  Calendar,\n  CalendarPreset,\n  type CalendarLabelsProp,\n} from \"@/registry/default/calendar/calendar\";\nimport { useControllableState } from \"@/registry/default/hooks/use-controllable-state\";\nimport type { inputVariants } from \"@/registry/default/input/input\";\nimport {\n  isSameDay,\n  startOfDay,\n  toISODate,\n} from \"@/registry/default/lib/date-utils\";\nimport { parseDate } from \"@/registry/default/lib/parse-date\";\nimport { Popover } from \"@/registry/default/popover/popover\";\n\nimport {\n  DatePickerClear,\n  DatePickerContent,\n  DatePickerField,\n  DatePickerTrigger,\n  toDisabledMatchers,\n  type DateFieldReading,\n  type PickerControlProps,\n} from \"./date-picker-parts\";\n\nexport interface DatePickerPreset {\n  label: string;\n  /** A date, or a function so relative presets (\"Tomorrow\") stay current. */\n  value: Date | (() => Date);\n}\n\n/** Copy overrides for the picker's own text, for wording and translation. */\nexport interface DatePickerLabels {\n  /** The calendar button beside an `editable` field. */\n  chooseDate: string;\n  clear: string;\n  /** The group of presets, for screen readers. */\n  presets: string;\n  /** Typed text that can't be read as a date (`editable`). */\n  invalid: string;\n  /** A typed date that's blocked by min, max, or `disabledDates`. */\n  unavailable: string;\n}\n\nconst DEFAULT_LABELS: DatePickerLabels = {\n  chooseDate: \"Choose date\",\n  clear: \"Clear date\",\n  presets: \"Presets\",\n  invalid: \"Not a date\",\n  unavailable: \"Unavailable\",\n};\n\nexport type DatePickerProps = PickerControlProps &\n  VariantProps<typeof inputVariants> & {\n    /** The selected date. `null` means nothing is selected. */\n    value?: Date | null;\n    defaultValue?: Date | null;\n    onValueChange?: (value: Date | null) => void;\n    open?: boolean;\n    defaultOpen?: boolean;\n    onOpenChange?: (open: boolean) => void;\n    placeholder?: string;\n    /** Formats the date on the trigger. Defaults to the locale's medium date. */\n    format?: (date: Date) => string;\n    /** Shows a button that clears the date. */\n    clearable?: boolean;\n    /**\n     * Makes the field a text input: type \"tomorrow\", \"next fri\", \"mar 14\" or\n     * \"3/14\", and the calendar opens from the button beside it.\n     */\n    editable?: boolean;\n    disabled?: boolean;\n    /** Earliest selectable date. Also stops navigation before its month. */\n    minDate?: Date;\n    /** Latest selectable date. Also stops navigation after its month. */\n    maxDate?: Date;\n    /** Extra dates to block, as DayPicker matchers. */\n    disabledDates?: Matcher | Matcher[];\n    /** One-click shortcuts shown beside the calendar. */\n    presets?: DatePickerPreset[];\n    /** Submits the date as `YYYY-MM-DD` under this name in a form. */\n    name?: string;\n    /**\n     * Passed through to the calendar (locale, week start, and so on). Its\n     * `labels` take DayPicker's and the calendar's own screen-reader text.\n     */\n    calendarProps?: Omit<Partial<PropsBase>, \"labels\"> & {\n      labels?: CalendarLabelsProp;\n    };\n    /** Copy overrides for the picker's own text, merged over the defaults. */\n    labels?: Partial<DatePickerLabels>;\n  };\n\nfunction resolvePreset(preset: DatePickerPreset): Date {\n  return startOfDay(\n    typeof preset.value === \"function\" ? preset.value() : preset.value,\n  );\n}\n\nfunction DatePicker({\n  value: valueProp,\n  defaultValue = null,\n  onValueChange,\n  open: openProp,\n  defaultOpen = false,\n  onOpenChange,\n  placeholder = \"Pick a date\",\n  format,\n  clearable = false,\n  editable = false,\n  disabled,\n  minDate,\n  maxDate,\n  disabledDates,\n  presets,\n  name,\n  calendarProps,\n  labels,\n  variant,\n  size,\n  className,\n  ...props\n}: DatePickerProps) {\n  const [value, setValue] = useControllableState<Date | null>({\n    value: valueProp,\n    defaultValue,\n    onValueChange,\n  });\n  const [open, setOpen] = useControllableState<boolean>({\n    value: openProp,\n    defaultValue: defaultOpen,\n    onValueChange: onOpenChange,\n  });\n  const triggerRef = React.useRef<HTMLButtonElement>(null);\n  const groupRef = React.useRef<HTMLDivElement>(null);\n\n  const copy = { ...DEFAULT_LABELS, ...labels };\n  const locale = calendarProps?.locale?.code;\n  const label = value\n    ? (format?.(value) ??\n      new Intl.DateTimeFormat(locale, { dateStyle: \"medium\" }).format(value))\n    : null;\n\n  const disabledMatchers = toDisabledMatchers(minDate, maxDate, disabledDates);\n  const isBlocked = (date: Date) =>\n    disabledMatchers.length > 0 && dateMatchModifiers(date, disabledMatchers);\n\n  const commit = (date: Date | null) => {\n    setValue(date);\n    setOpen(false);\n  };\n\n  const hintFormat = (date: Date) =>\n    new Intl.DateTimeFormat(locale, {\n      weekday: \"short\",\n      month: \"short\",\n      day: \"numeric\",\n      year:\n        date.getFullYear() === new Date().getFullYear() ? undefined : \"numeric\",\n    }).format(date);\n  const readText = (text: string): DateFieldReading<Date> | null => {\n    const date = parseDate(text, { locale });\n    if (!date) return null;\n    return {\n      value: date,\n      hint: hintFormat(date),\n      error: isBlocked(date) ? copy.unavailable : undefined,\n    };\n  };\n\n  const showClear = clearable && value !== null && !disabled;\n\n  return (\n    <div\n      data-slot=\"date-picker\"\n      className={cn(\"relative inline-flex w-60 max-w-full\", className)}\n    >\n      <Popover open={open} onOpenChange={(next) => setOpen(next)}>\n        {editable ? (\n          <DatePickerField\n            groupRef={groupRef}\n            text={label ?? \"\"}\n            read={readText}\n            onCommit={setValue}\n            onOpenRequest={() => setOpen(true)}\n            triggerLabel={copy.chooseDate}\n            clearLabel={copy.clear}\n            invalidLabel={copy.invalid}\n            showClear={showClear}\n            placeholder={placeholder}\n            disabled={disabled}\n            variant={variant}\n            size={size}\n            {...props}\n          />\n        ) : (\n          <DatePickerTrigger\n            ref={triggerRef}\n            disabled={disabled}\n            empty={value === null}\n            showClear={showClear}\n            variant={variant}\n            size={size}\n            {...props}\n          >\n            {label ?? placeholder}\n          </DatePickerTrigger>\n        )}\n        <DatePickerContent\n          anchor={editable ? groupRef : undefined}\n          presetsLabel={copy.presets}\n          presets={presets?.map((preset) => {\n            const date = resolvePreset(preset);\n            return (\n              <CalendarPreset\n                key={preset.label}\n                active={value !== null && isSameDay(value, date)}\n                disabled={isBlocked(date)}\n                onClick={() => commit(date)}\n              >\n                {preset.label}\n              </CalendarPreset>\n            );\n          })}\n        >\n          <Calendar\n            {...calendarProps}\n            framed={false}\n            mode=\"single\"\n            required\n            selected={value ?? undefined}\n            onSelect={(date) => commit(date)}\n            disabled={\n              disabledMatchers.length > 0 ? disabledMatchers : undefined\n            }\n            startMonth={calendarProps?.startMonth ?? minDate}\n            endMonth={calendarProps?.endMonth ?? maxDate}\n          />\n        </DatePickerContent>\n      </Popover>\n\n      {showClear && !editable && (\n        <DatePickerClear\n          label={copy.clear}\n          onClear={() => {\n            setValue(null);\n            triggerRef.current?.focus();\n          }}\n        />\n      )}\n\n      {name && (\n        <input\n          type=\"hidden\"\n          name={name}\n          value={value ? toISODate(value) : \"\"}\n          disabled={disabled}\n        />\n      )}\n    </div>\n  );\n}\n\nexport { DatePicker };\n",
      "type": "registry:ui",
      "target": "components/ui/cubby-ui/date-picker/date-picker.tsx"
    },
    {
      "path": "registry/default/date-picker/date-picker-parts.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport type { Matcher } from \"react-day-picker\";\nimport type { VariantProps } from \"class-variance-authority\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport { Calendar01Icon, Cancel01Icon } from \"@hugeicons/core-free-icons\";\n\nimport { cn } from \"@/lib/utils\";\nimport { CalendarPresets } from \"@/registry/default/calendar/calendar\";\nimport { inputVariants } from \"@/registry/default/input/input\";\nimport {\n  InputGroup,\n  InputGroupAddon,\n  InputGroupButton,\n  InputGroupInput,\n} from \"@/registry/default/input-group/input-group\";\nimport { startOfDay } from \"@/registry/default/lib/date-utils\";\nimport {\n  PopoverContent,\n  PopoverTrigger,\n} from \"@/registry/default/popover/popover\";\n\n/* Pieces shared by DatePicker and DateRangePicker. */\n\n/**\n * Props forwarded to the trigger button, or the text input when `editable`.\n * Limited to what both accept (id, aria-*, data-*, handlers).\n */\nexport type PickerControlProps = Omit<\n  React.HTMLAttributes<HTMLElement>,\n  \"defaultValue\" | \"onChange\" | \"onSelect\" | \"children\"\n>;\n\n/** min, max, and extra blocked dates as one list of DayPicker matchers. */\nexport function toDisabledMatchers(\n  minDate: Date | undefined,\n  maxDate: Date | undefined,\n  disabledDates: Matcher | Matcher[] | undefined,\n): Matcher[] {\n  return [\n    ...(minDate ? [{ before: startOfDay(minDate) }] : []),\n    ...(maxDate ? [{ after: startOfDay(maxDate) }] : []),\n    ...(disabledDates\n      ? Array.isArray(disabledDates)\n        ? disabledDates\n        : [disabledDates]\n      : []),\n  ];\n}\n\n/* -------------------------------------------------------------------------------------------------\n * Trigger and clear button\n * -------------------------------------------------------------------------------------------------*/\n\ntype DatePickerTriggerProps = PickerControlProps &\n  VariantProps<typeof inputVariants> & {\n    ref?: React.Ref<HTMLButtonElement>;\n    disabled?: boolean;\n    /** Nothing selected: the label is the placeholder. */\n    empty: boolean;\n    /** Leaves room for the clear button over the trigger's end. */\n    showClear: boolean;\n    children: React.ReactNode;\n  };\n\nfunction DatePickerTrigger({\n  variant,\n  size,\n  empty,\n  showClear,\n  children,\n  ...props\n}: DatePickerTriggerProps) {\n  return (\n    <PopoverTrigger\n      data-placeholder={empty || undefined}\n      className={cn(\n        inputVariants({ variant, size }),\n        \"cursor-pointer items-center gap-2 text-start\",\n        variant === \"elevated\"\n          ? \"hover:bg-surface-hover data-popup-open:bg-surface-hover\"\n          : \"hover:bg-(--outline-hover) data-popup-open:bg-(--outline-hover)\",\n        \"data-placeholder:text-muted-foreground\",\n        showClear && \"pe-9\",\n      )}\n      {...props}\n    >\n      <HugeiconsIcon\n        icon={Calendar01Icon}\n        strokeWidth={2}\n        className=\"text-muted-foreground size-4 shrink-0\"\n      />\n      <span className=\"min-w-0 flex-1 truncate tabular-nums\">{children}</span>\n    </PopoverTrigger>\n  );\n}\n\nfunction DatePickerClear({\n  label,\n  onClear,\n}: {\n  label: string;\n  onClear: () => void;\n}) {\n  return (\n    <button\n      type=\"button\"\n      data-slot=\"date-picker-clear\"\n      aria-label={label}\n      onClick={onClear}\n      className={cn(\n        \"text-muted-foreground hover:text-foreground hover:bg-surface-hover absolute end-1.5 top-1/2 flex size-6 -translate-y-1/2 cursor-pointer items-center justify-center rounded-md\",\n        // A 44px hit area without growing the button.\n        \"before:absolute before:-inset-2.5\",\n        \"focus-visible:outline-ring/50 outline-0 outline-offset-0 outline-transparent outline-solid focus-visible:outline-2 focus-visible:outline-offset-1\",\n        \"transition-[outline-color,outline-offset] duration-150 ease-out\",\n      )}\n    >\n      <HugeiconsIcon icon={Cancel01Icon} strokeWidth={2} className=\"size-3.5\" />\n    </button>\n  );\n}\n\n/* -------------------------------------------------------------------------------------------------\n * Popover content\n * -------------------------------------------------------------------------------------------------*/\n\nfunction DatePickerContent({\n  anchor,\n  presets,\n  presetsLabel,\n  children,\n}: {\n  /** Positions against this instead of the trigger (the editable field). */\n  anchor?: React.RefObject<HTMLElement | null>;\n  /** Preset buttons for the rail; nothing renders when empty. */\n  presets?: React.ReactNode[];\n  presetsLabel: string;\n  children: React.ReactNode;\n}) {\n  const panelRef = React.useRef<HTMLDivElement>(null);\n\n  return (\n    <PopoverContent\n      align=\"start\"\n      // The popover is the calendar's surface (its own frame is turned off);\n      // 16px corners over 4px padding keep the header strip concentric.\n      className=\"rounded-2xl\"\n      // The calendar goes from two months to one (or back) as the window\n      // narrows or widens while open.\n      followContentWidth\n      viewportClassName=\"p-1 [--viewport-padding:0.25rem]\"\n      anchor={anchor}\n      // Land on the selected day (or today), not the month arrows.\n      initialFocus={() =>\n        panelRef.current?.querySelector<HTMLElement>(\n          '[data-slot=\"calendar-day\"] button[tabindex=\"0\"]',\n        ) ?? true\n      }\n    >\n      <div ref={panelRef} className=\"flex flex-col gap-1 sm:flex-row\">\n        {presets && presets.length > 0 && (\n          <CalendarPresets\n            aria-label={presetsLabel}\n            className=\"border-border/60 border-b px-1 pt-1 pb-1.5 sm:w-32 sm:border-e sm:border-b-0 sm:p-1 sm:pe-2\"\n          >\n            {presets}\n          </CalendarPresets>\n        )}\n        {children}\n      </div>\n    </PopoverContent>\n  );\n}\n\n/* -------------------------------------------------------------------------------------------------\n * Editable field\n *\n * While typing, a hint shows how the text will be read, so \"next fri\" is\n * confirmed as a date before it commits on Enter or blur.\n * -------------------------------------------------------------------------------------------------*/\n\nexport interface DateFieldReading<T> {\n  value: T;\n  /** How the text was understood, shown while typing. */\n  hint: string;\n  /** Set when the text is a date that isn't allowed. */\n  error?: string;\n}\n\ntype DatePickerFieldProps<T> = PickerControlProps &\n  VariantProps<typeof inputVariants> & {\n    groupRef: React.Ref<HTMLDivElement>;\n    /** The committed value, written out. Empty when there is none. */\n    text: string;\n    read: (text: string) => DateFieldReading<T> | null;\n    onCommit: (value: T | null) => void;\n    /** Opens the calendar (ArrowDown in the input). */\n    onOpenRequest: () => void;\n    triggerLabel: string;\n    clearLabel: string;\n    /** Shown when committing text that can't be read. */\n    invalidLabel: string;\n    showClear: boolean;\n    placeholder?: string;\n    disabled?: boolean;\n  };\n\nfunction DatePickerField<T>({\n  groupRef,\n  text,\n  read,\n  onCommit,\n  onOpenRequest,\n  triggerLabel,\n  clearLabel,\n  invalidLabel,\n  showClear,\n  variant,\n  size,\n  disabled,\n  className,\n  onFocus,\n  onBlur,\n  onKeyDown,\n  ...props\n}: DatePickerFieldProps<T>) {\n  // `null` while not editing, so the input shows the committed value.\n  const [draft, setDraft] = React.useState<string | null>(null);\n  // A commit was refused (Enter or blur); the draft stays, marked invalid.\n  const [rejected, setRejected] = React.useState(false);\n  // A new value from elsewhere (the calendar, a preset, the parent) replaces\n  // any leftover draft, refused or not.\n  const [shownText, setShownText] = React.useState(text);\n  if (shownText !== text) {\n    setShownText(text);\n    setDraft(null);\n    setRejected(false);\n  }\n  const inputRef = React.useRef<HTMLInputElement>(null);\n  const hintId = React.useId();\n\n  const editing = draft !== null && draft.trim() !== \"\" && draft !== text;\n  const reading = editing ? read(draft) : null;\n\n  let hint = \"\";\n  if (editing && reading) hint = reading.error ?? reading.hint;\n  else if (editing && rejected) hint = invalidLabel;\n  const invalid = editing && (reading?.error !== undefined || rejected);\n\n  /** Applies the draft. False when it can't be read or isn't allowed. */\n  const commit = (): boolean => {\n    if (draft === null || draft === text) return true;\n    if (draft.trim() === \"\") {\n      onCommit(null);\n      return true;\n    }\n    if (!reading || reading.error) return false;\n    onCommit(reading.value);\n    return true;\n  };\n\n  const settle = () => {\n    if (commit()) {\n      setDraft(null);\n      setRejected(false);\n    } else {\n      setRejected(true);\n    }\n  };\n\n  return (\n    <InputGroup\n      ref={groupRef}\n      variant={variant}\n      size={size}\n      data-slot=\"date-picker-field\"\n      className={className}\n    >\n      <InputGroupAddon align=\"inline-start\">\n        <PopoverTrigger\n          disabled={disabled}\n          aria-label={triggerLabel}\n          render={<InputGroupButton size=\"icon\" />}\n        >\n          <HugeiconsIcon icon={Calendar01Icon} strokeWidth={2} />\n        </PopoverTrigger>\n      </InputGroupAddon>\n      <InputGroupInput\n        ref={inputRef}\n        size={size}\n        disabled={disabled}\n        autoComplete=\"off\"\n        spellCheck={false}\n        aria-invalid={invalid || undefined}\n        aria-describedby={hint ? hintId : undefined}\n        className=\"min-w-0 tabular-nums\"\n        {...props}\n        value={draft ?? text}\n        onFocus={(event) => {\n          // Keep refused text to fix; otherwise start from the value.\n          if (draft === null) setDraft(text);\n          event.currentTarget.select();\n          onFocus?.(event);\n        }}\n        onChange={(event) => {\n          setDraft(event.target.value);\n          setRejected(false);\n        }}\n        onBlur={(event) => {\n          settle();\n          onBlur?.(event);\n        }}\n        onKeyDown={(event) => {\n          onKeyDown?.(event);\n          if (event.defaultPrevented) return;\n          if (event.key === \"Enter\") {\n            event.preventDefault();\n            settle();\n          } else if (event.key === \"Escape\" && editing) {\n            // Revert the edit; a second Escape is left to the page.\n            event.preventDefault();\n            event.stopPropagation();\n            setDraft(text);\n            setRejected(false);\n            requestAnimationFrame(() => inputRef.current?.select());\n          } else if (event.key === \"ArrowDown\") {\n            event.preventDefault();\n            onOpenRequest();\n          }\n        }}\n      />\n      {/* Announces refused commits only. The visible hint changes on most\n          keystrokes, so it's linked by aria-describedby, not a live region. */}\n      <span aria-live=\"polite\" className=\"sr-only\">\n        {rejected && editing ? hint : \"\"}\n      </span>\n      {(hint || showClear) && (\n        <InputGroupAddon\n          align=\"inline-end\"\n          className=\"max-w-[60%] min-w-0 gap-1.5\"\n        >\n          <span\n            id={hintId}\n            className={cn(\n              \"min-w-0 truncate text-xs font-normal whitespace-nowrap tabular-nums\",\n              invalid && \"text-danger-foreground\",\n            )}\n          >\n            {hint}\n          </span>\n          {showClear && !editing && (\n            <InputGroupButton\n              size=\"icon\"\n              aria-label={clearLabel}\n              onClick={() => {\n                onCommit(null);\n                // Focusing starts an edit from the old text; the cleared value\n                // replaces that draft when it renders.\n                inputRef.current?.focus();\n              }}\n            >\n              <HugeiconsIcon icon={Cancel01Icon} strokeWidth={2} />\n            </InputGroupButton>\n          )}\n        </InputGroupAddon>\n      )}\n    </InputGroup>\n  );\n}\n\nexport {\n  DatePickerClear,\n  DatePickerContent,\n  DatePickerField,\n  DatePickerTrigger,\n};\n",
      "type": "registry:ui",
      "target": "components/ui/cubby-ui/date-picker/date-picker-parts.tsx"
    }
  ],
  "type": "registry:ui"
}