@superleapai/flow-ui 2.3.6 → 2.3.8

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.
@@ -0,0 +1,191 @@
1
+ /**
2
+ * Tabs Component (vanilla JS)
3
+ * Tabbed interface with list, triggers, and content panels.
4
+ * Ref: Radix-style Tabs; design tokens match design system.
5
+ */
6
+
7
+ (function (global) {
8
+ "use strict";
9
+
10
+ function getComponent(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 LIST_BASE_CLASS =
19
+ "inline-flex items-center justify-center gap-2 rounded-4 bg-fill-tertiary-fill-light-gray p-4";
20
+
21
+ /** Button variant classes for active (outline) vs inactive (ghost) */
22
+ var BUTTON_OUTLINE =
23
+ "shadow-soft-extra-small group bg-fill-quarternary-fill-white border-1/2 border-border-primary text-typography-primary-text hover:bg-fill-tertiary-fill-light-gray active:bg-fill-secondary-fill-gray disabled:opacity-50 disabled:border-fill-secondary-fill-gray";
24
+ var BUTTON_GHOST =
25
+ "group text-typography-primary-text hover:bg-fill-tertiary-fill-light-gray active:bg-fill-secondary-fill-gray disabled:text-typography-quaternary-text";
26
+ var BUTTON_BASE =
27
+ "inline-flex items-center justify-center whitespace-nowrap !text-med-12 transition-colors disabled:pointer-events-none hover:cursor-pointer h-fit";
28
+ var BUTTON_SIZES = {
29
+ default: "px-8 py-4 gap-4 rounded-4",
30
+ small: "px-8 py-4 gap-4 rounded-4",
31
+ large: "px-16 py-8 gap-4 rounded-4",
32
+ };
33
+
34
+ var CONTENT_BASE_CLASS =
35
+ "ring-offset-background focus-visible:ring-ring h-full focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2";
36
+
37
+ function join() {
38
+ return Array.prototype.filter.call(arguments, Boolean).join(" ");
39
+ }
40
+
41
+ /**
42
+ * Create a full Tabs component (list + triggers + content panels)
43
+ * @param {Object} config
44
+ * @param {string} [config.defaultValue] - initial active tab value
45
+ * @param {string} [config.value] - controlled active value
46
+ * @param {Function} [config.onChange] - (value) => void when tab changes
47
+ * @param {Array<{value: string, label: string, content: HTMLElement|string}>} config.tabs - tab definitions
48
+ * @param {string} [config.size] - 'default' | 'small' | 'large'
49
+ * @param {string} [config.listClassName] - extra class on list
50
+ * @param {string} [config.contentClassName] - extra class on content wrapper
51
+ * @returns {HTMLElement} root element (wrapper containing list + content area)
52
+ */
53
+ function create(config) {
54
+ var opts = config || {};
55
+ var defaultValue = opts.defaultValue;
56
+ var controlledValue = opts.value;
57
+ var onChange = opts.onChange;
58
+ var tabs = opts.tabs || [];
59
+ var size = opts.size || "default";
60
+ var listClassName = opts.listClassName || "";
61
+ var contentClassName = opts.contentClassName || "";
62
+
63
+ var sizeClass = BUTTON_SIZES[size] || BUTTON_SIZES.default;
64
+
65
+ var root = document.createElement("div");
66
+ root.className = "tabs-root w-full";
67
+
68
+ var activeValue = controlledValue !== undefined ? controlledValue : defaultValue !== undefined ? defaultValue : (tabs[0] && tabs[0].value) || "";
69
+
70
+ // List container
71
+ var list = document.createElement("div");
72
+ list.setAttribute("role", "tablist");
73
+ list.className = join(LIST_BASE_CLASS, listClassName);
74
+
75
+ // Content container (holds all panels; we show/hide by value)
76
+ var contentWrapper = document.createElement("div");
77
+ contentWrapper.className = join("tabs-content-wrapper mt-4", contentClassName);
78
+
79
+ var triggerEls = [];
80
+ var contentPanels = [];
81
+
82
+ var Button = getComponent("Button");
83
+
84
+ tabs.forEach(function (tab, index) {
85
+ var value = tab.value;
86
+ var label = tab.label != null ? tab.label : value;
87
+ var content = tab.content;
88
+
89
+ var isActive = value === activeValue;
90
+ var trigger = Button.create({
91
+ variant: isActive ? "outline" : "ghost",
92
+ size: size === "small" ? "small" : size === "large" ? "large" : "default",
93
+ text: label,
94
+ type: "button",
95
+ className: "mx-2",
96
+ onClick: function () {
97
+ if (activeValue === value) return;
98
+ if (controlledValue === undefined) {
99
+ activeValue = value;
100
+ updateActiveState();
101
+ }
102
+ if (typeof onChange === "function") {
103
+ onChange(value);
104
+ }
105
+ },
106
+ });
107
+
108
+ trigger.setAttribute("role", "tab");
109
+ trigger.setAttribute("aria-selected", value === activeValue ? "true" : "false");
110
+ trigger.setAttribute("data-state", value === activeValue ? "active" : "inactive");
111
+ trigger.setAttribute("data-value", value);
112
+ trigger.tabIndex = value === activeValue ? 0 : -1;
113
+
114
+ trigger.addEventListener("keydown", function (e) {
115
+ if (e.key === "Enter" || e.key === " ") {
116
+ e.preventDefault();
117
+ trigger.click();
118
+ }
119
+ if (e.key === "ArrowRight" || e.key === "ArrowLeft") {
120
+ e.preventDefault();
121
+ var nextIndex = e.key === "ArrowRight" ? index + 1 : index - 1;
122
+ if (nextIndex >= 0 && nextIndex < tabs.length) {
123
+ var nextTrigger = triggerEls[nextIndex];
124
+ if (nextTrigger) {
125
+ nextTrigger.focus();
126
+ nextTrigger.click();
127
+ }
128
+ }
129
+ }
130
+ });
131
+
132
+ list.appendChild(trigger);
133
+ triggerEls.push(trigger);
134
+
135
+ var panel = document.createElement("div");
136
+ panel.setAttribute("role", "tabpanel");
137
+ panel.setAttribute("aria-hidden", value !== activeValue ? "true" : "false");
138
+ panel.setAttribute("data-value", value);
139
+ panel.className = join(CONTENT_BASE_CLASS, value !== activeValue ? "hidden" : "");
140
+ if (typeof content === "string") {
141
+ panel.innerHTML = content;
142
+ } else if (content && content.nodeType === 1) {
143
+ panel.appendChild(content);
144
+ }
145
+ contentWrapper.appendChild(panel);
146
+ contentPanels.push(panel);
147
+ });
148
+
149
+ function updateActiveState() {
150
+ triggerEls.forEach(function (t, i) {
151
+ var val = tabs[i] && tabs[i].value;
152
+ var isActive = val === activeValue;
153
+ t.setAttribute("aria-selected", isActive ? "true" : "false");
154
+ t.setAttribute("data-state", isActive ? "active" : "inactive");
155
+ t.tabIndex = isActive ? 0 : -1;
156
+ t.className = join(BUTTON_BASE, isActive ? BUTTON_OUTLINE : BUTTON_GHOST, sizeClass, "mx-2");
157
+ });
158
+ contentPanels.forEach(function (p, i) {
159
+ var val = tabs[i] && tabs[i].value;
160
+ var isActive = val === activeValue;
161
+ p.setAttribute("aria-hidden", isActive ? "false" : "true");
162
+ p.classList.toggle("hidden", !isActive);
163
+ });
164
+ }
165
+
166
+ root.appendChild(list);
167
+ root.appendChild(contentWrapper);
168
+
169
+ root.getValue = function () {
170
+ return activeValue;
171
+ };
172
+
173
+ root.setValue = function (newValue) {
174
+ if (newValue === activeValue) return;
175
+ activeValue = newValue;
176
+ updateActiveState();
177
+ };
178
+
179
+ return root;
180
+ }
181
+
182
+ var Tabs = {
183
+ create: create,
184
+ };
185
+
186
+ if (typeof module !== "undefined" && module.exports) {
187
+ module.exports = Tabs;
188
+ } else {
189
+ global.Tabs = Tabs;
190
+ }
191
+ })(typeof window !== "undefined" ? window : this);
package/core/flow.js CHANGED
@@ -248,6 +248,48 @@
248
248
  return field;
249
249
  }
