@superleapai/flow-ui 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/LICENSE +21 -0
  3. package/README.md +451 -0
  4. package/components/alert.js +282 -0
  5. package/components/avatar.js +195 -0
  6. package/components/badge.js +135 -0
  7. package/components/button.js +201 -0
  8. package/components/checkbox.js +254 -0
  9. package/components/currency.js +227 -0
  10. package/components/date-time-picker/date-time-picker-utils.js +253 -0
  11. package/components/date-time-picker/date-time-picker.js +532 -0
  12. package/components/duration/duration-constants.js +46 -0
  13. package/components/duration/duration-utils.js +164 -0
  14. package/components/duration/duration.js +448 -0
  15. package/components/enum-multiselect.js +869 -0
  16. package/components/enum-select.js +831 -0
  17. package/components/enumeration.js +213 -0
  18. package/components/file-input.js +533 -0
  19. package/components/icon.js +200 -0
  20. package/components/input.js +259 -0
  21. package/components/label.js +111 -0
  22. package/components/multiselect.js +351 -0
  23. package/components/phone-input/phone-input.js +392 -0
  24. package/components/phone-input/phone-utils.js +157 -0
  25. package/components/popover.js +240 -0
  26. package/components/radio-group.js +435 -0
  27. package/components/record-multiselect.js +956 -0
  28. package/components/record-select.js +930 -0
  29. package/components/select.js +544 -0
  30. package/components/spinner.js +136 -0
  31. package/components/table.js +335 -0
  32. package/components/textarea.js +114 -0
  33. package/components/time-picker.js +357 -0
  34. package/components/toast.js +343 -0
  35. package/core/flow.js +1729 -0
  36. package/core/superleapClient.js +146 -0
  37. package/dist/output.css +2 -0
  38. package/index.d.ts +458 -0
  39. package/index.js +253 -0
  40. package/package.json +70 -0
