@superleapai/flow-ui 2.5.0 → 2.5.1

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,209 @@
1
+ /**
2
+ * CheckboxGroup Component (vanilla JS)
3
+ * Multi-select via checkboxes; same API as MultiSelect (options, value array, onValuesChange).
4
+ * Uses input.js-style variants and sizes for the group wrapper.
5
+ */
6
+
7
+ (function (global) {
8
+ "use strict";
9
+
10
+ // Wrapper classes aligned with input.js variants
11
+ var WRAPPER_CLASS = {
12
+ base:
13
+ "group flex flex-col border-1/2 border-border-primary rounded-4 text-typography-primary-text w-full transition-all ease-in-out group-has-[:disabled]:cursor-not-allowed group-has-[:disabled]:border-border-primary group-has-[:disabled]:bg-fill-tertiary-fill-light-gray group-has-[:disabled]:text-typography-quaternary-text",
14
+ default:
15
+ "bg-fill-quarternary-fill-white hover:border-primary-base focus-within:border-primary-base",
16
+ error:
17
+ "border-error-base bg-fill-quarternary-fill-white hover:border-error-base focus-within:border-error-base",
18
+ warning:
19
+ "border-warning-base bg-fill-quarternary-fill-white hover:border-warning-base focus-within:border-warning-base",
20
+ success:
21
+ "border-success-base bg-fill-quarternary-fill-white hover:border-success-base focus-within:border-success-base",
22
+ borderless:
23
+ "border-none shadow-none rounded-0 bg-fill-quarternary-fill-white",
24
+ inline:
25
+ "border-transparent shadow-none rounded-0 bg-fill-quarternary-fill-white hover:bg-fill-tertiary-fill-light-gray focus-within:border-transparent focus:bg-fill-tertiary-fill-light-gray focus-within:bg-fill-tertiary-fill-light-gray",
26
+ sizeDefault: "px-12 py-6 gap-6",
27
+ sizeLarge: "px-12 py-8 gap-8",
28
+ sizeSmall: "px-12 py-4 gap-4",
29
+ disabled:
30
+ "cursor-not-allowed border-border-primary bg-fill-tertiary-fill-light-gray text-typography-quaternary-text hover:border-border-primary",
31
+ };
32
+
33
+ function join() {
34
+ return Array.prototype.filter.call(arguments, Boolean).join(" ");
35
+ }
36
+
37
+ function getOptionValue(opt) {
38
+ return opt.value !== undefined && opt.value !== null
39
+ ? opt.value
40
+ : opt.slug || opt.id;
41
+ }
42
+
43
+ function getOptionLabel(opt) {
44
+ return opt.label || opt.name || opt.display_name || opt.value;
45
+ }
46
+
47
+ function getDep(name) {
48
+ if (typeof global.FlowUI !== "undefined" && typeof global.FlowUI._getComponent === "function") {
49
+ var c = global.FlowUI._getComponent(name);
50
+ if (c) return c;
51
+ }
52
+ return global[name];
53
+ }
54
+
55
+ /**
56
+ * Create a checkbox group component (multiselect-like: multiple values, options array)
57
+ * @param {Object} config
58
+ * @param {string} [config.fieldId] - Field ID for state management
59
+ * @param {Array} config.options - Array of { value, label } or { slug, display_name }
60
+ * @param {Array} [config.value] - Current selected values (array)
61
+ * @param {Function} config.onValuesChange - Change handler (values: string[])
62
+ * @param {boolean} [config.disabled] - Whether all checkboxes are disabled
63
+ * @param {string} [config.variant] - 'default' | 'error' | 'warning' | 'success' | 'borderless' | 'inline'
64
+ * @param {string} [config.size] - 'default' | 'large' | 'small'
65
+ * @param {string} [config.layout] - 'vertical' | 'horizontal'
66
+ * @param {string} [config.className] - Extra class on wrapper
67
+ * @returns {HTMLElement} CheckboxGroup container element
68
+ */
69
+ function createCheckboxGroup(config) {
70
+ var fieldId = config.fieldId;
71
+ var options = config.options || [];
72
+ var onValuesChange = config.onValuesChange;
73
+ var variant = config.variant || "default";
74
+ var size = config.size || "default";
75
+ var disabled = config.disabled === true;
76
+ var layout = config.layout || "vertical";
77
+ var className = config.className || "";
78
+
79
+ var values = Array.isArray(config.value)
80
+ ? config.value.slice()
81
+ : Array.isArray(config.values)
82
+ ? config.values.slice()
83
+ : [];
84
+
85
+ var Checkbox = getDep("Checkbox");
86
+ if (!Checkbox || typeof Checkbox.create !== "function") {
87
+ throw new Error("CheckboxGroup requires the Checkbox component. Load checkbox.js before checkbox-group.js.");
88
+ }
89
+
90
+ var container = document.createElement("div");
91
+ container.setAttribute("role", "group");
92
+ container.setAttribute("aria-label", config.ariaLabel || "Checkbox group");
93
+ if (fieldId) container.setAttribute("data-field-id", fieldId);
94
+
95
+ var sizeClass = size === "large" ? WRAPPER_CLASS.sizeLarge : size === "small" ? WRAPPER_CLASS.sizeSmall : WRAPPER_CLASS.sizeDefault;
96
+ function applyWrapperClasses() {
97
+ container.className = join(
98
+ WRAPPER_CLASS.base,
99
+ disabled ? WRAPPER_CLASS.disabled : (WRAPPER_CLASS[variant] || WRAPPER_CLASS.default),
100
+ sizeClass,
101
+ layout === "horizontal" ? "flex-row flex-wrap" : "flex-col",
102
+ "custom-checkbox-group",
103
+ className
104
+ );
105
+ }
106
+ applyWrapperClasses();
107
+ container.setAttribute("data-checkbox-group-variant", variant);
108
+
109
+ function isSelected(optionValue) {
110
+ return values.some(function (v) {
111
+ return v === optionValue || String(v) === String(optionValue);
112
+ });
113
+ }
114
+
115
+ function toggleValue(optionValue) {
116
+ var idx = values.findIndex(function (v) {
117
+ return v === optionValue || String(v) === String(optionValue);
118
+ });
119
+ if (idx >= 0) {
120
+ values.splice(idx, 1);
121
+ } else {
122
+ values.push(optionValue);
123
+ }
124
+ if (onValuesChange) onValuesChange(values.slice());
125
+ }
126
+
127
+ var optionsContainer = document.createElement("div");
128
+ optionsContainer.className = join(
129
+ "flex gap-8",
130
+ layout === "horizontal" ? "flex-row flex-wrap" : "flex-col"
131
+ );
132
+
133
+ function buildOptions() {
134
+ optionsContainer.innerHTML = "";
135
+ if (options.length === 0) {
136
+ var empty = document.createElement("div");
137
+ empty.className = "!text-reg-13 text-typography-quaternary-text py-4";
138
+ empty.textContent = "No options available";
139
+ optionsContainer.appendChild(empty);
140
+ return;
141
+ }
142
+
143
+ options.forEach(function (opt, index) {
144
+ var optionValue = getOptionValue(opt);
145
+ var optionLabel = getOptionLabel(opt);
146
+ var optionDisabled = disabled || !!opt.disabled;
147
+ var checked = isSelected(optionValue);
148
+
149
+ var cb = Checkbox.create({
150
+ id: (fieldId || "cbg") + "-" + index,
151
+ name: fieldId ? fieldId + "[]" : "checkbox-group-" + index,
152
+ checked: checked,
153
+ disabled: optionDisabled,
154
+ label: optionLabel,
155
+ align: "left",
156
+ size: size === "large" ? "large" : size === "small" ? "small" : "default",
157
+ onChange: function (isChecked) {
158
+ if (optionDisabled) return;
159
+ if (isChecked) {
160
+ if (!values.some(function (v) { return v === optionValue || String(v) === String(optionValue); })) {
161
+ values.push(optionValue);
162
+ }
163
+ } else {
164
+ var idx = values.findIndex(function (v) {
165
+ return v === optionValue || String(v) === String(optionValue);
166
+ });
167
+ if (idx >= 0) values.splice(idx, 1);
168
+ }
169
+ if (onValuesChange) onValuesChange(values.slice());
170
+ },
171
+ });
172
+ optionsContainer.appendChild(cb);
173
+ });
174
+ }
175
+
176
+ buildOptions();
177
+ container.appendChild(optionsContainer);
178
+
179
+ container.updateValues = function (newValues) {
180
+ values = Array.isArray(newValues) ? newValues.slice() : [];
181
+ buildOptions();
182
+ };
183
+
184
+ container.setDisabled = function (isDisabled) {
185
+ disabled = !!isDisabled;
186
+ applyWrapperClasses();
187
+ var wrappers = optionsContainer.querySelectorAll(":scope > div");
188
+ for (var i = 0; i < wrappers.length; i++) {
189
+ if (typeof wrappers[i].setDisabled === "function") wrappers[i].setDisabled(disabled);
190
+ }
191
+ };
192
+
193
+ container.setVariant = function (v) {
194
+ variant = v;
195
+ container.setAttribute("data-checkbox-group-variant", v);
196
+ applyWrapperClasses();
197
+ };
198
+
199
+ container.getValues = function () {
200
+ return values.slice();
201
+ };
202
+
203
+ return container;
204
+ }
205
+
206
+ global.CheckboxGroup = {
207
+ create: createCheckboxGroup,
208
+ };
209
+ })(typeof window !== "undefined" ? window : this);
@@ -40,6 +40,15 @@
40
40
  return ICONS.file;
