{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"date-time-picker","title":"Date Time Picker","description":"Combined locale-aware date and time picker with separate disclosures and one native form value. Search terms: calendar.","dependencies":["@internationalized/date@3.12.3","lucide-react@1.18.0","react-aria-components@1.20.0"],"registryDependencies":["@siteplane/base","@siteplane/button","@siteplane/calendar","@siteplane/date-field","@siteplane/input","@siteplane/input-group","@siteplane/locale","@siteplane/time-field"],"files":[{"path":"packages/ui/src/components/date-time-picker/date-time-picker.tsx","content":"\"use client\";\n\nimport {\n  CalendarDateTime,\n  type DateValue,\n  Time,\n  toCalendarDateTime as toCalendarDateTimeAt,\n} from \"@internationalized/date\";\nimport { CalendarDaysIcon, ClockIcon } from \"lucide-react\";\nimport * as React from \"react\";\nimport {\n  Button as DateTimeButtonPrimitive,\n  DateFieldStateContext,\n  DateInput as DateTimeInputPrimitive,\n  DatePicker as DateTimePickerPrimitive,\n  type DatePickerState,\n  DateSegment as DateTimeSegmentPrimitive,\n  Dialog as DateTimeDialogPrimitive,\n  DialogTrigger as TimeSelectorTriggerPrimitive,\n  Group as DateTimeGroupPrimitive,\n  Popover as DateTimePopoverPrimitive,\n  useLocale,\n} from \"react-aria-components\";\n\nimport { isInvalidAriaValue } from \"../../system/form/aria-invalid\";\nimport { useFormReset } from \"../../system/form/use-form-reset\";\nimport { normalizeFormattedText } from \"../../system/locale/formatted-text\";\nimport { SiteplaneLocaleProvider } from \"../../system/locale/siteplane-locale-provider\";\nimport {\n  type SiteplaneControlSize,\n  SiteplaneUIPortalScope,\n  useResolvedControlSize,\n} from \"../../system/provider/siteplane-ui-provider\";\nimport {\n  formControlTextClassNames,\n  surfaceClassNames,\n  type FormControlTextSize,\n  type FormControlVariant,\n} from \"../../system/styles/surface\";\nimport { cn } from \"../../system/utils/cn\";\nimport { Button } from \"../button\";\nimport { Calendar } from \"../calendar\";\nimport {\n  calendarMonthBounds,\n  type CalendarOptions,\n  fromNativeDate,\n  isCalendarDayDisabled,\n  toNativeDate,\n} from \"../calendar/calendar\";\nimport {\n  type FieldControlBridge,\n  hasClearedSegment,\n  type SegmentedValue,\n  useFieldControlBridge,\n  useFieldControlProxy,\n  useFieldErrorSegments,\n  useSegmentedValue,\n} from \"../field/field-control-bridge\";\nimport { Input } from \"../input\";\nimport { ExternalInputOwner } from \"../input/input\";\nimport { InputGroup, InputGroupAddon, InputGroupText } from \"../input-group\";\nimport {\n  type TimeFieldHourCycle,\n  TimeFieldOptionColumn,\n  useHourOptionLabels,\n} from \"../time-field/time-field\";\n\n// The shared date segment styles are owned by DateField.\nimport \"../date-field/date-field.css\";\nimport \"./date-time-picker.css\";\n\nexport type DateTimePickerValue = {\n  date: Date;\n  time: {\n    hour: number;\n    minute: number;\n  };\n};\n\nexport interface DateTimePickerProps {\n  \"aria-describedby\"?: string | undefined;\n  \"aria-invalid\"?: React.AriaAttributes[\"aria-invalid\"];\n  \"aria-label\"?: string;\n  \"aria-labelledby\"?: string | undefined;\n  calendarProps?: CalendarOptions;\n  className?: string;\n  defaultValue?: DateTimePickerValue;\n  disabled?: boolean;\n  form?: string;\n  /**\n   * The clock the time segments and the time selector show: `24` (00 to 23),\n   * or `12` with the locale's AM and PM. The value stays in 24-hour time.\n   */\n  hourCycle?: TimeFieldHourCycle;\n  /**\n   * The error a required picker shows while its date and time are\n   * incomplete, such as after a segment is cleared. Default: \"Enter a\n   * complete date and time.\"\n   */\n  incompleteLabel?: string;\n  locale?: string;\n  minuteStep?: number;\n  name?: string;\n  onValueChange?: (value: DateTimePickerValue | undefined) => void;\n  openDatePickerLabel?: string;\n  openTimePickerLabel?: string;\n  readOnly?: boolean;\n  required?: boolean;\n  size?: SiteplaneControlSize;\n  textSize?: FormControlTextSize;\n  value?: DateTimePickerValue | null;\n  variant?: FormControlVariant;\n}\n\nconst actionSizes = {\n  md: \"icon-xs\",\n  lg: \"icon-xs\",\n  sm: \"icon-2xs\",\n  xl: \"icon-sm\",\n} as const satisfies Record<\n  SiteplaneControlSize,\n  \"icon-2xs\" | \"icon-xs\" | \"icon-sm\"\n>;\n\nconst hourOptions = Array.from({ length: 24 }, (_, hour) =>\n  String(hour).padStart(2, \"0\"),\n);\n\nfunction normalizeTime(value: DateTimePickerValue[\"time\"]) {\n  return {\n    hour: Math.min(Math.max(Math.trunc(value.hour) || 0, 0), 23),\n    minute: Math.min(Math.max(Math.trunc(value.minute) || 0, 0), 59),\n  };\n}\n\nfunction toCalendarDateTime(\n  value: DateTimePickerValue | null | undefined,\n): CalendarDateTime | null {\n  if (!value) return null;\n  const time = normalizeTime(value.time);\n  return new CalendarDateTime(\n    value.date.getFullYear(),\n    value.date.getMonth() + 1,\n    value.date.getDate(),\n    time.hour,\n    time.minute,\n  );\n}\n\nfunction toNativeValue(\n  value: DateValue | null | undefined,\n): DateTimePickerValue | undefined {\n  if (!value || !(\"hour\" in value)) return undefined;\n  return {\n    date: toNativeDate(value),\n    time: { hour: value.hour, minute: value.minute },\n  };\n}\n\nfunction formatValue(value: DateValue | null | undefined): string {\n  const nativeValue = toNativeValue(value);\n  if (!nativeValue) return \"\";\n  return `${String(nativeValue.date.getFullYear()).padStart(4, \"0\")}-${String(nativeValue.date.getMonth() + 1).padStart(2, \"0\")}-${String(nativeValue.date.getDate()).padStart(2, \"0\")}T${String(nativeValue.time.hour).padStart(2, \"0\")}:${String(nativeValue.time.minute).padStart(2, \"0\")}`;\n}\n\n/**\n * The Calendar's month bounds as date-time bounds of the picker: from the\n * start of the first allowed day to 23:59 on the last.\n */\nfunction dateTimeBounds(calendarProps: CalendarOptions | undefined): {\n  maxValue?: CalendarDateTime;\n  minValue?: CalendarDateTime;\n} {\n  const { maxValue, minValue } = calendarMonthBounds(calendarProps ?? {});\n  return {\n    ...(maxValue === undefined\n      ? {}\n      : { maxValue: toCalendarDateTimeAt(maxValue, new Time(23, 59)) }),\n    ...(minValue === undefined\n      ? {}\n      : { minValue: toCalendarDateTimeAt(minValue) }),\n  };\n}\n\ntype DateTimeSegmentValue = React.ComponentProps<\n  typeof DateTimeSegmentPrimitive\n>[\"segment\"];\n\nfunction isTimeSegment(segment: DateTimeSegmentValue | undefined): boolean {\n  return (\n    segment?.type === \"hour\" ||\n    segment?.type === \"minute\" ||\n    segment?.type === \"second\" ||\n    segment?.type === \"dayPeriod\" ||\n    segment?.type === \"timeZoneName\"\n  );\n}\n\nfunction getSegmentPart(\n  segment: DateTimeSegmentValue,\n  segments: readonly DateTimeSegmentValue[],\n): \"date\" | \"hidden\" | \"time\" {\n  if (isTimeSegment(segment)) return \"time\";\n  if (segment.type !== \"literal\") return \"date\";\n  if (segment.text.includes(\":\")) return \"time\";\n  // The space before AM or PM on a 12-hour clock lies between two time\n  // segments, so it belongs to the time. React Aria isolates the time with\n  // bidi marks, which are literals of their own, so the neighbours are the\n  // nearest segments that are not literals.\n  if (/^\\s+$/.test(segment.text)) {\n    const index = segments.indexOf(segment);\n    const previous = segments\n      .slice(0, Math.max(index, 0))\n      .findLast(({ type }) => type !== \"literal\");\n    const next = segments\n      .slice(index + 1)\n      .find(({ type }) => type !== \"literal\");\n    if (index >= 0 && isTimeSegment(previous) && isTimeSegment(next)) {\n      return \"time\";\n    }\n  }\n  if (/^[./\\-\\s]+$/.test(segment.text)) return \"date\";\n  return \"hidden\";\n}\n\n// The picker's DateInput keeps its own field state; the segments tell the\n// picker whether it shows a cleared segment.\nfunction DateTimeSegment({\n  onClearedChange,\n  segment,\n}: {\n  onClearedChange: (cleared: boolean) => void;\n  segment: DateTimeSegmentValue;\n}): React.ReactElement {\n  const fieldState = React.useContext(DateFieldStateContext);\n  const cleared = hasClearedSegment(fieldState);\n  React.useLayoutEffect(() => onClearedChange(cleared));\n  const part = getSegmentPart(segment, fieldState?.segments ?? []);\n  // The segment shows the text the server and the browser agree on: Node's\n  // formatToParts puts a narrow no-break space before AM and PM where the\n  // browser has a plain one (time-field.tsx).\n  return (\n    <DateTimeSegmentPrimitive\n      className={cn(\n        \"sp-date-time-picker-segment\",\n        part === \"date\" && \"sp-date-segment\",\n        part === \"time\" && \"sp-time-segment\",\n      )}\n      data-date-time-part={part}\n      // A separator cancels the padding of the segments beside it: the date\n      // separators, the colon and the space before AM or PM.\n      data-literal-kind={\n        segment.type === \"literal\" &&\n        (/[.:/-]/.test(segment.text) ||\n          (part === \"time\" && /^\\s+$/.test(segment.text)))\n          ? \"separator\"\n          : undefined\n      }\n      data-slot=\"date-time-picker-segment\"\n      segment={segment}\n    >\n      {({ text }) => normalizeFormattedText(text)}\n    </DateTimeSegmentPrimitive>\n  );\n}\n\n// The date, the date button, the comma and the time read in the locale's\n// direction, which React Aria's arrow keys follow, whatever the page's: an\n// en-US value keeps its date before its time in a right-to-left page. The dir\n// attribute also isolates the row from the text around it. The field keeps\n// the page's direction, so the time button ends it at the page's inline end,\n// and date-time-picker.css keeps the field's insets on the field's sides.\nfunction DateTimeSegments({\n  children,\n}: {\n  children: React.ReactNode;\n}): React.ReactElement {\n  const { direction } = useLocale();\n\n  return (\n    <div\n      className=\"sp-date-time-picker-segments\"\n      data-slot=\"date-time-picker-segments\"\n      dir={direction}\n    >\n      {children}\n    </div>\n  );\n}\n\nfunction DateTimeHiddenInput({\n  cleared,\n  controlled,\n  controlledInvalid,\n  controlRef,\n  defaultValue,\n  field,\n  focusTarget,\n  form,\n  required,\n  segmented,\n  state,\n}: {\n  cleared: boolean;\n  controlled: boolean;\n  controlledInvalid: boolean | undefined;\n  controlRef: React.RefObject<HTMLDivElement | null>;\n  defaultValue: DateTimePickerValue | undefined;\n  field: FieldControlBridge;\n  focusTarget: () => HTMLElement | null;\n  form: string | undefined;\n  required: boolean | undefined;\n  segmented: SegmentedValue<DateValue>;\n  state: DatePickerState;\n}): React.ReactElement {\n  const inputRef = useFormReset<HTMLInputElement>(form, () => {\n    if (!controlled) state.setValue(toCalendarDateTime(defaultValue));\n  });\n  // A cleared segment leaves the picker without a value, although React Aria\n  // still holds the last complete one.\n  const fieldValue = cleared ? null : state.value;\n  const serializedValue = formatValue(fieldValue);\n  React.useLayoutEffect(() => {\n    segmented.sync({ cleared, effective: fieldValue, value: state.value });\n  });\n  const proxyProps = useFieldControlProxy({\n    controlled: controlledInvalid !== undefined,\n    disabled: field.disabled,\n    displayValidation: state.displayValidation,\n    focusTarget,\n    form,\n    name: field.name,\n    onRevalidate: state.commitValidation,\n    realtimeValidation: state.realtimeValidation,\n    required,\n    value: serializedValue,\n  });\n  useFieldErrorSegments(controlRef, {\n    controlled: controlledInvalid !== undefined,\n    ownerInvalid: state.isInvalid,\n  });\n  return (\n    <>\n      <Input\n        disabled={field.disabled}\n        form={form}\n        name={field.name}\n        readOnly\n        ref={inputRef}\n        required={required}\n        type=\"hidden\"\n        value={serializedValue}\n      />\n      {proxyProps ? (\n        <ExternalInputOwner>\n          <Input nativeInput unstyled {...proxyProps} />\n        </ExternalInputOwner>\n      ) : null}\n    </>\n  );\n}\n\nfunction TimeSelector({\n  hourCycle,\n  minuteStep,\n  onClose,\n  open,\n  state,\n}: {\n  hourCycle: TimeFieldHourCycle;\n  minuteStep: number;\n  onClose: () => void;\n  open: boolean;\n  state: DatePickerState;\n}): React.ReactElement {\n  const hourOptionLabels = useHourOptionLabels(hourCycle);\n  const resolvedStep = Math.min(Math.max(Math.trunc(minuteStep) || 5, 1), 60);\n  const minuteOptions = React.useMemo(\n    () =>\n      Array.from({ length: Math.ceil(60 / resolvedStep) }, (_, index) =>\n        String(index * resolvedStep).padStart(2, \"0\"),\n      ),\n    [resolvedStep],\n  );\n  const hour = state.timeValue?.hour ?? 0;\n  const minute = state.timeValue?.minute ?? 0;\n\n  return (\n    <div\n      className=\"sp-date-time-picker-time-selector sp-time-selector\"\n      data-slot=\"date-time-picker-time-selector\"\n    >\n      <TimeFieldOptionColumn\n        label=\"Hours\"\n        onValueChange={(nextHour) =>\n          state.setTimeValue(new Time(Number(nextHour), minute))\n        }\n        open={open}\n        optionLabels={hourOptionLabels}\n        options={hourOptions}\n        slot=\"time-field-hours\"\n        value={String(hour).padStart(2, \"0\")}\n      />\n      <TimeFieldOptionColumn\n        label=\"Minutes\"\n        onValueChange={(nextMinute) => {\n          state.setTimeValue(new Time(hour, Number(nextMinute)));\n          onClose();\n        }}\n        open={open}\n        options={minuteOptions}\n        slot=\"time-field-minutes\"\n        value={String(minute).padStart(2, \"0\")}\n      />\n    </div>\n  );\n}\n\nexport function DateTimePicker({\n  \"aria-describedby\": ariaDescribedBy,\n  \"aria-invalid\": ariaInvalid,\n  \"aria-label\": ariaLabel,\n  \"aria-labelledby\": ariaLabelledBy,\n  calendarProps,\n  className,\n  defaultValue,\n  disabled,\n  form,\n  hourCycle = 24,\n  incompleteLabel = \"Enter a complete date and time.\",\n  locale,\n  minuteStep = 5,\n  name,\n  onValueChange,\n  openDatePickerLabel = \"Open date calendar\",\n  openTimePickerLabel = \"Open time selector\",\n  readOnly,\n  required,\n  size,\n  textSize,\n  value,\n  variant = \"default\",\n}: DateTimePickerProps): React.ReactElement {\n  const controlled = value !== undefined;\n  const resolvedSize = useResolvedControlSize(size);\n  const resolvedTextSize =\n    textSize ??\n    (resolvedSize === \"lg\" || resolvedSize === \"xl\" ? \"base\" : undefined);\n  const [timeOpen, setTimeOpen] = React.useState(false);\n  const controlRef = React.useRef<HTMLDivElement>(null);\n  const field = useFieldControlBridge({\n    \"aria-describedby\": ariaDescribedBy,\n    \"aria-labelledby\": ariaLabelledBy,\n    disabled,\n    name,\n  });\n  const fieldAriaLabel = field.labelledBy ? undefined : ariaLabel;\n  // React Aria validates the typed date and time unless the application\n  // controls the invalid state through aria-invalid or an invalid Field.\n  const controlledInvalid =\n    ariaInvalid !== undefined\n      ? isInvalidAriaValue(ariaInvalid)\n      : field.invalid || undefined;\n  // A cleared segment empties the picker's value.\n  const [cleared, setCleared] = React.useState(false);\n  const segmented = useSegmentedValue<DateValue>({\n    controlled,\n    defaultValue: toCalendarDateTime(defaultValue),\n    incompleteMessage: incompleteLabel,\n    onValueChange: (nextValue) => onValueChange?.(toNativeValue(nextValue)),\n    required,\n    serialize: formatValue,\n    value: toCalendarDateTime(value),\n  });\n  // React Aria's optional props reject `undefined` under\n  // exactOptionalPropertyTypes, so unset values are omitted.\n  const picker = (\n    <DateTimePickerPrimitive<CalendarDateTime>\n      {...(field.describedBy === undefined\n        ? {}\n        : { \"aria-describedby\": field.describedBy })}\n      {...(fieldAriaLabel === undefined\n        ? {}\n        : { \"aria-label\": fieldAriaLabel })}\n      {...(field.labelledBy === undefined\n        ? {}\n        : { \"aria-labelledby\": field.labelledBy })}\n      className=\"sp-date-time-picker sp-base-date-time-picker contents\"\n      data-foundation=\"date-time-picker\"\n      data-slot=\"date-time-picker\"\n      {...(controlled\n        ? { value: segmented.ownerValue as CalendarDateTime | null }\n        : { defaultValue: toCalendarDateTime(defaultValue) })}\n      granularity=\"minute\"\n      hourCycle={hourCycle}\n      isDateUnavailable={(date) =>\n        isCalendarDayDisabled(toNativeDate(date), calendarProps?.disabled)\n      }\n      isDisabled={field.disabled}\n      {...(controlledInvalid === undefined\n        ? {}\n        : { isInvalid: controlledInvalid })}\n      {...(readOnly === undefined ? {} : { isReadOnly: readOnly })}\n      {...(required === undefined ? {} : { isRequired: required })}\n      {...dateTimeBounds(calendarProps)}\n      // Typed segments start from the calendar's today at midnight,\n      // `todayDate` when set.\n      {...(calendarProps?.todayDate === undefined\n        ? {}\n        : {\n            placeholderValue: toCalendarDateTimeAt(\n              fromNativeDate(calendarProps.todayDate),\n            ),\n          })}\n      onChange={segmented.onOwnerChange}\n      shouldCloseOnSelect\n      shouldForceLeadingZeros\n      validate={segmented.validate}\n    >\n      {({ isInvalid, state }) => (\n        <>\n          <DateTimeGroupPrimitive\n            className={cn(\"inline-flex max-w-full\", className)}\n          >\n            <InputGroup\n              aria-label={undefined}\n              // The default width never shrinks below the segments in a flex\n              // row (shrink-0 while w-max); a width utility replaces both.\n              className={cn(\n                \"sp-date-time-picker-control w-max [&.w-max]:shrink-0\",\n                className,\n              )}\n              data-invalid={\n                (controlledInvalid ?? (isInvalid || field.valid === false)) ||\n                undefined\n              }\n              data-readonly={(readOnly && !field.disabled) || undefined}\n              data-size={resolvedSize}\n              data-slot=\"date-time-picker-control\"\n              ref={controlRef}\n              // The field text scale: 14 px, or 16 px at xl.\n              textSize={\n                textSize ?? (resolvedSize === \"xl\" ? \"base\" : undefined)\n              }\n              variant={variant}\n            >\n              <DateTimeSegments>\n                <DateTimeInputPrimitive\n                  className=\"sp-date-time-picker-input sp-base-date-field contents\"\n                  data-foundation=\"date-field\"\n                  data-profile=\"input-group\"\n                  data-slot=\"date-time-picker-input\"\n                >\n                  {(segment) => (\n                    <DateTimeSegment\n                      onClearedChange={setCleared}\n                      segment={segment}\n                    />\n                  )}\n                </DateTimeInputPrimitive>\n                <InputGroupAddon\n                  align=\"inline-end\"\n                  data-date-time-part=\"date-action\"\n                >\n                  <Button\n                    aria-label={openDatePickerLabel}\n                    data-slot=\"date-time-picker-date-trigger\"\n                    disabled={field.disabled || readOnly}\n                    render={<DateTimeButtonPrimitive />}\n                    profile=\"field-action\"\n                    size={actionSizes[resolvedSize]}\n                  >\n                    <CalendarDaysIcon aria-hidden=\"true\" />\n                  </Button>\n                </InputGroupAddon>\n                <InputGroupText\n                  aria-hidden=\"true\"\n                  className=\"mx-1 text-muted-foreground\"\n                  data-date-time-part=\"separator\"\n                  data-slot=\"date-time-picker-separator\"\n                >\n                  ,\n                </InputGroupText>\n              </DateTimeSegments>\n              <InputGroupAddon\n                align=\"inline-end\"\n                data-date-time-part=\"time-action\"\n              >\n                <TimeSelectorTriggerPrimitive\n                  isOpen={timeOpen}\n                  onOpenChange={setTimeOpen}\n                >\n                  <Button\n                    aria-label={openTimePickerLabel}\n                    data-slot=\"date-time-picker-time-trigger\"\n                    disabled={field.disabled || readOnly || !state.dateValue}\n                    render={<DateTimeButtonPrimitive slot={null} />}\n                    profile=\"field-action\"\n                    size={actionSizes[resolvedSize]}\n                  >\n                    <ClockIcon aria-hidden=\"true\" />\n                  </Button>\n                  <DateTimePopoverPrimitive\n                    className={cn(\n                      surfaceClassNames.floatingOverlay,\n                      \"sp-time-field-popover sp-date-time-picker-time-popover\",\n                      resolvedTextSize\n                        ? formControlTextClassNames[resolvedTextSize]\n                        : \"text-base sm:text-sm\",\n                    )}\n                    data-slot=\"date-time-picker-time-popover\"\n                    offset={6}\n                    placement=\"bottom end\"\n                  >\n                    <SiteplaneUIPortalScope>\n                      <DateTimeDialogPrimitive\n                        aria-label=\"Choose time\"\n                        className=\"outline-none\"\n                      >\n                        <TimeSelector\n                          hourCycle={hourCycle}\n                          minuteStep={minuteStep}\n                          onClose={() => setTimeOpen(false)}\n                          open={timeOpen}\n                          state={state}\n                        />\n                      </DateTimeDialogPrimitive>\n                    </SiteplaneUIPortalScope>\n                  </DateTimePopoverPrimitive>\n                </TimeSelectorTriggerPrimitive>\n              </InputGroupAddon>\n            </InputGroup>\n          </DateTimeGroupPrimitive>\n          <DateTimePopoverPrimitive\n            className={cn(\n              surfaceClassNames.floatingOverlay,\n              \"sp-date-time-picker-calendar-popover\",\n            )}\n            data-slot=\"date-time-picker-calendar-popover\"\n            offset={4}\n            placement=\"bottom start\"\n          >\n            <SiteplaneUIPortalScope>\n              <DateTimeDialogPrimitive\n                className=\"sp-date-time-picker-calendar-dialog outline-none\"\n                data-slot=\"date-time-picker-calendar-dialog\"\n              >\n                <Calendar\n                  {...calendarProps}\n                  {...(fieldAriaLabel === undefined\n                    ? {}\n                    : { \"aria-label\": fieldAriaLabel })}\n                  {...(fieldAriaLabel || field.labelledBy === undefined\n                    ? {}\n                    : { \"aria-labelledby\": field.labelledBy })}\n                  data-slot=\"date-time-picker-calendar\"\n                />\n              </DateTimeDialogPrimitive>\n            </SiteplaneUIPortalScope>\n          </DateTimePopoverPrimitive>\n          <DateTimeHiddenInput\n            cleared={cleared}\n            controlled={controlled}\n            controlledInvalid={controlledInvalid}\n            controlRef={controlRef}\n            defaultValue={defaultValue}\n            field={field}\n            focusTarget={() =>\n              controlRef.current?.querySelector<HTMLElement>(\n                '[role=\"spinbutton\"], [role=\"textbox\"]',\n              ) ?? null\n            }\n            form={form}\n            required={required}\n            segmented={segmented}\n            state={state}\n          />\n        </>\n      )}\n    </DateTimePickerPrimitive>\n  );\n\n  return locale ? (\n    <SiteplaneLocaleProvider locale={locale}>{picker}</SiteplaneLocaleProvider>\n  ) : (\n    picker\n  );\n}\n","type":"registry:ui","target":"~/src/siteplane/components/date-time-picker/date-time-picker.tsx"},{"path":"packages/ui/src/components/date-time-picker/date-time-picker.css","content":"@layer theme, base, components, siteplane, utilities;\n\n@layer siteplane {\n  /* The heights are the Input scale (28, 32, 36 and 48 px), so the picker is as\n   * tall as the Input, Select, Button, DateField and TimeField of the same size. */\n  .sp-date-time-picker-control {\n    --date-time-picker-control-height: var(--sp-rw-control-md);\n    --date-time-picker-segment-start: calc(\n      var(--sp-rw-space-3) - var(--input-segment-padding-inline)\n    );\n    max-width: 100%;\n    height: var(--date-time-picker-control-height);\n    overflow: hidden;\n  }\n\n  .sp-date-time-picker-control[data-size=\"sm\"] {\n    --date-time-picker-control-height: var(--sp-rw-control-sm);\n    --date-time-picker-segment-start: calc(\n      var(--sp-rw-space-2-5) - var(--input-segment-padding-inline)\n    );\n  }\n\n  .sp-date-time-picker-control[data-size=\"lg\"] {\n    --date-time-picker-control-height: var(--sp-rw-control-lg);\n  }\n\n  .sp-date-time-picker-control[data-size=\"xl\"] {\n    --date-time-picker-control-height: var(--sp-rw-control-xl);\n    --date-time-picker-segment-start: calc(\n      var(--sp-rw-space-4) - var(--input-segment-padding-inline)\n    );\n  }\n\n  /* The segment row holds the date, the date button, the comma and the time,\n   * in the locale's direction (its dir). The insets belong to the field,\n   * which takes the page's direction: it pads its start, and the time button\n   * ends it at the page's inline end. */\n  .sp-date-time-picker-control {\n    padding-inline-start: var(--date-time-picker-segment-start);\n  }\n\n  .sp-date-time-picker-segments {\n    display: flex;\n    flex: none;\n    align-items: center;\n    align-self: stretch;\n  }\n\n  /* The row orders its parts: the date, its button, the comma and the time.\n   * The time button follows the row in the field. */\n  .sp-date-time-picker-segment[data-date-time-part=\"date\"] {\n    order: 1;\n  }\n\n  .sp-date-time-picker-control [data-date-time-part=\"date-action\"] {\n    order: 2;\n  }\n\n  .sp-date-time-picker-control [data-date-time-part=\"separator\"] {\n    order: 3;\n  }\n\n  .sp-date-time-picker-segment[data-date-time-part=\"time\"] {\n    order: 4;\n  }\n\n  .sp-date-time-picker-control [data-date-time-part=\"time-action\"] {\n    order: 5;\n  }\n\n  .sp-date-time-picker-segment[data-date-time-part=\"hidden\"] {\n    display: none;\n  }\n\n  /* The surface (border, radius, shadow) is the floating-md recipe that\n   * design-rework.css draws for each popover's data-slot. */\n  .sp-date-time-picker-calendar-popover,\n  .sp-date-time-picker-time-popover {\n    z-index: 50;\n    outline: none;\n    transform-origin: var(--trigger-anchor-point);\n  }\n\n  .sp-date-time-picker-calendar-popover {\n    width: max-content;\n    padding: var(--sp-rw-space-4);\n  }\n\n  /* The time selector is the Time Field's: its width comes from\n   * .sp-time-field-popover, and its padding is the compact one of the Time\n   * Field's Popover, so both selectors look alike. */\n  .sp-date-time-picker-time-popover {\n    padding: var(--sp-rw-space-2);\n  }\n\n  :is(\n    .sp-date-time-picker-calendar-popover,\n    .sp-date-time-picker-time-popover\n  )[data-entering] {\n    animation: sp-date-time-popover-enter var(--motion-duration-overlay)\n      var(--motion-ease-standard) both;\n  }\n\n  :is(\n    .sp-date-time-picker-calendar-popover,\n    .sp-date-time-picker-time-popover\n  )[data-exiting] {\n    animation: sp-date-time-popover-exit var(--motion-duration-fast)\n      var(--motion-ease-soft-in-out) both;\n  }\n\n  @keyframes sp-date-time-popover-enter {\n    from {\n      opacity: 0;\n      transform: scale(0.98);\n    }\n  }\n\n  @keyframes sp-date-time-popover-exit {\n    to {\n      opacity: 0;\n      transform: scale(0.98);\n    }\n  }\n\n  @media (prefers-reduced-motion: reduce) {\n    :is(\n      .sp-date-time-picker-calendar-popover,\n      .sp-date-time-picker-time-popover\n    )[data-entering],\n    :is(\n      .sp-date-time-picker-calendar-popover,\n      .sp-date-time-picker-time-popover\n    )[data-exiting] {\n      animation-duration: 1ms;\n    }\n  }\n\n  html[data-motion-reduce=\"true\"]\n    :is(\n      .sp-date-time-picker-calendar-popover,\n      .sp-date-time-picker-time-popover\n    )[data-entering],\n  html[data-motion-reduce=\"true\"]\n    :is(\n      .sp-date-time-picker-calendar-popover,\n      .sp-date-time-picker-time-popover\n    )[data-exiting],\n  [data-sp-motion=\"reduced\"]\n    :is(\n      .sp-date-time-picker-calendar-popover,\n      .sp-date-time-picker-time-popover\n    )[data-entering],\n  [data-sp-motion=\"reduced\"]\n    :is(\n      .sp-date-time-picker-calendar-popover,\n      .sp-date-time-picker-time-popover\n    )[data-exiting] {\n    animation-duration: 1ms;\n  }\n}\n","type":"registry:ui","target":"~/src/siteplane/components/date-time-picker/date-time-picker.css"},{"path":"packages/ui/src/components/date-time-picker/index.ts","content":"export * from \"./date-time-picker\";\n","type":"registry:ui","target":"~/src/siteplane/components/date-time-picker/index.ts"}],"type":"registry:ui","docs":"https://ui.siteplane.io/docs-markdown/components/date-time-picker.md","categories":["Date & color"],"meta":{"avoidWhen":["Avoid when date and time are independent values or only one of them is required."],"description":"Combined locale-aware date and time picker with separate disclosures and one native form value.","knownLimitations":["Values use native Date calendar fields in the local time zone; there is no time zone selector and no conversion between zones.","The time button stays disabled until a date is set."],"providerNotes":["Use the locale prop for this picker, or SiteplaneLocaleProvider for a shared locale. SiteplaneUIProvider configures visual defaults, not locale."],"releaseStatus":"stable","searchTerms":["date","time","picker","calendar","form"],"useWhen":["Use when one local appointment date and time should be edited and submitted together."]}}