@refraktor/dates 0.0.4 → 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.
Files changed (42) hide show
  1. package/build/style.css +2 -2
  2. package/package.json +33 -4
  3. package/.turbo/turbo-build.log +0 -4
  4. package/refraktor-dates-0.0.1-alpha.0.tgz +0 -0
  5. package/src/components/date-input/date-input.tsx +0 -379
  6. package/src/components/date-input/date-input.types.ts +0 -161
  7. package/src/components/date-input/index.ts +0 -13
  8. package/src/components/date-picker/date-picker.tsx +0 -649
  9. package/src/components/date-picker/date-picker.types.ts +0 -145
  10. package/src/components/date-picker/index.ts +0 -15
  11. package/src/components/dates-provider/context.ts +0 -18
  12. package/src/components/dates-provider/dates-provider.tsx +0 -136
  13. package/src/components/dates-provider/index.ts +0 -10
  14. package/src/components/dates-provider/types.ts +0 -33
  15. package/src/components/dates-provider/use-dates.ts +0 -5
  16. package/src/components/index.ts +0 -9
  17. package/src/components/month-input/index.ts +0 -13
  18. package/src/components/month-input/month-input.tsx +0 -366
  19. package/src/components/month-input/month-input.types.ts +0 -139
  20. package/src/components/month-picker/index.ts +0 -14
  21. package/src/components/month-picker/month-picker.tsx +0 -458
  22. package/src/components/month-picker/month-picker.types.ts +0 -117
  23. package/src/components/picker-shared/index.ts +0 -7
  24. package/src/components/picker-shared/picker-header.tsx +0 -178
  25. package/src/components/picker-shared/picker-header.types.ts +0 -49
  26. package/src/components/picker-shared/picker.styles.ts +0 -69
  27. package/src/components/picker-shared/picker.types.ts +0 -4
  28. package/src/components/time-input/index.ts +0 -6
  29. package/src/components/time-input/time-input.tsx +0 -48
  30. package/src/components/time-input/time-input.types.ts +0 -30
  31. package/src/components/time-picker/index.ts +0 -10
  32. package/src/components/time-picker/time-picker.tsx +0 -1088
  33. package/src/components/time-picker/time-picker.types.ts +0 -166
  34. package/src/components/year-input/index.ts +0 -13
  35. package/src/components/year-input/year-input.tsx +0 -350
  36. package/src/components/year-input/year-input.types.ts +0 -118
  37. package/src/components/year-picker/index.ts +0 -15
  38. package/src/components/year-picker/year-picker.tsx +0 -504
  39. package/src/components/year-picker/year-picker.types.ts +0 -108
  40. package/src/index.ts +0 -3
  41. package/src/style.css +0 -1
  42. package/tsconfig.json +0 -13
