@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,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Icon Component (vanilla JS)
|
|
3
|
+
* Renders a span with optional Tabler-style icon (by name) and bg/text color classes.
|
|
4
|
+
* API aligned with React Icon: iconStr, color, fallbackIconStr, fullSizeIcon, stroke, className.
|
|
5
|
+
* Tabler Icons: https://tabler.io/icons
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
(function (global) {
|
|
9
|
+
"use strict";
|
|
10
|
+
|
|
11
|
+
var TI =
|
|
12
|
+
' xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"';
|
|
13
|
+
var TI_PATH_NONE = '<path stroke="none" d="M0 0h24v24H0z" fill="none"/>';
|
|
14
|
+
|
|
15
|
+
/** Tabler icon name -> SVG string (stroke 1.5). Used when iconStr matches. */
|
|
16
|
+
var iconMap = {
|
|
17
|
+
IconDatabase:
|
|
18
|
+
"<svg" +
|
|
19
|
+
TI +
|
|
20
|
+
' stroke-width="1.5">' +
|
|
21
|
+
TI_PATH_NONE +
|
|
22
|
+
'<path d="M4 6a8 3 0 1 0 16 0a8 3 0 1 0 -16 0"/><path d="M4 6v6a8 3 0 0 0 16 0v-6"/><path d="M4 12v6a8 3 0 0 0 16 0v-6"/></svg>',
|
|
23
|
+
IconUsers:
|
|
24
|
+
"<svg" +
|
|
25
|
+
TI +
|
|
26
|
+
' stroke-width="1.5">' +
|
|
27
|
+
TI_PATH_NONE +
|
|
28
|
+
'<path d="M5 7a4 4 0 1 0 8 0a4 4 0 1 0 -8 0"/><path d="M3 21v-2a4 4 0 0 1 4 -4h4a4 4 0 0 1 4 4v2"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/><path d="M21 21v-2a4 4 0 0 0 -3 -3.85"/></svg>',
|
|
29
|
+
IconShield:
|
|
30
|
+
"<svg" +
|
|
31
|
+
TI +
|
|
32
|
+
' stroke-width="1.5">' +
|
|
33
|
+
TI_PATH_NONE +
|
|
34
|
+
'<path d="M12 3a12 12 0 0 0 8.5 3a12 12 0 0 1 -8.5 15a12 12 0 0 1 -8.5 -15a12 12 0 0 0 8.5 -3"/></svg>',
|
|
35
|
+
IconLayout:
|
|
36
|
+
"<svg" +
|
|
37
|
+
TI +
|
|
38
|
+
' stroke-width="1.5">' +
|
|
39
|
+
TI_PATH_NONE +
|
|
40
|
+
'<path d="M4 4m0 2a2 2 0 0 1 2 -2h2a2 2 0 0 1 2 2v1a2 2 0 0 1 -2 2h-2a2 2 0 0 1 -2 -2z"/><path d="M4 13m0 2a2 2 0 0 1 2 -2h2a2 2 0 0 1 2 2v3a2 2 0 0 1 -2 2h-2a2 2 0 0 1 -2 -2z"/><path d="M14 4m0 2a2 2 0 0 1 2 -2h2a2 2 0 0 1 2 2v3a2 2 0 0 1 -2 2h-2a2 2 0 0 1 -2 -2z"/><path d="M14 15m0 2a2 2 0 0 1 2 -2h2a2 2 0 0 1 2 2v1a2 2 0 0 1 -2 2h-2a2 2 0 0 1 -2 -2z"/></svg>',
|
|
41
|
+
IconUser:
|
|
42
|
+
"<svg" +
|
|
43
|
+
TI +
|
|
44
|
+
' stroke-width="1.5">' +
|
|
45
|
+
TI_PATH_NONE +
|
|
46
|
+
'<path d="M8 7a4 4 0 1 0 8 0a4 4 0 0 0 -8 0"/><path d="M6 21v-2a4 4 0 0 1 4 -4h4a4 4 0 0 1 4 4v2"/></svg>',
|
|
47
|
+
IconCurrencyDollar:
|
|
48
|
+
"<svg" +
|
|
49
|
+
TI +
|
|
50
|
+
' stroke-width="1.5">' +
|
|
51
|
+
TI_PATH_NONE +
|
|
52
|
+
'<path d="M16.7 8a3 3 0 0 0 -2.7 -2h-4a3 3 0 0 0 0 6h4a3 3 0 0 1 0 6h-4a3 3 0 0 1 -2.7 -2"/><path d="M12 3v3m0 12v3"/></svg>',
|
|
53
|
+
IconPhone:
|
|
54
|
+
"<svg" +
|
|
55
|
+
TI +
|
|
56
|
+
' stroke-width="1.5">' +
|
|
57
|
+
TI_PATH_NONE +
|
|
58
|
+
'<path d="M5 4h4l2 5l-2.5 1.5a11 11 0 0 0 5 5l1.5 -2.5l5 2v4a2 2 0 0 1 -2 2a16 16 0 0 1 -15 -15a2 2 0 0 1 2 -2"/></svg>',
|
|
59
|
+
IconMessage:
|
|
60
|
+
"<svg" +
|
|
61
|
+
TI +
|
|
62
|
+
' stroke-width="1.5">' +
|
|
63
|
+
TI_PATH_NONE +
|
|
64
|
+
'<path d="M8 9h8"/><path d="M8 13h6"/><path d="M18 4a3 3 0 0 1 3 3v8a3 3 0 0 1 -3 3h-5l-5 3v-3h-2a3 3 0 0 1 -3 -3v-8a3 3 0 0 1 3 -3h12"/></svg>',
|
|
65
|
+
IconClock:
|
|
66
|
+
"<svg" +
|
|
67
|
+
TI +
|
|
68
|
+
' stroke-width="1.5">' +
|
|
69
|
+
TI_PATH_NONE +
|
|
70
|
+
'<path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0"/><path d="M12 7v5l4 2"/></svg>',
|
|
71
|
+
IconMail:
|
|
72
|
+
"<svg" +
|
|
73
|
+
TI +
|
|
74
|
+
' stroke-width="1.5">' +
|
|
75
|
+
TI_PATH_NONE +
|
|
76
|
+
'<path d="M3 7a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v10a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2v-10"/><path d="M3 7l9 6l9 -6"/></svg>',
|
|
77
|
+
/** Filled circle for "just color" mode (IconOrColor when only icon_color is set) */
|
|
78
|
+
IconCircleFilled:
|
|
79
|
+
'<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none"><circle cx="12" cy="12" r="10" fill="currentColor"/></svg>',
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
function join() {
|
|
83
|
+
return Array.prototype.filter.call(arguments, Boolean).join(" ");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* IconOrColor config: { start_icon?: string, icon_color?: string }.
|
|
88
|
+
* Treat as empty: empty string, null, undefined, or empty array.
|
|
89
|
+
*/
|
|
90
|
+
function isFieldValueEmpty(value) {
|
|
91
|
+
if (value === null || value === undefined) return true;
|
|
92
|
+
if (typeof value === "string" && value.trim() === "") return true;
|
|
93
|
+
if (Array.isArray(value) && value.length === 0) return true;
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Simple emoji check: single character in common emoji ranges */
|
|
98
|
+
function isEmoji(str) {
|
|
99
|
+
if (!str || typeof str !== "string") return false;
|
|
100
|
+
var s = str.trim();
|
|
101
|
+
if (s.length === 0 || s.length > 4) return false;
|
|
102
|
+
var code = s.charCodeAt(0);
|
|
103
|
+
return (
|
|
104
|
+
(code >= 0x1f300 && code <= 0x1f9ff) ||
|
|
105
|
+
(code >= 0x2600 && code <= 0x26ff) ||
|
|
106
|
+
(code >= 0x2700 && code <= 0x27bf) ||
|
|
107
|
+
code === 0xfe0f
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Create an icon element.
|
|
113
|
+
* @param {Object} config
|
|
114
|
+
* @param {string} [config.iconStr] - Icon name (e.g. IconUsers, IconDatabase)
|
|
115
|
+
* @param {string} [config.iconSvg] - Optional raw SVG string; if set, used instead of iconMap[iconStr]
|
|
116
|
+
* @param {string} [config.color] - Token for bg/text (e.g. 'primary', 'neutral'); used as bg-{color}-surface-hover text-{color}-text-base
|
|
117
|
+
* @param {string} [config.fallbackIconStr] - Fallback icon name when iconStr not in map (default 'IconDatabase')
|
|
118
|
+
* @param {boolean} [config.defaultIcon] - If true, render fallback when iconStr missing; otherwise can return null
|
|
119
|
+
* @param {boolean} [config.fullSizeIcon] - If true, icon SVG gets size-full class wrapper
|
|
120
|
+
* @param {number} [config.stroke] - Stroke width for icon (default 1.5); only affects SVG when we support dynamic stroke
|
|
121
|
+
* @param {string} [config.className] - Extra classes (e.g. size-20 shrink-0 for record-select)
|
|
122
|
+
* @returns {HTMLElement} span with icon and color classes
|
|
123
|
+
*/
|
|
124
|
+
function create(config) {
|
|
125
|
+
var iconStr = (config && config.iconStr) || "";
|
|
126
|
+
var iconSvg = config && config.iconSvg;
|
|
127
|
+
var color = (config && config.color) != null ? config.color : "neutral";
|
|
128
|
+
var fallbackIconStr = (config && config.fallbackIconStr) || "IconDatabase";
|
|
129
|
+
var defaultIcon = !!(config && config.defaultIcon);
|
|
130
|
+
var fullSizeIcon = !!(config && config.fullSizeIcon);
|
|
131
|
+
var className = (config && config.className) || "";
|
|
132
|
+
|
|
133
|
+
if (isEmoji(iconStr)) {
|
|
134
|
+
var emojiSpan = document.createElement("span");
|
|
135
|
+
emojiSpan.className = join("pr-4", className);
|
|
136
|
+
emojiSpan.textContent = iconStr;
|
|
137
|
+
return emojiSpan;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
var svgContent = iconSvg || iconMap[iconStr] || iconMap[fallbackIconStr];
|
|
141
|
+
if (!svgContent && !defaultIcon) return null;
|
|
142
|
+
if (!svgContent) svgContent = iconMap[fallbackIconStr] || iconMap.IconDatabase;
|
|
143
|
+
|
|
144
|
+
var span = document.createElement("span");
|
|
145
|
+
span.className = join(
|
|
146
|
+
"box-content flex size-16 items-center justify-center rounded-4",
|
|
147
|
+
"bg-" + color + "-surface-hover",
|
|
148
|
+
"text-" + color + "-text-base",
|
|
149
|
+
className
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
if (fullSizeIcon) {
|
|
153
|
+
var wrap = document.createElement("span");
|
|
154
|
+
wrap.className = "size-full flex items-center justify-center";
|
|
155
|
+
wrap.innerHTML = svgContent;
|
|
156
|
+
span.appendChild(wrap);
|
|
157
|
+
} else {
|
|
158
|
+
span.innerHTML = svgContent;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return span;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* IconOrColor: show icon + color, or just color (filled circle), or nothing.
|
|
166
|
+
* Matches React IconOrColor: return null if both start_icon and icon_color are empty;
|
|
167
|
+
* if only icon_color set, render IconCircleFilled with that color; else render icon with color.
|
|
168
|
+
* @param {Object} config - { start_icon?: string, icon_color?: string }
|
|
169
|
+
* @param {string} [config.start_icon] - Icon name (e.g. IconUsers)
|
|
170
|
+
* @param {string} [config.icon_color] - Color token (e.g. 'primary', 'neutral')
|
|
171
|
+
* @param {string} [config.className] - Extra classes
|
|
172
|
+
* @returns {HTMLElement | null}
|
|
173
|
+
*/
|
|
174
|
+
function createIconOrColor(config) {
|
|
175
|
+
if (!config) return null;
|
|
176
|
+
if (isFieldValueEmpty(config.start_icon) && isFieldValueEmpty(config.icon_color)) return null;
|
|
177
|
+
|
|
178
|
+
var isJustColor = isFieldValueEmpty(config.start_icon) && !isFieldValueEmpty(config.icon_color);
|
|
179
|
+
var color = config.icon_color || "neutral";
|
|
180
|
+
var extraClass = (config.className || "") + (isJustColor ? " !bg-transparent text-" + color + "-base" : "");
|
|
181
|
+
|
|
182
|
+
return create({
|
|
183
|
+
iconStr: isJustColor ? "IconCircleFilled" : config.start_icon,
|
|
184
|
+
color: color,
|
|
185
|
+
fallbackIconStr: "IconCircleFilled",
|
|
186
|
+
defaultIcon: true,
|
|
187
|
+
className: extraClass.trim(),
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
global.Icon = {
|
|
192
|
+
iconMap: iconMap,
|
|
193
|
+
create: create,
|
|
194
|
+
createIconOrColor: createIconOrColor,
|
|
195
|
+
isEmoji: isEmoji,
|
|
196
|
+
isFieldValueEmpty: isFieldValueEmpty,
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
console.log("[Icon] Module loaded successfully");
|
|
200
|
+
})(typeof window !== "undefined" ? window : this);
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Input Component (vanilla JS)
|
|
3
|
+
* Design-system input with variants, sizes, status icons, and password toggle.
|
|
4
|
+
* Ref: React Input with cva variants; no React/cva/icons dependency.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
(function (global) {
|
|
8
|
+
"use strict";
|
|
9
|
+
|
|
10
|
+
// Inline SVGs (Tabler-style) – no external icon lib
|
|
11
|
+
var ICONS = {
|
|
12
|
+
alertCircle:
|
|
13
|
+
'<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>',
|
|
14
|
+
circleCheck:
|
|
15
|
+
'<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="m9 12 2 2 4-4"/></svg>',
|
|
16
|
+
eye:
|
|
17
|
+
'<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z"/><circle cx="12" cy="12" r="3"/></svg>',
|
|
18
|
+
eyeOff:
|
|
19
|
+
'<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9.88 9.88a3 3 0 1 0 4.24 4.24"/><path d="M10.73 5.08A10.43 10.43 0 0 1 12 5c7 0 10 7 10 7a13.16 13.16 0 0 1-1.67 2.68"/><path d="M6.61 6.61A13.16 13.16 0 0 0 2 12s3 7 10 7a9.9 9.9 0 0 0 5.39-1.61"/><line x1="2" y1="2" x2="22" y2="22"/></svg>',
|
|
20
|
+
lock:
|
|
21
|
+
'<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>',
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
var WRAPPER_CLASS = {
|
|
25
|
+
base:
|
|
26
|
+
"group flex items-center border-1/2 border-border-primary rounded-4 text-typography-primary-text gap-x-8 w-full transition-all ease-in-out",
|
|
27
|
+
default:
|
|
28
|
+
"bg-fill-quarternary-fill-white hover:border-primary-base focus-within:border-primary-base",
|
|
29
|
+
error:
|
|
30
|
+
"border-error-base bg-fill-quarternary-fill-white hover:border-error-base focus-within:border-error-base",
|
|
31
|
+
warning:
|
|
32
|
+
"border-warning-base bg-fill-quarternary-fill-white hover:border-warning-base focus-within:border-warning-base",
|
|
33
|
+
success:
|
|
34
|
+
"border-success-base bg-fill-quarternary-fill-white hover:border-success-base focus-within:border-success-base",
|
|
35
|
+
password:
|
|
36
|
+
"bg-fill-quarternary-fill-white hover:border-primary-base focus-within:border-primary-base",
|
|
37
|
+
borderless:
|
|
38
|
+
"border-none shadow-none rounded-0 bg-fill-quarternary-fill-white",
|
|
39
|
+
inline:
|
|
40
|
+
"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",
|
|
41
|
+
sizeDefault: "px-12 py-6",
|
|
42
|
+
sizeLarge: "px-12 py-8",
|
|
43
|
+
sizeSmall: "px-12 py-4",
|
|
44
|
+
disabled:
|
|
45
|
+
"cursor-not-allowed border-border-primary bg-fill-tertiary-fill-light-gray text-typography-quaternary-text hover:border-border-primary",
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
var INPUT_CLASS = {
|
|
49
|
+
base:
|
|
50
|
+
"w-full 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",
|
|
51
|
+
inline:
|
|
52
|
+
"transition-all focus:bg-fill-tertiary-fill-light-gray group-hover:bg-fill-tertiary-fill-light-gray",
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
var ICON_WRAPPER_CLASS = {
|
|
56
|
+
default: "flex items-center justify-center rounded-4 size-16 text-typography-quaternary-text shrink-0",
|
|
57
|
+
error: "flex items-center justify-center rounded-4 size-16 text-error-base shrink-0",
|
|
58
|
+
warning: "flex items-center justify-center rounded-4 size-16 text-warning-base shrink-0",
|
|
59
|
+
success: "flex items-center justify-center rounded-4 size-16 text-primary-base shrink-0",
|
|
60
|
+
password: "flex items-center justify-center rounded-4 size-16 text-primary-base hover:cursor-pointer shrink-0",
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
function join() {
|
|
64
|
+
return Array.prototype.filter.call(arguments, Boolean).join(" ");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Create an input component
|
|
69
|
+
* @param {Object} config
|
|
70
|
+
* @param {string} [config.variant] - 'default' | 'error' | 'warning' | 'success' | 'password' | 'borderless' | 'inline'
|
|
71
|
+
* @param {string} [config.inputSize] - 'default' | 'large' | 'small'
|
|
72
|
+
* @param {string} [config.type] - input type (text, email, password, etc.)
|
|
73
|
+
* @param {string} [config.placeholder]
|
|
74
|
+
* @param {string} [config.value]
|
|
75
|
+
* @param {boolean} [config.disabled]
|
|
76
|
+
* @param {boolean} [config.readOnly]
|
|
77
|
+
* @param {boolean} [config.showReadOnlyIcon] - show lock icon when readOnly
|
|
78
|
+
* @param {string} [config.name]
|
|
79
|
+
* @param {string} [config.id]
|
|
80
|
+
* @param {string} [config.className] - extra class on wrapper
|
|
81
|
+
* @param {string} [config.rootClassName] - extra class on inner input
|
|
82
|
+
* @param {HTMLElement|string} [config.prefixNode] - node or HTML string before input
|
|
83
|
+
* @param {HTMLElement|string} [config.startIcon] - node or HTML string at start (not for inline)
|
|
84
|
+
* @param {HTMLElement|string} [config.endIcon] - node or HTML string at end (default/borderless only)
|
|
85
|
+
* @param {Function} [config.onChange]
|
|
86
|
+
* @param {Function} [config.onInput]
|
|
87
|
+
* @param {Function} [config.onFocus]
|
|
88
|
+
* @param {Function} [config.onBlur]
|
|
89
|
+
* @returns {HTMLElement} wrapper element (contains input + optional icons)
|
|
90
|
+
*/
|
|
91
|
+
function create(config) {
|
|
92
|
+
var variant = config.variant || "default";
|
|
93
|
+
var inputSize = config.inputSize || "default";
|
|
94
|
+
var type = config.type || "text";
|
|
95
|
+
var disabled = !!config.disabled;
|
|
96
|
+
var readOnly = !!config.readOnly;
|
|
97
|
+
var showReadOnlyIcon = config.showReadOnlyIcon !== false;
|
|
98
|
+
var isPassword = type === "password";
|
|
99
|
+
|
|
100
|
+
var wrapper = document.createElement("div");
|
|
101
|
+
wrapper.className = join(
|
|
102
|
+
WRAPPER_CLASS.base,
|
|
103
|
+
WRAPPER_CLASS[variant] || WRAPPER_CLASS.default,
|
|
104
|
+
inputSize === "large" ? WRAPPER_CLASS.sizeLarge : inputSize === "small" ? WRAPPER_CLASS.sizeSmall : WRAPPER_CLASS.sizeDefault,
|
|
105
|
+
disabled ? WRAPPER_CLASS.disabled : "",
|
|
106
|
+
config.className || ""
|
|
107
|
+
);
|
|
108
|
+
wrapper.setAttribute("data-input-variant", variant);
|
|
109
|
+
|
|
110
|
+
if (config.prefixNode) {
|
|
111
|
+
var prefixWrap = document.createElement("span");
|
|
112
|
+
prefixWrap.className = "shrink-0";
|
|
113
|
+
if (typeof config.prefixNode === "string") {
|
|
114
|
+
prefixWrap.innerHTML = config.prefixNode;
|
|
115
|
+
} else {
|
|
116
|
+
prefixWrap.appendChild(config.prefixNode);
|
|
117
|
+
}
|
|
118
|
+
wrapper.appendChild(prefixWrap);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (config.startIcon && variant !== "inline") {
|
|
122
|
+
var startWrap = document.createElement("span");
|
|
123
|
+
startWrap.className = ICON_WRAPPER_CLASS[variant] || ICON_WRAPPER_CLASS.default;
|
|
124
|
+
if (typeof config.startIcon === "string") {
|
|
125
|
+
startWrap.innerHTML = config.startIcon;
|
|
126
|
+
} else {
|
|
127
|
+
startWrap.appendChild(config.startIcon);
|
|
128
|
+
}
|
|
129
|
+
wrapper.appendChild(startWrap);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
var input = document.createElement("input");
|
|
133
|
+
input.type = isPassword ? "text" : type;
|
|
134
|
+
input.autocomplete = "off";
|
|
135
|
+
if (config.placeholder != null) input.placeholder = config.placeholder;
|
|
136
|
+
if (config.value != null) input.value = config.value;
|
|
137
|
+
if (config.name) input.name = config.name;
|
|
138
|
+
if (config.id) input.id = config.id;
|
|
139
|
+
input.disabled = disabled;
|
|
140
|
+
input.readOnly = readOnly;
|
|
141
|
+
input.className = join(
|
|
142
|
+
"!text-reg-13",
|
|
143
|
+
INPUT_CLASS.base,
|
|
144
|
+
variant === "inline" ? INPUT_CLASS.inline : "",
|
|
145
|
+
config.rootClassName || ""
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
var showPassword = false;
|
|
149
|
+
if (isPassword) {
|
|
150
|
+
input.type = "password";
|
|
151
|
+
input.setAttribute("data-password-masked", "true");
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
wrapper.appendChild(input);
|
|
155
|
+
|
|
156
|
+
if (variant !== "inline") {
|
|
157
|
+
if (variant === "error") {
|
|
158
|
+
var errIcon = document.createElement("span");
|
|
159
|
+
errIcon.className = ICON_WRAPPER_CLASS.error;
|
|
160
|
+
errIcon.innerHTML = ICONS.alertCircle;
|
|
161
|
+
errIcon.setAttribute("aria-hidden", "true");
|
|
162
|
+
wrapper.appendChild(errIcon);
|
|
163
|
+
} else if (variant === "warning") {
|
|
164
|
+
var warnIcon = document.createElement("span");
|
|
165
|
+
warnIcon.className = ICON_WRAPPER_CLASS.warning;
|
|
166
|
+
warnIcon.innerHTML = ICONS.alertCircle;
|
|
167
|
+
warnIcon.setAttribute("aria-hidden", "true");
|
|
168
|
+
wrapper.appendChild(warnIcon);
|
|
169
|
+
} else if (variant === "success") {
|
|
170
|
+
var okIcon = document.createElement("span");
|
|
171
|
+
okIcon.className = ICON_WRAPPER_CLASS.success;
|
|
172
|
+
okIcon.innerHTML = ICONS.circleCheck;
|
|
173
|
+
okIcon.setAttribute("aria-hidden", "true");
|
|
174
|
+
wrapper.appendChild(okIcon);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (variant === "password" && isPassword) {
|
|
178
|
+
var pwdWrap = document.createElement("span");
|
|
179
|
+
pwdWrap.className = ICON_WRAPPER_CLASS.password;
|
|
180
|
+
pwdWrap.setAttribute("role", "button");
|
|
181
|
+
pwdWrap.setAttribute("tabindex", "0");
|
|
182
|
+
pwdWrap.setAttribute("aria-label", "Toggle password visibility");
|
|
183
|
+
pwdWrap.innerHTML = showPassword ? ICONS.eyeOff : ICONS.eye;
|
|
184
|
+
pwdWrap.addEventListener("click", function () {
|
|
185
|
+
if (disabled) return;
|
|
186
|
+
showPassword = !showPassword;
|
|
187
|
+
input.type = showPassword ? "text" : "password";
|
|
188
|
+
input.setAttribute("data-password-masked", showPassword ? "false" : "true");
|
|
189
|
+
pwdWrap.innerHTML = showPassword ? ICONS.eyeOff : ICONS.eye;
|
|
190
|
+
});
|
|
191
|
+
pwdWrap.addEventListener("keydown", function (e) {
|
|
192
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
193
|
+
e.preventDefault();
|
|
194
|
+
pwdWrap.click();
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
wrapper.appendChild(pwdWrap);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (readOnly && showReadOnlyIcon) {
|
|
201
|
+
var lockWrap = document.createElement("span");
|
|
202
|
+
lockWrap.className = join(ICON_WRAPPER_CLASS.default, "text-typography-quaternary-text");
|
|
203
|
+
lockWrap.innerHTML = ICONS.lock;
|
|
204
|
+
lockWrap.setAttribute("aria-hidden", "true");
|
|
205
|
+
wrapper.appendChild(lockWrap);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if ((!variant || variant === "default" || variant === "borderless") && config.endIcon) {
|
|
209
|
+
var endWrap = document.createElement("span");
|
|
210
|
+
endWrap.className = ICON_WRAPPER_CLASS.default;
|
|
211
|
+
if (typeof config.endIcon === "string") {
|
|
212
|
+
endWrap.innerHTML = config.endIcon;
|
|
213
|
+
} else {
|
|
214
|
+
endWrap.appendChild(config.endIcon);
|
|
215
|
+
}
|
|
216
|
+
wrapper.appendChild(endWrap);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (config.onChange) input.addEventListener("change", config.onChange);
|
|
221
|
+
if (config.onInput) input.addEventListener("input", config.onInput);
|
|
222
|
+
if (config.onFocus) input.addEventListener("focus", config.onFocus);
|
|
223
|
+
if (config.onBlur) input.addEventListener("blur", config.onBlur);
|
|
224
|
+
|
|
225
|
+
wrapper.getInput = function () {
|
|
226
|
+
return input;
|
|
227
|
+
};
|
|
228
|
+
wrapper.setValue = function (v) {
|
|
229
|
+
input.value = v;
|
|
230
|
+
};
|
|
231
|
+
wrapper.getValue = function () {
|
|
232
|
+
return input.value;
|
|
233
|
+
};
|
|
234
|
+
wrapper.setVariant = function (v) {
|
|
235
|
+
variant = v;
|
|
236
|
+
wrapper.setAttribute("data-input-variant", v);
|
|
237
|
+
wrapper.className = join(
|
|
238
|
+
WRAPPER_CLASS.base,
|
|
239
|
+
WRAPPER_CLASS[variant] || WRAPPER_CLASS.default,
|
|
240
|
+
inputSize === "large" ? WRAPPER_CLASS.sizeLarge : inputSize === "small" ? WRAPPER_CLASS.sizeSmall : WRAPPER_CLASS.sizeDefault,
|
|
241
|
+
disabled ? WRAPPER_CLASS.disabled : "",
|
|
242
|
+
config.className || ""
|
|
243
|
+
);
|
|
244
|
+
};
|
|
245
|
+
wrapper.setDisabled = function (d) {
|
|
246
|
+
disabled = !!d;
|
|
247
|
+
input.disabled = disabled;
|
|
248
|
+
wrapper.classList.toggle("cursor-not-allowed", disabled);
|
|
249
|
+
wrapper.classList.toggle("opacity-60", disabled);
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
return wrapper;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
global.InputComponent = {
|
|
256
|
+
create: create,
|
|
257
|
+
ICONS: ICONS,
|
|
258
|
+
};
|
|
259
|
+
})(typeof window !== "undefined" ? window : this);
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Label component – vanilla JS equivalent of Radix Label with CVA-style variants.
|
|
3
|
+
* Supports size, requiredPosition, optional, icon, and optional suffix (e.g. tooltip).
|
|
4
|
+
*
|
|
5
|
+
* Padding/margin use Tailwind theme.spacing (tailwind.config.js) → --sizes-size-* (tailwind-input.css):
|
|
6
|
+
* - pb-8 = padding-bottom 0.5rem (--sizes-size-8)
|
|
7
|
+
* - gap-2 = gap 0.125rem (--sizes-size-2) between label text, icon, asterisk
|
|
8
|
+
* - size-16 = icon wrapper 1rem (--sizes-size-16)
|
|
9
|
+
*/
|
|
10
|
+
(function (global) {
|
|
11
|
+
"use strict";
|
|
12
|
+
|
|
13
|
+
var SIZE_CLASSES = {
|
|
14
|
+
default: "text-reg-13",
|
|
15
|
+
small: "text-reg-12",
|
|
16
|
+
large: "text-reg-14",
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
var REQUIRED_POSITION_CLASSES = {
|
|
20
|
+
left: "required-left",
|
|
21
|
+
right: "required-right",
|
|
22
|
+
none: "required-none",
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
function joinClasses() {
|
|
26
|
+
var parts = [];
|
|
27
|
+
for (var i = 0; i < arguments.length; i++) {
|
|
28
|
+
if (arguments[i]) parts.push(arguments[i]);
|
|
29
|
+
}
|
|
30
|
+
return parts.join(" ");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Create a label element matching the Radix Label API (size, requiredPosition, optional, icon).
|
|
35
|
+
* @param {Object} config
|
|
36
|
+
* @param {string} config.label - Label text
|
|
37
|
+
* @param {boolean} [config.required=false] - Show required asterisk
|
|
38
|
+
* @param {'left'|'right'|'none'} [config.requiredPosition='right'] - Where to show asterisk
|
|
39
|
+
* @param {boolean} [config.optional=false] - Show "(optional)" after label
|
|
40
|
+
* @param {string} [config.size='default'] - 'default' | 'small' | 'large'
|
|
41
|
+
* @param {HTMLElement} [config.icon] - Optional icon node
|
|
42
|
+
* @param {string} [config.className] - Extra classes
|
|
43
|
+
* @param {string} [config.htmlFor] - For attribute
|
|
44
|
+
* @param {HTMLElement} [config.suffix] - Optional node appended after content (e.g. tooltip)
|
|
45
|
+
* @returns {HTMLElement} <label> element
|
|
46
|
+
*/
|
|
47
|
+
function create(config) {
|
|
48
|
+
var labelText = config.label != null ? String(config.label) : "";
|
|
49
|
+
var required = !!config.required;
|
|
50
|
+
var requiredPosition = config.requiredPosition || "right";
|
|
51
|
+
var optional = !!config.optional;
|
|
52
|
+
var size = config.size || "default";
|
|
53
|
+
var icon = config.icon;
|
|
54
|
+
var className = config.className || "";
|
|
55
|
+
var htmlFor = config.htmlFor;
|
|
56
|
+
var suffix = config.suffix;
|
|
57
|
+
|
|
58
|
+
var base =
|
|
59
|
+
"inline-flex items-center gap-2 flex-shrink-0 pb-8 leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70";
|
|
60
|
+
var sizeClass = SIZE_CLASSES[size] || SIZE_CLASSES.default;
|
|
61
|
+
var requiredPosClass = REQUIRED_POSITION_CLASSES[requiredPosition] || REQUIRED_POSITION_CLASSES.none;
|
|
62
|
+
var optionalClass = optional ? "optional-true" : "optional-false";
|
|
63
|
+
|
|
64
|
+
var labelEl = document.createElement("label");
|
|
65
|
+
labelEl.className = joinClasses(
|
|
66
|
+
"!text-typography-tertiary-text",
|
|
67
|
+
base,
|
|
68
|
+
sizeClass,
|
|
69
|
+
requiredPosClass,
|
|
70
|
+
optionalClass,
|
|
71
|
+
className
|
|
72
|
+
);
|
|
73
|
+
if (htmlFor) labelEl.htmlFor = htmlFor;
|
|
74
|
+
|
|
75
|
+
if (required && requiredPosition === "left") {
|
|
76
|
+
var leftAsterisk = document.createElement("span");
|
|
77
|
+
leftAsterisk.className = "required-label text-error-base";
|
|
78
|
+
leftAsterisk.textContent = "*";
|
|
79
|
+
labelEl.appendChild(leftAsterisk);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
labelEl.appendChild(document.createTextNode(labelText));
|
|
83
|
+
|
|
84
|
+
if (icon) {
|
|
85
|
+
var iconWrap = document.createElement("span");
|
|
86
|
+
iconWrap.className = "box-content flex size-16 items-center justify-center";
|
|
87
|
+
iconWrap.appendChild(icon);
|
|
88
|
+
labelEl.appendChild(iconWrap);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (required && requiredPosition === "right") {
|
|
92
|
+
var rightAsterisk = document.createElement("span");
|
|
93
|
+
rightAsterisk.className = "required-label text-error-base";
|
|
94
|
+
rightAsterisk.textContent = " *";
|
|
95
|
+
labelEl.appendChild(rightAsterisk);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (optional) {
|
|
99
|
+
var optionalSpan = document.createElement("span");
|
|
100
|
+
optionalSpan.className = "optional-label text-typography-tertiary-text";
|
|
101
|
+
optionalSpan.textContent = " (optional)";
|
|
102
|
+
labelEl.appendChild(optionalSpan);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (suffix) labelEl.appendChild(suffix);
|
|
106
|
+
|
|
107
|
+
return labelEl;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
global.Label = { create: create };
|
|
111
|
+
})(typeof window !== "undefined" ? window : this);
|