@superleapai/flow-ui 2.5.4 → 2.5.5

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.
@@ -88,7 +88,7 @@
88
88
  var wrapper = document.createElement("div");
89
89
  wrapper.setAttribute("role", "radiogroup");
90
90
  wrapper.setAttribute("dir", "ltr");
91
- wrapper.className = join("flex flex-col gap-3 w-full", className);
91
+ wrapper.className = join("flex flex-col gap-8 w-full", className);
92
92
 
93
93
  function updateAllCards(newValue) {
94
94
  var cards = wrapper.querySelectorAll("[data-card-value]");
@@ -35,10 +35,37 @@
35
35
  return Array.prototype.filter.call(arguments, Boolean).join(" ");
36
36
  }
37
37
 
38
+ // Mirror input wrapper variant + size behaviour for visual consistency
39
+ var TRIGGER_WRAPPER_CLASS = {
40
+ base:
41
+ "group flex items-center border-1/2 border-border-primary rounded-4 text-typography-primary-text gap-x-8 w-full transition-all ease-in-out group-has-[:disabled]:cursor-not-allowed group-has-[:disabled]:border-border-primary group-has-[:disabled]:bg-fill-tertiary-fill-light-gray group-has-[:disabled]:text-typography-quaternary-text group-has-[:disabled]:hover:border-border-primary",
42
+ default:
43
+ "bg-fill-quarternary-fill-white hover:border-primary-base focus-within:border-primary-base",
44
+ error:
45
+ "border-error-base bg-fill-quarternary-fill-white hover:border-error-base focus-within:border-error-base",
46
+ warning:
47
+ "border-warning-base bg-fill-quarternary-fill-white hover:border-warning-base focus-within:border-warning-base",
48
+ success:
49
+ "border-success-base bg-fill-quarternary-fill-white hover:border-success-base focus-within:border-success-base",
50
+ borderless:
51
+ "border-none shadow-none rounded-0 bg-fill-quarternary-fill-white",
52
+ inline:
53
+ "border-transparent shadow-none rounded-0 bg-fill-quarternary-fill-white hover:bg-fill-tertiary-fill-light-gray focus-within:border-transparent focus:bg-fill-tertiary-fill-light-gray focus-within:bg-fill-tertiary-fill-light-gray",
54
+ sizeDefault: "px-12 py-6",
55
+ sizeLarge: "px-12 py-8",
56
+ sizeSmall: "px-12 py-4",
57
+ disabled:
58
+ "cursor-not-allowed border-border-primary bg-fill-tertiary-fill-light-gray text-typography-quaternary-text hover:border-border-primary",
59
+ };
60
+
38
61
  /**
39
62
  * Create the calendar caption (month + year selects) and nav buttons
63
+ * @param {Object} [options] - Optional. nestedPopoverClass: string to add to Select/TimePicker popover wrappers so parent can ignore outside click
40
64
  */
