@drivenets/design-system 0.7.3 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/dist/index.cjs +4445 -3769
  3. package/dist/index.d.cts +1224 -945
  4. package/dist/index.d.cts.map +1 -1
  5. package/dist/index.d.ts +1224 -945
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +4446 -3776
  8. package/dist/index.js.map +1 -1
  9. package/dist/index.min.css +1 -1
  10. package/package.json +7 -8
  11. package/src/components/ds-autocomplete/ds-autocomplete.module.scss +1 -1
  12. package/src/components/ds-autocomplete/ds-autocomplete.tsx +13 -8
  13. package/src/components/ds-autocomplete/ds-autocomplete.types.ts +2 -1
  14. package/src/components/ds-comment-bubble/ds-comment-bubble.module.scss +6 -2
  15. package/src/components/ds-comment-bubble/ds-comment-bubble.tsx +22 -15
  16. package/src/components/ds-comment-bubble/ds-comment-bubble.types.ts +1 -0
  17. package/src/components/ds-date-input/ds-date-input.tsx +3 -4
  18. package/src/components/ds-date-input/ds-date-input.types.ts +3 -1
  19. package/src/components/ds-date-input/index.ts +10 -0
  20. package/src/components/ds-date-picker/components/calendar-table.module.scss +93 -0
  21. package/src/components/ds-date-picker/components/calendar-view-control/calendar-view-control.module.scss +48 -0
  22. package/src/components/ds-date-picker/components/calendar-view-control/calendar-view-control.tsx +20 -0
  23. package/src/components/ds-date-picker/components/calendar-view-control/index.ts +1 -0
  24. package/src/components/ds-date-picker/components/day-view/day-view.module.scss +12 -0
  25. package/src/components/ds-date-picker/components/day-view/day-view.tsx +45 -0
  26. package/src/components/ds-date-picker/components/day-view/index.ts +1 -0
  27. package/src/components/ds-date-picker/components/month-view/index.ts +1 -0
  28. package/src/components/ds-date-picker/components/month-view/month-view.module.scss +4 -0
  29. package/src/components/ds-date-picker/components/month-view/month-view.tsx +35 -0
  30. package/src/components/ds-date-picker/components/year-view/index.ts +1 -0
  31. package/src/components/ds-date-picker/components/year-view/year-view.module.scss +4 -0
  32. package/src/components/ds-date-picker/components/year-view/year-view.tsx +35 -0
  33. package/src/components/ds-date-picker/ds-date-picker.module.scss +19 -0
  34. package/src/components/ds-date-picker/ds-date-picker.tsx +209 -0
  35. package/src/components/ds-date-picker/ds-date-picker.types.ts +94 -0
  36. package/src/components/ds-date-picker/ds-date-picker.utils.ts +87 -0
  37. package/src/components/ds-date-picker/ds-date-picker.utils.unit.test.ts +226 -0
  38. package/src/components/ds-date-picker/index.ts +2 -0
  39. package/src/components/ds-date-range-picker/ds-date-range-picker.module.scss +20 -0
  40. package/src/components/ds-date-range-picker/ds-date-range-picker.tsx +101 -0
  41. package/src/components/ds-date-range-picker/ds-date-range-picker.types.ts +95 -0
  42. package/src/components/ds-date-range-picker/ds-date-range-picker.utils.ts +19 -0
  43. package/src/components/ds-date-range-picker/ds-date-range-picker.utils.unit.test.ts +48 -0
  44. package/src/components/ds-date-range-picker/index.ts +9 -0
  45. package/src/components/ds-file-upload/ds-file-upload-api.types.ts +1 -0
  46. package/src/components/ds-file-upload/hooks/use-file-upload.ts +1 -1
  47. package/src/components/ds-file-upload/index.ts +6 -1
  48. package/src/components/ds-file-upload/utils/file-factory.ts +1 -0
  49. package/src/components/ds-file-upload/utils/file-factory.unit.test.ts +37 -0
  50. package/src/components/ds-form-control/ds-form-control.tsx +5 -0
  51. package/src/components/ds-key-value-pair/ds-key-value-pair.module.scss +72 -0
  52. package/src/components/ds-key-value-pair/ds-key-value-pair.tsx +47 -0
  53. package/src/components/ds-key-value-pair/ds-key-value-pair.types.ts +24 -0
  54. package/src/components/ds-key-value-pair/index.ts +3 -0
  55. package/src/components/ds-loader/ds-loader.tsx +1 -1
  56. package/src/components/ds-status-badge/ds-status-badge.module.scss +1 -1
  57. package/src/components/ds-table/components/ds-table-body-virtualized/ds-table-body-virtualized.tsx +4 -2
  58. package/src/components/ds-time-picker/components/time-item/index.ts +1 -0
  59. package/src/components/ds-time-picker/components/time-item/time-item.module.scss +37 -0
  60. package/src/components/ds-time-picker/components/time-item/time-item.tsx +26 -0
  61. package/src/components/ds-time-picker/components/time-item/time-item.types.ts +10 -0
  62. package/src/components/ds-time-picker/components/time-scroller/index.ts +2 -0
  63. package/src/components/ds-time-picker/components/time-scroller/time-scroller.module.scss +25 -0
  64. package/src/components/ds-time-picker/components/time-scroller/time-scroller.tsx +61 -0
  65. package/src/components/ds-time-picker/components/time-scroller/time-scroller.types.ts +17 -0
  66. package/src/components/ds-time-picker/components/time-scroller/time-scroller.utils.ts +44 -0
  67. package/src/components/ds-time-picker/ds-time-picker.tsx +158 -0
  68. package/src/components/ds-time-picker/ds-time-picker.types.ts +48 -0
  69. package/src/components/ds-time-picker/ds-time-picker.utils.ts +176 -0
  70. package/src/components/ds-time-picker/ds-time-picker.utils.unit.test.ts +316 -0
  71. package/src/components/ds-time-picker/index.ts +3 -0
  72. package/src/components/ds-tooltip/ds-tooltip.module.scss +1 -7
  73. package/src/components/ds-tooltip/ds-tooltip.tsx +9 -9
  74. package/src/components/ds-tooltip/ds-tooltip.types.ts +7 -1
  75. package/src/index.ts +4 -0
  76. package/src/styles/_root_updated.scss +593 -0
  77. package/src/styles/shared/_dropdown.scss +4 -1
  78. package/src/utils/use-controlled.browser.test.ts +79 -0
  79. package/src/utils/use-controlled.ts +18 -0
