@superleapai/flow-ui 1.0.0

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.
Files changed (40) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/LICENSE +21 -0
  3. package/README.md +451 -0
  4. package/components/alert.js +282 -0
  5. package/components/avatar.js +195 -0
  6. package/components/badge.js +135 -0
  7. package/components/button.js +201 -0
  8. package/components/checkbox.js +254 -0
  9. package/components/currency.js +227 -0
  10. package/components/date-time-picker/date-time-picker-utils.js +253 -0
  11. package/components/date-time-picker/date-time-picker.js +532 -0
  12. package/components/duration/duration-constants.js +46 -0
  13. package/components/duration/duration-utils.js +164 -0
  14. package/components/duration/duration.js +448 -0
  15. package/components/enum-multiselect.js +869 -0
  16. package/components/enum-select.js +831 -0
  17. package/components/enumeration.js +213 -0
  18. package/components/file-input.js +533 -0
  19. package/components/icon.js +200 -0
  20. package/components/input.js +259 -0
  21. package/components/label.js +111 -0
  22. package/components/multiselect.js +351 -0
  23. package/components/phone-input/phone-input.js +392 -0
  24. package/components/phone-input/phone-utils.js +157 -0
  25. package/components/popover.js +240 -0
  26. package/components/radio-group.js +435 -0
  27. package/components/record-multiselect.js +956 -0
  28. package/components/record-select.js +930 -0
  29. package/components/select.js +544 -0
  30. package/components/spinner.js +136 -0
  31. package/components/table.js +335 -0
  32. package/components/textarea.js +114 -0
  33. package/components/time-picker.js +357 -0
  34. package/components/toast.js +343 -0
  35. package/core/flow.js +1729 -0
  36. package/core/superleapClient.js +146 -0
  37. package/dist/output.css +2 -0
  38. package/index.d.ts +458 -0
  39. package/index.js +253 -0
  40. package/package.json +70 -0