41
41
  }
42
42
 
43
+ /** Resolve client: use FlowUI._getComponent when bundle has captured globals, else global.superleapClient (same as enum-select) */
44
+ function getClient() {
45
+ if (global.FlowUI && typeof global.FlowUI._getComponent === "function") {
46
+ var c = global.FlowUI._getComponent("superleapClient");
47
+ if (c) return c;
48
+ }
49
+ return global.superleapClient;
50
+ }
51
+
43
52
  /**
44
53
  * Upload file to S3
45
54
  * @param {File} file - File to upload
@@ -51,26 +60,31 @@
51
60
  formData.append("file", file, file.name);
52
61
  formData.append("is_private", String(!!isPrivate));
53
62
 
54
- // Get upload URL - can be configured via global.S3_UPLOAD_URL
63
+ // Get upload path - can be configured via global.S3_UPLOAD_URL
55
64
  const uploadUrl = global.S3_UPLOAD_URL || "/org/file/upload";
56
- const baseUrl = global.SUPERLEAP_BASE_URL || "https://app.superleap.com/api/v1";
57
- const fullUrl = uploadUrl.startsWith("http") ? uploadUrl : `${baseUrl}${uploadUrl}`;
58
65
 
59
- // Get API key: use FlowUI._getComponent when globals were captured (index.js), else global.superleapClient
60
- let apiKey = null;
66
+ // Base URL and API key from superleapClient only (same pattern as enum-select)
67
+ var client = getClient();
68
+ var baseUrl = null;
69
+ var apiKey = null;
61
70
  try {
62
- const client =
63
- global.FlowUI && typeof global.FlowUI._getComponent === "function"
64
- ? global.FlowUI._getComponent("superleapClient")
65
- : global.superleapClient;
71
+ if (client && typeof client.getBaseUrl === "function") {
72
+ baseUrl = client.getBaseUrl();
73
+ }
66
74
  if (client && typeof client.getSdk === "function") {
67
- const sdk = client.getSdk();
68
- apiKey = sdk?.apiKey;
75
+ var sdk = client.getSdk();
76
+ apiKey = sdk ? sdk.apiKey : null;
69
77
  }
70
78
  } catch (e) {
71
- console.warn("[S3FileUpload] Could not get API key:", e);
79
+ console.warn("[S3FileUpload] Could not get client:", e);
72
80
  }
73
81
 
82
+ if (!baseUrl) {
83
+ throw new Error("SuperLeap client not initialized. Call superleapClient.init({ baseUrl, apiKey }) first.");
84
+ }
85
+
86
+ const fullUrl = uploadUrl.startsWith("http") ? uploadUrl : baseUrl.replace(/\/$/, "") + (uploadUrl.startsWith("/") ? uploadUrl : "/" + uploadUrl);
87
+
74
88
  const headers = {};
75
89
  if (apiKey) {
76
90
  headers.Authorization = `Bearer ${apiKey}`;
package/core/flow.js CHANGED
@@ -591,81 +591,6 @@
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
-
669
594
  /**
670
595
  * Create a multi-select field (uses MultiSelect component when available, else checkbox group)
671
596
  * @param {Object} config - Configuration object
@@ -1422,6 +1347,40 @@
1422
1347
  return field;
1423
1348
  }
1424
1349
 
1350
+ /**
1351
+ * Create a checkbox group field (multiselect-like: options array, value array, onValuesChange)
1352
+ * @param {Object} config - { label, fieldId, options, required, helpText, variant, size, layout, disabled, onChange }
1353
+ * @returns {HTMLElement} Field wrapper containing checkbox group
1354
+ */
1355
+ function createCheckboxGroup(config) {
1356
+ const { label, fieldId, options = [], required = false, helpText = null, variant, size, layout = "vertical", disabled = false, onChange } = config;
1357
+
1358
+ const field = createFieldWrapper(label, required, helpText);
1359
+ field.setAttribute("data-field-id", fieldId);
1360
+
1361
+ if (getComponent("CheckboxGroup") && getComponent("CheckboxGroup").create) {
1362
+ const currentValues = get(fieldId) || [];
1363
+ const groupEl = getComponent("CheckboxGroup").create({
1364
+ fieldId,
1365
+ options,
1366
+ value: currentValues,
1367
+ variant: variant || "default",
1368
+ size: size || "default",
1369
+ layout,
1370
+ disabled,
1371
+ onValuesChange: (values) => {
1372
+ set(fieldId, values);
1373
+ if (onChange) onChange(values);
1374
+ },
1375
+ });
1376
+ groupEl._fieldId = fieldId;
1377
+ field.appendChild(groupEl);
1378
+ return field;
1379
+ }
1380
+
1381
+ return field;
1382
+ }
1383
+
1425
1384
  // ============================================================================