250
250
 
251
+ /**
252
+ * Create a rich text editor field
253
+ * @param {Object} config - Configuration object
254
+ * @param {string} config.label - Field label
255
+ * @param {string} config.fieldId - State key for this field
256
+ * @param {string} [config.placeholder] - Placeholder when empty
257
+ * @param {boolean} [config.required] - Whether field is required
258
+ * @param {string} [config.helpText] - Optional help text for tooltip
259
+ * @param {number} [config.minHeightPx] - Min height of editor area in pixels (default 400)
260
+ * @param {boolean} [config.disabled] - Whether editor is disabled
261
+ * @returns {HTMLElement} Field element
262
+ */
263
+ function createRichTextEditor(config) {
264
+ const { label, fieldId, placeholder, required = false, helpText = null, minHeightPx = 400, disabled = false } = config;
265
+
266
+ const field = createFieldWrapper(label, required, helpText);
267
+ field.setAttribute("data-field-id", fieldId);
268
+
269
+ if (getComponent("RichTextEditorComponent") && getComponent("RichTextEditorComponent").create) {
270
+ const currentValue = get(fieldId) || "";
271
+ const editorEl = getComponent("RichTextEditorComponent").create({
272
+ value: currentValue,
273
+ placeholder: placeholder || "",
274
+ minHeightPx,
275
+ disabled,
276
+ onChange: (html) => set(fieldId, html),
277
+ });
278
+ editorEl._fieldId = fieldId;
279
+ field.appendChild(editorEl);
280
+ return field;
281
+ }
282
+
283
+ const fallback = document.createElement("textarea");
284
+ fallback.className = "textarea min-h-[400px]";
285
+ fallback.placeholder = placeholder || `Enter ${label.toLowerCase()}`;
286
+ fallback.value = get(fieldId) || "";
287
+ fallback.disabled = disabled;
288
+ fallback.addEventListener("change", (e) => set(fieldId, e.target.value));
289
+ field.appendChild(fallback);
290
+ return field;
291
+ }
292
+
251
293
  /**
252
294
  * Create a select dropdown field (using custom select component)
253
295
  * @param {Object} config - Configuration object
@@ -1332,6 +1374,38 @@
1332
1374
  });
1333
1375
  }
1334
1376
 
1377
+ /**
1378
+ * Create a Tabs component (list + triggers + content panels)
1379
+ * @param {Object} config - { defaultValue?, value?, onChange?, tabs: [{ value, label, content }], size?, variant?, listClassName?, contentClassName? }
1380
+ * @returns {HTMLElement} Tabs root element
1381
+ */
1382
+ function createTabs(config) {
1383
+ const Tabs = getComponent("Tabs");
1384
+ if (Tabs && typeof Tabs.create === "function") {
1385
+ return Tabs.create(config);
1386
+ }
1387
+ const fallback = document.createElement("div");
1388
+ fallback.className = "tabs-root";
1389
+ fallback.textContent = "Tabs component not loaded.";
1390
+ return fallback;
1391
+ }
1392
+
1393
+ /**
1394
+ * Create a Steps component (numbered step triggers + optional content panels)
1395
+ * @param {Object} config - { steps: [{ id, label, content? }], defaultValue?, value?, onChange?, size?, variant?, listClassName?, contentClassName?, showContent? }
1396
+ * @returns {HTMLElement} Steps root element
1397
+ */
1398
+ function createSteps(config) {
1399
+ const Steps = getComponent("Steps");
1400
+ if (Steps && typeof Steps.create === "function") {
1401
+ return Steps.create(config);
1402
+ }
1403
+ const fallback = document.createElement("div");
1404
+ fallback.className = "steps-root";
1405
+ fallback.textContent = "Steps component not loaded.";
1406
+ return fallback;
1407
+ }
1408
+
1335
1409
  // ============================================================================
1336
1410
  // TABLE COMPONENT
1337
1411
  // ============================================================================
@@ -1658,6 +1732,7 @@
1658
1732
  // Form components
1659
1733
  createInput,
1660
1734
  createTextarea,
1735
+ createRichTextEditor,
1661
1736
  createSelect,
1662
1737
  createTimePicker,
1663
1738
  createDateTimePicker,
@@ -1693,6 +1768,8 @@
1693
1768
 
1694
1769
  // Stepper
1695
1770
  renderStepper,
1771
+ createTabs,
1772
+ createSteps,
1696
1773
 
1697
1774
  // Alerts
1698
1775
  renderAlerts,