{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "date-time-picker",
  "type": "registry:ui",
  "title": "DateTimePicker",
  "description": "Accessible segmented DateTimePicker with shared calendar popover, selectable time controls, timezone-aware values, presets, and form wiring.",
  "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-time-picker/date-time-picker.tsx",
      "type": "registry:file",
      "target": "~/components/dethink/components/date-time-picker/date-time-picker.tsx",
      "content": "/* eslint-disable jsx-a11y/no-autofocus -- React Aria places focus inside a dialog only after the user opens it. */\nimport {\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  TimeField,\n  useLocale,\n  type DatePickerRenderProps,\n  type DateSegmentProps,\n} from \"react-aria-components\";\nimport { forwardRef, type ReactNode, useId, useRef, useState } from \"react\";\nimport type { DateValue } from \"@internationalized/date\";\nimport { DateCalendarGrid } from \"../calendar\";\nimport {\n  DethinkPortalProvider,\n  useProviderPortalRoot,\n} from \"../../utils/provider-portal\";\nimport { cn } from \"../../utils/cn\";\nimport {\n  getDateTimePickerPlaceholderValue,\n  getDateTimePickerTimeOptionValue,\n  getDateTimePickerTimeOptions,\n  getDateTimePickerTimeZone,\n  isDateTimePickerTimeOptionSelected,\n  isDateTimePickerValueUnavailable,\n  serializeDateTimePickerValue,\n  type DateTimePickerGranularity,\n  type DateTimePickerTimeOption,\n  type DateTimePickerTimeStep,\n  type DateTimePickerValue,\n  type DateTimePickerWeekStartsOn,\n} from \"./date-time-picker-utils\";\n\nexport type {\n  DateTimePickerGranularity,\n  DateTimePickerTimeOption,\n  DateTimePickerTimeStep,\n  DateTimePickerValue,\n  DateTimePickerWeekStartsOn,\n} from \"./date-time-picker-utils\";\n\nexport type DateTimePickerPreset = {\n  label: ReactNode;\n  value: DateTimePickerValue;\n};\n\nexport interface DateTimePickerProps {\n  value?: DateTimePickerValue | null;\n  defaultValue?: DateTimePickerValue | null;\n  onValueChange?: (value: DateTimePickerValue | 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  timeZone?: string;\n  hideTimeZone?: boolean;\n  granularity?: DateTimePickerGranularity;\n  hourCycle?: 12 | 24;\n  weekStartsOn?: DateTimePickerWeekStartsOn;\n  presets?: DateTimePickerPreset[];\n  clearable?: boolean;\n  timeOptions?: DateTimePickerTimeOption[];\n  timeSelector?: boolean;\n  timeStep?: DateTimePickerTimeStep;\n}\n\nconst dateTimePickerRootClasses =\n  \"group/date-time-picker grid w-full max-w-md gap-[var(--dt-space-2)] text-foreground\";\n\nconst dateTimePickerLabelClasses =\n  \"text-sm font-medium leading-none text-foreground data-[disabled=true]:opacity-60\";\n\nconst dateTimePickerControlClasses =\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 dateTimePickerInputClasses =\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 dateTimePickerSegmentClasses =\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 dateTimePickerClearButtonClasses =\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 dateTimePickerTriggerButtonClasses =\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 dateTimePickerPopoverClasses =\n  \"z-50 max-h-[min(42rem,calc(100dvh-2rem))] max-w-[calc(100vw-1.5rem)] overflow-y-auto rounded-xl border border-border bg-background p-4 text-foreground shadow-xl outline-none motion-safe:transition-[opacity,transform] motion-safe:duration-150 data-[exiting]:translate-y-1 data-[exiting]:opacity-0\";\n\nconst dateTimePickerDialogClasses = \"grid gap-[var(--dt-space-3)] outline-none\";\n\nconst dateTimePickerCalendarClasses = \"grid gap-[var(--dt-space-3)]\";\n\nconst dateTimePickerPresetsClasses =\n  \"grid gap-[var(--dt-space-1)] border-b border-border pb-[var(--dt-space-3)] sm:grid-cols-2\";\n\nconst dateTimePickerPresetButtonClasses =\n  \"rounded-md border border-border bg-background px-[var(--dt-space-3)] py-[var(--dt-space-2)] text-left text-sm text-foreground shadow-sm motion-safe:transition-[background-color,border-color,box-shadow,transform] motion-safe:duration-150 hover:border-ring/50 hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring active:translate-y-px disabled:pointer-events-none disabled:opacity-50\";\n\nconst dateTimePickerHelpClasses = \"text-xs leading-5 text-muted-foreground\";\n\nconst dateTimePickerErrorClasses = \"text-xs leading-5 text-destructive\";\n\nconst dateTimePickerTimezoneClasses =\n  \"text-xs font-medium leading-5 text-muted-foreground\";\n\nconst dateTimePickerTimeSelectorClasses =\n  \"grid min-w-0 content-start gap-3 border-t border-border pt-4 sm:w-64 sm:border-s sm:border-t-0 sm:ps-5 sm:pt-0\";\n\nconst dateTimePickerTimeSelectorLabelClasses =\n  \"text-xs font-medium uppercase tracking-wide text-muted-foreground\";\n\nconst dateTimePickerTimeInputClasses =\n  \"flex min-h-12 min-w-0 items-center justify-center gap-1 rounded-lg border border-input bg-muted/40 px-3 text-xl font-medium tabular-nums shadow-sm data-[disabled]:opacity-50\";\n\nconst dateTimePickerTimeOptionsLabelClasses =\n  \"text-xs font-medium uppercase tracking-wide text-muted-foreground\";\n\nconst dateTimePickerTimeOptionsClasses =\n  \"relative grid max-h-44 grid-cols-[repeat(3,minmax(0,1fr))] gap-[var(--dt-space-1)] overflow-y-auto overscroll-contain pe-[var(--dt-space-1)]\";\n\nconst dateTimePickerTimeOptionClasses =\n  \"min-h-10 min-w-0 rounded-md border border-border bg-background px-[var(--dt-space-2)] py-[var(--dt-space-1-5)] text-center text-xs font-medium tabular-nums text-foreground shadow-sm outline-none motion-safe:transition-[background-color,border-color,box-shadow,color,transform] motion-safe:duration-150 hover:border-ring/50 hover:bg-muted focus-visible:ring-2 focus-visible:ring-ring active:translate-y-px disabled:pointer-events-none disabled:opacity-45 data-[selected=true]:border-primary data-[selected=true]:bg-primary data-[selected=true]:text-primary-foreground\";\n\ntype DateTimePickerPanel = \"calendar\" | \"time\";\n\nconst dateTimePickerTimeSegmentTypes = new Set([\n  \"hour\",\n  \"minute\",\n  \"second\",\n  \"dayPeriod\",\n]);\n\nfunction getRootRenderClasses(\n  renderProps: DatePickerRenderProps,\n  className: string | undefined,\n) {\n  return cn(dateTimePickerRootClasses, className);\n}\n\nfunction isDateTimePickerTimeSegment(segmentType: string) {\n  return dateTimePickerTimeSegmentTypes.has(segmentType);\n}\n\nfunction ClockIcon() {\n  return (\n    <svg aria-hidden=\"true\" className=\"size-4\" fill=\"none\" viewBox=\"0 0 16 16\">\n      <circle cx=\"8\" cy=\"8\" r=\"6\" stroke=\"currentColor\" strokeWidth=\"1.5\" />\n      <path\n        d=\"M8 4.5V8l2.5 1.5\"\n        stroke=\"currentColor\"\n        strokeWidth=\"1.5\"\n        strokeLinecap=\"round\"\n      />\n    </svg>\n  );\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 DateTimePicker = forwardRef<HTMLDivElement, DateTimePickerProps>(\n  (\n    {\n      className,\n      clearable = false,\n      defaultValue,\n      description,\n      disabled = false,\n      errorMessage,\n      granularity = \"minute\",\n      hideTimeZone = false,\n      hourCycle,\n      id,\n      invalid = false,\n      isDateUnavailable,\n      label,\n      locale,\n      maxValue,\n      minValue,\n      name,\n      onValueChange,\n      presets = [],\n      readOnly = false,\n      required = false,\n      timeZone,\n      timeOptions,\n      timeSelector = true,\n      timeStep = 30,\n      value,\n      weekStartsOn,\n    },\n    ref,\n  ) => {\n    const triggerRef = useRef<HTMLButtonElement>(null);\n    const focusReturnRef = useRef<HTMLElement | null>(null);\n    const inheritedLocale = useLocale().locale;\n    const resolvedHourCycle =\n      hourCycle ??\n      (new Intl.DateTimeFormat(locale ?? inheritedLocale, {\n        hour: \"numeric\",\n      }).resolvedOptions().hour12\n        ? 12\n        : 24);\n    const hourFormatter = new Intl.NumberFormat(locale ?? inheritedLocale, {\n      minimumIntegerDigits: 2,\n      useGrouping: false,\n    });\n    // Intl implementations differ in 24-hour padding and space characters.\n    // Normalize these literals so server and browser field markup agree.\n    const normalizeSegment = (segment: DateSegmentProps[\"segment\"]) => {\n      if (segment.type === \"literal\") {\n        return {\n          ...segment,\n          text: segment.text.replace(/[\\u00a0\\u202f]/g, \" \"),\n        };\n      }\n      if (\n        segment.type === \"hour\" &&\n        resolvedHourCycle === 24 &&\n        !segment.isPlaceholder &&\n        segment.value != null\n      ) {\n        return { ...segment, text: hourFormatter.format(segment.value) };\n      }\n      return segment;\n    };\n    const timeSelectorLabelId = useId();\n    const timeOptionsLabelId = useId();\n    const timeHelpId = useId();\n    const [activePanel, setActivePanel] =\n      useState<DateTimePickerPanel>(\"calendar\");\n    const { portalContainer, rootRef } = useProviderPortalRoot<HTMLDivElement>({\n      forwardedRef: ref,\n      portalSlot: \"date-time-picker-portal-container\",\n    });\n    const picker = (\n      <DethinkPortalProvider container={portalContainer}>\n        <AriaDatePicker<DateTimePickerValue>\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          granularity={granularity}\n          hourCycle={hourCycle}\n          hideTimeZone={hideTimeZone}\n          firstDayOfWeek={weekStartsOn}\n          placeholderValue={getDateTimePickerPlaceholderValue({\n            defaultValue,\n            timeZone,\n            value,\n          })}\n          shouldCloseOnSelect={!timeSelector}\n          onOpenChange={(isOpen) => {\n            if (!isOpen) {\n              setActivePanel(\"calendar\");\n              queueMicrotask(() =>\n                (focusReturnRef.current ?? triggerRef.current)?.focus(),\n              );\n            }\n          }}\n          validationBehavior=\"aria\"\n          data-slot=\"date-time-picker\"\n          data-granularity={granularity}\n          className={(renderProps) =>\n            getRootRenderClasses(renderProps, className)\n          }\n        >\n          {({ state, isDisabled, isFocusWithin, isInvalid, isReadOnly }) => {\n            const currentValue = state.value as DateTimePickerValue | null;\n            const timeZoneLabel = hideTimeZone\n              ? null\n              : getDateTimePickerTimeZone(currentValue, timeZone);\n            const canClear = clearable && Boolean(currentValue);\n            const resolvedTimeOptions =\n              timeOptions ??\n              getDateTimePickerTimeOptions({\n                granularity,\n                hourCycle: resolvedHourCycle,\n                step: timeStep,\n              });\n            const timeControlsDisabled =\n              !currentValue || isDisabled || isReadOnly;\n            const currentUnavailable = currentValue\n              ? isDateTimePickerValueUnavailable(\n                  currentValue,\n                  minValue,\n                  maxValue,\n                  isDateUnavailable,\n                )\n              : false;\n\n            return (\n              <>\n                {label ? (\n                  <Label\n                    data-slot=\"date-time-picker-label\"\n                    data-disabled={isDisabled ? \"true\" : undefined}\n                    className={dateTimePickerLabelClasses}\n                  >\n                    {label}\n                    {required ? <span aria-hidden=\"true\"> *</span> : null}\n                  </Label>\n                ) : null}\n                <Group\n                  data-slot=\"date-time-picker-field\"\n                  data-disabled={isDisabled ? \"true\" : undefined}\n                  data-focus-within={isFocusWithin ? \"true\" : undefined}\n                  data-invalid={isInvalid ? \"true\" : undefined}\n                  className={dateTimePickerControlClasses}\n                >\n                  <DateInput\n                    data-slot=\"date-time-picker-input\"\n                    className={dateTimePickerInputClasses}\n                  >\n                    {(segment) =>\n                      (() => {\n                        const isTimeSegment = isDateTimePickerTimeSegment(\n                          segment.type,\n                        );\n                        const openTimeSelector = () => {\n                          if (\n                            !timeSelector ||\n                            !isTimeSegment ||\n                            isDisabled ||\n                            isReadOnly\n                          ) {\n                            return;\n                          }\n\n                          focusReturnRef.current =\n                            document.activeElement as HTMLElement;\n                          setActivePanel(\"time\");\n                          state.open();\n                        };\n\n                        return (\n                          <DateSegment\n                            data-segment={segment.type}\n                            data-slot=\"date-time-picker-segment\"\n                            segment={normalizeSegment(segment)}\n                            className={dateTimePickerSegmentClasses}\n                            onPointerDownCapture={() => {\n                              setActivePanel(\n                                isTimeSegment ? \"time\" : \"calendar\",\n                              );\n                            }}\n                            onClick={openTimeSelector}\n                          />\n                        );\n                      })()\n                    }\n                  </DateInput>\n                  {clearable ? (\n                    <button\n                      aria-label=\"Clear date and time\"\n                      className={dateTimePickerClearButtonClasses}\n                      data-slot=\"date-time-picker-clear\"\n                      disabled={!canClear || isDisabled || isReadOnly}\n                      type=\"button\"\n                      onClick={() => state.setValue(null)}\n                    >\n                      <ClearIcon />\n                    </button>\n                  ) : null}\n                  {timeSelector ? (\n                    <button\n                      type=\"button\"\n                      aria-label=\"Open time picker\"\n                      aria-haspopup=\"dialog\"\n                      aria-expanded={state.isOpen}\n                      disabled={isDisabled || isReadOnly}\n                      className={dateTimePickerTriggerButtonClasses}\n                      data-slot=\"date-time-picker-time-trigger\"\n                      onClick={(event) => {\n                        focusReturnRef.current = event.currentTarget;\n                        setActivePanel(\"time\");\n                        state.open();\n                      }}\n                    >\n                      <ClockIcon />\n                    </button>\n                  ) : null}\n                  <AriaButton\n                    ref={triggerRef}\n                    aria-label=\"Open calendar\"\n                    className={dateTimePickerTriggerButtonClasses}\n                    data-slot=\"date-time-picker-trigger\"\n                    onClickCapture={() => {\n                      focusReturnRef.current = triggerRef.current;\n                      setActivePanel(\"calendar\");\n                    }}\n                  >\n                    <CalendarIcon />\n                  </AriaButton>\n                </Group>\n                {timeZoneLabel ? (\n                  <div\n                    data-slot=\"date-time-picker-timezone\"\n                    className={dateTimePickerTimezoneClasses}\n                  >\n                    {timeZoneLabel}\n                  </div>\n                ) : null}\n                {description ? (\n                  <Text\n                    slot=\"description\"\n                    data-slot=\"date-time-picker-description\"\n                    className={dateTimePickerHelpClasses}\n                  >\n                    {description}\n                  </Text>\n                ) : null}\n                {errorMessage ? (\n                  <FieldError\n                    data-slot=\"date-time-picker-error\"\n                    className={dateTimePickerErrorClasses}\n                  >\n                    {errorMessage}\n                  </FieldError>\n                ) : null}\n                {name ? (\n                  <input\n                    data-slot=\"date-time-picker-form-value\"\n                    name={name}\n                    readOnly\n                    type=\"hidden\"\n                    value={serializeDateTimePickerValue(\n                      state.value as DateTimePickerValue | null,\n                    )}\n                  />\n                ) : null}\n                <Popover\n                  data-panel={activePanel}\n                  data-slot=\"date-time-picker-popover\"\n                  className={dateTimePickerPopoverClasses}\n                >\n                  <Dialog\n                    data-slot=\"date-time-picker-dialog\"\n                    className={dateTimePickerDialogClasses}\n                  >\n                    {presets.length > 0 ? (\n                      <div\n                        data-slot=\"date-time-picker-presets\"\n                        className={dateTimePickerPresetsClasses}\n                      >\n                        {presets.map((preset, index) => (\n                          <button\n                            className={dateTimePickerPresetButtonClasses}\n                            data-slot=\"date-time-picker-preset\"\n                            disabled={\n                              isDisabled ||\n                              isReadOnly ||\n                              isDateTimePickerValueUnavailable(\n                                preset.value,\n                                minValue,\n                                maxValue,\n                                isDateUnavailable,\n                              )\n                            }\n                            key={index}\n                            type=\"button\"\n                            onClick={() => {\n                              state.setValue(preset.value);\n                              state.close();\n                            }}\n                          >\n                            {preset.label}\n                          </button>\n                        ))}\n                      </div>\n                    ) : null}\n                    <div\n                      className={cn(\n                        \"grid gap-5\",\n                        timeSelector && \"sm:grid-cols-[auto_auto]\",\n                      )}\n                    >\n                      <AriaCalendar\n                        autoFocus={\n                          activePanel === \"calendar\" || timeControlsDisabled\n                        }\n                        data-slot=\"date-time-picker-calendar\"\n                        className={dateTimePickerCalendarClasses}\n                      >\n                        <DateCalendarGrid dataSlotPrefix=\"date-time-picker-calendar\" />\n                      </AriaCalendar>\n                      {timeSelector ? (\n                        <div\n                          aria-labelledby={timeSelectorLabelId}\n                          data-slot=\"date-time-picker-time-selector\"\n                          role=\"group\"\n                          className={dateTimePickerTimeSelectorClasses}\n                        >\n                          <div\n                            id={timeSelectorLabelId}\n                            data-slot=\"date-time-picker-time-selector-label\"\n                            className={dateTimePickerTimeSelectorLabelClasses}\n                          >\n                            Time\n                          </div>\n                          <TimeField\n                            aria-label=\"Exact time\"\n                            aria-describedby={timeHelpId}\n                            data-slot=\"date-time-picker-time-input\"\n                            value={currentValue}\n                            isDisabled={timeControlsDisabled}\n                            isInvalid={currentUnavailable}\n                            autoFocus={\n                              activePanel === \"time\" && !timeControlsDisabled\n                            }\n                            granularity={granularity}\n                            hourCycle={hourCycle}\n                            hideTimeZone\n                            onChange={(next) => {\n                              if (currentValue && next) {\n                                state.setValue(\n                                  currentValue.set({\n                                    hour: next.hour,\n                                    minute: next.minute,\n                                    second: next.second,\n                                    millisecond: 0,\n                                  }),\n                                );\n                              }\n                            }}\n                          >\n                            <DateInput\n                              className={dateTimePickerTimeInputClasses}\n                            >\n                              {(segment) => (\n                                <DateSegment\n                                  segment={normalizeSegment(segment)}\n                                  className={dateTimePickerSegmentClasses}\n                                />\n                              )}\n                            </DateInput>\n                          </TimeField>\n                          <p\n                            id={timeHelpId}\n                            className={\n                              currentUnavailable\n                                ? dateTimePickerErrorClasses\n                                : dateTimePickerHelpClasses\n                            }\n                            aria-live=\"polite\"\n                          >\n                            {!currentValue\n                              ? \"Choose a date first.\"\n                              : currentUnavailable\n                                ? \"Choose an available date and time.\"\n                                : \"Type a time or choose a quick pick.\"}\n                          </p>\n                          {resolvedTimeOptions.length > 0 ? (\n                            <>\n                              <div\n                                id={timeOptionsLabelId}\n                                data-slot=\"date-time-picker-time-options-label\"\n                                className={\n                                  dateTimePickerTimeOptionsLabelClasses\n                                }\n                              >\n                                Quick picks\n                              </div>\n                              <div\n                                aria-labelledby={timeOptionsLabelId}\n                                data-slot=\"date-time-picker-time-options\"\n                                role=\"group\"\n                                className={dateTimePickerTimeOptionsClasses}\n                              >\n                                {resolvedTimeOptions.map((option) => {\n                                  const selected =\n                                    isDateTimePickerTimeOptionSelected({\n                                      granularity,\n                                      option,\n                                      value: currentValue,\n                                    });\n                                  const label =\n                                    option.label ??\n                                    `${String(option.hour).padStart(\n                                      2,\n                                      \"0\",\n                                    )}:${String(option.minute ?? 0).padStart(\n                                      2,\n                                      \"0\",\n                                    )}`;\n\n                                  return (\n                                    <button\n                                      aria-pressed={selected}\n                                      className={\n                                        dateTimePickerTimeOptionClasses\n                                      }\n                                      data-selected={\n                                        selected ? \"true\" : undefined\n                                      }\n                                      data-slot=\"date-time-picker-time-option\"\n                                      disabled={\n                                        timeControlsDisabled ||\n                                        Boolean(\n                                          currentValue &&\n                                          isDateTimePickerValueUnavailable(\n                                            getDateTimePickerTimeOptionValue(\n                                              currentValue,\n                                              option,\n                                            ),\n                                            minValue,\n                                            maxValue,\n                                            isDateUnavailable,\n                                          ),\n                                        )\n                                      }\n                                      ref={(node) => {\n                                        if (selected && node?.parentElement) {\n                                          const list = node.parentElement;\n                                          list.scrollTop =\n                                            node.offsetTop -\n                                            list.clientHeight / 2 +\n                                            node.clientHeight / 2;\n                                        }\n                                      }}\n                                      key={`${option.hour}:${\n                                        option.minute ?? 0\n                                      }:${option.second ?? 0}`}\n                                      type=\"button\"\n                                      onClick={() => {\n                                        if (!currentValue) {\n                                          return;\n                                        }\n\n                                        state.setValue(\n                                          getDateTimePickerTimeOptionValue(\n                                            currentValue,\n                                            option,\n                                          ),\n                                        );\n                                      }}\n                                    >\n                                      {label}\n                                    </button>\n                                  );\n                                })}\n                              </div>\n                            </>\n                          ) : null}\n                        </div>\n                      ) : null}\n                    </div>\n                    {timeSelector ? (\n                      <div className=\"border-border flex items-center justify-between gap-3 border-t pt-3\">\n                        <span className={dateTimePickerHelpClasses}>\n                          {timeZoneLabel ?? \"Date & time\"}\n                        </span>\n                        <button\n                          type=\"button\"\n                          onClick={() => state.close()}\n                          className=\"bg-primary text-primary-foreground focus-visible:ring-ring focus-visible:ring-offset-background min-h-10 rounded-md px-5 text-sm font-medium outline-none hover:opacity-90 focus-visible:ring-2 focus-visible:ring-offset-2\"\n                        >\n                          Done\n                        </button>\n                      </div>\n                    ) : null}\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\nDateTimePicker.displayName = \"DateTimePicker\";\n"
    },
    {
      "path": "packages/components/src/components/date-time-picker/date-time-picker-utils.ts",
      "type": "registry:file",
      "target": "~/components/dethink/components/date-time-picker/date-time-picker-utils.ts",
      "content": "import {\n  now,\n  toCalendarDate,\n  type CalendarDateTime,\n  type DateValue,\n  type ZonedDateTime,\n} from \"@internationalized/date\";\n\nexport type DateTimePickerValue = CalendarDateTime | ZonedDateTime;\n\nexport type DateTimePickerGranularity = \"hour\" | \"minute\" | \"second\";\n\nexport type DateTimePickerTimeStep = 5 | 10 | 15 | 30 | 60;\n\nexport type DateTimePickerTimeOption = {\n  hour: number;\n  label?: string;\n  minute?: number;\n  second?: number;\n};\n\nexport type DateTimePickerWeekStartsOn =\n  \"sun\" | \"mon\" | \"tue\" | \"wed\" | \"thu\" | \"fri\" | \"sat\";\n\nexport function serializeDateTimePickerValue(\n  value: DateTimePickerValue | null | undefined,\n) {\n  return value?.toString() ?? \"\";\n}\n\nexport function hasTimeZone(\n  value: DateValue | null | undefined,\n): value is ZonedDateTime {\n  return Boolean(value && \"timeZone\" in value);\n}\n\nexport function getDateTimePickerTimeZone(\n  value: DateValue | null | undefined,\n  timeZone: string | undefined,\n) {\n  if (timeZone) {\n    return timeZone;\n  }\n\n  if (hasTimeZone(value)) {\n    return value.timeZone;\n  }\n\n  return null;\n}\n\nexport function getDateTimePickerPlaceholderValue({\n  defaultValue,\n  timeZone,\n  value,\n}: {\n  defaultValue?: DateTimePickerValue | null;\n  timeZone?: string;\n  value?: DateTimePickerValue | null;\n}) {\n  if (!timeZone || value || defaultValue) {\n    return undefined;\n  }\n\n  return now(timeZone);\n}\n\nfunction formatTimeOptionLabel({\n  hour,\n  hourCycle,\n  minute,\n}: {\n  hour: number;\n  hourCycle: 12 | 24;\n  minute: number;\n}) {\n  const paddedMinute = String(minute).padStart(2, \"0\");\n\n  if (hourCycle === 12) {\n    const period = hour < 12 ? \"AM\" : \"PM\";\n    const displayHour = hour % 12 || 12;\n\n    return `${displayHour}:${paddedMinute} ${period}`;\n  }\n\n  return `${String(hour).padStart(2, \"0\")}:${paddedMinute}`;\n}\n\nexport function getDateTimePickerTimeOptions({\n  granularity = \"minute\",\n  hourCycle = 24,\n  step = 30,\n}: {\n  granularity?: DateTimePickerGranularity;\n  hourCycle?: 12 | 24;\n  step?: DateTimePickerTimeStep;\n} = {}): DateTimePickerTimeOption[] {\n  const minuteStep = granularity === \"hour\" ? 60 : step;\n  const options: DateTimePickerTimeOption[] = [];\n\n  for (let hour = 0; hour < 24; hour += 1) {\n    for (let minute = 0; minute < 60; minute += minuteStep) {\n      options.push({\n        hour,\n        label: formatTimeOptionLabel({ hour, hourCycle, minute }),\n        minute,\n        second: 0,\n      });\n    }\n  }\n\n  return options;\n}\n\nexport function getDateTimePickerTimeOptionValue(\n  value: DateTimePickerValue,\n  option: DateTimePickerTimeOption,\n) {\n  return value.set({\n    hour: option.hour,\n    millisecond: 0,\n    minute: option.minute ?? 0,\n    second: option.second ?? 0,\n  });\n}\n\n/** Date-only bounds include the entire day; timestamp bounds include the time. */\nexport function isDateTimePickerValueUnavailable(\n  value: DateTimePickerValue,\n  minValue?: DateValue,\n  maxValue?: DateValue,\n  isDateUnavailable?: (date: DateValue) => boolean,\n) {\n  const compare = (bound: DateValue) =>\n    \"hour\" in bound\n      ? value.compare(bound)\n      : toCalendarDate(value).compare(bound);\n\n  return Boolean(\n    (minValue && compare(minValue) < 0) ||\n    (maxValue && compare(maxValue) > 0) ||\n    isDateUnavailable?.(value),\n  );\n}\n\nexport function getDateTimePickerTimeInputStep(\n  granularity: DateTimePickerGranularity = \"minute\",\n) {\n  if (granularity === \"second\") {\n    return 1;\n  }\n\n  if (granularity === \"hour\") {\n    return 3600;\n  }\n\n  return 60;\n}\n\nexport function getDateTimePickerTimeInputValue({\n  granularity = \"minute\",\n  value,\n}: {\n  granularity?: DateTimePickerGranularity;\n  value: DateTimePickerValue | null | undefined;\n}) {\n  if (!value) {\n    return \"\";\n  }\n\n  const hour = String(value.hour).padStart(2, \"0\");\n  const minute =\n    granularity === \"hour\" ? \"00\" : String(value.minute).padStart(2, \"0\");\n\n  if (granularity === \"second\") {\n    return `${hour}:${minute}:${String(value.second).padStart(2, \"0\")}`;\n  }\n\n  return `${hour}:${minute}`;\n}\n\nexport function getDateTimePickerTimeInputValueChange({\n  granularity = \"minute\",\n  inputValue,\n  value,\n}: {\n  granularity?: DateTimePickerGranularity;\n  inputValue: string;\n  value: DateTimePickerValue | null | undefined;\n}) {\n  if (!value || !inputValue) {\n    return null;\n  }\n\n  const [hourPart, minutePart = \"0\", secondPart = \"0\"] = inputValue.split(\":\");\n  const hour = Number(hourPart);\n  const minute = granularity === \"hour\" ? 0 : Number(minutePart);\n  const second = granularity === \"second\" ? Number(secondPart) : 0;\n  const isValidTime =\n    Number.isInteger(hour) &&\n    Number.isInteger(minute) &&\n    Number.isInteger(second) &&\n    hour >= 0 &&\n    hour <= 23 &&\n    minute >= 0 &&\n    minute <= 59 &&\n    second >= 0 &&\n    second <= 59;\n\n  if (!isValidTime) {\n    return null;\n  }\n\n  return value.set({\n    hour,\n    millisecond: 0,\n    minute,\n    second,\n  });\n}\n\nexport function isDateTimePickerTimeOptionSelected({\n  granularity = \"minute\",\n  option,\n  value,\n}: {\n  granularity?: DateTimePickerGranularity;\n  option: DateTimePickerTimeOption;\n  value: DateTimePickerValue | null | undefined;\n}) {\n  if (!value) {\n    return false;\n  }\n\n  if (value.hour !== option.hour) {\n    return false;\n  }\n\n  if (granularity === \"hour\") {\n    return true;\n  }\n\n  if (value.minute !== (option.minute ?? 0)) {\n    return false;\n  }\n\n  if (granularity === \"second\") {\n    return value.second === (option.second ?? 0);\n  }\n\n  return true;\n}\n"
    },
    {
      "path": "packages/components/src/components/date-time-picker/index.ts",
      "type": "registry:file",
      "target": "~/components/dethink/components/date-time-picker/index.ts",
      "content": "export {\n  DateTimePicker,\n  type DateTimePickerPreset,\n  type DateTimePickerProps,\n} from \"./date-time-picker\";\nexport {\n  getDateTimePickerPlaceholderValue,\n  getDateTimePickerTimeInputStep,\n  getDateTimePickerTimeInputValue,\n  getDateTimePickerTimeInputValueChange,\n  getDateTimePickerTimeZone,\n  hasTimeZone,\n  getDateTimePickerTimeOptionValue,\n  getDateTimePickerTimeOptions,\n  isDateTimePickerTimeOptionSelected,\n  serializeDateTimePickerValue,\n  type DateTimePickerGranularity,\n  type DateTimePickerTimeOption,\n  type DateTimePickerTimeStep,\n  type DateTimePickerValue,\n  type DateTimePickerWeekStartsOn,\n} from \"./date-time-picker-utils\";\n"
    },
    {
      "path": "packages/components/src/utils/cn.ts",
      "type": "registry:file",
      "target": "~/components/dethink/utils/cn.ts",
      "content": "import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs));\n}\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"
    }
  ]
}
