@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.
- package/.turbo/turbo-build.log +4 -0
- package/LICENSE +21 -0
- package/README.md +21 -0
- package/build/components/date-input/date-input.d.ts +4 -0
- package/build/components/date-input/date-input.d.ts.map +1 -0
- package/build/components/date-input/date-input.js +164 -0
- package/build/components/date-input/date-input.types.d.ts +96 -0
- package/build/components/date-input/date-input.types.d.ts.map +1 -0
- package/build/components/date-input/date-input.types.js +1 -0
- package/build/components/date-input/index.d.ts +3 -0
- package/build/components/date-input/index.d.ts.map +1 -0
- package/build/components/date-input/index.js +1 -0
- package/build/components/date-picker/date-picker.d.ts +4 -0
- package/build/components/date-picker/date-picker.d.ts.map +1 -0
- package/build/components/date-picker/date-picker.js +307 -0
- package/build/components/date-picker/date-picker.types.d.ts +86 -0
- package/build/components/date-picker/date-picker.types.d.ts.map +1 -0
- package/build/components/date-picker/date-picker.types.js +1 -0
- package/build/components/date-picker/index.d.ts +3 -0
- package/build/components/date-picker/index.d.ts.map +1 -0
- package/build/components/date-picker/index.js +1 -0
- package/build/components/dates-provider/context.d.ts +4 -0
- package/build/components/dates-provider/context.d.ts.map +1 -0
- package/build/components/dates-provider/context.js +10 -0
- package/build/components/dates-provider/dates-provider.d.ts +7 -0
- package/build/components/dates-provider/dates-provider.d.ts.map +1 -0
- package/build/components/dates-provider/dates-provider.js +65 -0
- package/build/components/dates-provider/index.d.ts +5 -0
- package/build/components/dates-provider/index.d.ts.map +1 -0
- package/build/components/dates-provider/index.js +3 -0
- package/build/components/dates-provider/types.d.ts +26 -0
- package/build/components/dates-provider/types.d.ts.map +1 -0
- package/build/components/dates-provider/types.js +1 -0
- package/build/components/dates-provider/use-dates.d.ts +2 -0
- package/build/components/dates-provider/use-dates.d.ts.map +1 -0
- package/build/components/dates-provider/use-dates.js +4 -0
- package/build/components/index.d.ts +8 -0
- package/build/components/index.d.ts.map +1 -0
- package/build/components/index.js +7 -0
- package/build/components/month-input/index.d.ts +3 -0
- package/build/components/month-input/index.d.ts.map +1 -0
- package/build/components/month-input/index.js +1 -0
- package/build/components/month-input/month-input.d.ts +4 -0
- package/build/components/month-input/month-input.d.ts.map +1 -0
- package/build/components/month-input/month-input.js +161 -0
- package/build/components/month-input/month-input.types.d.ts +85 -0
- package/build/components/month-input/month-input.types.d.ts.map +1 -0
- package/build/components/month-input/month-input.types.js +1 -0
- package/build/components/month-picker/index.d.ts +3 -0
- package/build/components/month-picker/index.d.ts.map +1 -0
- package/build/components/month-picker/index.js +1 -0
- package/build/components/month-picker/month-picker.d.ts +4 -0
- package/build/components/month-picker/month-picker.d.ts.map +1 -0
- package/build/components/month-picker/month-picker.js +229 -0
- package/build/components/month-picker/month-picker.types.d.ts +69 -0
- package/build/components/month-picker/month-picker.types.d.ts.map +1 -0
- package/build/components/month-picker/month-picker.types.js +1 -0
- package/build/components/picker-shared/index.d.ts +5 -0
- package/build/components/picker-shared/index.d.ts.map +1 -0
- package/build/components/picker-shared/index.js +2 -0
- package/build/components/picker-shared/picker-header.d.ts +4 -0
- package/build/components/picker-shared/picker-header.d.ts.map +1 -0
- package/build/components/picker-shared/picker-header.js +27 -0
- package/build/components/picker-shared/picker-header.types.d.ts +36 -0
- package/build/components/picker-shared/picker-header.types.d.ts.map +1 -0
- package/build/components/picker-shared/picker-header.types.js +1 -0
- package/build/components/picker-shared/picker.styles.d.ts +12 -0
- package/build/components/picker-shared/picker.styles.d.ts.map +1 -0
- package/build/components/picker-shared/picker.styles.js +53 -0
- package/build/components/picker-shared/picker.types.d.ts +4 -0
- package/build/components/picker-shared/picker.types.d.ts.map +1 -0
- package/build/components/picker-shared/picker.types.js +1 -0
- package/build/components/year-input/index.d.ts +3 -0
- package/build/components/year-input/index.d.ts.map +1 -0
- package/build/components/year-input/index.js +1 -0
- package/build/components/year-input/year-input.d.ts +4 -0
- package/build/components/year-input/year-input.d.ts.map +1 -0
- package/build/components/year-input/year-input.js +157 -0
- package/build/components/year-input/year-input.types.d.ts +74 -0
- package/build/components/year-input/year-input.types.d.ts.map +1 -0
- package/build/components/year-input/year-input.types.js +1 -0
- package/build/components/year-picker/index.d.ts +3 -0
- package/build/components/year-picker/index.d.ts.map +1 -0
- package/build/components/year-picker/index.js +1 -0
- package/build/components/year-picker/year-picker.d.ts +4 -0
- package/build/components/year-picker/year-picker.d.ts.map +1 -0
- package/build/components/year-picker/year-picker.js +236 -0
- package/build/components/year-picker/year-picker.types.d.ts +70 -0
- package/build/components/year-picker/year-picker.types.d.ts.map +1 -0
- package/build/components/year-picker/year-picker.types.js +1 -0
- package/build/index.d.ts +3 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +2 -0
- package/build/style.css +2 -0
- package/package.json +38 -0
- package/refraktor-dates-0.0.1-alpha.0.tgz +0 -0
- package/src/components/date-input/date-input.tsx +376 -0
- package/src/components/date-input/date-input.types.ts +161 -0
- package/src/components/date-input/index.ts +13 -0
- package/src/components/date-picker/date-picker.tsx +649 -0
- package/src/components/date-picker/date-picker.types.ts +145 -0
- package/src/components/date-picker/index.ts +15 -0
- package/src/components/dates-provider/context.ts +18 -0
- package/src/components/dates-provider/dates-provider.tsx +136 -0
- package/src/components/dates-provider/index.ts +10 -0
- package/src/components/dates-provider/types.ts +33 -0
- package/src/components/dates-provider/use-dates.ts +5 -0
- package/src/components/index.ts +7 -0
- package/src/components/month-input/index.ts +13 -0
- package/src/components/month-input/month-input.tsx +363 -0
- package/src/components/month-input/month-input.types.ts +139 -0
- package/src/components/month-picker/index.ts +14 -0
- package/src/components/month-picker/month-picker.tsx +458 -0
- package/src/components/month-picker/month-picker.types.ts +117 -0
- package/src/components/picker-shared/index.ts +7 -0
- package/src/components/picker-shared/picker-header.tsx +178 -0
- package/src/components/picker-shared/picker-header.types.ts +49 -0
- package/src/components/picker-shared/picker.styles.ts +69 -0
- package/src/components/picker-shared/picker.types.ts +4 -0
- package/src/components/year-input/index.ts +13 -0
- package/src/components/year-input/year-input.tsx +347 -0
- package/src/components/year-input/year-input.types.ts +118 -0
- package/src/components/year-picker/index.ts +15 -0
- package/src/components/year-picker/year-picker.tsx +504 -0
- package/src/components/year-picker/year-picker.types.ts +108 -0
- package/src/index.ts +3 -0
- package/src/style.css +1 -0
- package/tsconfig.json +13 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
FlipOptions,
|
|
3
|
+
InlineOptions,
|
|
4
|
+
Placement,
|
|
5
|
+
ShiftOptions
|
|
6
|
+
} from "@floating-ui/react";
|
|
7
|
+
import {
|
|
8
|
+
createClassNamesConfig,
|
|
9
|
+
createComponentConfig,
|
|
10
|
+
FactoryPayload,
|
|
11
|
+
InputFieldClassNames,
|
|
12
|
+
InputProps,
|
|
13
|
+
RefraktorRadius,
|
|
14
|
+
RefraktorSize,
|
|
15
|
+
TransitionProps
|
|
16
|
+
} from "@refraktor/core";
|
|
17
|
+
|
|
18
|
+
export type YearInputValue = number;
|
|
19
|
+
export type YearInputSize = RefraktorSize;
|
|
20
|
+
export type YearInputRadius = RefraktorRadius;
|
|
21
|
+
export type YearInputOnChange = (value: YearInputValue) => void;
|
|
22
|
+
export type YearInputValueFormat = string;
|
|
23
|
+
|
|
24
|
+
export type YearInputPositioning = {
|
|
25
|
+
/** The placement of the dropdown relative to the input @default `bottom-start` */
|
|
26
|
+
placement?: Placement;
|
|
27
|
+
|
|
28
|
+
/** Offset distance from the input in pixels @default `4` */
|
|
29
|
+
offset?: number;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type YearInputMiddlewares = {
|
|
33
|
+
shift?: boolean | ShiftOptions;
|
|
34
|
+
flip?: boolean | FlipOptions;
|
|
35
|
+
inline?: boolean | InlineOptions;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export type YearInputClassNames = {
|
|
39
|
+
input?: string;
|
|
40
|
+
dropdown?: string;
|
|
41
|
+
yearPicker?: string;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
interface _YearInputProps {
|
|
45
|
+
/** Selected year (controlled). */
|
|
46
|
+
value?: YearInputValue;
|
|
47
|
+
|
|
48
|
+
/** Initial selected year (uncontrolled). */
|
|
49
|
+
defaultValue?: YearInputValue;
|
|
50
|
+
|
|
51
|
+
/** Callback called when selected year changes. */
|
|
52
|
+
onChange?: YearInputOnChange;
|
|
53
|
+
|
|
54
|
+
/** Dropdown open state (controlled). */
|
|
55
|
+
opened?: boolean;
|
|
56
|
+
|
|
57
|
+
/** Initial dropdown open state (uncontrolled). */
|
|
58
|
+
defaultOpened?: boolean;
|
|
59
|
+
|
|
60
|
+
/** Callback called when dropdown open state changes. */
|
|
61
|
+
onOpenedChange?: (opened: boolean) => void;
|
|
62
|
+
|
|
63
|
+
/** Minimum selectable year. */
|
|
64
|
+
minYear?: number;
|
|
65
|
+
|
|
66
|
+
/** Maximum selectable year. */
|
|
67
|
+
maxYear?: number;
|
|
68
|
+
|
|
69
|
+
/** Years rendered in one page @default `9` */
|
|
70
|
+
yearsPerPage?: number;
|
|
71
|
+
|
|
72
|
+
/** Grid columns used by the year list @default `3` */
|
|
73
|
+
columns?: number;
|
|
74
|
+
|
|
75
|
+
/** Dayjs format used to render selected year in the input @default `YYYY` */
|
|
76
|
+
valueFormat?: YearInputValueFormat;
|
|
77
|
+
|
|
78
|
+
/** Positioning settings for the dropdown. */
|
|
79
|
+
positioning?: YearInputPositioning;
|
|
80
|
+
|
|
81
|
+
/** Floating middleware settings. */
|
|
82
|
+
middlewares?: YearInputMiddlewares;
|
|
83
|
+
|
|
84
|
+
/** Whether to render dropdown in a portal @default `true` */
|
|
85
|
+
withinPortal?: boolean;
|
|
86
|
+
|
|
87
|
+
/** Whether to close on click outside @default `true` */
|
|
88
|
+
closeOnClickOutside?: boolean;
|
|
89
|
+
|
|
90
|
+
/** Whether to close on Escape key @default `true` */
|
|
91
|
+
closeOnEscape?: boolean;
|
|
92
|
+
|
|
93
|
+
/** Transition props for dropdown, uses Transition internally */
|
|
94
|
+
transitionProps?: Omit<TransitionProps, "children" | "mounted">;
|
|
95
|
+
|
|
96
|
+
/** Used for styling the core Input field parts. */
|
|
97
|
+
inputClassNames?: InputFieldClassNames;
|
|
98
|
+
|
|
99
|
+
/** Used for styling YearInput parts. */
|
|
100
|
+
classNames?: YearInputClassNames;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export type YearInputProps = _YearInputProps &
|
|
104
|
+
Omit<
|
|
105
|
+
InputProps,
|
|
106
|
+
"value" | "defaultValue" | "onChange" | "readOnly" | "classNames"
|
|
107
|
+
>;
|
|
108
|
+
|
|
109
|
+
export interface YearInputFactoryPayload extends FactoryPayload {
|
|
110
|
+
props: YearInputProps;
|
|
111
|
+
ref: HTMLInputElement;
|
|
112
|
+
compound: {
|
|
113
|
+
configure: ReturnType<typeof createComponentConfig<YearInputProps>>;
|
|
114
|
+
classNames: ReturnType<
|
|
115
|
+
typeof createClassNamesConfig<YearInputClassNames>
|
|
116
|
+
>;
|
|
117
|
+
};
|
|
118
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export { default as YearPicker } from "./year-picker";
|
|
2
|
+
export type {
|
|
3
|
+
YearPickerClassNames,
|
|
4
|
+
YearPickerGetHeaderLabel,
|
|
5
|
+
YearPickerGetNavigationAriaLabel,
|
|
6
|
+
YearPickerGetYearAriaLabel,
|
|
7
|
+
YearPickerGetYearLabel,
|
|
8
|
+
YearPickerNavigationDirection,
|
|
9
|
+
YearPickerOnChange,
|
|
10
|
+
YearPickerProps,
|
|
11
|
+
YearPickerRadius,
|
|
12
|
+
YearPickerRange,
|
|
13
|
+
YearPickerSize,
|
|
14
|
+
YearPickerValue
|
|
15
|
+
} from "./year-picker.types";
|
|
@@ -0,0 +1,504 @@
|
|
|
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 {
|
|
17
|
+
YearPickerClassNames,
|
|
18
|
+
YearPickerFactoryPayload,
|
|
19
|
+
YearPickerNavigationDirection,
|
|
20
|
+
YearPickerProps,
|
|
21
|
+
YearPickerRange
|
|
22
|
+
} from "./year-picker.types";
|
|
23
|
+
|
|
24
|
+
const DEFAULT_YEARS_PER_PAGE = 9;
|
|
25
|
+
const DEFAULT_COLUMNS = 3;
|
|
26
|
+
|
|
27
|
+
const defaultProps = {
|
|
28
|
+
yearsPerPage: DEFAULT_YEARS_PER_PAGE,
|
|
29
|
+
columns: DEFAULT_COLUMNS,
|
|
30
|
+
disabled: false,
|
|
31
|
+
size: "md",
|
|
32
|
+
radius: "default"
|
|
33
|
+
} satisfies Partial<YearPickerProps>;
|
|
34
|
+
|
|
35
|
+
type YearBounds = {
|
|
36
|
+
min: number;
|
|
37
|
+
max: number;
|
|
38
|
+
hasMin: boolean;
|
|
39
|
+
hasMax: boolean;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const toSafeInteger = (value: number | undefined, fallback: number) => {
|
|
43
|
+
if (!Number.isFinite(value)) {
|
|
44
|
+
return fallback;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return Math.trunc(value as number);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const clamp = (value: number, min: number, max: number) =>
|
|
51
|
+
Math.min(max, Math.max(min, value));
|
|
52
|
+
|
|
53
|
+
const getBounds = (minYear?: number, maxYear?: number): YearBounds => {
|
|
54
|
+
const hasMin = Number.isFinite(minYear);
|
|
55
|
+
const hasMax = Number.isFinite(maxYear);
|
|
56
|
+
|
|
57
|
+
const min = hasMin
|
|
58
|
+
? Math.trunc(minYear as number)
|
|
59
|
+
: Number.MIN_SAFE_INTEGER;
|
|
60
|
+
const max = hasMax
|
|
61
|
+
? Math.trunc(maxYear as number)
|
|
62
|
+
: Number.MAX_SAFE_INTEGER;
|
|
63
|
+
|
|
64
|
+
if (min <= max) {
|
|
65
|
+
return { min, max, hasMin, hasMax };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
min: max,
|
|
70
|
+
max: min,
|
|
71
|
+
hasMin,
|
|
72
|
+
hasMax
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const getCenteredRangeStart = (year: number, yearsPerPage: number) =>
|
|
77
|
+
year - Math.floor(yearsPerPage / 2);
|
|
78
|
+
|
|
79
|
+
const clampRangeStart = (
|
|
80
|
+
rangeStart: number,
|
|
81
|
+
bounds: YearBounds,
|
|
82
|
+
yearsPerPage: number
|
|
83
|
+
) => {
|
|
84
|
+
const totalVisibleYears = bounds.max - bounds.min + 1;
|
|
85
|
+
|
|
86
|
+
if (totalVisibleYears <= yearsPerPage) {
|
|
87
|
+
return bounds.min;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const maxStart = bounds.max - yearsPerPage + 1;
|
|
91
|
+
|
|
92
|
+
return clamp(rangeStart, bounds.min, maxStart);
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const getRangeEnd = (
|
|
96
|
+
rangeStart: number,
|
|
97
|
+
yearsPerPage: number,
|
|
98
|
+
bounds: YearBounds
|
|
99
|
+
) => Math.min(rangeStart + yearsPerPage - 1, bounds.max);
|
|
100
|
+
|
|
101
|
+
const ensureRangeContainsYear = (
|
|
102
|
+
year: number,
|
|
103
|
+
rangeStart: number,
|
|
104
|
+
bounds: YearBounds,
|
|
105
|
+
yearsPerPage: number
|
|
106
|
+
) => {
|
|
107
|
+
const normalizedRangeStart = clampRangeStart(
|
|
108
|
+
rangeStart,
|
|
109
|
+
bounds,
|
|
110
|
+
yearsPerPage
|
|
111
|
+
);
|
|
112
|
+
const rangeEnd = getRangeEnd(normalizedRangeStart, yearsPerPage, bounds);
|
|
113
|
+
|
|
114
|
+
if (year >= normalizedRangeStart && year <= rangeEnd) {
|
|
115
|
+
return normalizedRangeStart;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return clampRangeStart(
|
|
119
|
+
getCenteredRangeStart(year, yearsPerPage),
|
|
120
|
+
bounds,
|
|
121
|
+
yearsPerPage
|
|
122
|
+
);
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const getVisibleYears = (
|
|
126
|
+
rangeStart: number,
|
|
127
|
+
bounds: YearBounds,
|
|
128
|
+
yearsPerPage: number
|
|
129
|
+
) => {
|
|
130
|
+
const years: number[] = [];
|
|
131
|
+
|
|
132
|
+
for (let index = 0; index < yearsPerPage; index += 1) {
|
|
133
|
+
const year = rangeStart + index;
|
|
134
|
+
|
|
135
|
+
if (year < bounds.min || year > bounds.max) {
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
years.push(year);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return years;
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const YearPicker = factory<YearPickerFactoryPayload>((_props, ref) => {
|
|
146
|
+
const { cx, getRadius } = useTheme();
|
|
147
|
+
const {
|
|
148
|
+
id,
|
|
149
|
+
value,
|
|
150
|
+
defaultValue,
|
|
151
|
+
onChange,
|
|
152
|
+
minYear,
|
|
153
|
+
maxYear,
|
|
154
|
+
yearsPerPage,
|
|
155
|
+
columns,
|
|
156
|
+
disabled,
|
|
157
|
+
size,
|
|
158
|
+
radius,
|
|
159
|
+
getYearLabel,
|
|
160
|
+
getYearAriaLabel,
|
|
161
|
+
getHeaderLabel,
|
|
162
|
+
getNavigationAriaLabel,
|
|
163
|
+
className,
|
|
164
|
+
classNames,
|
|
165
|
+
...props
|
|
166
|
+
} = useProps("YearPicker", defaultProps, _props);
|
|
167
|
+
const classes = useClassNames("YearPicker", classNames);
|
|
168
|
+
|
|
169
|
+
const _id = useId(id);
|
|
170
|
+
|
|
171
|
+
const currentYear = new Date().getFullYear();
|
|
172
|
+
const bounds = useMemo(
|
|
173
|
+
() => getBounds(minYear, maxYear),
|
|
174
|
+
[minYear, maxYear]
|
|
175
|
+
);
|
|
176
|
+
const safeYearsPerPage = Math.max(
|
|
177
|
+
1,
|
|
178
|
+
toSafeInteger(yearsPerPage, DEFAULT_YEARS_PER_PAGE)
|
|
179
|
+
);
|
|
180
|
+
const safeColumns = clamp(
|
|
181
|
+
toSafeInteger(columns, DEFAULT_COLUMNS),
|
|
182
|
+
1,
|
|
183
|
+
Math.min(6, safeYearsPerPage)
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
const [selectedYearState, setSelectedYear, isControlled] = useUncontrolled<
|
|
187
|
+
number | undefined
|
|
188
|
+
>({
|
|
189
|
+
value,
|
|
190
|
+
defaultValue,
|
|
191
|
+
finalValue: undefined,
|
|
192
|
+
onChange: (nextYear) => {
|
|
193
|
+
if (nextYear !== undefined) {
|
|
194
|
+
onChange?.(nextYear);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
const initialSelectedYear = clamp(
|
|
200
|
+
toSafeInteger(selectedYearState, currentYear),
|
|
201
|
+
bounds.min,
|
|
202
|
+
bounds.max
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
const [rangeStart, setRangeStart] = useState(() =>
|
|
206
|
+
clampRangeStart(
|
|
207
|
+
getCenteredRangeStart(initialSelectedYear, safeYearsPerPage),
|
|
208
|
+
bounds,
|
|
209
|
+
safeYearsPerPage
|
|
210
|
+
)
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
const selectedYear =
|
|
214
|
+
selectedYearState === undefined
|
|
215
|
+
? undefined
|
|
216
|
+
: clamp(
|
|
217
|
+
toSafeInteger(selectedYearState, currentYear),
|
|
218
|
+
bounds.min,
|
|
219
|
+
bounds.max
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
const rangeAnchorYear =
|
|
223
|
+
selectedYear ?? clamp(currentYear, bounds.min, bounds.max);
|
|
224
|
+
|
|
225
|
+
useEffect(() => {
|
|
226
|
+
if (isControlled) {
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (selectedYearState === undefined || selectedYear === undefined) {
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (selectedYearState !== selectedYear) {
|
|
235
|
+
setSelectedYear(selectedYear);
|
|
236
|
+
}
|
|
237
|
+
}, [
|
|
238
|
+
bounds.max,
|
|
239
|
+
bounds.min,
|
|
240
|
+
isControlled,
|
|
241
|
+
selectedYear,
|
|
242
|
+
selectedYearState,
|
|
243
|
+
setSelectedYear
|
|
244
|
+
]);
|
|
245
|
+
|
|
246
|
+
useEffect(() => {
|
|
247
|
+
setRangeStart((previousRangeStart) =>
|
|
248
|
+
ensureRangeContainsYear(
|
|
249
|
+
rangeAnchorYear,
|
|
250
|
+
previousRangeStart,
|
|
251
|
+
bounds,
|
|
252
|
+
safeYearsPerPage
|
|
253
|
+
)
|
|
254
|
+
);
|
|
255
|
+
}, [bounds, rangeAnchorYear, safeYearsPerPage]);
|
|
256
|
+
|
|
257
|
+
const visibleYears = useMemo(
|
|
258
|
+
() => getVisibleYears(rangeStart, bounds, safeYearsPerPage),
|
|
259
|
+
[bounds, rangeStart, safeYearsPerPage]
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
const visibleRange: YearPickerRange = {
|
|
263
|
+
start: visibleYears[0] ?? rangeStart,
|
|
264
|
+
end:
|
|
265
|
+
visibleYears[visibleYears.length - 1] ??
|
|
266
|
+
getRangeEnd(rangeStart, safeYearsPerPage, bounds)
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
const sizeStyles = getPickerSizeStyles(size);
|
|
270
|
+
|
|
271
|
+
const canGoPrevious =
|
|
272
|
+
!disabled && (!bounds.hasMin || rangeStart > bounds.min);
|
|
273
|
+
const canGoNext =
|
|
274
|
+
!disabled &&
|
|
275
|
+
(!bounds.hasMax ||
|
|
276
|
+
getRangeEnd(rangeStart, safeYearsPerPage, bounds) < bounds.max);
|
|
277
|
+
|
|
278
|
+
const resolveNavigationLabel = (
|
|
279
|
+
direction: YearPickerNavigationDirection
|
|
280
|
+
) => {
|
|
281
|
+
if (getNavigationAriaLabel) {
|
|
282
|
+
return getNavigationAriaLabel(direction, visibleRange);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return direction === "previous"
|
|
286
|
+
? "Show previous years"
|
|
287
|
+
: "Show next years";
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
const resolveYearAriaLabel = (year: number, isSelected: boolean) => {
|
|
291
|
+
if (getYearAriaLabel) {
|
|
292
|
+
return getYearAriaLabel(year, isSelected);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return isSelected ? `Year ${year}, selected` : `Choose year ${year}`;
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
const setYear = (nextYear: number) => {
|
|
299
|
+
if (disabled) {
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const normalizedYear = clamp(nextYear, bounds.min, bounds.max);
|
|
304
|
+
|
|
305
|
+
if (normalizedYear === selectedYear) {
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
setSelectedYear(normalizedYear);
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
const shiftRange = (direction: -1 | 1) => {
|
|
313
|
+
setRangeStart((previousRangeStart) =>
|
|
314
|
+
clampRangeStart(
|
|
315
|
+
previousRangeStart + direction * safeYearsPerPage,
|
|
316
|
+
bounds,
|
|
317
|
+
safeYearsPerPage
|
|
318
|
+
)
|
|
319
|
+
);
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
const handlePrevious = () => {
|
|
323
|
+
if (!canGoPrevious) {
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
shiftRange(-1);
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
const handleNext = () => {
|
|
331
|
+
if (!canGoNext) {
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
shiftRange(1);
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
const handleYearSelect = (year: number) => {
|
|
339
|
+
setRangeStart((previousRangeStart) =>
|
|
340
|
+
ensureRangeContainsYear(
|
|
341
|
+
year,
|
|
342
|
+
previousRangeStart,
|
|
343
|
+
bounds,
|
|
344
|
+
safeYearsPerPage
|
|
345
|
+
)
|
|
346
|
+
);
|
|
347
|
+
setYear(year);
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
const handleGridKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {
|
|
351
|
+
if (disabled) {
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const keyboardBaseYear =
|
|
356
|
+
selectedYear ?? visibleYears[0] ?? rangeAnchorYear;
|
|
357
|
+
|
|
358
|
+
const keyboardSteps: Record<string, number> = {
|
|
359
|
+
ArrowLeft: -1,
|
|
360
|
+
ArrowRight: 1,
|
|
361
|
+
ArrowUp: -safeColumns,
|
|
362
|
+
ArrowDown: safeColumns
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
const step = keyboardSteps[event.key];
|
|
366
|
+
|
|
367
|
+
if (step !== undefined) {
|
|
368
|
+
event.preventDefault();
|
|
369
|
+
const nextYear = clamp(
|
|
370
|
+
keyboardBaseYear + step,
|
|
371
|
+
bounds.min,
|
|
372
|
+
bounds.max
|
|
373
|
+
);
|
|
374
|
+
handleYearSelect(nextYear);
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
if (event.key === "Home") {
|
|
379
|
+
event.preventDefault();
|
|
380
|
+
|
|
381
|
+
if (visibleYears.length > 0) {
|
|
382
|
+
handleYearSelect(visibleYears[0]);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (event.key === "End") {
|
|
389
|
+
event.preventDefault();
|
|
390
|
+
|
|
391
|
+
if (visibleYears.length > 0) {
|
|
392
|
+
handleYearSelect(visibleYears[visibleYears.length - 1]);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (event.key === "PageUp") {
|
|
399
|
+
event.preventDefault();
|
|
400
|
+
handleYearSelect(keyboardBaseYear - safeYearsPerPage);
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
if (event.key === "PageDown") {
|
|
405
|
+
event.preventDefault();
|
|
406
|
+
handleYearSelect(keyboardBaseYear + safeYearsPerPage);
|
|
407
|
+
}
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
const hasVisibleSelection =
|
|
411
|
+
selectedYear !== undefined && visibleYears.includes(selectedYear);
|
|
412
|
+
|
|
413
|
+
return (
|
|
414
|
+
<div
|
|
415
|
+
ref={ref}
|
|
416
|
+
id={_id}
|
|
417
|
+
className={cx(
|
|
418
|
+
"inline-flex w-full flex-col gap-2 bg-[var(--refraktor-bg)] p-2",
|
|
419
|
+
getRadius(radius),
|
|
420
|
+
classes.root,
|
|
421
|
+
className
|
|
422
|
+
)}
|
|
423
|
+
{...props}
|
|
424
|
+
>
|
|
425
|
+
<PickerHeader
|
|
426
|
+
label={
|
|
427
|
+
getHeaderLabel
|
|
428
|
+
? getHeaderLabel(visibleRange)
|
|
429
|
+
: `${visibleRange.start} - ${visibleRange.end}`
|
|
430
|
+
}
|
|
431
|
+
onPrevious={handlePrevious}
|
|
432
|
+
onNext={handleNext}
|
|
433
|
+
previousDisabled={!canGoPrevious}
|
|
434
|
+
nextDisabled={!canGoNext}
|
|
435
|
+
previousLabel={resolveNavigationLabel("previous")}
|
|
436
|
+
nextLabel={resolveNavigationLabel("next")}
|
|
437
|
+
size={size}
|
|
438
|
+
radius={radius}
|
|
439
|
+
classNames={{
|
|
440
|
+
root: classes.header,
|
|
441
|
+
controls: classes.headerControls,
|
|
442
|
+
control: classes.headerControl,
|
|
443
|
+
previousControl: classes.headerPreviousControl,
|
|
444
|
+
nextControl: classes.headerNextControl,
|
|
445
|
+
label: classes.headerLabel
|
|
446
|
+
}}
|
|
447
|
+
/>
|
|
448
|
+
|
|
449
|
+
<div
|
|
450
|
+
role="grid"
|
|
451
|
+
aria-label="Year picker"
|
|
452
|
+
className={cx(
|
|
453
|
+
"grid",
|
|
454
|
+
getGridColumns(safeColumns),
|
|
455
|
+
sizeStyles.gridGap,
|
|
456
|
+
classes.grid
|
|
457
|
+
)}
|
|
458
|
+
onKeyDown={handleGridKeyDown}
|
|
459
|
+
>
|
|
460
|
+
{visibleYears.map((year, index) => {
|
|
461
|
+
const isSelected = year === selectedYear;
|
|
462
|
+
const tabIndex =
|
|
463
|
+
isSelected || (!hasVisibleSelection && index === 0)
|
|
464
|
+
? 0
|
|
465
|
+
: -1;
|
|
466
|
+
|
|
467
|
+
return (
|
|
468
|
+
<button
|
|
469
|
+
key={year}
|
|
470
|
+
type="button"
|
|
471
|
+
role="gridcell"
|
|
472
|
+
aria-selected={isSelected}
|
|
473
|
+
aria-label={resolveYearAriaLabel(year, isSelected)}
|
|
474
|
+
tabIndex={tabIndex}
|
|
475
|
+
data-active={isSelected}
|
|
476
|
+
data-disabled={disabled}
|
|
477
|
+
disabled={disabled}
|
|
478
|
+
className={cx(
|
|
479
|
+
"inline-flex items-center justify-center font-medium text-[var(--refraktor-text)] transition-colors",
|
|
480
|
+
isSelected
|
|
481
|
+
? "bg-[var(--refraktor-primary)] text-[var(--refraktor-primary-text)]"
|
|
482
|
+
: "hover:bg-[var(--refraktor-bg-hover)]",
|
|
483
|
+
disabled &&
|
|
484
|
+
"pointer-events-none cursor-not-allowed opacity-50 data-[disabled=true]:pointer-events-none",
|
|
485
|
+
sizeStyles.cell,
|
|
486
|
+
getRadius(radius),
|
|
487
|
+
classes.year
|
|
488
|
+
)}
|
|
489
|
+
onClick={() => handleYearSelect(year)}
|
|
490
|
+
>
|
|
491
|
+
{getYearLabel ? getYearLabel(year) : year}
|
|
492
|
+
</button>
|
|
493
|
+
);
|
|
494
|
+
})}
|
|
495
|
+
</div>
|
|
496
|
+
</div>
|
|
497
|
+
);
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
YearPicker.displayName = "@refraktor/dates/YearPicker";
|
|
501
|
+
YearPicker.configure = createComponentConfig<YearPickerProps>();
|
|
502
|
+
YearPicker.classNames = createClassNamesConfig<YearPickerClassNames>();
|
|
503
|
+
|
|
504
|
+
export default YearPicker;
|