@@ -0,0 +1,61 @@
1
+ import classNames from 'classnames';
2
+ import { TimeItem } from '../time-item';
3
+ import type { TimePeriod, TimeScrollerProps } from './time-scroller.types';
4
+ import styles from './time-scroller.module.scss';
5
+ import { useScrollToSelected } from './time-scroller.utils';
6
+
7
+ /**
8
+ * In AM/PM mode, hours start from 12 and then go from to 1 to 11.
9
+ * e.g. 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
10
+ */
11
+ const HOURS = [12, ...Array.from({ length: 11 }, (_, i) => i + 1)];
12
+
13
+ const MINUTES = Array.from({ length: 60 }, (_, i) => i);
14
+ const PERIODS: TimePeriod[] = ['AM', 'PM'];
15
+
16
+ export const TimeScroller = ({ open, slotProps: { hour, minute, period }, className }: TimeScrollerProps) => {
17
+ const hourScroll = useScrollToSelected(hour.value, open);
18
+ const minuteScroll = useScrollToSelected(minute.value, open);
19
+
20
+ return (
21
+ <div className={classNames(styles.container, className)}>
22
+ <div ref={hourScroll.columnRef} className={styles.column} role="listbox" aria-label="Hour">
23
+ {HOURS.map((h) => (
24
+ <TimeItem
25
+ key={h}
26
+ ref={h === hour.value ? hourScroll.selectedRef : undefined}
27
+ label={String(h).padStart(2, '0')}
28
+ selected={h === hour.value}
29
+ disabled={hour.isDisabled?.(h)}
30
+ onClick={() => hour.onChange?.(h)}
31
+ />
32
+ ))}
33
+ </div>
34
+
35
+ <div ref={minuteScroll.columnRef} className={styles.column} role="listbox" aria-label="Minute">
36
+ {MINUTES.map((m) => (
37
+ <TimeItem
38
+ key={m}
39
+ ref={m === minute.value ? minuteScroll.selectedRef : undefined}
40
+ label={String(m).padStart(2, '0')}
41
+ selected={m === minute.value}
42
+ disabled={minute.isDisabled?.(m)}
43
+ onClick={() => minute.onChange?.(m)}
44
+ />
45
+ ))}
46
+ </div>
47
+
48
+ <div className={styles.column} role="listbox" aria-label="AM/PM">
49
+ {PERIODS.map((p) => (
50
+ <TimeItem
51
+ key={p}
52
+ label={p}
53
+ selected={p === period.value}
54
+ disabled={period.isDisabled?.(p)}
55
+ onClick={() => period.onChange?.(p)}
56
+ />
57
+ ))}
58
+ </div>
59
+ </div>
60
+ );
61
+ };
@@ -0,0 +1,17 @@
1
+ export type TimePeriod = 'AM' | 'PM';
2
+
3
+ export interface TimeScrollerProps {
4
+ className?: string;
5
+ open: boolean;
6
+ slotProps: {
7
+ hour: TimeScrollerSlotProps<number>;
8
+ minute: TimeScrollerSlotProps<number>;
9
+ period: TimeScrollerSlotProps<TimePeriod>;
10
+ };
11
+ }
12
+
13
+ export interface TimeScrollerSlotProps<TValue> {
14
+ value?: TValue;
15
+ onChange?: (value: TValue) => void;
16
+ isDisabled?: (value: TValue) => boolean;
17
+ }
@@ -0,0 +1,44 @@
1
+ import { useLayoutEffect, useEffect, useRef } from 'react';
2
+
3
+ export const useScrollToSelected = (value: unknown, open: boolean) => {
4
+ const columnRef = useRef<HTMLDivElement | null>(null);
5
+ const selectedRef = useRef<HTMLButtonElement | null>(null);
6
+
7
+ useLayoutEffect(() => {
8
+ const column = columnRef.current;
9
+ const selected = selectedRef.current;
10
+
11
+ if (!column || !selected) {
12
+ return;
13
+ }
14
+
15
+ scrollToCenter(column, selected, 'smooth');
16
+ }, [value]);
17
+
18
+ useEffect(() => {
19
+ if (!open) {
20
+ return;
21
+ }
22
+
23
+ // Ark UI removes `display: none` from Popover.Content after React's commit phase,
24
+ // so layout measurements are only valid after the browser has painted.
25
+ requestAnimationFrame(() => {
26
+ const column = columnRef.current;
27
+ const selected = selectedRef.current;
28
+
29
+ if (!column || !selected) {
30
+ return;
31
+ }
32
+
33
+ scrollToCenter(column, selected, 'instant');
34
+ });
35
+ }, [open]);
36
+
37
+ return { columnRef, selectedRef };
38
+ };
39
+
40
+ const scrollToCenter = (column: HTMLDivElement, selected: HTMLButtonElement, behavior: ScrollBehavior) => {
41
+ const columnCenter = column.offsetHeight / 2;
42
+ const itemCenter = selected.offsetTop + selected.offsetHeight / 2;
43
+ column.scrollTo({ top: itemCenter - columnCenter, behavior });
44
+ };
@@ -0,0 +1,158 @@
1
+ import { type ChangeEvent, Fragment, useEffect, useRef, useState } from 'react';
2
+ import { Popover } from '@ark-ui/react/popover';
3
+ import { Portal } from '@ark-ui/react/portal';
4
+ import type { DsTimePickerProps } from './ds-time-picker.types';
5
+ import { clampTime, formatTime, parseTime, timeScrollerAdapter } from './ds-time-picker.utils';
6
+ import { TimeScroller } from './components/time-scroller/time-scroller';
7
+ import { DsIcon } from '../ds-icon';
8
+ import { DsButton } from '../ds-button';
9
+ import { DsTextInput } from '../ds-text-input';
10
+ import { useControlled } from '../../utils/use-controlled';
11
+
12
+ const DsTimePicker = (props: DsTimePickerProps) => {
13
+ const {
14
+ id,
15
+ ref,
16
+ className,
17
+ placeholder,
18
+ min,
19
+ max,
20
+ disabled,
21
+ readOnly,
22
+ disablePortal = false,
23
+ locale,
24
+ slotProps,
25
+ } = props;
26
+ const [value, setValue] = useControlled(props.value, props.onChange, props.defaultValue ?? null);
27
+ const [isOpen, setIsOpen] = useControlled(props.open, props.onOpenChange, props.defaultOpen ?? false);
28
+ const [isFocused, setIsFocused] = useState(false);
29
+
30
+ const inputRef = useRef<HTMLInputElement>(null);
31
+
32
+ const formattedValue = formatTime(value);
33
+
34
+ // If the time is before the min or after the max, immediately clamp it to the min or max
35
+ if (value) {
36
+ const clamped = clampTime(value, min, max);
37
+
38
+ if (clamped !== value) {
39
+ setValue(clamped);
40
+ }
41
+ }
42
+
43
+ // Reformat on value change, but skip while focused to avoid disrupting typing
44
+ useEffect(() => {
45
+ if (!isFocused && inputRef.current) {
46
+ inputRef.current.value = formattedValue;
47
+ }
48
+ }, [isFocused, formattedValue]);
49
+
50
+ const handleInputChange = (e: ChangeEvent<HTMLInputElement>) => {
51
+ const inputVal = e.target.value;
52
+
53
+ if (!inputVal) {
54
+ setValue(null);
55
+ return;
56
+ }
57
+
58
+ const parsed = parseTime(inputVal);
59
+
60
+ if (parsed) {
61
+ const newDate = value ? new Date(value) : new Date();
62
+ newDate.setHours(parsed.hours, parsed.minutes, 0, 0);
63
+ setValue(newDate);
64
+ }
65
+ };
66
+
67
+ const handleFocus = () => {
68
+ setIsFocused(true);
69
+ };
70
+
71
+ const resetInput = () => {
72
+ if (inputRef.current) {
73
+ inputRef.current.value = value ? formattedValue : '';
74
+ }
75
+ };
76
+
77
+ const handleBlur = () => {
78
+ setIsFocused(false);
79
+
80
+ const inputVal = inputRef.current?.value ?? '';
81
+
82
+ if (!inputVal) {
83
+ setValue(null);
84
+ return;
85
+ }
86
+
87
+ const parsed = parseTime(inputVal);
88
+
89
+ if (parsed) {
90
+ const newDate = value ? new Date(value) : new Date();
91
+ newDate.setHours(parsed.hours, parsed.minutes, 0, 0);
92
+ setValue(newDate);
93
+ } else {
94
+ resetInput();
95
+ }
96
+ };
97
+
98
+ const handleOpenChange = (details: Popover.OpenChangeDetails) => {
99
+ if (disabled || readOnly) {
100
+ return;
101
+ }
102
+
103
+ setIsOpen(details.open);
104
+ };
105
+
106
+ const Wrapper = disablePortal ? Fragment : Portal;
107
+
108
+ return (
109
+ <Popover.Root
110
+ open={isOpen}
111
+ onOpenChange={handleOpenChange}
112
+ positioning={{ placement: 'bottom-start', gutter: 4 }}
113
+ >
114
+ <Popover.Anchor asChild>
115
+ <div ref={ref} className={className}>
116
+ <DsTextInput
117
+ ref={inputRef}
118
+ id={id}
119
+ placeholder={placeholder ?? 'hh:mm AM/PM'}
120
+ defaultValue={formattedValue}
121
+ onChange={handleInputChange}
122
+ onFocus={handleFocus}
123
+ onBlur={handleBlur}
124
+ readOnly={readOnly}
125
+ disabled={disabled}
126
+ {...slotProps?.input}
127
+ slots={{
128
+ endAdornment: slotProps?.input?.slots?.endAdornment ?? (
129
+ <Popover.Trigger asChild>
130
+ <DsButton
131
+ design="v1.2"
132
+ size="tiny"
133
+ buttonType="tertiary"
134
+ disabled={disabled || readOnly}
135
+ aria-label={locale?.openLabel ?? 'Open time picker'}
136
+ >
137
+ <DsIcon icon="schedule" variant="outlined" size="tiny" />
138
+ </DsButton>
139
+ </Popover.Trigger>
140
+ ),
141
+ ...slotProps?.input?.slots,
142
+ }}
143
+ />
144
+ </div>
145
+ </Popover.Anchor>
146
+
147
+ <Wrapper>
148
+ <Popover.Positioner>
149
+ <Popover.Content {...slotProps?.popover}>
150
+ <TimeScroller open={isOpen} {...timeScrollerAdapter(value, setValue, min, max)} />
151
+ </Popover.Content>
152
+ </Popover.Positioner>
153
+ </Wrapper>
154
+ </Popover.Root>
155
+ );
156
+ };
157
+
158
+ export default DsTimePicker;
@@ -0,0 +1,48 @@
1
+ import type { RefObject } from 'react';
2
+ import type { DsTextInputProps } from '../ds-text-input';
3
+ import type { PopoverContentProps } from '@ark-ui/react/popover';
4
+
5
+ export interface DsTimePickerProps {
6
+ id?: string;
7
+ ref?: RefObject<HTMLDivElement>;
8
+ className?: string;
9
+
10
+ value?: Date | null;
11
+ defaultValue?: Date;
12
+
13
+ open?: boolean;
14
+ defaultOpen?: boolean;
15
+
16
+ placeholder?: string;
17
+
18
+ min?: Date;
19
+ max?: Date;
20
+
21
+ disabled?: boolean;
22
+ readOnly?: boolean;
23
+
24
+ /**
25
+ * Whether to disable the portal for the popover content
26
+ * @default false
27
+ */
28
+ disablePortal?: boolean;
29
+
30
+ locale?: DsTimePickerLocale;
31
+ slotProps?: DsTimePickerSlotProps;
32
+
33
+ onChange?: (value: Date | null) => void;
34
+ onOpenChange?: (open: boolean) => void;
35
+ }
36
+
37
+ export interface DsTimePickerLocale {
38
+ openLabel?: string;
39
+ clearLabel?: string;
40
+ hourLabel?: string;
41
+ minuteLabel?: string;
42
+ periodLabel?: string;
43
+ }
44
+
45
+ export interface DsTimePickerSlotProps {
46
+ input?: DsTextInputProps;
47
+ popover?: PopoverContentProps;
48
+ }
@@ -0,0 +1,176 @@
1
+ import type { TimePeriod, TimeScrollerProps } from './components/time-scroller';
2
+
3
+ const AM_END = 11 * 60 + 59;
4
+ const PM_START = 12 * 60;
5
+ const PM_END = 23 * 60 + 59;
6
+
7
+ const to24Hour = (hour12: number, period: TimePeriod): number => {
8
+ if (period === 'AM') {
9
+ return hour12 === 12 ? 0 : hour12;
10
+ }
11
+
12
+ return hour12 === 12 ? 12 : hour12 + 12;
13
+ };
14
+
15
+ const to12Hour = (hour24: number): number => {
16
+ if (hour24 === 0) {
17
+ return 12;
18
+ }
19
+
20
+ return hour24 > 12 ? hour24 - 12 : hour24;
21
+ };
22
+
23
+ const toMinutesSinceMidnight = (date: Date): number => {
24
+ return date.getHours() * 60 + date.getMinutes();
25
+ };
26
+
27
+ /**
28
+ * If the time is before the min or after the max, clamp it to the min or max
29
+ */
30
+ export const clampTime = (date: Date, min?: Date, max?: Date): Date => {
31
+ const time = toMinutesSinceMidnight(date);
32
+
33
+ if (min && time < toMinutesSinceMidnight(min)) {
34
+ const clamped = new Date(date);
35
+ clamped.setHours(min.getHours(), min.getMinutes(), 0, 0);
36
+ return clamped;
37
+ }
38
+
39
+ if (max && time > toMinutesSinceMidnight(max)) {
40
+ const clamped = new Date(date);
41
+ clamped.setHours(max.getHours(), max.getMinutes(), 0, 0);
42
+ return clamped;
43
+ }
44
+
45
+ return date;
46
+ };
47
+
48
+ const getTimeRange = (min?: Date, max?: Date) => ({
49
+ minTime: min ? toMinutesSinceMidnight(min) : 0,
50
+ maxTime: max ? toMinutesSinceMidnight(max) : PM_END,
51
+ });
52
+
53
+ const validateMinMax = (hour?: number, period?: TimePeriod, min?: Date, max?: Date) => {
54
+ if (!min && !max) {
55
+ return {};
56
+ }
57
+
58
+ const { minTime, maxTime } = getTimeRange(min, max);
59
+
60
+ return {
61
+ isPeriodDisabled: (p: TimePeriod) => {
62
+ if (p === 'AM') {
63
+ return AM_END < minTime;
64
+ }
65
+
66
+ return PM_END < minTime || PM_START > maxTime;
67
+ },
68
+
69
+ isHourDisabled: (h: number) => {
70
+ if (!period) {
71
+ return false;
72
+ }
73
+
74
+ const h24 = to24Hour(h, period);
75
+ return h24 * 60 + 59 < minTime || h24 * 60 > maxTime;
76
+ },
77
+
78
+ isMinuteDisabled: (m: number) => {
79
+ if (hour === undefined || !period) {
80
+ return false;
81
+ }
82
+
83
+ const time = to24Hour(hour, period) * 60 + m;
84
+ return time < minTime || time > maxTime;
85
+ },
86
+ };
87
+ };
88
+
89
+ /**
90
+ * Format a Date to a 12-hour time string (hh:mm AM/PM)
91
+ */
92
+ export const formatTime = (date?: Date | null): string => {
93
+ if (!date) {
94
+ return '';
95
+ }
96
+
97
+ const hour24 = date.getHours();
98
+ const minute = date.getMinutes();
99
+ const period = hour24 >= 12 ? 'PM' : 'AM';
100
+ const paddedHour = String(to12Hour(hour24)).padStart(2, '0');
101
+ const paddedMinute = String(minute).padStart(2, '0');
102
+
103
+ return `${paddedHour}:${paddedMinute} ${period}`;
104
+ };
105
+
106
+ /**
107
+ * Parse a 12-hour time string (hh:mm AM/PM) to an object with hours and minutes
108
+ */
109
+ export const parseTime = (text: string) => {
110
+ const match = text.match(/^(\d{1,2}):(\d{2})\s+(AM|PM)$/i);
111
+
112
+ if (!match?.[1] || !match[2] || !match[3]) {
113
+ return null;
114
+ }
115
+
116
+ const h12 = parseInt(match[1], 10);
117
+ const minutes = parseInt(match[2], 10);
118
+ const period = match[3].toUpperCase();
119
+
120
+ if (h12 < 1 || h12 > 12 || minutes < 0 || minutes > 59) {
121
+ return null;
122
+ }
123
+
124
+ const hours = period === 'AM' ? (h12 === 12 ? 0 : h12) : h12 === 12 ? 12 : h12 + 12;
125
+
126
+ return { hours, minutes };
127
+ };
128
+
129
+ export const timeScrollerAdapter = (
130
+ date?: Date | null,
131
+ setDate?: (date: Date | null) => void,
132
+ min?: Date,
133
+ max?: Date,
134
+ ) => {
135
+ const hour = date ? to12Hour(date.getHours()) : undefined;
136
+ const minute = date?.getMinutes();
137
+ const period = date ? (date.getHours() >= 12 ? 'PM' : 'AM') : undefined;
138
+
139
+ const clampAndSet = (newDate: Date) => {
140
+ setDate?.(clampTime(newDate, min, max));
141
+ };
142
+
143
+ const { isHourDisabled, isMinuteDisabled, isPeriodDisabled } = validateMinMax(hour, period, min, max);
144
+
145
+ return {
146
+ slotProps: {
147
+ hour: {
148
+ value: hour,
149
+ onChange: (h: number) => {
150
+ const newDate = date ? new Date(date) : new Date();
151
+ newDate.setHours(to24Hour(h, period ?? 'AM'), minute ?? 0, 0, 0);
152
+ clampAndSet(newDate);
153
+ },
154
+ isDisabled: isHourDisabled,
155
+ },
156
+ minute: {
157
+ value: minute,
158
+ onChange: (m: number) => {
159
+ const newDate = date ? new Date(date) : new Date();
160
+ newDate.setHours(to24Hour(hour ?? 12, period ?? 'AM'), m, 0, 0);
161
+ clampAndSet(newDate);
162
+ },
163
+ isDisabled: isMinuteDisabled,
164
+ },
165
+ period: {
166
+ value: period,
167
+ onChange: (p: TimePeriod) => {
168
+ const newDate = date ? new Date(date) : new Date();
169
+ newDate.setHours(to24Hour(hour ?? 12, p), minute ?? 0, 0, 0);
170
+ clampAndSet(newDate);
171
+ },
172
+ isDisabled: isPeriodDisabled,
173
+ },
174
+ },
175
+ } satisfies Pick<TimeScrollerProps, 'slotProps'>;
176
+ };