@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.
- package/CHANGELOG.md +65 -0
- package/LICENSE +21 -0
- package/README.md +451 -0
- package/components/alert.js +282 -0
- package/components/avatar.js +195 -0
- package/components/badge.js +135 -0
- package/components/button.js +201 -0
- package/components/checkbox.js +254 -0
- package/components/currency.js +227 -0
- package/components/date-time-picker/date-time-picker-utils.js +253 -0
- package/components/date-time-picker/date-time-picker.js +532 -0
- package/components/duration/duration-constants.js +46 -0
- package/components/duration/duration-utils.js +164 -0
- package/components/duration/duration.js +448 -0
- package/components/enum-multiselect.js +869 -0
- package/components/enum-select.js +831 -0
- package/components/enumeration.js +213 -0
- package/components/file-input.js +533 -0
- package/components/icon.js +200 -0
- package/components/input.js +259 -0
- package/components/label.js +111 -0
- package/components/multiselect.js +351 -0
- package/components/phone-input/phone-input.js +392 -0
- package/components/phone-input/phone-utils.js +157 -0
- package/components/popover.js +240 -0
- package/components/radio-group.js +435 -0
- package/components/record-multiselect.js +956 -0
- package/components/record-select.js +930 -0
- package/components/select.js +544 -0
- package/components/spinner.js +136 -0
- package/components/table.js +335 -0
- package/components/textarea.js +114 -0
- package/components/time-picker.js +357 -0
- package/components/toast.js +343 -0
- package/core/flow.js +1729 -0
- package/core/superleapClient.js +146 -0
- package/dist/output.css +2 -0
- package/index.d.ts +458 -0
- package/index.js +253 -0
- package/package.json +70 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Button Component (vanilla JS)
|
|
3
|
+
* Design-system button with variants and sizes via Tailwind.
|
|
4
|
+
* Ref: React Button with cva variants (primary, outline, ghost, link, destructive, dashed, toggle, etc.)
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
(function (global) {
|
|
8
|
+
"use strict";
|
|
9
|
+
|
|
10
|
+
var BASE_CLASS =
|
|
11
|
+
"inline-flex items-center justify-center whitespace-nowrap !text-med-12 transition-colors disabled:pointer-events-none hover:cursor-pointer h-fit";
|
|
12
|
+
|
|
13
|
+
var VARIANTS = {
|
|
14
|
+
primary:
|
|
15
|
+
"shadow-soft-extra-small group bg-primary-base border-1/2 border-primary-base text-typography-invert-text hover:bg-primary-hover active:bg-primary-active disabled:opacity-50",
|
|
16
|
+
outline:
|
|
17
|
+
"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",
|
|
18
|
+
ghost:
|
|
19
|
+
"group text-typography-primary-text hover:bg-fill-tertiary-fill-light-gray active:bg-fill-secondary-fill-gray disabled:text-typography-quaternary-text",
|
|
20
|
+
link: "group text-primary-text-base hover:bg-primary-surface active:bg-primary-surface-hover disabled:text-typography-quaternary-text",
|
|
21
|
+
primaryDestructive:
|
|
22
|
+
"shadow-soft-extra-small group bg-error-base text-typography-invert-text border-1/2 border-error-base hover:bg-error-hover active:bg-error-active disabled:opacity-50",
|
|
23
|
+
outlineDestructive:
|
|
24
|
+
"shadow-soft-extra-small group bg-fill-quarternary-fill-white border-1/2 border-error-border text-error-text-base hover:bg-error-surface active:bg-error-surface-hover active:text-error-text-active disabled:opacity-50 disabled:border-fill-secondary-fill-gray disabled:text-typography-quaternary-text",
|
|
25
|
+
ghostDestructive:
|
|
26
|
+
"group text-error-text-base hover:bg-error-surface active:bg-error-surface-hover active:text-error-text-active disabled:opacity-50",
|
|
27
|
+
dashed:
|
|
28
|
+
"group bg-fill-quarternary-fill-white border border-border-primary border-dashed text-typography-secondary-text hover:bg-fill-tertiary-fill-light-gray active:bg-fill-secondary-fill-gray disabled:opacity-50 disabled:border-fill-secondary-fill-gray",
|
|
29
|
+
toggleOff:
|
|
30
|
+
"shadow-soft-extra-small group bg-fill-quarternary-fill-white border-1/2 border-border-primary text-typography-primary-text hover:bg-primary-surface active:bg-primary-surface disabled:opacity-50 disabled:border-fill-secondary-fill-gray",
|
|
31
|
+
toggleOn:
|
|
32
|
+
"shadow-soft-extra-small group bg-primary-surface border-1/2 border-border-primary text-primary-text-base hover:bg-primary-surface active:bg-primary-surface disabled:opacity-50 disabled:border-fill-secondary-fill-gray",
|
|
33
|
+
ghostInline:
|
|
34
|
+
"group text-typography-primary-text disabled:text-typography-quaternary-text hover:bg-transparent active:bg-transparent !px-0 hover:text-typography-secondary-text",
|
|
35
|
+
linkInline:
|
|
36
|
+
"group text-primary-text-base hover:bg-transparent active:bg-transparent disabled:text-typography-quaternary-text !px-0 hover:text-primary-text-active",
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
var SIZES = {
|
|
40
|
+
small: "px-8 py-4 gap-4 rounded-4",
|
|
41
|
+
medium: "px-12 py-6 gap-4 rounded-4",
|
|
42
|
+
default: "px-8 py-4 gap-4 rounded-4",
|
|
43
|
+
large: "px-16 py-8 gap-4 rounded-4",
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
var SHADOW_CLASS = "shadow-soft-extra-small";
|
|
47
|
+
|
|
48
|
+
// Icon-only button variants (same visual, different size treatment)
|
|
49
|
+
var ICON_SIZES = {
|
|
50
|
+
small: "box-content size-16 p-4 rounded-4",
|
|
51
|
+
medium: "box-content size-16 p-6 rounded-4",
|
|
52
|
+
default: "box-content size-16 p-4 rounded-4",
|
|
53
|
+
large: "box-content size-16 p-8 rounded-4",
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
var ICON_BASE = "flex items-center justify-center disabled:pointer-events-none hover:cursor-pointer";
|
|
57
|
+
|
|
58
|
+
function join() {
|
|
59
|
+
return Array.prototype.filter.call(arguments, Boolean).join(" ");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Create a button element
|
|
64
|
+
* @param {Object} config
|
|
65
|
+
* @param {string} [config.variant] - 'primary' | 'outline' | 'ghost' | 'link' | 'primaryDestructive' | 'outlineDestructive' | 'ghostDestructive' | 'dashed' | 'toggleOff' | 'toggleOn' | 'ghostInline' | 'linkInline'
|
|
66
|
+
* @param {string} [config.size] - 'small' | 'medium' | 'default' | 'large'
|
|
67
|
+
* @param {boolean} [config.isShadow] - add extra shadow
|
|
68
|
+
* @param {string} [config.className] - extra Tailwind/classes
|
|
69
|
+
* @param {boolean} [config.disabled]
|
|
70
|
+
* @param {string} [config.type] - 'button' | 'submit' | 'reset'
|
|
71
|
+
* @param {string} [config.label] - accessible label (aria-label)
|
|
72
|
+
* @param {string} [config.text] - button text (same as children for simple case)
|
|
73
|
+
* @param {string} [config.innerHTML] - raw HTML for content (use for icons + text)
|
|
74
|
+
* @param {HTMLElement|string} [config.startIcon] - element or HTML string for left icon
|
|
75
|
+
* @param {HTMLElement|string} [config.endIcon] - element or HTML string for right icon
|
|
76
|
+
* @param {HTMLElement|string} [config.icon] - single icon for icon-only button (no text)
|
|
77
|
+
* @param {function} [config.onClick] - click handler
|
|
78
|
+
* @returns {HTMLButtonElement}
|
|
79
|
+
*/
|
|
80
|
+
function create(config) {
|
|
81
|
+
var opts = config || {};
|
|
82
|
+
var variant = opts.variant || "primary";
|
|
83
|
+
var size = opts.size || "default";
|
|
84
|
+
var isShadow = opts.isShadow === true;
|
|
85
|
+
var className = opts.className || "";
|
|
86
|
+
var disabled = opts.disabled === true;
|
|
87
|
+
var type = opts.type || "button";
|
|
88
|
+
var label = opts.label;
|
|
89
|
+
var text = opts.text;
|
|
90
|
+
var innerHTML = opts.innerHTML;
|
|
91
|
+
var startIcon = opts.startIcon;
|
|
92
|
+
var endIcon = opts.endIcon;
|
|
93
|
+
var iconOnly = opts.icon != null;
|
|
94
|
+
var icon = opts.icon;
|
|
95
|
+
var onClick = opts.onClick;
|
|
96
|
+
|
|
97
|
+
var button = document.createElement("button");
|
|
98
|
+
button.type = type;
|
|
99
|
+
if (disabled) {
|
|
100
|
+
button.disabled = true;
|
|
101
|
+
}
|
|
102
|
+
if (label) {
|
|
103
|
+
button.setAttribute("aria-label", label);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
var variantClass = VARIANTS[variant] != null ? VARIANTS[variant] : VARIANTS.primary;
|
|
107
|
+
var sizeClass = SIZES[size] != null ? SIZES[size] : SIZES.default;
|
|
108
|
+
|
|
109
|
+
if (iconOnly) {
|
|
110
|
+
var iconVariantClass = VARIANTS[variant] != null ? VARIANTS[variant] : VARIANTS.primary;
|
|
111
|
+
var iconSizeClass = ICON_SIZES[size] != null ? ICON_SIZES[size] : ICON_SIZES.default;
|
|
112
|
+
button.className = join(
|
|
113
|
+
BASE_CLASS,
|
|
114
|
+
ICON_BASE,
|
|
115
|
+
iconVariantClass,
|
|
116
|
+
iconSizeClass,
|
|
117
|
+
isShadow ? SHADOW_CLASS : "",
|
|
118
|
+
className
|
|
119
|
+
);
|
|
120
|
+
appendContent(button, icon);
|
|
121
|
+
} else {
|
|
122
|
+
button.className = join(
|
|
123
|
+
BASE_CLASS,
|
|
124
|
+
variantClass,
|
|
125
|
+
sizeClass,
|
|
126
|
+
isShadow ? SHADOW_CLASS : "",
|
|
127
|
+
className
|
|
128
|
+
);
|
|
129
|
+
if (startIcon) {
|
|
130
|
+
var startSpan = document.createElement("span");
|
|
131
|
+
startSpan.className = "box-content flex size-16 items-center justify-center rounded-4 !p-0";
|
|
132
|
+
appendContent(startSpan, startIcon);
|
|
133
|
+
button.appendChild(startSpan);
|
|
134
|
+
}
|
|
135
|
+
if (innerHTML != null) {
|
|
136
|
+
var frag = document.createElement("span");
|
|
137
|
+
frag.innerHTML = innerHTML;
|
|
138
|
+
while (frag.firstChild) {
|
|
139
|
+
button.appendChild(frag.firstChild);
|
|
140
|
+
}
|
|
141
|
+
} else if (text != null) {
|
|
142
|
+
button.appendChild(document.createTextNode(text));
|
|
143
|
+
}
|
|
144
|
+
if (endIcon) {
|
|
145
|
+
var endSpan = document.createElement("span");
|
|
146
|
+
endSpan.className = "box-content flex size-16 items-center justify-center rounded-4 !p-0";
|
|
147
|
+
appendContent(endSpan, endIcon);
|
|
148
|
+
button.appendChild(endSpan);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (typeof onClick === "function") {
|
|
153
|
+
button.addEventListener("click", onClick);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return button;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function appendContent(el, content) {
|
|
160
|
+
if (content == null) return;
|
|
161
|
+
if (typeof content === "string") {
|
|
162
|
+
el.insertAdjacentHTML("beforeend", content);
|
|
163
|
+
} else if (content instanceof HTMLElement) {
|
|
164
|
+
el.appendChild(content);
|
|
165
|
+
} else if (content instanceof Node) {
|
|
166
|
+
el.appendChild(content);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Get Tailwind class string for a button (for use with asChild or custom elements)
|
|
172
|
+
* @param {Object} options - { variant, size, isShadow, iconOnly }
|
|
173
|
+
* @returns {string}
|
|
174
|
+
*/
|
|
175
|
+
function getButtonClasses(options) {
|
|
176
|
+
var o = options || {};
|
|
177
|
+
var variant = o.variant || "primary";
|
|
178
|
+
var size = o.size || "default";
|
|
179
|
+
var isShadow = o.isShadow === true;
|
|
180
|
+
var iconOnly = o.iconOnly === true;
|
|
181
|
+
var variantClass = VARIANTS[variant] != null ? VARIANTS[variant] : VARIANTS.primary;
|
|
182
|
+
var sizeClass = iconOnly
|
|
183
|
+
? (ICON_SIZES[size] != null ? ICON_SIZES[size] : ICON_SIZES.default)
|
|
184
|
+
: (SIZES[size] != null ? SIZES[size] : SIZES.default);
|
|
185
|
+
var base = iconOnly ? join(BASE_CLASS, ICON_BASE) : BASE_CLASS;
|
|
186
|
+
return join(base, variantClass, sizeClass, isShadow ? SHADOW_CLASS : "");
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
var Button = {
|
|
190
|
+
create: create,
|
|
191
|
+
getButtonClasses: getButtonClasses,
|
|
192
|
+
VARIANTS: VARIANTS,
|
|
193
|
+
SIZES: SIZES,
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
if (typeof module !== "undefined" && module.exports) {
|
|
197
|
+
module.exports = Button;
|
|
198
|
+
} else {
|
|
199
|
+
global.Button = Button;
|
|
200
|
+
}
|
|
201
|
+
})(typeof window !== "undefined" ? window : this);
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checkbox Component (vanilla JS)
|
|
3
|
+
* Design-system checkbox with variants, sizes, indeterminate state, and label support.
|
|
4
|
+
* Ref: React Checkbox with Radix UI primitives and cva variants; no React/Radix/cva dependency.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
(function (global) {
|
|
8
|
+
"use strict";
|
|
9
|
+
|
|
10
|
+
// Inline SVGs (Tabler-style icons for check and minus)
|
|
11
|
+
var ICONS = {
|
|
12
|
+
check:
|
|
13
|
+
'<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>',
|
|
14
|
+
minus:
|
|
15
|
+
'<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="5" y1="12" x2="19" y2="12"/></svg>',
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// Size-based icon variants
|
|
19
|
+
var ICON_SIZES = {
|
|
20
|
+
small: { width: "12", height: "12" },
|
|
21
|
+
default: { width: "14", height: "14" },
|
|
22
|
+
large: { width: "16", height: "16" },
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
var ALIGN_VARIANTS = {
|
|
26
|
+
left: "justify-start",
|
|
27
|
+
center: "justify-center",
|
|
28
|
+
right: "justify-end",
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
var CHECKBOX_SIZES = {
|
|
32
|
+
small: "size-12",
|
|
33
|
+
default: "size-16",
|
|
34
|
+
large: "size-20",
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
var CHECKBOX_BASE_CLASS =
|
|
38
|
+
"flex items-center justify-center rounded-2 border-1/2 border-borderColor-border-primary bg-fill-quarternary-fill-white p-4 transition-all hover:border-primary-base hover:shadow-primary-focused disabled:cursor-not-allowed disabled:border-borderColor-border-primary disabled:opacity-50 disabled:hover:shadow-none";
|
|
39
|
+
|
|
40
|
+
var CHECKBOX_CHECKED_CLASS =
|
|
41
|
+
"data-checked:border-transparent data-checked:bg-primary-base data-checked:hover:border-primary-base data-checked:hover:shadow-primary-focused data-checked:disabled:border-borderColor-border-primary";
|
|
42
|
+
|
|
43
|
+
var LABEL_BASE_CLASS =
|
|
44
|
+
"cursor-pointer pb-0 text-reg-12 leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70";
|
|
45
|
+
|
|
46
|
+
function join() {
|
|
47
|
+
return Array.prototype.filter.call(arguments, Boolean).join(" ");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Create a checkbox component
|
|
52
|
+
* @param {Object} config
|
|
53
|
+
* @param {string} [config.id] - checkbox input id (also used for label htmlFor)
|
|
54
|
+
* @param {string} [config.name] - name attribute for the checkbox
|
|
55
|
+
* @param {boolean} [config.checked] - initial checked state
|
|
56
|
+
* @param {boolean} [config.indeterminate] - indeterminate state (shows minus icon)
|
|
57
|
+
* @param {boolean} [config.disabled] - disabled state
|
|
58
|
+
* @param {string} [config.label] - label text
|
|
59
|
+
* @param {boolean} [config.isLabelCaps] - capitalize label
|
|
60
|
+
* @param {string} [config.align] - 'left' | 'center' | 'right' (default: 'center')
|
|
61
|
+
* @param {string} [config.size] - 'small' | 'default' | 'large'
|
|
62
|
+
* @param {string} [config.className] - extra class on wrapper
|
|
63
|
+
* @param {Function} [config.onChange] - change handler (receives checked state)
|
|
64
|
+
* @returns {HTMLElement} wrapper element containing checkbox and optional label
|
|
65
|
+
*/
|
|
66
|
+
function create(config) {
|
|
67
|
+
var opts = config || {};
|
|
68
|
+
var id = opts.id || "checkbox-" + Math.random().toString(36).substr(2, 9);
|
|
69
|
+
var name = opts.name;
|
|
70
|
+
var checked = !!opts.checked;
|
|
71
|
+
var indeterminate = !!opts.indeterminate;
|
|
72
|
+
var disabled = !!opts.disabled;
|
|
73
|
+
var label = opts.label;
|
|
74
|
+
var isLabelCaps = !!opts.isLabelCaps;
|
|
75
|
+
var align = opts.align || "center";
|
|
76
|
+
var size = opts.size || "default";
|
|
77
|
+
var className = opts.className || "";
|
|
78
|
+
var onChange = opts.onChange;
|
|
79
|
+
|
|
80
|
+
// Wrapper container
|
|
81
|
+
var wrapper = document.createElement("div");
|
|
82
|
+
wrapper.className = join(
|
|
83
|
+
"flex gap-8 items-center",
|
|
84
|
+
ALIGN_VARIANTS[align] || ALIGN_VARIANTS.center,
|
|
85
|
+
className
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
// Hidden native checkbox (for accessibility and form submission)
|
|
89
|
+
var input = document.createElement("input");
|
|
90
|
+
input.type = "checkbox";
|
|
91
|
+
input.id = id;
|
|
92
|
+
if (name) input.name = name;
|
|
93
|
+
input.checked = checked;
|
|
94
|
+
input.disabled = disabled;
|
|
95
|
+
input.className = "absolute opacity-0 w-0 h-0 peer"; // completely hidden
|
|
96
|
+
input.setAttribute("aria-checked", indeterminate ? "mixed" : checked ? "true" : "false");
|
|
97
|
+
input.style.position = "absolute";
|
|
98
|
+
input.style.opacity = "0";
|
|
99
|
+
input.style.width = "0";
|
|
100
|
+
input.style.height = "0";
|
|
101
|
+
input.style.pointerEvents = "none";
|
|
102
|
+
|
|
103
|
+
// Custom checkbox visual
|
|
104
|
+
var checkboxBox = document.createElement("div");
|
|
105
|
+
checkboxBox.className = join(
|
|
106
|
+
CHECKBOX_BASE_CLASS,
|
|
107
|
+
CHECKBOX_CHECKED_CLASS,
|
|
108
|
+
CHECKBOX_SIZES[size] || CHECKBOX_SIZES.default,
|
|
109
|
+
"cursor-pointer"
|
|
110
|
+
);
|
|
111
|
+
checkboxBox.setAttribute("role", "checkbox");
|
|
112
|
+
checkboxBox.setAttribute("tabindex", disabled ? "-1" : "0");
|
|
113
|
+
checkboxBox.setAttribute("aria-checked", indeterminate ? "mixed" : checked ? "true" : "false");
|
|
114
|
+
if (disabled) {
|
|
115
|
+
checkboxBox.setAttribute("aria-disabled", "true");
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Indicator (checkmark or minus icon)
|
|
119
|
+
var indicator = document.createElement("span");
|
|
120
|
+
indicator.className = "flex items-center justify-center text-current";
|
|
121
|
+
updateIndicator(indicator, checked, indeterminate, size);
|
|
122
|
+
|
|
123
|
+
checkboxBox.appendChild(indicator);
|
|
124
|
+
|
|
125
|
+
// Update visual state
|
|
126
|
+
function updateCheckedState() {
|
|
127
|
+
var isChecked = input.checked;
|
|
128
|
+
var isIndeterminate = input.indeterminate;
|
|
129
|
+
checkboxBox.setAttribute("aria-checked", isIndeterminate ? "mixed" : isChecked ? "true" : "false");
|
|
130
|
+
input.setAttribute("aria-checked", isIndeterminate ? "mixed" : isChecked ? "true" : "false");
|
|
131
|
+
|
|
132
|
+
if (isChecked || isIndeterminate) {
|
|
133
|
+
checkboxBox.setAttribute("data-checked", "true");
|
|
134
|
+
checkboxBox.classList.add("border-transparent", "bg-primary-base");
|
|
135
|
+
checkboxBox.classList.remove("border-borderColor-border-primary", "bg-fill-quarternary-fill-white");
|
|
136
|
+
} else {
|
|
137
|
+
checkboxBox.removeAttribute("data-checked");
|
|
138
|
+
checkboxBox.classList.remove("border-transparent", "bg-primary-base");
|
|
139
|
+
checkboxBox.classList.add("border-borderColor-border-primary", "bg-fill-quarternary-fill-white");
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
updateIndicator(indicator, isChecked, isIndeterminate, size);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Toggle on click
|
|
146
|
+
checkboxBox.addEventListener("click", function () {
|
|
147
|
+
if (disabled) return;
|
|
148
|
+
input.indeterminate = false;
|
|
149
|
+
input.checked = !input.checked;
|
|
150
|
+
updateCheckedState();
|
|
151
|
+
if (typeof onChange === "function") {
|
|
152
|
+
onChange(input.checked);
|
|
153
|
+
}
|
|
154
|
+
input.dispatchEvent(new Event("change", { bubbles: true }));
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// Keyboard support
|
|
158
|
+
checkboxBox.addEventListener("keydown", function (e) {
|
|
159
|
+
if (disabled) return;
|
|
160
|
+
if (e.key === " " || e.key === "Enter") {
|
|
161
|
+
e.preventDefault();
|
|
162
|
+
checkboxBox.click();
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// Sync with native input changes (for programmatic updates)
|
|
167
|
+
input.addEventListener("change", function () {
|
|
168
|
+
updateCheckedState();
|
|
169
|
+
if (typeof onChange === "function") {
|
|
170
|
+
onChange(input.checked);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
wrapper.appendChild(input);
|
|
175
|
+
wrapper.appendChild(checkboxBox);
|
|
176
|
+
|
|
177
|
+
// Label
|
|
178
|
+
if (label) {
|
|
179
|
+
var labelEl = document.createElement("label");
|
|
180
|
+
labelEl.htmlFor = id;
|
|
181
|
+
labelEl.className = join(
|
|
182
|
+
LABEL_BASE_CLASS,
|
|
183
|
+
isLabelCaps ? "capitalize" : "",
|
|
184
|
+
disabled ? "text-typography-quaternary-text" : ""
|
|
185
|
+
);
|
|
186
|
+
labelEl.textContent = label;
|
|
187
|
+
wrapper.appendChild(labelEl);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Set initial visual state
|
|
191
|
+
if (indeterminate) {
|
|
192
|
+
input.indeterminate = true;
|
|
193
|
+
}
|
|
194
|
+
updateCheckedState();
|
|
195
|
+
|
|
196
|
+
// Public API
|
|
197
|
+
wrapper.getInput = function () {
|
|
198
|
+
return input;
|
|
199
|
+
};
|
|
200
|
+
wrapper.setChecked = function (value) {
|
|
201
|
+
input.indeterminate = false;
|
|
202
|
+
input.checked = !!value;
|
|
203
|
+
updateCheckedState();
|
|
204
|
+
};
|
|
205
|
+
wrapper.getChecked = function () {
|
|
206
|
+
return input.checked;
|
|
207
|
+
};
|
|
208
|
+
wrapper.setIndeterminate = function (value) {
|
|
209
|
+
input.indeterminate = !!value;
|
|
210
|
+
if (value) {
|
|
211
|
+
input.checked = false; // typically indeterminate means partially checked, not fully checked
|
|
212
|
+
}
|
|
213
|
+
updateCheckedState();
|
|
214
|
+
};
|
|
215
|
+
wrapper.getIndeterminate = function () {
|
|
216
|
+
return input.indeterminate;
|
|
217
|
+
};
|
|
218
|
+
wrapper.setDisabled = function (value) {
|
|
219
|
+
disabled = !!value;
|
|
220
|
+
input.disabled = disabled;
|
|
221
|
+
checkboxBox.setAttribute("tabindex", disabled ? "-1" : "0");
|
|
222
|
+
if (disabled) {
|
|
223
|
+
checkboxBox.setAttribute("aria-disabled", "true");
|
|
224
|
+
} else {
|
|
225
|
+
checkboxBox.removeAttribute("aria-disabled");
|
|
226
|
+
}
|
|
227
|
+
updateCheckedState();
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
return wrapper;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function updateIndicator(indicator, checked, indeterminate, size) {
|
|
234
|
+
indicator.innerHTML = "";
|
|
235
|
+
if (checked || indeterminate) {
|
|
236
|
+
var iconSize = ICON_SIZES[size] || ICON_SIZES.default;
|
|
237
|
+
var iconSVG = indeterminate ? ICONS.minus : ICONS.check;
|
|
238
|
+
// Update SVG with size
|
|
239
|
+
iconSVG = iconSVG.replace(/width="\d+"/, 'width="' + iconSize.width + '"');
|
|
240
|
+
iconSVG = iconSVG.replace(/height="\d+"/, 'height="' + iconSize.height + '"');
|
|
241
|
+
indicator.innerHTML = iconSVG;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
var Checkbox = {
|
|
246
|
+
create: create,
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
if (typeof module !== "undefined" && module.exports) {
|
|
250
|
+
module.exports = Checkbox;
|
|
251
|
+
} else {
|
|
252
|
+
global.Checkbox = Checkbox;
|
|
253
|
+
}
|
|
254
|
+
})(typeof window !== "undefined" ? window : this);
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Currency Component (vanilla JS)
|
|
3
|
+
* Label (currency type, fit-content) + decimal input. Styles/variants match input.js.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
(function (global) {
|
|
7
|
+
"use strict";
|
|
8
|
+
|
|
9
|
+
// Match input.js WRAPPER_CLASS for style and variants
|
|
10
|
+
var WRAPPER_CLASS = {
|
|
11
|
+
base:
|
|
12
|
+
"group flex items-stretch border-1/2 border-border-primary rounded-4 text-typography-primary-text gap-x-0 w-full transition-all ease-in-out overflow-hidden",
|
|
13
|
+
default:
|
|
14
|
+
"bg-fill-quarternary-fill-white hover:border-primary-base focus-within:border-primary-base",
|
|
15
|
+
error:
|
|
16
|
+
"border-error-base bg-fill-quarternary-fill-white hover:border-error-base focus-within:border-error-base",
|
|
17
|
+
warning:
|
|
18
|
+
"border-warning-base bg-fill-quarternary-fill-white hover:border-warning-base focus-within:border-warning-base",
|
|
19
|
+
success:
|
|
20
|
+
"border-success-base bg-fill-quarternary-fill-white hover:border-success-base focus-within:border-success-base",
|
|
21
|
+
borderless:
|
|
22
|
+
"border-none shadow-none rounded-0 bg-fill-quarternary-fill-white",
|
|
23
|
+
inline:
|
|
24
|
+
"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",
|
|
25
|
+
sizeDefault: "",
|
|
26
|
+
sizeLarge: "",
|
|
27
|
+
sizeSmall: "",
|
|
28
|
+
disabled:
|
|
29
|
+
"cursor-not-allowed border-border-primary bg-fill-tertiary-fill-light-gray text-typography-quaternary-text hover:border-border-primary",
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// Currency type label: fit-content, separator (border-r) on type only, full height
|
|
33
|
+
var LABEL_CLASS =
|
|
34
|
+
"flex shrink-0 w-fit items-center justify-center border-r-1/2 border-border-primary bg-fill-tertiary-fill-light-gray text-typography-primary-text px-12 text-reg-12";
|
|
35
|
+
var LABEL_SIZE = {
|
|
36
|
+
default: "py-6",
|
|
37
|
+
large: "py-8",
|
|
38
|
+
small: "py-4",
|
|
39
|
+
};
|
|
40
|
+
var INPUT_WRAPPER_CLASS = "flex items-center flex-1 min-w-0 border-0";
|
|
41
|
+
// Input: no separator/border (separator is on type only)
|
|
42
|
+
var INPUT_CLASS = {
|
|
43
|
+
base:
|
|
44
|
+
"w-full min-w-0 border-0 bg-inherit text-start outline-none placeholder:text-typography-quaternary-text focus:bg-inherit hover:bg-inherit focus-visible:outline-none disabled:cursor-not-allowed disabled:text-typography-quaternary-text file:rounded-4 file:border-none file:text-typography-primary-text file:shadow-none file:outline-none !text-reg-13 px-12",
|
|
45
|
+
inline:
|
|
46
|
+
"transition-all focus:bg-fill-tertiary-fill-light-gray group-hover:bg-fill-tertiary-fill-light-gray",
|
|
47
|
+
sizeDefault: "py-6",
|
|
48
|
+
sizeLarge: "py-8",
|
|
49
|
+
sizeSmall: "py-4",
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
var DECIMAL_PLACES = 2;
|
|
53
|
+
var DECIMAL_REGEX = /^-?\d*\.?\d{0,2}$/;
|
|
54
|
+
|
|
55
|
+
function join() {
|
|
56
|
+
return Array.prototype.filter.call(arguments, Boolean).join(" ");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Parse string to number or null (2 decimal places).
|
|
61
|
+
* @param {string} str
|
|
62
|
+
* @returns {number|null}
|
|
63
|
+
*/
|
|
64
|
+
function parseDecimal(str) {
|
|
65
|
+
if (str === "" || str == null) return null;
|
|
66
|
+
var trimmed = String(str).trim();
|
|
67
|
+
if (trimmed === "") return null;
|
|
68
|
+
var n = parseFloat(trimmed);
|
|
69
|
+
return isNaN(n) ? null : Math.round(n * 100) / 100;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Format number for display (2 decimal places).
|
|
74
|
+
* @param {number|null} num
|
|
75
|
+
* @returns {string}
|
|
76
|
+
*/
|
|
77
|
+
function formatDecimal(num) {
|
|
78
|
+
if (num == null || isNaN(num)) return "";
|
|
79
|
+
return Number(num).toFixed(DECIMAL_PLACES);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Restrict input to decimal with max 2 decimal places.
|
|
84
|
+
* @param {HTMLInputElement} input
|
|
85
|
+
*/
|
|
86
|
+
function restrictDecimalInput(input) {
|
|
87
|
+
input.addEventListener("input", function () {
|
|
88
|
+
var val = input.value;
|
|
89
|
+
if (val === "" || val === "-") return;
|
|
90
|
+
if (!DECIMAL_REGEX.test(val)) {
|
|
91
|
+
var neg = val.charAt(0) === "-" ? "-" : "";
|
|
92
|
+
var rest = neg ? val.slice(1) : val;
|
|
93
|
+
var dotIdx = rest.indexOf(".");
|
|
94
|
+
if (dotIdx === -1) {
|
|
95
|
+
input.value = neg + rest.replace(/\D/g, "");
|
|
96
|
+
} else {
|
|
97
|
+
var before = rest.slice(0, dotIdx).replace(/\D/g, "");
|
|
98
|
+
var after = rest.slice(dotIdx + 1).replace(/\D/g, "").slice(0, DECIMAL_PLACES);
|
|
99
|
+
input.value = neg + (after.length ? before + "." + after : before);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Create a currency field (currency type label fit-content + decimal input).
|
|
107
|
+
* Variants/sizes match input.js.
|
|
108
|
+
* @param {Object} config
|
|
109
|
+
* @param {string} [config.variant] - 'default' | 'error' | 'warning' | 'success' | 'borderless' | 'inline'
|
|
110
|
+
* @param {string} [config.size] - 'small' | 'default' | 'large' (maps to inputSize)
|
|
111
|
+
* @param {string} [config.placeholder]
|
|
112
|
+
* @param {boolean} [config.disabled]
|
|
113
|
+
* @param {string} [config.className]
|
|
114
|
+
* @param {Object} [config.column] - { properties?: { currency?: { currency?: string } }, placeholder?: string }
|
|
115
|
+
* @param {Function} [config.onChange] - (v: number | null) => void
|
|
116
|
+
* @param {number|null} [config.value]
|
|
117
|
+
* @param {boolean} [config.autoFocus]
|
|
118
|
+
* @param {Function} [config.onBlur]
|
|
119
|
+
* @param {string} [config.rootClassName]
|
|
120
|
+
* @returns {HTMLElement} Wrapper (label + input)
|
|
121
|
+
*/
|
|
122
|
+
function create(config) {
|
|
123
|
+
var variant = config.variant || "default";
|
|
124
|
+
var size = config.size || "default";
|
|
125
|
+
var disabled = !!config.disabled;
|
|
126
|
+
var className = config.className || "";
|
|
127
|
+
var column = config.column || {};
|
|
128
|
+
var onChange = config.onChange;
|
|
129
|
+
var value = config.value != null ? config.value : null;
|
|
130
|
+
var autoFocus = !!config.autoFocus;
|
|
131
|
+
var onBlur = config.onBlur;
|
|
132
|
+
var placeholder =
|
|
133
|
+
config.placeholder != null
|
|
134
|
+
? config.placeholder
|
|
135
|
+
: (column.placeholder || "Enter value");
|
|
136
|
+
var rootClassName = config.rootClassName || "";
|
|
137
|
+
|
|
138
|
+
var currencyLabel =
|
|
139
|
+
(column.properties && column.properties.currency && column.properties.currency.currency) ||
|
|
140
|
+
"Currency";
|
|
141
|
+
|
|
142
|
+
var sizeClass = size === "large" ? "large" : size === "small" ? "small" : "default";
|
|
143
|
+
|
|
144
|
+
var wrapper = document.createElement("div");
|
|
145
|
+
wrapper.className = join(
|
|
146
|
+
WRAPPER_CLASS.base,
|
|
147
|
+
WRAPPER_CLASS[variant] != null ? WRAPPER_CLASS[variant] : WRAPPER_CLASS.default,
|
|
148
|
+
disabled ? WRAPPER_CLASS.disabled : "",
|
|
149
|
+
className
|
|
150
|
+
);
|
|
151
|
+
wrapper.setAttribute("data-currency-variant", variant);
|
|
152
|
+
|
|
153
|
+
var labelEl = document.createElement("div");
|
|
154
|
+
labelEl.className = join(LABEL_CLASS, LABEL_SIZE[sizeClass]);
|
|
155
|
+
labelEl.textContent = currencyLabel;
|
|
156
|
+
wrapper.appendChild(labelEl);
|
|
157
|
+
|
|
158
|
+
var inputWrap = document.createElement("div");
|
|
159
|
+
inputWrap.className = INPUT_WRAPPER_CLASS;
|
|
160
|
+
|
|
161
|
+
var input = document.createElement("input");
|
|
162
|
+
input.type = "text";
|
|
163
|
+
input.inputMode = "decimal";
|
|
164
|
+
input.autocomplete = "off";
|
|
165
|
+
input.placeholder = placeholder;
|
|
166
|
+
input.value = value != null ? formatDecimal(value) : "";
|
|
167
|
+
input.disabled = disabled;
|
|
168
|
+
input.className = join(
|
|
169
|
+
INPUT_CLASS.base,
|
|
170
|
+
variant === "inline" ? INPUT_CLASS.inline : "",
|
|
171
|
+
INPUT_CLASS[sizeClass === "large" ? "sizeLarge" : sizeClass === "small" ? "sizeSmall" : "sizeDefault"],
|
|
172
|
+
rootClassName
|
|
173
|
+
);
|
|
174
|
+
if (autoFocus) input.autofocus = true;
|
|
175
|
+
|
|
176
|
+
restrictDecimalInput(input);
|
|
177
|
+
|
|
178
|
+
function notifyChange() {
|
|
179
|
+
var parsed = parseDecimal(input.value);
|
|
180
|
+
if (onChange) onChange(parsed);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
input.addEventListener("input", notifyChange);
|
|
184
|
+
input.addEventListener("change", notifyChange);
|
|
185
|
+
if (onBlur) input.addEventListener("blur", onBlur);
|
|
186
|
+
|
|
187
|
+
inputWrap.appendChild(input);
|
|
188
|
+
wrapper.appendChild(inputWrap);
|
|
189
|
+
|
|
190
|
+
wrapper.getInput = function () {
|
|
191
|
+
return input;
|
|
192
|
+
};
|
|
193
|
+
wrapper.setValue = function (v) {
|
|
194
|
+
if (v == null || v === "") {
|
|
195
|
+
input.value = "";
|
|
196
|
+
} else {
|
|
197
|
+
input.value = formatDecimal(Number(v));
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
wrapper.getValue = function () {
|
|
201
|
+
return parseDecimal(input.value);
|
|
202
|
+
};
|
|
203
|
+
wrapper.setVariant = function (v) {
|
|
204
|
+
variant = v;
|
|
205
|
+
wrapper.setAttribute("data-currency-variant", v);
|
|
206
|
+
wrapper.className = join(
|
|
207
|
+
WRAPPER_CLASS.base,
|
|
208
|
+
WRAPPER_CLASS[variant] != null ? WRAPPER_CLASS[variant] : WRAPPER_CLASS.default,
|
|
209
|
+
disabled ? WRAPPER_CLASS.disabled : "",
|
|
210
|
+
config.className || ""
|
|
211
|
+
);
|
|
212
|
+
};
|
|
213
|
+
wrapper.setDisabled = function (d) {
|
|
214
|
+
disabled = !!d;
|
|
215
|
+
input.disabled = disabled;
|
|
216
|
+
wrapper.classList.toggle("cursor-not-allowed", disabled);
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
return wrapper;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
global.CurrencyComponent = {
|
|
223
|
+
create: create,
|
|
224
|
+
parseDecimal: parseDecimal,
|
|
225
|
+
formatDecimal: formatDecimal,
|
|
226
|
+
};
|
|
227
|
+
})(typeof window !== "undefined" ? window : this);
|