@bricks-toolkit/toolkit 0.1.25 → 0.1.27
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/dist/{chunk-WVWZ73XT.cjs → chunk-6GU53FA5.cjs} +12 -4
- package/dist/{chunk-EKN2P6XK.mjs → chunk-BLELGRQT.mjs} +13 -5
- package/dist/{chunk-PMOWOLIQ.cjs → chunk-C5CAUNLU.cjs} +232 -229
- package/dist/{chunk-DL6RO422.cjs → chunk-IINDMJBE.cjs} +12 -4
- package/dist/{chunk-EGGXXVN2.mjs → chunk-XAQT2CYE.mjs} +235 -232
- package/dist/{chunk-GQCJTIVO.mjs → chunk-XDN5IK2K.mjs} +13 -5
- package/dist/date-picker/index.cjs +2 -2
- package/dist/date-picker/index.d.mts +1 -0
- package/dist/date-picker/index.d.ts +1 -0
- package/dist/date-picker/index.mjs +1 -1
- package/dist/date-time-picker/index.cjs +2 -2
- package/dist/date-time-picker/index.d.mts +1 -0
- package/dist/date-time-picker/index.d.ts +1 -0
- package/dist/date-time-picker/index.mjs +1 -1
- package/dist/index.cjs +6 -6
- package/dist/index.mjs +3 -3
- package/dist/time-picker/index.cjs +2 -2
- package/dist/time-picker/index.d.mts +1 -0
- package/dist/time-picker/index.d.ts +1 -0
- package/dist/time-picker/index.mjs +1 -1
- package/package.json +36 -36
|
@@ -255,9 +255,11 @@ var DateTimePicker = react.forwardRef(
|
|
|
255
255
|
use12Hour = false,
|
|
256
256
|
id: idProp,
|
|
257
257
|
name,
|
|
258
|
+
type,
|
|
258
259
|
"aria-label": ariaLabel,
|
|
259
260
|
"aria-describedby": ariaDescribedby
|
|
260
261
|
}, ref) => {
|
|
262
|
+
const internalRef = react.useRef(null);
|
|
261
263
|
const genId = react.useId();
|
|
262
264
|
const inputId = idProp ?? genId;
|
|
263
265
|
const popoverId = `${inputId}-popover`;
|
|
@@ -281,8 +283,7 @@ var DateTimePicker = react.forwardRef(
|
|
|
281
283
|
react.useEffect(() => {
|
|
282
284
|
if (!isOpen) return;
|
|
283
285
|
const h = (e) => {
|
|
284
|
-
|
|
285
|
-
if (wrapper && !wrapper.contains(e.target)) {
|
|
286
|
+
if (internalRef.current && !internalRef.current.contains(e.target)) {
|
|
286
287
|
setIsOpen(false);
|
|
287
288
|
setViewMode("day");
|
|
288
289
|
}
|
|
@@ -395,7 +396,14 @@ var DateTimePicker = react.forwardRef(
|
|
|
395
396
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
396
397
|
"div",
|
|
397
398
|
{
|
|
398
|
-
ref
|
|
399
|
+
ref: (node) => {
|
|
400
|
+
internalRef.current = node;
|
|
401
|
+
if (typeof ref === "function") {
|
|
402
|
+
ref(node);
|
|
403
|
+
} else if (ref) {
|
|
404
|
+
ref.current = node;
|
|
405
|
+
}
|
|
406
|
+
},
|
|
399
407
|
className: chunkL5VQZZVR_cjs.cn(
|
|
400
408
|
"flex flex-col gap-1 relative",
|
|
401
409
|
fullWidth ? "w-full" : "w-fit",
|
|
@@ -430,7 +438,7 @@ var DateTimePicker = react.forwardRef(
|
|
|
430
438
|
"button",
|
|
431
439
|
{
|
|
432
440
|
id: inputId,
|
|
433
|
-
type: "button",
|
|
441
|
+
type: type ?? "button",
|
|
434
442
|
role: "combobox",
|
|
435
443
|
"aria-expanded": isOpen,
|
|
436
444
|
"aria-haspopup": "dialog",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { parseISODate, toISODateString, buildCalendarGrid, getYearRange, MONTH_NAMES, DAY_NAMES_SHORT, isSameDay, isDisabledDate, MONTH_NAMES_SHORT, formatDate } from './chunk-4PRNRENN.mjs';
|
|
2
2
|
import { cn } from './chunk-OCPFOFJ4.mjs';
|
|
3
|
-
import { forwardRef, useId, useState, useMemo, useEffect, useCallback
|
|
3
|
+
import { forwardRef, useRef, useId, useState, useMemo, useEffect, useCallback } from 'react';
|
|
4
4
|
import { CalendarIcon, XMarkIcon, ClockIcon, ChevronLeftIcon, ChevronRightIcon, ChevronUpIcon, ChevronDownIcon } from '@heroicons/react/24/outline';
|
|
5
5
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
6
6
|
|
|
@@ -253,9 +253,11 @@ var DateTimePicker = forwardRef(
|
|
|
253
253
|
use12Hour = false,
|
|
254
254
|
id: idProp,
|
|
255
255
|
name,
|
|
256
|
+
type,
|
|
256
257
|
"aria-label": ariaLabel,
|
|
257
258
|
"aria-describedby": ariaDescribedby
|
|
258
259
|
}, ref) => {
|
|
260
|
+
const internalRef = useRef(null);
|
|
259
261
|
const genId = useId();
|
|
260
262
|
const inputId = idProp ?? genId;
|
|
261
263
|
const popoverId = `${inputId}-popover`;
|
|
@@ -279,8 +281,7 @@ var DateTimePicker = forwardRef(
|
|
|
279
281
|
useEffect(() => {
|
|
280
282
|
if (!isOpen) return;
|
|
281
283
|
const h = (e) => {
|
|
282
|
-
|
|
283
|
-
if (wrapper && !wrapper.contains(e.target)) {
|
|
284
|
+
if (internalRef.current && !internalRef.current.contains(e.target)) {
|
|
284
285
|
setIsOpen(false);
|
|
285
286
|
setViewMode("day");
|
|
286
287
|
}
|
|
@@ -393,7 +394,14 @@ var DateTimePicker = forwardRef(
|
|
|
393
394
|
return /* @__PURE__ */ jsxs(
|
|
394
395
|
"div",
|
|
395
396
|
{
|
|
396
|
-
ref
|
|
397
|
+
ref: (node) => {
|
|
398
|
+
internalRef.current = node;
|
|
399
|
+
if (typeof ref === "function") {
|
|
400
|
+
ref(node);
|
|
401
|
+
} else if (ref) {
|
|
402
|
+
ref.current = node;
|
|
403
|
+
}
|
|
404
|
+
},
|
|
397
405
|
className: cn(
|
|
398
406
|
"flex flex-col gap-1 relative",
|
|
399
407
|
fullWidth ? "w-full" : "w-fit",
|
|
@@ -428,7 +436,7 @@ var DateTimePicker = forwardRef(
|
|
|
428
436
|
"button",
|
|
429
437
|
{
|
|
430
438
|
id: inputId,
|
|
431
|
-
type: "button",
|
|
439
|
+
type: type ?? "button",
|
|
432
440
|
role: "combobox",
|
|
433
441
|
"aria-expanded": isOpen,
|
|
434
442
|
"aria-haspopup": "dialog",
|
|
@@ -5,30 +5,51 @@ var react = require('react');
|
|
|
5
5
|
var outline = require('@heroicons/react/24/outline');
|
|
6
6
|
var jsxRuntime = require('react/jsx-runtime');
|
|
7
7
|
|
|
8
|
-
function
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
8
|
+
function parseTypedTime(str) {
|
|
9
|
+
const clean = str.trim();
|
|
10
|
+
if (!clean) return null;
|
|
11
|
+
const match24 = /^(\d{2}):(\d{2})$/.exec(clean);
|
|
12
|
+
if (match24?.[1] && match24[2]) {
|
|
13
|
+
const hour = parseInt(match24[1], 10);
|
|
14
|
+
const minute = parseInt(match24[2], 10);
|
|
15
|
+
if (hour >= 0 && hour <= 23 && minute >= 0 && minute <= 59) {
|
|
16
|
+
return `${String(hour).padStart(2, "0")}:${String(minute).padStart(2, "0")}`;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return null;
|
|
17
20
|
}
|
|
18
|
-
function
|
|
19
|
-
|
|
21
|
+
function formatTime12(iso) {
|
|
22
|
+
if (!iso) return "";
|
|
23
|
+
const parts = iso.split(":");
|
|
24
|
+
if (parts.length < 2 || !parts[0] || !parts[1]) return iso;
|
|
25
|
+
const hour = parseInt(parts[0], 10);
|
|
26
|
+
const minute = parseInt(parts[1], 10);
|
|
27
|
+
if (isNaN(hour) || isNaN(minute)) return iso;
|
|
28
|
+
const displayHour = hour % 12 || 12;
|
|
29
|
+
const ampm = hour >= 12 ? "PM" : "AM";
|
|
30
|
+
return `${String(displayHour)}:${String(minute).padStart(2, "0")} ${ampm}`;
|
|
20
31
|
}
|
|
21
|
-
function formatDisplay(
|
|
22
|
-
if (!
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
32
|
+
function formatDisplay(iso, use12Hour) {
|
|
33
|
+
if (!iso) return "";
|
|
34
|
+
return use12Hour ? formatTime12(iso) : iso;
|
|
35
|
+
}
|
|
36
|
+
function generateTimeSlots(step, use12Hour) {
|
|
37
|
+
const slots = [];
|
|
38
|
+
const limit = 24 * 60;
|
|
39
|
+
const s = step > 0 && step <= 720 ? step : 1;
|
|
40
|
+
for (let minutes = 0; minutes < limit; minutes += s) {
|
|
41
|
+
const hour = Math.floor(minutes / 60);
|
|
42
|
+
const minute = minutes % 60;
|
|
43
|
+
const value = `${String(hour).padStart(2, "0")}:${String(minute).padStart(2, "0")}`;
|
|
44
|
+
const label = use12Hour ? (() => {
|
|
45
|
+
const displayHour = hour % 12 || 12;
|
|
46
|
+
const ampm = hour >= 12 ? "PM" : "AM";
|
|
47
|
+
return `${String(displayHour)}:${String(minute).padStart(2, "0")} ${ampm}`;
|
|
48
|
+
})() : value;
|
|
49
|
+
slots.push({ label, value });
|
|
28
50
|
}
|
|
29
|
-
return
|
|
51
|
+
return slots;
|
|
30
52
|
}
|
|
31
|
-
var ITEM_HEIGHT = 36;
|
|
32
53
|
var sizeClasses = {
|
|
33
54
|
xs: "h-6 px-2 text-xs",
|
|
34
55
|
sm: "h-8 px-3 text-sm",
|
|
@@ -81,104 +102,12 @@ var stateMessageClasses = {
|
|
|
81
102
|
success: "text-success font-medium",
|
|
82
103
|
warning: "text-warning font-medium"
|
|
83
104
|
};
|
|
84
|
-
function ScrollColumn({ items, selected, onSelect, label }) {
|
|
85
|
-
const listRef = react.useRef(null);
|
|
86
|
-
const scrollToSelected = react.useCallback(
|
|
87
|
-
(behavior = "smooth") => {
|
|
88
|
-
if (!listRef.current) return;
|
|
89
|
-
const idx = items.indexOf(selected);
|
|
90
|
-
if (idx < 0) return;
|
|
91
|
-
const top = idx * ITEM_HEIGHT - ITEM_HEIGHT * 2;
|
|
92
|
-
if (typeof listRef.current.scrollTo === "function") {
|
|
93
|
-
listRef.current.scrollTo({ top: Math.max(0, top), behavior });
|
|
94
|
-
}
|
|
95
|
-
},
|
|
96
|
-
[items, selected]
|
|
97
|
-
);
|
|
98
|
-
react.useEffect(() => {
|
|
99
|
-
scrollToSelected("instant");
|
|
100
|
-
}, []);
|
|
101
|
-
react.useEffect(() => {
|
|
102
|
-
scrollToSelected("smooth");
|
|
103
|
-
}, [scrollToSelected]);
|
|
104
|
-
const step = (dir) => {
|
|
105
|
-
const idx = items.indexOf(selected);
|
|
106
|
-
const next = items[(idx + dir + items.length) % items.length];
|
|
107
|
-
if (next) onSelect(next);
|
|
108
|
-
};
|
|
109
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-0.5", "aria-label": label, role: "group", children: [
|
|
110
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
111
|
-
"button",
|
|
112
|
-
{
|
|
113
|
-
type: "button",
|
|
114
|
-
onClick: () => {
|
|
115
|
-
step(-1);
|
|
116
|
-
},
|
|
117
|
-
className: "p-1 rounded hover:bg-hover text-text-secondary hover:text-text transition-colors",
|
|
118
|
-
"aria-label": `Decrease ${label}`,
|
|
119
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(outline.ChevronUpIcon, { className: "w-4 h-4" })
|
|
120
|
-
}
|
|
121
|
-
),
|
|
122
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
|
|
123
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
124
|
-
"div",
|
|
125
|
-
{
|
|
126
|
-
className: "absolute inset-x-0 rounded-lg bg-primary/10 border-y border-primary/20 pointer-events-none",
|
|
127
|
-
style: { top: ITEM_HEIGHT * 2, height: ITEM_HEIGHT }
|
|
128
|
-
}
|
|
129
|
-
),
|
|
130
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
131
|
-
"div",
|
|
132
|
-
{
|
|
133
|
-
ref: listRef,
|
|
134
|
-
className: "overflow-y-auto scrollbar-none",
|
|
135
|
-
style: { height: ITEM_HEIGHT * 5, width: 56 },
|
|
136
|
-
children: [
|
|
137
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { height: ITEM_HEIGHT * 2 } }),
|
|
138
|
-
items.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
139
|
-
"button",
|
|
140
|
-
{
|
|
141
|
-
type: "button",
|
|
142
|
-
onClick: () => {
|
|
143
|
-
onSelect(item);
|
|
144
|
-
},
|
|
145
|
-
className: chunkL5VQZZVR_cjs.cn(
|
|
146
|
-
"w-full flex items-center justify-center rounded-lg text-sm font-medium transition-colors duration-150",
|
|
147
|
-
"hover:bg-hover",
|
|
148
|
-
selected === item ? "text-primary font-bold" : "text-text-secondary"
|
|
149
|
-
),
|
|
150
|
-
style: { height: ITEM_HEIGHT },
|
|
151
|
-
"aria-pressed": selected === item,
|
|
152
|
-
"aria-label": `${label} ${item}`,
|
|
153
|
-
children: item
|
|
154
|
-
},
|
|
155
|
-
item
|
|
156
|
-
)),
|
|
157
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { height: ITEM_HEIGHT * 2 } })
|
|
158
|
-
]
|
|
159
|
-
}
|
|
160
|
-
)
|
|
161
|
-
] }),
|
|
162
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
163
|
-
"button",
|
|
164
|
-
{
|
|
165
|
-
type: "button",
|
|
166
|
-
onClick: () => {
|
|
167
|
-
step(1);
|
|
168
|
-
},
|
|
169
|
-
className: "p-1 rounded hover:bg-hover text-text-secondary hover:text-text transition-colors",
|
|
170
|
-
"aria-label": `Increase ${label}`,
|
|
171
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(outline.ChevronDownIcon, { className: "w-4 h-4" })
|
|
172
|
-
}
|
|
173
|
-
)
|
|
174
|
-
] });
|
|
175
|
-
}
|
|
176
105
|
function AddonIcon({ children, side, size, onClick, asButton = false, ariaLabel }) {
|
|
177
106
|
const iconSize = {
|
|
178
|
-
xs: "w-3
|
|
179
|
-
sm: "w-
|
|
107
|
+
xs: "w-3 h-3",
|
|
108
|
+
sm: "w-3.5 h-3.5",
|
|
180
109
|
md: "w-4 h-4",
|
|
181
|
-
lg: "w-5 h-5",
|
|
110
|
+
lg: "w-4.5 h-4.5",
|
|
182
111
|
xl: "w-5 h-5"
|
|
183
112
|
};
|
|
184
113
|
const base = chunkL5VQZZVR_cjs.cn(
|
|
@@ -192,7 +121,7 @@ function AddonIcon({ children, side, size, onClick, asButton = false, ariaLabel
|
|
|
192
121
|
{
|
|
193
122
|
type: "button",
|
|
194
123
|
onClick,
|
|
195
|
-
className: chunkL5VQZZVR_cjs.cn(base, "cursor-pointer hover:text-text transition-colors"),
|
|
124
|
+
className: chunkL5VQZZVR_cjs.cn(base, "cursor-pointer hover:text-text transition-colors z-20"),
|
|
196
125
|
tabIndex: -1,
|
|
197
126
|
"aria-label": ariaLabel,
|
|
198
127
|
children
|
|
@@ -203,7 +132,7 @@ function AddonIcon({ children, side, size, onClick, asButton = false, ariaLabel
|
|
|
203
132
|
"span",
|
|
204
133
|
{
|
|
205
134
|
"aria-hidden": !ariaLabel,
|
|
206
|
-
className: chunkL5VQZZVR_cjs.cn(base, "pointer-events-none"),
|
|
135
|
+
className: chunkL5VQZZVR_cjs.cn(base, "pointer-events-none z-20"),
|
|
207
136
|
"aria-label": ariaLabel,
|
|
208
137
|
children
|
|
209
138
|
}
|
|
@@ -264,21 +193,43 @@ var TimePicker = react.forwardRef(
|
|
|
264
193
|
const helperId = `${inputId}-helper`;
|
|
265
194
|
const stateMessageId = `${inputId}-state`;
|
|
266
195
|
const popoverId = `${inputId}-popover`;
|
|
196
|
+
const internalRef = react.useRef(null);
|
|
197
|
+
const inputRef = react.useRef(null);
|
|
198
|
+
const dropdownRef = react.useRef(null);
|
|
199
|
+
const debounceTimerRef = react.useRef(null);
|
|
267
200
|
const isControlled = value !== void 0;
|
|
268
201
|
const [internalValue, setInternalValue] = react.useState(defaultValue ?? "");
|
|
269
202
|
const displayValue = isControlled ? value : internalValue;
|
|
270
|
-
const { hours: selHours, minutes: selMinutes } = parseTime(displayValue);
|
|
271
|
-
const selAmpm = selHours >= 12 ? "PM" : "AM";
|
|
272
|
-
const selHours12 = selHours % 12 || 12;
|
|
273
|
-
const pickerHour = String(use12Hour ? selHours12 : selHours).padStart(2, "0");
|
|
274
|
-
const pickerMinute = String(selMinutes).padStart(2, "0");
|
|
275
|
-
const pickerAmpm = selAmpm;
|
|
276
203
|
const [isOpen, setIsOpen] = react.useState(false);
|
|
204
|
+
const [typedValue, setTypedValue] = react.useState(null);
|
|
205
|
+
const displayInputValue = typedValue ?? formatDisplay(displayValue, use12Hour);
|
|
206
|
+
const timeSlots = generateTimeSlots(minuteStep, use12Hour);
|
|
207
|
+
const filteredSlots = timeSlots.filter((slot) => {
|
|
208
|
+
if (!displayInputValue) return true;
|
|
209
|
+
const typedClean = displayInputValue.replace(/[^0-9]/g, "");
|
|
210
|
+
const slotLabelClean = slot.label.replace(/[^0-9]/g, "");
|
|
211
|
+
const slotValueClean = slot.value.replace(/[^0-9]/g, "");
|
|
212
|
+
return slot.label.toLowerCase().includes(displayInputValue.toLowerCase()) || slot.value.includes(displayInputValue) || slotLabelClean.includes(typedClean) || slotValueClean.includes(typedClean);
|
|
213
|
+
});
|
|
214
|
+
react.useEffect(() => {
|
|
215
|
+
if (isOpen && dropdownRef.current) {
|
|
216
|
+
const activeItem = dropdownRef.current.querySelector('[data-active="true"]');
|
|
217
|
+
if (activeItem) {
|
|
218
|
+
activeItem.scrollIntoView({ block: "nearest" });
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}, [isOpen]);
|
|
222
|
+
react.useEffect(() => {
|
|
223
|
+
return () => {
|
|
224
|
+
if (debounceTimerRef.current) {
|
|
225
|
+
clearTimeout(debounceTimerRef.current);
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
}, []);
|
|
277
229
|
react.useEffect(() => {
|
|
278
230
|
if (!isOpen) return;
|
|
279
231
|
function handleClick(e) {
|
|
280
|
-
|
|
281
|
-
if (wrapper && !wrapper.contains(e.target)) {
|
|
232
|
+
if (internalRef.current && !internalRef.current.contains(e.target)) {
|
|
282
233
|
setIsOpen(false);
|
|
283
234
|
}
|
|
284
235
|
}
|
|
@@ -286,45 +237,91 @@ var TimePicker = react.forwardRef(
|
|
|
286
237
|
return () => {
|
|
287
238
|
document.removeEventListener("mousedown", handleClick);
|
|
288
239
|
};
|
|
289
|
-
}, [isOpen
|
|
290
|
-
const
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
(_, i) => String(i * minuteStep).padStart(2, "0")
|
|
294
|
-
);
|
|
295
|
-
const applyTime = react.useCallback(
|
|
296
|
-
(h, m, ap) => {
|
|
297
|
-
let hours24 = parseInt(h, 10);
|
|
298
|
-
if (use12Hour) {
|
|
299
|
-
if (ap === "AM" && hours24 === 12) hours24 = 0;
|
|
300
|
-
if (ap === "PM" && hours24 !== 12) hours24 += 12;
|
|
301
|
-
}
|
|
302
|
-
const iso = toTimeString(hours24, parseInt(m, 10));
|
|
303
|
-
if (!isControlled) setInternalValue(iso);
|
|
304
|
-
onChange?.(iso);
|
|
305
|
-
onTimeChange?.(iso);
|
|
306
|
-
},
|
|
307
|
-
[isControlled, onChange, onTimeChange, use12Hour]
|
|
308
|
-
);
|
|
309
|
-
const handleHourSelect = (h) => {
|
|
310
|
-
applyTime(h, pickerMinute, pickerAmpm);
|
|
311
|
-
};
|
|
312
|
-
const handleMinuteSelect = (m) => {
|
|
313
|
-
applyTime(pickerHour, m, pickerAmpm);
|
|
314
|
-
};
|
|
315
|
-
const handleAmpmSelect = (ap) => {
|
|
316
|
-
const a = ap;
|
|
317
|
-
applyTime(pickerHour, pickerMinute, a);
|
|
318
|
-
};
|
|
319
|
-
const handleClear = () => {
|
|
240
|
+
}, [isOpen]);
|
|
241
|
+
const handleClear = (e) => {
|
|
242
|
+
e.stopPropagation();
|
|
243
|
+
if (debounceTimerRef.current) clearTimeout(debounceTimerRef.current);
|
|
320
244
|
if (!isControlled) setInternalValue("");
|
|
321
245
|
onChange?.("");
|
|
322
246
|
onTimeChange?.(null);
|
|
247
|
+
setTypedValue(null);
|
|
323
248
|
};
|
|
324
|
-
const toggleOpen = () => {
|
|
249
|
+
const toggleOpen = (e) => {
|
|
250
|
+
e.stopPropagation();
|
|
325
251
|
if (disabled || isLoading) return;
|
|
326
252
|
setIsOpen((prev) => !prev);
|
|
327
253
|
};
|
|
254
|
+
const handleInputChange = (e) => {
|
|
255
|
+
let val = e.target.value;
|
|
256
|
+
val = val.replace(/[^0-9:]/g, "");
|
|
257
|
+
if (val.length > 5) {
|
|
258
|
+
val = val.slice(0, 5);
|
|
259
|
+
}
|
|
260
|
+
const currentLength = typedValue?.length ?? 0;
|
|
261
|
+
if (val.length === 2 && !val.includes(":") && val.length > currentLength) {
|
|
262
|
+
val = val + ":";
|
|
263
|
+
}
|
|
264
|
+
if (val.length === 5) {
|
|
265
|
+
const parts = val.split(":");
|
|
266
|
+
if (parts[0] && parts[1]) {
|
|
267
|
+
let hours = parseInt(parts[0], 10);
|
|
268
|
+
let minutes = parseInt(parts[1], 10);
|
|
269
|
+
if (hours > 23) hours = 23;
|
|
270
|
+
if (minutes > 59) minutes = 59;
|
|
271
|
+
val = `${String(hours).padStart(2, "0")}:${String(minutes).padStart(2, "0")}`;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
setTypedValue(val);
|
|
275
|
+
if (debounceTimerRef.current) {
|
|
276
|
+
clearTimeout(debounceTimerRef.current);
|
|
277
|
+
}
|
|
278
|
+
debounceTimerRef.current = setTimeout(() => {
|
|
279
|
+
const parsed = parseTypedTime(val);
|
|
280
|
+
if (parsed) {
|
|
281
|
+
if (!isControlled) setInternalValue(parsed);
|
|
282
|
+
onChange?.(parsed);
|
|
283
|
+
onTimeChange?.(parsed);
|
|
284
|
+
}
|
|
285
|
+
}, 1e3);
|
|
286
|
+
};
|
|
287
|
+
const handleKeyDown = (e) => {
|
|
288
|
+
const allowedKeys = [
|
|
289
|
+
"Backspace",
|
|
290
|
+
"Delete",
|
|
291
|
+
"ArrowLeft",
|
|
292
|
+
"ArrowRight",
|
|
293
|
+
"Tab",
|
|
294
|
+
"Escape",
|
|
295
|
+
"Enter"
|
|
296
|
+
];
|
|
297
|
+
if (allowedKeys.includes(e.key) || e.ctrlKey || e.metaKey) {
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
if (!/[0-9:]/.test(e.key)) {
|
|
301
|
+
e.preventDefault();
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
const handleInputBlur = () => {
|
|
305
|
+
setTimeout(() => {
|
|
306
|
+
if (debounceTimerRef.current) {
|
|
307
|
+
clearTimeout(debounceTimerRef.current);
|
|
308
|
+
}
|
|
309
|
+
const finalVal = typedValue ?? displayValue;
|
|
310
|
+
const parsed = parseTypedTime(finalVal);
|
|
311
|
+
if (parsed) {
|
|
312
|
+
if (!isControlled) setInternalValue(parsed);
|
|
313
|
+
onChange?.(parsed);
|
|
314
|
+
onTimeChange?.(parsed);
|
|
315
|
+
}
|
|
316
|
+
setTypedValue(null);
|
|
317
|
+
}, 150);
|
|
318
|
+
};
|
|
319
|
+
const handleInputFocus = () => {
|
|
320
|
+
setTypedValue(displayValue);
|
|
321
|
+
if (!disabled && !isLoading) {
|
|
322
|
+
setIsOpen(true);
|
|
323
|
+
}
|
|
324
|
+
};
|
|
328
325
|
const showClear = clearable && displayValue.length > 0 && !disabled && !isLoading;
|
|
329
326
|
const rightPad = {
|
|
330
327
|
xs: "pr-6",
|
|
@@ -338,7 +335,14 @@ var TimePicker = react.forwardRef(
|
|
|
338
335
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
339
336
|
"div",
|
|
340
337
|
{
|
|
341
|
-
ref
|
|
338
|
+
ref: (node) => {
|
|
339
|
+
internalRef.current = node;
|
|
340
|
+
if (typeof ref === "function") {
|
|
341
|
+
ref(node);
|
|
342
|
+
} else if (ref) {
|
|
343
|
+
ref.current = node;
|
|
344
|
+
}
|
|
345
|
+
},
|
|
342
346
|
className: chunkL5VQZZVR_cjs.cn(
|
|
343
347
|
"flex flex-col gap-1 relative",
|
|
344
348
|
fullWidth ? "w-full" : "w-fit",
|
|
@@ -370,33 +374,35 @@ var TimePicker = react.forwardRef(
|
|
|
370
374
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex flex-1 items-center", children: [
|
|
371
375
|
/* @__PURE__ */ jsxRuntime.jsx(AddonIcon, { side: "left", size, children: leftElement ?? /* @__PURE__ */ jsxRuntime.jsx(outline.ClockIcon, { className: "w-full h-full" }) }),
|
|
372
376
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
373
|
-
"
|
|
377
|
+
"input",
|
|
374
378
|
{
|
|
379
|
+
ref: inputRef,
|
|
375
380
|
id: inputId,
|
|
376
|
-
type: "
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
+
type: "text",
|
|
382
|
+
value: displayInputValue,
|
|
383
|
+
onChange: handleInputChange,
|
|
384
|
+
onKeyDown: handleKeyDown,
|
|
385
|
+
onFocus: handleInputFocus,
|
|
386
|
+
onBlur: handleInputBlur,
|
|
387
|
+
placeholder,
|
|
381
388
|
"aria-label": ariaLabel ?? label ?? "Time picker",
|
|
382
389
|
"aria-describedby": describedBy,
|
|
383
390
|
"aria-required": required,
|
|
384
391
|
"aria-invalid": state === "error" ? true : void 0,
|
|
385
392
|
disabled: Boolean(disabled) || Boolean(isLoading),
|
|
386
|
-
onClick: toggleOpen,
|
|
387
393
|
name,
|
|
388
394
|
className: chunkL5VQZZVR_cjs.cn(
|
|
389
|
-
"w-full flex items-center text-left transition-colors duration-150
|
|
395
|
+
"w-full flex items-center text-left transition-colors duration-150",
|
|
390
396
|
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
397
|
+
"focus:outline-none focus:ring-0",
|
|
391
398
|
sizeClasses[size],
|
|
392
399
|
variantClasses[variant],
|
|
393
400
|
stateVariantClasses[state][variant],
|
|
394
401
|
"pl-9",
|
|
395
|
-
|
|
402
|
+
rightPad[size],
|
|
396
403
|
prefix !== void 0 && suffix !== void 0 ? "rounded-none" : prefix !== void 0 ? "rounded-l-none" : suffix !== void 0 ? "rounded-r-none" : "",
|
|
397
404
|
inputClassName
|
|
398
|
-
)
|
|
399
|
-
children: displayValue ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-text", children: formatDisplay(displayValue, use12Hour) }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-text-muted", children: placeholder })
|
|
405
|
+
)
|
|
400
406
|
}
|
|
401
407
|
),
|
|
402
408
|
isLoading ? /* @__PURE__ */ jsxRuntime.jsx(AddonIcon, { side: "right", size, children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -438,12 +444,68 @@ var TimePicker = react.forwardRef(
|
|
|
438
444
|
ariaLabel: "Clear time",
|
|
439
445
|
children: /* @__PURE__ */ jsxRuntime.jsx(outline.XMarkIcon, { className: "w-full h-full", strokeWidth: 2.5 })
|
|
440
446
|
}
|
|
441
|
-
) : rightElement ? /* @__PURE__ */ jsxRuntime.jsx(AddonIcon, { side: "right", size, children: rightElement }) :
|
|
447
|
+
) : rightElement ? /* @__PURE__ */ jsxRuntime.jsx(AddonIcon, { side: "right", size, children: rightElement }) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
448
|
+
AddonIcon,
|
|
449
|
+
{
|
|
450
|
+
side: "right",
|
|
451
|
+
size,
|
|
452
|
+
asButton: true,
|
|
453
|
+
onClick: toggleOpen,
|
|
454
|
+
ariaLabel: "Open time picker dropdown",
|
|
455
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(outline.ChevronDownIcon, { className: "w-full h-full text-text-secondary/70 hover:text-text transition-colors" })
|
|
456
|
+
}
|
|
457
|
+
)
|
|
442
458
|
] }),
|
|
443
459
|
suffix !== void 0 && /* @__PURE__ */ jsxRuntime.jsx(Addon, { side: "right", children: suffix })
|
|
444
460
|
]
|
|
445
461
|
}
|
|
446
462
|
),
|
|
463
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
464
|
+
"div",
|
|
465
|
+
{
|
|
466
|
+
id: popoverId,
|
|
467
|
+
ref: dropdownRef,
|
|
468
|
+
role: "listbox",
|
|
469
|
+
"aria-label": "Time options",
|
|
470
|
+
className: chunkL5VQZZVR_cjs.cn(
|
|
471
|
+
"absolute top-full left-0 z-50 mt-1.5 w-full max-h-60 overflow-y-auto",
|
|
472
|
+
"bg-surface/90 backdrop-blur-md border border-border/80 rounded-xl shadow-xl py-1.5",
|
|
473
|
+
"transition-all duration-200 origin-top ease-out",
|
|
474
|
+
isOpen ? "opacity-100 scale-100 pointer-events-auto translate-y-0" : "opacity-0 scale-95 pointer-events-none -translate-y-1"
|
|
475
|
+
),
|
|
476
|
+
children: filteredSlots.length > 0 ? filteredSlots.map((slot) => {
|
|
477
|
+
const isSelected = slot.value === displayValue;
|
|
478
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
479
|
+
"button",
|
|
480
|
+
{
|
|
481
|
+
type: "button",
|
|
482
|
+
"data-active": isSelected,
|
|
483
|
+
onMouseDown: (e) => {
|
|
484
|
+
e.preventDefault();
|
|
485
|
+
},
|
|
486
|
+
onClick: () => {
|
|
487
|
+
if (debounceTimerRef.current) clearTimeout(debounceTimerRef.current);
|
|
488
|
+
if (!isControlled) setInternalValue(slot.value);
|
|
489
|
+
onChange?.(slot.value);
|
|
490
|
+
onTimeChange?.(slot.value);
|
|
491
|
+
setTypedValue(null);
|
|
492
|
+
setIsOpen(false);
|
|
493
|
+
},
|
|
494
|
+
className: chunkL5VQZZVR_cjs.cn(
|
|
495
|
+
"w-full text-left px-4 py-2.5 text-sm transition-all duration-normal cursor-pointer flex items-center justify-between",
|
|
496
|
+
"hover:bg-primary/5 hover:text-primary hover:pl-5 font-medium",
|
|
497
|
+
isSelected ? "bg-primary/10 text-primary font-bold pl-5" : "text-text-secondary"
|
|
498
|
+
),
|
|
499
|
+
children: [
|
|
500
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: slot.label }),
|
|
501
|
+
isSelected && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "w-1.5 h-1.5 rounded-full bg-primary animate-pulse" })
|
|
502
|
+
]
|
|
503
|
+
},
|
|
504
|
+
slot.value
|
|
505
|
+
);
|
|
506
|
+
}) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-center text-xs text-text-muted py-3", children: "No matching times found" })
|
|
507
|
+
}
|
|
508
|
+
),
|
|
447
509
|
helperText !== void 0 && /* @__PURE__ */ jsxRuntime.jsx(
|
|
448
510
|
"p",
|
|
449
511
|
{
|
|
@@ -464,65 +526,6 @@ var TimePicker = react.forwardRef(
|
|
|
464
526
|
),
|
|
465
527
|
children: stateMessage
|
|
466
528
|
}
|
|
467
|
-
),
|
|
468
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
469
|
-
"div",
|
|
470
|
-
{
|
|
471
|
-
id: popoverId,
|
|
472
|
-
role: "dialog",
|
|
473
|
-
"aria-label": "Time picker",
|
|
474
|
-
"aria-modal": "true",
|
|
475
|
-
className: chunkL5VQZZVR_cjs.cn(
|
|
476
|
-
"absolute top-full left-0 z-50 mt-1",
|
|
477
|
-
"bg-surface border border-border rounded-xl shadow-xl",
|
|
478
|
-
"transition-all duration-200 origin-top",
|
|
479
|
-
isOpen ? "opacity-100 scale-100 pointer-events-auto" : "opacity-0 scale-95 pointer-events-none"
|
|
480
|
-
),
|
|
481
|
-
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-4", children: [
|
|
482
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs font-bold text-text-muted uppercase tracking-widest mb-3 text-center", children: "Select Time" }),
|
|
483
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-1", children: [
|
|
484
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
485
|
-
ScrollColumn,
|
|
486
|
-
{
|
|
487
|
-
items: hourItems,
|
|
488
|
-
selected: pickerHour,
|
|
489
|
-
onSelect: handleHourSelect,
|
|
490
|
-
label: "Hour"
|
|
491
|
-
}
|
|
492
|
-
),
|
|
493
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
494
|
-
"div",
|
|
495
|
-
{
|
|
496
|
-
className: "flex items-center self-center pb-2 text-text-muted font-bold text-lg select-none",
|
|
497
|
-
style: { height: ITEM_HEIGHT },
|
|
498
|
-
children: ":"
|
|
499
|
-
}
|
|
500
|
-
),
|
|
501
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
502
|
-
ScrollColumn,
|
|
503
|
-
{
|
|
504
|
-
items: minuteItems,
|
|
505
|
-
selected: pickerMinute,
|
|
506
|
-
onSelect: handleMinuteSelect,
|
|
507
|
-
label: "Minute"
|
|
508
|
-
}
|
|
509
|
-
),
|
|
510
|
-
use12Hour && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
511
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-px self-stretch bg-border mx-1" }),
|
|
512
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
513
|
-
ScrollColumn,
|
|
514
|
-
{
|
|
515
|
-
items: ["AM", "PM"],
|
|
516
|
-
selected: pickerAmpm,
|
|
517
|
-
onSelect: handleAmpmSelect,
|
|
518
|
-
label: "AM/PM"
|
|
519
|
-
}
|
|
520
|
-
)
|
|
521
|
-
] })
|
|
522
|
-
] }),
|
|
523
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-3 pt-3 border-t border-border text-center", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-bold text-primary font-mono", children: displayValue ? formatDisplay(displayValue, use12Hour) : "\u2013\u2013:\u2013\u2013" }) })
|
|
524
|
-
] })
|
|
525
|
-
}
|
|
526
529
|
)
|
|
527
530
|
]
|
|
528
531
|
}
|