@superleapai/flow-ui 2.3.7 → 2.3.9

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,245 @@
1
+ /**
2
+ * Steps Component (vanilla JS)
3
+ * Multi-step flow with numbered step triggers and optional content panels.
4
+ * Use for wizards / step-by-step forms (e.g. "1 Step One", "2 Step Two").
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
+ "flex items-center flex-wrap gap-0 rounded-4 border border-border-primary bg-fill-quarternary-fill-white p-4";
20
+
21
+ /** Inactive step: muted label color only (button stays ghost large) */
22
+ var INACTIVE_LABEL_CLASS = "!text-typography-quaternary-text";
23
+
24
+ /** Box around step number: active = dark fill + invert text, inactive = dark gray fill + tertiary text */
25
+ var NUMBER_BOX_ACTIVE =
26
+ "flex h-16 w-16 items-center justify-center rounded-4 bg-typography-primary-text text-typography-invert-text shrink-0";
27
+ var NUMBER_BOX_INACTIVE =
28
+ "flex h-16 w-16 items-center justify-center rounded-4 bg-fill-primary-fill-dark-gray text-typography-tertiary-text shrink-0";
29
+
30
+ /** Horizontal line between steps (step1 ——— step2) */
31
+ var CONNECTOR_CLASS = "flex-1 min-w-12 max-w-32 h-0 border-t border-border-primary shrink-0 mx-2 self-center";
32
+
33
+ var CONTENT_BASE_CLASS =
34
+ "ring-offset-background focus-visible:ring-2 focus-visible:ring-offset-2 h-full";
35
+
36
+ function join() {
37
+ return Array.prototype.filter.call(arguments, Boolean).join(" ");
38
+ }
39
+
40
+ /**
41
+ * Create a Steps component (numbered step triggers + optional content)
42
+ * Design: ghost large Button for all steps; inactive steps use muted label color. Connectors between steps. Optional title/description above.
43
+ * @param {Object} config
44
+ * @param {Array<{id: string, label: string, content?: HTMLElement|string}>} config.steps - step definitions
45
+ * @param {string} [config.defaultValue] - initial active step id
46
+ * @param {string} [config.value] - controlled active step id
47
+ * @param {Function} [config.onChange] - (stepId) => void when step changes
48
+ * @param {string} [config.title] - optional title above steps
49
+ * @param {string} [config.description] - optional description above steps
50
+ * @param {string} [config.listClassName] - extra class on step list
51
+ * @param {string} [config.contentClassName] - extra class on content wrapper
52
+ * @param {boolean} [config.showContent=true] - whether to render content panels (if steps have content)
53
+ * @returns {HTMLElement} root element
54
+ */
55
+ function create(config) {
56
+ var opts = config || {};
57
+ var steps = opts.steps || [];
58
+ var defaultValue = opts.defaultValue;
59
+ var controlledValue = opts.value;
60
+ var onChange = opts.onChange;
61
+ var title = opts.title;
62
+ var description = opts.description;
63
+ var listClassName = opts.listClassName || "";
64
+ var contentClassName = opts.contentClassName || "";
65
+ var showContent = opts.showContent !== false;
66
+
67
+ var root = document.createElement("div");
68
+ root.className = "steps-root w-full";
69
+
70
+ var activeId =
71
+ controlledValue !== undefined
72
+ ? controlledValue
73
+ : defaultValue !== undefined
74
+ ? defaultValue
75
+ : (steps[0] && steps[0].id) || "";
76
+
77
+ if (title != null && title !== "") {
78
+ var titleEl = document.createElement("h3");
79
+ titleEl.className = "text-typography-primary-text";
80
+ titleEl.textContent = title;
81
+ root.appendChild(titleEl);
82
+ }
83
+ if (description != null && description !== "") {
84
+ var descEl = document.createElement("p");
85
+ descEl.className = "text-typography-secondary-text";
86
+ descEl.textContent = description;
87
+ root.appendChild(descEl);
88
+ }
89
+
90
+ // Step list (horizontal: step, connector, step, connector, ...)
91
+ var list = document.createElement("div");
92
+ list.setAttribute("role", "tablist");
93
+ list.setAttribute("aria-label", "Steps");
94
+ list.className = join(LIST_BASE_CLASS, listClassName);
95
+
96
+ var triggerEls = [];
97
+ var numberBoxEls = [];
98
+ var contentPanels = [];
99
+ var contentWrapper = null;
100
+
101
+ if (showContent && steps.some(function (s) { return s.content != null; })) {
102
+ contentWrapper = document.createElement("div");
103
+ contentWrapper.className = join("steps-content-wrapper mt-4", contentClassName);
104
+ }
105
+
106
+ var Button = getComponent("Button");
107
+
108
+ steps.forEach(function (step, index) {
109
+ var stepId = step.id;
110
+ var label = step.label != null ? step.label : stepId;
111
+ var content = step.content;
112
+ var stepNumber = index + 1;
113
+ var isActive = stepId === activeId;
114
+
115
+ var trigger = Button.create({
116
+ variant: "ghost",
117
+ size: "large",
118
+ text: "\u00A0",
119
+ type: "button",
120
+ className: isActive ? "" : INACTIVE_LABEL_CLASS,
121
+ onClick: function () {
122
+ if (activeId === stepId) return;
123
+ if (controlledValue === undefined) {
124
+ activeId = stepId;
125
+ updateActiveState();
126
+ }
127
+ if (typeof onChange === "function") {
128
+ onChange(stepId);
129
+ }
130
+ },
131
+ });
132
+
133
+ var numberBox = document.createElement("span");
134
+ numberBox.setAttribute("aria-hidden", "true");
135
+ numberBox.className = isActive ? NUMBER_BOX_ACTIVE : NUMBER_BOX_INACTIVE;
136
+ numberBox.textContent = String(stepNumber);
137
+
138
+ trigger.innerHTML = "";
139
+ trigger.appendChild(numberBox);
140
+ trigger.appendChild(document.createTextNode(" " + label));
141
+
142
+ trigger.setAttribute("role", "tab");
143
+ trigger.setAttribute("aria-selected", isActive ? "true" : "false");
144
+ trigger.setAttribute("data-state", isActive ? "active" : "inactive");
145
+ trigger.setAttribute("data-step-id", stepId);
146
+ trigger.tabIndex = isActive ? 0 : -1;
147
+
148
+ numberBoxEls.push(numberBox);
149
+
150
+ trigger.addEventListener("keydown", function (e) {
151
+ if (e.key === "Enter" || e.key === " ") {
152
+ e.preventDefault();
153
+ trigger.click();
154
+ }
155
+ if (e.key === "ArrowRight" || e.key === "ArrowLeft") {
156
+ e.preventDefault();
157
+ var nextIndex = e.key === "ArrowRight" ? index + 1 : index - 1;
158
+ if (nextIndex >= 0 && nextIndex < steps.length) {
159
+ var nextTrigger = triggerEls[nextIndex];
160
+ if (nextTrigger) {
161
+ nextTrigger.focus();
162
+ nextTrigger.click();
163
+ }
164
+ }
165
+ }
166
+ });
167
+
168
+ list.appendChild(trigger);
169
+ triggerEls.push(trigger);
170
+
171
+ if (index < steps.length - 1) {
172
+ var connector = document.createElement("span");
173
+ connector.className = CONNECTOR_CLASS;
174
+ connector.setAttribute("aria-hidden", "true");
175
+ list.appendChild(connector);
176
+ }
177
+
178
+ if (contentWrapper && content != null) {
179
+ var panel = document.createElement("div");
180
+ panel.setAttribute("role", "tabpanel");
181
+ panel.setAttribute("aria-hidden", stepId !== activeId ? "true" : "false");
182
+ panel.setAttribute("data-step-id", stepId);
183
+ panel.className = join(CONTENT_BASE_CLASS, stepId !== activeId ? "hidden" : "");
184
+ if (typeof content === "string") {
185
+ panel.innerHTML = content;
186
+ } else if (content && content.nodeType === 1) {
187
+ panel.appendChild(content);
188
+ }
189
+ contentWrapper.appendChild(panel);
190
+ contentPanels.push(panel);
191
+ }
192
+ });
193
+
194
+ function updateActiveState() {
195
+ triggerEls.forEach(function (t, i) {
196
+ var step = steps[i];
197
+ var id = step && step.id;
198
+ var isActive = id === activeId;
199
+ t.setAttribute("aria-selected", isActive ? "true" : "false");
200
+ t.setAttribute("data-state", isActive ? "active" : "inactive");
201
+ t.tabIndex = isActive ? 0 : -1;
202
+ if (isActive) {
203
+ t.classList.remove(INACTIVE_LABEL_CLASS);
204
+ } else {
205
+ t.classList.add(INACTIVE_LABEL_CLASS);
206
+ }
207
+ numberBoxEls[i].className = isActive ? NUMBER_BOX_ACTIVE : NUMBER_BOX_INACTIVE;
208
+ });
209
+ contentPanels.forEach(function (p, i) {
210
+ var step = steps[i];
211
+ var id = step && step.id;
212
+ var isActive = id === activeId;
213
+ p.setAttribute("aria-hidden", isActive ? "false" : "true");
214
+ p.classList.toggle("hidden", !isActive);
215
+ });
216
+ }
217
+
218
+ root.appendChild(list);
219
+ if (contentWrapper) {
220
+ root.appendChild(contentWrapper);
221
+ }
222
+
223
+ root.getValue = function () {
224
+ return activeId;
225
+ };
226
+
227
+ root.setValue = function (newId) {
228
+ if (newId === activeId) return;
229
+ activeId = newId;
230
+ updateActiveState();
231
+ };
232
+
233
+ return root;
234
+ }
235
+
236
+ var Steps = {
237
+ create: create,
238
+ };
239
+
240
+ if (typeof module !== "undefined" && module.exports) {
241
+ module.exports = Steps;
242
+ } else {
243
+ global.Steps = Steps;
244
+ }
245
+ })(typeof window !== "undefined" ? window : this);
@@ -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
@@ -1374,6 +1374,38 @@
1374
1374
  });
1375
1375
  }
1376
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
+
1377
1409
  // ============================================================================
1378
1410
  // TABLE COMPONENT
1379
1411
  // ============================================================================
@@ -1736,6 +1768,8 @@
1736
1768
 
1737
1769
  // Stepper
1738
1770
  renderStepper,
1771
+ createTabs,
1772
+ createSteps,
1739
1773
 
1740
1774
  // Alerts
1741
1775
  renderAlerts,