@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.
- package/components/icon.js +34 -0
- package/components/richtext-editor.js +336 -0
- package/components/steps.js +245 -0
- package/components/tabs.js +191 -0
- package/core/flow.js +77 -0
- package/dist/output.css +1 -1
- package/dist/superleap-flow.min.js +2 -2
- package/index.d.ts +36 -0
- package/index.js +6 -0
- package/package.json +1 -1
|
@@ -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,
|