@refraktor/dates 0.0.2 → 0.0.4
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 +1 -1
- package/build/components/date-input/date-input.d.ts.map +1 -1
- package/build/components/date-input/date-input.js +5 -3
- package/build/components/date-range-picker/date-range-picker.d.ts +4 -0
- package/build/components/date-range-picker/date-range-picker.d.ts.map +1 -0
- package/build/components/date-range-picker/date-range-picker.js +379 -0
- package/build/components/date-range-picker/date-range-picker.types.d.ts +100 -0
- package/build/components/date-range-picker/date-range-picker.types.d.ts.map +1 -0
- package/build/components/date-range-picker/date-range-picker.types.js +1 -0
- package/build/components/date-range-picker/index.d.ts +3 -0
- package/build/components/date-range-picker/index.d.ts.map +1 -0
- package/build/components/date-range-picker/index.js +1 -0
- package/build/components/index.d.ts +2 -0
- package/build/components/index.d.ts.map +1 -1
- package/build/components/index.js +2 -0
- package/build/components/month-input/month-input.d.ts.map +1 -1
- package/build/components/month-input/month-input.js +5 -3
- package/build/components/time-input/index.d.ts +3 -0
- package/build/components/time-input/index.d.ts.map +1 -0
- package/build/components/time-input/index.js +1 -0
- package/build/components/time-input/time-input.d.ts +4 -0
- package/build/components/time-input/time-input.d.ts.map +1 -0
- package/build/components/time-input/time-input.js +18 -0
- package/build/components/time-input/time-input.types.d.ts +21 -0
- package/build/components/time-input/time-input.types.d.ts.map +1 -0
- package/build/components/time-input/time-input.types.js +1 -0
- package/build/components/time-picker/index.d.ts +3 -0
- package/build/components/time-picker/index.d.ts.map +1 -0
- package/build/components/time-picker/index.js +1 -0
- package/build/components/time-picker/time-picker.d.ts +4 -0
- package/build/components/time-picker/time-picker.d.ts.map +1 -0
- package/build/components/time-picker/time-picker.js +553 -0
- package/build/components/time-picker/time-picker.types.d.ts +114 -0
- package/build/components/time-picker/time-picker.types.d.ts.map +1 -0
- package/build/components/time-picker/time-picker.types.js +1 -0
- package/build/components/year-input/year-input.d.ts.map +1 -1
- package/build/components/year-input/year-input.js +5 -3
- package/build/style.css +1 -1
- package/package.json +3 -3
- package/src/components/date-input/date-input.tsx +5 -2
- package/src/components/index.ts +2 -0
- package/src/components/month-input/month-input.tsx +5 -2
- package/src/components/time-input/index.ts +6 -0
- package/src/components/time-input/time-input.tsx +48 -0
- package/src/components/time-input/time-input.types.ts +30 -0
- package/src/components/time-picker/index.ts +10 -0
- package/src/components/time-picker/time-picker.tsx +1088 -0
- package/src/components/time-picker/time-picker.types.ts +166 -0
- package/src/components/year-input/year-input.tsx +5 -2
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { createClassNamesConfig, createComponentConfig, FactoryPayload, InputProps } from "@refraktor/core";
|
|
2
|
+
export type TimeInputClassNames = {
|
|
3
|
+
input?: string;
|
|
4
|
+
};
|
|
5
|
+
interface _TimeInputProps {
|
|
6
|
+
/** Show seconds in the native time input @default `false` */
|
|
7
|
+
withSeconds?: boolean;
|
|
8
|
+
/** Used for styling TimeInput parts */
|
|
9
|
+
classNames?: TimeInputClassNames;
|
|
10
|
+
}
|
|
11
|
+
export type TimeInputProps = _TimeInputProps & Omit<InputProps, "type" | "classNames">;
|
|
12
|
+
export interface TimeInputFactoryPayload extends FactoryPayload {
|
|
13
|
+
props: TimeInputProps;
|
|
14
|
+
ref: HTMLInputElement;
|
|
15
|
+
compound: {
|
|
16
|
+
configure: ReturnType<typeof createComponentConfig<TimeInputProps>>;
|
|
17
|
+
classNames: ReturnType<typeof createClassNamesConfig<TimeInputClassNames>>;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export {};
|
|
21
|
+
//# sourceMappingURL=time-input.types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"time-input.types.d.ts","sourceRoot":"","sources":["../../../src/components/time-input/time-input.types.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,sBAAsB,EACtB,qBAAqB,EACrB,cAAc,EACd,UAAU,EACb,MAAM,iBAAiB,CAAC;AAEzB,MAAM,MAAM,mBAAmB,GAAG;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,UAAU,eAAe;IACrB,6DAA6D;IAC7D,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB,uCAAuC;IACvC,UAAU,CAAC,EAAE,mBAAmB,CAAC;CACpC;AAED,MAAM,MAAM,cAAc,GAAG,eAAe,GACxC,IAAI,CAAC,UAAU,EAAE,MAAM,GAAG,YAAY,CAAC,CAAC;AAE5C,MAAM,WAAW,uBAAwB,SAAQ,cAAc;IAC3D,KAAK,EAAE,cAAc,CAAC;IACtB,GAAG,EAAE,gBAAgB,CAAC;IACtB,QAAQ,EAAE;QACN,SAAS,EAAE,UAAU,CAAC,OAAO,qBAAqB,CAAC,cAAc,CAAC,CAAC,CAAC;QACpE,UAAU,EAAE,UAAU,CAAC,OAAO,sBAAsB,CAAC,mBAAmB,CAAC,CAAC,CAAC;KAC9E,CAAC;CACL"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { default as TimePicker } from "./time-picker";
|
|
2
|
+
export type { TimePickerAmPmLabels, TimePickerClassNames, TimePickerFactoryPayload, TimePickerFormat, TimePickerPopoverProps, TimePickerProps, TimePickerValue } from "./time-picker.types";
|
|
3
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/time-picker/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,eAAe,CAAC;AACtD,YAAY,EACR,oBAAoB,EACpB,oBAAoB,EACpB,wBAAwB,EACxB,gBAAgB,EAChB,sBAAsB,EACtB,eAAe,EACf,eAAe,EAClB,MAAM,qBAAqB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as TimePicker } from "./time-picker";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"time-picker.d.ts","sourceRoot":"","sources":["../../../src/components/time-picker/time-picker.tsx"],"names":[],"mappings":"AAkCA,OAAO,EAEH,wBAAwB,EAI3B,MAAM,qBAAqB,CAAC;AA8K7B,QAAA,MAAM,UAAU,wEAm2Bd,CAAC;AAMH,eAAe,UAAU,CAAC"}
|
|
@@ -0,0 +1,553 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useId, useUncontrolled } from "@refraktor/utils";
|
|
3
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
4
|
+
import { createClassNamesConfig, createComponentConfig, factory, Input, useClassNames, useProps, useTheme } from "@refraktor/core";
|
|
5
|
+
import { autoUpdate, flip, FloatingFocusManager, FloatingPortal, offset, shift, useClick, useDismiss, useFloating, useInteractions, useRole } from "@floating-ui/react";
|
|
6
|
+
import { getPickerSizeStyles } from "../picker-shared";
|
|
7
|
+
const HOURS_IN_DAY = 24;
|
|
8
|
+
const MINUTES_IN_HOUR = 60;
|
|
9
|
+
const SECONDS_IN_MINUTE = 60;
|
|
10
|
+
const PLACEHOLDER = "--";
|
|
11
|
+
const defaultProps = {
|
|
12
|
+
format: "24h",
|
|
13
|
+
withSeconds: false,
|
|
14
|
+
withDropdown: false,
|
|
15
|
+
clearable: false,
|
|
16
|
+
disabled: false,
|
|
17
|
+
readOnly: false,
|
|
18
|
+
variant: "default",
|
|
19
|
+
size: "md",
|
|
20
|
+
radius: "default",
|
|
21
|
+
hoursStep: 1,
|
|
22
|
+
minutesStep: 1,
|
|
23
|
+
secondsStep: 1,
|
|
24
|
+
amPmLabels: { am: "AM", pm: "PM" }
|
|
25
|
+
};
|
|
26
|
+
const pad = (value) => String(value).padStart(2, "0");
|
|
27
|
+
const parseValue = (value, withSeconds) => {
|
|
28
|
+
if (!value || value === "") {
|
|
29
|
+
return { hours: null, minutes: null, seconds: null, amPm: null };
|
|
30
|
+
}
|
|
31
|
+
const segments = value.trim().split(":");
|
|
32
|
+
if (segments.length < 2) {
|
|
33
|
+
return { hours: null, minutes: null, seconds: null, amPm: null };
|
|
34
|
+
}
|
|
35
|
+
const hours = Number.parseInt(segments[0], 10);
|
|
36
|
+
const minutes = Number.parseInt(segments[1], 10);
|
|
37
|
+
const seconds = segments.length >= 3 ? Number.parseInt(segments[2], 10) : 0;
|
|
38
|
+
if (Number.isNaN(hours) ||
|
|
39
|
+
Number.isNaN(minutes) ||
|
|
40
|
+
Number.isNaN(seconds) ||
|
|
41
|
+
hours < 0 ||
|
|
42
|
+
hours >= HOURS_IN_DAY ||
|
|
43
|
+
minutes < 0 ||
|
|
44
|
+
minutes >= MINUTES_IN_HOUR ||
|
|
45
|
+
seconds < 0 ||
|
|
46
|
+
seconds >= SECONDS_IN_MINUTE) {
|
|
47
|
+
return { hours: null, minutes: null, seconds: null, amPm: null };
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
hours,
|
|
51
|
+
minutes,
|
|
52
|
+
seconds: withSeconds ? seconds : null,
|
|
53
|
+
amPm: hours >= 12 ? "PM" : "AM"
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
const formatValue = (parts, withSeconds) => {
|
|
57
|
+
if (parts.hours === null || parts.minutes === null) {
|
|
58
|
+
return "";
|
|
59
|
+
}
|
|
60
|
+
const base = `${pad(parts.hours)}:${pad(parts.minutes)}`;
|
|
61
|
+
if (withSeconds) {
|
|
62
|
+
return `${base}:${pad(parts.seconds ?? 0)}`;
|
|
63
|
+
}
|
|
64
|
+
return base;
|
|
65
|
+
};
|
|
66
|
+
const to12Hour = (hours24) => {
|
|
67
|
+
if (hours24 === 0 || hours24 === 12)
|
|
68
|
+
return 12;
|
|
69
|
+
return hours24 % 12;
|
|
70
|
+
};
|
|
71
|
+
const to24Hour = (hour12, amPm) => {
|
|
72
|
+
if (amPm === "AM") {
|
|
73
|
+
return hour12 === 12 ? 0 : hour12;
|
|
74
|
+
}
|
|
75
|
+
return hour12 === 12 ? 12 : hour12 + 12;
|
|
76
|
+
};
|
|
77
|
+
const clampValue = (value, min, max, step) => {
|
|
78
|
+
const clamped = Math.max(min, Math.min(max, value));
|
|
79
|
+
return Math.round(clamped / step) * step;
|
|
80
|
+
};
|
|
81
|
+
const parseMinMax = (timeStr) => {
|
|
82
|
+
if (!timeStr)
|
|
83
|
+
return null;
|
|
84
|
+
const segments = timeStr.trim().split(":");
|
|
85
|
+
if (segments.length < 2)
|
|
86
|
+
return null;
|
|
87
|
+
const hours = Number.parseInt(segments[0], 10);
|
|
88
|
+
const minutes = Number.parseInt(segments[1], 10);
|
|
89
|
+
const seconds = segments.length >= 3 ? Number.parseInt(segments[2], 10) : 0;
|
|
90
|
+
if (Number.isNaN(hours) || Number.isNaN(minutes) || Number.isNaN(seconds))
|
|
91
|
+
return null;
|
|
92
|
+
return { hours, minutes, seconds };
|
|
93
|
+
};
|
|
94
|
+
const setRef = (ref, node) => {
|
|
95
|
+
if (typeof ref === "function") {
|
|
96
|
+
ref(node);
|
|
97
|
+
}
|
|
98
|
+
else if (ref && "current" in ref) {
|
|
99
|
+
ref.current = node;
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
const inputSizes = {
|
|
103
|
+
xs: "h-5 px-2 text-[8px]",
|
|
104
|
+
sm: "h-6 px-2.5 text-[10px]",
|
|
105
|
+
md: "h-8 px-3 text-xs",
|
|
106
|
+
lg: "h-10 px-3.5 text-sm",
|
|
107
|
+
xl: "h-12 px-4 text-base"
|
|
108
|
+
};
|
|
109
|
+
const inputVariants = {
|
|
110
|
+
default: "bg-[var(--refraktor-bg)] text-[var(--refraktor-text)] border border-[var(--refraktor-border)]",
|
|
111
|
+
filled: "bg-[var(--refraktor-bg)] text-[var(--refraktor-text)]",
|
|
112
|
+
outline: "bg-transparent text-[var(--refraktor-text)] border border-[var(--refraktor-border)]"
|
|
113
|
+
};
|
|
114
|
+
const segmentWidths = {
|
|
115
|
+
xs: "w-[1.25rem]",
|
|
116
|
+
sm: "w-[1.5rem]",
|
|
117
|
+
md: "w-[1.75rem]",
|
|
118
|
+
lg: "w-[2rem]",
|
|
119
|
+
xl: "w-[2.5rem]"
|
|
120
|
+
};
|
|
121
|
+
const amPmWidths = {
|
|
122
|
+
xs: "w-[1.75rem]",
|
|
123
|
+
sm: "w-[2rem]",
|
|
124
|
+
md: "w-[2.25rem]",
|
|
125
|
+
lg: "w-[2.75rem]",
|
|
126
|
+
xl: "w-[3.25rem]"
|
|
127
|
+
};
|
|
128
|
+
const separatorSizes = {
|
|
129
|
+
xs: "text-[8px]",
|
|
130
|
+
sm: "text-[10px]",
|
|
131
|
+
md: "text-xs",
|
|
132
|
+
lg: "text-sm",
|
|
133
|
+
xl: "text-base"
|
|
134
|
+
};
|
|
135
|
+
const TimePicker = factory((_props, ref) => {
|
|
136
|
+
const { cx, getRadius } = useTheme();
|
|
137
|
+
const { id, value, defaultValue, onChange, format, withSeconds, withDropdown, clearable, min, max, hoursStep, minutesStep, secondsStep, amPmLabels, disabled, readOnly, variant, size, radius, label, description, error, required, withAsterisk, leftSection, rightSection, hoursRef: hoursRefProp, minutesRef: minutesRefProp, secondsRef: secondsRefProp, amPmRef: amPmRefProp, hoursInputLabel, minutesInputLabel, secondsInputLabel, amPmInputLabel, popoverProps, onFocus, onBlur, className, classNames, ...props } = useProps("TimePicker", defaultProps, _props);
|
|
138
|
+
const classes = useClassNames("TimePicker", classNames);
|
|
139
|
+
const _id = useId(id);
|
|
140
|
+
const is12h = format === "12h";
|
|
141
|
+
const hoursRef = useRef(null);
|
|
142
|
+
const minutesRef = useRef(null);
|
|
143
|
+
const secondsRef = useRef(null);
|
|
144
|
+
const amPmRef = useRef(null);
|
|
145
|
+
const wrapperRef = useRef(null);
|
|
146
|
+
const blurTimeoutRef = useRef(null);
|
|
147
|
+
const minParsed = useMemo(() => parseMinMax(min), [min]);
|
|
148
|
+
const maxParsed = useMemo(() => parseMinMax(max), [max]);
|
|
149
|
+
const [internalValue, setInternalValue] = useUncontrolled({
|
|
150
|
+
value,
|
|
151
|
+
defaultValue,
|
|
152
|
+
finalValue: "",
|
|
153
|
+
onChange
|
|
154
|
+
});
|
|
155
|
+
const [parts, setParts] = useState(() => parseValue(internalValue, withSeconds));
|
|
156
|
+
useEffect(() => {
|
|
157
|
+
const parsed = parseValue(internalValue, withSeconds);
|
|
158
|
+
setParts(parsed);
|
|
159
|
+
}, [internalValue, withSeconds]);
|
|
160
|
+
const [dropdownOpen, setDropdownOpen] = useState(false);
|
|
161
|
+
const isDropdownVisible = withDropdown && dropdownOpen && !disabled;
|
|
162
|
+
const floating = useFloating({
|
|
163
|
+
placement: popoverProps?.placement ?? "bottom-start",
|
|
164
|
+
open: isDropdownVisible,
|
|
165
|
+
onOpenChange: (open) => {
|
|
166
|
+
if (!disabled && !readOnly)
|
|
167
|
+
setDropdownOpen(open);
|
|
168
|
+
},
|
|
169
|
+
middleware: [
|
|
170
|
+
offset(popoverProps?.offset ?? 4),
|
|
171
|
+
flip(),
|
|
172
|
+
shift()
|
|
173
|
+
],
|
|
174
|
+
whileElementsMounted: autoUpdate,
|
|
175
|
+
strategy: "fixed"
|
|
176
|
+
});
|
|
177
|
+
const click = useClick(floating.context, {
|
|
178
|
+
enabled: withDropdown && !disabled && !readOnly
|
|
179
|
+
});
|
|
180
|
+
const dismiss = useDismiss(floating.context);
|
|
181
|
+
const role = useRole(floating.context, { role: "dialog" });
|
|
182
|
+
const { getReferenceProps, getFloatingProps } = useInteractions([
|
|
183
|
+
click,
|
|
184
|
+
dismiss,
|
|
185
|
+
role
|
|
186
|
+
]);
|
|
187
|
+
const emitValue = useCallback((nextParts) => {
|
|
188
|
+
const allFilled = nextParts.hours !== null &&
|
|
189
|
+
nextParts.minutes !== null &&
|
|
190
|
+
(!withSeconds || nextParts.seconds !== null) &&
|
|
191
|
+
(!is12h || nextParts.amPm !== null);
|
|
192
|
+
const allEmpty = nextParts.hours === null &&
|
|
193
|
+
nextParts.minutes === null &&
|
|
194
|
+
(nextParts.seconds === null || !withSeconds);
|
|
195
|
+
if (allFilled) {
|
|
196
|
+
let hours = nextParts.hours;
|
|
197
|
+
if (is12h && nextParts.amPm !== null) {
|
|
198
|
+
hours = to24Hour(hours, nextParts.amPm);
|
|
199
|
+
}
|
|
200
|
+
const formatted = formatValue({ ...nextParts, hours }, withSeconds);
|
|
201
|
+
setInternalValue(formatted);
|
|
202
|
+
}
|
|
203
|
+
else if (allEmpty) {
|
|
204
|
+
setInternalValue("");
|
|
205
|
+
}
|
|
206
|
+
}, [is12h, setInternalValue, withSeconds]);
|
|
207
|
+
const updateParts = useCallback((updater) => {
|
|
208
|
+
setParts((prev) => {
|
|
209
|
+
const next = updater(prev);
|
|
210
|
+
emitValue(next);
|
|
211
|
+
return next;
|
|
212
|
+
});
|
|
213
|
+
}, [emitValue]);
|
|
214
|
+
const getDisplayHours = useCallback((hours24) => {
|
|
215
|
+
if (hours24 === null)
|
|
216
|
+
return PLACEHOLDER;
|
|
217
|
+
if (is12h)
|
|
218
|
+
return pad(to12Hour(hours24));
|
|
219
|
+
return pad(hours24);
|
|
220
|
+
}, [is12h]);
|
|
221
|
+
const handleClear = useCallback(() => {
|
|
222
|
+
if (disabled || readOnly)
|
|
223
|
+
return;
|
|
224
|
+
const empty = {
|
|
225
|
+
hours: null,
|
|
226
|
+
minutes: null,
|
|
227
|
+
seconds: null,
|
|
228
|
+
amPm: null
|
|
229
|
+
};
|
|
230
|
+
setParts(empty);
|
|
231
|
+
setInternalValue("");
|
|
232
|
+
hoursRef.current?.focus();
|
|
233
|
+
}, [disabled, readOnly, setInternalValue]);
|
|
234
|
+
const createSegmentKeyHandler = useCallback((segment, prevRef, nextRef) => {
|
|
235
|
+
return (event) => {
|
|
236
|
+
if (disabled || readOnly)
|
|
237
|
+
return;
|
|
238
|
+
const step = segment === "hours"
|
|
239
|
+
? hoursStep
|
|
240
|
+
: segment === "minutes"
|
|
241
|
+
? minutesStep
|
|
242
|
+
: secondsStep;
|
|
243
|
+
const maxVal = segment === "hours"
|
|
244
|
+
? is12h
|
|
245
|
+
? 12
|
|
246
|
+
: HOURS_IN_DAY - 1
|
|
247
|
+
: segment === "minutes"
|
|
248
|
+
? MINUTES_IN_HOUR - 1
|
|
249
|
+
: SECONDS_IN_MINUTE - 1;
|
|
250
|
+
const minVal = segment === "hours" && is12h ? 1 : 0;
|
|
251
|
+
if (event.key === "ArrowUp") {
|
|
252
|
+
event.preventDefault();
|
|
253
|
+
updateParts((prev) => {
|
|
254
|
+
const current = prev[segment] ?? minVal;
|
|
255
|
+
let next = current + step;
|
|
256
|
+
if (next > maxVal)
|
|
257
|
+
next = minVal;
|
|
258
|
+
return { ...prev, [segment]: next };
|
|
259
|
+
});
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
if (event.key === "ArrowDown") {
|
|
263
|
+
event.preventDefault();
|
|
264
|
+
updateParts((prev) => {
|
|
265
|
+
const current = prev[segment] ?? maxVal;
|
|
266
|
+
let next = current - step;
|
|
267
|
+
if (next < minVal)
|
|
268
|
+
next = maxVal;
|
|
269
|
+
return { ...prev, [segment]: next };
|
|
270
|
+
});
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
if (event.key === "ArrowRight") {
|
|
274
|
+
event.preventDefault();
|
|
275
|
+
nextRef.current?.focus();
|
|
276
|
+
nextRef.current?.select();
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
if (event.key === "ArrowLeft") {
|
|
280
|
+
event.preventDefault();
|
|
281
|
+
prevRef.current?.focus();
|
|
282
|
+
prevRef.current?.select();
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
if (event.key === "Home") {
|
|
286
|
+
event.preventDefault();
|
|
287
|
+
updateParts((prev) => ({ ...prev, [segment]: minVal }));
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
if (event.key === "End") {
|
|
291
|
+
event.preventDefault();
|
|
292
|
+
updateParts((prev) => ({ ...prev, [segment]: maxVal }));
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
if (event.key === "Backspace") {
|
|
296
|
+
event.preventDefault();
|
|
297
|
+
updateParts((prev) => {
|
|
298
|
+
if (prev[segment] === null) {
|
|
299
|
+
prevRef.current?.focus();
|
|
300
|
+
prevRef.current?.select();
|
|
301
|
+
return prev;
|
|
302
|
+
}
|
|
303
|
+
return { ...prev, [segment]: null };
|
|
304
|
+
});
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
if (event.key === "Tab") {
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
if (/^\d$/.test(event.key)) {
|
|
311
|
+
event.preventDefault();
|
|
312
|
+
const digit = Number.parseInt(event.key, 10);
|
|
313
|
+
updateParts((prev) => {
|
|
314
|
+
const current = prev[segment];
|
|
315
|
+
let next;
|
|
316
|
+
if (current === null || current >= 10) {
|
|
317
|
+
next = digit;
|
|
318
|
+
}
|
|
319
|
+
else {
|
|
320
|
+
next = current * 10 + digit;
|
|
321
|
+
}
|
|
322
|
+
if (next > maxVal) {
|
|
323
|
+
next = digit;
|
|
324
|
+
}
|
|
325
|
+
const result = { ...prev, [segment]: next };
|
|
326
|
+
if (next >= (maxVal + 1) / 10 || (current !== null && current < 10)) {
|
|
327
|
+
requestAnimationFrame(() => {
|
|
328
|
+
nextRef.current?.focus();
|
|
329
|
+
nextRef.current?.select();
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
return result;
|
|
333
|
+
});
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
if (!event.ctrlKey && !event.metaKey && !event.altKey) {
|
|
337
|
+
event.preventDefault();
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
}, [disabled, hoursStep, is12h, minutesStep, readOnly, secondsStep, updateParts]);
|
|
341
|
+
const handleAmPmKeyDown = useCallback((event) => {
|
|
342
|
+
if (disabled || readOnly)
|
|
343
|
+
return;
|
|
344
|
+
if (event.key === "ArrowUp" || event.key === "ArrowDown") {
|
|
345
|
+
event.preventDefault();
|
|
346
|
+
updateParts((prev) => ({
|
|
347
|
+
...prev,
|
|
348
|
+
amPm: prev.amPm === "AM" ? "PM" : "AM"
|
|
349
|
+
}));
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
if (event.key === "ArrowLeft") {
|
|
353
|
+
event.preventDefault();
|
|
354
|
+
const prev = withSeconds ? secondsRef : minutesRef;
|
|
355
|
+
prev.current?.focus();
|
|
356
|
+
prev.current?.select();
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
if (event.key.toLowerCase() === "a" ||
|
|
360
|
+
event.key.toLowerCase() === "p") {
|
|
361
|
+
event.preventDefault();
|
|
362
|
+
const nextAmPm = event.key.toLowerCase() === "a" ? "AM" : "PM";
|
|
363
|
+
updateParts((prev) => ({ ...prev, amPm: nextAmPm }));
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
if (event.key === "Backspace") {
|
|
367
|
+
event.preventDefault();
|
|
368
|
+
updateParts((prev) => {
|
|
369
|
+
if (prev.amPm === null) {
|
|
370
|
+
const prev2 = withSeconds ? secondsRef : minutesRef;
|
|
371
|
+
prev2.current?.focus();
|
|
372
|
+
prev2.current?.select();
|
|
373
|
+
return prev;
|
|
374
|
+
}
|
|
375
|
+
return { ...prev, amPm: null };
|
|
376
|
+
});
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
if (event.key === "Tab") {
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
if (!event.ctrlKey && !event.metaKey && !event.altKey) {
|
|
383
|
+
event.preventDefault();
|
|
384
|
+
}
|
|
385
|
+
}, [disabled, readOnly, updateParts, withSeconds]);
|
|
386
|
+
const handleWrapperFocus = useCallback((event) => {
|
|
387
|
+
if (blurTimeoutRef.current !== null) {
|
|
388
|
+
clearTimeout(blurTimeoutRef.current);
|
|
389
|
+
blurTimeoutRef.current = null;
|
|
390
|
+
}
|
|
391
|
+
if (!wrapperRef.current?.contains(event.relatedTarget)) {
|
|
392
|
+
onFocus?.(event);
|
|
393
|
+
if (withDropdown && !disabled && !readOnly) {
|
|
394
|
+
setDropdownOpen(true);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}, [disabled, onFocus, readOnly, withDropdown]);
|
|
398
|
+
const handleWrapperBlur = useCallback((event) => {
|
|
399
|
+
blurTimeoutRef.current = setTimeout(() => {
|
|
400
|
+
if (!wrapperRef.current?.contains(document.activeElement) &&
|
|
401
|
+
!floating.refs.floating.current?.contains(document.activeElement)) {
|
|
402
|
+
onBlur?.(event);
|
|
403
|
+
setDropdownOpen(false);
|
|
404
|
+
}
|
|
405
|
+
}, 0);
|
|
406
|
+
}, [floating.refs.floating, onBlur]);
|
|
407
|
+
const displayHours = is12h
|
|
408
|
+
? parts.hours !== null
|
|
409
|
+
? pad(to12Hour(parts.hours))
|
|
410
|
+
: PLACEHOLDER
|
|
411
|
+
: parts.hours !== null
|
|
412
|
+
? pad(parts.hours)
|
|
413
|
+
: PLACEHOLDER;
|
|
414
|
+
const displayMinutes = parts.minutes !== null ? pad(parts.minutes) : PLACEHOLDER;
|
|
415
|
+
const displaySeconds = parts.seconds !== null ? pad(parts.seconds) : PLACEHOLDER;
|
|
416
|
+
const displayAmPm = parts.amPm ?? PLACEHOLDER;
|
|
417
|
+
const hasValue = parts.hours !== null ||
|
|
418
|
+
parts.minutes !== null ||
|
|
419
|
+
(withSeconds && parts.seconds !== null);
|
|
420
|
+
const showClearButton = clearable && hasValue && !disabled && !readOnly;
|
|
421
|
+
const effectiveRightSection = showClearButton ? (_jsx("button", { type: "button", tabIndex: -1, "aria-label": "Clear time", className: cx("inline-flex items-center justify-center text-[var(--refraktor-text-secondary)] hover:text-[var(--refraktor-text)] transition-colors cursor-pointer"), onClick: handleClear, onMouseDown: (e) => e.preventDefault(), children: _jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 16 16", fill: "currentColor", className: "size-3.5", children: _jsx("path", { d: "M3.72 3.72a.75.75 0 0 1 1.06 0L8 6.94l3.22-3.22a.75.75 0 1 1 1.06 1.06L9.06 8l3.22 3.22a.75.75 0 1 1-1.06 1.06L8 9.06l-3.22 3.22a.75.75 0 0 1-1.06-1.06L6.94 8 3.72 4.78a.75.75 0 0 1 0-1.06Z" }) }) })) : (rightSection);
|
|
422
|
+
const sizeClass = inputSizes[size] ?? inputSizes.md;
|
|
423
|
+
const variantClass = inputVariants[variant] ?? inputVariants.default;
|
|
424
|
+
const segmentWidth = segmentWidths[size] ?? segmentWidths.md;
|
|
425
|
+
const amPmWidth = amPmWidths[size] ?? amPmWidths.md;
|
|
426
|
+
const sepSize = separatorSizes[size] ?? separatorSizes.md;
|
|
427
|
+
const hoursKeyHandler = createSegmentKeyHandler("hours", { current: null }, minutesRef);
|
|
428
|
+
const minutesKeyHandler = createSegmentKeyHandler("minutes", hoursRef, withSeconds ? secondsRef : is12h ? amPmRef : { current: null });
|
|
429
|
+
const secondsKeyHandler = createSegmentKeyHandler("seconds", minutesRef, is12h ? amPmRef : { current: null });
|
|
430
|
+
const renderDropdown = () => {
|
|
431
|
+
if (!withDropdown)
|
|
432
|
+
return null;
|
|
433
|
+
const sizeStyles = getPickerSizeStyles(size);
|
|
434
|
+
const hoursCount = is12h ? 12 : HOURS_IN_DAY;
|
|
435
|
+
const hoursStart = is12h ? 1 : 0;
|
|
436
|
+
const hourOptions = [];
|
|
437
|
+
for (let i = hoursStart; i < hoursStart + hoursCount; i += hoursStep) {
|
|
438
|
+
hourOptions.push(i);
|
|
439
|
+
}
|
|
440
|
+
const minuteOptions = [];
|
|
441
|
+
for (let i = 0; i < MINUTES_IN_HOUR; i += minutesStep) {
|
|
442
|
+
minuteOptions.push(i);
|
|
443
|
+
}
|
|
444
|
+
const secondOptions = [];
|
|
445
|
+
for (let i = 0; i < SECONDS_IN_MINUTE; i += secondsStep) {
|
|
446
|
+
secondOptions.push(i);
|
|
447
|
+
}
|
|
448
|
+
const currentHourDisplay = is12h
|
|
449
|
+
? parts.hours !== null
|
|
450
|
+
? to12Hour(parts.hours)
|
|
451
|
+
: null
|
|
452
|
+
: parts.hours;
|
|
453
|
+
const renderColumn = (columnLabel, options, selectedValue, onSelect, extraClassName) => (_jsxs("div", { className: cx("flex flex-col min-w-0", classes.dropdownColumn, extraClassName), children: [_jsx("div", { className: cx("py-1.5 text-center font-medium text-[var(--refraktor-text-secondary)] border-b border-[var(--refraktor-border)] bg-[var(--refraktor-bg-subtle)]", sizeStyles.label, classes.dropdownColumnLabel), children: columnLabel }), _jsx("div", { className: cx("refraktor-scrollbar flex max-h-52 flex-col gap-0.5 p-1 overflow-y-auto"), children: options.map((opt) => {
|
|
454
|
+
const isSelected = opt === selectedValue;
|
|
455
|
+
const displayLabel = typeof opt === "number" ? pad(opt) : opt;
|
|
456
|
+
return (_jsx("button", { type: "button", tabIndex: -1, className: cx("inline-flex w-full items-center justify-center font-medium transition-colors", isSelected
|
|
457
|
+
? "bg-[var(--refraktor-primary)] text-[var(--refraktor-primary-text)]"
|
|
458
|
+
: "hover:bg-[var(--refraktor-bg-hover)] text-[var(--refraktor-text)]", sizeStyles.cell, getRadius(radius), classes.dropdownOption, isSelected && classes.dropdownOptionActive), onMouseDown: (e) => e.preventDefault(), onClick: () => onSelect(opt), children: displayLabel }, String(opt)));
|
|
459
|
+
}) })] }));
|
|
460
|
+
const handleDropdownHourSelect = (val) => {
|
|
461
|
+
const hourVal = typeof val === "number" ? val : Number(val);
|
|
462
|
+
updateParts((prev) => {
|
|
463
|
+
const next = { ...prev, hours: is12h ? to24Hour(hourVal, prev.amPm ?? "AM") : hourVal };
|
|
464
|
+
if (next.minutes === null) {
|
|
465
|
+
requestAnimationFrame(() => {
|
|
466
|
+
minutesRef.current?.focus();
|
|
467
|
+
minutesRef.current?.select();
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
return next;
|
|
471
|
+
});
|
|
472
|
+
};
|
|
473
|
+
const handleDropdownMinuteSelect = (val) => {
|
|
474
|
+
const minVal = typeof val === "number" ? val : Number(val);
|
|
475
|
+
updateParts((prev) => {
|
|
476
|
+
const next = { ...prev, minutes: minVal };
|
|
477
|
+
if (withSeconds && next.seconds === null) {
|
|
478
|
+
requestAnimationFrame(() => {
|
|
479
|
+
secondsRef.current?.focus();
|
|
480
|
+
secondsRef.current?.select();
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
return next;
|
|
484
|
+
});
|
|
485
|
+
};
|
|
486
|
+
const handleDropdownSecondSelect = (val) => {
|
|
487
|
+
const secVal = typeof val === "number" ? val : Number(val);
|
|
488
|
+
updateParts((prev) => ({ ...prev, seconds: secVal }));
|
|
489
|
+
};
|
|
490
|
+
const handleDropdownAmPmSelect = (val) => {
|
|
491
|
+
const amPmVal = val;
|
|
492
|
+
updateParts((prev) => {
|
|
493
|
+
if (prev.hours === null)
|
|
494
|
+
return { ...prev, amPm: amPmVal };
|
|
495
|
+
const hour12 = to12Hour(prev.hours);
|
|
496
|
+
const newHours24 = to24Hour(hour12, amPmVal);
|
|
497
|
+
return { ...prev, hours: newHours24, amPm: amPmVal };
|
|
498
|
+
});
|
|
499
|
+
};
|
|
500
|
+
const columnCount = (withSeconds ? 4 : 3) + (is12h ? 1 : 0) - (withSeconds ? 0 : 1);
|
|
501
|
+
const gridColsClass = columnCount === 2
|
|
502
|
+
? "grid-cols-2"
|
|
503
|
+
: columnCount === 3
|
|
504
|
+
? "grid-cols-3"
|
|
505
|
+
: "grid-cols-4";
|
|
506
|
+
return (_jsx(FloatingPortal, { children: isDropdownVisible && (_jsx(FloatingFocusManager, { context: floating.context, modal: false, initialFocus: -1, returnFocus: false, children: _jsx("div", { ref: floating.refs.setFloating, style: {
|
|
507
|
+
...floating.floatingStyles,
|
|
508
|
+
zIndex: 1000
|
|
509
|
+
}, className: cx("border border-[var(--refraktor-border)] bg-[var(--refraktor-bg)] shadow-md overflow-hidden", getRadius(radius), classes.dropdown), ...getFloatingProps(), children: _jsxs("div", { className: cx("grid divide-x divide-[var(--refraktor-border)]", gridColsClass), children: [renderColumn("Hour", hourOptions, currentHourDisplay, handleDropdownHourSelect), renderColumn("Min", minuteOptions, parts.minutes, handleDropdownMinuteSelect), withSeconds &&
|
|
510
|
+
renderColumn("Sec", secondOptions, parts.seconds, handleDropdownSecondSelect), is12h &&
|
|
511
|
+
renderColumn(amPmLabels.am + "/" + amPmLabels.pm, [amPmLabels.am, amPmLabels.pm], parts.amPm ===
|
|
512
|
+
"AM"
|
|
513
|
+
? amPmLabels.am
|
|
514
|
+
: parts.amPm === "PM"
|
|
515
|
+
? amPmLabels.pm
|
|
516
|
+
: null, (val) => {
|
|
517
|
+
const normalized = val === amPmLabels.am
|
|
518
|
+
? "AM"
|
|
519
|
+
: "PM";
|
|
520
|
+
handleDropdownAmPmSelect(normalized);
|
|
521
|
+
})] }) }) })) }));
|
|
522
|
+
};
|
|
523
|
+
const hasWrapper = label || description || error;
|
|
524
|
+
const inputContent = (_jsxs("div", { ref: (node) => {
|
|
525
|
+
if (wrapperRef)
|
|
526
|
+
wrapperRef.current = node;
|
|
527
|
+
floating.refs.setReference(node);
|
|
528
|
+
}, className: cx("relative w-full inline-flex items-center transition-all", sizeClass, variantClass, getRadius(radius), "focus-within:border-[var(--refraktor-primary)]", error && typeof error !== "boolean" && "border-[var(--refraktor-colors-red-6)]", disabled && "opacity-50 cursor-not-allowed", classes.fieldsWrapper, !hasWrapper && className), onFocus: handleWrapperFocus, onBlur: handleWrapperBlur, ...(!hasWrapper ? getReferenceProps(props) : getReferenceProps()), children: [leftSection && (_jsx("div", { className: "flex h-full items-center justify-center text-[var(--refraktor-text-secondary)] shrink-0 select-none", children: leftSection })), _jsxs("div", { className: cx("flex items-center flex-1 min-w-0 gap-0.5"), children: [_jsx("input", { ref: (node) => {
|
|
529
|
+
hoursRef.current = node;
|
|
530
|
+
setRef(hoursRefProp, node);
|
|
531
|
+
}, id: `${_id}-hours`, type: "text", inputMode: "numeric", autoComplete: "off", placeholder: PLACEHOLDER, value: parts.hours !== null ? (is12h ? pad(to12Hour(parts.hours)) : pad(parts.hours)) : "", "aria-label": hoursInputLabel ?? "Hours", readOnly: true, tabIndex: disabled ? -1 : 0, disabled: disabled, className: cx("bg-transparent border-none outline-none text-center text-[var(--refraktor-text)] placeholder:text-[var(--refraktor-text-tertiary)] cursor-default select-all p-0", segmentWidth, classes.field), onKeyDown: hoursKeyHandler, onFocus: (e) => e.target.select() }), _jsx("span", { className: cx("text-[var(--refraktor-text-secondary)] select-none leading-none", sepSize, classes.separator), children: ":" }), _jsx("input", { ref: (node) => {
|
|
532
|
+
minutesRef.current = node;
|
|
533
|
+
setRef(minutesRefProp, node);
|
|
534
|
+
}, id: `${_id}-minutes`, type: "text", inputMode: "numeric", autoComplete: "off", placeholder: PLACEHOLDER, value: parts.minutes !== null ? pad(parts.minutes) : "", "aria-label": minutesInputLabel ?? "Minutes", readOnly: true, tabIndex: disabled ? -1 : 0, disabled: disabled, className: cx("bg-transparent border-none outline-none text-center text-[var(--refraktor-text)] placeholder:text-[var(--refraktor-text-tertiary)] cursor-default select-all p-0", segmentWidth, classes.field), onKeyDown: minutesKeyHandler, onFocus: (e) => e.target.select() }), withSeconds && (_jsxs(_Fragment, { children: [_jsx("span", { className: cx("text-[var(--refraktor-text-secondary)] select-none leading-none", sepSize, classes.separator), children: ":" }), _jsx("input", { ref: (node) => {
|
|
535
|
+
secondsRef.current = node;
|
|
536
|
+
setRef(secondsRefProp, node);
|
|
537
|
+
}, id: `${_id}-seconds`, type: "text", inputMode: "numeric", autoComplete: "off", placeholder: PLACEHOLDER, value: parts.seconds !== null
|
|
538
|
+
? pad(parts.seconds)
|
|
539
|
+
: "", "aria-label": secondsInputLabel ?? "Seconds", readOnly: true, tabIndex: disabled ? -1 : 0, disabled: disabled, className: cx("bg-transparent border-none outline-none text-center text-[var(--refraktor-text)] placeholder:text-[var(--refraktor-text-tertiary)] cursor-default select-all p-0", segmentWidth, classes.field), onKeyDown: secondsKeyHandler, onFocus: (e) => e.target.select() })] })), is12h && (_jsx("input", { ref: (node) => {
|
|
540
|
+
amPmRef.current = node;
|
|
541
|
+
setRef(amPmRefProp, node);
|
|
542
|
+
}, id: `${_id}-ampm`, type: "text", autoComplete: "off", placeholder: PLACEHOLDER, value: parts.amPm !== null
|
|
543
|
+
? parts.amPm === "AM"
|
|
544
|
+
? amPmLabels.am
|
|
545
|
+
: amPmLabels.pm
|
|
546
|
+
: "", "aria-label": amPmInputLabel ?? "AM/PM", readOnly: true, tabIndex: disabled ? -1 : 0, disabled: disabled, className: cx("bg-transparent border-none outline-none text-center text-[var(--refraktor-text)] placeholder:text-[var(--refraktor-text-tertiary)] cursor-default select-all p-0 ml-1", amPmWidth, classes.amPmInput), onKeyDown: handleAmPmKeyDown, onFocus: (e) => e.target.select() }))] }), effectiveRightSection && (_jsx("div", { className: "flex h-full items-center justify-center text-[var(--refraktor-text-secondary)] shrink-0 select-none", children: effectiveRightSection }))] }));
|
|
547
|
+
const content = hasWrapper ? (_jsxs(Input.Wrapper, { ref: ref, label: label, description: description, error: error, required: required, withAsterisk: withAsterisk, inputId: `${_id}-hours`, className: cx(classes.root, className), children: [inputContent, renderDropdown()] })) : (_jsxs("div", { ref: ref, className: cx(classes.root, className), children: [inputContent, renderDropdown()] }));
|
|
548
|
+
return content;
|
|
549
|
+
});
|
|
550
|
+
TimePicker.displayName = "@refraktor/dates/TimePicker";
|
|
551
|
+
TimePicker.configure = createComponentConfig();
|
|
552
|
+
TimePicker.classNames = createClassNamesConfig();
|
|
553
|
+
export default TimePicker;
|