{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "date-picker",
  "type": "registry:ui",
  "title": "DatePicker",
  "description": "Accessible date-only picker with segmented entry, calendar popover, constraints, clear action, form serialization, validation states, and provider-themed styling.",
  "dependencies": [
    "@internationalized/date@^3.12.4",
    "react-aria@^3.52.1",
    "react-aria-components@^1.21.1"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://components.dethink.co.uk/r/dethink-base.json",
    "https://components.dethink.co.uk/r/calendar.json"
  ],
  "files": [
    {
      "path": "packages/components/src/components/date-picker/date-picker.tsx",
      "type": "registry:file",
      "target": "~/components/dethink/components/date-picker/date-picker.tsx",
      "content": "import {\n  Button as AriaButton,\n  Calendar as AriaCalendar,\n  DateInput,\n  DatePicker as AriaDatePicker,\n  DateSegment,\n  Dialog,\n  FieldError,\n  Group,\n  I18nProvider,\n  Label,\n  Popover,\n  Text,\n  type DatePickerRenderProps,\n} from \"react-aria-components\";\nimport { forwardRef, type ReactNode, useRef } from \"react\";\nimport type { DateValue } from \"@internationalized/date\";\nimport { DateCalendarGrid, type CalendarWeekStartsOn } from \"../calendar\";\nimport {\n  DethinkPortalProvider,\n  useProviderPortalRoot,\n} from \"../../utils/provider-portal\";\nimport { cn } from \"../../utils/cn\";\nimport {\n  serializeDatePickerValue,\n  type DatePickerValue,\n} from \"./date-picker-utils\";\n\nexport type { DatePickerValue } from \"./date-picker-utils\";\n\nexport interface DatePickerProps {\n  value?: DatePickerValue | null;\n  defaultValue?: DatePickerValue | null;\n  onValueChange?: (value: DatePickerValue | null) => void;\n  className?: string;\n  id?: string;\n  name?: string;\n  label?: ReactNode;\n  description?: ReactNode;\n  errorMessage?: ReactNode;\n  required?: boolean;\n  disabled?: boolean;\n  readOnly?: boolean;\n  invalid?: boolean;\n  minValue?: DateValue;\n  maxValue?: DateValue;\n  isDateUnavailable?: (date: DateValue) => boolean;\n  locale?: string;\n  weekStartsOn?: CalendarWeekStartsOn;\n  clearable?: boolean;\n}\n\nconst datePickerRootClasses =\n  \"group/date-picker grid w-full max-w-md gap-[var(--dt-space-2)] text-foreground\";\n\nconst datePickerLabelClasses =\n  \"text-sm font-medium leading-none text-foreground data-[disabled=true]:opacity-60\";\n\nconst datePickerControlClasses =\n  \"flex min-h-density-control items-center rounded-md border border-input bg-background text-sm text-foreground shadow-sm motion-safe:transition-[border-color,box-shadow,background-color] motion-safe:duration-150 data-[disabled=true]:cursor-not-allowed data-[disabled=true]:opacity-60 data-[focus-within=true]:border-ring data-[focus-within=true]:ring-2 data-[focus-within=true]:ring-ring/20 data-[invalid=true]:border-destructive data-[invalid=true]:ring-destructive/15\";\n\nconst datePickerInputClasses =\n  \"flex min-w-0 flex-1 items-center gap-[var(--dt-space-0-5)] px-[var(--dt-space-3)] py-[var(--dt-space-2)]\";\n\nconst datePickerSegmentClasses =\n  \"rounded-sm px-[var(--dt-space-0-5)] tabular-nums outline-none motion-safe:transition-colors motion-safe:duration-150 data-[focused]:bg-primary data-[focused]:text-primary-foreground data-[placeholder]:text-muted-foreground data-[disabled]:text-muted-foreground data-[readonly]:text-muted-foreground data-[invalid]:text-destructive\";\n\nconst datePickerClearButtonClasses =\n  \"me-[var(--dt-space-1)] inline-flex size-8 shrink-0 items-center justify-center rounded-md text-muted-foreground motion-safe:transition-[background-color,color,transform] motion-safe:duration-150 hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring active:translate-y-px disabled:pointer-events-none disabled:opacity-40\";\n\nconst datePickerTriggerButtonClasses =\n  \"me-[var(--dt-space-1)] inline-flex size-8 shrink-0 items-center justify-center rounded-md text-muted-foreground motion-safe:transition-[background-color,color,transform] motion-safe:duration-150 hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring active:translate-y-px disabled:pointer-events-none disabled:opacity-40 data-[pressed]:bg-muted data-[pressed]:text-foreground\";\n\nconst datePickerPopoverClasses =\n  \"z-50 rounded-md border border-border bg-background p-[var(--dt-space-3)] text-foreground shadow-lg outline-none motion-safe:transition-[opacity,transform] motion-safe:duration-150 data-[entering]:opacity-100 data-[exiting]:translate-y-1 data-[exiting]:opacity-0\";\n\nconst datePickerDialogClasses = \"grid gap-[var(--dt-space-3)] outline-none\";\n\nconst datePickerCalendarClasses = \"grid gap-[var(--dt-space-3)]\";\n\nconst datePickerHelpClasses = \"text-xs leading-5 text-muted-foreground\";\n\nconst datePickerErrorClasses = \"text-xs leading-5 text-destructive\";\n\nfunction getRootRenderClasses(\n  renderProps: DatePickerRenderProps,\n  className: string | undefined,\n) {\n  return cn(datePickerRootClasses, className);\n}\n\nexport function datePickerClassNames({\n  className,\n}: Pick<DatePickerProps, \"className\"> = {}) {\n  return cn(datePickerRootClasses, className);\n}\n\nfunction ClearIcon() {\n  return (\n    <svg aria-hidden=\"true\" className=\"size-4\" fill=\"none\" viewBox=\"0 0 16 16\">\n      <path\n        d=\"m4.5 4.5 7 7m0-7-7 7\"\n        stroke=\"currentColor\"\n        strokeLinecap=\"round\"\n        strokeWidth=\"1.75\"\n      />\n    </svg>\n  );\n}\n\nfunction CalendarIcon() {\n  return (\n    <svg aria-hidden=\"true\" className=\"size-4\" fill=\"none\" viewBox=\"0 0 16 16\">\n      <path\n        d=\"M4.5 2.5v2m7-2v2M3 6.5h10M3.5 4h9A1.5 1.5 0 0 1 14 5.5v7A1.5 1.5 0 0 1 12.5 14h-9A1.5 1.5 0 0 1 2 12.5v-7A1.5 1.5 0 0 1 3.5 4Z\"\n        stroke=\"currentColor\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        strokeWidth=\"1.5\"\n      />\n    </svg>\n  );\n}\n\nexport const DatePicker = forwardRef<HTMLDivElement, DatePickerProps>(\n  (\n    {\n      className,\n      clearable = false,\n      defaultValue,\n      description,\n      disabled = false,\n      errorMessage,\n      id,\n      invalid = false,\n      isDateUnavailable,\n      label,\n      locale,\n      maxValue,\n      minValue,\n      name,\n      onValueChange,\n      readOnly = false,\n      required = false,\n      value,\n      weekStartsOn,\n    },\n    ref,\n  ) => {\n    const triggerRef = useRef<HTMLButtonElement>(null);\n    const { portalContainer, rootRef } = useProviderPortalRoot<HTMLDivElement>({\n      forwardedRef: ref,\n      portalSlot: \"date-picker-portal-container\",\n    });\n    const picker = (\n      <DethinkPortalProvider container={portalContainer}>\n        <AriaDatePicker<DatePickerValue>\n          ref={rootRef}\n          id={id}\n          value={value}\n          defaultValue={defaultValue}\n          onChange={onValueChange}\n          isDisabled={disabled}\n          isReadOnly={readOnly}\n          isRequired={required}\n          isInvalid={invalid}\n          minValue={minValue}\n          maxValue={maxValue}\n          isDateUnavailable={isDateUnavailable}\n          firstDayOfWeek={weekStartsOn}\n          onOpenChange={(isOpen) => {\n            if (!isOpen) {\n              queueMicrotask(() => triggerRef.current?.focus());\n            }\n          }}\n          validationBehavior=\"aria\"\n          data-slot=\"date-picker\"\n          className={(renderProps) =>\n            getRootRenderClasses(renderProps, className)\n          }\n        >\n          {({ state, isDisabled, isFocusWithin, isInvalid, isReadOnly }) => {\n            const canClear = clearable && Boolean(state.value);\n\n            return (\n              <>\n                {label ? (\n                  <Label\n                    data-slot=\"date-picker-label\"\n                    data-disabled={isDisabled ? \"true\" : undefined}\n                    className={datePickerLabelClasses}\n                  >\n                    {label}\n                    {required ? <span aria-hidden=\"true\"> *</span> : null}\n                  </Label>\n                ) : null}\n                <Group\n                  data-slot=\"date-picker-field\"\n                  data-disabled={isDisabled ? \"true\" : undefined}\n                  data-focus-within={isFocusWithin ? \"true\" : undefined}\n                  data-invalid={isInvalid ? \"true\" : undefined}\n                  className={datePickerControlClasses}\n                >\n                  <DateInput\n                    data-slot=\"date-picker-input\"\n                    className={datePickerInputClasses}\n                  >\n                    {(segment) => (\n                      <DateSegment\n                        data-slot=\"date-picker-segment\"\n                        segment={segment}\n                        className={datePickerSegmentClasses}\n                      />\n                    )}\n                  </DateInput>\n                  {clearable ? (\n                    <button\n                      aria-label=\"Clear date\"\n                      className={datePickerClearButtonClasses}\n                      data-slot=\"date-picker-clear\"\n                      disabled={!canClear || isDisabled || isReadOnly}\n                      type=\"button\"\n                      onClick={() => state.setValue(null)}\n                    >\n                      <ClearIcon />\n                    </button>\n                  ) : null}\n                  <AriaButton\n                    ref={triggerRef}\n                    aria-label=\"Open calendar\"\n                    className={datePickerTriggerButtonClasses}\n                    data-slot=\"date-picker-trigger\"\n                  >\n                    <CalendarIcon />\n                  </AriaButton>\n                </Group>\n                {description ? (\n                  <Text\n                    slot=\"description\"\n                    data-slot=\"date-picker-description\"\n                    className={datePickerHelpClasses}\n                  >\n                    {description}\n                  </Text>\n                ) : null}\n                {errorMessage ? (\n                  <FieldError\n                    data-slot=\"date-picker-error\"\n                    className={datePickerErrorClasses}\n                  >\n                    {errorMessage}\n                  </FieldError>\n                ) : null}\n                {name ? (\n                  <input\n                    data-slot=\"date-picker-form-value\"\n                    name={name}\n                    readOnly\n                    type=\"hidden\"\n                    value={serializeDatePickerValue(state.value)}\n                  />\n                ) : null}\n                <Popover\n                  data-slot=\"date-picker-popover\"\n                  className={datePickerPopoverClasses}\n                >\n                  <Dialog\n                    data-slot=\"date-picker-dialog\"\n                    className={datePickerDialogClasses}\n                  >\n                    <AriaCalendar\n                      data-slot=\"date-picker-calendar\"\n                      className={datePickerCalendarClasses}\n                    >\n                      <DateCalendarGrid dataSlotPrefix=\"date-picker-calendar\" />\n                    </AriaCalendar>\n                  </Dialog>\n                </Popover>\n              </>\n            );\n          }}\n        </AriaDatePicker>\n      </DethinkPortalProvider>\n    );\n\n    if (locale) {\n      return <I18nProvider locale={locale}>{picker}</I18nProvider>;\n    }\n\n    return picker;\n  },\n);\n\nDatePicker.displayName = \"DatePicker\";\n"
    },
    {
      "path": "packages/components/src/components/date-picker/date-picker-utils.ts",
      "type": "registry:file",
      "target": "~/components/dethink/components/date-picker/date-picker-utils.ts",
      "content": "import type { CalendarDate, DateValue } from \"@internationalized/date\";\n\nexport type DatePickerValue = CalendarDate;\n\nexport function serializeDatePickerValue(value: DateValue | null | undefined) {\n  return value?.toString() ?? \"\";\n}\n"
    },
    {
      "path": "packages/components/src/components/date-picker/index.ts",
      "type": "registry:file",
      "target": "~/components/dethink/components/date-picker/index.ts",
      "content": "export {\n  DatePicker,\n  datePickerClassNames,\n  type DatePickerProps,\n} from \"./date-picker\";\nexport {\n  serializeDatePickerValue,\n  type DatePickerValue,\n} from \"./date-picker-utils\";\n"
    },
    {
      "path": "packages/components/src/utils/provider-portal.tsx",
      "type": "registry:file",
      "target": "~/components/dethink/utils/provider-portal.tsx",
      "content": "import {\n  createContext,\n  type ForwardedRef,\n  type ReactNode,\n  type RefCallback,\n  useCallback,\n  useContext,\n  useEffect,\n  useRef,\n  useState,\n} from \"react\";\nimport { UNSAFE_PortalProvider } from \"react-aria/PortalProvider\";\nimport { setDethinkPortalOwner } from \"./portal-owner\";\n\nconst providerPortalDefaultClasses = \"bg-background font-sans text-foreground\";\nconst providerPortalMirroredAttributes = [\n  \"data-theme\",\n  \"data-density\",\n  \"dir\",\n] as const;\n\nexport type ProviderPortalMirroredAttribute =\n  (typeof providerPortalMirroredAttributes)[number];\n\nexport interface ProviderPortalRootOptions<T extends HTMLElement> {\n  forwardedRef?: ForwardedRef<T>;\n  portalSlot: string;\n}\n\nexport interface ProviderPortalRootState<T extends HTMLElement> {\n  portalContainer: HTMLElement | null;\n  rootRef: RefCallback<T>;\n}\n\nexport interface DethinkPortalProviderProps {\n  children?: ReactNode;\n  container: HTMLElement | null;\n}\n\nconst DethinkPortalContext = createContext(false);\nconst OverlayVisibilityContext = createContext(true);\n\n/** Persistent surfaces can dismiss overlays without unmounting their content. */\nexport function DethinkOverlayScope({\n  visible,\n  children,\n}: {\n  visible: boolean;\n  children?: ReactNode;\n}) {\n  const parentVisible = useContext(OverlayVisibilityContext);\n  return (\n    <OverlayVisibilityContext.Provider value={visible && parentVisible}>\n      {children}\n    </OverlayVisibilityContext.Provider>\n  );\n}\n\nexport function useScopedOverlayState({\n  open,\n  defaultOpen = false,\n  onOpenChange,\n}: {\n  open?: boolean;\n  defaultOpen?: boolean;\n  onOpenChange?: (open: boolean) => void;\n}): readonly [boolean, (open: boolean) => void] {\n  const visible = useContext(OverlayVisibilityContext);\n  const [localOpen, setLocalOpen] = useState(defaultOpen);\n  const requestedOpen = open ?? localOpen;\n  const closeRequested = useRef(false);\n  const setOpen = useCallback(\n    (next: boolean) => {\n      if (next && !visible) return;\n      if (open === undefined) setLocalOpen(next);\n      onOpenChange?.(next);\n    },\n    [open, onOpenChange, visible],\n  );\n  useEffect(() => {\n    if (visible) closeRequested.current = false;\n    else if (requestedOpen && !closeRequested.current) {\n      closeRequested.current = true;\n      // Synchronize overlay state with the containing surface's lifecycle.\n      setOpen(false);\n    }\n  }, [visible, requestedOpen, setOpen]);\n  return [visible && requestedOpen, setOpen];\n}\n\nfunction assignForwardedRef<T>(\n  ref: ForwardedRef<T> | undefined,\n  value: T | null,\n) {\n  if (typeof ref === \"function\") {\n    ref(value);\n  } else if (ref) {\n    ref.current = value;\n  }\n}\n\nfunction fallbackProviderAttribute(name: ProviderPortalMirroredAttribute) {\n  if (typeof document === \"undefined\") {\n    return null;\n  }\n\n  if (name === \"data-density\") {\n    return \"default\";\n  }\n\n  if (name === \"data-theme\") {\n    return document.documentElement.getAttribute(name) ?? \"system\";\n  }\n\n  return document.documentElement.getAttribute(\"dir\") ?? document.dir ?? \"ltr\";\n}\n\nexport function syncDethinkPortalContainer({\n  container,\n  portalSlot,\n  provider,\n}: {\n  container: HTMLElement;\n  portalSlot: string;\n  provider: HTMLElement | null;\n}) {\n  const source =\n    provider ??\n    (typeof document === \"undefined\" ? null : document.documentElement);\n\n  container.setAttribute(\"data-slot\", portalSlot);\n  container.setAttribute(\"data-dethink-provider\", \"\");\n  container.className = provider?.className || providerPortalDefaultClasses;\n  container.style.cssText = provider?.getAttribute(\"style\") ?? \"\";\n  container.style.display = \"contents\";\n\n  for (const attribute of providerPortalMirroredAttributes) {\n    const value =\n      source?.getAttribute(attribute) ?? fallbackProviderAttribute(attribute);\n\n    if (value) {\n      container.setAttribute(attribute, value);\n    } else {\n      container.removeAttribute(attribute);\n    }\n  }\n}\n\nexport function useProviderPortalRoot<T extends HTMLElement>({\n  forwardedRef,\n  portalSlot,\n}: ProviderPortalRootOptions<T>): ProviderPortalRootState<T> {\n  const [portalContainer] = useState<HTMLElement | null>(() => {\n    if (typeof document === \"undefined\") {\n      return null;\n    }\n\n    return document.createElement(\"div\");\n  });\n  const [rootElement, setRootElement] = useState<T | null>(null);\n\n  const rootRef = useCallback(\n    (node: T | null) => {\n      setRootElement(node);\n      assignForwardedRef(forwardedRef, node);\n\n      if (node && portalContainer && typeof document !== \"undefined\") {\n        setDethinkPortalOwner(portalContainer, node);\n        const provider =\n          node.closest<HTMLElement>(\"[data-dethink-provider]\") ?? null;\n\n        syncDethinkPortalContainer({\n          container: portalContainer,\n          portalSlot,\n          provider,\n        });\n\n        if (!portalContainer.isConnected) {\n          document.body.appendChild(portalContainer);\n        }\n      }\n    },\n    [forwardedRef, portalContainer, portalSlot],\n  );\n\n  useEffect(() => {\n    if (!portalContainer || typeof document === \"undefined\") {\n      return undefined;\n    }\n\n    const provider =\n      rootElement?.closest<HTMLElement>(\"[data-dethink-provider]\") ?? null;\n    const syncContainer = () => {\n      syncDethinkPortalContainer({\n        container: portalContainer,\n        portalSlot,\n        provider,\n      });\n    };\n\n    syncContainer();\n    document.body.appendChild(portalContainer);\n\n    const observerTarget = provider ?? document.documentElement;\n    const observer = new MutationObserver(syncContainer);\n    observer.observe(observerTarget, {\n      attributeFilter: [\"class\", \"data-density\", \"data-theme\", \"dir\", \"style\"],\n      attributes: true,\n    });\n\n    return () => {\n      observer.disconnect();\n      portalContainer.remove();\n    };\n  }, [portalContainer, portalSlot, rootElement]);\n\n  return {\n    portalContainer,\n    rootRef,\n  };\n}\n\nexport function DethinkPortalProvider({\n  children,\n  container,\n}: DethinkPortalProviderProps) {\n  const hasDethinkPortalParent = useContext(DethinkPortalContext);\n  const content = hasDethinkPortalParent ? (\n    children\n  ) : (\n    <UNSAFE_PortalProvider getContainer={() => container}>\n      {children}\n    </UNSAFE_PortalProvider>\n  );\n\n  return (\n    <DethinkPortalContext.Provider value>\n      {content}\n    </DethinkPortalContext.Provider>\n  );\n}\n"
    },
    {
      "path": "packages/components/src/utils/portal-owner.ts",
      "type": "registry:file",
      "target": "~/components/dethink/utils/portal-owner.ts",
      "content": "/**\n * Links a Dethink portal container on <body> to the component root that owns\n * it, so a modal surface (the mobile sidebar) can keep overlays opened from\n * inside it usable while it makes the rest of the page inert. A global symbol\n * key, so components copied from the registry agree without sharing imports.\n */\nconst PORTAL_OWNER: unique symbol = Symbol.for(\"dethink.portal-owner\");\n\ntype OwnedElement = Element & { [PORTAL_OWNER]?: Element };\n\nexport function setDethinkPortalOwner(container: Element, owner: Element) {\n  (container as OwnedElement)[PORTAL_OWNER] = owner;\n}\n\n/** The element owning a Dethink portal container, or null for any other element. */\nexport function getDethinkPortalOwner(element: Element): Element | null {\n  return (element as OwnedElement)[PORTAL_OWNER] ?? null;\n}\n"
    }
  ]
}
