@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,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,4 @@
1
+ import type { RefraktorRadius, RefraktorSize } from "@refraktor/core";
2
+
3
+ export type PickerRadius = RefraktorRadius;
4
+ export type PickerSize = RefraktorSize;
@@ -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;