41
- function createCaption(displayMonth, fromDate, toDate, yearRange, onMonthChange, onPrev, onNext) {
65
+ function createCaption(displayMonth, fromDate, toDate, yearRange, onMonthChange, onPrev, onNext, options) {
66
+ options = options || {};
67
+ var nestedPopoverClass = options.nestedPopoverClass || "";
68
+ var closeTimePickerFn = null;
42
69
  var Utils = global.DateTimePickerUtils;
43
70
  if (!Utils) throw new Error("DateTimePickerUtils required");
44
71
  var MONTHS = Utils.genMonths();
@@ -49,6 +76,8 @@
49
76
  var captionWrap = document.createElement("div");
50
77
  captionWrap.className = "flex justify-between relative items-center p-6";
51
78
 
79
+ var monthSelect = null;
80
+ var yearSelect = null;
52
81
  var Select = getDep("Select");
53
82
  if (Select && typeof Select.create === "function") {
54
83
  var monthValue = displayMonth.getMonth();
@@ -61,22 +90,32 @@
61
90
  });
62
91
  var selectWrap = document.createElement("div");
63
92
  selectWrap.className = "inline-flex gap-x-8";
64
- var monthSelect = Select.create({
93
+ monthSelect = Select.create({
65
94
  options: monthOptions,
66
95
  value: monthValue,
67
96
  placeholder: "Month",
68
97
  size: "small",
98
+ dropdownMaxHeightVh: 20,
99
+ popoverWrapperClassName: nestedPopoverClass,
100
+ onOpenCallback: function () {
101
+ if (closeTimePickerFn) closeTimePickerFn();
102
+ },
69
103
  onChange: function (val) {
70
104
  var d = new Date(displayMonth.getTime());
71
105
  d.setMonth(Number(val));
72
106
  onMonthChange(d);
73
107
  },
74
108
  });
75
- var yearSelect = Select.create({
109
+ yearSelect = Select.create({
76
110
  options: yearOptions,
77
111
  value: yearValue,
78
112
  placeholder: "Year",
79
113
  size: "small",
114
+ dropdownMaxHeightVh: 20,
115
+ popoverWrapperClassName: nestedPopoverClass,
116
+ onOpenCallback: function () {
117
+ if (closeTimePickerFn) closeTimePickerFn();
118
+ },
80
119
  onChange: function (val) {
81
120
  var d = new Date(displayMonth.getTime());
82
121
  d.setFullYear(Number(val));
@@ -88,13 +127,18 @@
88
127
  captionWrap.appendChild(selectWrap);
89
128
  }
90
129
 
130
+ function closeDropdowns() {
131
+ if (monthSelect && monthSelect.popoverInstance) monthSelect.popoverInstance.hide();
132
+ if (yearSelect && yearSelect.popoverInstance) yearSelect.popoverInstance.hide();
133
+ }
134
+
91
135
  var Button = getDep("Button");
92
136
  if (!Button || typeof Button.create !== "function") {
93
137
  var fallbackNav = document.createElement("div");
94
138
  fallbackNav.className = "flex items-center gap-x-8";
95
139
  fallbackNav.textContent = "Button component required";
96
140
  captionWrap.appendChild(fallbackNav);
97
- return { element: captionWrap, setMonth: function () {} };
141
+ return { element: captionWrap, setMonth: function () {}, closeDropdowns: function () {}, setCloseTimePicker: function () {} };
98
142
  }
99
143
  var nav = document.createElement("div");
100
144
  nav.className = "flex items-center gap-x-8";
@@ -120,6 +164,36 @@
120
164
  nav.appendChild(nextBtn);
121
165
  captionWrap.appendChild(nav);
122
166
 
167
+ /**
168
+ * Keep nav chevron disabled state in sync with current month
169
+ * so that when we move away from fromDate/toDate month they re-enable.
170
+ */
171
+ function updateNavDisabled(d) {
172
+ var prevDisabled = Utils.isPreviousMonthDisabled(d, fromDate);
173
+ var nextDisabled = Utils.isNextMonthDisabled(d, toDate);
174
+
175
+ if (prevBtn) {
176
+ prevBtn.disabled = prevDisabled;
177
+ if (prevDisabled) {
178
+ prevBtn.setAttribute("disabled", "disabled");
179
+ } else {
180
+ prevBtn.removeAttribute("disabled");
181
+ }
182
+ }
183
+
184
+ if (nextBtn) {
185
+ nextBtn.disabled = nextDisabled;
186
+ if (nextDisabled) {
187
+ nextBtn.setAttribute("disabled", "disabled");
188
+ } else {
189
+ nextBtn.removeAttribute("disabled");
190
+ }
191
+ }
192
+ }
193
+
194
+ // Ensure initial state is correct
195
+ updateNavDisabled(displayMonth);
196
+
123
197
  return {
124
198
  element: captionWrap,
125
199
  setMonth: function (d) {
@@ -128,6 +202,11 @@
128
202
  if (selects[0].updateValue) selects[0].updateValue(d.getMonth());
129
203
  if (selects[1].updateValue) selects[1].updateValue(d.getFullYear());
130
204
  }
205
+ updateNavDisabled(d);
206
+ },
207
+ closeDropdowns: closeDropdowns,
208
+ setCloseTimePicker: function (fn) {
209
+ closeTimePickerFn = fn;
131
210
  },
132
211
  };
133
212
  }
@@ -244,11 +323,19 @@
244
323
 
245
324
  var triggerWrapper = document.createElement("div");
246
325
  function getTriggerClassName(disabledState) {
326
+ var sizeClass =
327
+ size === "large"
328
+ ? TRIGGER_WRAPPER_CLASS.sizeLarge
329
+ : size === "small"
330
+ ? TRIGGER_WRAPPER_CLASS.sizeSmall
331
+ : TRIGGER_WRAPPER_CLASS.sizeDefault;
332
+
247
333
  return join(
248
- "group flex items-center border-1/2 border-border-primary rounded-4 text-typography-primary-text gap-x-8 w-full transition-all ease-in-out",
249
- "bg-fill-quarternary-fill-white hover:border-primary-base focus-within:border-primary-base",
250
- size === "large" ? "px-12 py-8" : size === "small" ? "px-12 py-4" : "px-12 py-6",
251
- disabledState ? "pointer-events-none cursor-not-allowed border-border-primary bg-fill-tertiary-fill-light-gray text-typography-quaternary-text hover:border-border-primary" : "cursor-pointer"
334
+ TRIGGER_WRAPPER_CLASS.base,
335
+ disabledState
336
+ ? TRIGGER_WRAPPER_CLASS.disabled
337
+ : TRIGGER_WRAPPER_CLASS[variant] || TRIGGER_WRAPPER_CLASS.default,
338
+ sizeClass
252
339
  );
253
340
  }
254
341
  triggerWrapper.className = getTriggerClassName(disabled);
@@ -289,8 +376,14 @@
289
376
  var calendarEl;
290
377
  var timePickerEl;
291
378
  var quickRow;
379
+ var isTimeDropdownOpen = false;
292
380
 
293
381
  function handleSelectDay(dayDate) {
382
+ // If time picker dropdown is open, close it first and ignore this click.
383
+ if (isTimeDropdownOpen) {
384
+ if (timePickerEl && timePickerEl.closeDropdown) timePickerEl.closeDropdown();
385
+ return;
386
+ }
294
387
  var out;
295
388
  if (granularity === "day") {
296
389
  out = Utils.startOfDay(dayDate);
@@ -339,7 +432,7 @@
339
432
  function buildPanelContent() {
340
433
  var fragment = document.createDocumentFragment();
341
434
 
342
- // Caption (month/year dropdowns then nav chevrons)
435
+ // Caption (month/year dropdowns then nav chevrons); nested popover class so main popover does not close when selecting month/year/time
343
436
  captionEl = createCaption(
344
437
  displayMonth,
345
438
  fromDate,
@@ -363,7 +456,8 @@
363
456
  refreshCalendar();
364
457
  if (captionEl && captionEl.setMonth) captionEl.setMonth(displayMonth);
365
458
  }
366
- }
459
+ },
460
+ { nestedPopoverClass: "dtp-nested-popover" }
367
461
  );
368
462
  fragment.appendChild(captionEl.element);
369
463
 
@@ -392,6 +486,13 @@
392
486
  placeholder: "Time",
393
487
  use24Hour: hourCycle === 24,
394
488
  size: "small",
489
+ popoverWrapperClassName: "dtp-nested-popover",
490
+ onDropdownOpen: function () {
491
+ isTimeDropdownOpen = true;
492
+ },
493
+ onDropdownClose: function () {
494
+ isTimeDropdownOpen = false;
495
+ },
395
496
  onChange: function (timeStr) {
396
497
  var base = value ? new Date(value.getTime()) : new Date();
397
498
  Utils.setTimeFromString(base, timeStr);
@@ -402,6 +503,16 @@
402
503
  },
403
504
  });
404
505
  timeRow.appendChild(timePickerEl);
506
+ // When opening time picker, close month/year selects; when opening month/year, close time picker
507
+ if (captionEl && captionEl.setCloseTimePicker) {
508
+ captionEl.setCloseTimePicker(function () {
509
+ if (timePickerEl && timePickerEl.closeDropdown) timePickerEl.closeDropdown();
510
+ isTimeDropdownOpen = false;
511
+ });
512
+ }
513
+ timePickerEl.addEventListener("click", function () {
514
+ if (captionEl && captionEl.closeDropdowns) captionEl.closeDropdowns();
515
+ }, true);
405
516
  fragment.appendChild(timeRow);
406
517
  }
407
518
 
@@ -416,6 +527,10 @@
416
527
  text: "Today",
417
528
  startIcon: ICON_CALENDAR_DOT,
418
529
  onClick: function () {
530
+ if (isTimeDropdownOpen) {
531
+ if (timePickerEl && timePickerEl.closeDropdown) timePickerEl.closeDropdown();
532
+ return;
533
+ }
419
534
  var t = granularity === "day" ? Utils.startOfDay(new Date()) : new Date();
420
535
  value = Utils.getValidDate(t, fromDate, toDate);
421
536
  validDate = value;
@@ -437,6 +552,10 @@
437
552
  text: "Tomorrow",
438
553
  startIcon: ICON_CALENDAR_UP,
439
554
  onClick: function () {
555
+ if (isTimeDropdownOpen) {
556
+ if (timePickerEl && timePickerEl.closeDropdown) timePickerEl.closeDropdown();
557
+ return;
558
+ }
440
559
  var t = Utils.addDays(new Date(), 1);
441
560
  t = granularity === "day" ? Utils.startOfDay(t) : t;
442
561
  value = Utils.getValidDate(t, fromDate, toDate);
@@ -458,6 +577,10 @@
458
577
  text: "No date",
459
578
  startIcon: ICON_CALENDAR_MINUS,
460
579
  onClick: function () {
580
+ if (isTimeDropdownOpen) {
581
+ if (timePickerEl && timePickerEl.closeDropdown) timePickerEl.closeDropdown();
582
+ return;
583
+ }
461
584
  value = undefined;
462
585
  validDate = undefined;
463
586
  setMonth(new Date());
@@ -479,7 +602,10 @@
479
602
  content: panelInner,
480
603
  placement: "bottom",
481
604
  align: align,
605
+ modal: true,
606
+ zIndex: 998,
482
607
  closeOnClickOutside: true,
608
+ outsideClickIgnoreSelector: ".dtp-nested-popover",
483
609
  onOpen: function () {
484
610
  triggerWrapper.setAttribute("aria-expanded", "true");
485
611
  displayMonth = validDate ? new Date(validDate.getTime()) : new Date();
@@ -490,13 +616,19 @@
490
616
  },
491
617
  });
492
618
 
493
- // Popover already toggles on trigger click; we only block when disabled (use capture so we run first)
619
+ // Popover already toggles on trigger click; we block when disabled, and when time dropdown is open we close it instead of toggling date popover.
494
620
  triggerWrapper.addEventListener(
495
621
  "click",
496
622
  function (e) {
497
623
  if (disabled) {
498
624
  e.preventDefault();
499
625
  e.stopImmediatePropagation();
626
+ return;
627
+ }
628
+ if (isTimeDropdownOpen) {
629
+ e.preventDefault();
630
+ e.stopImmediatePropagation();
631
+ if (timePickerEl && timePickerEl.closeDropdown) timePickerEl.closeDropdown();
500
632
  }
501
633
  },
502
634
  true
@@ -20,6 +20,9 @@
20
20
  * @param {string} [config.bodyClassName] - Optional class for body wrapper (overrides default padding)
21
21
  * @param {string} [config.panelClassName] - Optional class to add to panel (e.g. for width)
22
22
  * @param {boolean} [config.modal=false] - If true, lock body scroll and show backdrop; only popover and trigger are interactive
23
+ * @param {number} [config.zIndex=999] - z-index for the wrapper when modal (use lower e.g. 998 so nested popovers open above)
24
+ * @param {string} [config.wrapperClassName] - Optional class to add to the wrapper (e.g. for nested popovers so parent can ignore outside click)
25
+ * @param {string} [config.outsideClickIgnoreSelector] - If click target is inside this selector, do not close (e.g. ".dtp-nested-popover")
23
26
  * @returns {Object} Popover API {show, hide, destroy, element}
24
27
  */
25
28
  function create(config = {}) {
@@ -35,6 +38,9 @@
35
38
  bodyClassName = "",
36
39
  panelClassName = "",
37
40
  modal = true,
41
+ zIndex = 999,
42
+ wrapperClassName = "",
43
+ outsideClickIgnoreSelector = "",
38
44
  } = config;
39
45
 
40
46
  const triggerEl =
@@ -55,7 +61,8 @@
55
61
 
56
62
  const wrapper = document.createElement("div");
57
63
  wrapper.className =
58
- "fixed z-50 pointer-events-none opacity-0 invisible transition-opacity duration-150 ease-out";
64
+ "fixed z-50 pointer-events-none opacity-0 invisible transition-opacity duration-150 ease-out" +
65
+ (wrapperClassName ? " " + wrapperClassName : "");
59
66
  wrapper.setAttribute("aria-hidden", "true");
60
67
 
61
68
  const panel = document.createElement("div");
@@ -119,7 +126,7 @@
119
126
  });
120
127
  }
121
128
  document.body.appendChild(backdropEl);
122
- wrapper.style.zIndex = "999";
129
+ wrapper.style.zIndex = String(zIndex);
123
130
  }
