@refraktor/dates 0.0.1

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 (128) hide show
  1. package/.turbo/turbo-build.log +4 -0
  2. package/LICENSE +21 -0
  3. package/README.md +21 -0
  4. package/build/components/date-input/date-input.d.ts +4 -0
  5. package/build/components/date-input/date-input.d.ts.map +1 -0
  6. package/build/components/date-input/date-input.js +164 -0
  7. package/build/components/date-input/date-input.types.d.ts +96 -0
  8. package/build/components/date-input/date-input.types.d.ts.map +1 -0
  9. package/build/components/date-input/date-input.types.js +1 -0
  10. package/build/components/date-input/index.d.ts +3 -0
  11. package/build/components/date-input/index.d.ts.map +1 -0
  12. package/build/components/date-input/index.js +1 -0
  13. package/build/components/date-picker/date-picker.d.ts +4 -0
  14. package/build/components/date-picker/date-picker.d.ts.map +1 -0
  15. package/build/components/date-picker/date-picker.js +307 -0
  16. package/build/components/date-picker/date-picker.types.d.ts +86 -0
  17. package/build/components/date-picker/date-picker.types.d.ts.map +1 -0
  18. package/build/components/date-picker/date-picker.types.js +1 -0
  19. package/build/components/date-picker/index.d.ts +3 -0
  20. package/build/components/date-picker/index.d.ts.map +1 -0
  21. package/build/components/date-picker/index.js +1 -0
  22. package/build/components/dates-provider/context.d.ts +4 -0
  23. package/build/components/dates-provider/context.d.ts.map +1 -0
  24. package/build/components/dates-provider/context.js +10 -0
  25. package/build/components/dates-provider/dates-provider.d.ts +7 -0
  26. package/build/components/dates-provider/dates-provider.d.ts.map +1 -0
  27. package/build/components/dates-provider/dates-provider.js +65 -0
  28. package/build/components/dates-provider/index.d.ts +5 -0
  29. package/build/components/dates-provider/index.d.ts.map +1 -0
  30. package/build/components/dates-provider/index.js +3 -0
  31. package/build/components/dates-provider/types.d.ts +26 -0
  32. package/build/components/dates-provider/types.d.ts.map +1 -0
  33. package/build/components/dates-provider/types.js +1 -0
  34. package/build/components/dates-provider/use-dates.d.ts +2 -0
  35. package/build/components/dates-provider/use-dates.d.ts.map +1 -0
  36. package/build/components/dates-provider/use-dates.js +4 -0
  37. package/build/components/index.d.ts +8 -0
  38. package/build/components/index.d.ts.map +1 -0
  39. package/build/components/index.js +7 -0
  40. package/build/components/month-input/index.d.ts +3 -0
  41. package/build/components/month-input/index.d.ts.map +1 -0
  42. package/build/components/month-input/index.js +1 -0
  43. package/build/components/month-input/month-input.d.ts +4 -0
  44. package/build/components/month-input/month-input.d.ts.map +1 -0
  45. package/build/components/month-input/month-input.js +161 -0
  46. package/build/components/month-input/month-input.types.d.ts +85 -0
  47. package/build/components/month-input/month-input.types.d.ts.map +1 -0
  48. package/build/components/month-input/month-input.types.js +1 -0
  49. package/build/components/month-picker/index.d.ts +3 -0
  50. package/build/components/month-picker/index.d.ts.map +1 -0
  51. package/build/components/month-picker/index.js +1 -0
  52. package/build/components/month-picker/month-picker.d.ts +4 -0
  53. package/build/components/month-picker/month-picker.d.ts.map +1 -0
  54. package/build/components/month-picker/month-picker.js +229 -0
  55. package/build/components/month-picker/month-picker.types.d.ts +69 -0
  56. package/build/components/month-picker/month-picker.types.d.ts.map +1 -0
  57. package/build/components/month-picker/month-picker.types.js +1 -0
  58. package/build/components/picker-shared/index.d.ts +5 -0
  59. package/build/components/picker-shared/index.d.ts.map +1 -0
  60. package/build/components/picker-shared/index.js +2 -0
  61. package/build/components/picker-shared/picker-header.d.ts +4 -0
  62. package/build/components/picker-shared/picker-header.d.ts.map +1 -0
  63. package/build/components/picker-shared/picker-header.js +27 -0
  64. package/build/components/picker-shared/picker-header.types.d.ts +36 -0
  65. package/build/components/picker-shared/picker-header.types.d.ts.map +1 -0
  66. package/build/components/picker-shared/picker-header.types.js +1 -0
  67. package/build/components/picker-shared/picker.styles.d.ts +12 -0
  68. package/build/components/picker-shared/picker.styles.d.ts.map +1 -0
  69. package/build/components/picker-shared/picker.styles.js +53 -0
  70. package/build/components/picker-shared/picker.types.d.ts +4 -0
  71. package/build/components/picker-shared/picker.types.d.ts.map +1 -0
  72. package/build/components/picker-shared/picker.types.js +1 -0
  73. package/build/components/year-input/index.d.ts +3 -0
  74. package/build/components/year-input/index.d.ts.map +1 -0
  75. package/build/components/year-input/index.js +1 -0
  76. package/build/components/year-input/year-input.d.ts +4 -0
  77. package/build/components/year-input/year-input.d.ts.map +1 -0
  78. package/build/components/year-input/year-input.js +157 -0
  79. package/build/components/year-input/year-input.types.d.ts +74 -0
  80. package/build/components/year-input/year-input.types.d.ts.map +1 -0
  81. package/build/components/year-input/year-input.types.js +1 -0
  82. package/build/components/year-picker/index.d.ts +3 -0
  83. package/build/components/year-picker/index.d.ts.map +1 -0
  84. package/build/components/year-picker/index.js +1 -0
  85. package/build/components/year-picker/year-picker.d.ts +4 -0
  86. package/build/components/year-picker/year-picker.d.ts.map +1 -0
  87. package/build/components/year-picker/year-picker.js +236 -0
  88. package/build/components/year-picker/year-picker.types.d.ts +70 -0
  89. package/build/components/year-picker/year-picker.types.d.ts.map +1 -0
  90. package/build/components/year-picker/year-picker.types.js +1 -0
  91. package/build/index.d.ts +3 -0
  92. package/build/index.d.ts.map +1 -0
  93. package/build/index.js +2 -0
  94. package/build/style.css +2 -0
  95. package/package.json +38 -0
  96. package/refraktor-dates-0.0.1-alpha.0.tgz +0 -0
  97. package/src/components/date-input/date-input.tsx +376 -0
  98. package/src/components/date-input/date-input.types.ts +161 -0
  99. package/src/components/date-input/index.ts +13 -0
  100. package/src/components/date-picker/date-picker.tsx +649 -0
  101. package/src/components/date-picker/date-picker.types.ts +145 -0
  102. package/src/components/date-picker/index.ts +15 -0
  103. package/src/components/dates-provider/context.ts +18 -0
  104. package/src/components/dates-provider/dates-provider.tsx +136 -0
  105. package/src/components/dates-provider/index.ts +10 -0
  106. package/src/components/dates-provider/types.ts +33 -0
  107. package/src/components/dates-provider/use-dates.ts +5 -0
  108. package/src/components/index.ts +7 -0
  109. package/src/components/month-input/index.ts +13 -0
  110. package/src/components/month-input/month-input.tsx +363 -0
  111. package/src/components/month-input/month-input.types.ts +139 -0
  112. package/src/components/month-picker/index.ts +14 -0
  113. package/src/components/month-picker/month-picker.tsx +458 -0
  114. package/src/components/month-picker/month-picker.types.ts +117 -0
  115. package/src/components/picker-shared/index.ts +7 -0
  116. package/src/components/picker-shared/picker-header.tsx +178 -0
  117. package/src/components/picker-shared/picker-header.types.ts +49 -0
  118. package/src/components/picker-shared/picker.styles.ts +69 -0
  119. package/src/components/picker-shared/picker.types.ts +4 -0
  120. package/src/components/year-input/index.ts +13 -0
  121. package/src/components/year-input/year-input.tsx +347 -0
  122. package/src/components/year-input/year-input.types.ts +118 -0
  123. package/src/components/year-picker/index.ts +15 -0
  124. package/src/components/year-picker/year-picker.tsx +504 -0
  125. package/src/components/year-picker/year-picker.types.ts +108 -0
  126. package/src/index.ts +3 -0
  127. package/src/style.css +1 -0
  128. package/tsconfig.json +13 -0