@@ -0,0 +1,335 @@
1
+ /**
2
+ * Table Component (vanilla JS, Tailwind)
3
+ * A flexible table component for rendering tabular data with support for
4
+ * custom rendering and infinite scroll.
5
+ */
6
+
7
+ (function (global) {
8
+ "use strict";
9
+
10
+ /**
11
+ * Create a table component
12
+ * @param {Object} options - Configuration options
13
+ * @param {Array} options.data - Array of data objects to display
14
+ * @param {Array} options.columns - Column definitions
15
+ * @param {boolean} options.showHeader - Show table header (default: true)
16
+ * @param {string} options.headerSize - Header size: 'small', 'default', 'large'
17
+ * @param {boolean} options.hasBorder - Show borders (default: true)
18
+ * @param {Function} options.onRowClick - Callback when row is clicked
19
+ * @param {Function} options.onFetch - Callback for infinite scroll
20
+ * @param {boolean} options.hasMore - Whether more data is available
21
+ * @param {boolean} options.isLoading - Loading state
22
+ * @param {string} options.emptyMessage - Message to show when no data
23
+ * @returns {Object} Table instance with methods
24
+ */
25
+ function createTable(options = {}) {
26
+ const {
27
+ data = [],
28
+ columns = [],
29
+ showHeader = true,
30
+ headerSize = "small",
31
+ hasBorder = true,
32
+ onRowClick = null,
33
+ onFetch = null,
34
+ hasMore = false,
35
+ isLoading = false,
36
+ emptyMessage = "No data available",
37
+ } = options;
38
+
39
+ let currentData = [...data];
40
+ let container = null;
41
+ let tableElement = null;
42
+
43
+ /**
44
+ * Get header size classes
45
+ */
46
+ function getHeaderSizeClasses(size) {
47
+ const sizes = {
48
+ small: "h-32",
49
+ default: "h-44",
50
+ large: "h-48",
51
+ };
52
+ return sizes[size] || sizes.small;
53
+ }
54
+
55
+ /**
56
+ * Get cell size classes
57
+ */
58
+ function getCellSizeClasses(size) {
59
+ const sizes = {
60
+ small: "h-36",
61
+ default: "h-44",
62
+ large: "h-48",
63
+ };
64
+ return sizes[size] || sizes.default;
65
+ }
66
+
67
+ /**
68
+ * Handle row click
69
+ */
70
+ function handleRowClick(rowId, event) {
71
+ if (onRowClick) {
72
+ onRowClick(rowId);
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Create table header
78
+ */
79
+ function createTableHeader() {
80
+ const thead = document.createElement("thead");
81
+ thead.className = "[&_tr]:border-0 text-typography-quaternary-text";
82
+
83
+ const tr = document.createElement("tr");
84
+
85
+ // Data columns
86
+ columns.forEach((column, index) => {
87
+ const th = document.createElement("th");
88
+ const isLast = index === columns.length - 1;
89
+
90
+ // Base classes from design system
91
+ const borderClass = hasBorder
92
+ ? "border-1/2 border-borderColor-border-secondary"
93
+ : "border-b-1/2 border-b-borderColor-border-secondary";
94
+
95
+ th.className = `min-w-[150px] max-w-[500px] text-nowrap bg-fill-tertiary-fill-light-gray px-12 py-8 text-left align-middle !text-semi-10 uppercase leading-[12px] tracking-[0.5px] text-typography-tertiary-text hover:bg-fill-tertiary-fill-light-gray ${borderClass} ${getHeaderSizeClasses(
96
+ headerSize
97
+ )} ${isLast ? "w-full" : ""}`;
98
+
99
+ if (column.width) {
100
+ th.style.width = column.width;
101
+ th.style.minWidth = column.width;
102
+ }
103
+
104
+ const headerText = document.createElement("span");
105
+ headerText.textContent = column.header || "";
106
+
107
+ th.appendChild(headerText);
108
+ tr.appendChild(th);
109
+ });
110
+
111
+ thead.appendChild(tr);
112
+ return thead;
113
+ }
114
+
115
+ /**
116
+ * Create table body
117
+ */
118
+ function createTableBody() {
119
+ const tbody = document.createElement("tbody");
120
+ tbody.className = "[&_tr:last-child]:border-0";
121
+
122
+ if (currentData.length === 0 && !isLoading) {
123
+ // Empty state
124
+ const tr = document.createElement("tr");
125
+ const td = document.createElement("td");
126
+ td.colSpan = columns.length;
127
+ td.className = "text-center py-12 text-typography-secondary-text";
128
+ td.textContent = emptyMessage;
129
+ tr.appendChild(td);
130
+ tbody.appendChild(tr);
131
+ return tbody;
132
+ }
133
+
134
+ // Data rows
135
+ currentData.forEach((row) => {
136
+ const tr = document.createElement("tr");
137
+ tr.className = "transition-colors hover:bg-fill-tertiary-fill-light-gray";
138
+ if (onRowClick) {
139
+ tr.className += " cursor-pointer";
140
+ }
141
+
142
+ tr.addEventListener("click", (e) => handleRowClick(row.id, e));
143
+
144
+ // Data columns
145
+ columns.forEach((column, index) => {
146
+ const td = document.createElement("td");
147
+ const isLast = index === columns.length - 1;
148
+
149
+ // Base classes from design system
150
+ const borderClass = hasBorder
151
+ ? "border-1/2 border-borderColor-border-secondary"
152
+ : "border-b-1/2 border-b-borderColor-border-secondary";
153
+
154
+ td.className = `box-border w-fit min-w-[150px] max-w-[500px] text-ellipsis text-nowrap bg-fill-quarternary-fill-white px-12 py-8 align-middle !text-reg-12 text-typography-primary-text overflow-hidden truncate ${borderClass} ${getCellSizeClasses(
155
+ "default"
156
+ )} ${isLast ? "w-full" : ""}`;
157
+
158
+ if (column.width) {
159
+ td.style.width = column.width;
160
+ td.style.minWidth = column.width;
161
+ }
162
+
163
+ // Render cell content
164
+ if (column.cell && typeof column.cell === "function") {
165
+ const cellContent = column.cell(row);
166
+ if (typeof cellContent === "string") {
167
+ td.innerHTML = cellContent;
168
+ } else if (cellContent instanceof HTMLElement) {
169
+ td.appendChild(cellContent);
170
+ } else {
171
+ td.textContent = cellContent;
172
+ }
173
+ } else if (column.accessor) {
174
+ const value = row[column.accessor];
175
+ td.textContent = value !== null && value !== undefined ? value : "";
176
+ } else {
177
+ td.textContent = "";
178
+ }
179
+
180
+ tr.appendChild(td);
181
+ });
182
+
183
+ tbody.appendChild(tr);
184
+ });
185
+
186
+ // Loading skeleton rows
187
+ if (isLoading) {
188
+ for (let i = 0; i < 3; i++) {
189
+ const tr = document.createElement("tr");
190
+ tr.className = "animate-pulse";
191
+
192
+ columns.forEach((column, index) => {
193
+ const td = document.createElement("td");
194
+ const isLast = index === columns.length - 1;
195
+
196
+ const borderClass = hasBorder
197
+ ? "border-1/2 border-borderColor-border-secondary"
198
+ : "border-b-1/2 border-b-borderColor-border-secondary";
199
+
200
+ td.className = `px-12 py-8 ${borderClass} ${getCellSizeClasses("default")} ${
201
+ isLast ? "w-full" : ""
202
+ }`;
203
+
204
+ if (column.width) {
205
+ td.style.width = column.width;
206
+ td.style.minWidth = column.width;
207
+ }
208
+
209
+ const skeleton = document.createElement("div");
210
+ skeleton.className = "h-4 bg-fill-secondary-fill-lighter-gray rounded";
211
+ td.appendChild(skeleton);
212
+ tr.appendChild(td);
213
+ });
214
+
215
+ tbody.appendChild(tr);
216
+ }
217
+ }
218
+
219
+ return tbody;
220
+ }
221
+
222
+ /**
223
+ * Handle scroll for infinite loading
224
+ */
225
+ function handleScroll(e) {
226
+ if (!onFetch || isLoading || !hasMore) return;
227
+
228
+ const { scrollTop, scrollHeight, clientHeight } = e.target;
229
+
230
+ // Check if near bottom (within 1.5x viewport height)
231
+ if (scrollHeight - scrollTop <= clientHeight * 1.5) {
232
+ onFetch();
233
+ }
234
+ }
235
+
236
+ /**
237
+ * Render the table
238
+ */
239
+ function render() {
240
+ if (!container) return;
241
+
242
+ // Clear container
243
+ container.innerHTML = "";
244
+
245
+ // Create table wrapper
246
+ const wrapper = document.createElement("div");
247
+ wrapper.className = "relative h-full max-w-full overflow-y-auto";
248
+ wrapper.addEventListener("scroll", handleScroll);
249
+
250
+ // Create table
251
+ tableElement = document.createElement("table");
252
+ const tableBorderClass = hasBorder
253
+ ? "border-1/2 border-borderColor-border-secondary"
254
+ : "";
255
+ tableElement.className = `w-full caption-bottom bg-fill-quarternary-fill-white text-reg-14 ${tableBorderClass}`;
256
+
257
+ // Add header
258
+ if (showHeader) {
259
+ tableElement.appendChild(createTableHeader());
260
+ }
261
+
262
+ // Add body
263
+ tableElement.appendChild(createTableBody());
264
+
265
+ wrapper.appendChild(tableElement);
266
+ container.appendChild(wrapper);
267
+ }
268
+
269
+ /**
270
+ * Public API
271
+ */
272
+ return {
273
+ /**
274
+ * Mount the table to a DOM element
275
+ */
276
+ mount(element) {
277
+ if (typeof element === "string") {
278
+ container = document.querySelector(element);
279
+ } else {
280
+ container = element;
281
+ }
282
+
283
+ if (!container) {
284
+ console.error("Table: Invalid container element");
285
+ return this;
286
+ }
287
+
288
+ render();
289
+ return this;
290
+ },
291
+
292
+ /**
293
+ * Update table data
294
+ */
295
+ setData(newData) {
296
+ currentData = [...newData];
297
+ render();
298
+ return this;
299
+ },
300
+
301
+ /**
302
+ * Get current data
303
+ */
304
+ getData() {
305
+ return [...currentData];
306
+ },
307
+
308
+ /**
309
+ * Destroy the table
310
+ */
311
+ destroy() {
312
+ if (container) {
313
+ container.innerHTML = "";
314
+ }
315
+ container = null;
316
+ tableElement = null;
317
+ },
318
+
319
+ /**
320
+ * Refresh/re-render the table
321
+ */
322
+ refresh() {
323
+ render();
324
+ return this;
325
+ },
326
+ };
327
+ }
328
+
329
+ // Export to global scope
330
+ if (typeof module !== "undefined" && module.exports) {
331
+ module.exports = { createTable };
332
+ } else {
333
+ global.SuperleapTable = { createTable };
334
+ }
335
+ })(typeof window !== "undefined" ? window : this);
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Textarea Component (vanilla JS)
3
+ * Design-system textarea with variants; matches React Textarea cva variants.
4
+ * Ref: React Textarea with cva variants; no React/cva dependency.
5
+ */
6
+
7
+ (function (global) {
8
+ "use strict";
9
+
10
+ var TEXTAREA_CLASS = {
11
+ base:
12
+ "flex w-full rounded-4 border-1/2 border-border-primary bg-fill-quarternary-fill-white px-12 py-6 !text-reg-13 outline-none placeholder:text-typography-quaternary-text text-typography-primary-text resize-y transition-all ease-in-out",
13
+ default:
14
+ "min-h-[80px] hover:border-primary-base focus:border-primary-base",
15
+ borderless:
16
+ "min-h-[80px] border-none shadow-none rounded-0 bg-fill-quarternary-fill-white",
17
+ inline:
18
+ "border-transparent shadow-none rounded-0 bg-fill-quarternary-fill-white hover:bg-fill-tertiary-fill-light-gray focus:border-transparent focus:bg-fill-tertiary-fill-light-gray py-4",
19
+ error:
20
+ "min-h-[80px] border-error-base hover:border-error-base focus:border-error-base",
21
+ warning:
22
+ "min-h-[80px] border-warning-base hover:border-warning-base focus:border-warning-base",
23
+ disabled:
24
+ "cursor-not-allowed border-border-primary bg-fill-tertiary-fill-light-gray text-typography-quaternary-text hover:border-border-primary",
25
+ };
26
+
27
+ function join() {
28
+ return Array.prototype.filter.call(arguments, Boolean).join(" ");
29
+ }
30
+
31
+ /**
32
+ * Create a textarea component
33
+ * @param {Object} config
34
+ * @param {string} [config.variant] - 'default' | 'borderless' | 'inline' | 'error' | 'warning'
35
+ * @param {string} [config.placeholder]
36
+ * @param {string} [config.value]
37
+ * @param {number} [config.rows]
38
+ * @param {boolean} [config.disabled]
39
+ * @param {boolean} [config.readOnly]
40
+ * @param {string} [config.name]
41
+ * @param {string} [config.id]
42
+ * @param {string} [config.className] - extra class on textarea
43
+ * @param {number} [config.maxLength]
44
+ * @param {Function} [config.onChange]
45
+ * @param {Function} [config.onInput]
46
+ * @param {Function} [config.onFocus]
47
+ * @param {Function} [config.onBlur]
48
+ * @returns {HTMLTextAreaElement} textarea element (with getValue/setValue/setVariant/setDisabled attached)
49
+ */
50
+ function create(config) {
51
+ var variant = config.variant || "default";
52
+ var disabled = !!config.disabled;
53
+
54
+ var textarea = document.createElement("textarea");
55
+ textarea.autocomplete = "off";
56
+ if (config.placeholder != null) textarea.placeholder = config.placeholder;
57
+ if (config.value != null) textarea.value = config.value;
58
+ if (config.name) textarea.name = config.name;
59
+ if (config.id) textarea.id = config.id;
60
+ if (config.rows != null) textarea.rows = config.rows;
61
+ if (config.maxLength != null) textarea.maxLength = config.maxLength;
62
+ textarea.disabled = disabled;
63
+ textarea.readOnly = !!config.readOnly;
64
+
65
+ textarea.className = join(
66
+ TEXTAREA_CLASS.base,
67
+ TEXTAREA_CLASS[variant] || TEXTAREA_CLASS.default,
68
+ disabled ? TEXTAREA_CLASS.disabled : "",
69
+ config.className || ""
70
+ );
71
+ textarea.setAttribute("data-textarea-variant", variant);
72
+
73
+ if (config.onChange) textarea.addEventListener("change", config.onChange);
74
+ if (config.onInput) textarea.addEventListener("input", config.onInput);
75
+ if (config.onFocus) textarea.addEventListener("focus", config.onFocus);
76
+ if (config.onBlur) textarea.addEventListener("blur", config.onBlur);
77
+
78
+ textarea.getInput = function () {
79
+ return textarea;
80
+ };
81
+ textarea.setValue = function (v) {
82
+ textarea.value = v;
83
+ };
84
+ textarea.getValue = function () {
85
+ return textarea.value;
86
+ };
87
+ textarea.setVariant = function (v) {
88
+ variant = v;
89
+ textarea.setAttribute("data-textarea-variant", v);
90
+ textarea.className = join(
91
+ TEXTAREA_CLASS.base,
92
+ TEXTAREA_CLASS[variant] || TEXTAREA_CLASS.default,
93
+ disabled ? TEXTAREA_CLASS.disabled : "",
94
+ config.className || ""
95
+ );
96
+ };
97
+ textarea.setDisabled = function (d) {
98
+ disabled = !!d;
99
+ textarea.disabled = disabled;
100
+ textarea.className = join(
101
+ TEXTAREA_CLASS.base,
102
+ TEXTAREA_CLASS[variant] || TEXTAREA_CLASS.default,
103
+ disabled ? TEXTAREA_CLASS.disabled : "",
104
+ config.className || ""
105
+ );
106
+ };
107
+
108
+ return textarea;
109
+ }
110
+
111
+ global.TextareaComponent = {
112
+ create: create,
113
+ };
114
+ })(typeof window !== "undefined" ? window : this);