124
131
 
125
132
  function applyModalClose() {
@@ -190,15 +197,28 @@
190
197
  ? triggerRect.height - panelRect.height
191
198
  : 0;
192
199
 
200
+ // Clear top-placement max-height when not opening above
201
+ if (effectivePlacement !== "top") {
202
+ panel.style.maxHeight = "";
203
+ }
204
+
193
205
  switch (effectivePlacement) {
194
206
  case "bottom":
195
207
  top = triggerRect.bottom + gap;
196
208
  left = triggerRect.left + alignLeft;
197
209
  break;
198
- case "top":
199
- top = triggerRect.top - panelRect.height - gap;
210
+ case "top": {
211
+ // Larger gap when above the trigger so the popover doesn't overlap it.
212
+ const topGap = 24;
213
+ top = triggerRect.top - panelRect.height - topGap;
200
214
  left = triggerRect.left + alignLeft;
215
+ // Cap panel height so when content loads/tall it doesn't grow downward into the trigger.
216
+ const maxHeightAbove = triggerRect.top - topGap - 12;
217
+ if (maxHeightAbove > 0) {
218
+ panel.style.maxHeight = maxHeightAbove + "px";
219
+ }
201
220
  break;
221
+ }
202
222
  case "right":
203
223
  top = triggerRect.top + alignTop;
204
224
  left = triggerRect.right + gap;
@@ -242,6 +262,9 @@
242
262
  applyModalOpen();
243
263
  requestAnimationFrame(function () {
244
264
  position();
265
+ // Set panel to open state before making wrapper visible so we never show
266
+ // the closed state (avoids 2–3 frame flicker of fade-out/zoom-out then open).
267
+ panel.setAttribute("data-state", "open");
245
268
  wrapper.classList.remove(
246
269
  "invisible",
247
270
  "opacity-0",
@@ -258,11 +281,6 @@
258
281
  });
259
282
  resizeObserver.observe(panel);
260
283
  }
261
- requestAnimationFrame(function () {
262
- requestAnimationFrame(function () {
263
- panel.setAttribute("data-state", "open");
264
- });
265
- });
266
284
  });
