@superleapai/flow-ui 2.5.2 → 2.5.3
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/card-select.js +283 -0
- package/core/flow.js +76 -0
- package/dist/output.css +1 -1
- package/dist/superleap-flow.js +271 -1860
- package/dist/superleap-flow.js.map +1 -1
- package/dist/superleap-flow.min.js +2 -2
- package/index.js +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CardSelect Component (vanilla JS)
|
|
3
|
+
* Full-width clickable card selection with icon, title, description, and check indicator.
|
|
4
|
+
* Drop-in replacement / upgrade to RadioGroup when visual card UI is preferred.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
(function (global) {
|
|
8
|
+
"use strict";
|
|
9
|
+
|
|
10
|
+
var COLORS = {
|
|
11
|
+
selectedBorder: "#175259",
|
|
12
|
+
unselectedBorder: "#e5e7eb",
|
|
13
|
+
selectedBg: "#f0f9f8",
|
|
14
|
+
unselectedBg: "#ffffff",
|
|
15
|
+
selectedShadow: "0px 0px 0px 2px #e9f7f5",
|
|
16
|
+
unselectedShadow: "0px 1.5px 4px -1px rgba(10,9,11,0.07)",
|
|
17
|
+
hoverBorder: "#9ca3af",
|
|
18
|
+
hoverShadow: "0px 5px 13px -5px rgba(10,9,11,0.05), 0px 2px 4px -1px rgba(10,9,11,0.02)",
|
|
19
|
+
iconSelectedBg: "#d0ede9",
|
|
20
|
+
iconUnselectedBg: "#f3f4f6",
|
|
21
|
+
iconSelectedColor: "#175259",
|
|
22
|
+
iconUnselectedColor: "#6b7280",
|
|
23
|
+
titleSelected: "#175259",
|
|
24
|
+
titleUnselected: "#111827",
|
|
25
|
+
descSelected: "#35b18b",
|
|
26
|
+
descUnselected: "#6b7280",
|
|
27
|
+
checkBorderSelected: "#175259",
|
|
28
|
+
checkBorderUnselected: "#d1d5db",
|
|
29
|
+
checkBgSelected: "#175259",
|
|
30
|
+
checkBgUnselected: "transparent",
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
var CHECK_ICON =
|
|
34
|
+
'<svg width="10" height="10" viewBox="0 0 10 10" fill="none"><path d="M2 5l2.5 2.5L8 3" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>';
|
|
35
|
+
|
|
36
|
+
function join() {
|
|
37
|
+
return Array.prototype.filter.call(arguments, Boolean).join(" ");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function applyCardStyles(card, isSelected) {
|
|
41
|
+
card.style.borderColor = isSelected ? COLORS.selectedBorder : COLORS.unselectedBorder;
|
|
42
|
+
card.style.background = isSelected ? COLORS.selectedBg : COLORS.unselectedBg;
|
|
43
|
+
card.style.boxShadow = isSelected ? COLORS.selectedShadow : COLORS.unselectedShadow;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function applyIconStyles(iconWrapper, isSelected) {
|
|
47
|
+
iconWrapper.style.background = isSelected ? COLORS.iconSelectedBg : COLORS.iconUnselectedBg;
|
|
48
|
+
iconWrapper.style.color = isSelected ? COLORS.iconSelectedColor : COLORS.iconUnselectedColor;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function applyTitleStyles(titleEl, isSelected) {
|
|
52
|
+
titleEl.style.color = isSelected ? COLORS.titleSelected : COLORS.titleUnselected;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function applyDescStyles(descEl, isSelected) {
|
|
56
|
+
descEl.style.color = isSelected ? COLORS.descSelected : COLORS.descUnselected;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function applyCheckStyles(checkEl, isSelected) {
|
|
60
|
+
checkEl.style.borderColor = isSelected ? COLORS.checkBorderSelected : COLORS.checkBorderUnselected;
|
|
61
|
+
checkEl.style.background = isSelected ? COLORS.checkBgSelected : COLORS.checkBgUnselected;
|
|
62
|
+
checkEl.innerHTML = isSelected ? CHECK_ICON : "";
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Create a card select component
|
|
67
|
+
* @param {Object} config
|
|
68
|
+
* @param {string} [config.name] - name attribute for the group (used for id generation)
|
|
69
|
+
* @param {Array} config.options - array of { value, label, description?, icon?, disabled? }
|
|
70
|
+
* @param {string} [config.defaultValue] - initial selected value
|
|
71
|
+
* @param {string} [config.value] - controlled value (takes priority over defaultValue)
|
|
72
|
+
* @param {boolean} [config.disabled] - disable all cards
|
|
73
|
+
* @param {string} [config.className] - extra class on wrapper
|
|
74
|
+
* @param {Function} [config.onChange] - change handler (receives selected value)
|
|
75
|
+
* @returns {HTMLElement} wrapper element with getValue/setValue/setDisabled API
|
|
76
|
+
*/
|
|
77
|
+
function create(config) {
|
|
78
|
+
var opts = config || {};
|
|
79
|
+
var name = opts.name || "card-select-" + Math.random().toString(36).substr(2, 9);
|
|
80
|
+
var options = opts.options || [];
|
|
81
|
+
var defaultValue = opts.defaultValue;
|
|
82
|
+
var selectedValue = opts.value !== undefined ? opts.value : defaultValue;
|
|
83
|
+
var disabled = !!opts.disabled;
|
|
84
|
+
var className = opts.className || "";
|
|
85
|
+
var onChange = opts.onChange;
|
|
86
|
+
|
|
87
|
+
// Wrapper container
|
|
88
|
+
var wrapper = document.createElement("div");
|
|
89
|
+
wrapper.setAttribute("role", "radiogroup");
|
|
90
|
+
wrapper.setAttribute("dir", "ltr");
|
|
91
|
+
wrapper.className = join("flex flex-col gap-3 w-full", className);
|
|
92
|
+
|
|
93
|
+
function updateAllCards(newValue) {
|
|
94
|
+
var cards = wrapper.querySelectorAll("[data-card-value]");
|
|
95
|
+
cards.forEach(function (card) {
|
|
96
|
+
var cv = card.dataset.cardValue;
|
|
97
|
+
var active = cv === newValue;
|
|
98
|
+
applyCardStyles(card, active);
|
|
99
|
+
card.setAttribute("aria-checked", active ? "true" : "false");
|
|
100
|
+
var iw = card.querySelector("[data-icon]");
|
|
101
|
+
var titleEl = card.querySelector("[data-title]");
|
|
102
|
+
var descEl = card.querySelector("[data-desc]");
|
|
103
|
+
var checkEl = card.querySelector("[data-check]");
|
|
104
|
+
if (iw) applyIconStyles(iw, active);
|
|
105
|
+
if (titleEl) applyTitleStyles(titleEl, active);
|
|
106
|
+
if (descEl) applyDescStyles(descEl, active);
|
|
107
|
+
if (checkEl) applyCheckStyles(checkEl, active);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
options.forEach(function (option, index) {
|
|
112
|
+
var optionValue = option.value;
|
|
113
|
+
var optionLabel = option.label || option.value;
|
|
114
|
+
var optionDesc = option.description || "";
|
|
115
|
+
var optionIcon = option.icon || "";
|
|
116
|
+
var optionDisabled = disabled || !!option.disabled;
|
|
117
|
+
var isSelected = optionValue === selectedValue;
|
|
118
|
+
|
|
119
|
+
// Card element
|
|
120
|
+
var card = document.createElement("div");
|
|
121
|
+
card.dataset.cardValue = optionValue;
|
|
122
|
+
card.id = name + "-card-" + index;
|
|
123
|
+
card.setAttribute("role", "radio");
|
|
124
|
+
card.setAttribute("aria-checked", isSelected ? "true" : "false");
|
|
125
|
+
card.setAttribute("tabindex", optionDisabled ? "-1" : "0");
|
|
126
|
+
|
|
127
|
+
card.style.cssText = [
|
|
128
|
+
"display: flex",
|
|
129
|
+
"align-items: flex-start",
|
|
130
|
+
"gap: 16px",
|
|
131
|
+
"padding: 18px 20px",
|
|
132
|
+
"border-radius: 10px",
|
|
133
|
+
"border: 1.5px solid " + (isSelected ? COLORS.selectedBorder : COLORS.unselectedBorder),
|
|
134
|
+
"background: " + (isSelected ? COLORS.selectedBg : COLORS.unselectedBg),
|
|
135
|
+
"cursor: " + (optionDisabled ? "not-allowed" : "pointer"),
|
|
136
|
+
"transition: border-color 0.15s, background 0.15s, box-shadow 0.15s",
|
|
137
|
+
"box-shadow: " + (isSelected ? COLORS.selectedShadow : COLORS.unselectedShadow),
|
|
138
|
+
"user-select: none",
|
|
139
|
+
optionDisabled ? "opacity: 0.5" : "",
|
|
140
|
+
].filter(Boolean).join("; ");
|
|
141
|
+
|
|
142
|
+
// Icon wrapper (only rendered when icon is provided)
|
|
143
|
+
if (optionIcon) {
|
|
144
|
+
var iconWrapper = document.createElement("div");
|
|
145
|
+
iconWrapper.dataset.icon = "";
|
|
146
|
+
iconWrapper.style.cssText = [
|
|
147
|
+
"flex-shrink: 0",
|
|
148
|
+
"width: 44px",
|
|
149
|
+
"height: 44px",
|
|
150
|
+
"border-radius: 8px",
|
|
151
|
+
"background: " + (isSelected ? COLORS.iconSelectedBg : COLORS.iconUnselectedBg),
|
|
152
|
+
"display: flex",
|
|
153
|
+
"align-items: center",
|
|
154
|
+
"justify-content: center",
|
|
155
|
+
"color: " + (isSelected ? COLORS.iconSelectedColor : COLORS.iconUnselectedColor),
|
|
156
|
+
"transition: background 0.15s, color 0.15s",
|
|
157
|
+
].join("; ");
|
|
158
|
+
iconWrapper.innerHTML = optionIcon;
|
|
159
|
+
card.appendChild(iconWrapper);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Text wrapper
|
|
163
|
+
var textWrapper = document.createElement("div");
|
|
164
|
+
textWrapper.style.cssText = "display: flex; flex-direction: column; gap: 4px; flex: 1;";
|
|
165
|
+
|
|
166
|
+
var titleEl = document.createElement("span");
|
|
167
|
+
titleEl.dataset.title = "";
|
|
168
|
+
titleEl.textContent = optionLabel;
|
|
169
|
+
titleEl.style.cssText = [
|
|
170
|
+
"font-size: 14px",
|
|
171
|
+
"font-weight: 600",
|
|
172
|
+
"color: " + (isSelected ? COLORS.titleSelected : COLORS.titleUnselected),
|
|
173
|
+
"line-height: 1.4",
|
|
174
|
+
"transition: color 0.15s",
|
|
175
|
+
].join("; ");
|
|
176
|
+
textWrapper.appendChild(titleEl);
|
|
177
|
+
|
|
178
|
+
if (optionDesc) {
|
|
179
|
+
var descEl = document.createElement("span");
|
|
180
|
+
descEl.dataset.desc = "";
|
|
181
|
+
descEl.textContent = optionDesc;
|
|
182
|
+
descEl.style.cssText = [
|
|
183
|
+
"font-size: 12px",
|
|
184
|
+
"color: " + (isSelected ? COLORS.descSelected : COLORS.descUnselected),
|
|
185
|
+
"line-height: 1.5",
|
|
186
|
+
"transition: color 0.15s",
|
|
187
|
+
].join("; ");
|
|
188
|
+
textWrapper.appendChild(descEl);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
card.appendChild(textWrapper);
|
|
192
|
+
|
|
193
|
+
// Check indicator (radio circle in top-right)
|
|
194
|
+
var checkEl = document.createElement("div");
|
|
195
|
+
checkEl.dataset.check = "";
|
|
196
|
+
checkEl.style.cssText = [
|
|
197
|
+
"flex-shrink: 0",
|
|
198
|
+
"width: 18px",
|
|
199
|
+
"height: 18px",
|
|
200
|
+
"border-radius: 50%",
|
|
201
|
+
"border: 2px solid " + (isSelected ? COLORS.checkBorderSelected : COLORS.checkBorderUnselected),
|
|
202
|
+
"background: " + (isSelected ? COLORS.checkBgSelected : COLORS.checkBgUnselected),
|
|
203
|
+
"display: flex",
|
|
204
|
+
"align-items: center",
|
|
205
|
+
"justify-content: center",
|
|
206
|
+
"margin-top: 2px",
|
|
207
|
+
"transition: all 0.15s",
|
|
208
|
+
].join("; ");
|
|
209
|
+
if (isSelected) {
|
|
210
|
+
checkEl.innerHTML = CHECK_ICON;
|
|
211
|
+
}
|
|
212
|
+
card.appendChild(checkEl);
|
|
213
|
+
|
|
214
|
+
// Hover and focus styles
|
|
215
|
+
if (!optionDisabled) {
|
|
216
|
+
card.addEventListener("mouseenter", function () {
|
|
217
|
+
if (card.getAttribute("aria-checked") !== "true") {
|
|
218
|
+
card.style.borderColor = COLORS.hoverBorder;
|
|
219
|
+
card.style.boxShadow = COLORS.hoverShadow;
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
card.addEventListener("mouseleave", function () {
|
|
223
|
+
if (card.getAttribute("aria-checked") !== "true") {
|
|
224
|
+
card.style.borderColor = COLORS.unselectedBorder;
|
|
225
|
+
card.style.boxShadow = COLORS.unselectedShadow;
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// Click handler
|
|
230
|
+
card.addEventListener("click", function () {
|
|
231
|
+
if (optionDisabled || disabled) return;
|
|
232
|
+
selectedValue = optionValue;
|
|
233
|
+
updateAllCards(selectedValue);
|
|
234
|
+
if (typeof onChange === "function") {
|
|
235
|
+
onChange(selectedValue);
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
// Keyboard support
|
|
240
|
+
card.addEventListener("keydown", function (e) {
|
|
241
|
+
if (optionDisabled || disabled) return;
|
|
242
|
+
if (e.key === " " || e.key === "Enter") {
|
|
243
|
+
e.preventDefault();
|
|
244
|
+
card.click();
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
wrapper.appendChild(card);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
// Public API
|
|
253
|
+
wrapper.getValue = function () {
|
|
254
|
+
return selectedValue !== undefined ? selectedValue : null;
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
wrapper.setValue = function (newValue) {
|
|
258
|
+
selectedValue = newValue;
|
|
259
|
+
updateAllCards(newValue);
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
wrapper.setDisabled = function (isDisabled) {
|
|
263
|
+
disabled = !!isDisabled;
|
|
264
|
+
wrapper.querySelectorAll("[data-card-value]").forEach(function (card) {
|
|
265
|
+
card.style.cursor = disabled ? "not-allowed" : "pointer";
|
|
266
|
+
card.style.opacity = disabled ? "0.5" : "1";
|
|
267
|
+
card.setAttribute("tabindex", disabled ? "-1" : "0");
|
|
268
|
+
});
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
return wrapper;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
var CardSelect = {
|
|
275
|
+
create: create,
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
if (typeof module !== "undefined" && module.exports) {
|
|
279
|
+
module.exports = CardSelect;
|
|
280
|
+
} else {
|
|
281
|
+
global.CardSelect = CardSelect;
|
|
282
|
+
}
|
|
283
|
+
})(typeof window !== "undefined" ? window : this);
|
package/core/flow.js
CHANGED
|
@@ -591,6 +591,81 @@
|
|
|
591
591
|
return field;
|
|
592
592
|
}
|
|
593
593
|
|
|
594
|
+
/**
|
|
595
|
+
* Create a card select field (uses CardSelect component when available, else radio fallback)
|
|
596
|
+
* @param {Object} config - Configuration object
|
|
597
|
+
* @param {string} config.label - Field label
|
|
598
|
+
* @param {string} config.fieldId - State key for this field
|
|
599
|
+
* @param {Array} config.options - Array of { value, label, description?, icon?, disabled? }
|
|
600
|
+
* @param {boolean} [config.required] - Whether field is required
|
|
601
|
+
* @param {Function} [config.onChange] - Optional change handler (receives selected value)
|
|
602
|
+
* @param {string} [config.helpText] - Optional help text for tooltip
|
|
603
|
+
* @param {boolean} [config.disabled] - Whether all cards are disabled
|
|
604
|
+
* @param {string} [config.className] - Extra CSS class on card container
|
|
605
|
+
* @returns {HTMLElement} Field wrapper element
|
|
606
|
+
*/
|
|
607
|
+
function createCardSelect(config) {
|
|
608
|
+
const { label, fieldId, options = [], required = false, onChange, helpText = null, disabled = false, className } = config;
|
|
609
|
+
|
|
610
|
+
const field = createFieldWrapper(label, required, helpText);
|
|
611
|
+
|
|
612
|
+
if (getComponent("CardSelect") && getComponent("CardSelect").create) {
|
|
613
|
+
const currentValue = get(fieldId);
|
|
614
|
+
const cardSelectEl = getComponent("CardSelect").create({
|
|
615
|
+
name: fieldId,
|
|
616
|
+
options: options.map((opt) => ({
|
|
617
|
+
value: opt.value,
|
|
618
|
+
label: opt.label || opt.value,
|
|
619
|
+
description: opt.description,
|
|
620
|
+
icon: opt.icon,
|
|
621
|
+
disabled: opt.disabled,
|
|
622
|
+
})),
|
|
623
|
+
value: currentValue,
|
|
624
|
+
disabled,
|
|
625
|
+
className,
|
|
626
|
+
onChange: (value) => {
|
|
627
|
+
set(fieldId, value);
|
|
628
|
+
if (onChange) onChange(value);
|
|
629
|
+
},
|
|
630
|
+
});
|
|
631
|
+
cardSelectEl._fieldId = fieldId;
|
|
632
|
+
field.appendChild(cardSelectEl);
|
|
633
|
+
return field;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// Fallback: native radio buttons
|
|
637
|
+
const radioGroup = document.createElement("div");
|
|
638
|
+
radioGroup.className = "card-select-fallback";
|
|
639
|
+
|
|
640
|
+
options.forEach((opt) => {
|
|
641
|
+
const wrapper = document.createElement("div");
|
|
642
|
+
wrapper.className = "card-option";
|
|
643
|
+
|
|
644
|
+
const radio = document.createElement("input");
|
|
645
|
+
radio.type = "radio";
|
|
646
|
+
radio.name = fieldId;
|
|
647
|
+
radio.value = opt.value;
|
|
648
|
+
radio.id = `${fieldId}-${opt.value}`;
|
|
649
|
+
radio.checked = get(fieldId) === opt.value;
|
|
650
|
+
radio.disabled = disabled || !!opt.disabled;
|
|
651
|
+
radio.addEventListener("change", () => {
|
|
652
|
+
set(fieldId, opt.value);
|
|
653
|
+
if (onChange) { onChange(opt.value); }
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
const radioLabel = document.createElement("label");
|
|
657
|
+
radioLabel.htmlFor = `${fieldId}-${opt.value}`;
|
|
658
|
+
radioLabel.textContent = opt.label || opt.value;
|
|
659
|
+
|
|
660
|
+
wrapper.appendChild(radio);
|
|
661
|
+
wrapper.appendChild(radioLabel);
|
|
662
|
+
radioGroup.appendChild(wrapper);
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
field.appendChild(radioGroup);
|
|
666
|
+
return field;
|
|
667
|
+
}
|
|
668
|
+
|
|
594
669
|
/**
|
|
595
670
|
* Create a multi-select field (uses MultiSelect component when available, else checkbox group)
|
|
596
671
|
* @param {Object} config - Configuration object
|
|
@@ -1775,6 +1850,7 @@
|
|
|
1775
1850
|
createTimePicker,
|
|
1776
1851
|
createDateTimePicker,
|
|
1777
1852
|
createRadioGroup,
|
|
1853
|
+
createCardSelect,
|
|
1778
1854
|
createMultiSelect,
|
|
1779
1855
|
createRecordSelect,
|
|
1780
1856
|
createRecordMultiSelect,
|