@@ -1,458 +0,0 @@
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 { YearPicker } from "../year-picker";
18
- import {
19
- MonthPickerClassNames,
20
- MonthPickerFactoryPayload,
21
- MonthPickerNavigationDirection,
22
- MonthPickerProps
23
- } from "./month-picker.types";
24
-
25
- const DEFAULT_COLUMNS = 3;
26
- const DEFAULT_YEAR_PICKER_YEARS_PER_PAGE = 9;
27
- const DEFAULT_YEAR_PICKER_COLUMNS = 3;
28
- const MONTHS_IN_YEAR = 12;
29
-
30
- const defaultProps = {
31
- columns: DEFAULT_COLUMNS,
32
- yearPickerYearsPerPage: DEFAULT_YEAR_PICKER_YEARS_PER_PAGE,
33
- yearPickerColumns: DEFAULT_YEAR_PICKER_COLUMNS,
34
- disabled: false,
35
- size: "md",
36
- radius: "default"
37
- } satisfies Partial<MonthPickerProps>;
38
-
39
- type YearBounds = {
40
- min: number;
41
- max: number;
42
- hasMin: boolean;
43
- hasMax: boolean;
44
- };
45
-
46
- const isValidDate = (value: unknown): value is Date =>
47
- value instanceof Date && !Number.isNaN(value.getTime());
48
-
49
- const toSafeInteger = (value: number | undefined, fallback: number) => {
50
- if (!Number.isFinite(value)) {
51
- return fallback;
52
- }
53
-
54
- return Math.trunc(value as number);
55
- };
56
-
57
- const clamp = (value: number, min: number, max: number) =>
58
- Math.min(max, Math.max(min, value));
59
-
60
- const getBounds = (minYear?: number, maxYear?: number): YearBounds => {
61
- const hasMin = Number.isFinite(minYear);
62
- const hasMax = Number.isFinite(maxYear);
63
-
64
- const min = hasMin
65
- ? Math.trunc(minYear as number)
66
- : Number.MIN_SAFE_INTEGER;
67
- const max = hasMax
68
- ? Math.trunc(maxYear as number)
69
- : Number.MAX_SAFE_INTEGER;
70
-
71
- if (min <= max) {
72
- return { min, max, hasMin, hasMax };
73
- }
74
-
75
- return {
76
- min: max,
77
- max: min,
78
- hasMin,
79
- hasMax
80
- };
81
- };
82
-
83
- const createMonthValue = (baseDate: Date, year: number, month: number) => {
84
- const nextDate = new Date(baseDate);
85
- nextDate.setFullYear(year, month, 1);
86
- nextDate.setHours(0, 0, 0, 0);
87
- return nextDate;
88
- };
89
-
90
- const isSameMonth = (first: Date, second: Date) =>
91
- first.getFullYear() === second.getFullYear() &&
92
- first.getMonth() === second.getMonth();
93
-
94
- type MonthPickerView = "month" | "year";
95
-
96
- const MonthPicker = factory<MonthPickerFactoryPayload>((_props, ref) => {
97
- const { cx, getRadius } = useTheme();
98
- const { createDate } = useDates();
99
- const {
100
- id,
101
- value,
102
- defaultValue,
103
- onChange,
104
- minYear,
105
- maxYear,
106
- columns,
107
- yearPickerYearsPerPage,
108
- yearPickerColumns,
109
- disabled,
110
- size,
111
- radius,
112
- getMonthLabel,
113
- getMonthAriaLabel,
114
- getHeaderLabel,
115
- getNavigationAriaLabel,
116
- className,
117
- classNames,
118
- ...props
119
- } = useProps("MonthPicker", defaultProps, _props);
120
- const classes = useClassNames("MonthPicker", classNames);
121
-
122
- const _id = useId(id);
123
-
124
- const today = new Date();
125
- const currentYear = today.getFullYear();
126
- const bounds = useMemo(
127
- () => getBounds(minYear, maxYear),
128
- [minYear, maxYear]
129
- );
130
-
131
- const safeColumns = clamp(
132
- toSafeInteger(columns, DEFAULT_COLUMNS),
133
- 1,
134
- Math.min(6, MONTHS_IN_YEAR)
135
- );
136
- const safeYearPickerYearsPerPage = Math.max(
137
- 1,
138
- toSafeInteger(
139
- yearPickerYearsPerPage,
140
- DEFAULT_YEAR_PICKER_YEARS_PER_PAGE
141
- )
142
- );
143
- const safeYearPickerColumns = clamp(
144
- toSafeInteger(yearPickerColumns, DEFAULT_YEAR_PICKER_COLUMNS),
145
- 1,
146
- Math.min(6, safeYearPickerYearsPerPage)
147
- );
148
-
149
- const [selectedDateState, setSelectedDate] = useUncontrolled<
150
- Date | undefined
151
- >({
152
- value,
153
- defaultValue,
154
- finalValue: undefined,
155
- onChange: (nextDate) => {
156
- if (nextDate !== undefined) {
157
- onChange?.(nextDate);
158
- }
159
- }
160
- });
161
-
162
- const selectedDate = isValidDate(selectedDateState)
163
- ? selectedDateState
164
- : undefined;
165
- const selectedYear = selectedDate?.getFullYear();
166
-
167
- const [displayedYear, setDisplayedYear] = useState(() =>
168
- clamp(selectedYear ?? currentYear, bounds.min, bounds.max)
169
- );
170
- const [view, setView] = useState<MonthPickerView>("month");
171
-
172
- useEffect(() => {
173
- setDisplayedYear((previousYear) =>
174
- clamp(previousYear, bounds.min, bounds.max)
175
- );
176
- }, [bounds.max, bounds.min]);
177
-
178
- useEffect(() => {
179
- if (selectedYear === undefined) {
180
- return;
181
- }
182
-
183
- const normalizedYear = clamp(selectedYear, bounds.min, bounds.max);
184
-
185
- setDisplayedYear((previousYear) => {
186
- if (previousYear === normalizedYear) {
187
- return previousYear;
188
- }
189
-
190
- return normalizedYear;
191
- });
192
- }, [bounds.max, bounds.min, selectedYear]);
193
-
194
- const canGoPrevious =
195
- !disabled && (!bounds.hasMin || displayedYear > bounds.min);
196
- const canGoNext = !disabled && (!bounds.hasMax || displayedYear < bounds.max);
197
-
198
- const sizeStyles = getPickerSizeStyles(size);
199
-
200
- const resolveNavigationLabel = (
201
- direction: MonthPickerNavigationDirection
202
- ) => {
203
- if (getNavigationAriaLabel) {
204
- return getNavigationAriaLabel(direction, displayedYear);
205
- }
206
-
207
- return direction === "previous"
208
- ? `Show previous year (${displayedYear - 1})`
209
- : `Show next year (${displayedYear + 1})`;
210
- };
211
-
212
- const monthItems = useMemo(
213
- () =>
214
- Array.from({ length: MONTHS_IN_YEAR }, (_, month) => {
215
- const dayjsMonth = createDate(new Date(displayedYear, month, 1));
216
-
217
- return {
218
- month,
219
- label: getMonthLabel
220
- ? getMonthLabel(month, displayedYear, dayjsMonth.toDate())
221
- : dayjsMonth.format("MMM"),
222
- fullLabel: dayjsMonth.format("MMMM")
223
- };
224
- }),
225
- [createDate, displayedYear, getMonthLabel]
226
- );
227
-
228
- const selectedMonth =
229
- selectedDate !== undefined && selectedDate.getFullYear() === displayedYear
230
- ? selectedDate.getMonth()
231
- : undefined;
232
-
233
- const resolveMonthAriaLabel = (month: number, isSelected: boolean) => {
234
- if (getMonthAriaLabel) {
235
- return getMonthAriaLabel(month, displayedYear, isSelected);
236
- }
237
-
238
- const fullMonthLabel = monthItems[month]?.fullLabel ?? `Month ${month + 1}`;
239
-
240
- return isSelected
241
- ? `${fullMonthLabel} ${displayedYear}, selected`
242
- : `Choose ${fullMonthLabel} ${displayedYear}`;
243
- };
244
-
245
- const handleMonthSelect = (month: number, year = displayedYear) => {
246
- if (disabled) {
247
- return;
248
- }
249
-
250
- const normalizedMonth = clamp(month, 0, MONTHS_IN_YEAR - 1);
251
- const normalizedYear = clamp(year, bounds.min, bounds.max);
252
-
253
- setDisplayedYear(normalizedYear);
254
-
255
- const baseDate = selectedDate ?? today;
256
- const nextDate = createMonthValue(baseDate, normalizedYear, normalizedMonth);
257
-
258
- if (selectedDate && isSameMonth(selectedDate, nextDate)) {
259
- return;
260
- }
261
-
262
- setSelectedDate(nextDate);
263
- };
264
-
265
- const shiftYear = (direction: -1 | 1) => {
266
- setDisplayedYear((previousYear) =>
267
- clamp(previousYear + direction, bounds.min, bounds.max)
268
- );
269
- };
270
-
271
- const handlePrevious = () => {
272
- if (!canGoPrevious) {
273
- return;
274
- }
275
-
276
- shiftYear(-1);
277
- };
278
-
279
- const handleNext = () => {
280
- if (!canGoNext) {
281
- return;
282
- }
283
-
284
- shiftYear(1);
285
- };
286
-
287
- const handleGridKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {
288
- if (disabled) {
289
- return;
290
- }
291
-
292
- const keyboardBaseMonth = selectedMonth ?? 0;
293
-
294
- const keyboardSteps: Record<string, number> = {
295
- ArrowLeft: -1,
296
- ArrowRight: 1,
297
- ArrowUp: -safeColumns,
298
- ArrowDown: safeColumns
299
- };
300
-
301
- const step = keyboardSteps[event.key];
302
-
303
- if (step !== undefined) {
304
- event.preventDefault();
305
- handleMonthSelect(keyboardBaseMonth + step);
306
- return;
307
- }
308
-
309
- if (event.key === "Home") {
310
- event.preventDefault();
311
- handleMonthSelect(0);
312
- return;
313
- }
314
-
315
- if (event.key === "End") {
316
- event.preventDefault();
317
- handleMonthSelect(MONTHS_IN_YEAR - 1);
318
- return;
319
- }
320
-
321
- if (event.key === "PageUp") {
322
- event.preventDefault();
323
- handleMonthSelect(keyboardBaseMonth, displayedYear - 1);
324
- return;
325
- }
326
-
327
- if (event.key === "PageDown") {
328
- event.preventDefault();
329
- handleMonthSelect(keyboardBaseMonth, displayedYear + 1);
330
- }
331
- };
332
-
333
- const handleHeaderLabelClick = () => {
334
- if (disabled) {
335
- return;
336
- }
337
-
338
- setView("year");
339
- };
340
-
341
- const handleYearSelect = (year: number) => {
342
- setDisplayedYear(clamp(year, bounds.min, bounds.max));
343
- setView("month");
344
- };
345
-
346
- const hasVisibleSelection = selectedMonth !== undefined;
347
-
348
- return (
349
- <div
350
- ref={ref}
351
- id={_id}
352
- className={cx(
353
- "inline-flex w-full flex-col gap-2 bg-[var(--refraktor-bg)] p-2",
354
- getRadius(radius),
355
- classes.root,
356
- className
357
- )}
358
- {...props}
359
- >
360
- {view === "year" ? (
361
- <YearPicker
362
- value={displayedYear}
363
- onChange={handleYearSelect}
364
- minYear={minYear}
365
- maxYear={maxYear}
366
- yearsPerPage={safeYearPickerYearsPerPage}
367
- columns={safeYearPickerColumns}
368
- disabled={disabled}
369
- size={size}
370
- radius={radius}
371
- className={cx("bg-transparent p-0", classes.yearPicker)}
372
- />
373
- ) : (
374
- <>
375
- <PickerHeader
376
- label={
377
- getHeaderLabel ? getHeaderLabel(displayedYear) : displayedYear
378
- }
379
- onPrevious={handlePrevious}
380
- onNext={handleNext}
381
- onLabelClick={
382
- disabled ? undefined : handleHeaderLabelClick
383
- }
384
- previousDisabled={!canGoPrevious}
385
- nextDisabled={!canGoNext}
386
- previousLabel={resolveNavigationLabel("previous")}
387
- nextLabel={resolveNavigationLabel("next")}
388
- size={size}
389
- radius={radius}
390
- classNames={{
391
- root: classes.header,
392
- controls: classes.headerControls,
393
- control: classes.headerControl,
394
- previousControl: classes.headerPreviousControl,
395
- nextControl: classes.headerNextControl,
396
- label: classes.headerLabel
397
- }}
398
- />
399
-
400
- <div
401
- role="grid"
402
- aria-label={`Month picker, ${displayedYear}`}
403
- className={cx(
404
- "grid",
405
- getGridColumns(safeColumns),
406
- sizeStyles.gridGap,
407
- classes.grid
408
- )}
409
- onKeyDown={handleGridKeyDown}
410
- >
411
- {monthItems.map(({ month, label }) => {
412
- const isSelected = month === selectedMonth;
413
- const tabIndex =
414
- isSelected || (!hasVisibleSelection && month === 0)
415
- ? 0
416
- : -1;
417
-
418
- return (
419
- <button
420
- key={month}
421
- type="button"
422
- role="gridcell"
423
- aria-selected={isSelected}
424
- aria-label={resolveMonthAriaLabel(month, isSelected)}
425
- tabIndex={tabIndex}
426
- data-active={isSelected}
427
- data-disabled={disabled}
428
- disabled={disabled}
429
- className={cx(
430
- "inline-flex items-center justify-center font-medium text-[var(--refraktor-text)] transition-colors",
431
- isSelected
432
- ? "bg-[var(--refraktor-primary)] text-[var(--refraktor-primary-text)]"
433
- : "hover:bg-[var(--refraktor-bg-hover)]",
434
- disabled &&
435
- "pointer-events-none cursor-not-allowed opacity-50 data-[disabled=true]:pointer-events-none",
436
- sizeStyles.cell,
437
- getRadius(radius),
438
- classes.month,
439
- isSelected && classes.monthActive
440
- )}
441
- onClick={() => handleMonthSelect(month)}
442
- >
443
- {label}
444
- </button>
445
- );
446
- })}
447
- </div>
448
- </>
449
- )}
450
- </div>
451
- );
452
- });
453
-
454
- MonthPicker.displayName = "@refraktor/dates/MonthPicker";
455
- MonthPicker.configure = createComponentConfig<MonthPickerProps>();
456
- MonthPicker.classNames = createClassNamesConfig<MonthPickerClassNames>();
457
-
458
- export default MonthPicker;
@@ -1,117 +0,0 @@
1
- import { ComponentPropsWithoutRef, ReactNode } from "react";
2
- import {
3
- RefraktorRadius,
4
- RefraktorSize,
5
- createClassNamesConfig,
6
- createComponentConfig,
7
- FactoryPayload
8
- } from "@refraktor/core";
9
-
10
- export type MonthPickerValue = Date;
11
- export type MonthPickerSize = RefraktorSize;
12
- export type MonthPickerRadius = RefraktorRadius;
13
-
14
- export type MonthPickerNavigationDirection = "previous" | "next";
15
-
16
- export type MonthPickerOnChange = (value: MonthPickerValue) => void;
17
-
18
- export type MonthPickerGetMonthLabel = (
19
- month: number,
20
- year: number,
21
- value: Date
22
- ) => ReactNode;
23
-
24
- export type MonthPickerGetMonthAriaLabel = (
25
- month: number,
26
- year: number,
27
- selected: boolean
28
- ) => string;
29
-
30
- export type MonthPickerGetHeaderLabel = (year: number) => ReactNode;
31
-
32
- export type MonthPickerGetNavigationAriaLabel = (
33
- direction: MonthPickerNavigationDirection,
34
- year: number
35
- ) => string;
36
-
37
- export type MonthPickerClassNames = {
38
- root?: string;
39
- header?: string;
40
- headerControls?: string;
41
- headerControl?: string;
42
- headerPreviousControl?: string;
43
- headerNextControl?: string;
44
- headerLabel?: string;
45
- grid?: string;
46
- month?: string;
47
- monthActive?: string;
48
- yearPicker?: string;
49
- };
50
-
51
- export interface MonthPickerProps
52
- extends Omit<
53
- ComponentPropsWithoutRef<"div">,
54
- "onChange" | "value" | "defaultValue"
55
- > {
56
- /** Active month (controlled). */
57
- value?: MonthPickerValue;
58
-
59
- /** Initial active month (uncontrolled). */
60
- defaultValue?: MonthPickerValue;
61
-
62
- /** Callback called when active month changes. */
63
- onChange?: MonthPickerOnChange;
64
-
65
- /** Minimum available year in month and year views. */
66
- minYear?: number;
67
-
68
- /** Maximum available year in month and year views. */
69
- maxYear?: number;
70
-
71
- /** Grid columns used by the month list @default `3` */
72
- columns?: number;
73
-
74
- /** Year picker years rendered in one page @default `9` */
75
- yearPickerYearsPerPage?: number;
76
-
77
- /** Year picker columns @default `3` */
78
- yearPickerColumns?: number;
79
-
80
- /** Whether all controls are disabled @default `false` */
81
- disabled?: boolean;
82
-
83
- /** Component size @default `md` */
84
- size?: MonthPickerSize;
85
-
86
- /** Border radius @default `default` */
87
- radius?: MonthPickerRadius;
88
-
89
- /** Custom month label renderer. */
90
- getMonthLabel?: MonthPickerGetMonthLabel;
91
-
92
- /** Custom aria-label generator for month buttons. */
93
- getMonthAriaLabel?: MonthPickerGetMonthAriaLabel;
94
-
95
- /** Custom header label renderer for visible year. */
96
- getHeaderLabel?: MonthPickerGetHeaderLabel;
97
-
98
- /** Custom aria-label generator for previous/next controls. */
99
- getNavigationAriaLabel?: MonthPickerGetNavigationAriaLabel;
100
-
101
- /** Used for editing root class name. */
102
- className?: string;
103
-
104
- /** Used for styling different parts of the component. */
105
- classNames?: MonthPickerClassNames;
106
- }
107
-
108
- export interface MonthPickerFactoryPayload extends FactoryPayload {
109
- props: MonthPickerProps;
110
- ref: HTMLDivElement;
111
- compound: {
112
- configure: ReturnType<typeof createComponentConfig<MonthPickerProps>>;
113
- classNames: ReturnType<
114
- typeof createClassNamesConfig<MonthPickerClassNames>
115
- >;
116
- };
117
- }
@@ -1,7 +0,0 @@
1
- export { default as PickerHeader } from "./picker-header";
2
- export type {
3
- PickerHeaderClassNames,
4
- PickerHeaderProps
5
- } from "./picker-header.types";
6
- export { getGridColumns, getPickerSizeStyles } from "./picker.styles";
7
- export type { PickerRadius, PickerSize } from "./picker.types";