@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,178 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createClassNamesConfig,
|
|
3
|
+
createComponentConfig,
|
|
4
|
+
factory,
|
|
5
|
+
useTheme,
|
|
6
|
+
useClassNames,
|
|
7
|
+
useProps
|
|
8
|
+
} from "@refraktor/core";
|
|
9
|
+
import { getPickerSizeStyles } from "./picker.styles";
|
|
10
|
+
import {
|
|
11
|
+
PickerHeaderClassNames,
|
|
12
|
+
PickerHeaderFactoryPayload,
|
|
13
|
+
PickerHeaderProps
|
|
14
|
+
} from "./picker-header.types";
|
|
15
|
+
|
|
16
|
+
const defaultProps = {
|
|
17
|
+
previousDisabled: false,
|
|
18
|
+
nextDisabled: false,
|
|
19
|
+
previousLabel: "Show previous period",
|
|
20
|
+
nextLabel: "Show next period",
|
|
21
|
+
size: "md",
|
|
22
|
+
radius: "default"
|
|
23
|
+
} satisfies Partial<PickerHeaderProps>;
|
|
24
|
+
|
|
25
|
+
const ChevronIcon = ({
|
|
26
|
+
direction,
|
|
27
|
+
size
|
|
28
|
+
}: {
|
|
29
|
+
direction: "left" | "right";
|
|
30
|
+
size: number;
|
|
31
|
+
}) => (
|
|
32
|
+
<svg
|
|
33
|
+
aria-hidden="true"
|
|
34
|
+
viewBox="0 0 16 16"
|
|
35
|
+
fill="none"
|
|
36
|
+
width={size}
|
|
37
|
+
height={size}
|
|
38
|
+
className={direction === "right" ? "rotate-180" : undefined}
|
|
39
|
+
>
|
|
40
|
+
<path
|
|
41
|
+
d="M9.5 3.5L5 8l4.5 4.5"
|
|
42
|
+
stroke="currentColor"
|
|
43
|
+
strokeWidth="1.6"
|
|
44
|
+
strokeLinecap="round"
|
|
45
|
+
strokeLinejoin="round"
|
|
46
|
+
/>
|
|
47
|
+
</svg>
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
const PickerHeader = factory<PickerHeaderFactoryPayload>((_props, ref) => {
|
|
51
|
+
const { cx, getRadius } = useTheme();
|
|
52
|
+
const {
|
|
53
|
+
label,
|
|
54
|
+
onPrevious,
|
|
55
|
+
onNext,
|
|
56
|
+
onLabelClick,
|
|
57
|
+
previousDisabled,
|
|
58
|
+
nextDisabled,
|
|
59
|
+
previousLabel,
|
|
60
|
+
nextLabel,
|
|
61
|
+
size,
|
|
62
|
+
radius,
|
|
63
|
+
className,
|
|
64
|
+
classNames,
|
|
65
|
+
...props
|
|
66
|
+
} = useProps("PickerHeader", defaultProps, _props);
|
|
67
|
+
const classes = useClassNames("PickerHeader", classNames);
|
|
68
|
+
const sizeStyles = getPickerSizeStyles(size);
|
|
69
|
+
const radiusStyles = getRadius(radius);
|
|
70
|
+
|
|
71
|
+
const isPreviousDisabled = previousDisabled || !onPrevious;
|
|
72
|
+
const isNextDisabled = nextDisabled || !onNext;
|
|
73
|
+
|
|
74
|
+
const getStyles = (part: keyof PickerHeaderClassNames) => classes[part];
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<div
|
|
78
|
+
ref={ref}
|
|
79
|
+
className={cx(
|
|
80
|
+
"flex items-center justify-between gap-2",
|
|
81
|
+
getStyles("root"),
|
|
82
|
+
className
|
|
83
|
+
)}
|
|
84
|
+
{...props}
|
|
85
|
+
>
|
|
86
|
+
<div
|
|
87
|
+
className={cx(
|
|
88
|
+
"inline-flex shrink-0 items-center gap-1",
|
|
89
|
+
getStyles("controls")
|
|
90
|
+
)}
|
|
91
|
+
>
|
|
92
|
+
<button
|
|
93
|
+
type="button"
|
|
94
|
+
aria-label={previousLabel}
|
|
95
|
+
aria-disabled={isPreviousDisabled}
|
|
96
|
+
data-disabled={isPreviousDisabled}
|
|
97
|
+
disabled={isPreviousDisabled}
|
|
98
|
+
onClick={onPrevious}
|
|
99
|
+
className={cx(
|
|
100
|
+
"inline-flex items-center justify-center cursor-pointer",
|
|
101
|
+
"text-[var(--refraktor-text)] transition-colors hover:bg-[var(--refraktor-bg-hover)]",
|
|
102
|
+
"data-[disabled=true]:pointer-events-none data-[disabled=true]:cursor-not-allowed data-[disabled=true]:opacity-50",
|
|
103
|
+
sizeStyles.control,
|
|
104
|
+
radiusStyles,
|
|
105
|
+
getStyles("control"),
|
|
106
|
+
getStyles("previousControl")
|
|
107
|
+
)}
|
|
108
|
+
>
|
|
109
|
+
<ChevronIcon direction="left" size={sizeStyles.iconSize} />
|
|
110
|
+
</button>
|
|
111
|
+
</div>
|
|
112
|
+
|
|
113
|
+
<div
|
|
114
|
+
className={cx(
|
|
115
|
+
"min-w-0 flex-1 truncate text-center font-medium text-[var(--refraktor-text)]",
|
|
116
|
+
sizeStyles.label,
|
|
117
|
+
getStyles("label")
|
|
118
|
+
)}
|
|
119
|
+
>
|
|
120
|
+
{onLabelClick ? (
|
|
121
|
+
<button
|
|
122
|
+
type="button"
|
|
123
|
+
onClick={onLabelClick}
|
|
124
|
+
className={cx(
|
|
125
|
+
"inline-flex w-full cursor-pointer items-center justify-center text-center transition-colors hover:bg-[var(--refraktor-bg-hover)] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--refraktor-primary)]",
|
|
126
|
+
sizeStyles.labelButton,
|
|
127
|
+
radiusStyles,
|
|
128
|
+
getStyles("labelButton")
|
|
129
|
+
)}
|
|
130
|
+
>
|
|
131
|
+
<span
|
|
132
|
+
className={cx("truncate", getStyles("labelText"))}
|
|
133
|
+
>
|
|
134
|
+
{label}
|
|
135
|
+
</span>
|
|
136
|
+
</button>
|
|
137
|
+
) : (
|
|
138
|
+
<span className={cx("truncate", getStyles("labelText"))}>
|
|
139
|
+
{label}
|
|
140
|
+
</span>
|
|
141
|
+
)}
|
|
142
|
+
</div>
|
|
143
|
+
|
|
144
|
+
<div
|
|
145
|
+
className={cx(
|
|
146
|
+
"inline-flex shrink-0 items-center gap-1",
|
|
147
|
+
getStyles("controls")
|
|
148
|
+
)}
|
|
149
|
+
>
|
|
150
|
+
<button
|
|
151
|
+
type="button"
|
|
152
|
+
aria-label={nextLabel}
|
|
153
|
+
aria-disabled={isNextDisabled}
|
|
154
|
+
data-disabled={isNextDisabled}
|
|
155
|
+
disabled={isNextDisabled}
|
|
156
|
+
onClick={onNext}
|
|
157
|
+
className={cx(
|
|
158
|
+
"inline-flex items-center justify-center cursor-pointer",
|
|
159
|
+
"text-[var(--refraktor-text)] transition-colors hover:bg-[var(--refraktor-bg-hover)]",
|
|
160
|
+
"data-[disabled=true]:pointer-events-none data-[disabled=true]:cursor-not-allowed data-[disabled=true]:opacity-50",
|
|
161
|
+
sizeStyles.control,
|
|
162
|
+
radiusStyles,
|
|
163
|
+
getStyles("control"),
|
|
164
|
+
getStyles("nextControl")
|
|
165
|
+
)}
|
|
166
|
+
>
|
|
167
|
+
<ChevronIcon direction="right" size={sizeStyles.iconSize} />
|
|
168
|
+
</button>
|
|
169
|
+
</div>
|
|
170
|
+
</div>
|
|
171
|
+
);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
PickerHeader.displayName = "@refraktor/dates/PickerHeader";
|
|
175
|
+
PickerHeader.configure = createComponentConfig<PickerHeaderProps>();
|
|
176
|
+
PickerHeader.classNames = createClassNamesConfig<PickerHeaderClassNames>();
|
|
177
|
+
|
|
178
|
+
export default PickerHeader;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ComponentPropsWithoutRef,
|
|
3
|
+
MouseEventHandler,
|
|
4
|
+
ReactNode
|
|
5
|
+
} from "react";
|
|
6
|
+
import {
|
|
7
|
+
createClassNamesConfig,
|
|
8
|
+
createComponentConfig,
|
|
9
|
+
FactoryPayload
|
|
10
|
+
} from "@refraktor/core";
|
|
11
|
+
import { PickerRadius, PickerSize } from "./picker.types";
|
|
12
|
+
|
|
13
|
+
export type PickerHeaderClassNames = {
|
|
14
|
+
root?: string;
|
|
15
|
+
controls?: string;
|
|
16
|
+
control?: string;
|
|
17
|
+
previousControl?: string;
|
|
18
|
+
nextControl?: string;
|
|
19
|
+
label?: string;
|
|
20
|
+
labelButton?: string;
|
|
21
|
+
labelText?: string;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export interface PickerHeaderProps
|
|
25
|
+
extends Omit<ComponentPropsWithoutRef<"div">, "children"> {
|
|
26
|
+
label: ReactNode;
|
|
27
|
+
onPrevious?: () => void;
|
|
28
|
+
onNext?: () => void;
|
|
29
|
+
onLabelClick?: MouseEventHandler<HTMLButtonElement>;
|
|
30
|
+
previousDisabled?: boolean;
|
|
31
|
+
nextDisabled?: boolean;
|
|
32
|
+
previousLabel?: string;
|
|
33
|
+
nextLabel?: string;
|
|
34
|
+
size?: PickerSize;
|
|
35
|
+
radius?: PickerRadius;
|
|
36
|
+
className?: string;
|
|
37
|
+
classNames?: PickerHeaderClassNames;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface PickerHeaderFactoryPayload extends FactoryPayload {
|
|
41
|
+
props: PickerHeaderProps;
|
|
42
|
+
ref: HTMLDivElement;
|
|
43
|
+
compound: {
|
|
44
|
+
configure: ReturnType<typeof createComponentConfig<PickerHeaderProps>>;
|
|
45
|
+
classNames: ReturnType<
|
|
46
|
+
typeof createClassNamesConfig<PickerHeaderClassNames>
|
|
47
|
+
>;
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { PickerSize } from "./picker.types";
|
|
2
|
+
|
|
3
|
+
export type PickerSizeStyles = {
|
|
4
|
+
control: string;
|
|
5
|
+
label: string;
|
|
6
|
+
labelButton: string;
|
|
7
|
+
cell: string;
|
|
8
|
+
gridGap: string;
|
|
9
|
+
iconSize: number;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const pickerSizes: Record<PickerSize, PickerSizeStyles> = {
|
|
13
|
+
xs: {
|
|
14
|
+
control: "size-6 text-xs",
|
|
15
|
+
label: "text-xs",
|
|
16
|
+
labelButton: "py-0.5 px-1",
|
|
17
|
+
cell: "h-7 px-1 text-xs",
|
|
18
|
+
gridGap: "gap-1",
|
|
19
|
+
iconSize: 12
|
|
20
|
+
},
|
|
21
|
+
sm: {
|
|
22
|
+
control: "size-7 text-xs",
|
|
23
|
+
label: "text-xs",
|
|
24
|
+
labelButton: "py-0.5 px-1",
|
|
25
|
+
cell: "h-8 px-1.5 text-xs",
|
|
26
|
+
gridGap: "gap-1",
|
|
27
|
+
iconSize: 14
|
|
28
|
+
},
|
|
29
|
+
md: {
|
|
30
|
+
control: "size-8 text-sm",
|
|
31
|
+
label: "text-sm",
|
|
32
|
+
labelButton: "py-1 px-1.5",
|
|
33
|
+
cell: "h-9 px-2 text-sm",
|
|
34
|
+
gridGap: "gap-1.5",
|
|
35
|
+
iconSize: 16
|
|
36
|
+
},
|
|
37
|
+
lg: {
|
|
38
|
+
control: "size-9 text-base",
|
|
39
|
+
label: "text-base",
|
|
40
|
+
labelButton: "py-1 px-2",
|
|
41
|
+
cell: "h-10 px-2.5 text-base",
|
|
42
|
+
gridGap: "gap-2",
|
|
43
|
+
iconSize: 18
|
|
44
|
+
},
|
|
45
|
+
xl: {
|
|
46
|
+
control: "size-10 text-lg",
|
|
47
|
+
label: "text-lg",
|
|
48
|
+
labelButton: "py-1.5 px-2.5",
|
|
49
|
+
cell: "h-11 px-3 text-lg",
|
|
50
|
+
gridGap: "gap-2",
|
|
51
|
+
iconSize: 20
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const gridColumns: Record<number, string> = {
|
|
56
|
+
1: "grid-cols-1",
|
|
57
|
+
2: "grid-cols-2",
|
|
58
|
+
3: "grid-cols-3",
|
|
59
|
+
4: "grid-cols-4",
|
|
60
|
+
5: "grid-cols-5",
|
|
61
|
+
6: "grid-cols-6",
|
|
62
|
+
7: "grid-cols-7"
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export const getPickerSizeStyles = (size: PickerSize = "md") =>
|
|
66
|
+
pickerSizes[size] ?? pickerSizes.md;
|
|
67
|
+
|
|
68
|
+
export const getGridColumns = (columns: number) =>
|
|
69
|
+
gridColumns[columns] ?? gridColumns[4];
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export { default as YearInput } from "./year-input";
|
|
2
|
+
export type {
|
|
3
|
+
YearInputClassNames,
|
|
4
|
+
YearInputFactoryPayload,
|
|
5
|
+
YearInputMiddlewares,
|
|
6
|
+
YearInputOnChange,
|
|
7
|
+
YearInputPositioning,
|
|
8
|
+
YearInputProps,
|
|
9
|
+
YearInputRadius,
|
|
10
|
+
YearInputSize,
|
|
11
|
+
YearInputValue,
|
|
12
|
+
YearInputValueFormat
|
|
13
|
+
} from "./year-input.types";
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
import { useId, useUncontrolled } from "@refraktor/utils";
|
|
2
|
+
import { useCallback, useMemo } from "react";
|
|
3
|
+
import {
|
|
4
|
+
createClassNamesConfig,
|
|
5
|
+
createComponentConfig,
|
|
6
|
+
factory,
|
|
7
|
+
Input,
|
|
8
|
+
Transition,
|
|
9
|
+
useClassNames,
|
|
10
|
+
useProps,
|
|
11
|
+
useTheme
|
|
12
|
+
} from "@refraktor/core";
|
|
13
|
+
import {
|
|
14
|
+
autoUpdate,
|
|
15
|
+
flip,
|
|
16
|
+
FloatingFocusManager,
|
|
17
|
+
FloatingPortal,
|
|
18
|
+
inline,
|
|
19
|
+
Middleware,
|
|
20
|
+
offset,
|
|
21
|
+
shift,
|
|
22
|
+
useDismiss,
|
|
23
|
+
useFloating,
|
|
24
|
+
useFocus,
|
|
25
|
+
useInteractions,
|
|
26
|
+
useRole
|
|
27
|
+
} from "@floating-ui/react";
|
|
28
|
+
import { useDates } from "../dates-provider";
|
|
29
|
+
import { YearPicker } from "../year-picker";
|
|
30
|
+
import {
|
|
31
|
+
YearInputClassNames,
|
|
32
|
+
YearInputFactoryPayload,
|
|
33
|
+
YearInputProps
|
|
34
|
+
} from "./year-input.types";
|
|
35
|
+
|
|
36
|
+
const DEFAULT_YEARS_PER_PAGE = 9;
|
|
37
|
+
const DEFAULT_COLUMNS = 3;
|
|
38
|
+
const DEFAULT_VALUE_FORMAT = "YYYY";
|
|
39
|
+
|
|
40
|
+
const defaultProps = {
|
|
41
|
+
yearsPerPage: DEFAULT_YEARS_PER_PAGE,
|
|
42
|
+
columns: DEFAULT_COLUMNS,
|
|
43
|
+
valueFormat: DEFAULT_VALUE_FORMAT,
|
|
44
|
+
disabled: false,
|
|
45
|
+
size: "md",
|
|
46
|
+
radius: "default",
|
|
47
|
+
positioning: {
|
|
48
|
+
placement: "bottom-start",
|
|
49
|
+
offset: 4
|
|
50
|
+
},
|
|
51
|
+
middlewares: {
|
|
52
|
+
flip: true,
|
|
53
|
+
shift: true
|
|
54
|
+
},
|
|
55
|
+
withinPortal: true,
|
|
56
|
+
closeOnClickOutside: true,
|
|
57
|
+
closeOnEscape: true
|
|
58
|
+
} satisfies Partial<YearInputProps>;
|
|
59
|
+
|
|
60
|
+
const toSafeInteger = (value: number | undefined) => {
|
|
61
|
+
if (!Number.isFinite(value)) {
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return Math.trunc(value as number);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const YearInput = factory<YearInputFactoryPayload>((_props, ref) => {
|
|
69
|
+
const { cx, getRadius } = useTheme();
|
|
70
|
+
const { createDate } = useDates();
|
|
71
|
+
const {
|
|
72
|
+
id,
|
|
73
|
+
value,
|
|
74
|
+
defaultValue,
|
|
75
|
+
onChange,
|
|
76
|
+
opened,
|
|
77
|
+
defaultOpened,
|
|
78
|
+
onOpenedChange,
|
|
79
|
+
minYear,
|
|
80
|
+
maxYear,
|
|
81
|
+
yearsPerPage,
|
|
82
|
+
columns,
|
|
83
|
+
valueFormat,
|
|
84
|
+
disabled,
|
|
85
|
+
size,
|
|
86
|
+
radius,
|
|
87
|
+
positioning,
|
|
88
|
+
middlewares,
|
|
89
|
+
withinPortal,
|
|
90
|
+
closeOnClickOutside,
|
|
91
|
+
closeOnEscape,
|
|
92
|
+
transitionProps,
|
|
93
|
+
inputClassNames,
|
|
94
|
+
className,
|
|
95
|
+
classNames,
|
|
96
|
+
onFocus,
|
|
97
|
+
onBlur,
|
|
98
|
+
onClick,
|
|
99
|
+
onKeyDown,
|
|
100
|
+
...inputProps
|
|
101
|
+
} = useProps("YearInput", defaultProps, _props);
|
|
102
|
+
const classes = useClassNames("YearInput", classNames);
|
|
103
|
+
|
|
104
|
+
const _id = useId(id);
|
|
105
|
+
const dropdownId = `${_id}-dropdown`;
|
|
106
|
+
|
|
107
|
+
const [selectedYearState, setSelectedYear] = useUncontrolled<
|
|
108
|
+
number | undefined
|
|
109
|
+
>({
|
|
110
|
+
value,
|
|
111
|
+
defaultValue,
|
|
112
|
+
finalValue: undefined,
|
|
113
|
+
onChange: (nextYear) => {
|
|
114
|
+
if (nextYear !== undefined) {
|
|
115
|
+
onChange?.(nextYear);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const [isOpenState, setIsOpen] = useUncontrolled<boolean>({
|
|
121
|
+
value: opened,
|
|
122
|
+
defaultValue: defaultOpened,
|
|
123
|
+
finalValue: false,
|
|
124
|
+
onChange: onOpenedChange
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
const isOpen = isOpenState && !disabled;
|
|
128
|
+
|
|
129
|
+
const middleware = useMemo(() => {
|
|
130
|
+
const middlewareList: Middleware[] = [];
|
|
131
|
+
|
|
132
|
+
middlewareList.push(offset(positioning?.offset ?? 4));
|
|
133
|
+
|
|
134
|
+
if (middlewares?.flip ?? true) {
|
|
135
|
+
middlewareList.push(
|
|
136
|
+
flip(
|
|
137
|
+
typeof middlewares?.flip === "boolean"
|
|
138
|
+
? undefined
|
|
139
|
+
: middlewares.flip
|
|
140
|
+
)
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (middlewares?.shift ?? true) {
|
|
145
|
+
middlewareList.push(
|
|
146
|
+
shift(
|
|
147
|
+
typeof middlewares?.shift === "boolean"
|
|
148
|
+
? undefined
|
|
149
|
+
: middlewares.shift
|
|
150
|
+
)
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (middlewares?.inline) {
|
|
155
|
+
middlewareList.push(
|
|
156
|
+
inline(
|
|
157
|
+
typeof middlewares.inline === "boolean"
|
|
158
|
+
? undefined
|
|
159
|
+
: middlewares.inline
|
|
160
|
+
)
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return middlewareList;
|
|
165
|
+
}, [middlewares, positioning?.offset]);
|
|
166
|
+
|
|
167
|
+
const handleOpenChange = useCallback(
|
|
168
|
+
(nextOpen: boolean) => {
|
|
169
|
+
if (disabled && nextOpen) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
setIsOpen(nextOpen);
|
|
174
|
+
},
|
|
175
|
+
[disabled, setIsOpen]
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
const floating = useFloating({
|
|
179
|
+
placement: positioning?.placement ?? "bottom-start",
|
|
180
|
+
open: isOpen,
|
|
181
|
+
onOpenChange: handleOpenChange,
|
|
182
|
+
middleware,
|
|
183
|
+
whileElementsMounted: autoUpdate
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
const focus = useFocus(floating.context, {
|
|
187
|
+
enabled: !disabled
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
const dismiss = useDismiss(floating.context, {
|
|
191
|
+
outsidePress: closeOnClickOutside,
|
|
192
|
+
escapeKey: closeOnEscape
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
const role = useRole(floating.context, {
|
|
196
|
+
role: "dialog"
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
const { getReferenceProps, getFloatingProps } = useInteractions([
|
|
200
|
+
focus,
|
|
201
|
+
dismiss,
|
|
202
|
+
role
|
|
203
|
+
]);
|
|
204
|
+
|
|
205
|
+
const setInputRef = useCallback(
|
|
206
|
+
(node: HTMLInputElement | null) => {
|
|
207
|
+
floating.refs.setReference(node);
|
|
208
|
+
|
|
209
|
+
if (typeof ref === "function") {
|
|
210
|
+
ref(node);
|
|
211
|
+
} else if (ref) {
|
|
212
|
+
ref.current = node;
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
[floating.refs, ref]
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
const handleInputKeyDown = useCallback(
|
|
219
|
+
(event: React.KeyboardEvent<HTMLInputElement>) => {
|
|
220
|
+
onKeyDown?.(event);
|
|
221
|
+
|
|
222
|
+
if (event.defaultPrevented || disabled) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (
|
|
227
|
+
event.key === "ArrowDown" ||
|
|
228
|
+
event.key === "Enter" ||
|
|
229
|
+
event.key === " "
|
|
230
|
+
) {
|
|
231
|
+
event.preventDefault();
|
|
232
|
+
setIsOpen(true);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (event.key === "Escape") {
|
|
237
|
+
event.preventDefault();
|
|
238
|
+
setIsOpen(false);
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
[disabled, onKeyDown, setIsOpen]
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
const handleYearChange = useCallback(
|
|
245
|
+
(nextYear: number) => {
|
|
246
|
+
setSelectedYear(nextYear);
|
|
247
|
+
setIsOpen(false);
|
|
248
|
+
},
|
|
249
|
+
[setIsOpen, setSelectedYear]
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
const selectedYear = toSafeInteger(selectedYearState);
|
|
253
|
+
const inputValue =
|
|
254
|
+
selectedYear === undefined
|
|
255
|
+
? ""
|
|
256
|
+
: createDate(new Date(selectedYear, 0, 1)).format(valueFormat);
|
|
257
|
+
|
|
258
|
+
const mergedReferenceProps = getReferenceProps({
|
|
259
|
+
onFocus,
|
|
260
|
+
onBlur,
|
|
261
|
+
onClick,
|
|
262
|
+
onKeyDown: handleInputKeyDown
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
const dropdownContent = (
|
|
266
|
+
<Transition
|
|
267
|
+
transition="fade"
|
|
268
|
+
duration={150}
|
|
269
|
+
mounted={isOpen}
|
|
270
|
+
{...transitionProps}
|
|
271
|
+
>
|
|
272
|
+
<div
|
|
273
|
+
ref={floating.refs.setFloating}
|
|
274
|
+
id={dropdownId}
|
|
275
|
+
style={{
|
|
276
|
+
...floating.floatingStyles
|
|
277
|
+
}}
|
|
278
|
+
className={cx(
|
|
279
|
+
"w-60 z-50 border border-[var(--refraktor-border)] bg-[var(--refraktor-bg)] p-2 text-[var(--refraktor-text)] shadow-md",
|
|
280
|
+
getRadius(radius),
|
|
281
|
+
classes.dropdown
|
|
282
|
+
)}
|
|
283
|
+
{...getFloatingProps()}
|
|
284
|
+
>
|
|
285
|
+
<YearPicker
|
|
286
|
+
value={selectedYear}
|
|
287
|
+
onChange={handleYearChange}
|
|
288
|
+
minYear={minYear}
|
|
289
|
+
maxYear={maxYear}
|
|
290
|
+
yearsPerPage={yearsPerPage}
|
|
291
|
+
columns={columns}
|
|
292
|
+
disabled={disabled}
|
|
293
|
+
size={size}
|
|
294
|
+
radius={radius}
|
|
295
|
+
className={cx("bg-transparent p-0", classes.yearPicker)}
|
|
296
|
+
/>
|
|
297
|
+
</div>
|
|
298
|
+
</Transition>
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
const wrappedContent = isOpen ? (
|
|
302
|
+
<FloatingFocusManager
|
|
303
|
+
context={floating.context}
|
|
304
|
+
modal={false}
|
|
305
|
+
initialFocus={-1}
|
|
306
|
+
returnFocus={false}
|
|
307
|
+
>
|
|
308
|
+
{dropdownContent}
|
|
309
|
+
</FloatingFocusManager>
|
|
310
|
+
) : (
|
|
311
|
+
dropdownContent
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
return (
|
|
315
|
+
<>
|
|
316
|
+
<Input
|
|
317
|
+
ref={setInputRef}
|
|
318
|
+
id={_id}
|
|
319
|
+
readOnly
|
|
320
|
+
value={inputValue}
|
|
321
|
+
disabled={disabled}
|
|
322
|
+
size={size}
|
|
323
|
+
radius={radius}
|
|
324
|
+
role="combobox"
|
|
325
|
+
aria-haspopup="dialog"
|
|
326
|
+
aria-expanded={isOpen}
|
|
327
|
+
aria-controls={isOpen ? dropdownId : undefined}
|
|
328
|
+
className={cx(classes.input, className)}
|
|
329
|
+
classNames={inputClassNames}
|
|
330
|
+
{...inputProps}
|
|
331
|
+
{...(mergedReferenceProps as any)}
|
|
332
|
+
/>
|
|
333
|
+
|
|
334
|
+
{withinPortal ? (
|
|
335
|
+
<FloatingPortal>{wrappedContent}</FloatingPortal>
|
|
336
|
+
) : (
|
|
337
|
+
wrappedContent
|
|
338
|
+
)}
|
|
339
|
+
</>
|
|
340
|
+
);
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
YearInput.displayName = "@refraktor/dates/YearInput";
|
|
344
|
+
YearInput.configure = createComponentConfig<YearInputProps>();
|
|
345
|
+
YearInput.classNames = createClassNamesConfig<YearInputClassNames>();
|
|
346
|
+
|
|
347
|
+
export default YearInput;
|