@refraktor/dates 0.0.3 → 0.0.5
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.
- package/build/components/date-range-picker/date-range-picker.d.ts +4 -0
- package/build/components/date-range-picker/date-range-picker.d.ts.map +1 -0
- package/build/components/date-range-picker/date-range-picker.js +379 -0
- package/build/components/date-range-picker/date-range-picker.types.d.ts +100 -0
- package/build/components/date-range-picker/date-range-picker.types.d.ts.map +1 -0
- package/build/components/date-range-picker/date-range-picker.types.js +1 -0
- package/build/components/date-range-picker/index.d.ts +3 -0
- package/build/components/date-range-picker/index.d.ts.map +1 -0
- package/build/components/date-range-picker/index.js +1 -0
- package/build/components/time-input/index.d.ts +1 -1
- package/build/components/time-input/index.d.ts.map +1 -1
- package/build/components/time-input/time-input.d.ts.map +1 -1
- package/build/components/time-input/time-input.js +7 -196
- package/build/components/time-input/time-input.types.d.ts +5 -83
- package/build/components/time-input/time-input.types.d.ts.map +1 -1
- package/build/components/time-picker/index.d.ts +1 -1
- package/build/components/time-picker/index.d.ts.map +1 -1
- package/build/components/time-picker/time-picker.d.ts.map +1 -1
- package/build/components/time-picker/time-picker.js +498 -350
- package/build/components/time-picker/time-picker.types.d.ts +96 -61
- package/build/components/time-picker/time-picker.types.d.ts.map +1 -1
- package/build/style.css +2 -2
- package/package.json +33 -4
- package/.turbo/turbo-build.log +0 -4
- package/refraktor-dates-0.0.1-alpha.0.tgz +0 -0
- package/src/components/date-input/date-input.tsx +0 -379
- package/src/components/date-input/date-input.types.ts +0 -161
- package/src/components/date-input/index.ts +0 -13
- package/src/components/date-picker/date-picker.tsx +0 -649
- package/src/components/date-picker/date-picker.types.ts +0 -145
- package/src/components/date-picker/index.ts +0 -15
- package/src/components/dates-provider/context.ts +0 -18
- package/src/components/dates-provider/dates-provider.tsx +0 -136
- package/src/components/dates-provider/index.ts +0 -10
- package/src/components/dates-provider/types.ts +0 -33
- package/src/components/dates-provider/use-dates.ts +0 -5
- package/src/components/index.ts +0 -9
- package/src/components/month-input/index.ts +0 -13
- package/src/components/month-input/month-input.tsx +0 -366
- package/src/components/month-input/month-input.types.ts +0 -139
- package/src/components/month-picker/index.ts +0 -14
- package/src/components/month-picker/month-picker.tsx +0 -458
- package/src/components/month-picker/month-picker.types.ts +0 -117
- package/src/components/picker-shared/index.ts +0 -7
- package/src/components/picker-shared/picker-header.tsx +0 -178
- package/src/components/picker-shared/picker-header.types.ts +0 -49
- package/src/components/picker-shared/picker.styles.ts +0 -69
- package/src/components/picker-shared/picker.types.ts +0 -4
- package/src/components/time-input/index.ts +0 -23
- package/src/components/time-input/time-input.tsx +0 -453
- package/src/components/time-input/time-input.types.ts +0 -163
- package/src/components/time-picker/index.ts +0 -19
- package/src/components/time-picker/time-picker.tsx +0 -737
- package/src/components/time-picker/time-picker.types.ts +0 -135
- package/src/components/year-input/index.ts +0 -13
- package/src/components/year-input/year-input.tsx +0 -350
- package/src/components/year-input/year-input.types.ts +0 -118
- package/src/components/year-picker/index.ts +0 -15
- package/src/components/year-picker/year-picker.tsx +0 -504
- package/src/components/year-picker/year-picker.types.ts +0 -108
- package/src/index.ts +0 -3
- package/src/style.css +0 -1
- package/tsconfig.json +0 -13
|
@@ -1,737 +0,0 @@
|
|
|
1
|
-
import { useId, useUncontrolled } from "@refraktor/utils";
|
|
2
|
-
import { KeyboardEvent, ReactNode, useMemo } from "react";
|
|
3
|
-
import {
|
|
4
|
-
createClassNamesConfig,
|
|
5
|
-
createComponentConfig,
|
|
6
|
-
factory,
|
|
7
|
-
useClassNames,
|
|
8
|
-
useProps,
|
|
9
|
-
useTheme
|
|
10
|
-
} from "@refraktor/core";
|
|
11
|
-
import { getGridColumns, getPickerSizeStyles } from "../picker-shared";
|
|
12
|
-
import {
|
|
13
|
-
TimePickerClassNames,
|
|
14
|
-
TimePickerFactoryPayload,
|
|
15
|
-
TimePickerMode,
|
|
16
|
-
TimePickerPeriod,
|
|
17
|
-
TimePickerProps,
|
|
18
|
-
TimePickerValue
|
|
19
|
-
} from "./time-picker.types";
|
|
20
|
-
|
|
21
|
-
const HOURS_IN_DAY = 24;
|
|
22
|
-
const MINUTES_IN_HOUR = 60;
|
|
23
|
-
const SECONDS_IN_MINUTE = 60;
|
|
24
|
-
const SECONDS_IN_DAY = HOURS_IN_DAY * MINUTES_IN_HOUR * SECONDS_IN_MINUTE;
|
|
25
|
-
const DEFAULT_MODE: TimePickerMode = "24h";
|
|
26
|
-
const TIME_SEGMENT_PATTERN = /^\d{1,2}$/;
|
|
27
|
-
|
|
28
|
-
const defaultProps = {
|
|
29
|
-
mode: DEFAULT_MODE,
|
|
30
|
-
disabled: false,
|
|
31
|
-
size: "md",
|
|
32
|
-
radius: "default"
|
|
33
|
-
} satisfies Partial<TimePickerProps>;
|
|
34
|
-
|
|
35
|
-
type TimeBounds = {
|
|
36
|
-
minSeconds: number;
|
|
37
|
-
maxSeconds: number;
|
|
38
|
-
hasMin: boolean;
|
|
39
|
-
hasMax: boolean;
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
type TimeParts = {
|
|
43
|
-
hours24: number;
|
|
44
|
-
hour12: number;
|
|
45
|
-
minutes: number;
|
|
46
|
-
seconds: number;
|
|
47
|
-
period: TimePickerPeriod;
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
type PickerOptionValue = number | TimePickerPeriod;
|
|
51
|
-
|
|
52
|
-
type PickerOption<TValue extends PickerOptionValue = PickerOptionValue> = {
|
|
53
|
-
value: TValue;
|
|
54
|
-
label: ReactNode;
|
|
55
|
-
ariaLabel: string;
|
|
56
|
-
selected: boolean;
|
|
57
|
-
disabled: boolean;
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
const pad = (value: number) => String(value).padStart(2, "0");
|
|
61
|
-
|
|
62
|
-
const to12Hour = (hours24: number) => {
|
|
63
|
-
const normalized = hours24 % 12;
|
|
64
|
-
return normalized === 0 ? 12 : normalized;
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
const to24Hour = (hour12: number, period: TimePickerPeriod) => {
|
|
68
|
-
const normalizedHour = hour12 % 12;
|
|
69
|
-
return period === "pm" ? normalizedHour + 12 : normalizedHour;
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
const toSecondsOfDay = (hours24: number, minutes: number, seconds: number) =>
|
|
73
|
-
hours24 * MINUTES_IN_HOUR * SECONDS_IN_MINUTE + minutes * SECONDS_IN_MINUTE + seconds;
|
|
74
|
-
|
|
75
|
-
const createTimeParts = (hours24: number, minutes: number, seconds: number): TimeParts => {
|
|
76
|
-
const normalizedHours24 = hours24 % HOURS_IN_DAY;
|
|
77
|
-
|
|
78
|
-
return {
|
|
79
|
-
hours24: normalizedHours24,
|
|
80
|
-
hour12: to12Hour(normalizedHours24),
|
|
81
|
-
minutes,
|
|
82
|
-
seconds,
|
|
83
|
-
period: normalizedHours24 >= 12 ? "pm" : "am"
|
|
84
|
-
};
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
const parseTimeValue = (value: unknown): TimeParts | undefined => {
|
|
88
|
-
if (typeof value !== "string") {
|
|
89
|
-
return undefined;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const segments = value.trim().split(":");
|
|
93
|
-
|
|
94
|
-
if (
|
|
95
|
-
segments.length !== 3 ||
|
|
96
|
-
!segments.every((segment) => TIME_SEGMENT_PATTERN.test(segment))
|
|
97
|
-
) {
|
|
98
|
-
return undefined;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const [hoursText, minutesText, secondsText] = segments;
|
|
102
|
-
const hours24 = Number.parseInt(hoursText, 10);
|
|
103
|
-
const minutes = Number.parseInt(minutesText, 10);
|
|
104
|
-
const seconds = Number.parseInt(secondsText, 10);
|
|
105
|
-
|
|
106
|
-
if (
|
|
107
|
-
hours24 < 0 ||
|
|
108
|
-
hours24 >= HOURS_IN_DAY ||
|
|
109
|
-
minutes < 0 ||
|
|
110
|
-
minutes >= MINUTES_IN_HOUR ||
|
|
111
|
-
seconds < 0 ||
|
|
112
|
-
seconds >= SECONDS_IN_MINUTE
|
|
113
|
-
) {
|
|
114
|
-
return undefined;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
return createTimeParts(hours24, minutes, seconds);
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
const formatTimeValue = (hours24: number, minutes: number, seconds: number) =>
|
|
121
|
-
`${pad(hours24)}:${pad(minutes)}:${pad(seconds)}`;
|
|
122
|
-
|
|
123
|
-
const toTimePartsFromSeconds = (seconds: number) => {
|
|
124
|
-
const normalizedSeconds =
|
|
125
|
-
((seconds % SECONDS_IN_DAY) + SECONDS_IN_DAY) % SECONDS_IN_DAY;
|
|
126
|
-
const hours24 = Math.floor(
|
|
127
|
-
normalizedSeconds / (MINUTES_IN_HOUR * SECONDS_IN_MINUTE)
|
|
128
|
-
);
|
|
129
|
-
const minutes = Math.floor(
|
|
130
|
-
(normalizedSeconds % (MINUTES_IN_HOUR * SECONDS_IN_MINUTE)) /
|
|
131
|
-
SECONDS_IN_MINUTE
|
|
132
|
-
);
|
|
133
|
-
const remainingSeconds = normalizedSeconds % SECONDS_IN_MINUTE;
|
|
134
|
-
|
|
135
|
-
return createTimeParts(hours24, minutes, remainingSeconds);
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
const clampTimeParts = (parts: TimeParts, bounds: TimeBounds) => {
|
|
139
|
-
const seconds = toSecondsOfDay(parts.hours24, parts.minutes, parts.seconds);
|
|
140
|
-
|
|
141
|
-
if (bounds.hasMin && seconds < bounds.minSeconds) {
|
|
142
|
-
return toTimePartsFromSeconds(bounds.minSeconds);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
if (bounds.hasMax && seconds > bounds.maxSeconds) {
|
|
146
|
-
return toTimePartsFromSeconds(bounds.maxSeconds);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
return parts;
|
|
150
|
-
};
|
|
151
|
-
|
|
152
|
-
const getCurrentTimeParts = () => {
|
|
153
|
-
const current = new Date();
|
|
154
|
-
return createTimeParts(
|
|
155
|
-
current.getHours(),
|
|
156
|
-
current.getMinutes(),
|
|
157
|
-
current.getSeconds()
|
|
158
|
-
);
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
const getTimeBounds = (
|
|
162
|
-
minTime?: TimePickerValue,
|
|
163
|
-
maxTime?: TimePickerValue
|
|
164
|
-
): TimeBounds => {
|
|
165
|
-
const minParts = parseTimeValue(minTime);
|
|
166
|
-
const maxParts = parseTimeValue(maxTime);
|
|
167
|
-
const hasMin = minParts !== undefined;
|
|
168
|
-
const hasMax = maxParts !== undefined;
|
|
169
|
-
|
|
170
|
-
const minSeconds = hasMin
|
|
171
|
-
? toSecondsOfDay(minParts.hours24, minParts.minutes, minParts.seconds)
|
|
172
|
-
: Number.NEGATIVE_INFINITY;
|
|
173
|
-
const maxSeconds = hasMax
|
|
174
|
-
? toSecondsOfDay(maxParts.hours24, maxParts.minutes, maxParts.seconds)
|
|
175
|
-
: Number.POSITIVE_INFINITY;
|
|
176
|
-
|
|
177
|
-
if (hasMin && hasMax && minSeconds > maxSeconds) {
|
|
178
|
-
return {
|
|
179
|
-
minSeconds: maxSeconds,
|
|
180
|
-
maxSeconds: minSeconds,
|
|
181
|
-
hasMin: true,
|
|
182
|
-
hasMax: true
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
return {
|
|
187
|
-
minSeconds,
|
|
188
|
-
maxSeconds,
|
|
189
|
-
hasMin,
|
|
190
|
-
hasMax
|
|
191
|
-
};
|
|
192
|
-
};
|
|
193
|
-
|
|
194
|
-
const isTimeDisabled = (seconds: number, disabled: boolean, bounds: TimeBounds) =>
|
|
195
|
-
disabled ||
|
|
196
|
-
(bounds.hasMin && seconds < bounds.minSeconds) ||
|
|
197
|
-
(bounds.hasMax && seconds > bounds.maxSeconds);
|
|
198
|
-
|
|
199
|
-
const getSelectablePeriodRange = (
|
|
200
|
-
period: TimePickerPeriod,
|
|
201
|
-
bounds: TimeBounds
|
|
202
|
-
) => {
|
|
203
|
-
const periodStart = period === "am" ? 0 : SECONDS_IN_DAY / 2;
|
|
204
|
-
const periodEnd = period === "am" ? SECONDS_IN_DAY / 2 - 1 : SECONDS_IN_DAY - 1;
|
|
205
|
-
|
|
206
|
-
const start = Math.max(periodStart, bounds.minSeconds);
|
|
207
|
-
const end = Math.min(periodEnd, bounds.maxSeconds);
|
|
208
|
-
|
|
209
|
-
if (start > end) {
|
|
210
|
-
return undefined;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
return { start, end };
|
|
214
|
-
};
|
|
215
|
-
|
|
216
|
-
const getFirstEnabledIndex = <TValue extends PickerOptionValue>(
|
|
217
|
-
options: PickerOption<TValue>[]
|
|
218
|
-
) => options.findIndex((option) => !option.disabled);
|
|
219
|
-
|
|
220
|
-
const getLastEnabledIndex = <TValue extends PickerOptionValue>(
|
|
221
|
-
options: PickerOption<TValue>[]
|
|
222
|
-
) => {
|
|
223
|
-
for (let index = options.length - 1; index >= 0; index -= 1) {
|
|
224
|
-
if (!options[index].disabled) {
|
|
225
|
-
return index;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
return -1;
|
|
230
|
-
};
|
|
231
|
-
|
|
232
|
-
const findNextEnabledIndex = <TValue extends PickerOptionValue>(
|
|
233
|
-
options: PickerOption<TValue>[],
|
|
234
|
-
startIndex: number,
|
|
235
|
-
direction: 1 | -1
|
|
236
|
-
) => {
|
|
237
|
-
let index = startIndex + direction;
|
|
238
|
-
|
|
239
|
-
while (index >= 0 && index < options.length) {
|
|
240
|
-
if (!options[index].disabled) {
|
|
241
|
-
return index;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
index += direction;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
return startIndex;
|
|
248
|
-
};
|
|
249
|
-
|
|
250
|
-
const handleListKeyDown = <TValue extends PickerOptionValue>(
|
|
251
|
-
event: KeyboardEvent<HTMLDivElement>,
|
|
252
|
-
options: PickerOption<TValue>[],
|
|
253
|
-
onSelect: (value: TValue) => void
|
|
254
|
-
) => {
|
|
255
|
-
const firstEnabledIndex = getFirstEnabledIndex(options);
|
|
256
|
-
const lastEnabledIndex = getLastEnabledIndex(options);
|
|
257
|
-
|
|
258
|
-
if (firstEnabledIndex === -1 || lastEnabledIndex === -1) {
|
|
259
|
-
return;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
const selectedIndex = options.findIndex((option) => option.selected);
|
|
263
|
-
|
|
264
|
-
if (event.key === "Home") {
|
|
265
|
-
event.preventDefault();
|
|
266
|
-
onSelect(options[firstEnabledIndex].value);
|
|
267
|
-
return;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
if (event.key === "End") {
|
|
271
|
-
event.preventDefault();
|
|
272
|
-
onSelect(options[lastEnabledIndex].value);
|
|
273
|
-
return;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
const applyStep = (direction: 1 | -1, repeat = 1) => {
|
|
277
|
-
event.preventDefault();
|
|
278
|
-
|
|
279
|
-
let index =
|
|
280
|
-
selectedIndex === -1
|
|
281
|
-
? direction === 1
|
|
282
|
-
? firstEnabledIndex
|
|
283
|
-
: lastEnabledIndex
|
|
284
|
-
: selectedIndex;
|
|
285
|
-
|
|
286
|
-
for (let step = 0; step < repeat; step += 1) {
|
|
287
|
-
const nextIndex = findNextEnabledIndex(options, index, direction);
|
|
288
|
-
|
|
289
|
-
if (nextIndex === index) {
|
|
290
|
-
break;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
index = nextIndex;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
onSelect(options[index].value);
|
|
297
|
-
};
|
|
298
|
-
|
|
299
|
-
if (event.key === "ArrowDown") {
|
|
300
|
-
applyStep(1);
|
|
301
|
-
return;
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
if (event.key === "ArrowUp") {
|
|
305
|
-
applyStep(-1);
|
|
306
|
-
return;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
if (event.key === "PageDown") {
|
|
310
|
-
applyStep(1, 5);
|
|
311
|
-
return;
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
if (event.key === "PageUp") {
|
|
315
|
-
applyStep(-1, 5);
|
|
316
|
-
}
|
|
317
|
-
};
|
|
318
|
-
|
|
319
|
-
const TimePicker = factory<TimePickerFactoryPayload>((_props, ref) => {
|
|
320
|
-
const { cx, getRadius } = useTheme();
|
|
321
|
-
const {
|
|
322
|
-
id,
|
|
323
|
-
value,
|
|
324
|
-
defaultValue,
|
|
325
|
-
onChange,
|
|
326
|
-
minTime,
|
|
327
|
-
maxTime,
|
|
328
|
-
mode,
|
|
329
|
-
disabled,
|
|
330
|
-
size,
|
|
331
|
-
radius,
|
|
332
|
-
getHourLabel,
|
|
333
|
-
getMinuteLabel,
|
|
334
|
-
getSecondLabel,
|
|
335
|
-
getPeriodLabel,
|
|
336
|
-
getHourAriaLabel,
|
|
337
|
-
getMinuteAriaLabel,
|
|
338
|
-
getSecondAriaLabel,
|
|
339
|
-
getPeriodAriaLabel,
|
|
340
|
-
className,
|
|
341
|
-
classNames,
|
|
342
|
-
...props
|
|
343
|
-
} = useProps("TimePicker", defaultProps, _props);
|
|
344
|
-
const classes = useClassNames("TimePicker", classNames);
|
|
345
|
-
|
|
346
|
-
const _id = useId(id);
|
|
347
|
-
const sizeStyles = getPickerSizeStyles(size);
|
|
348
|
-
|
|
349
|
-
const bounds = useMemo(() => getTimeBounds(minTime, maxTime), [minTime, maxTime]);
|
|
350
|
-
|
|
351
|
-
const [selectedTimeState, setSelectedTime] = useUncontrolled<
|
|
352
|
-
TimePickerValue | undefined
|
|
353
|
-
>({
|
|
354
|
-
value,
|
|
355
|
-
defaultValue,
|
|
356
|
-
finalValue: undefined,
|
|
357
|
-
onChange: (nextTime) => {
|
|
358
|
-
if (nextTime !== undefined) {
|
|
359
|
-
onChange?.(nextTime);
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
});
|
|
363
|
-
|
|
364
|
-
const selectedParts = useMemo(() => {
|
|
365
|
-
const parsed = parseTimeValue(selectedTimeState);
|
|
366
|
-
|
|
367
|
-
if (!parsed) {
|
|
368
|
-
return undefined;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
return clampTimeParts(parsed, bounds);
|
|
372
|
-
}, [bounds, selectedTimeState]);
|
|
373
|
-
const selectedTime = useMemo(
|
|
374
|
-
() =>
|
|
375
|
-
selectedParts
|
|
376
|
-
? formatTimeValue(
|
|
377
|
-
selectedParts.hours24,
|
|
378
|
-
selectedParts.minutes,
|
|
379
|
-
selectedParts.seconds
|
|
380
|
-
)
|
|
381
|
-
: undefined,
|
|
382
|
-
[selectedParts]
|
|
383
|
-
);
|
|
384
|
-
const fallbackParts = useMemo(
|
|
385
|
-
() => clampTimeParts(getCurrentTimeParts(), bounds),
|
|
386
|
-
[bounds]
|
|
387
|
-
);
|
|
388
|
-
const activeParts = selectedParts ?? fallbackParts;
|
|
389
|
-
|
|
390
|
-
const applyTime = (hours24: number, minutes: number, seconds: number) => {
|
|
391
|
-
if (disabled) {
|
|
392
|
-
return;
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
const nextSeconds = toSecondsOfDay(hours24, minutes, seconds);
|
|
396
|
-
|
|
397
|
-
if (isTimeDisabled(nextSeconds, false, bounds)) {
|
|
398
|
-
return;
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
const nextTime = formatTimeValue(hours24, minutes, seconds);
|
|
402
|
-
|
|
403
|
-
if (selectedTime === nextTime) {
|
|
404
|
-
return;
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
setSelectedTime(nextTime);
|
|
408
|
-
};
|
|
409
|
-
|
|
410
|
-
const setHour = (hour: number) => {
|
|
411
|
-
const nextHour = mode === "12h" ? to24Hour(hour, activeParts.period) : hour;
|
|
412
|
-
applyTime(nextHour, activeParts.minutes, activeParts.seconds);
|
|
413
|
-
};
|
|
414
|
-
|
|
415
|
-
const setMinute = (minute: number) => {
|
|
416
|
-
applyTime(activeParts.hours24, minute, activeParts.seconds);
|
|
417
|
-
};
|
|
418
|
-
|
|
419
|
-
const setSecond = (second: number) => {
|
|
420
|
-
applyTime(activeParts.hours24, activeParts.minutes, second);
|
|
421
|
-
};
|
|
422
|
-
|
|
423
|
-
const setPeriod = (period: TimePickerPeriod) => {
|
|
424
|
-
const selectableRange = getSelectablePeriodRange(period, bounds);
|
|
425
|
-
|
|
426
|
-
if (!selectableRange) {
|
|
427
|
-
return;
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
const nextHour24 = to24Hour(activeParts.hour12, period);
|
|
431
|
-
const candidateSeconds = toSecondsOfDay(
|
|
432
|
-
nextHour24,
|
|
433
|
-
activeParts.minutes,
|
|
434
|
-
activeParts.seconds
|
|
435
|
-
);
|
|
436
|
-
const nextSeconds = Math.min(
|
|
437
|
-
selectableRange.end,
|
|
438
|
-
Math.max(selectableRange.start, candidateSeconds)
|
|
439
|
-
);
|
|
440
|
-
const nextParts = toTimePartsFromSeconds(nextSeconds);
|
|
441
|
-
|
|
442
|
-
applyTime(nextParts.hours24, nextParts.minutes, nextParts.seconds);
|
|
443
|
-
};
|
|
444
|
-
|
|
445
|
-
const hourOptions = useMemo<PickerOption<number>[]>(
|
|
446
|
-
() => {
|
|
447
|
-
const totalHours = mode === "12h" ? 12 : HOURS_IN_DAY;
|
|
448
|
-
|
|
449
|
-
return Array.from({ length: totalHours }, (_, index) => {
|
|
450
|
-
const hour = mode === "12h" ? index + 1 : index;
|
|
451
|
-
const hours24 =
|
|
452
|
-
mode === "12h" ? to24Hour(hour, activeParts.period) : hour;
|
|
453
|
-
const nextSeconds = toSecondsOfDay(
|
|
454
|
-
hours24,
|
|
455
|
-
activeParts.minutes,
|
|
456
|
-
activeParts.seconds
|
|
457
|
-
);
|
|
458
|
-
const selected =
|
|
459
|
-
selectedParts !== undefined &&
|
|
460
|
-
(mode === "12h"
|
|
461
|
-
? selectedParts.hour12 === hour
|
|
462
|
-
: selectedParts.hours24 === hour);
|
|
463
|
-
|
|
464
|
-
return {
|
|
465
|
-
value: hour,
|
|
466
|
-
label: getHourLabel ? getHourLabel(hour, mode) : pad(hour),
|
|
467
|
-
ariaLabel: getHourAriaLabel
|
|
468
|
-
? getHourAriaLabel(hour, mode, selected)
|
|
469
|
-
: selected
|
|
470
|
-
? `Hour ${pad(hour)}, selected`
|
|
471
|
-
: `Choose hour ${pad(hour)}`,
|
|
472
|
-
selected,
|
|
473
|
-
disabled: isTimeDisabled(nextSeconds, disabled ?? false, bounds)
|
|
474
|
-
};
|
|
475
|
-
});
|
|
476
|
-
},
|
|
477
|
-
[
|
|
478
|
-
activeParts.minutes,
|
|
479
|
-
activeParts.period,
|
|
480
|
-
activeParts.seconds,
|
|
481
|
-
bounds,
|
|
482
|
-
disabled,
|
|
483
|
-
getHourAriaLabel,
|
|
484
|
-
getHourLabel,
|
|
485
|
-
mode,
|
|
486
|
-
selectedParts
|
|
487
|
-
]
|
|
488
|
-
);
|
|
489
|
-
|
|
490
|
-
const minuteOptions = useMemo<PickerOption<number>[]>(
|
|
491
|
-
() =>
|
|
492
|
-
Array.from({ length: MINUTES_IN_HOUR }, (_, minute) => {
|
|
493
|
-
const nextSeconds = toSecondsOfDay(
|
|
494
|
-
activeParts.hours24,
|
|
495
|
-
minute,
|
|
496
|
-
activeParts.seconds
|
|
497
|
-
);
|
|
498
|
-
const selected = selectedParts?.minutes === minute;
|
|
499
|
-
|
|
500
|
-
return {
|
|
501
|
-
value: minute,
|
|
502
|
-
label: getMinuteLabel ? getMinuteLabel(minute) : pad(minute),
|
|
503
|
-
ariaLabel: getMinuteAriaLabel
|
|
504
|
-
? getMinuteAriaLabel(minute, selected)
|
|
505
|
-
: selected
|
|
506
|
-
? `Minute ${pad(minute)}, selected`
|
|
507
|
-
: `Choose minute ${pad(minute)}`,
|
|
508
|
-
selected,
|
|
509
|
-
disabled: isTimeDisabled(nextSeconds, disabled ?? false, bounds)
|
|
510
|
-
};
|
|
511
|
-
}),
|
|
512
|
-
[
|
|
513
|
-
activeParts.hours24,
|
|
514
|
-
activeParts.seconds,
|
|
515
|
-
bounds,
|
|
516
|
-
disabled,
|
|
517
|
-
getMinuteAriaLabel,
|
|
518
|
-
getMinuteLabel,
|
|
519
|
-
selectedParts
|
|
520
|
-
]
|
|
521
|
-
);
|
|
522
|
-
|
|
523
|
-
const secondOptions = useMemo<PickerOption<number>[]>(
|
|
524
|
-
() =>
|
|
525
|
-
Array.from({ length: SECONDS_IN_MINUTE }, (_, second) => {
|
|
526
|
-
const nextSeconds = toSecondsOfDay(
|
|
527
|
-
activeParts.hours24,
|
|
528
|
-
activeParts.minutes,
|
|
529
|
-
second
|
|
530
|
-
);
|
|
531
|
-
const selected = selectedParts?.seconds === second;
|
|
532
|
-
|
|
533
|
-
return {
|
|
534
|
-
value: second,
|
|
535
|
-
label: getSecondLabel ? getSecondLabel(second) : pad(second),
|
|
536
|
-
ariaLabel: getSecondAriaLabel
|
|
537
|
-
? getSecondAriaLabel(second, selected)
|
|
538
|
-
: selected
|
|
539
|
-
? `Second ${pad(second)}, selected`
|
|
540
|
-
: `Choose second ${pad(second)}`,
|
|
541
|
-
selected,
|
|
542
|
-
disabled: isTimeDisabled(nextSeconds, disabled ?? false, bounds)
|
|
543
|
-
};
|
|
544
|
-
}),
|
|
545
|
-
[
|
|
546
|
-
activeParts.hours24,
|
|
547
|
-
activeParts.minutes,
|
|
548
|
-
bounds,
|
|
549
|
-
disabled,
|
|
550
|
-
getSecondAriaLabel,
|
|
551
|
-
getSecondLabel,
|
|
552
|
-
selectedParts
|
|
553
|
-
]
|
|
554
|
-
);
|
|
555
|
-
|
|
556
|
-
const periodOptions = useMemo<PickerOption<TimePickerPeriod>[]>(
|
|
557
|
-
() =>
|
|
558
|
-
(["am", "pm"] as const).map((period) => {
|
|
559
|
-
const selected = selectedParts?.period === period;
|
|
560
|
-
|
|
561
|
-
return {
|
|
562
|
-
value: period,
|
|
563
|
-
label: getPeriodLabel
|
|
564
|
-
? getPeriodLabel(period)
|
|
565
|
-
: period.toUpperCase(),
|
|
566
|
-
ariaLabel: getPeriodAriaLabel
|
|
567
|
-
? getPeriodAriaLabel(period, selected)
|
|
568
|
-
: selected
|
|
569
|
-
? `${period.toUpperCase()}, selected`
|
|
570
|
-
: `Choose ${period.toUpperCase()}`,
|
|
571
|
-
selected,
|
|
572
|
-
disabled:
|
|
573
|
-
(disabled ?? false) ||
|
|
574
|
-
!getSelectablePeriodRange(period, bounds)
|
|
575
|
-
};
|
|
576
|
-
}),
|
|
577
|
-
[
|
|
578
|
-
bounds,
|
|
579
|
-
disabled,
|
|
580
|
-
getPeriodAriaLabel,
|
|
581
|
-
getPeriodLabel,
|
|
582
|
-
selectedParts
|
|
583
|
-
]
|
|
584
|
-
);
|
|
585
|
-
|
|
586
|
-
const renderSection = <TValue extends PickerOptionValue>({
|
|
587
|
-
label,
|
|
588
|
-
labelId,
|
|
589
|
-
listLabel,
|
|
590
|
-
options,
|
|
591
|
-
onSelect,
|
|
592
|
-
className
|
|
593
|
-
}: {
|
|
594
|
-
label: ReactNode;
|
|
595
|
-
labelId: string;
|
|
596
|
-
listLabel: string;
|
|
597
|
-
options: PickerOption<TValue>[];
|
|
598
|
-
onSelect: (value: TValue) => void;
|
|
599
|
-
className?: string;
|
|
600
|
-
}) => {
|
|
601
|
-
const hasVisibleSelection = options.some((option) => option.selected);
|
|
602
|
-
const firstEnabledIndex = getFirstEnabledIndex(options);
|
|
603
|
-
|
|
604
|
-
return (
|
|
605
|
-
<div
|
|
606
|
-
role="group"
|
|
607
|
-
aria-labelledby={labelId}
|
|
608
|
-
className={cx("flex flex-col min-w-0", classes.section, className)}
|
|
609
|
-
>
|
|
610
|
-
<div
|
|
611
|
-
id={labelId}
|
|
612
|
-
className={cx(
|
|
613
|
-
"py-2 text-center font-medium text-[var(--refraktor-text-secondary)] border-b border-[var(--refraktor-border)] bg-[var(--refraktor-bg-subtle)]",
|
|
614
|
-
sizeStyles.label,
|
|
615
|
-
classes.sectionLabel
|
|
616
|
-
)}
|
|
617
|
-
>
|
|
618
|
-
{label}
|
|
619
|
-
</div>
|
|
620
|
-
|
|
621
|
-
<div
|
|
622
|
-
role="listbox"
|
|
623
|
-
aria-label={listLabel}
|
|
624
|
-
className={cx(
|
|
625
|
-
"refraktor-scrollbar flex max-h-64 flex-col p-1 overflow-y-auto",
|
|
626
|
-
sizeStyles.gridGap,
|
|
627
|
-
classes.list
|
|
628
|
-
)}
|
|
629
|
-
onKeyDown={(event) => handleListKeyDown(event, options, onSelect)}
|
|
630
|
-
>
|
|
631
|
-
{options.map((option, index) => {
|
|
632
|
-
const tabIndex =
|
|
633
|
-
option.selected ||
|
|
634
|
-
(!hasVisibleSelection && index === firstEnabledIndex)
|
|
635
|
-
? 0
|
|
636
|
-
: -1;
|
|
637
|
-
|
|
638
|
-
return (
|
|
639
|
-
<button
|
|
640
|
-
key={`${labelId}-${String(option.value)}`}
|
|
641
|
-
type="button"
|
|
642
|
-
role="option"
|
|
643
|
-
aria-selected={option.selected}
|
|
644
|
-
aria-label={option.ariaLabel}
|
|
645
|
-
data-active={option.selected}
|
|
646
|
-
data-disabled={option.disabled}
|
|
647
|
-
disabled={option.disabled}
|
|
648
|
-
tabIndex={tabIndex}
|
|
649
|
-
className={cx(
|
|
650
|
-
"inline-flex w-full items-center justify-center font-medium text-[var(--refraktor-text)] transition-colors",
|
|
651
|
-
option.selected
|
|
652
|
-
? "bg-[var(--refraktor-primary)] text-[var(--refraktor-primary-text)]"
|
|
653
|
-
: "hover:bg-[var(--refraktor-bg-hover)]",
|
|
654
|
-
option.disabled &&
|
|
655
|
-
"pointer-events-none cursor-not-allowed opacity-50",
|
|
656
|
-
sizeStyles.cell,
|
|
657
|
-
getRadius(radius),
|
|
658
|
-
classes.option,
|
|
659
|
-
option.selected && classes.optionActive,
|
|
660
|
-
option.disabled && classes.optionDisabled
|
|
661
|
-
)}
|
|
662
|
-
onClick={() => onSelect(option.value)}
|
|
663
|
-
>
|
|
664
|
-
{option.label}
|
|
665
|
-
</button>
|
|
666
|
-
);
|
|
667
|
-
})}
|
|
668
|
-
</div>
|
|
669
|
-
</div>
|
|
670
|
-
);
|
|
671
|
-
};
|
|
672
|
-
|
|
673
|
-
return (
|
|
674
|
-
<div
|
|
675
|
-
ref={ref}
|
|
676
|
-
id={_id}
|
|
677
|
-
className={cx(
|
|
678
|
-
"inline-flex w-full flex-col bg-[var(--refraktor-bg)] overflow-hidden border border-[var(--refraktor-border)]",
|
|
679
|
-
getRadius(radius),
|
|
680
|
-
classes.root,
|
|
681
|
-
className
|
|
682
|
-
)}
|
|
683
|
-
{...props}
|
|
684
|
-
>
|
|
685
|
-
<div
|
|
686
|
-
className={cx(
|
|
687
|
-
"grid divide-x divide-[var(--refraktor-border)]",
|
|
688
|
-
getGridColumns(mode === "12h" ? 4 : 3),
|
|
689
|
-
classes.grid
|
|
690
|
-
)}
|
|
691
|
-
>
|
|
692
|
-
{renderSection({
|
|
693
|
-
label: "Hour",
|
|
694
|
-
labelId: `${_id}-hour-label`,
|
|
695
|
-
listLabel: "Hour options",
|
|
696
|
-
options: hourOptions,
|
|
697
|
-
onSelect: setHour,
|
|
698
|
-
className: classes.hourSection
|
|
699
|
-
})}
|
|
700
|
-
|
|
701
|
-
{renderSection({
|
|
702
|
-
label: "Minute",
|
|
703
|
-
labelId: `${_id}-minute-label`,
|
|
704
|
-
listLabel: "Minute options",
|
|
705
|
-
options: minuteOptions,
|
|
706
|
-
onSelect: setMinute,
|
|
707
|
-
className: classes.minuteSection
|
|
708
|
-
})}
|
|
709
|
-
|
|
710
|
-
{renderSection({
|
|
711
|
-
label: "Second",
|
|
712
|
-
labelId: `${_id}-second-label`,
|
|
713
|
-
listLabel: "Second options",
|
|
714
|
-
options: secondOptions,
|
|
715
|
-
onSelect: setSecond,
|
|
716
|
-
className: classes.secondSection
|
|
717
|
-
})}
|
|
718
|
-
|
|
719
|
-
{mode === "12h" &&
|
|
720
|
-
renderSection({
|
|
721
|
-
label: "Period",
|
|
722
|
-
labelId: `${_id}-period-label`,
|
|
723
|
-
listLabel: "AM or PM options",
|
|
724
|
-
options: periodOptions,
|
|
725
|
-
onSelect: setPeriod,
|
|
726
|
-
className: classes.periodSection
|
|
727
|
-
})}
|
|
728
|
-
</div>
|
|
729
|
-
</div>
|
|
730
|
-
);
|
|
731
|
-
});
|
|
732
|
-
|
|
733
|
-
TimePicker.displayName = "@refraktor/dates/TimePicker";
|
|
734
|
-
TimePicker.configure = createComponentConfig<TimePickerProps>();
|
|
735
|
-
TimePicker.classNames = createClassNamesConfig<TimePickerClassNames>();
|
|
736
|
-
|
|
737
|
-
export default TimePicker;
|