1426
1385
  // STEPPER COMPONENT
1427
1386
  // ============================================================================
@@ -1816,7 +1775,6 @@
1816
1775
  createTimePicker,
1817
1776
  createDateTimePicker,
1818
1777
  createRadioGroup,
1819
- createCardSelect,
1820
1778
  createMultiSelect,
1821
1779
  createRecordSelect,
1822
1780
  createRecordMultiSelect,
@@ -1828,6 +1786,7 @@
1828
1786
  createCurrency,
1829
1787
  createPhoneInput,
1830
1788
  createCheckbox,
1789
+ createCheckboxGroup,
1831
1790
 
1832
1791
  // Button (delegates to Button component when available; resolved at call time via getComponent)
1833
1792
  createButton: function (config) {
@@ -137,11 +137,23 @@
137
137
  return mergeConfig({}, DEFAULT_CONFIG);
138
138
  }
139
139
 
140
+ /**
141
+ * Return the current base URL (from merged config after init).
142
+ * Used by file-input and other components that build API URLs.
143
+ *
144
+ * @returns {string|null} baseUrl or null if not initialized
145
+ */
146
+ function getBaseUrl() {
147
+ if (_config && _config.baseUrl) return _config.baseUrl;
148
+ return null;
149
+ }
150
+
140
151
  var superleapClient = {
141
152
  init: init,
142
153
  getSdk: getSdk,
143
154
  isAvailable: isAvailable,
144
155
  getDefaultConfig: getDefaultConfig,
156
+ getBaseUrl: getBaseUrl,
145
157
  };
146
158
 
147
159
  if (global) {