267
285
  }
268
286
 
@@ -302,13 +320,10 @@
302
320
  }
303
321
 
304
322
  function outsideClick(e) {
305
- if (
306
- wrapper.classList.contains("visible") &&
307
- !wrapper.contains(e.target) &&
308
- !triggerEl.contains(e.target)
309
- ) {
310
- hide();
311
- }
323
+ if (!wrapper.classList.contains("visible")) return;
324
+ if (wrapper.contains(e.target) || triggerEl.contains(e.target)) return;
325
+ if (outsideClickIgnoreSelector && e.target.closest && e.target.closest(outsideClickIgnoreSelector)) return;
326
+ hide();
312
327
  }
313
328
 
314
329
  function handleKeyDown(e) {
@@ -84,6 +84,9 @@
84
84
  var size = config.size || "default";
85
85
  var canClear = !!config.canClear;
86
86
  var onClear = config.onClear;
87
+ var popoverWrapperClassName = config.popoverWrapperClassName || "";
88
+ var onOpenCallback = config.onOpenCallback || null;
89
+ var dropdownMaxHeightVh = config.dropdownMaxHeightVh != null ? config.dropdownMaxHeightVh : 45;
87
90
 
88
91
  var disabled = config.disabled === true;
89
92
  var value =
@@ -193,11 +196,13 @@
193
196
 
194
197
  var content = document.createElement("div");
195
198
  content.setAttribute("role", "listbox");
196
- content.className = "custom-select-content w-full max-h-[45vh] overflow-hidden flex flex-col";
199
+ content.className = "custom-select-content w-full overflow-hidden flex flex-col";
200
+ content.style.maxHeight = dropdownMaxHeightVh + "vh";
197
201
 
198
202
  var optionsList = document.createElement("div");
199
203
  optionsList.className =
200
- "overflow-y-auto max-h-[45vh] p-2 w-full rounded-4 bg-fill-quarternary-fill-white";
204
+ "overflow-y-auto p-2 w-full rounded-4 bg-fill-quarternary-fill-white";
205
+ optionsList.style.maxHeight = dropdownMaxHeightVh + "vh";
201
206
 
202
207
  if (options.length === 0) {
203
208
  var noOpt = document.createElement("div");
@@ -257,11 +262,13 @@
257
262
  closeOnClickOutside: true,
258
263
  bodyClassName: "p-0 overflow-hidden",
259
264
  panelClassName: "min-w-[var(--trigger-width)] max-h-[45vh] overflow-hidden",
265
+ wrapperClassName: popoverWrapperClassName,
260
266
  onOpen: function () {
261
267
  if (disabled) {
262
268
  popover.hide();
263
269
  return;
264
270
  }
271
+ if (onOpenCallback) onOpenCallback();
265
272
  document
266
273
  .querySelectorAll(".custom-select, .record-select, .enum-select, .enum-multiselect, .custom-multiselect, .record-multiselect")
267
274
  .forEach(function (other) {
@@ -47,16 +47,50 @@
47
47
  return pad(h12) + " : " + pad(minute) + " " + amPm;
48
48
  }
49
49
 
50
- function triggerClasses(disabled, hasValue, size) {
51
- var base =
52
- "group flex items-center border-1/2 border-border-primary rounded-4 text-typography-primary-text gap-x-8 w-full transition-all ease-in-out " +
53
- "bg-fill-quarternary-fill-white hover:border-primary-base focus-within:border-primary-base focus:outline-none min-h-0 h-full truncate hover:cursor-pointer ";
54
- var sizeClass = size === "small" ? "px-12 py-4 !text-reg-12" : "px-12 py-6 !text-reg-13";
50
+ // Mirror input wrapper variant + size for visual consistency
51
+ var TRIGGER_CLASS = {
52
+ base:
53
+ "group flex items-center border-1/2 border-border-primary rounded-4 text-typography-primary-text gap-x-8 w-full transition-all ease-in-out focus:outline-none min-h-0 h-full truncate hover:cursor-pointer " +
54
+ "group-has-[:disabled]:cursor-not-allowed group-has-[:disabled]:border-border-primary group-has-[:disabled]:bg-fill-tertiary-fill-light-gray group-has-[:disabled]:text-typography-quaternary-text group-has-[:disabled]:hover:border-border-primary",
55
+ default:
56
+ "bg-fill-quarternary-fill-white hover:border-primary-base focus-within:border-primary-base",
57
+ error:
58
+ "border-error-base bg-fill-quarternary-fill-white hover:border-error-base focus-within:border-error-base",
59
+ warning:
60
+ "border-warning-base bg-fill-quarternary-fill-white hover:border-warning-base focus-within:border-warning-base",
61
+ success:
62
+ "border-success-base bg-fill-quarternary-fill-white hover:border-success-base focus-within:border-success-base",
63
+ borderless:
64
+ "border-none shadow-none rounded-0 bg-fill-quarternary-fill-white",
65
+ inline:
66
+ "border-transparent shadow-none rounded-0 bg-fill-quarternary-fill-white hover:bg-fill-tertiary-fill-light-gray focus-within:border-transparent focus:bg-fill-tertiary-fill-light-gray focus-within:bg-fill-tertiary-fill-light-gray",
67
+ sizeDefault: "px-12 py-6 !text-reg-13",
68
+ sizeSmall: "px-12 py-4 !text-reg-12",
69
+ sizeLarge: "px-12 py-8 !text-reg-14",
70
+ disabled:
71
+ "pointer-events-none cursor-not-allowed border-border-primary bg-fill-tertiary-fill-light-gray text-typography-quaternary-text hover:border-border-primary",
72
+ };
73
+
74
+ function join() {
75
+ return Array.prototype.filter.call(arguments, Boolean).join(" ");
76
+ }
77
+
78
+ function triggerClasses(disabled, hasValue, size, variant) {
79
+ variant = variant || "default";
80
+ var sizeClass =
81
+ size === "small"
82
+ ? TRIGGER_CLASS.sizeSmall
83
+ : size === "large"
84
+ ? TRIGGER_CLASS.sizeLarge
85
+ : TRIGGER_CLASS.sizeDefault;
55
86
  var valueClass = hasValue ? "text-inherit" : "text-typography-quaternary-text";
56
- var disabledClass = disabled
57
- ? " pointer-events-none cursor-not-allowed border-border-primary bg-fill-tertiary-fill-light-gray text-typography-quaternary-text hover:border-border-primary"
58
- : "";
59
- return base + sizeClass + " " + valueClass + disabledClass;
87
+ var variantClass = disabled ? TRIGGER_CLASS.disabled : (TRIGGER_CLASS[variant] || TRIGGER_CLASS.default);
88
+ return join(
89
+ TRIGGER_CLASS.base,
90
+ variantClass,
91
+ sizeClass,
92
+ valueClass
93
+ );
60
94
  }
61
95
 
62
96
  /**
@@ -132,7 +166,8 @@
132
166
  * @param {Function} config.onChange - Change handler (value: string "HH:mm")
133
167
  * @param {boolean} config.disabled - Whether the picker is disabled
134
168
  * @param {boolean} config.use24Hour - Use 24-hour format (default: false, i.e. 12-hour AM/PM)
135
- * @param {string} [config.size] - 'default' | 'small' (matches Input/Select sizes)
169
+ * @param {string} [config.size] - 'default' | 'small' | 'large' (matches Input/Select sizes)
170
+ * @param {string} [config.variant] - 'default' | 'error' | 'warning' | 'success' | 'borderless' | 'inline' (matches Input trigger styles)
136
171
  * @returns {HTMLElement} Time picker container element
137
172
  */
138
173
  function create(config) {
@@ -140,9 +175,13 @@
140
175
  var initialValue = config.value !== undefined ? config.value : "";
141
176
  var placeholder = config.placeholder || "Select time";
142
177
  var onChange = config.onChange;
178
+ var onDropdownOpen = typeof config.onDropdownOpen === "function" ? config.onDropdownOpen : null;
179
+ var onDropdownClose = typeof config.onDropdownClose === "function" ? config.onDropdownClose : null;
143
180
  var disabled = config.disabled === true;
144
181
  var use24Hour = config.use24Hour === true;
145
- var size = config.size === "small" ? "small" : "default";
182
+ var size = config.size === "small" ? "small" : config.size === "large" ? "large" : "default";
183
+ var variant = config.variant || "default";
184
+ var popoverWrapperClassName = config.popoverWrapperClassName || "";
146
185
 
147
186
  var value = typeof initialValue === "string" ? initialValue : "";
148
187
  var parsed = parseValue(value);
@@ -161,7 +200,7 @@
161
200
  trigger.setAttribute("aria-haspopup", "listbox");
162
201
  trigger.setAttribute("aria-expanded", "false");
163
202
  trigger.setAttribute("aria-label", placeholder);
164
- trigger.className = triggerClasses(disabled, !!value, size);
203
+ trigger.className = triggerClasses(disabled, !!value, size, variant);
165
204
 
166
205
  var triggerText = document.createElement("span");
167
206
  triggerText.className =
@@ -192,6 +231,8 @@
192
231
  }
193
232
  var periodOptions = [{ value: "AM", label: "AM" }, { value: "PM", label: "PM" }];
194
233
 
234
+ var isOpen = false;
235
+
195
236
  var hourColVal = use24Hour ? hour : hour12;
196
237
  var hourColumn = createColumn(hourOptions, hourColVal, function (v) {
197
238
  if (use24Hour) {
@@ -207,7 +248,7 @@
207
248
  triggerText.className =
208
249
  "flex-1 truncate text-left tabular-nums " +
209
250
  (value ? "text-inherit" : "text-typography-quaternary-text");
210
- trigger.className = triggerClasses(disabled, !!value, size);
251
+ trigger.className = triggerClasses(disabled, !!value, size, variant);
211
252
  if (onChange) onChange(value);
212
253
  }, true);
213
254
  var minuteColumn = createColumn(minuteOptions, minute, function (v) {
@@ -219,7 +260,7 @@
219
260
  triggerText.className =
220
261
  "flex-1 truncate text-left tabular-nums " +
221
262
  (value ? "text-inherit" : "text-typography-quaternary-text");
222
- trigger.className = triggerClasses(disabled, !!value, size);
263
+ trigger.className = triggerClasses(disabled, !!value, size, variant);
223
264
  if (onChange) onChange(value);
224
265
  }, true);
225
266
  var periodColumn = null;
@@ -234,7 +275,7 @@
234
275
  triggerText.className =
235
276
  "flex-1 truncate text-left tabular-nums " +
236
277
  (value ? "text-inherit" : "text-typography-quaternary-text");
237
- trigger.className = triggerClasses(disabled, !!value, size);
278
+ trigger.className = triggerClasses(disabled, !!value, size, variant);
238
279
  if (onChange) onChange(value);
239
280
  }, false);
240
281
  }
@@ -310,17 +351,29 @@
310
351
  closeOnClickOutside: true,
311
352
  bodyClassName: "p-0",
312
353
  panelClassName: "!w-auto min-w-max",
354
+ wrapperClassName: popoverWrapperClassName,
313
355
  onOpen: function () {
314
356
  trigger.setAttribute("aria-expanded", "true");
315
357
  container.classList.add("open");
358
+ isOpen = true;
359
+ if (onDropdownOpen) onDropdownOpen();
316
360
  syncColumnsAndScroll();
317
361
  },
318
362
  onClose: function () {
319
363
  trigger.setAttribute("aria-expanded", "false");
320
364
  container.classList.remove("open");
365
+ isOpen = false;
366
+ if (onDropdownClose) onDropdownClose();
321
367
  },
322
368
  });
323
369
  hidePopover = popover.hide;
370
+ container.closeDropdown = function () {
371
+ popover.hide();
372
+ };
373
+
374
+ container.isDropdownOpen = function () {
375
+ return isOpen;
376
+ };
324
377
 
325
378
  container.updateValue = function (newVal) {
326
379
  value = typeof newVal === "string" ? newVal : "";
@@ -333,7 +386,7 @@
333
386
  triggerText.className =
334
387
  "flex-1 truncate text-left tabular-nums " +
335
388
  (value ? "text-inherit" : "text-typography-quaternary-text");
336
- trigger.className = triggerClasses(disabled, !!value, size);
389
+ trigger.className = triggerClasses(disabled, !!value, size, variant);
337
390
  hourColumn.setSelected(use24Hour ? hour : hour12);
338
391
  minuteColumn.setSelected(minute);
339
392
  if (periodColumn) periodColumn.setSelected(period);
@@ -342,7 +395,7 @@
342
395
  container.setDisabled = function (isDisabled) {
343
396
  disabled = !!isDisabled;
344
397
  trigger.disabled = disabled;
345
- trigger.className = triggerClasses(disabled, !!value, size);
398
+ trigger.className = triggerClasses(disabled, !!value, size, variant);
346
399
  if (disabled) popover.hide();
347
400
  };
348
401
 
package/core/flow.js CHANGED
@@ -386,10 +386,13 @@
386
386
  * @param {Function} config.onChange - Optional change handler
387
387
  * @param {boolean} config.disabled - Whether time picker is disabled
388
388
  * @param {boolean} config.use24Hour - Use 24-hour format (default: false, i.e. 12-hour AM/PM)
389
+ * @param {string} [config.size] - 'default' | 'small' | 'large'
390
+ * @param {string} [config.variant] - 'default' | 'error' | 'warning' | 'success' | 'borderless' | 'inline'
391
+ * @param {string} [config.helpText] - Optional help text for tooltip
389
392
  * @returns {HTMLElement} Field element
390
393
  */
391
394
  function createTimePicker(config) {
392
- const { label, fieldId, value: initialValue, placeholder, required = false, onChange, disabled = false, use24Hour = false, helpText = null } = config;
395
+ const { label, fieldId, value: initialValue, placeholder, required = false, onChange, disabled = false, use24Hour = false, size, variant, helpText = null } = config;
393
396
 
394
397
  const field = createFieldWrapper(label, required, helpText);
395
398
 
@@ -402,6 +405,8 @@
402
405
  placeholder: placeholder || `Select ${label}`,
403
406
  disabled,
404
407
  use24Hour,
408
+ size,
409
+ variant,
405
410
  onChange: (value) => {
406
411
  set(fieldId, value);
407
412
  if (onChange) { onChange(value); }
@@ -441,6 +446,7 @@
441
446
  * @param {12|24} config.hourCycle - 12 or 24 hour format
442
447
  * @param {string} config.granularity - 'day' | 'hour' | 'minute' | 'second'
443
448
  * @param {string} config.size - 'small' | 'default' | 'large'
449
+ * @param {string} [config.variant] - 'default' | 'error' | 'warning' | 'success' | 'borderless' | 'inline'
444
450
  * @param {Date} config.fromDate - Min selectable date
445
451
  * @param {Date} config.toDate - Max selectable date
446
452
  * @param {string} config.helpText - Optional help text for tooltip
@@ -458,6 +464,7 @@
458
464
  hourCycle = 12,
459
465
  granularity = "minute",
460
466
  size = "default",
467
+ variant,
461
468
  fromDate,
462
469
  toDate,
463
470
  helpText = null,
@@ -488,6 +495,7 @@
488
495
  hourCycle,
489
496
  granularity,
490
497
  size,
498
+ variant,
491
499
  fromDate,
492
500
  toDate,
493
501
  onChange: (date) => {