@@ -0,0 +1,357 @@
1
+ /**
2
+ * Time Picker Component (vanilla JS, Tailwind)
3
+ * Dropdown with three vertical scrollable columns (hour, minute, AM/PM).
4
+ * Selected items: dark teal background, white text. Trigger shows time + clock icon.
5
+ */
6
+
7
+ (function (global) {
8
+ "use strict";
9
+
10
+ function getDep(name) {
11
+ if (typeof global.FlowUI !== "undefined" && typeof global.FlowUI._getComponent === "function") {
12
+ var c = global.FlowUI._getComponent(name);
13
+ if (c) return c;
14
+ }
15
+ return global[name];
16
+ }
17
+
18
+ var CLOCK_SVG =
19
+ '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>';
20
+
21
+ function pad(n) {
22
+ return String(n).padStart(2, "0");
23
+ }
24
+
25
+ function parseValue(value) {
26
+ if (!value || typeof value !== "string") {
27
+ return { hour: 0, minute: 0 };
28
+ }
29
+ var parts = value.trim().split(":");
30
+ var hour = Math.min(23, Math.max(0, parseInt(parts[0], 10) || 0));
31
+ var minute = Math.min(59, Math.max(0, parseInt(parts[1], 10) || 0));
32
+ return { hour: hour, minute: minute };
33
+ }
34
+
35
+ /** Internal value "HH:mm" (24h) */
36
+ function formatValue(hour, minute) {
37
+ return pad(hour) + ":" + pad(minute);
38
+ }
39
+
40
+ /** Display string: "12 : 00 AM" or "00 : 00" */
41
+ function formatDisplay(hour, minute, use24Hour) {
42
+ if (use24Hour) {
43
+ return pad(hour) + " : " + pad(minute);
44
+ }
45
+ var h12 = hour === 0 ? 12 : hour > 12 ? hour - 12 : hour;
46
+ var amPm = hour >= 12 ? "PM" : "AM";
47
+ return pad(h12) + " : " + pad(minute) + " " + amPm;
48
+ }
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";
55
+ 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;
60
+ }
61
+
62
+ /**
63
+ * Create a scrollable column of options; one item is selected (teal bg, white text).
64
+ * Uses Button component for each option.
65
+ * @param {Array<{value: string|number, label: string}>} options
66
+ * @param {string|number} selectedValue
67
+ * @param {function(string|number)} onSelect
68
+ * @param {boolean} [tabularNums] - use tabular-nums for digit alignment (hour/minute)
69
+ * @returns {{ el: HTMLElement, setSelected: function, scrollToSelected: function }}
70
+ */
71
+ function createColumn(options, selectedValue, onSelect, tabularNums) {
72
+ var Button = getDep("Button");
73
+ var col = document.createElement("div");
74
+ col.className =
75
+ "flex flex-col flex-shrink-0 min-w-32 overflow-y-auto max-h-[200px] p-6 [scrollbar-width:none] [-ms-overflow-style:none] [&::-webkit-scrollbar]:[display:none]";
76
+ col.setAttribute("role", "listbox");
77
+
78
+ var itemClassName = (tabularNums ? "tabular-nums" : "");
79
+ var items = [];
80
+ if (!Button || typeof Button.create !== "function") {
81
+ var fallbackCol = document.createElement("div");
82
+ fallbackCol.className = col.className;
83
+ fallbackCol.setAttribute("role", "listbox");
84
+ fallbackCol.textContent = "Button component required";
85
+ return { el: fallbackCol, setSelected: function () {}, scrollToSelected: function () {}, items: [] };
86
+ }
87
+ options.forEach(function (opt) {
88
+ var val = opt.value !== undefined ? opt.value : opt.label;
89
+ var label = opt.label !== undefined ? opt.label : String(val);
90
+ var isSelected = String(val) === String(selectedValue);
91
+ var item = Button.create({
92
+ variant: isSelected ? "primary" : "ghost",
93
+ size: "small",
94
+ text: label,
95
+ className: itemClassName,
96
+ onClick: function () {
97
+ onSelect(val);
98
+ },
99
+ });
100
+ item.setAttribute("role", "option");
101
+ item.setAttribute("data-value", String(val));
102
+ item.setAttribute("aria-selected", isSelected);
103
+ col.appendChild(item);
104
+ items.push({ el: item, value: val, label: label });
105
+ });
106
+
107
+ function setSelected(val) {
108
+ items.forEach(function (x) {
109
+ var selected = String(x.value) === String(val);
110
+ x.el.setAttribute("aria-selected", selected);
111
+ x.el.className = Button.getButtonClasses({
112
+ variant: selected ? "primary" : "ghost",
113
+ size: "small",
114
+ }) + " " + itemClassName;
115
+ });
116
+ }
117
+
118
+ function scrollToSelected() {
119
+ var selectedEl = col.querySelector("[aria-selected=true]");
120
+ if (selectedEl) selectedEl.scrollIntoView({ block: "nearest", behavior: "auto" });
121
+ }
122
+
123
+ return { el: col, setSelected: setSelected, scrollToSelected: scrollToSelected, items: items };
124
+ }
125
+
126
+ /**
127
+ * Create a time picker component
128
+ * @param {Object} config - Configuration object
129
+ * @param {string} config.fieldId - Field ID for state / form
130
+ * @param {string} config.value - Current value "HH:mm"
131
+ * @param {string} config.placeholder - Placeholder text
132
+ * @param {Function} config.onChange - Change handler (value: string "HH:mm")
133
+ * @param {boolean} config.disabled - Whether the picker is disabled
134
+ * @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)
136
+ * @returns {HTMLElement} Time picker container element
137
+ */
138
+ function create(config) {
139
+ var fieldId = config.fieldId;
140
+ var initialValue = config.value !== undefined ? config.value : "";
141
+ var placeholder = config.placeholder || "Select time";
142
+ var onChange = config.onChange;
143
+ var disabled = config.disabled === true;
144
+ var use24Hour = config.use24Hour === true;
145
+ var size = config.size === "small" ? "small" : "default";
146
+
147
+ var value = typeof initialValue === "string" ? initialValue : "";
148
+ var parsed = parseValue(value);
149
+ var hour = parsed.hour;
150
+ var minute = parsed.minute;
151
+ var period = hour >= 12 ? "PM" : "AM";
152
+ var hour12 = hour === 0 ? 12 : hour > 12 ? hour - 12 : hour;
153
+
154
+ var container = document.createElement("div");
155
+ container.className = "time-picker relative w-full group";
156
+ if (fieldId) container.setAttribute("data-field-id", fieldId);
157
+
158
+ var trigger = document.createElement("button");
159
+ trigger.type = "button";
160
+ trigger.disabled = disabled;
161
+ trigger.setAttribute("aria-haspopup", "listbox");
162
+ trigger.setAttribute("aria-expanded", "false");
163
+ trigger.setAttribute("aria-label", placeholder);
164
+ trigger.className = triggerClasses(disabled, !!value, size);
165
+
166
+ var triggerText = document.createElement("span");
167
+ triggerText.className =
168
+ "flex-1 truncate text-left tabular-nums " +
169
+ (value ? "text-inherit" : "text-typography-quaternary-text");
170
+ triggerText.textContent = value ? formatDisplay(hour, minute, use24Hour) : placeholder;
171
+ trigger.appendChild(triggerText);
172
+
173
+ var iconWrap = document.createElement("span");
174
+ iconWrap.className = "flex size-16 shrink-0 items-center justify-center text-typography-primary-text [&_svg]:text-typography-primary-text";
175
+ iconWrap.setAttribute("aria-hidden", "true");
176
+ iconWrap.innerHTML = CLOCK_SVG;
177
+ trigger.appendChild(iconWrap);
178
+
179
+ var hourOptions = [];
180
+ if (use24Hour) {
181
+ for (var h = 0; h <= 23; h++) {
182
+ hourOptions.push({ value: h, label: pad(h) });
183
+ }
184
+ } else {
185
+ for (var h = 1; h <= 12; h++) {
186
+ hourOptions.push({ value: h, label: pad(h) });
187
+ }
188
+ }
189
+ var minuteOptions = [];
190
+ for (var m = 0; m <= 59; m++) {
191
+ minuteOptions.push({ value: m, label: pad(m) });
192
+ }
193
+ var periodOptions = [{ value: "AM", label: "AM" }, { value: "PM", label: "PM" }];
194
+
195
+ var hourColVal = use24Hour ? hour : hour12;
196
+ var hourColumn = createColumn(hourOptions, hourColVal, function (v) {
197
+ if (use24Hour) {
198
+ hour = v;
199
+ } else {
200
+ hour12 = v;
201
+ hour = period === "PM" ? (v === 12 ? 12 : v + 12) : (v === 12 ? 0 : v);
202
+ }
203
+ hourColumn.setSelected(use24Hour ? hour : hour12);
204
+ hourColumn.scrollToSelected();
205
+ value = formatValue(hour, minute);
206
+ triggerText.textContent = formatDisplay(hour, minute, use24Hour);
207
+ triggerText.className =
208
+ "flex-1 truncate text-left tabular-nums " +
209
+ (value ? "text-inherit" : "text-typography-quaternary-text");
210
+ trigger.className = triggerClasses(disabled, !!value, size);
211
+ if (onChange) onChange(value);
212
+ }, true);
213
+ var minuteColumn = createColumn(minuteOptions, minute, function (v) {
214
+ minute = v;
215
+ minuteColumn.setSelected(minute);
216
+ minuteColumn.scrollToSelected();
217
+ value = formatValue(hour, minute);
218
+ triggerText.textContent = formatDisplay(hour, minute, use24Hour);
219
+ triggerText.className =
220
+ "flex-1 truncate text-left tabular-nums " +
221
+ (value ? "text-inherit" : "text-typography-quaternary-text");
222
+ trigger.className = triggerClasses(disabled, !!value, size);
223
+ if (onChange) onChange(value);
224
+ }, true);
225
+ var periodColumn = null;
226
+ if (!use24Hour) {
227
+ periodColumn = createColumn(periodOptions, period, function (v) {
228
+ period = v;
229
+ hour = period === "PM" ? (hour12 === 12 ? 12 : hour12 + 12) : (hour12 === 12 ? 0 : hour12);
230
+ periodColumn.setSelected(period);
231
+ periodColumn.scrollToSelected();
232
+ value = formatValue(hour, minute);
233
+ triggerText.textContent = formatDisplay(hour, minute, use24Hour);
234
+ triggerText.className =
235
+ "flex-1 truncate text-left tabular-nums " +
236
+ (value ? "text-inherit" : "text-typography-quaternary-text");
237
+ trigger.className = triggerClasses(disabled, !!value, size);
238
+ if (onChange) onChange(value);
239
+ }, false);
240
+ }
241
+
242
+ var columnsRow = document.createElement("div");
243
+ columnsRow.className = "flex flex-row flex-nowrap items-stretch gap-0";
244
+ // Condition-based borders: only add separator between columns (not before first / after last)
245
+ hourColumn.el.className += " border-r-1/2 border-border-primary";
246
+ minuteColumn.el.className += " border-l-1/2 border-border-primary";
247
+ if (periodColumn) {
248
+ minuteColumn.el.className += " border-r-1/2 border-border-primary";
249
+ periodColumn.el.className += " border-l-1/2 border-border-primary";
250
+ }
251
+ columnsRow.appendChild(hourColumn.el);
252
+ columnsRow.appendChild(minuteColumn.el);
253
+ if (periodColumn) columnsRow.appendChild(periodColumn.el);
254
+
255
+ var hidePopover = null;
256
+ var ButtonForClose = getDep("Button");
257
+ var closeBtn = ButtonForClose && typeof ButtonForClose.create === "function"
258
+ ? ButtonForClose.create({
259
+ variant: "outline",
260
+ size: "small",
261
+ text: "Close",
262
+ onClick: function () {
263
+ if (hidePopover) hidePopover();
264
+ },
265
+ })
266
+ : (function () {
267
+ var b = document.createElement("button");
268
+ b.type = "button";
269
+ b.className = "px-2 py-1 border rounded text-sm";
270
+ b.textContent = "Close";
271
+ b.onclick = function () { if (hidePopover) hidePopover(); };
272
+ return b;
273
+ })();
274
+
275
+ var closeRow = document.createElement("div");
276
+ closeRow.className =
277
+ "flex justify-end border-t-1/2 border-border-primary pt-6 px-8 pb-12";
278
+ closeRow.appendChild(closeBtn);
279
+
280
+ var popoverContent = document.createElement("div");
281
+ popoverContent.setAttribute("role", "listbox");
282
+ popoverContent.setAttribute("data-time-picker-dropdown", "true");
283
+ popoverContent.appendChild(columnsRow);
284
+ popoverContent.appendChild(closeRow);
285
+
286
+ function syncColumnsAndScroll() {
287
+ if (disabled) return;
288
+ hourColumn.setSelected(use24Hour ? hour : hour12);
289
+ minuteColumn.setSelected(minute);
290
+ if (periodColumn) {
291
+ period = hour >= 12 ? "PM" : "AM";
292
+ periodColumn.setSelected(period);
293
+ }
294
+ hourColumn.scrollToSelected();
295
+ minuteColumn.scrollToSelected();
296
+ if (periodColumn) periodColumn.scrollToSelected();
297
+ }
298
+
299
+ var Popover = getDep("Popover");
300
+ if (!Popover || typeof Popover.create !== "function") {
301
+ container.appendChild(trigger);
302
+ container.updateValue = function (newVal) { value = typeof newVal === "string" ? newVal : ""; };
303
+ container.setDisabled = function (isDisabled) { trigger.disabled = !!isDisabled; };
304
+ return container;
305
+ }
306
+ var popover = Popover.create({
307
+ trigger: trigger,
308
+ content: popoverContent,
309
+ placement: "bottom",
310
+ closeOnClickOutside: true,
311
+ bodyClassName: "p-0",
312
+ panelClassName: "!w-auto min-w-max",
313
+ onOpen: function () {
314
+ trigger.setAttribute("aria-expanded", "true");
315
+ container.classList.add("open");
316
+ syncColumnsAndScroll();
317
+ },
318
+ onClose: function () {
319
+ trigger.setAttribute("aria-expanded", "false");
320
+ container.classList.remove("open");
321
+ },
322
+ });
323
+ hidePopover = popover.hide;
324
+
325
+ container.appendChild(trigger);
326
+
327
+ container.updateValue = function (newVal) {
328
+ value = typeof newVal === "string" ? newVal : "";
329
+ var p = parseValue(value);
330
+ hour = p.hour;
331
+ minute = p.minute;
332
+ hour12 = hour === 0 ? 12 : hour > 12 ? hour - 12 : hour;
333
+ period = hour >= 12 ? "PM" : "AM";
334
+ triggerText.textContent = value ? formatDisplay(hour, minute, use24Hour) : placeholder;
335
+ triggerText.className =
336
+ "flex-1 truncate text-left tabular-nums " +
337
+ (value ? "text-inherit" : "text-typography-quaternary-text");
338
+ trigger.className = triggerClasses(disabled, !!value, size);
339
+ hourColumn.setSelected(use24Hour ? hour : hour12);
340
+ minuteColumn.setSelected(minute);
341
+ if (periodColumn) periodColumn.setSelected(period);
342
+ };
343
+
344
+ container.setDisabled = function (isDisabled) {
345
+ disabled = !!isDisabled;
346
+ trigger.disabled = disabled;
347
+ trigger.className = triggerClasses(disabled, !!value, size);
348
+ if (disabled) popover.hide();
349
+ };
350
+
351
+ return container;
352
+ }
353
+
354
+ global.TimePicker = {
355
+ create: create,
356
+ };
357
+ })(typeof window !== "undefined" ? window : this);
@@ -0,0 +1,343 @@
1
+ /**
2
+ * Toast Component (vanilla JS, Tailwind)
3
+ * Toast notification system with multiple variants.
4
+ * Adapted from Radix UI Toast primitives.
5
+ */
6
+
7
+ (function (global) {
8
+ "use strict";
9
+
10
+ // SVG Icons
11
+ const ICONS = {
12
+ alertCircle: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>`,
13
+ circleCheck: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="m9 12 2 2 4-4"/></svg>`,
14
+ infoCircle: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/></svg>`,
15
+ loader: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 1 1-6.219-8.56"/></svg>`,
16
+ playstationX: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="m15 9-6 6"/><path d="m9 9 6 6"/></svg>`,
17
+ x: `<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>`,
18
+ };
19
+
20
+ // Variant to icon mapping
21
+ const VARIANT_ICONS = {
22
+ loading: "loader",
23
+ default: "alertCircle",
24
+ warning: "alertCircle",
25
+ error: "playstationX",
26
+ success: "circleCheck",
27
+ info: "infoCircle",
28
+ notification: null,
29
+ };
30
+
31
+ // Toast container singleton
32
+ let toastViewport = null;
33
+ let toastCount = 0;
34
+ let styleInjected = false;
35
+
36
+ /**
37
+ * Inject toast styles into document
38
+ */
39
+ function injectStyles() {
40
+ if (styleInjected) return;
41
+
42
+ const style = document.createElement("style");
43
+ style.id = "toast-styles";
44
+ style.textContent = `
45
+ @keyframes toast-slide-in {
46
+ from {
47
+ opacity: 0;
48
+ transform: translateY(-100%);
49
+ }
50
+ to {
51
+ opacity: 1;
52
+ transform: translateY(0);
53
+ }
54
+ }
55
+ @keyframes toast-slide-out {
56
+ from {
57
+ opacity: 1;
58
+ transform: translateY(0);
59
+ }
60
+ to {
61
+ opacity: 0;
62
+ transform: translateY(-100%);
63
+ }
64
+ }
65
+ @keyframes toast-spin {
66
+ from { transform: rotate(0deg); }
67
+ to { transform: rotate(360deg); }
68
+ }
69
+ .toast-viewport {
70
+ position: fixed;
71
+ left: 50%;
72
+ top: 5%;
73
+ z-index: 100;
74
+ max-height: 100vh;
75
+ width: fit-content;
76
+ transform: translateX(-50%);
77
+ padding: 1rem;
78
+ max-width: 26rem;
79
+ display: flex;
80
+ flex-direction: column;
81
+ gap: 0.5rem;
82
+ pointer-events: none;
83
+ }
84
+ .toast-root {
85
+ pointer-events: auto;
86
+ width: 100%;
87
+ border-radius: 8px;
88
+ position: relative;
89
+ display: flex;
90
+ align-items: center;
91
+ gap: 0.5rem;
92
+ overflow: hidden;
93
+ padding: 0.75rem 1rem;
94
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
95
+ animation: toast-slide-in 0.3s cubic-bezier(0.4, 0, 0.2, 1);
96
+ }
97
+ .toast-root[data-state="closed"] {
98
+ animation: toast-slide-out 0.3s cubic-bezier(0.4, 0, 0.2, 1);
99
+ }
100
+ .toast-icon-spin {
101
+ animation: toast-spin 1s linear infinite;
102
+ }
103
+ `;
104
+ document.head.appendChild(style);
105
+ styleInjected = true;
106
+ }
107
+
108
+ /**
109
+ * Initialize or get the toast viewport container
110
+ */
111
+ function initToastViewport() {
112
+ if (toastViewport && document.body.contains(toastViewport)) {
113
+ return toastViewport;
114
+ }
115
+
116
+ injectStyles();
117
+
118
+ toastViewport = document.createElement("div");
119
+ toastViewport.className = "toast-viewport";
120
+ toastViewport.setAttribute("role", "region");
121
+ toastViewport.setAttribute("aria-label", "Notifications");
122
+ document.body.appendChild(toastViewport);
123
+
124
+ return toastViewport;
125
+ }
126
+
127
+ /**
128
+ * Get variant-specific Tailwind classes
129
+ */
130
+ function getVariantClasses(variant) {
131
+ const variants = {
132
+ loading: "bg-fill-tertiary-fill-light-gray border-borderColor-border-primary text-typography-primary-text",
133
+ default: "bg-fill-quarternary-fill-white border-borderColor-border-primary text-typography-primary-text",
134
+ warning: "bg-warning-surface border-warning-border text-warning-text-base",
135
+ error: "bg-error-surface border-error-border text-error-text-base",
136
+ success: "bg-success-surface border-success-border text-success-text-base",
137
+ info: "bg-info-surface border-info-border text-info-text-base",
138
+ notification: "bg-fill-quarternary-fill-white border-borderColor-border-primary text-typography-primary-text",
139
+ };
140
+ return variants[variant] || variants.default;
141
+ }
142
+
143
+ /**
144
+ * Create a toast notification
145
+ * @param {Object} config - Configuration object
146
+ * @param {string} config.title - Toast title (optional)
147
+ * @param {string} config.description - Toast description/message
148
+ * @param {string} [config.variant='default'] - 'loading' | 'default' | 'warning' | 'error' | 'success' | 'info' | 'notification'
149
+ * @param {number} [config.duration=4000] - Duration in ms (0 for persistent)
150
+ * @param {Function} [config.onClose] - Called when toast closes
151
+ * @param {boolean} [config.showClose=true] - Show close button
152
+ * @returns {Object} Toast API {close, element}
153
+ */
154
+ function create(config = {}) {
155
+ const {
156
+ title = "",
157
+ description = "",
158
+ variant = "default",
159
+ duration = 4000,
160
+ onClose = null,
161
+ showClose = true,
162
+ } = config;
163
+
164
+ if (!description && !title) {
165
+ console.warn("[Toast] No title or description provided");
166
+ return { close: noop, element: null };
167
+ }
168
+
169
+ const viewport = initToastViewport();
170
+ const id = ++toastCount;
171
+ const variantClasses = getVariantClasses(variant);
172
+
173
+ // Create toast root element
174
+ const toast = document.createElement("div");
175
+ toast.className = `toast-root border-1/2 ${variantClasses}`;
176
+ toast.setAttribute("data-toast-id", id);
177
+ toast.setAttribute("data-state", "closed");
178
+ toast.setAttribute("role", "status");
179
+ toast.setAttribute("aria-live", "polite");
180
+ toast.setAttribute("aria-atomic", "true");
181
+
182
+ // Create content wrapper
183
+ const contentWrapper = document.createElement("div");
184
+ contentWrapper.className = "flex w-full items-center justify-start gap-8";
185
+
186
+ // Add icon if variant has one
187
+ const iconKey = VARIANT_ICONS[variant];
188
+ if (iconKey) {
189
+ const iconWrapper = document.createElement("div");
190
+ iconWrapper.className = `flex items-center justify-center ${variant === "loading" ? "toast-icon-spin" : ""}`;
191
+ iconWrapper.style.cssText = "width: 16px; height: 16px; flex-shrink: 0;";
192
+ iconWrapper.innerHTML = ICONS[iconKey];
193
+ contentWrapper.appendChild(iconWrapper);
194
+ }
195
+
196
+ // Create text content container
197
+ const textContainer = document.createElement("div");
198
+ textContainer.className = "flex-1 text-reg-12";
199
+
200
+ if (title) {
201
+ const titleEl = document.createElement("div");
202
+ titleEl.className = "text-med-12 mb-4";
203
+ titleEl.textContent = title;
204
+ textContainer.appendChild(titleEl);
205
+ }
206
+
207
+ if (description) {
208
+ const descEl = document.createElement("div");
209
+ descEl.className = "text-reg-12";
210
+ descEl.textContent = description;
211
+ textContainer.appendChild(descEl);
212
+ }
213
+
214
+ contentWrapper.appendChild(textContainer);
215
+
216
+ // Add close button
217
+ if (showClose) {
218
+ const closeBtn = document.createElement("button");
219
+ closeBtn.type = "button";
220
+ closeBtn.className = "ml-auto flex items-center justify-center rounded-4 p-4 transition-opacity hover:opacity-100 focus:opacity-100 focus:outline-none";
221
+ closeBtn.style.cssText = "flex-shrink: 0; width: 16px; height: 16px; border: none; background: transparent; opacity: 0.6; cursor: pointer;";
222
+ closeBtn.innerHTML = ICONS.x;
223
+ closeBtn.addEventListener("click", close);
224
+ contentWrapper.appendChild(closeBtn);
225
+ }
226
+
227
+ toast.appendChild(contentWrapper);
228
+
229
+ function noop() {}
230
+
231
+ function close() {
232
+ toast.setAttribute("data-state", "closed");
233
+
234
+ setTimeout(() => {
235
+ if (toast.parentElement) {
236
+ toast.parentElement.removeChild(toast);
237
+ }
238
+ // Clean up viewport if empty
239
+ if (toastViewport && toastViewport.children.length === 0) {
240
+ toastViewport.remove();
241
+ toastViewport = null;
242
+ }
243
+ if (onClose) onClose();
244
+ }, 300); // Match animation duration
245
+ }
246
+
247
+ // Append and animate in
248
+ viewport.appendChild(toast);
249
+
250
+ // Trigger animation
251
+ requestAnimationFrame(() => {
252
+ toast.setAttribute("data-state", "open");
253
+ });
254
+
255
+ // Auto-dismiss if duration is set
256
+ if (duration > 0) {
257
+ setTimeout(close, duration);
258
+ }
259
+
260
+ return {
261
+ close,
262
+ element: toast,
263
+ };
264
+ }
265
+
266
+ /**
267
+ * Show a simple toast with message and type
268
+ * Convenience method compatible with the old showAlert API
269
+ * @param {string} message - Toast message
270
+ * @param {string} [type='info'] - Toast type: 'success' | 'error' | 'warning' | 'info' | 'loading'
271
+ * @param {number} [duration=4000] - Duration in ms (0 for persistent)
272
+ * @returns {Object} Toast API {close, element}
273
+ */
274
+ function show(message, type = "info", duration = 4000) {
275
+ console.log("[Toast] Showing toast:", message, "Type:", type);
276
+ return create({
277
+ description: message,
278
+ variant: type,
279
+ duration: duration,
280
+ });
281
+ }
282
+
283
+ /**
284
+ * Show a success toast
285
+ * @param {string} message - Toast message
286
+ * @param {number} [duration=4000] - Duration in ms
287
+ * @returns {Object} Toast API {close, element}
288
+ */
289
+ function success(message, duration = 4000) {
290
+ return show(message, "success", duration);
291
+ }
292
+
293
+ /**
294
+ * Show an error toast
295
+ * @param {string} message - Toast message
296
+ * @param {number} [duration=4000] - Duration in ms
297
+ * @returns {Object} Toast API {close, element}
298
+ */
299
+ function error(message, duration = 4000) {
300
+ return show(message, "error", duration);
301
+ }
302
+
303
+ /**
304
+ * Show a warning toast
305
+ * @param {string} message - Toast message
306
+ * @param {number} [duration=4000] - Duration in ms
307
+ * @returns {Object} Toast API {close, element}
308
+ */
309
+ function warning(message, duration = 4000) {
310
+ return show(message, "warning", duration);
311
+ }
312
+
313
+ /**
314
+ * Show an info toast
315
+ * @param {string} message - Toast message
316
+ * @param {number} [duration=4000] - Duration in ms
317
+ * @returns {Object} Toast API {close, element}
318
+ */
319
+ function info(message, duration = 4000) {
320
+ return show(message, "info", duration);
321
+ }
322
+
323
+ /**
324
+ * Show a loading toast
325
+ * @param {string} message - Toast message
326
+ * @param {number} [duration=0] - Duration in ms (0 for persistent by default)
327
+ * @returns {Object} Toast API {close, element}
328
+ */
329
+ function loading(message, duration = 0) {
330
+ return show(message, "loading", duration);
331
+ }
332
+
333
+ // Export to global namespace
334
+ global.Toast = {
335
+ create,
336
+ show,
337
+ success,
338
+ error,
339
+ warning,
340
+ info,
341
+ loading,
342
+ };
343
+ })(typeof window !== "undefined" ? window : this);