@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.
@@ -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
- const wrapper = ref && typeof ref === "object" && "current" in ref ? ref.current : null;
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, useRef } from 'react';
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
- const wrapper = ref && typeof ref === "object" && "current" in ref ? ref.current : null;
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 parseTime(value) {
9
- if (!value) return { hours: 0, minutes: 0 };
10
- const numbers = value.split(":").map(Number);
11
- const h = numbers[0];
12
- const m = numbers[1];
13
- return {
14
- hours: h === void 0 || isNaN(h) ? 0 : h,
15
- minutes: m === void 0 || isNaN(m) ? 0 : m
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 toTimeString(hours, minutes) {
19
- return `${String(hours).padStart(2, "0")}:${String(minutes).padStart(2, "0")}`;
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(value, use12Hour) {
22
- if (!value) return "";
23
- const { hours, minutes } = parseTime(value);
24
- if (use12Hour) {
25
- const ampm = hours >= 12 ? "PM" : "AM";
26
- const h = hours % 12 || 12;
27
- return `${String(h).padStart(2, "0")}:${String(minutes).padStart(2, "0")} ${ampm}`;
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 toTimeString(hours, minutes);
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.5 h-3.5",
179
- sm: "w-4 h-4",
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
- const wrapper = ref && typeof ref === "object" && "current" in ref ? ref.current : null;
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, ref]);
290
- const hourItems = use12Hour ? Array.from({ length: 12 }, (_, i) => String(i + 1).padStart(2, "0")) : Array.from({ length: 24 }, (_, i) => String(i).padStart(2, "0"));
291
- const minuteItems = Array.from(
292
- { length: Math.ceil(60 / minuteStep) },
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
- "button",
377
+ "input",
374
378
  {
379
+ ref: inputRef,
375
380
  id: inputId,
376
- type: "button",
377
- role: "combobox",
378
- "aria-expanded": isOpen,
379
- "aria-haspopup": "dialog",
380
- "aria-controls": popoverId,
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 cursor-pointer",
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
- showClear || rightElement ? rightPad[size] : "",
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 }) : null
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
  }