@@ -0,0 +1,649 @@
1
+ import { useId, useUncontrolled } from "@refraktor/utils";
2
+ import { KeyboardEvent, useEffect, useMemo, useState } from "react";
3
+ import {
4
+ createClassNamesConfig,
5
+ createComponentConfig,
6
+ factory,
7
+ useTheme,
8
+ useClassNames,
9
+ useProps
10
+ } from "@refraktor/core";
11
+ import {
12
+ getGridColumns,
13
+ getPickerSizeStyles,
14
+ PickerHeader
15
+ } from "../picker-shared";
16
+ import { useDates } from "../dates-provider";
17
+ import { MonthPicker } from "../month-picker";
18
+ import {
19
+ DatePickerClassNames,
20
+ DatePickerFactoryPayload,
21
+ DatePickerNavigationDirection,
22
+ DatePickerProps
23
+ } from "./date-picker.types";
24
+
25
+ const DAYS_IN_WEEK = 7;
26
+ const CONSISTENT_WEEK_COUNT = 6;
27
+ const DEFAULT_MONTH_PICKER_COLUMNS = 3;
28
+ const DEFAULT_YEAR_PICKER_YEARS_PER_PAGE = 9;
29
+ const DEFAULT_YEAR_PICKER_COLUMNS = 3;
30
+
31
+ const defaultProps = {
32
+ monthPickerColumns: DEFAULT_MONTH_PICKER_COLUMNS,
33
+ yearPickerYearsPerPage: DEFAULT_YEAR_PICKER_YEARS_PER_PAGE,
34
+ yearPickerColumns: DEFAULT_YEAR_PICKER_COLUMNS,
35
+ disabled: false,
36
+ size: "md",
37
+ radius: "default"
38
+ } satisfies Partial<DatePickerProps>;
39
+
40
+ type DateBounds = {
41
+ minDate: Date | undefined;
42
+ maxDate: Date | undefined;
43
+ minTimestamp: number;
44
+ maxTimestamp: number;
45
+ hasMin: boolean;
46
+ hasMax: boolean;
47
+ };
48
+
49
+ type DatePickerView = "day" | "month";
50
+
51
+ const isValidDate = (value: unknown): value is Date =>
52
+ value instanceof Date && !Number.isNaN(value.getTime());
53
+
54
+ const toSafeInteger = (value: number | undefined, fallback: number) => {
55
+ if (!Number.isFinite(value)) {
56
+ return fallback;
57
+ }
58
+
59
+ return Math.trunc(value as number);
60
+ };
61
+
62
+ const clamp = (value: number, min: number, max: number) =>
63
+ Math.min(max, Math.max(min, value));
64
+
65
+ const startOfDay = (value: Date) => {
66
+ const next = new Date(value);
67
+ next.setHours(0, 0, 0, 0);
68
+ return next;
69
+ };
70
+
71
+ const startOfMonth = (value: Date) => {
72
+ const next = startOfDay(value);
73
+ next.setDate(1);
74
+ return next;
75
+ };
76
+
77
+ const isSameDay = (first: Date, second: Date) =>
78
+ first.getFullYear() === second.getFullYear() &&
79
+ first.getMonth() === second.getMonth() &&
80
+ first.getDate() === second.getDate();
81
+
82
+ const isSameMonth = (first: Date, second: Date) =>
83
+ first.getFullYear() === second.getFullYear() &&
84
+ first.getMonth() === second.getMonth();
85
+
86
+ const addDays = (value: Date, amount: number) => {
87
+ const next = startOfDay(value);
88
+ next.setDate(next.getDate() + amount);
89
+ return next;
90
+ };
91
+
92
+ const addMonthsPreservingDay = (value: Date, amount: number) => {
93
+ const originalDay = value.getDate();
94
+ const next = startOfDay(value);
95
+ next.setDate(1);
96
+ next.setMonth(next.getMonth() + amount);
97
+
98
+ const lastDayInTargetMonth = new Date(
99
+ next.getFullYear(),
100
+ next.getMonth() + 1,
101
+ 0
102
+ ).getDate();
103
+
104
+ next.setDate(Math.min(originalDay, lastDayInTargetMonth));
105
+ return next;
106
+ };
107
+
108
+ const getDateBounds = (minDate?: Date, maxDate?: Date): DateBounds => {
109
+ const normalizedMin = isValidDate(minDate) ? startOfDay(minDate) : undefined;
110
+ const normalizedMax = isValidDate(maxDate) ? startOfDay(maxDate) : undefined;
111
+
112
+ if (normalizedMin && normalizedMax && normalizedMin > normalizedMax) {
113
+ return {
114
+ minDate: normalizedMax,
115
+ maxDate: normalizedMin,
116
+ minTimestamp: normalizedMax.getTime(),
117
+ maxTimestamp: normalizedMin.getTime(),
118
+ hasMin: true,
119
+ hasMax: true
120
+ };
121
+ }
122
+
123
+ return {
124
+ minDate: normalizedMin,
125
+ maxDate: normalizedMax,
126
+ minTimestamp: normalizedMin?.getTime() ?? Number.NEGATIVE_INFINITY,
127
+ maxTimestamp: normalizedMax?.getTime() ?? Number.POSITIVE_INFINITY,
128
+ hasMin: normalizedMin !== undefined,
129
+ hasMax: normalizedMax !== undefined
130
+ };
131
+ };
132
+
133
+ const clampDate = (value: Date, bounds: DateBounds) => {
134
+ const normalizedValue = startOfDay(value);
135
+ const timestamp = normalizedValue.getTime();
136
+
137
+ if (timestamp < bounds.minTimestamp && bounds.minDate) {
138
+ return bounds.minDate;
139
+ }
140
+
141
+ if (timestamp > bounds.maxTimestamp && bounds.maxDate) {
142
+ return bounds.maxDate;
143
+ }
144
+
145
+ return normalizedValue;
146
+ };
147
+
148
+ const isDateDisabled = (value: Date, disabled: boolean, bounds: DateBounds) =>
149
+ disabled ||
150
+ value.getTime() < bounds.minTimestamp ||
151
+ value.getTime() > bounds.maxTimestamp;
152
+
153
+ const toMonthIndex = (value: Date) => value.getFullYear() * 12 + value.getMonth();
154
+
155
+ const resolveMonthStart = (value: Date, bounds: DateBounds) =>
156
+ startOfMonth(clampDate(value, bounds));
157
+
158
+ const getVisibleDays = (
159
+ displayedMonth: Date,
160
+ firstDayOfWeek: number,
161
+ consistentWeeks: boolean
162
+ ) => {
163
+ const monthStart = startOfMonth(displayedMonth);
164
+ const leadingDays =
165
+ (monthStart.getDay() - firstDayOfWeek + DAYS_IN_WEEK) % DAYS_IN_WEEK;
166
+ const daysInMonth = new Date(
167
+ monthStart.getFullYear(),
168
+ monthStart.getMonth() + 1,
169
+ 0
170
+ ).getDate();
171
+
172
+ const visibleWithoutTrailing = leadingDays + daysInMonth;
173
+ const trailingDays = consistentWeeks
174
+ ? DAYS_IN_WEEK * CONSISTENT_WEEK_COUNT - visibleWithoutTrailing
175
+ : (DAYS_IN_WEEK - (visibleWithoutTrailing % DAYS_IN_WEEK)) % DAYS_IN_WEEK;
176
+
177
+ const totalDays = visibleWithoutTrailing + trailingDays;
178
+ const firstVisibleDate = addDays(monthStart, -leadingDays);
179
+
180
+ return Array.from({ length: totalDays }, (_, index) =>
181
+ addDays(firstVisibleDate, index)
182
+ );
183
+ };
184
+
185
+ const DatePicker = factory<DatePickerFactoryPayload>((_props, ref) => {
186
+ const { cx, getRadius } = useTheme();
187
+ const { createDate, firstDayOfWeek, consistentWeeks, weekdays } = useDates();
188
+ const {
189
+ id,
190
+ value,
191
+ defaultValue,
192
+ onChange,
193
+ minDate,
194
+ maxDate,
195
+ monthPickerColumns,
196
+ yearPickerYearsPerPage,
197
+ yearPickerColumns,
198
+ disabled,
199
+ size,
200
+ radius,
201
+ getWeekdayLabel,
202
+ getDayLabel,
203
+ getDayAriaLabel,
204
+ getHeaderLabel,
205
+ getNavigationAriaLabel,
206
+ getMonthLabel,
207
+ getMonthAriaLabel,
208
+ getMonthHeaderLabel,
209
+ getMonthNavigationAriaLabel,
210
+ className,
211
+ classNames,
212
+ ...props
213
+ } = useProps("DatePicker", defaultProps, _props);
214
+ const classes = useClassNames("DatePicker", classNames);
215
+
216
+ const _id = useId(id);
217
+ const today = useMemo(() => startOfDay(new Date()), []);
218
+ const bounds = useMemo(() => getDateBounds(minDate, maxDate), [minDate, maxDate]);
219
+
220
+ const safeMonthPickerColumns = clamp(
221
+ toSafeInteger(monthPickerColumns, DEFAULT_MONTH_PICKER_COLUMNS),
222
+ 1,
223
+ 6
224
+ );
225
+ const safeYearPickerYearsPerPage = Math.max(
226
+ 1,
227
+ toSafeInteger(
228
+ yearPickerYearsPerPage,
229
+ DEFAULT_YEAR_PICKER_YEARS_PER_PAGE
230
+ )
231
+ );
232
+ const safeYearPickerColumns = clamp(
233
+ toSafeInteger(yearPickerColumns, DEFAULT_YEAR_PICKER_COLUMNS),
234
+ 1,
235
+ Math.min(6, safeYearPickerYearsPerPage)
236
+ );
237
+
238
+ const [selectedDateState, setSelectedDate] = useUncontrolled<
239
+ Date | undefined
240
+ >({
241
+ value,
242
+ defaultValue,
243
+ finalValue: undefined,
244
+ onChange: (nextDate) => {
245
+ if (nextDate !== undefined) {
246
+ onChange?.(nextDate);
247
+ }
248
+ }
249
+ });
250
+
251
+ const selectedDateTimestamp = isValidDate(selectedDateState)
252
+ ? startOfDay(selectedDateState).getTime()
253
+ : undefined;
254
+ const selectedDate =
255
+ selectedDateTimestamp !== undefined
256
+ ? new Date(selectedDateTimestamp)
257
+ : undefined;
258
+
259
+ const [displayedMonth, setDisplayedMonth] = useState(() =>
260
+ resolveMonthStart(selectedDate ?? today, bounds)
261
+ );
262
+ const [view, setView] = useState<DatePickerView>("day");
263
+
264
+ useEffect(() => {
265
+ setDisplayedMonth((previousMonth) =>
266
+ resolveMonthStart(previousMonth, bounds)
267
+ );
268
+ }, [bounds]);
269
+
270
+ useEffect(() => {
271
+ if (selectedDate === undefined) {
272
+ return;
273
+ }
274
+
275
+ const normalizedMonth = resolveMonthStart(selectedDate, bounds);
276
+
277
+ setDisplayedMonth((previousMonth) => {
278
+ if (isSameMonth(previousMonth, normalizedMonth)) {
279
+ return previousMonth;
280
+ }
281
+
282
+ return normalizedMonth;
283
+ });
284
+ }, [bounds, selectedDateTimestamp]);
285
+
286
+ const visibleDays = useMemo(
287
+ () => getVisibleDays(displayedMonth, firstDayOfWeek, consistentWeeks),
288
+ [consistentWeeks, displayedMonth, firstDayOfWeek]
289
+ );
290
+
291
+ const hasVisibleSelection =
292
+ selectedDate !== undefined &&
293
+ visibleDays.some((day) => isSameDay(day, selectedDate));
294
+
295
+ const firstAvailableDay = useMemo(
296
+ () =>
297
+ visibleDays.find(
298
+ (day) => !isDateDisabled(day, disabled ?? false, bounds)
299
+ ),
300
+ [bounds, disabled, visibleDays]
301
+ );
302
+
303
+ const canGoPrevious =
304
+ !disabled &&
305
+ (!bounds.hasMin ||
306
+ (bounds.minDate !== undefined &&
307
+ toMonthIndex(displayedMonth) > toMonthIndex(bounds.minDate)));
308
+ const canGoNext =
309
+ !disabled &&
310
+ (!bounds.hasMax ||
311
+ (bounds.maxDate !== undefined &&
312
+ toMonthIndex(displayedMonth) < toMonthIndex(bounds.maxDate)));
313
+
314
+ const sizeStyles = getPickerSizeStyles(size);
315
+
316
+ const resolveNavigationLabel = (
317
+ direction: DatePickerNavigationDirection
318
+ ) => {
319
+ if (getNavigationAriaLabel) {
320
+ return getNavigationAriaLabel(direction, displayedMonth);
321
+ }
322
+
323
+ const nextVisibleMonth = addMonthsPreservingDay(
324
+ displayedMonth,
325
+ direction === "previous" ? -1 : 1
326
+ );
327
+
328
+ return direction === "previous"
329
+ ? `Show previous month (${createDate(nextVisibleMonth).format("MMMM YYYY")})`
330
+ : `Show next month (${createDate(nextVisibleMonth).format("MMMM YYYY")})`;
331
+ };
332
+
333
+ const resolveDayAriaLabel = (
334
+ day: Date,
335
+ isSelected: boolean,
336
+ isOutside: boolean,
337
+ isDisabled: boolean
338
+ ) => {
339
+ if (getDayAriaLabel) {
340
+ return getDayAriaLabel(day, isSelected);
341
+ }
342
+
343
+ const dayLabel = createDate(day).format("dddd, MMMM D, YYYY");
344
+
345
+ if (isSelected) {
346
+ return `${dayLabel}, selected`;
347
+ }
348
+
349
+ const suffix = [
350
+ isOutside ? "outside current month" : undefined,
351
+ isDisabled ? "unavailable" : undefined
352
+ ]
353
+ .filter(Boolean)
354
+ .join(", ");
355
+
356
+ return suffix ? `Choose ${dayLabel}, ${suffix}` : `Choose ${dayLabel}`;
357
+ };
358
+
359
+ const setDay = (day: Date) => {
360
+ const normalizedDay = startOfDay(day);
361
+
362
+ if (isDateDisabled(normalizedDay, disabled ?? false, bounds)) {
363
+ return;
364
+ }
365
+
366
+ if (selectedDate !== undefined && isSameDay(selectedDate, normalizedDay)) {
367
+ return;
368
+ }
369
+
370
+ setDisplayedMonth(startOfMonth(normalizedDay));
371
+ setSelectedDate(normalizedDay);
372
+ };
373
+
374
+ const shiftDisplayedMonth = (direction: -1 | 1) => {
375
+ setDisplayedMonth((previousMonth) =>
376
+ resolveMonthStart(
377
+ addMonthsPreservingDay(previousMonth, direction),
378
+ bounds
379
+ )
380
+ );
381
+ };
382
+
383
+ const handlePrevious = () => {
384
+ if (!canGoPrevious) {
385
+ return;
386
+ }
387
+
388
+ shiftDisplayedMonth(-1);
389
+ };
390
+
391
+ const handleNext = () => {
392
+ if (!canGoNext) {
393
+ return;
394
+ }
395
+
396
+ shiftDisplayedMonth(1);
397
+ };
398
+
399
+ const handleGridKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {
400
+ if (disabled) {
401
+ return;
402
+ }
403
+
404
+ const keyboardBaseDay =
405
+ selectedDate && visibleDays.some((day) => isSameDay(day, selectedDate))
406
+ ? selectedDate
407
+ : firstAvailableDay ?? clampDate(displayedMonth, bounds);
408
+
409
+ const keyboardSteps: Record<string, number> = {
410
+ ArrowLeft: -1,
411
+ ArrowRight: 1,
412
+ ArrowUp: -DAYS_IN_WEEK,
413
+ ArrowDown: DAYS_IN_WEEK
414
+ };
415
+
416
+ const step = keyboardSteps[event.key];
417
+
418
+ if (step !== undefined) {
419
+ event.preventDefault();
420
+ setDay(addDays(keyboardBaseDay, step));
421
+ return;
422
+ }
423
+
424
+ if (event.key === "Home") {
425
+ event.preventDefault();
426
+ setDay(startOfMonth(displayedMonth));
427
+ return;
428
+ }
429
+
430
+ if (event.key === "End") {
431
+ event.preventDefault();
432
+ setDay(
433
+ new Date(
434
+ displayedMonth.getFullYear(),
435
+ displayedMonth.getMonth() + 1,
436
+ 0
437
+ )
438
+ );
439
+ return;
440
+ }
441
+
442
+ if (event.key === "PageUp") {
443
+ event.preventDefault();
444
+ setDay(addMonthsPreservingDay(keyboardBaseDay, -1));
445
+ return;
446
+ }
447
+
448
+ if (event.key === "PageDown") {
449
+ event.preventDefault();
450
+ setDay(addMonthsPreservingDay(keyboardBaseDay, 1));
451
+ }
452
+ };
453
+
454
+ const handleHeaderLabelClick = () => {
455
+ if (disabled) {
456
+ return;
457
+ }
458
+
459
+ setView("month");
460
+ };
461
+
462
+ const handleMonthChange = (nextMonth: Date) => {
463
+ if (disabled) {
464
+ return;
465
+ }
466
+
467
+ setDisplayedMonth(resolveMonthStart(nextMonth, bounds));
468
+ setView("day");
469
+ };
470
+
471
+ const monthViewMinYear = bounds.minDate?.getFullYear();
472
+ const monthViewMaxYear = bounds.maxDate?.getFullYear();
473
+
474
+ return (
475
+ <div
476
+ ref={ref}
477
+ id={_id}
478
+ className={cx(
479
+ "inline-flex w-full flex-col gap-2 bg-[var(--refraktor-bg)] p-2",
480
+ getRadius(radius),
481
+ classes.root,
482
+ className
483
+ )}
484
+ {...props}
485
+ >
486
+ {view === "month" ? (
487
+ <MonthPicker
488
+ value={displayedMonth}
489
+ onChange={handleMonthChange}
490
+ minYear={monthViewMinYear}
491
+ maxYear={monthViewMaxYear}
492
+ columns={safeMonthPickerColumns}
493
+ yearPickerYearsPerPage={safeYearPickerYearsPerPage}
494
+ yearPickerColumns={safeYearPickerColumns}
495
+ disabled={disabled}
496
+ size={size}
497
+ radius={radius}
498
+ getMonthLabel={getMonthLabel}
499
+ getMonthAriaLabel={getMonthAriaLabel}
500
+ getHeaderLabel={getMonthHeaderLabel}
501
+ getNavigationAriaLabel={getMonthNavigationAriaLabel}
502
+ className={cx("bg-transparent p-0", classes.monthPicker)}
503
+ />
504
+ ) : (
505
+ <>
506
+ <PickerHeader
507
+ label={
508
+ getHeaderLabel
509
+ ? getHeaderLabel(
510
+ displayedMonth.getMonth(),
511
+ displayedMonth.getFullYear(),
512
+ displayedMonth
513
+ )
514
+ : createDate(displayedMonth).format("MMMM YYYY")
515
+ }
516
+ onPrevious={handlePrevious}
517
+ onNext={handleNext}
518
+ onLabelClick={
519
+ disabled ? undefined : handleHeaderLabelClick
520
+ }
521
+ previousDisabled={!canGoPrevious}
522
+ nextDisabled={!canGoNext}
523
+ previousLabel={resolveNavigationLabel("previous")}
524
+ nextLabel={resolveNavigationLabel("next")}
525
+ size={size}
526
+ radius={radius}
527
+ classNames={{
528
+ root: classes.header,
529
+ controls: classes.headerControls,
530
+ control: classes.headerControl,
531
+ previousControl: classes.headerPreviousControl,
532
+ nextControl: classes.headerNextControl,
533
+ label: classes.headerLabel
534
+ }}
535
+ />
536
+
537
+ <div
538
+ role="row"
539
+ className={cx(
540
+ "grid grid-cols-7 text-center text-[var(--refraktor-text-secondary)]",
541
+ sizeStyles.label,
542
+ sizeStyles.gridGap,
543
+ classes.weekdays
544
+ )}
545
+ >
546
+ {weekdays.map((weekday, index) => {
547
+ const dayOfWeek =
548
+ (firstDayOfWeek + index) % DAYS_IN_WEEK;
549
+
550
+ return (
551
+ <span
552
+ key={`${weekday}-${index}`}
553
+ className={cx("truncate", classes.weekday)}
554
+ >
555
+ {getWeekdayLabel
556
+ ? getWeekdayLabel(dayOfWeek, weekday)
557
+ : weekday}
558
+ </span>
559
+ );
560
+ })}
561
+ </div>
562
+
563
+ <div
564
+ role="grid"
565
+ aria-label={`Date picker, ${createDate(displayedMonth).format("MMMM YYYY")}`}
566
+ className={cx(
567
+ "grid",
568
+ getGridColumns(DAYS_IN_WEEK),
569
+ sizeStyles.gridGap,
570
+ classes.grid
571
+ )}
572
+ onKeyDown={handleGridKeyDown}
573
+ >
574
+ {visibleDays.map((day) => {
575
+ const isOutside = !isSameMonth(day, displayedMonth);
576
+ const isSelected =
577
+ selectedDate !== undefined &&
578
+ isSameDay(day, selectedDate);
579
+ const isToday = isSameDay(day, today);
580
+ const isDayDisabled = isDateDisabled(
581
+ day,
582
+ disabled ?? false,
583
+ bounds
584
+ );
585
+ const tabIndex =
586
+ isSelected ||
587
+ (!hasVisibleSelection &&
588
+ firstAvailableDay !== undefined &&
589
+ isSameDay(day, firstAvailableDay))
590
+ ? 0
591
+ : -1;
592
+
593
+ return (
594
+ <button
595
+ key={day.toISOString()}
596
+ type="button"
597
+ role="gridcell"
598
+ aria-selected={isSelected}
599
+ aria-current={isToday ? "date" : undefined}
600
+ aria-label={resolveDayAriaLabel(
601
+ day,
602
+ isSelected,
603
+ isOutside,
604
+ isDayDisabled
605
+ )}
606
+ tabIndex={tabIndex}
607
+ data-active={isSelected}
608
+ data-outside={isOutside}
609
+ data-today={isToday}
610
+ data-disabled={isDayDisabled}
611
+ disabled={isDayDisabled}
612
+ className={cx(
613
+ "inline-flex items-center justify-center font-medium text-[var(--refraktor-text)] transition-colors",
614
+ "hover:bg-[var(--refraktor-bg-hover)]",
615
+ isSelected &&
616
+ "bg-[var(--refraktor-primary)] text-[var(--refraktor-primary-text)] hover:bg-[var(--refraktor-primary)]",
617
+ isOutside &&
618
+ "text-[var(--refraktor-text-secondary)]",
619
+ isToday &&
620
+ !isSelected &&
621
+ "ring-1 ring-[var(--refraktor-primary)]",
622
+ isDayDisabled &&
623
+ "pointer-events-none cursor-not-allowed opacity-50",
624
+ sizeStyles.cell,
625
+ getRadius(radius),
626
+ classes.day,
627
+ isOutside && classes.dayOutside,
628
+ isToday && classes.dayToday,
629
+ isSelected && classes.daySelected,
630
+ isDayDisabled && classes.dayDisabled
631
+ )}
632
+ onClick={() => setDay(day)}
633
+ >
634
+ {getDayLabel ? getDayLabel(day) : day.getDate()}
635
+ </button>
636
+ );
637
+ })}
638
+ </div>
639
+ </>
640
+ )}
641
+ </div>
642
+ );
643
+ });
644
+
645
+ DatePicker.displayName = "@refraktor/dates/DatePicker";
646
+ DatePicker.configure = createComponentConfig<DatePickerProps>();
647
+ DatePicker.classNames = createClassNamesConfig<DatePickerClassNames>();
648
+
649
+ export default DatePicker;