@diwauhris/ui 1.7.4 → 1.7.7
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/README.md +24 -64
- package/lib/assets/ui.css +189 -7
- package/lib/index.cjs +935 -340
- package/lib/index.mjs +935 -340
- package/lib/types/lib/composeRefs.d.ts +9 -0
- package/lib/types/ui-library/gallery/accordion/Accordion.d.ts +1 -1
- package/lib/types/ui-library/gallery/breadcrumb/Breadcrumb.d.ts +2 -1
- package/lib/types/ui-library/gallery/button/Button.d.ts +7 -1
- package/lib/types/ui-library/gallery/card/Card.d.ts +4 -3
- package/lib/types/ui-library/gallery/checkbox/Checkbox.d.ts +3 -1
- package/lib/types/ui-library/gallery/column-manager/ColumnManager.d.ts +2 -1
- package/lib/types/ui-library/gallery/combobox/Combobox.d.ts +14 -7
- package/lib/types/ui-library/gallery/command-palette/CommandPalette.d.ts +3 -1
- package/lib/types/ui-library/gallery/confirm-action/ConfirmAction.d.ts +3 -1
- package/lib/types/ui-library/gallery/confirm-dialog/ConfirmDialog.d.ts +3 -1
- package/lib/types/ui-library/gallery/date-picker/DatePicker.d.ts +3 -1
- package/lib/types/ui-library/gallery/dialog/Dialog.d.ts +9 -1
- package/lib/types/ui-library/gallery/drawer/Drawer.d.ts +3 -1
- package/lib/types/ui-library/gallery/field/Field.d.ts +2 -1
- package/lib/types/ui-library/gallery/field-input/FieldInput.d.ts +3 -1
- package/lib/types/ui-library/gallery/file-upload/FileUpload.d.ts +7 -1
- package/lib/types/ui-library/gallery/icon-button/IconButton.d.ts +6 -1
- package/lib/types/ui-library/gallery/input/Input.d.ts +3 -1
- package/lib/types/ui-library/gallery/menu/Menu.d.ts +7 -1
- package/lib/types/ui-library/gallery/modal/Modal.d.ts +11 -1
- package/lib/types/ui-library/gallery/pagination/Pagination.d.ts +2 -1
- package/lib/types/ui-library/gallery/popover/Popover.d.ts +3 -2
- package/lib/types/ui-library/gallery/radio-group/RadioGroup.d.ts +2 -1
- package/lib/types/ui-library/gallery/search/Search.d.ts +23 -0
- package/lib/types/ui-library/gallery/select/Select.d.ts +13 -1
- package/lib/types/ui-library/gallery/skeleton/Skeleton.d.ts +19 -9
- package/lib/types/ui-library/gallery/stat-card/Statistic.d.ts +2 -1
- package/lib/types/ui-library/gallery/stepper/Stepper.d.ts +3 -1
- package/lib/types/ui-library/gallery/switch/Switch.d.ts +6 -3
- package/lib/types/ui-library/gallery/textarea/Textarea.d.ts +3 -1
- package/lib/types/ui-library/gallery/timeline/Timeline.d.ts +4 -2
- package/lib/types/ui-library/gallery/tooltip/Tooltip.d.ts +8 -6
- package/lib/types/ui-library/index.d.ts +4 -2
- package/package.json +2 -3
package/lib/index.cjs
CHANGED
|
@@ -1,47 +1,47 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
//#region \0rolldown/runtime.js
|
|
3
3
|
var __create = Object.create;
|
|
4
|
-
var __defProp = Object.defineProperty;
|
|
4
|
+
var __defProp$2 = Object.defineProperty;
|
|
5
5
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
6
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
7
|
var __getProtoOf = Object.getPrototypeOf;
|
|
8
8
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
9
|
var __exportAll = (all, no_symbols) => {
|
|
10
10
|
let target = {};
|
|
11
|
-
for (var name in all) __defProp(target, name, {
|
|
11
|
+
for (var name in all) __defProp$2(target, name, {
|
|
12
12
|
get: all[name],
|
|
13
13
|
enumerable: true
|
|
14
14
|
});
|
|
15
|
-
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
15
|
+
if (!no_symbols) __defProp$2(target, Symbol.toStringTag, { value: "Module" });
|
|
16
16
|
return target;
|
|
17
17
|
};
|
|
18
18
|
var __copyProps = (to, from, except, desc) => {
|
|
19
19
|
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
20
20
|
key = keys[i];
|
|
21
|
-
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
21
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp$2(to, key, {
|
|
22
22
|
get: ((k) => from[k]).bind(null, key),
|
|
23
23
|
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
24
24
|
});
|
|
25
25
|
}
|
|
26
26
|
return to;
|
|
27
27
|
};
|
|
28
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
|
|
28
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp$2(target, "default", {
|
|
29
29
|
value: mod,
|
|
30
30
|
enumerable: true
|
|
31
31
|
}) : target, mod));
|
|
32
32
|
//#endregion
|
|
33
|
-
let react_jsx_runtime = require("react/jsx-runtime");
|
|
34
|
-
let react = require("react");
|
|
35
|
-
react = __toESM(react, 1);
|
|
36
33
|
let clsx = require("clsx");
|
|
37
34
|
let tailwind_merge = require("tailwind-merge");
|
|
35
|
+
let react = require("react");
|
|
36
|
+
react = __toESM(react, 1);
|
|
37
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
38
|
+
let react_dom = require("react-dom");
|
|
38
39
|
let lucide_react = require("lucide-react");
|
|
39
40
|
let react_calendar = require("react-calendar");
|
|
40
41
|
react_calendar = __toESM(react_calendar, 1);
|
|
41
42
|
let date_fns = require("date-fns");
|
|
42
43
|
let _radix_ui_react_dialog = require("@radix-ui/react-dialog");
|
|
43
44
|
_radix_ui_react_dialog = __toESM(_radix_ui_react_dialog, 1);
|
|
44
|
-
let react_dom = require("react-dom");
|
|
45
45
|
let _radix_ui_react_popover = require("@radix-ui/react-popover");
|
|
46
46
|
_radix_ui_react_popover = __toESM(_radix_ui_react_popover, 1);
|
|
47
47
|
let _radix_ui_react_tooltip = require("@radix-ui/react-tooltip");
|
|
@@ -50,6 +50,158 @@ let react_router_dom = require("react-router-dom");
|
|
|
50
50
|
let d3 = require("d3");
|
|
51
51
|
d3 = __toESM(d3, 1);
|
|
52
52
|
let sonner = require("sonner");
|
|
53
|
+
//#region src/lib/utils.ts
|
|
54
|
+
function cn(...inputs) {
|
|
55
|
+
return (0, tailwind_merge.twMerge)((0, clsx.clsx)(inputs));
|
|
56
|
+
}
|
|
57
|
+
//#endregion
|
|
58
|
+
//#region node_modules/@radix-ui/react-compose-refs/dist/index.mjs
|
|
59
|
+
var __defProp$1 = Object.defineProperty;
|
|
60
|
+
var __name$1 = (target, value) => __defProp$1(target, "name", {
|
|
61
|
+
value,
|
|
62
|
+
configurable: true
|
|
63
|
+
});
|
|
64
|
+
function setRef(ref, value) {
|
|
65
|
+
if (typeof ref === "function") return ref(value);
|
|
66
|
+
else if (ref !== null && ref !== void 0) ref.current = value;
|
|
67
|
+
}
|
|
68
|
+
__name$1(setRef, "setRef");
|
|
69
|
+
function composeRefs$1(...refs) {
|
|
70
|
+
return (node) => {
|
|
71
|
+
let hasCleanup = false;
|
|
72
|
+
const cleanups = refs.map((ref) => {
|
|
73
|
+
const cleanup = setRef(ref, node);
|
|
74
|
+
if (!hasCleanup && typeof cleanup == "function") hasCleanup = true;
|
|
75
|
+
return cleanup;
|
|
76
|
+
});
|
|
77
|
+
if (hasCleanup) return () => {
|
|
78
|
+
for (let i = 0; i < cleanups.length; i++) {
|
|
79
|
+
const cleanup = cleanups[i];
|
|
80
|
+
if (typeof cleanup == "function") cleanup();
|
|
81
|
+
else setRef(refs[i], null);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
__name$1(composeRefs$1, "composeRefs");
|
|
87
|
+
function useComposedRefs(...refs) {
|
|
88
|
+
return react.useCallback(composeRefs$1(...refs), refs);
|
|
89
|
+
}
|
|
90
|
+
__name$1(useComposedRefs, "useComposedRefs");
|
|
91
|
+
//#endregion
|
|
92
|
+
//#region node_modules/@radix-ui/react-slot/dist/index.mjs
|
|
93
|
+
var __defProp = Object.defineProperty;
|
|
94
|
+
var __name = (target, value) => __defProp(target, "name", {
|
|
95
|
+
value,
|
|
96
|
+
configurable: true
|
|
97
|
+
});
|
|
98
|
+
// @__NO_SIDE_EFFECTS__
|
|
99
|
+
function createSlot(ownerName) {
|
|
100
|
+
const Slot2 = react.forwardRef((props, forwardedRef) => {
|
|
101
|
+
let { children, ...slotProps } = props;
|
|
102
|
+
let slottableElement = null;
|
|
103
|
+
let hasSlottable = false;
|
|
104
|
+
const newChildren = [];
|
|
105
|
+
if (isLazyComponent(children) && typeof use$1 === "function") children = use$1(children._payload);
|
|
106
|
+
react.Children.forEach(children, (maybeSlottable) => {
|
|
107
|
+
if (isSlottable(maybeSlottable)) {
|
|
108
|
+
hasSlottable = true;
|
|
109
|
+
const slottable = maybeSlottable;
|
|
110
|
+
let child = "child" in slottable.props ? slottable.props.child : slottable.props.children;
|
|
111
|
+
if (isLazyComponent(child) && typeof use$1 === "function") child = use$1(child._payload);
|
|
112
|
+
slottableElement = getSlottableElementFromSlottable(slottable, child);
|
|
113
|
+
newChildren.push(slottableElement?.props?.children);
|
|
114
|
+
} else newChildren.push(maybeSlottable);
|
|
115
|
+
});
|
|
116
|
+
if (slottableElement) slottableElement = react.cloneElement(slottableElement, void 0, newChildren);
|
|
117
|
+
else if (!hasSlottable && react.Children.count(children) === 1 && react.isValidElement(children)) slottableElement = children;
|
|
118
|
+
const slottableElementRef = slottableElement ? getElementRef(slottableElement) : void 0;
|
|
119
|
+
const composedRef = useComposedRefs(forwardedRef, slottableElementRef);
|
|
120
|
+
if (!slottableElement) {
|
|
121
|
+
if (children || children === 0) throw new Error(hasSlottable ? createSlottableError(ownerName) : createSlotError(ownerName));
|
|
122
|
+
return children;
|
|
123
|
+
}
|
|
124
|
+
const mergedProps = mergeProps(slotProps, slottableElement.props ?? {});
|
|
125
|
+
if (slottableElement.type !== react.Fragment) mergedProps.ref = forwardedRef ? composedRef : slottableElementRef;
|
|
126
|
+
return react.cloneElement(slottableElement, mergedProps);
|
|
127
|
+
});
|
|
128
|
+
Slot2.displayName = `${ownerName}.Slot`;
|
|
129
|
+
return Slot2;
|
|
130
|
+
}
|
|
131
|
+
__name(createSlot, "createSlot");
|
|
132
|
+
var Slot = /* @__PURE__ */ createSlot("Slot");
|
|
133
|
+
var SLOTTABLE_IDENTIFIER = Symbol.for("radix.slottable");
|
|
134
|
+
// @__NO_SIDE_EFFECTS__
|
|
135
|
+
function createSlottable(ownerName) {
|
|
136
|
+
const Slottable2 = /* @__PURE__ */ __name((props) => "child" in props ? props.children(props.child) : props.children, "Slottable");
|
|
137
|
+
Slottable2.displayName = `${ownerName}.Slottable`;
|
|
138
|
+
Slottable2.__radixId = SLOTTABLE_IDENTIFIER;
|
|
139
|
+
return Slottable2;
|
|
140
|
+
}
|
|
141
|
+
__name(createSlottable, "createSlottable");
|
|
142
|
+
var getSlottableElementFromSlottable = /* @__PURE__ */ __name((slottable, child) => {
|
|
143
|
+
if ("child" in slottable.props) {
|
|
144
|
+
const child2 = slottable.props.child;
|
|
145
|
+
if (!react.isValidElement(child2)) return null;
|
|
146
|
+
return react.cloneElement(child2, void 0, slottable.props.children(child2.props.children));
|
|
147
|
+
}
|
|
148
|
+
return react.isValidElement(child) ? child : null;
|
|
149
|
+
}, "getSlottableElementFromSlottable");
|
|
150
|
+
function mergeProps(slotProps, childProps) {
|
|
151
|
+
const overrideProps = { ...childProps };
|
|
152
|
+
for (const propName in childProps) {
|
|
153
|
+
const slotPropValue = slotProps[propName];
|
|
154
|
+
const childPropValue = childProps[propName];
|
|
155
|
+
if (/^on[A-Z]/.test(propName)) {
|
|
156
|
+
if (slotPropValue && childPropValue) overrideProps[propName] = (...args) => {
|
|
157
|
+
const result = childPropValue(...args);
|
|
158
|
+
slotPropValue(...args);
|
|
159
|
+
return result;
|
|
160
|
+
};
|
|
161
|
+
else if (slotPropValue) overrideProps[propName] = slotPropValue;
|
|
162
|
+
} else if (propName === "style") overrideProps[propName] = {
|
|
163
|
+
...slotPropValue,
|
|
164
|
+
...childPropValue
|
|
165
|
+
};
|
|
166
|
+
else if (propName === "className") overrideProps[propName] = [slotPropValue, childPropValue].filter(Boolean).join(" ");
|
|
167
|
+
}
|
|
168
|
+
return {
|
|
169
|
+
...slotProps,
|
|
170
|
+
...overrideProps
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
__name(mergeProps, "mergeProps");
|
|
174
|
+
function getElementRef(element) {
|
|
175
|
+
let getter = Object.getOwnPropertyDescriptor(element.props, "ref")?.get;
|
|
176
|
+
let mayWarn = getter && "isReactWarning" in getter && getter.isReactWarning;
|
|
177
|
+
if (mayWarn) return element.ref;
|
|
178
|
+
getter = Object.getOwnPropertyDescriptor(element, "ref")?.get;
|
|
179
|
+
mayWarn = getter && "isReactWarning" in getter && getter.isReactWarning;
|
|
180
|
+
if (mayWarn) return element.props.ref;
|
|
181
|
+
return element.props.ref || element.ref;
|
|
182
|
+
}
|
|
183
|
+
__name(getElementRef, "getElementRef");
|
|
184
|
+
function isSlottable(child) {
|
|
185
|
+
return react.isValidElement(child) && typeof child.type === "function" && "__radixId" in child.type && child.type.__radixId === SLOTTABLE_IDENTIFIER;
|
|
186
|
+
}
|
|
187
|
+
__name(isSlottable, "isSlottable");
|
|
188
|
+
var REACT_LAZY_TYPE = Symbol.for("react.lazy");
|
|
189
|
+
function isLazyComponent(element) {
|
|
190
|
+
return element != null && typeof element === "object" && "$$typeof" in element && element.$$typeof === REACT_LAZY_TYPE && "_payload" in element && isPromiseLike(element._payload);
|
|
191
|
+
}
|
|
192
|
+
__name(isLazyComponent, "isLazyComponent");
|
|
193
|
+
function isPromiseLike(value) {
|
|
194
|
+
return typeof value === "object" && value !== null && "then" in value;
|
|
195
|
+
}
|
|
196
|
+
__name(isPromiseLike, "isPromiseLike");
|
|
197
|
+
var createSlotError = /* @__PURE__ */ __name((ownerName) => {
|
|
198
|
+
return `${ownerName} failed to slot onto its children. Expected a single React element child or \`Slottable\`.`;
|
|
199
|
+
}, "createSlotError");
|
|
200
|
+
var createSlottableError = /* @__PURE__ */ __name((ownerName) => {
|
|
201
|
+
return `${ownerName} failed to slot onto its \`Slottable\`. Expected \`Slottable\` to receive a single React element child.`;
|
|
202
|
+
}, "createSlottableError");
|
|
203
|
+
var use$1 = react[" use ".trim().toString()];
|
|
204
|
+
//#endregion
|
|
53
205
|
//#region src/ui-library/gallery/button/Button.tsx
|
|
54
206
|
var VARIANT_STYLES$1 = {
|
|
55
207
|
primary: "font-bold bg-brand-blue text-white shadow-lg shadow-brand-blue/25 hover:bg-brand-navy hover:shadow-xl hover:shadow-brand-blue/30 active:bg-brand-navy focus-visible:ring-brand-blue/30 disabled:bg-slate-200 disabled:text-slate-400 disabled:shadow-none",
|
|
@@ -64,23 +216,17 @@ var SIZE_STYLES$1 = {
|
|
|
64
216
|
lg: "h-11 gap-2.5 rounded-md px-6 text-base"
|
|
65
217
|
};
|
|
66
218
|
var BASE$1 = "inline-flex items-center justify-center transition-all duration-150 ease-out active:scale-[.98] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:active:scale-100 select-none";
|
|
67
|
-
function Button({ variant = "primary", size = "md", loading = false, leftIcon, rightIcon, fullWidth = false, children, className = "", disabled, ...props }) {
|
|
68
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.
|
|
69
|
-
type: "button",
|
|
70
|
-
disabled: disabled || loading,
|
|
71
|
-
className: [
|
|
72
|
-
BASE$1,
|
|
73
|
-
VARIANT_STYLES$1[variant],
|
|
74
|
-
SIZE_STYLES$1[size],
|
|
75
|
-
fullWidth ? "w-full" : "",
|
|
76
|
-
className
|
|
77
|
-
].filter(Boolean).join(" "),
|
|
219
|
+
function Button({ variant = "primary", size = "md", loading = false, leftIcon, rightIcon, fullWidth = false, children, className = "", disabled, asChild = false, ...props }) {
|
|
220
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(asChild ? Slot : "button", {
|
|
221
|
+
type: asChild ? void 0 : "button",
|
|
222
|
+
disabled: asChild ? void 0 : disabled || loading,
|
|
223
|
+
className: cn(BASE$1, VARIANT_STYLES$1[variant], SIZE_STYLES$1[size], fullWidth && "w-full", className),
|
|
78
224
|
...props,
|
|
79
|
-
children: [
|
|
225
|
+
children: asChild ? children : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
80
226
|
leftIcon && !loading && leftIcon,
|
|
81
227
|
children,
|
|
82
228
|
rightIcon && !loading && rightIcon
|
|
83
|
-
]
|
|
229
|
+
] })
|
|
84
230
|
});
|
|
85
231
|
}
|
|
86
232
|
//#endregion
|
|
@@ -91,14 +237,10 @@ var VARIANT_STYLES = {
|
|
|
91
237
|
edit: "hover:bg-blue-50 hover:text-brand-blue focus-visible:ring-brand-blue/20",
|
|
92
238
|
danger: "hover:bg-rose-50 hover:text-rose-600 focus-visible:ring-rose-100"
|
|
93
239
|
};
|
|
94
|
-
function IconButton({ icon, variant = "default", className = "", ...props }) {
|
|
95
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
96
|
-
type: "button",
|
|
97
|
-
className: [
|
|
98
|
-
BASE,
|
|
99
|
-
VARIANT_STYLES[variant],
|
|
100
|
-
className
|
|
101
|
-
].filter(Boolean).join(" "),
|
|
240
|
+
function IconButton({ icon, variant = "default", className = "", asChild = false, ...props }) {
|
|
241
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(asChild ? Slot : "button", {
|
|
242
|
+
type: asChild ? void 0 : "button",
|
|
243
|
+
className: cn(BASE, VARIANT_STYLES[variant], className),
|
|
102
244
|
...props,
|
|
103
245
|
children: icon
|
|
104
246
|
});
|
|
@@ -128,18 +270,28 @@ var SIZE_STYLES = {
|
|
|
128
270
|
};
|
|
129
271
|
function Badge({ tone = "neutral", size = "md", icon, children, className = "" }) {
|
|
130
272
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
131
|
-
className: [
|
|
132
|
-
"inline-flex items-center border font-semibold uppercase tracking-wide",
|
|
133
|
-
STATUS_TONES.has(tone) ? "rounded" : "rounded-full",
|
|
134
|
-
TONE_STYLES$1[tone],
|
|
135
|
-
SIZE_STYLES[size],
|
|
136
|
-
className
|
|
137
|
-
].filter(Boolean).join(" "),
|
|
273
|
+
className: cn("inline-flex items-center border font-semibold uppercase tracking-wide", STATUS_TONES.has(tone) ? "rounded" : "rounded-full", TONE_STYLES$1[tone], SIZE_STYLES[size], className),
|
|
138
274
|
children: [icon, children]
|
|
139
275
|
});
|
|
140
276
|
}
|
|
141
277
|
//#endregion
|
|
142
278
|
//#region src/ui-library/gallery/spinner/Spinner.tsx
|
|
279
|
+
/**
|
|
280
|
+
* Spinner — Design System Component
|
|
281
|
+
*
|
|
282
|
+
* An indeterminate loading indicator. Purely presentational.
|
|
283
|
+
*
|
|
284
|
+
* Sizes: sm | md | lg
|
|
285
|
+
*
|
|
286
|
+
* Usage:
|
|
287
|
+
* - Inline: inside a button or alongside text
|
|
288
|
+
* - Standalone: centered in a container while content loads
|
|
289
|
+
*
|
|
290
|
+
* Accessibility:
|
|
291
|
+
* - Renders a <span role="status"> that announces label to screen readers
|
|
292
|
+
* - label is required and should describe what is loading
|
|
293
|
+
* - Respects prefers-reduced-motion: animation is suppressed, label stays
|
|
294
|
+
*/
|
|
143
295
|
var SIZE_TRACK = {
|
|
144
296
|
sm: "h-4 w-4 border-2",
|
|
145
297
|
md: "h-6 w-6 border-2",
|
|
@@ -153,7 +305,7 @@ var SIZE_TEXT = {
|
|
|
153
305
|
function Spinner({ size = "md", label = "Loading…", labelHidden = false, className = "" }) {
|
|
154
306
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
155
307
|
role: "status",
|
|
156
|
-
className:
|
|
308
|
+
className: cn("inline-flex items-center gap-2", className),
|
|
157
309
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
158
310
|
"aria-hidden": "true",
|
|
159
311
|
className: ["inline-block animate-spin rounded-full border-slate-200 border-t-brand-blue motion-reduce:animate-none", SIZE_TRACK[size]].join(" ")
|
|
@@ -165,6 +317,25 @@ function Spinner({ size = "md", label = "Loading…", labelHidden = false, class
|
|
|
165
317
|
}
|
|
166
318
|
//#endregion
|
|
167
319
|
//#region src/ui-library/gallery/divider/Divider.tsx
|
|
320
|
+
/**
|
|
321
|
+
* Divider — Design System Component
|
|
322
|
+
*
|
|
323
|
+
* A thin visual separator between content areas.
|
|
324
|
+
*
|
|
325
|
+
* Decorative usage (default, aria-hidden="true"):
|
|
326
|
+
* Used purely for visual grouping. Screen readers skip it.
|
|
327
|
+
* This is the appropriate choice for most cases.
|
|
328
|
+
*
|
|
329
|
+
* Semantic usage (role="separator"):
|
|
330
|
+
* Use when the separation is meaningful to AT users, e.g. between
|
|
331
|
+
* logically distinct regions where the boundary matters.
|
|
332
|
+
* Provide aria-label when context is needed.
|
|
333
|
+
*
|
|
334
|
+
* Orientation:
|
|
335
|
+
* - horizontal (default): full-width horizontal line, displayed as block.
|
|
336
|
+
* - vertical: full-height vertical line, displayed inline. The parent
|
|
337
|
+
* container must give it an explicit height or use flex/grid context.
|
|
338
|
+
*/
|
|
168
339
|
function Divider({ orientation = "horizontal", decorative = true, "aria-label": ariaLabel, className = "" }) {
|
|
169
340
|
const isHorizontal = orientation === "horizontal";
|
|
170
341
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
@@ -173,7 +344,7 @@ function Divider({ orientation = "horizontal", decorative = true, "aria-label":
|
|
|
173
344
|
"aria-label": ariaLabel,
|
|
174
345
|
"aria-orientation": orientation
|
|
175
346
|
},
|
|
176
|
-
className:
|
|
347
|
+
className: cn(isHorizontal ? "w-full border-t border-slate-200" : "self-stretch border-l border-slate-200", className)
|
|
177
348
|
});
|
|
178
349
|
}
|
|
179
350
|
//#endregion
|
|
@@ -237,12 +408,7 @@ function Avatar({ name, src, size = "md", color, className = "" }) {
|
|
|
237
408
|
const bg = color ?? deriveColor(name);
|
|
238
409
|
const initials = getInitials(name);
|
|
239
410
|
const label = name || "Unknown";
|
|
240
|
-
const baseClass = [
|
|
241
|
-
"inline-flex items-center justify-center rounded-full shrink-0",
|
|
242
|
-
"font-bold text-white select-none overflow-hidden",
|
|
243
|
-
SIZE_CLASSES[size],
|
|
244
|
-
className
|
|
245
|
-
].filter(Boolean).join(" ");
|
|
411
|
+
const baseClass = cn("inline-flex items-center justify-center rounded-full shrink-0", "font-bold text-white select-none overflow-hidden", SIZE_CLASSES[size], className);
|
|
246
412
|
if (showImage) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
|
|
247
413
|
src,
|
|
248
414
|
alt: label,
|
|
@@ -263,7 +429,7 @@ function Section({ title, description, actions, level = 2, children, className =
|
|
|
263
429
|
const Tag = `h${level}`;
|
|
264
430
|
const headingClass = level === 1 ? "text-2xl font-bold text-slate-900" : level === 2 ? "text-lg font-bold text-slate-900" : "text-base font-bold text-slate-800";
|
|
265
431
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
266
|
-
className:
|
|
432
|
+
className: cn("space-y-4", className),
|
|
267
433
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
268
434
|
className: "flex items-start justify-between gap-4",
|
|
269
435
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -286,7 +452,7 @@ function Section({ title, description, actions, level = 2, children, className =
|
|
|
286
452
|
//#region src/ui-library/gallery/page-header/PageHeader.tsx
|
|
287
453
|
function PageHeader({ title, subtitle, icon, actions, className = "" }) {
|
|
288
454
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
289
|
-
className,
|
|
455
|
+
className: cn(className),
|
|
290
456
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
291
457
|
className: "flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between",
|
|
292
458
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -315,11 +481,7 @@ function PageHeader({ title, subtitle, icon, actions, className = "" }) {
|
|
|
315
481
|
//#region src/ui-library/gallery/toolbar/Toolbar.tsx
|
|
316
482
|
function Toolbar({ left, right, className = "" }) {
|
|
317
483
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
318
|
-
className:
|
|
319
|
-
"w-full flex flex-wrap items-center justify-between gap-4",
|
|
320
|
-
"bg-white p-2 border border-slate-100 rounded-2xl shadow-sm",
|
|
321
|
-
className
|
|
322
|
-
].filter(Boolean).join(" "),
|
|
484
|
+
className: cn("w-full flex flex-wrap items-center justify-between gap-4", "bg-white p-2 border border-slate-100 rounded-2xl shadow-sm", className),
|
|
323
485
|
children: [left, right]
|
|
324
486
|
});
|
|
325
487
|
}
|
|
@@ -347,7 +509,7 @@ function useFieldContext() {
|
|
|
347
509
|
* FieldInput inside this wrapper automatically receive the correct id,
|
|
348
510
|
* aria-required, aria-invalid, and aria-describedby — with no prop threading.
|
|
349
511
|
*/
|
|
350
|
-
function Field({ label, required = false, error, children, inputId }) {
|
|
512
|
+
function Field({ label, required = false, error, children, inputId, className = "" }) {
|
|
351
513
|
const generatedId = (0, react.useId)();
|
|
352
514
|
const fieldId = inputId ?? generatedId;
|
|
353
515
|
const errorId = `${fieldId}-error`;
|
|
@@ -359,7 +521,7 @@ function Field({ label, required = false, error, children, inputId }) {
|
|
|
359
521
|
invalid: !!error
|
|
360
522
|
},
|
|
361
523
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
362
|
-
className:
|
|
524
|
+
className: cn("flex flex-col gap-1.5", error && "shake", className),
|
|
363
525
|
children: [
|
|
364
526
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
365
527
|
htmlFor: fieldId,
|
|
@@ -386,11 +548,6 @@ function Field({ label, required = false, error, children, inputId }) {
|
|
|
386
548
|
});
|
|
387
549
|
}
|
|
388
550
|
//#endregion
|
|
389
|
-
//#region src/lib/utils.ts
|
|
390
|
-
function cn(...inputs) {
|
|
391
|
-
return (0, tailwind_merge.twMerge)((0, clsx.clsx)(inputs));
|
|
392
|
-
}
|
|
393
|
-
//#endregion
|
|
394
551
|
//#region src/ui-library/gallery/field-input/FieldInput.tsx
|
|
395
552
|
/**
|
|
396
553
|
* FieldInput — Gallery-native context-aware <input> wrapper.
|
|
@@ -398,13 +555,14 @@ function cn(...inputs) {
|
|
|
398
555
|
* Updated to import FieldContext from gallery-local field directory
|
|
399
556
|
* and to carry full DIWA visual styling.
|
|
400
557
|
*/
|
|
401
|
-
function FieldInput({ id, "aria-required": ariaRequired, "aria-invalid": ariaInvalid, "aria-describedby": ariaDescribedBy, className = "", disabled, ...rest }) {
|
|
558
|
+
function FieldInput({ id, "aria-required": ariaRequired, "aria-invalid": ariaInvalid, "aria-describedby": ariaDescribedBy, className = "", disabled, ref, ...rest }) {
|
|
402
559
|
const ctx = useFieldContext();
|
|
403
560
|
const resolvedId = id ?? ctx?.inputId;
|
|
404
561
|
const resolvedRequired = ariaRequired ?? (ctx?.required ? "true" : void 0);
|
|
405
562
|
const resolvedInvalid = ariaInvalid ?? (ctx?.invalid ? "true" : void 0);
|
|
406
563
|
const resolvedDescribedBy = ariaDescribedBy ?? (ctx?.invalid && ctx?.errorId ? ctx.errorId : void 0);
|
|
407
564
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
565
|
+
ref,
|
|
408
566
|
id: resolvedId,
|
|
409
567
|
"aria-required": resolvedRequired,
|
|
410
568
|
"aria-invalid": resolvedInvalid,
|
|
@@ -439,22 +597,18 @@ var FIELD_HELPER_COLOR = {
|
|
|
439
597
|
};
|
|
440
598
|
//#endregion
|
|
441
599
|
//#region src/ui-library/gallery/input/Input.tsx
|
|
442
|
-
function Input({ status = "default", helper, prefix, suffix, disabled, className = "", ...props }) {
|
|
600
|
+
function Input({ status = "default", helper, prefix, suffix, disabled, className = "", ref, ...props }) {
|
|
443
601
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
444
602
|
className: "w-full",
|
|
445
603
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
446
|
-
className: [
|
|
447
|
-
"flex w-full items-center rounded-md border bg-white transition",
|
|
448
|
-
FIELD_STATUS_RING_WITHIN[status],
|
|
449
|
-
disabled ? "cursor-not-allowed bg-slate-50 opacity-60" : "",
|
|
450
|
-
className
|
|
451
|
-
].filter(Boolean).join(" "),
|
|
604
|
+
className: cn("flex w-full items-center rounded-md border bg-white transition", FIELD_STATUS_RING_WITHIN[status], disabled && "cursor-not-allowed bg-slate-50 opacity-60", className),
|
|
452
605
|
children: [
|
|
453
606
|
prefix && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
454
607
|
className: "flex shrink-0 items-center pl-3.5 text-slate-400",
|
|
455
608
|
children: prefix
|
|
456
609
|
}),
|
|
457
610
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
611
|
+
ref,
|
|
458
612
|
disabled,
|
|
459
613
|
className: [
|
|
460
614
|
"w-full bg-transparent py-2.5 text-sm font-medium text-slate-900 outline-none placeholder:text-slate-400",
|
|
@@ -488,7 +642,7 @@ var RESIZE_CLASS = {
|
|
|
488
642
|
vertical: "resize-y",
|
|
489
643
|
both: "resize"
|
|
490
644
|
};
|
|
491
|
-
function Textarea({ status = "default", helper, maxLength, resize = "vertical", disabled, value, defaultValue, onChange, className = "", ...props }) {
|
|
645
|
+
function Textarea({ status = "default", helper, maxLength, resize = "vertical", disabled, value, defaultValue, onChange, className = "", ref, ...props }) {
|
|
492
646
|
const isControlled = value !== void 0;
|
|
493
647
|
const [uncontrolledLength, setUncontrolledLength] = (0, react.useState)(typeof defaultValue === "string" ? defaultValue.length : 0);
|
|
494
648
|
const currentLength = isControlled ? typeof value === "string" ? value.length : 0 : uncontrolledLength;
|
|
@@ -499,18 +653,13 @@ function Textarea({ status = "default", helper, maxLength, resize = "vertical",
|
|
|
499
653
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
500
654
|
className: "w-full",
|
|
501
655
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
|
|
656
|
+
ref,
|
|
502
657
|
maxLength,
|
|
503
658
|
disabled,
|
|
504
659
|
value,
|
|
505
660
|
defaultValue,
|
|
506
661
|
onChange: handleChange,
|
|
507
|
-
className: [
|
|
508
|
-
"w-full rounded-xl border bg-white px-4 py-3 text-sm font-medium text-slate-900 outline-none transition placeholder:text-slate-400",
|
|
509
|
-
FIELD_STATUS_RING_DIRECT[status],
|
|
510
|
-
RESIZE_CLASS[resize],
|
|
511
|
-
disabled ? "cursor-not-allowed bg-slate-50 opacity-60" : "",
|
|
512
|
-
className
|
|
513
|
-
].filter(Boolean).join(" "),
|
|
662
|
+
className: cn("w-full rounded-xl border bg-white px-4 py-3 text-sm font-medium text-slate-900 outline-none transition placeholder:text-slate-400", FIELD_STATUS_RING_DIRECT[status], RESIZE_CLASS[resize], disabled && "cursor-not-allowed bg-slate-50 opacity-60", className),
|
|
514
663
|
...props
|
|
515
664
|
}), (helper || maxLength !== void 0) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
516
665
|
className: "mt-1.5 flex items-start justify-between gap-2",
|
|
@@ -544,23 +693,227 @@ function Textarea({ status = "default", helper, maxLength, resize = "vertical",
|
|
|
544
693
|
* - For searchable/multi-select use cases, use Combobox (Phase 3).
|
|
545
694
|
*
|
|
546
695
|
* The label is NOT included. The consuming feature pairs a <label>.
|
|
696
|
+
*
|
|
697
|
+
* When renderOption is provided, a custom dropdown is rendered instead
|
|
698
|
+
* of the native <select>. Only use this when native option rendering
|
|
699
|
+
* is insufficient (e.g., icons, avatars, multi-line content).
|
|
547
700
|
*/
|
|
548
701
|
function isGroup(item) {
|
|
549
702
|
return "group" in item;
|
|
550
703
|
}
|
|
551
|
-
function
|
|
704
|
+
function CustomDropdown({ items, value, placeholder, status, disabled, renderOption, onChange, id, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, className = "" }) {
|
|
705
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
706
|
+
const [highlightIndex, setHighlightIndex] = (0, react.useState)(-1);
|
|
707
|
+
const containerRef = (0, react.useRef)(null);
|
|
708
|
+
const triggerRef = (0, react.useRef)(null);
|
|
709
|
+
const listboxId = (0, react.useId)();
|
|
710
|
+
const triggerId = (0, react.useId)();
|
|
711
|
+
const optionId = (index) => `${listboxId}-opt-${index}`;
|
|
712
|
+
const [panelStyle, setPanelStyle] = (0, react.useState)({});
|
|
713
|
+
const flatItems = [];
|
|
714
|
+
for (const item of items) if (isGroup(item)) {
|
|
715
|
+
flatItems.push({
|
|
716
|
+
type: "group",
|
|
717
|
+
label: item.group
|
|
718
|
+
});
|
|
719
|
+
for (const opt of item.options) flatItems.push({
|
|
720
|
+
type: "option",
|
|
721
|
+
opt
|
|
722
|
+
});
|
|
723
|
+
} else flatItems.push({
|
|
724
|
+
type: "option",
|
|
725
|
+
opt: item
|
|
726
|
+
});
|
|
727
|
+
const flatOptions = flatItems.filter((fi) => fi.type === "option").map((fi) => fi.opt);
|
|
728
|
+
const selectedOption = flatOptions.find((o) => String(o.value) === String(value ?? ""));
|
|
729
|
+
const displayLabel = selectedOption?.label ?? placeholder ?? "";
|
|
730
|
+
const hasValue = Boolean(selectedOption);
|
|
731
|
+
(0, react.useEffect)(() => {
|
|
732
|
+
if (!open) setHighlightIndex(-1);
|
|
733
|
+
}, [open]);
|
|
734
|
+
const listRef = (0, react.useRef)(null);
|
|
735
|
+
(0, react.useEffect)(() => {
|
|
736
|
+
if (highlightIndex >= 0 && listRef.current) listRef.current.querySelector(`[id="${optionId(highlightIndex)}"]`)?.scrollIntoView({ block: "nearest" });
|
|
737
|
+
}, [highlightIndex]);
|
|
738
|
+
(0, react.useEffect)(() => {
|
|
739
|
+
if (!open) return;
|
|
740
|
+
const handlePointerDown = (e) => {
|
|
741
|
+
if (containerRef.current && !containerRef.current.contains(e.target)) setOpen(false);
|
|
742
|
+
};
|
|
743
|
+
document.addEventListener("mousedown", handlePointerDown);
|
|
744
|
+
return () => document.removeEventListener("mousedown", handlePointerDown);
|
|
745
|
+
}, [open]);
|
|
746
|
+
(0, react.useEffect)(() => {
|
|
747
|
+
if (!open || !triggerRef.current) return;
|
|
748
|
+
const rect = triggerRef.current.getBoundingClientRect();
|
|
749
|
+
setPanelStyle({
|
|
750
|
+
position: "fixed",
|
|
751
|
+
top: rect.bottom + 4,
|
|
752
|
+
left: rect.left,
|
|
753
|
+
zIndex: 9999,
|
|
754
|
+
width: rect.width
|
|
755
|
+
});
|
|
756
|
+
}, [open]);
|
|
757
|
+
const handleSelect = (0, react.useCallback)((opt) => {
|
|
758
|
+
if (opt.disabled) return;
|
|
759
|
+
const syntheticEvent = {
|
|
760
|
+
target: { value: opt.value },
|
|
761
|
+
currentTarget: { value: opt.value }
|
|
762
|
+
};
|
|
763
|
+
onChange?.(syntheticEvent);
|
|
764
|
+
setOpen(false);
|
|
765
|
+
triggerRef.current?.focus();
|
|
766
|
+
}, [onChange]);
|
|
767
|
+
const handleKeyDown = (e) => {
|
|
768
|
+
const nextEnabled = (from, dir) => {
|
|
769
|
+
let idx = from;
|
|
770
|
+
while (idx >= 0 && idx < flatOptions.length) {
|
|
771
|
+
if (!flatOptions[idx]?.disabled) return idx;
|
|
772
|
+
idx += dir;
|
|
773
|
+
}
|
|
774
|
+
return -1;
|
|
775
|
+
};
|
|
776
|
+
switch (e.key) {
|
|
777
|
+
case "ArrowDown":
|
|
778
|
+
e.preventDefault();
|
|
779
|
+
if (!open) {
|
|
780
|
+
setOpen(true);
|
|
781
|
+
const first = nextEnabled(0, 1);
|
|
782
|
+
setHighlightIndex(first);
|
|
783
|
+
} else setHighlightIndex((prev) => {
|
|
784
|
+
const next = nextEnabled(prev + 1, 1);
|
|
785
|
+
return next === -1 ? prev : next;
|
|
786
|
+
});
|
|
787
|
+
break;
|
|
788
|
+
case "ArrowUp":
|
|
789
|
+
e.preventDefault();
|
|
790
|
+
if (open) setHighlightIndex((prev) => {
|
|
791
|
+
const next = nextEnabled(prev - 1, -1);
|
|
792
|
+
return next === -1 ? prev : next;
|
|
793
|
+
});
|
|
794
|
+
break;
|
|
795
|
+
case "Home":
|
|
796
|
+
e.preventDefault();
|
|
797
|
+
if (open) {
|
|
798
|
+
const first = nextEnabled(0, 1);
|
|
799
|
+
setHighlightIndex(first);
|
|
800
|
+
}
|
|
801
|
+
break;
|
|
802
|
+
case "End":
|
|
803
|
+
e.preventDefault();
|
|
804
|
+
if (open) {
|
|
805
|
+
const last = nextEnabled(flatOptions.length - 1, -1);
|
|
806
|
+
setHighlightIndex(last);
|
|
807
|
+
}
|
|
808
|
+
break;
|
|
809
|
+
case "Enter":
|
|
810
|
+
case " ":
|
|
811
|
+
e.preventDefault();
|
|
812
|
+
if (!open) setOpen(true);
|
|
813
|
+
else if (highlightIndex >= 0 && flatOptions[highlightIndex] && !flatOptions[highlightIndex].disabled) handleSelect(flatOptions[highlightIndex]);
|
|
814
|
+
else setOpen(false);
|
|
815
|
+
break;
|
|
816
|
+
case "Escape":
|
|
817
|
+
e.preventDefault();
|
|
818
|
+
setOpen(false);
|
|
819
|
+
}
|
|
820
|
+
};
|
|
821
|
+
const effectiveTriggerId = id ?? triggerId;
|
|
822
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
823
|
+
className: "relative",
|
|
824
|
+
ref: containerRef,
|
|
825
|
+
children: [
|
|
826
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
827
|
+
ref: triggerRef,
|
|
828
|
+
id: effectiveTriggerId,
|
|
829
|
+
type: "button",
|
|
830
|
+
role: "combobox",
|
|
831
|
+
"aria-haspopup": "listbox",
|
|
832
|
+
"aria-expanded": open,
|
|
833
|
+
"aria-controls": listboxId,
|
|
834
|
+
"aria-activedescendant": open && highlightIndex >= 0 ? optionId(highlightIndex) : void 0,
|
|
835
|
+
"aria-label": ariaLabel,
|
|
836
|
+
"aria-labelledby": ariaLabelledBy,
|
|
837
|
+
disabled,
|
|
838
|
+
onClick: () => setOpen((v) => !v),
|
|
839
|
+
onKeyDown: handleKeyDown,
|
|
840
|
+
className: cn("w-full appearance-none rounded-md border bg-white py-2.5 pl-4 pr-10 text-left text-sm font-medium outline-none transition", FIELD_STATUS_RING_DIRECT[status], hasValue ? "text-slate-900" : "text-slate-400", disabled ? "cursor-not-allowed bg-slate-50 opacity-60" : "cursor-pointer", className),
|
|
841
|
+
children: displayLabel
|
|
842
|
+
}),
|
|
843
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
844
|
+
className: "pointer-events-none absolute right-3.5 top-1/2 -translate-y-1/2 text-slate-400",
|
|
845
|
+
"aria-hidden": "true",
|
|
846
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronDown, {
|
|
847
|
+
size: 14,
|
|
848
|
+
className: open ? "rotate-180 transition-transform" : "transition-transform"
|
|
849
|
+
})
|
|
850
|
+
}),
|
|
851
|
+
open && !disabled && (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
852
|
+
ref: listRef,
|
|
853
|
+
id: listboxId,
|
|
854
|
+
role: "listbox",
|
|
855
|
+
"aria-labelledby": effectiveTriggerId,
|
|
856
|
+
style: panelStyle,
|
|
857
|
+
className: "overflow-y-auto max-h-60 rounded-xl border border-slate-200 bg-white py-1 shadow-xl",
|
|
858
|
+
children: flatItems.map((fi, i) => {
|
|
859
|
+
if (fi.type === "group") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
|
|
860
|
+
role: "presentation",
|
|
861
|
+
className: "px-3 py-1.5 text-[10px] font-bold uppercase tracking-widest text-slate-400",
|
|
862
|
+
children: fi.label
|
|
863
|
+
}, `group-${fi.label}-${i}`);
|
|
864
|
+
const opt = fi.opt;
|
|
865
|
+
const optIndex = flatOptions.indexOf(opt);
|
|
866
|
+
const isSelected = String(opt.value) === String(value ?? "");
|
|
867
|
+
const isHighlighted = optIndex === highlightIndex;
|
|
868
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
|
|
869
|
+
id: optionId(optIndex),
|
|
870
|
+
role: "option",
|
|
871
|
+
"aria-selected": isSelected,
|
|
872
|
+
"aria-disabled": opt.disabled,
|
|
873
|
+
onClick: () => handleSelect(opt),
|
|
874
|
+
className: [
|
|
875
|
+
"cursor-pointer px-3 py-2 text-sm transition-colors",
|
|
876
|
+
opt.disabled ? "cursor-not-allowed opacity-50" : "",
|
|
877
|
+
isHighlighted && !opt.disabled ? "bg-blue-50 font-semibold text-brand-blue" : "",
|
|
878
|
+
isSelected && !isHighlighted ? "bg-blue-50 font-semibold text-brand-blue" : "",
|
|
879
|
+
!isHighlighted && !isSelected && !opt.disabled ? "hover:bg-slate-50 text-slate-700" : "",
|
|
880
|
+
opt.disabled ? "text-slate-400" : ""
|
|
881
|
+
].filter(Boolean).join(" "),
|
|
882
|
+
children: renderOption(opt)
|
|
883
|
+
}, opt.value);
|
|
884
|
+
})
|
|
885
|
+
}), document.body)
|
|
886
|
+
]
|
|
887
|
+
});
|
|
888
|
+
}
|
|
889
|
+
function Select({ items, placeholder, status = "default", helper, disabled, className = "", renderOption, ref, ...props }) {
|
|
890
|
+
if (renderOption) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
891
|
+
className: "w-full",
|
|
892
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(CustomDropdown, {
|
|
893
|
+
items,
|
|
894
|
+
value: props.value,
|
|
895
|
+
placeholder,
|
|
896
|
+
status,
|
|
897
|
+
disabled,
|
|
898
|
+
renderOption,
|
|
899
|
+
onChange: props.onChange,
|
|
900
|
+
id: props.id,
|
|
901
|
+
"aria-label": props["aria-label"],
|
|
902
|
+
"aria-labelledby": props["aria-labelledby"],
|
|
903
|
+
className
|
|
904
|
+
}), helper && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
905
|
+
className: `mt-1.5 text-xs font-medium ${FIELD_HELPER_COLOR[status]}`,
|
|
906
|
+
children: helper
|
|
907
|
+
})]
|
|
908
|
+
});
|
|
552
909
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
553
910
|
className: "w-full",
|
|
554
911
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
555
912
|
className: "relative",
|
|
556
913
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
914
|
+
ref,
|
|
557
915
|
disabled,
|
|
558
|
-
className: [
|
|
559
|
-
"w-full appearance-none rounded-md border bg-white py-2.5 pl-4 pr-10 text-sm font-medium text-slate-900 outline-none transition",
|
|
560
|
-
FIELD_STATUS_RING_DIRECT[status],
|
|
561
|
-
disabled ? "cursor-not-allowed bg-slate-50 opacity-60" : "cursor-pointer",
|
|
562
|
-
className
|
|
563
|
-
].filter(Boolean).join(" "),
|
|
916
|
+
className: cn("w-full appearance-none rounded-md border bg-white py-2.5 pl-4 pr-10 text-sm font-medium text-slate-900 outline-none transition", FIELD_STATUS_RING_DIRECT[status], disabled ? "cursor-not-allowed bg-slate-50 opacity-60" : "cursor-pointer", className),
|
|
564
917
|
...props,
|
|
565
918
|
children: [placeholder && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
566
919
|
value: "",
|
|
@@ -590,6 +943,24 @@ function Select({ items, placeholder, status = "default", helper, disabled, clas
|
|
|
590
943
|
});
|
|
591
944
|
}
|
|
592
945
|
//#endregion
|
|
946
|
+
//#region src/lib/composeRefs.ts
|
|
947
|
+
/**
|
|
948
|
+
* Composes multiple React refs into one callback ref.
|
|
949
|
+
* Used when a component needs both an internal ref and a forwarded external ref.
|
|
950
|
+
*
|
|
951
|
+
* Example:
|
|
952
|
+
* <input ref={composeRefs(internalRef, forwardedRef)} />
|
|
953
|
+
*/
|
|
954
|
+
function composeRefs(...refs) {
|
|
955
|
+
return (node) => {
|
|
956
|
+
refs.forEach((ref) => {
|
|
957
|
+
if (!ref) return;
|
|
958
|
+
if (typeof ref === "function") ref(node);
|
|
959
|
+
else ref.current = node;
|
|
960
|
+
});
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
//#endregion
|
|
593
964
|
//#region src/ui-library/gallery/checkbox/Checkbox.tsx
|
|
594
965
|
/**
|
|
595
966
|
* Checkbox — Design System Component
|
|
@@ -600,14 +971,14 @@ function Select({ items, placeholder, status = "default", helper, disabled, clas
|
|
|
600
971
|
*
|
|
601
972
|
* States: unchecked | checked | indeterminate | disabled | error
|
|
602
973
|
*/
|
|
603
|
-
function Checkbox({ label, description, indeterminate = false, error = false, helper, disabled, checked, id: externalId, className = "", ...props }) {
|
|
974
|
+
function Checkbox({ label, description, indeterminate = false, error = false, helper, disabled, checked, id: externalId, className = "", ref, ...props }) {
|
|
604
975
|
const generatedId = (0, react.useId)();
|
|
605
976
|
const id = externalId ?? generatedId;
|
|
606
977
|
const attachIndeterminate = (el) => {
|
|
607
978
|
if (el) el.indeterminate = indeterminate;
|
|
608
979
|
};
|
|
609
980
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
610
|
-
className:
|
|
981
|
+
className: cn("flex flex-col gap-1", className),
|
|
611
982
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
612
983
|
htmlFor: id,
|
|
613
984
|
className: `group inline-flex cursor-pointer items-start gap-2.5 ${disabled ? "cursor-not-allowed opacity-60" : ""}`,
|
|
@@ -615,7 +986,7 @@ function Checkbox({ label, description, indeterminate = false, error = false, he
|
|
|
615
986
|
className: "relative mt-0.5 shrink-0",
|
|
616
987
|
children: [
|
|
617
988
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
618
|
-
ref: attachIndeterminate,
|
|
989
|
+
ref: composeRefs(attachIndeterminate, ref),
|
|
619
990
|
id,
|
|
620
991
|
type: "checkbox",
|
|
621
992
|
checked,
|
|
@@ -710,26 +1081,23 @@ function RadioGroup({ name: externalName, defaultValue = "", value: controlledVa
|
|
|
710
1081
|
"aria-labelledby": ariaLabelledby,
|
|
711
1082
|
"aria-required": required || void 0,
|
|
712
1083
|
"aria-disabled": disabled || void 0,
|
|
713
|
-
className:
|
|
1084
|
+
className: cn(orientation === "horizontal" ? "flex flex-wrap gap-4" : "flex flex-col gap-3", className),
|
|
714
1085
|
children
|
|
715
1086
|
})
|
|
716
1087
|
});
|
|
717
1088
|
}
|
|
718
|
-
function RadioItem({ value, label, description, disabled: itemDisabled = false, className = "" }) {
|
|
1089
|
+
function RadioItem({ value, label, description, disabled: itemDisabled = false, className = "", ref }) {
|
|
719
1090
|
const { name, activeValue, select, disabled: groupDisabled, required } = useRadioGroupContext();
|
|
720
1091
|
const id = (0, react.useId)();
|
|
721
1092
|
const isDisabled = groupDisabled || itemDisabled;
|
|
722
1093
|
const isChecked = activeValue === value;
|
|
723
1094
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
724
1095
|
htmlFor: id,
|
|
725
|
-
className:
|
|
726
|
-
"group flex cursor-pointer items-start gap-3",
|
|
727
|
-
isDisabled ? "cursor-not-allowed opacity-50" : "",
|
|
728
|
-
className
|
|
729
|
-
].filter(Boolean).join(" "),
|
|
1096
|
+
className: cn("group flex cursor-pointer items-start gap-3", isDisabled && "cursor-not-allowed opacity-50", className),
|
|
730
1097
|
children: [
|
|
731
1098
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
732
1099
|
type: "radio",
|
|
1100
|
+
ref,
|
|
733
1101
|
id,
|
|
734
1102
|
name,
|
|
735
1103
|
value,
|
|
@@ -765,14 +1133,24 @@ function RadioItem({ value, label, description, disabled: itemDisabled = false,
|
|
|
765
1133
|
}
|
|
766
1134
|
//#endregion
|
|
767
1135
|
//#region src/ui-library/gallery/switch/Switch.tsx
|
|
768
|
-
function Switch({ checked, onChange, label, description, disabled = false, loading = false, id: externalId }) {
|
|
1136
|
+
function Switch({ checked: controlledChecked, defaultChecked = false, onChange, label, description, disabled = false, loading = false, id: externalId, className = "", ref }) {
|
|
1137
|
+
const [uncontrolled, setUncontrolled] = (0, react.useState)(defaultChecked);
|
|
1138
|
+
const isControlled = controlledChecked !== void 0;
|
|
1139
|
+
const checked = isControlled ? controlledChecked : uncontrolled;
|
|
769
1140
|
const generatedId = (0, react.useId)();
|
|
770
1141
|
const id = externalId ?? generatedId;
|
|
771
1142
|
const labelId = label ? `${id}-label` : void 0;
|
|
772
1143
|
const isInteractive = !disabled && !loading;
|
|
1144
|
+
const handleClick = () => {
|
|
1145
|
+
if (!isInteractive) return;
|
|
1146
|
+
const next = !checked;
|
|
1147
|
+
if (!isControlled) setUncontrolled(next);
|
|
1148
|
+
onChange?.(next);
|
|
1149
|
+
};
|
|
773
1150
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
774
|
-
className: "flex items-start gap-3",
|
|
1151
|
+
className: cn("flex items-start gap-3", className),
|
|
775
1152
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1153
|
+
ref,
|
|
776
1154
|
id,
|
|
777
1155
|
type: "button",
|
|
778
1156
|
role: "switch",
|
|
@@ -781,7 +1159,7 @@ function Switch({ checked, onChange, label, description, disabled = false, loadi
|
|
|
781
1159
|
"aria-label": !label ? "Toggle" : void 0,
|
|
782
1160
|
"aria-disabled": !isInteractive || void 0,
|
|
783
1161
|
disabled: !isInteractive,
|
|
784
|
-
onClick:
|
|
1162
|
+
onClick: handleClick,
|
|
785
1163
|
className: [
|
|
786
1164
|
"relative inline-flex h-6 w-11 shrink-0 items-center rounded-full border-2 border-transparent transition-colors duration-200",
|
|
787
1165
|
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30 focus-visible:ring-offset-1",
|
|
@@ -818,13 +1196,17 @@ function Switch({ checked, onChange, label, description, disabled = false, loadi
|
|
|
818
1196
|
*/
|
|
819
1197
|
var fieldInputClass = "w-full rounded-xl border border-slate-200 bg-slate-50 px-4 py-2.5 text-sm font-medium text-slate-900 outline-none transition placeholder:text-slate-400 focus:border-brand-blue/40 focus:ring-2 focus:ring-brand-blue/15 disabled:cursor-not-allowed disabled:opacity-60";
|
|
820
1198
|
var fieldInputErrorClass = "border-rose-300 focus:border-rose-400 focus:ring-rose-100";
|
|
821
|
-
function Combobox({ value, onChange, options, placeholder, className, disabled, loading, error, "aria-label": ariaLabel }) {
|
|
1199
|
+
function Combobox({ value: controlledValue, defaultValue = "", onChange, options, placeholder, className, disabled, loading, error, "aria-label": ariaLabel, onBlur, ref }) {
|
|
1200
|
+
const isControlled = controlledValue !== void 0;
|
|
1201
|
+
const [internalValue, setInternalValue] = (0, react.useState)(defaultValue);
|
|
1202
|
+
const value = isControlled ? controlledValue : internalValue;
|
|
822
1203
|
const [isOpen, setIsOpen] = (0, react.useState)(false);
|
|
823
1204
|
const [search, setSearch] = (0, react.useState)(value);
|
|
824
1205
|
const [highlightIndex, setHighlightIndex] = (0, react.useState)(-1);
|
|
825
1206
|
const inputRef = (0, react.useRef)(null);
|
|
826
1207
|
const listRef = (0, react.useRef)(null);
|
|
827
1208
|
const wrapperRef = (0, react.useRef)(null);
|
|
1209
|
+
const [panelStyle, setPanelStyle] = (0, react.useState)({});
|
|
828
1210
|
const uid = (0, react.useId)();
|
|
829
1211
|
const listboxId = `${uid}-listbox`;
|
|
830
1212
|
const optionId = (index) => `${uid}-option-${index}`;
|
|
@@ -841,11 +1223,23 @@ function Combobox({ value, onChange, options, placeholder, className, disabled,
|
|
|
841
1223
|
}, [value]);
|
|
842
1224
|
(0, react.useEffect)(() => {
|
|
843
1225
|
function handleClickOutside(e) {
|
|
844
|
-
|
|
1226
|
+
const target = e.target;
|
|
1227
|
+
if (wrapperRef.current && !wrapperRef.current.contains(target) && !(listRef.current && listRef.current.contains(target))) setIsOpen(false);
|
|
845
1228
|
}
|
|
846
1229
|
document.addEventListener("mousedown", handleClickOutside);
|
|
847
1230
|
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
848
1231
|
}, []);
|
|
1232
|
+
(0, react.useEffect)(() => {
|
|
1233
|
+
if (!isOpen || !wrapperRef.current) return;
|
|
1234
|
+
const rect = wrapperRef.current.getBoundingClientRect();
|
|
1235
|
+
setPanelStyle({
|
|
1236
|
+
position: "fixed",
|
|
1237
|
+
top: rect.bottom + 4,
|
|
1238
|
+
left: rect.left,
|
|
1239
|
+
zIndex: 9999,
|
|
1240
|
+
width: rect.width
|
|
1241
|
+
});
|
|
1242
|
+
}, [isOpen]);
|
|
849
1243
|
(0, react.useEffect)(() => {
|
|
850
1244
|
if (!isOpen) setHighlightIndex(-1);
|
|
851
1245
|
}, [isOpen]);
|
|
@@ -853,7 +1247,9 @@ function Combobox({ value, onChange, options, placeholder, className, disabled,
|
|
|
853
1247
|
if (highlightIndex >= 0 && listRef.current) listRef.current.children[highlightIndex]?.scrollIntoView({ block: "nearest" });
|
|
854
1248
|
}, [highlightIndex]);
|
|
855
1249
|
const selectOption = (opt) => {
|
|
856
|
-
|
|
1250
|
+
if (opt.disabled) return;
|
|
1251
|
+
if (!isControlled) setInternalValue(opt.value);
|
|
1252
|
+
onChange?.(opt.value);
|
|
857
1253
|
setSearch(opt.label);
|
|
858
1254
|
setIsOpen(false);
|
|
859
1255
|
inputRef.current?.focus();
|
|
@@ -861,37 +1257,60 @@ function Combobox({ value, onChange, options, placeholder, className, disabled,
|
|
|
861
1257
|
const handleInputChange = (e) => {
|
|
862
1258
|
const val = e.target.value;
|
|
863
1259
|
setSearch(val);
|
|
864
|
-
|
|
1260
|
+
if (!isControlled) setInternalValue(val);
|
|
865
1261
|
setIsOpen(true);
|
|
866
1262
|
setHighlightIndex(-1);
|
|
867
1263
|
};
|
|
868
1264
|
const handleKeyDown = (e) => {
|
|
1265
|
+
const nextEnabled = (from, dir) => {
|
|
1266
|
+
let idx = from;
|
|
1267
|
+
while (idx >= 0 && idx < displayed.length) {
|
|
1268
|
+
if (!displayed[idx]?.disabled) return idx;
|
|
1269
|
+
idx += dir;
|
|
1270
|
+
}
|
|
1271
|
+
return -1;
|
|
1272
|
+
};
|
|
869
1273
|
if (e.key === "ArrowDown") {
|
|
870
1274
|
e.preventDefault();
|
|
871
1275
|
if (!isOpen) {
|
|
872
1276
|
setIsOpen(true);
|
|
873
1277
|
return;
|
|
874
1278
|
}
|
|
875
|
-
setHighlightIndex((prev) =>
|
|
1279
|
+
setHighlightIndex((prev) => {
|
|
1280
|
+
const next = nextEnabled(prev + 1, 1);
|
|
1281
|
+
return next === -1 ? prev : next;
|
|
1282
|
+
});
|
|
876
1283
|
} else if (e.key === "ArrowUp") {
|
|
877
1284
|
e.preventDefault();
|
|
878
|
-
setHighlightIndex((prev) =>
|
|
1285
|
+
setHighlightIndex((prev) => {
|
|
1286
|
+
const next = nextEnabled(prev - 1, -1);
|
|
1287
|
+
return next === -1 ? prev : next;
|
|
1288
|
+
});
|
|
879
1289
|
} else if (e.key === "Enter") {
|
|
880
1290
|
e.preventDefault();
|
|
881
|
-
if (highlightIndex >= 0 && displayed[highlightIndex]) selectOption(displayed[highlightIndex]);
|
|
1291
|
+
if (highlightIndex >= 0 && displayed[highlightIndex] && !displayed[highlightIndex].disabled) selectOption(displayed[highlightIndex]);
|
|
882
1292
|
else if (isOpen) setIsOpen(false);
|
|
883
1293
|
} else if (e.key === "Escape") {
|
|
884
1294
|
e.preventDefault();
|
|
885
1295
|
setIsOpen(false);
|
|
886
1296
|
}
|
|
887
1297
|
};
|
|
1298
|
+
const handleBlur = () => {
|
|
1299
|
+
requestAnimationFrame(() => {
|
|
1300
|
+
const active = document.activeElement;
|
|
1301
|
+
if (!wrapperRef.current?.contains(active) && !listRef.current?.contains(active)) {
|
|
1302
|
+
setIsOpen(false);
|
|
1303
|
+
onBlur?.();
|
|
1304
|
+
}
|
|
1305
|
+
});
|
|
1306
|
+
};
|
|
888
1307
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
889
1308
|
ref: wrapperRef,
|
|
890
1309
|
className: "relative",
|
|
891
1310
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
892
1311
|
className: "relative",
|
|
893
1312
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
894
|
-
ref: inputRef,
|
|
1313
|
+
ref: composeRefs(inputRef, ref),
|
|
895
1314
|
type: "text",
|
|
896
1315
|
role: "combobox",
|
|
897
1316
|
"aria-expanded": isOpen,
|
|
@@ -903,20 +1322,22 @@ function Combobox({ value, onChange, options, placeholder, className, disabled,
|
|
|
903
1322
|
value: search,
|
|
904
1323
|
onChange: handleInputChange,
|
|
905
1324
|
onFocus: () => setIsOpen(true),
|
|
1325
|
+
onBlur: handleBlur,
|
|
906
1326
|
onKeyDown: handleKeyDown,
|
|
907
1327
|
placeholder,
|
|
908
1328
|
disabled,
|
|
909
|
-
className:
|
|
1329
|
+
className: cn(fieldInputClass, "pr-8", error && fieldInputErrorClass, className)
|
|
910
1330
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronDown, {
|
|
911
1331
|
size: 16,
|
|
912
1332
|
"aria-hidden": "true",
|
|
913
1333
|
className: `absolute right-3 top-1/2 -translate-y-1/2 text-slate-300 pointer-events-none transition-transform ${isOpen ? "rotate-180" : ""}`
|
|
914
1334
|
})]
|
|
915
|
-
}), isOpen && !disabled && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1335
|
+
}), isOpen && !disabled && (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
916
1336
|
ref: listRef,
|
|
917
1337
|
id: listboxId,
|
|
918
1338
|
role: "listbox",
|
|
919
|
-
|
|
1339
|
+
style: panelStyle,
|
|
1340
|
+
className: "bg-white border border-slate-200 rounded-xl shadow-lg max-h-60 overflow-y-auto",
|
|
920
1341
|
children: error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
921
1342
|
className: "px-3 py-2 text-sm text-rose-500 font-semibold",
|
|
922
1343
|
children: error
|
|
@@ -930,11 +1351,12 @@ function Combobox({ value, onChange, options, placeholder, className, disabled,
|
|
|
930
1351
|
id: optionId(i),
|
|
931
1352
|
role: "option",
|
|
932
1353
|
"aria-selected": opt.value === value,
|
|
1354
|
+
"aria-disabled": opt.disabled,
|
|
933
1355
|
onMouseDown: (e) => {
|
|
934
1356
|
e.preventDefault();
|
|
935
|
-
selectOption(opt);
|
|
1357
|
+
if (!opt.disabled) selectOption(opt);
|
|
936
1358
|
},
|
|
937
|
-
className: `px-3 py-2 text-sm cursor-
|
|
1359
|
+
className: `px-3 py-2 text-sm transition-colors ${opt.disabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer"} ${i === highlightIndex && !opt.disabled ? "bg-blue-50 text-brand-blue font-semibold" : opt.disabled ? "text-slate-400" : "text-slate-700 hover:bg-slate-50"} ${opt.value === value ? "font-medium" : ""}`,
|
|
938
1360
|
children: opt.label
|
|
939
1361
|
}, opt.value)), filtered.length > DISPLAY_LIMIT && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
940
1362
|
className: "px-3 py-2 text-xs text-slate-400 font-semibold text-center border-t border-slate-100",
|
|
@@ -944,7 +1366,7 @@ function Combobox({ value, onChange, options, placeholder, className, disabled,
|
|
|
944
1366
|
" more results — refine your search"
|
|
945
1367
|
]
|
|
946
1368
|
})] })
|
|
947
|
-
})]
|
|
1369
|
+
}), document.body)]
|
|
948
1370
|
});
|
|
949
1371
|
}
|
|
950
1372
|
//#endregion
|
|
@@ -956,10 +1378,9 @@ function Combobox({ value, onChange, options, placeholder, className, disabled,
|
|
|
956
1378
|
* Uses react-calendar and date-fns.
|
|
957
1379
|
* Note: CSS for .pis-datepicker-popover and .pis-rc-calendar is in index.css.
|
|
958
1380
|
*/
|
|
959
|
-
var MAX_DATE = new Date(2026, 11, 31);
|
|
960
1381
|
var POPOVER_WIDTH = 352;
|
|
961
1382
|
var CALENDAR_HEIGHT = 360;
|
|
962
|
-
function DatePicker({ value, onChange, placeholder, className, disabled, error, minDate, id, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, required }) {
|
|
1383
|
+
function DatePicker({ value, onChange, placeholder, className, disabled, error, minDate, maxDate, id, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, required }) {
|
|
963
1384
|
const [isOpen, setIsOpen] = (0, react.useState)(false);
|
|
964
1385
|
const [popupStyle, setPopupStyle] = (0, react.useState)({});
|
|
965
1386
|
const wrapperRef = (0, react.useRef)(null);
|
|
@@ -1040,12 +1461,7 @@ function DatePicker({ value, onChange, placeholder, className, disabled, error,
|
|
|
1040
1461
|
else openPopup();
|
|
1041
1462
|
}
|
|
1042
1463
|
}
|
|
1043
|
-
const inputClasses =
|
|
1044
|
-
"w-full rounded-xl border bg-slate-50 px-4 py-2.5 text-sm font-medium text-slate-900 outline-none transition placeholder:text-slate-400 pr-8 cursor-pointer select-none",
|
|
1045
|
-
error ? "border-rose-300 focus:border-rose-400 focus:ring-2 focus:ring-rose-100" : "border-slate-200 focus:border-brand-blue/40 focus:ring-2 focus:ring-brand-blue/15",
|
|
1046
|
-
disabled ? "cursor-not-allowed opacity-60" : "",
|
|
1047
|
-
className ?? ""
|
|
1048
|
-
].filter(Boolean).join(" ");
|
|
1464
|
+
const inputClasses = cn("w-full rounded-xl border bg-slate-50 px-4 py-2.5 text-sm font-medium text-slate-900 outline-none transition placeholder:text-slate-400 pr-8 cursor-pointer select-none", error ? "border-rose-300 focus:border-rose-400 focus:ring-2 focus:ring-rose-100" : "border-slate-200 focus:border-brand-blue/40 focus:ring-2 focus:ring-brand-blue/15", disabled && "cursor-not-allowed opacity-60", className);
|
|
1049
1465
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1050
1466
|
ref: wrapperRef,
|
|
1051
1467
|
className: "relative",
|
|
@@ -1094,7 +1510,7 @@ function DatePicker({ value, onChange, placeholder, className, disabled, error,
|
|
|
1094
1510
|
},
|
|
1095
1511
|
defaultActiveStartDate: selectedDate ?? minDate,
|
|
1096
1512
|
minDate,
|
|
1097
|
-
maxDate
|
|
1513
|
+
maxDate,
|
|
1098
1514
|
defaultView: "month",
|
|
1099
1515
|
showNeighboringDecade: true,
|
|
1100
1516
|
showNeighboringCentury: true,
|
|
@@ -1183,7 +1599,7 @@ function OtpInput({ length = 6, value, onChange, disabled = false, error = false
|
|
|
1183
1599
|
].join(" ");
|
|
1184
1600
|
const cellClass = error ? `${cellBase} border-rose-400 bg-rose-50/40 text-rose-800 focus:ring-rose-100` : `${cellBase} border-slate-200 bg-white text-slate-900`;
|
|
1185
1601
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1186
|
-
className:
|
|
1602
|
+
className: cn("space-y-2", className),
|
|
1187
1603
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1188
1604
|
role: "group",
|
|
1189
1605
|
"aria-label": ariaLabel,
|
|
@@ -1231,14 +1647,20 @@ function OtpInput({ length = 6, value, onChange, disabled = false, error = false
|
|
|
1231
1647
|
* - Keyboard: Tab to focus the input; Enter/Space to open the picker.
|
|
1232
1648
|
* - Disabled: input has disabled attr; zone has aria-disabled="true".
|
|
1233
1649
|
*/
|
|
1234
|
-
function FileUpload({ onFiles, accept, multiple = false, disabled = false, "aria-label": ariaLabel = "Upload file", className = "" }) {
|
|
1650
|
+
function FileUpload({ onFiles, accept, multiple = false, disabled = false, "aria-label": ariaLabel = "Upload file", className = "", value, onChange, ref }) {
|
|
1235
1651
|
const inputRef = (0, react.useRef)(null);
|
|
1236
1652
|
const [dragging, setDragging] = (0, react.useState)(false);
|
|
1237
|
-
const [
|
|
1653
|
+
const [internalFiles, setInternalFiles] = (0, react.useState)([]);
|
|
1654
|
+
const isControlled = value !== void 0;
|
|
1655
|
+
const selected = isControlled ? value : internalFiles;
|
|
1656
|
+
const setFiles = (next) => {
|
|
1657
|
+
if (!isControlled) setInternalFiles(next);
|
|
1658
|
+
onChange?.(next);
|
|
1659
|
+
};
|
|
1238
1660
|
const handleFiles = (files) => {
|
|
1239
1661
|
if (!files || files.length === 0) return;
|
|
1240
1662
|
const arr = Array.from(files);
|
|
1241
|
-
|
|
1663
|
+
setFiles(multiple ? arr : [arr[0]]);
|
|
1242
1664
|
onFiles?.(multiple ? arr : [arr[0]]);
|
|
1243
1665
|
};
|
|
1244
1666
|
const handleChange = (e) => {
|
|
@@ -1257,15 +1679,9 @@ function FileUpload({ onFiles, accept, multiple = false, disabled = false, "aria
|
|
|
1257
1679
|
};
|
|
1258
1680
|
const handleDragLeave = () => setDragging(false);
|
|
1259
1681
|
const removeFile = (i) => {
|
|
1260
|
-
|
|
1682
|
+
setFiles(selected.filter((_, idx) => idx !== i));
|
|
1261
1683
|
};
|
|
1262
|
-
const zoneClass = [
|
|
1263
|
-
"flex flex-col items-center justify-center gap-3",
|
|
1264
|
-
"rounded-2xl border-2 border-dashed p-8 text-center cursor-pointer",
|
|
1265
|
-
"transition-colors focus-within:outline-none focus-within:ring-2 focus-within:ring-brand-blue/20",
|
|
1266
|
-
dragging ? "border-brand-blue/60 bg-blue-50" : disabled ? "border-slate-100 bg-slate-50/40 cursor-not-allowed" : "border-slate-200 bg-white hover:border-brand-sky/50 hover:bg-[#eef2f8]",
|
|
1267
|
-
className
|
|
1268
|
-
].filter(Boolean).join(" ");
|
|
1684
|
+
const zoneClass = cn("flex flex-col items-center justify-center gap-3", "rounded-2xl border-2 border-dashed p-8 text-center cursor-pointer", "transition-colors focus-within:outline-none focus-within:ring-2 focus-within:ring-brand-blue/20", dragging ? "border-brand-blue/60 bg-blue-50" : disabled ? "border-slate-100 bg-slate-50/40 cursor-not-allowed" : "border-slate-200 bg-white hover:border-brand-sky/50 hover:bg-[#eef2f8]", className);
|
|
1269
1685
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1270
1686
|
className: "space-y-3",
|
|
1271
1687
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
@@ -1276,7 +1692,7 @@ function FileUpload({ onFiles, accept, multiple = false, disabled = false, "aria
|
|
|
1276
1692
|
"aria-disabled": disabled,
|
|
1277
1693
|
children: [
|
|
1278
1694
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1279
|
-
ref: inputRef,
|
|
1695
|
+
ref: composeRefs(inputRef, ref),
|
|
1280
1696
|
type: "file",
|
|
1281
1697
|
accept,
|
|
1282
1698
|
multiple,
|
|
@@ -1350,13 +1766,7 @@ function FileUpload({ onFiles, accept, multiple = false, disabled = false, "aria
|
|
|
1350
1766
|
*/
|
|
1351
1767
|
function FilterChip({ label, onRemove, disabled = false, removeLabel = "Remove filter", className = "" }) {
|
|
1352
1768
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1353
|
-
className: [
|
|
1354
|
-
"inline-flex max-w-[20rem] items-center gap-1.5 rounded-full border",
|
|
1355
|
-
"py-1 pl-3 text-xs font-semibold",
|
|
1356
|
-
onRemove ? "pr-1.5" : "pr-3",
|
|
1357
|
-
disabled ? "border-slate-200 bg-slate-50 text-slate-400" : "border-brand-blue/30 bg-blue-50 text-brand-blue",
|
|
1358
|
-
className
|
|
1359
|
-
].filter(Boolean).join(" "),
|
|
1769
|
+
className: cn("inline-flex max-w-[20rem] items-center gap-1.5 rounded-full border", "py-1 pl-3 text-xs font-semibold", onRemove ? "pr-1.5" : "pr-3", disabled ? "border-slate-200 bg-slate-50 text-slate-400" : "border-brand-blue/30 bg-blue-50 text-brand-blue", className),
|
|
1360
1770
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1361
1771
|
className: "truncate",
|
|
1362
1772
|
children: label
|
|
@@ -1365,11 +1775,7 @@ function FilterChip({ label, onRemove, disabled = false, removeLabel = "Remove f
|
|
|
1365
1775
|
onClick: !disabled ? onRemove : void 0,
|
|
1366
1776
|
disabled,
|
|
1367
1777
|
"aria-label": removeLabel,
|
|
1368
|
-
className:
|
|
1369
|
-
"flex h-4 w-4 shrink-0 items-center justify-center rounded-full transition",
|
|
1370
|
-
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30",
|
|
1371
|
-
disabled ? "cursor-not-allowed text-slate-300" : "text-brand-blue hover:bg-blue-100 hover:text-brand-navy"
|
|
1372
|
-
].filter(Boolean).join(" "),
|
|
1778
|
+
className: cn("flex h-4 w-4 shrink-0 items-center justify-center rounded-full transition", "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30", disabled ? "cursor-not-allowed text-slate-300" : "text-brand-blue hover:bg-blue-100 hover:text-brand-navy"),
|
|
1373
1779
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.X, {
|
|
1374
1780
|
size: 10,
|
|
1375
1781
|
"aria-hidden": "true",
|
|
@@ -1379,6 +1785,52 @@ function FilterChip({ label, onRemove, disabled = false, removeLabel = "Remove f
|
|
|
1379
1785
|
});
|
|
1380
1786
|
}
|
|
1381
1787
|
//#endregion
|
|
1788
|
+
//#region src/ui-library/gallery/search/Search.tsx
|
|
1789
|
+
/**
|
|
1790
|
+
* Search — Design System Component
|
|
1791
|
+
*
|
|
1792
|
+
* A search input with a built-in search icon prefix and optional clear button.
|
|
1793
|
+
* Built for filtering and discovery use cases — not for form submission fields
|
|
1794
|
+
* (use Input for those).
|
|
1795
|
+
*
|
|
1796
|
+
* Features:
|
|
1797
|
+
* - Search icon in prefix by default
|
|
1798
|
+
* - Optional × clear button when value is non-empty (pass onClear)
|
|
1799
|
+
* - Controlled via value + onChange (standard input event)
|
|
1800
|
+
* - Forwards ref to the underlying <input>
|
|
1801
|
+
* - All native <input> attributes are forwarded
|
|
1802
|
+
*/
|
|
1803
|
+
function Search({ value, onChange, onClear, placeholder = "Search…", disabled = false, className = "", ref, ...props }) {
|
|
1804
|
+
const hasValue = value !== void 0 && value !== "";
|
|
1805
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1806
|
+
className: cn("flex w-full items-center rounded-xl border bg-slate-50 transition-all", "border-slate-200 focus-within:border-brand-blue/50 focus-within:bg-white focus-within:ring-2 focus-within:ring-brand-blue/15", disabled && "cursor-not-allowed opacity-60", className),
|
|
1807
|
+
children: [
|
|
1808
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1809
|
+
className: "flex shrink-0 items-center pl-3.5 text-slate-400",
|
|
1810
|
+
"aria-hidden": "true",
|
|
1811
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Search, { size: 15 })
|
|
1812
|
+
}),
|
|
1813
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1814
|
+
ref,
|
|
1815
|
+
type: "search",
|
|
1816
|
+
value,
|
|
1817
|
+
onChange,
|
|
1818
|
+
placeholder,
|
|
1819
|
+
disabled,
|
|
1820
|
+
className: "w-full bg-transparent py-2.5 pl-2.5 pr-3 text-sm font-medium text-slate-900 outline-none placeholder:text-slate-400 disabled:cursor-not-allowed [&::-webkit-search-cancel-button]:hidden [&::-webkit-search-decoration]:hidden",
|
|
1821
|
+
...props
|
|
1822
|
+
}),
|
|
1823
|
+
onClear && hasValue && !disabled && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1824
|
+
type: "button",
|
|
1825
|
+
onClick: onClear,
|
|
1826
|
+
"aria-label": "Clear search",
|
|
1827
|
+
className: "flex shrink-0 items-center pr-3 text-slate-400 transition-colors hover:text-slate-600",
|
|
1828
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.X, { size: 14 })
|
|
1829
|
+
})
|
|
1830
|
+
]
|
|
1831
|
+
});
|
|
1832
|
+
}
|
|
1833
|
+
//#endregion
|
|
1382
1834
|
//#region src/ui-library/gallery/segmented-control/SegmentedControl.tsx
|
|
1383
1835
|
/**
|
|
1384
1836
|
* SegmentedControl — Design System Component
|
|
@@ -1446,11 +1898,7 @@ function SegmentedControl({ options, defaultValue, value: controlledValue, onVal
|
|
|
1446
1898
|
"aria-labelledby": ariaLabelledby,
|
|
1447
1899
|
"aria-disabled": disabled || void 0,
|
|
1448
1900
|
onKeyDown: handleKeyDown,
|
|
1449
|
-
className:
|
|
1450
|
-
"inline-flex rounded-xl bg-slate-100 p-1",
|
|
1451
|
-
disabled ? "opacity-50 pointer-events-none" : "",
|
|
1452
|
-
className
|
|
1453
|
-
].filter(Boolean).join(" "),
|
|
1901
|
+
className: cn("inline-flex rounded-xl bg-slate-100 p-1", disabled && "opacity-50 pointer-events-none", className),
|
|
1454
1902
|
children: options.map((opt) => {
|
|
1455
1903
|
const isSelected = opt.value === activeValue;
|
|
1456
1904
|
const isDisabled = disabled || opt.disabled;
|
|
@@ -1475,6 +1923,33 @@ function SegmentedControl({ options, defaultValue, value: controlledValue, onVal
|
|
|
1475
1923
|
}
|
|
1476
1924
|
//#endregion
|
|
1477
1925
|
//#region src/ui-library/gallery/validation-summary/ValidationSummary.tsx
|
|
1926
|
+
/**
|
|
1927
|
+
* ValidationSummary — Design System Component
|
|
1928
|
+
*
|
|
1929
|
+
* Renders a list of validation errors or warnings before a form is submitted.
|
|
1930
|
+
* Matches the production implementation in shared/components/ValidationSummary.tsx.
|
|
1931
|
+
*
|
|
1932
|
+
* Accessibility:
|
|
1933
|
+
* - role="alert" causes immediate announcement when injected into the DOM.
|
|
1934
|
+
* - <ul> with <li> items provides proper list semantics.
|
|
1935
|
+
* - The heading is a <div role="heading" aria-level="3"> to avoid disrupting
|
|
1936
|
+
* page outline; callers may override via headingLevel.
|
|
1937
|
+
* - Each issue is identified by field name + message.
|
|
1938
|
+
*
|
|
1939
|
+
* Usage:
|
|
1940
|
+
* const [issues, setIssues] = useState<ValidationIssue[]>([]);
|
|
1941
|
+
* // ... after validation:
|
|
1942
|
+
* setIssues([{ field: 'Employee ID', message: 'is required' }]);
|
|
1943
|
+
* // Render:
|
|
1944
|
+
* <ValidationSummary issues={issues} />
|
|
1945
|
+
*
|
|
1946
|
+
* // With scroll-to-field (wizard steps):
|
|
1947
|
+
* <ValidationSummary
|
|
1948
|
+
* issues={issues}
|
|
1949
|
+
* fieldIds={{ 'Employee ID': 'employee-id-input', 'Position': 'position-select' }}
|
|
1950
|
+
* fieldLabels={{ 'Employee ID': 'Employee ID', 'Position': 'Position Title' }}
|
|
1951
|
+
* />
|
|
1952
|
+
*/
|
|
1478
1953
|
function scrollToField(domId) {
|
|
1479
1954
|
const el = document.getElementById(domId);
|
|
1480
1955
|
if (!el) return;
|
|
@@ -1489,7 +1964,7 @@ function ValidationSummary({ issues, heading = "Review these fields before savin
|
|
|
1489
1964
|
if (issues.length === 0) return null;
|
|
1490
1965
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1491
1966
|
role: "alert",
|
|
1492
|
-
className:
|
|
1967
|
+
className: cn("rounded-xl border border-amber-200 bg-amber-50 px-4 py-3 text-sm text-amber-900", className),
|
|
1493
1968
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1494
1969
|
className: "font-bold",
|
|
1495
1970
|
children: heading
|
|
@@ -1553,7 +2028,7 @@ function Alert({ tone, title, children, icon = true, onDismiss, subtle = false,
|
|
|
1553
2028
|
const colorClass = subtle ? TONE_SUBTLE[tone] : TONE_FULL[tone];
|
|
1554
2029
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1555
2030
|
role: "alert",
|
|
1556
|
-
className:
|
|
2031
|
+
className: cn("flex gap-3 rounded-md pl-4 pr-4 py-3.5", colorClass, className),
|
|
1557
2032
|
children: [
|
|
1558
2033
|
icon && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
1559
2034
|
size: 20,
|
|
@@ -1594,12 +2069,33 @@ function ErrorBanner({ message, tone = "error", className = "" }) {
|
|
|
1594
2069
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1595
2070
|
role: tone === "error" ? "alert" : void 0,
|
|
1596
2071
|
"aria-live": tone !== "error" ? "polite" : void 0,
|
|
1597
|
-
className:
|
|
2072
|
+
className: cn("rounded-2xl border px-5 py-4 text-sm font-bold", STYLES[tone], className),
|
|
1598
2073
|
children: message
|
|
1599
2074
|
});
|
|
1600
2075
|
}
|
|
1601
2076
|
//#endregion
|
|
1602
2077
|
//#region src/ui-library/gallery/progress/Progress.tsx
|
|
2078
|
+
/**
|
|
2079
|
+
* Progress — Design System Component
|
|
2080
|
+
*
|
|
2081
|
+
* Determinate and indeterminate linear progress bar.
|
|
2082
|
+
*
|
|
2083
|
+
* Accessibility:
|
|
2084
|
+
* - role="progressbar"
|
|
2085
|
+
* - aria-valuenow, aria-valuemin, aria-valuemax (determinate)
|
|
2086
|
+
* - aria-label for screen-reader context
|
|
2087
|
+
* - When indeterminate: aria-valuenow is omitted
|
|
2088
|
+
*
|
|
2089
|
+
* Usage:
|
|
2090
|
+
* // Determinate (0–100)
|
|
2091
|
+
* <Progress value={65} aria-label="Upload progress" />
|
|
2092
|
+
*
|
|
2093
|
+
* // Indeterminate
|
|
2094
|
+
* <Progress aria-label="Loading" />
|
|
2095
|
+
*
|
|
2096
|
+
* // Custom max
|
|
2097
|
+
* <Progress value={3} max={5} aria-label="Step 3 of 5" />
|
|
2098
|
+
*/
|
|
1603
2099
|
function Progress({ value, max = 100, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledby, className = "" }) {
|
|
1604
2100
|
const isIndeterminate = value === void 0;
|
|
1605
2101
|
const clamped = isIndeterminate ? 0 : Math.min(Math.max(value, 0), max);
|
|
@@ -1611,7 +2107,7 @@ function Progress({ value, max = 100, "aria-label": ariaLabel, "aria-labelledby"
|
|
|
1611
2107
|
"aria-valuemax": max,
|
|
1612
2108
|
"aria-label": ariaLabel,
|
|
1613
2109
|
"aria-labelledby": ariaLabelledby,
|
|
1614
|
-
className:
|
|
2110
|
+
className: cn("h-2 w-full overflow-hidden rounded-full bg-slate-200", className),
|
|
1615
2111
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1616
2112
|
className: ["h-full rounded-full bg-brand-blue transition-[width] duration-300", isIndeterminate ? "w-1/3 motion-safe:animate-pulse opacity-75" : ""].filter(Boolean).join(" "),
|
|
1617
2113
|
style: isIndeterminate ? void 0 : { width: `${percent}%` },
|
|
@@ -1621,17 +2117,20 @@ function Progress({ value, max = 100, "aria-label": ariaLabel, "aria-labelledby"
|
|
|
1621
2117
|
}
|
|
1622
2118
|
//#endregion
|
|
1623
2119
|
//#region src/ui-library/gallery/skeleton/Skeleton.tsx
|
|
1624
|
-
function Skeleton({ className = "" }) {
|
|
2120
|
+
function Skeleton({ className = "", variant = "pulse" }) {
|
|
1625
2121
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1626
2122
|
"aria-hidden": "true",
|
|
1627
|
-
className:
|
|
2123
|
+
className: cn("rounded-lg", variant === "shimmer" ? "skeleton-shimmer" : "animate-pulse bg-slate-200", className)
|
|
1628
2124
|
});
|
|
1629
2125
|
}
|
|
1630
|
-
function SkeletonText({ lines = 3, className = "" }) {
|
|
2126
|
+
function SkeletonText({ lines = 3, className = "", variant = "pulse" }) {
|
|
1631
2127
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1632
|
-
className:
|
|
2128
|
+
className: cn("space-y-2", className),
|
|
1633
2129
|
"aria-hidden": "true",
|
|
1634
|
-
children: Array.from({ length: lines }, (_, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Skeleton, {
|
|
2130
|
+
children: Array.from({ length: lines }, (_, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Skeleton, {
|
|
2131
|
+
variant,
|
|
2132
|
+
className: `h-3.5 ${i === lines - 1 && lines > 1 ? "w-2/3" : "w-full"}`
|
|
2133
|
+
}, i))
|
|
1635
2134
|
});
|
|
1636
2135
|
}
|
|
1637
2136
|
var AVATAR_SIZE = {
|
|
@@ -1639,17 +2138,23 @@ var AVATAR_SIZE = {
|
|
|
1639
2138
|
md: "h-10 w-10",
|
|
1640
2139
|
lg: "h-14 w-14"
|
|
1641
2140
|
};
|
|
1642
|
-
function SkeletonAvatar({ size = "md", className = "" }) {
|
|
2141
|
+
function SkeletonAvatar({ size = "md", className = "", variant = "pulse" }) {
|
|
1643
2142
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1644
2143
|
"aria-hidden": "true",
|
|
1645
|
-
className:
|
|
2144
|
+
className: cn("rounded-full shrink-0", variant === "shimmer" ? "skeleton-shimmer" : "animate-pulse bg-slate-200", AVATAR_SIZE[size], className)
|
|
1646
2145
|
});
|
|
1647
2146
|
}
|
|
1648
|
-
function SkeletonCard({ className = "", children }) {
|
|
2147
|
+
function SkeletonCard({ className = "", children, variant = "pulse" }) {
|
|
1649
2148
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1650
|
-
className:
|
|
2149
|
+
className: cn("rounded-2xl border border-slate-100 bg-white p-5 space-y-3", className),
|
|
1651
2150
|
"aria-hidden": "true",
|
|
1652
|
-
children: children ?? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Skeleton, {
|
|
2151
|
+
children: children ?? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Skeleton, {
|
|
2152
|
+
variant,
|
|
2153
|
+
className: "h-4 w-1/3"
|
|
2154
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SkeletonText, {
|
|
2155
|
+
variant,
|
|
2156
|
+
lines: 2
|
|
2157
|
+
})] })
|
|
1653
2158
|
});
|
|
1654
2159
|
}
|
|
1655
2160
|
function SkeletonTableRow({ rows = 5, columns = [
|
|
@@ -1657,12 +2162,15 @@ function SkeletonTableRow({ rows = 5, columns = [
|
|
|
1657
2162
|
"w-24",
|
|
1658
2163
|
"w-20",
|
|
1659
2164
|
"w-28"
|
|
1660
|
-
] }) {
|
|
2165
|
+
], variant = "pulse" }) {
|
|
1661
2166
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: Array.from({ length: rows }, (_, r) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tr", {
|
|
1662
2167
|
"aria-hidden": "true",
|
|
1663
2168
|
children: columns.map((width, c) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
|
|
1664
2169
|
className: "px-5 py-3.5",
|
|
1665
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Skeleton, {
|
|
2170
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Skeleton, {
|
|
2171
|
+
variant,
|
|
2172
|
+
className: `h-3.5 ${width}`
|
|
2173
|
+
})
|
|
1666
2174
|
}, c))
|
|
1667
2175
|
}, r)) });
|
|
1668
2176
|
}
|
|
@@ -1678,12 +2186,7 @@ var TONE_STYLES = {
|
|
|
1678
2186
|
};
|
|
1679
2187
|
function StatusBadge({ tone = "neutral", children, className = "" }) {
|
|
1680
2188
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1681
|
-
className: [
|
|
1682
|
-
"inline-flex items-center rounded-lg border px-2.5 py-1",
|
|
1683
|
-
"text-[10px] font-bold uppercase tracking-wider",
|
|
1684
|
-
TONE_STYLES[tone],
|
|
1685
|
-
className
|
|
1686
|
-
].filter(Boolean).join(" "),
|
|
2189
|
+
className: cn("inline-flex items-center rounded-lg border px-2.5 py-1", "text-[10px] font-bold uppercase tracking-wider", TONE_STYLES[tone], className),
|
|
1687
2190
|
children
|
|
1688
2191
|
});
|
|
1689
2192
|
}
|
|
@@ -1760,7 +2263,7 @@ var typography = {
|
|
|
1760
2263
|
* - Close button styling
|
|
1761
2264
|
* - Danger tone (rose badge, rose title, rose submit button)
|
|
1762
2265
|
*/
|
|
1763
|
-
function Modal({ isOpen, onClose, title, children, icon, subtitle, headerMeta, footer, onSubmit, submitLabel = "Submit", submittingLabel = "Saving...", submitting = false, danger = false, error, maxWidth = "max-w-xl", closeLabel = "Close" }) {
|
|
2266
|
+
function Modal({ isOpen, onClose, title, children, icon, subtitle, headerMeta, footer, onSubmit, submitLabel = "Submit", submittingLabel = "Saving...", submitting = false, danger = false, error, maxWidth = "max-w-xl", closeLabel = "Close", preventClose = false, onOpenAutoFocus, onCloseAutoFocus, onEscapeKeyDown, onInteractOutside }) {
|
|
1764
2267
|
const open = isOpen !== false;
|
|
1765
2268
|
const defaultFooter = onSubmit ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1766
2269
|
className: "flex items-center justify-end gap-3",
|
|
@@ -1793,13 +2296,17 @@ function Modal({ isOpen, onClose, title, children, icon, subtitle, headerMeta, f
|
|
|
1793
2296
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_radix_ui_react_dialog.Root, {
|
|
1794
2297
|
open,
|
|
1795
2298
|
onOpenChange: (isOpen) => {
|
|
1796
|
-
if (!isOpen) onClose();
|
|
2299
|
+
if (!isOpen && !preventClose) onClose();
|
|
1797
2300
|
},
|
|
1798
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_radix_ui_react_dialog.Portal, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_radix_ui_react_dialog.Overlay, { className: "fixed inset-0 z-50 bg-brand-navy/50 backdrop-blur-sm" }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_radix_ui_react_dialog.Content, {
|
|
1799
|
-
className: cn("fixed left-1/2 top-1/2 z-50 -translate-x-1/2 -translate-y-1/2", "flex w-full flex-col", "rounded-lg border border-slate-200 bg-white shadow-xl", "max-h-[85vh] outline-none", "
|
|
2301
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_radix_ui_react_dialog.Portal, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_radix_ui_react_dialog.Overlay, { className: "fixed inset-0 z-50 bg-brand-navy/50 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=open]:fade-in-0 data-[state=closed]:fade-out-0 duration-200" }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_radix_ui_react_dialog.Content, {
|
|
2302
|
+
className: cn("fixed left-1/2 top-1/2 z-50 -translate-x-1/2 -translate-y-1/2", "flex w-full flex-col", "rounded-lg border border-slate-200 bg-white shadow-xl", "max-h-[85vh] outline-none", maxWidth, "data-[state=open]:animate-in data-[state=closed]:animate-out", "data-[state=open]:fade-in-0 data-[state=closed]:fade-out-0", "data-[state=open]:zoom-in-95 data-[state=closed]:zoom-out-95", "duration-200"),
|
|
1800
2303
|
style: { padding: 0 },
|
|
2304
|
+
onOpenAutoFocus,
|
|
2305
|
+
onCloseAutoFocus,
|
|
2306
|
+
onEscapeKeyDown: preventClose ? (e) => e.preventDefault() : onEscapeKeyDown,
|
|
2307
|
+
onInteractOutside: preventClose ? (e) => e.preventDefault() : onInteractOutside,
|
|
1801
2308
|
children: [
|
|
1802
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_radix_ui_react_dialog.Close, {
|
|
2309
|
+
!preventClose && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_radix_ui_react_dialog.Close, {
|
|
1803
2310
|
asChild: true,
|
|
1804
2311
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1805
2312
|
type: "button",
|
|
@@ -1890,17 +2397,17 @@ var SIZE_CLASS = {
|
|
|
1890
2397
|
};
|
|
1891
2398
|
function DialogBody({ children, className = "" }) {
|
|
1892
2399
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1893
|
-
className:
|
|
2400
|
+
className: cn("min-h-0 overflow-y-auto overscroll-contain px-6 py-4", className),
|
|
1894
2401
|
children
|
|
1895
2402
|
});
|
|
1896
2403
|
}
|
|
1897
2404
|
function DialogFooter({ children, className = "" }) {
|
|
1898
2405
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1899
|
-
className:
|
|
2406
|
+
className: cn("flex items-center justify-end gap-3 border-t border-slate-100 px-6 py-4", className),
|
|
1900
2407
|
children
|
|
1901
2408
|
});
|
|
1902
2409
|
}
|
|
1903
|
-
function Dialog({ open, onClose, title, description, preventClose = false, size = "md", children }) {
|
|
2410
|
+
function Dialog({ open, onClose, title, description, preventClose = false, size = "md", children, onOpenAutoFocus, onCloseAutoFocus, onEscapeKeyDown, onInteractOutside }) {
|
|
1904
2411
|
const hasTitle = Boolean(title);
|
|
1905
2412
|
const hasDesc = Boolean(description);
|
|
1906
2413
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_radix_ui_react_dialog.Root, {
|
|
@@ -1908,10 +2415,12 @@ function Dialog({ open, onClose, title, description, preventClose = false, size
|
|
|
1908
2415
|
onOpenChange: (isOpen) => {
|
|
1909
2416
|
if (!isOpen && !preventClose) onClose();
|
|
1910
2417
|
},
|
|
1911
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_radix_ui_react_dialog.Portal, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_radix_ui_react_dialog.Overlay, { className: "fixed inset-0 z-50 bg-brand-navy/50 backdrop-blur-sm" }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_radix_ui_react_dialog.Content, {
|
|
1912
|
-
className: cn("fixed left-1/2 top-1/2 z-50 -translate-x-1/2 -translate-y-1/2", "flex w-full flex-col overflow-hidden", "rounded-lg bg-white shadow-xl", "max-h-[90vh] outline-none", "
|
|
1913
|
-
onEscapeKeyDown: preventClose ? (e) => e.preventDefault() :
|
|
1914
|
-
onInteractOutside: preventClose ? (e) => e.preventDefault() :
|
|
2418
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_radix_ui_react_dialog.Portal, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_radix_ui_react_dialog.Overlay, { className: "fixed inset-0 z-50 bg-brand-navy/50 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=open]:fade-in-0 data-[state=closed]:fade-out-0 duration-200" }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_radix_ui_react_dialog.Content, {
|
|
2419
|
+
className: cn("fixed left-1/2 top-1/2 z-50 -translate-x-1/2 -translate-y-1/2", "flex w-full flex-col overflow-hidden", "rounded-lg bg-white shadow-xl", "max-h-[90vh] outline-none", SIZE_CLASS[size], "data-[state=open]:animate-in data-[state=closed]:animate-out", "data-[state=open]:fade-in-0 data-[state=closed]:fade-out-0", "data-[state=open]:zoom-in-95 data-[state=closed]:zoom-out-95", "duration-200"),
|
|
2420
|
+
onEscapeKeyDown: preventClose ? (e) => e.preventDefault() : onEscapeKeyDown,
|
|
2421
|
+
onInteractOutside: preventClose ? (e) => e.preventDefault() : onInteractOutside,
|
|
2422
|
+
onOpenAutoFocus,
|
|
2423
|
+
onCloseAutoFocus,
|
|
1915
2424
|
style: { padding: 0 },
|
|
1916
2425
|
children: [
|
|
1917
2426
|
hasTitle ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -1961,7 +2470,7 @@ var PLACEMENT_PANEL_CLASS = {
|
|
|
1961
2470
|
right: "fixed top-0 right-0 bottom-0 flex flex-col",
|
|
1962
2471
|
bottom: "fixed bottom-0 left-0 right-0 flex flex-col rounded-t-xl"
|
|
1963
2472
|
};
|
|
1964
|
-
function Drawer({ open, onClose, title, description, placement = "right", width = "w-80", maxHeight = "max-h-[85vh]", closeLabel = "Close drawer", children }) {
|
|
2473
|
+
function Drawer({ open, onClose, title, description, placement = "right", width = "w-80", maxHeight = "max-h-[85vh]", closeLabel = "Close drawer", children, className = "" }) {
|
|
1965
2474
|
const panelRef = (0, react.useRef)(null);
|
|
1966
2475
|
const titleId = (0, react.useId)();
|
|
1967
2476
|
const [rendered, setRendered] = (0, react.useState)(open);
|
|
@@ -2022,14 +2531,7 @@ function Drawer({ open, onClose, title, description, placement = "right", width
|
|
|
2022
2531
|
return () => document.removeEventListener("keydown", handleKeyDown);
|
|
2023
2532
|
}, [open, onClose]);
|
|
2024
2533
|
if (!rendered) return null;
|
|
2025
|
-
const panelClass = [
|
|
2026
|
-
PLACEMENT_PANEL_CLASS[placement],
|
|
2027
|
-
placement === "right" ? width : maxHeight,
|
|
2028
|
-
"bg-white shadow-xl focus:outline-none",
|
|
2029
|
-
placement === "bottom" ? "overflow-y-auto" : "overflow-hidden",
|
|
2030
|
-
"transition-transform duration-300 ease-out",
|
|
2031
|
-
placement === "right" ? visible ? "translate-x-0" : "translate-x-full" : visible ? "translate-y-0" : "translate-y-full"
|
|
2032
|
-
].filter(Boolean).join(" ");
|
|
2534
|
+
const panelClass = cn(PLACEMENT_PANEL_CLASS[placement], placement === "right" ? width : maxHeight, "bg-white shadow-xl focus:outline-none", placement === "bottom" ? "overflow-y-auto" : "overflow-hidden", "transition-transform duration-300 ease-out", placement === "right" ? visible ? "translate-x-0" : "translate-x-full" : visible ? "translate-y-0" : "translate-y-full", className);
|
|
2033
2535
|
return (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2034
2536
|
className: "fixed inset-0 z-50",
|
|
2035
2537
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
@@ -2140,6 +2642,30 @@ function toRadixPlacement(placement) {
|
|
|
2140
2642
|
side: "top",
|
|
2141
2643
|
align: "end"
|
|
2142
2644
|
};
|
|
2645
|
+
case "left-start": return {
|
|
2646
|
+
side: "left",
|
|
2647
|
+
align: "start"
|
|
2648
|
+
};
|
|
2649
|
+
case "left": return {
|
|
2650
|
+
side: "left",
|
|
2651
|
+
align: "center"
|
|
2652
|
+
};
|
|
2653
|
+
case "left-end": return {
|
|
2654
|
+
side: "left",
|
|
2655
|
+
align: "end"
|
|
2656
|
+
};
|
|
2657
|
+
case "right-start": return {
|
|
2658
|
+
side: "right",
|
|
2659
|
+
align: "start"
|
|
2660
|
+
};
|
|
2661
|
+
case "right": return {
|
|
2662
|
+
side: "right",
|
|
2663
|
+
align: "center"
|
|
2664
|
+
};
|
|
2665
|
+
case "right-end": return {
|
|
2666
|
+
side: "right",
|
|
2667
|
+
align: "end"
|
|
2668
|
+
};
|
|
2143
2669
|
}
|
|
2144
2670
|
}
|
|
2145
2671
|
function Popover({ open, onOpenChange, defaultOpen = false, placement = "bottom-start", children }) {
|
|
@@ -2164,12 +2690,12 @@ var PopoverPlacementContext = (0, react.createContext)({
|
|
|
2164
2690
|
function usePlacement() {
|
|
2165
2691
|
return (0, react.useContext)(PopoverPlacementContext);
|
|
2166
2692
|
}
|
|
2167
|
-
function PopoverTrigger({ children }) {
|
|
2693
|
+
function PopoverTrigger({ children, className = "" }) {
|
|
2168
2694
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_radix_ui_react_popover.Trigger, {
|
|
2169
2695
|
asChild: true,
|
|
2170
2696
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2171
2697
|
type: "button",
|
|
2172
|
-
className: "inline-flex items-center gap-2 rounded-xl border border-slate-200 bg-white px-4 py-2 text-sm font-semibold text-slate-700 shadow-sm transition hover:border-slate-300 hover:bg-slate-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30",
|
|
2698
|
+
className: cn("inline-flex items-center gap-2 rounded-xl border border-slate-200 bg-white px-4 py-2 text-sm font-semibold text-slate-700 shadow-sm transition hover:border-slate-300 hover:bg-slate-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30", className),
|
|
2173
2699
|
children
|
|
2174
2700
|
})
|
|
2175
2701
|
});
|
|
@@ -2180,7 +2706,7 @@ function PopoverContent({ title, children, maxWidth = "max-w-xs", className = ""
|
|
|
2180
2706
|
side,
|
|
2181
2707
|
align,
|
|
2182
2708
|
sideOffset: 8,
|
|
2183
|
-
className: cn("z-50 w-max rounded-xl border border-slate-200 bg-white shadow-lg", "outline-none", maxWidth, className),
|
|
2709
|
+
className: cn("z-50 w-max rounded-xl border border-slate-200 bg-white shadow-lg", "outline-none", "data-[state=open]:animate-in data-[state=closed]:animate-out", "data-[state=open]:fade-in-0 data-[state=closed]:fade-out-0", "data-[state=open]:zoom-in-95 data-[state=closed]:zoom-out-95", "duration-200", maxWidth, className),
|
|
2184
2710
|
children: [title && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2185
2711
|
className: "border-b border-slate-100 px-4 py-3",
|
|
2186
2712
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
@@ -2198,8 +2724,8 @@ function PopoverContent({ title, children, maxWidth = "max-w-xs", className = ""
|
|
|
2198
2724
|
/**
|
|
2199
2725
|
* Tooltip — Design System Component
|
|
2200
2726
|
*
|
|
2201
|
-
* A short
|
|
2202
|
-
* an element.
|
|
2727
|
+
* A short label or non-interactive formatted content that appears on hover
|
|
2728
|
+
* or focus to describe an element.
|
|
2203
2729
|
*
|
|
2204
2730
|
* Behavior is provided by @radix-ui/react-tooltip, which handles:
|
|
2205
2731
|
* - Trigger scoping (fires only on the direct trigger, not descendants)
|
|
@@ -2216,14 +2742,16 @@ function PopoverContent({ title, children, maxWidth = "max-w-xs", className = ""
|
|
|
2216
2742
|
* - Typography and spacing
|
|
2217
2743
|
*
|
|
2218
2744
|
* Rules:
|
|
2219
|
-
* - Tooltip content must be
|
|
2745
|
+
* - Tooltip content must be non-interactive. Interactive elements (links,
|
|
2746
|
+
* buttons) inside `content` will be unclickable due to `pointer-events-none`
|
|
2747
|
+
* on the tooltip panel.
|
|
2220
2748
|
* - Do not put ESSENTIAL information only in a tooltip.
|
|
2221
2749
|
* - Use for supplementary descriptions: icon button labels, abbrev. meanings.
|
|
2222
2750
|
*
|
|
2223
2751
|
* Placement: 'top' | 'bottom' | 'left' | 'right'
|
|
2224
2752
|
*
|
|
2225
2753
|
* Difference from Popover:
|
|
2226
|
-
* - Tooltip is
|
|
2754
|
+
* - Tooltip is for non-interactive content, triggered by hover/focus.
|
|
2227
2755
|
* - Popover contains interactive content, triggered by explicit click.
|
|
2228
2756
|
*/
|
|
2229
2757
|
function Tooltip({ content, placement = "top", delayMs = 400, children, className = "" }) {
|
|
@@ -2239,7 +2767,7 @@ function Tooltip({ content, placement = "top", delayMs = 400, children, classNam
|
|
|
2239
2767
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_radix_ui_react_tooltip.Portal, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_radix_ui_react_tooltip.Content, {
|
|
2240
2768
|
side: placement,
|
|
2241
2769
|
sideOffset: 8,
|
|
2242
|
-
className: cn("pointer-events-none z-50 whitespace-nowrap", "rounded-lg bg-slate-900 px-2.5 py-1.5 text-xs font-semibold text-white shadow-lg", "border-b-2 border-brand-sky/60"),
|
|
2770
|
+
className: cn("pointer-events-none z-50 whitespace-nowrap", "rounded-lg bg-slate-900 px-2.5 py-1.5 text-xs font-semibold text-white shadow-lg", "border-b-2 border-brand-sky/60", "data-[state=open]:animate-in data-[state=open]:fade-in-0", "duration-150"),
|
|
2243
2771
|
children: content
|
|
2244
2772
|
}) })] })
|
|
2245
2773
|
});
|
|
@@ -2299,7 +2827,7 @@ var BTN_CANCEL = [
|
|
|
2299
2827
|
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/15",
|
|
2300
2828
|
"disabled:cursor-not-allowed disabled:opacity-60"
|
|
2301
2829
|
].join(" ");
|
|
2302
|
-
function ConfirmDialog({ open, onClose, onConfirm, title, description, confirmLabel = "Confirm", cancelLabel = "Cancel", tone = "danger" }) {
|
|
2830
|
+
function ConfirmDialog({ open, onClose, onConfirm, title, description, confirmLabel = "Confirm", cancelLabel = "Cancel", tone = "danger", className = "" }) {
|
|
2303
2831
|
const [loading, setLoading] = (0, react.useState)(false);
|
|
2304
2832
|
const handleConfirm = async () => {
|
|
2305
2833
|
setLoading(true);
|
|
@@ -2317,17 +2845,20 @@ function ConfirmDialog({ open, onClose, onConfirm, title, description, confirmLa
|
|
|
2317
2845
|
title,
|
|
2318
2846
|
description: typeof description === "string" ? description : void 0,
|
|
2319
2847
|
size: "sm",
|
|
2320
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DialogBody, {
|
|
2321
|
-
className
|
|
2322
|
-
children:
|
|
2323
|
-
className:
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2848
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DialogBody, {
|
|
2849
|
+
className,
|
|
2850
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2851
|
+
className: "flex items-start gap-3",
|
|
2852
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2853
|
+
className: `mt-0.5 flex h-9 w-9 shrink-0 items-center justify-center rounded-md ${ICON_WRAPPER[tone]}`,
|
|
2854
|
+
"aria-hidden": "true",
|
|
2855
|
+
children: tone === "danger" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.AlertTriangle, { size: 20 }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Info, { size: 20 })
|
|
2856
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2857
|
+
className: "text-sm font-medium leading-relaxed text-slate-600",
|
|
2858
|
+
children: description
|
|
2859
|
+
})]
|
|
2860
|
+
})
|
|
2861
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(DialogFooter, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2331
2862
|
type: "button",
|
|
2332
2863
|
className: BTN_CANCEL,
|
|
2333
2864
|
onClick: onClose,
|
|
@@ -2421,7 +2952,7 @@ var radius = {
|
|
|
2421
2952
|
* - Use gallery Button component (Phase 11 — replaces legacy inlined btn* strings)
|
|
2422
2953
|
* - Use a simple inline ErrorBanner instead of importing from ui.tsx
|
|
2423
2954
|
*/
|
|
2424
|
-
function ConfirmAction({ title, message, label, description, confirmLabel = "Confirm", tone = "danger", disabled = false, onConfirm, children }) {
|
|
2955
|
+
function ConfirmAction({ title, message, label, description, confirmLabel = "Confirm", tone = "danger", disabled = false, onConfirm, children, className = "" }) {
|
|
2425
2956
|
const [open, setOpen] = (0, react.useState)(false);
|
|
2426
2957
|
const [pending, setPending] = (0, react.useState)(false);
|
|
2427
2958
|
const [error, setError] = (0, react.useState)("");
|
|
@@ -2445,7 +2976,7 @@ function ConfirmAction({ title, message, label, description, confirmLabel = "Con
|
|
|
2445
2976
|
},
|
|
2446
2977
|
disabled
|
|
2447
2978
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2448
|
-
className: "inline-flex",
|
|
2979
|
+
className: cn("inline-flex", className),
|
|
2449
2980
|
onClick: () => {
|
|
2450
2981
|
if (!disabled) setOpen(true);
|
|
2451
2982
|
},
|
|
@@ -2505,7 +3036,7 @@ function ConfirmAction({ title, message, label, description, confirmLabel = "Con
|
|
|
2505
3036
|
* This component is presentation-only: callers provide commands and
|
|
2506
3037
|
* handle the query / selection logic.
|
|
2507
3038
|
*/
|
|
2508
|
-
function CommandPalette({ open, onClose, items, query, onQueryChange, placeholder = "Search commands…", "aria-label": ariaLabel = "Command palette" }) {
|
|
3039
|
+
function CommandPalette({ open, onClose, items, query, onQueryChange, placeholder = "Search commands…", "aria-label": ariaLabel = "Command palette", panelClassName = "" }) {
|
|
2509
3040
|
const inputRef = (0, react.useRef)(null);
|
|
2510
3041
|
const panelRef = (0, react.useRef)(null);
|
|
2511
3042
|
const listboxId = (0, react.useId)();
|
|
@@ -2539,6 +3070,23 @@ function CommandPalette({ open, onClose, items, query, onQueryChange, placeholde
|
|
|
2539
3070
|
e.preventDefault();
|
|
2540
3071
|
items[activeIndex]?.onSelect();
|
|
2541
3072
|
onClose();
|
|
3073
|
+
} else if (e.key === "Tab") {
|
|
3074
|
+
const panel = panelRef.current;
|
|
3075
|
+
if (!panel) return;
|
|
3076
|
+
const focusable = Array.from(panel.querySelectorAll("a[href],button:not([disabled]),textarea:not([disabled]),input:not([disabled]),select:not([disabled]),[tabindex]:not([tabindex=\"-1\"])"));
|
|
3077
|
+
if (!focusable.length) {
|
|
3078
|
+
e.preventDefault();
|
|
3079
|
+
return;
|
|
3080
|
+
}
|
|
3081
|
+
const first = focusable[0];
|
|
3082
|
+
const last = focusable[focusable.length - 1];
|
|
3083
|
+
if (e.shiftKey && document.activeElement === first) {
|
|
3084
|
+
e.preventDefault();
|
|
3085
|
+
last.focus();
|
|
3086
|
+
} else if (!e.shiftKey && document.activeElement === last) {
|
|
3087
|
+
e.preventDefault();
|
|
3088
|
+
first.focus();
|
|
3089
|
+
}
|
|
2542
3090
|
}
|
|
2543
3091
|
};
|
|
2544
3092
|
document.addEventListener("keydown", handle);
|
|
@@ -2568,7 +3116,7 @@ function CommandPalette({ open, onClose, items, query, onQueryChange, placeholde
|
|
|
2568
3116
|
role: "dialog",
|
|
2569
3117
|
"aria-modal": "true",
|
|
2570
3118
|
"aria-label": ariaLabel,
|
|
2571
|
-
className: "relative z-10 w-full max-w-lg rounded-2xl border border-slate-200 bg-white shadow-2xl overflow-hidden",
|
|
3119
|
+
className: cn("relative z-10 w-full max-w-lg rounded-2xl border border-slate-200 bg-white shadow-2xl overflow-hidden", panelClassName),
|
|
2572
3120
|
children: [
|
|
2573
3121
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2574
3122
|
className: "flex items-center gap-3 px-4 py-3 border-b border-slate-100",
|
|
@@ -2679,10 +3227,11 @@ function CommandPalette({ open, onClose, items, query, onQueryChange, placeholde
|
|
|
2679
3227
|
* ]}
|
|
2680
3228
|
* />
|
|
2681
3229
|
*/
|
|
2682
|
-
function Breadcrumb({ items, showHome = true, ariaLabel = "Breadcrumb" }) {
|
|
3230
|
+
function Breadcrumb({ items, showHome = true, ariaLabel = "Breadcrumb", className = "" }) {
|
|
2683
3231
|
if (items.length === 0) return null;
|
|
2684
3232
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("nav", {
|
|
2685
3233
|
"aria-label": ariaLabel,
|
|
3234
|
+
className: cn(className),
|
|
2686
3235
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("ol", {
|
|
2687
3236
|
className: "flex flex-wrap items-center gap-1 text-sm font-semibold text-slate-400",
|
|
2688
3237
|
children: [showHome && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
|
|
@@ -2823,7 +3372,7 @@ function TabList({ "aria-label": ariaLabel, children, className = "" }) {
|
|
|
2823
3372
|
role: "tablist",
|
|
2824
3373
|
"aria-label": ariaLabel,
|
|
2825
3374
|
onKeyDown: handleKeyDown,
|
|
2826
|
-
className:
|
|
3375
|
+
className: cn("flex border-b-2 border-slate-200", className),
|
|
2827
3376
|
children
|
|
2828
3377
|
});
|
|
2829
3378
|
}
|
|
@@ -2843,12 +3392,7 @@ function Tab({ value, disabled = false, children, className = "" }) {
|
|
|
2843
3392
|
disabled,
|
|
2844
3393
|
tabIndex: isActive ? 0 : -1,
|
|
2845
3394
|
onClick: () => activate(value),
|
|
2846
|
-
className:
|
|
2847
|
-
"relative px-5 py-3 text-sm font-semibold font-heading border-b-4 transition-colors",
|
|
2848
|
-
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-brand-blue/30",
|
|
2849
|
-
isActive ? "border-brand-blue text-brand-blue" : disabled ? "border-transparent text-slate-300 cursor-not-allowed" : "border-transparent text-slate-500 hover:text-slate-700 hover:border-slate-300",
|
|
2850
|
-
className
|
|
2851
|
-
].filter(Boolean).join(" "),
|
|
3395
|
+
className: cn("relative px-5 py-3 text-sm font-semibold font-heading border-b-4 transition-colors", "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-brand-blue/30", isActive ? "border-brand-blue text-brand-blue" : disabled ? "border-transparent text-slate-300 cursor-not-allowed" : "border-transparent text-slate-500 hover:text-slate-700 hover:border-slate-300", className),
|
|
2852
3396
|
children
|
|
2853
3397
|
});
|
|
2854
3398
|
}
|
|
@@ -2866,7 +3410,7 @@ function TabPanel({ value, children, className = "" }) {
|
|
|
2866
3410
|
role: "tabpanel",
|
|
2867
3411
|
"aria-labelledby": tabId(baseId, value),
|
|
2868
3412
|
tabIndex: 0,
|
|
2869
|
-
className:
|
|
3413
|
+
className: cn("focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-brand-blue/30 rounded-sm", className),
|
|
2870
3414
|
children
|
|
2871
3415
|
});
|
|
2872
3416
|
}
|
|
@@ -2899,14 +3443,14 @@ function getPageNumbers(current, total) {
|
|
|
2899
3443
|
pages.push(total);
|
|
2900
3444
|
return pages;
|
|
2901
3445
|
}
|
|
2902
|
-
function Pagination({ page, totalPages, onPageChange, ariaLabel = "Pagination", totalItems, pageSize, itemLabel = "items" }) {
|
|
3446
|
+
function Pagination({ page, totalPages, onPageChange, ariaLabel = "Pagination", totalItems, pageSize, itemLabel = "items", className = "" }) {
|
|
2903
3447
|
if (totalPages <= 1 && !totalItems) return null;
|
|
2904
3448
|
const showRecordCount = typeof totalItems === "number" && typeof pageSize === "number";
|
|
2905
3449
|
const rangeStart = showRecordCount ? (page - 1) * pageSize + 1 : null;
|
|
2906
3450
|
const rangeEnd = showRecordCount ? Math.min(page * pageSize, totalItems) : null;
|
|
2907
3451
|
const pageNumbers = getPageNumbers(page, totalPages);
|
|
2908
3452
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2909
|
-
className: "flex items-center justify-between gap-4",
|
|
3453
|
+
className: cn("flex items-center justify-between gap-4", className),
|
|
2910
3454
|
children: [showRecordCount ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
2911
3455
|
className: "text-[11px] font-bold tabular-nums text-slate-400",
|
|
2912
3456
|
"aria-live": "polite",
|
|
@@ -2975,14 +3519,19 @@ function resolveState(id, currentStep, completedSteps, disabledSteps) {
|
|
|
2975
3519
|
if (id === currentStep) return "current";
|
|
2976
3520
|
return "upcoming";
|
|
2977
3521
|
}
|
|
2978
|
-
function StepCircle({ state, number, icon }) {
|
|
3522
|
+
function StepCircle({ state, number, icon, dark = false }) {
|
|
2979
3523
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2980
|
-
className: `flex h-9 w-9 shrink-0 items-center justify-center rounded-full border-2 text-sm font-bold transition-all ${{
|
|
3524
|
+
className: `flex h-9 w-9 shrink-0 items-center justify-center rounded-full border-2 text-sm font-bold transition-all ${(dark ? {
|
|
3525
|
+
completed: "border-[#2D8ACA] bg-[#2D8ACA] text-white",
|
|
3526
|
+
current: "border-white bg-white text-[#00377B] shadow-md shadow-black/20",
|
|
3527
|
+
upcoming: "border-white/30 bg-white/10 text-white/60",
|
|
3528
|
+
disabled: "border-white/10 bg-white/5 text-white/20"
|
|
3529
|
+
} : {
|
|
2981
3530
|
completed: "border-brand-blue bg-brand-blue text-white",
|
|
2982
3531
|
current: "border-brand-blue bg-white text-brand-blue shadow-md shadow-brand-blue/15",
|
|
2983
3532
|
upcoming: "border-slate-300 bg-white text-slate-400",
|
|
2984
3533
|
disabled: "border-slate-200 bg-slate-50 text-slate-300"
|
|
2985
|
-
}[state]}`,
|
|
3534
|
+
})[state]}`,
|
|
2986
3535
|
"aria-hidden": "true",
|
|
2987
3536
|
children: state === "completed" ? icon ?? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Check, {
|
|
2988
3537
|
size: 16,
|
|
@@ -2990,17 +3539,31 @@ function StepCircle({ state, number, icon }) {
|
|
|
2990
3539
|
}) : icon ?? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: number })
|
|
2991
3540
|
});
|
|
2992
3541
|
}
|
|
2993
|
-
function Stepper({ steps, currentStep, completedSteps = [], disabledSteps = [], orientation = "horizontal", className = "" }) {
|
|
2994
|
-
|
|
3542
|
+
function Stepper({ steps, currentStep, completedSteps = [], disabledSteps = [], orientation = "horizontal", theme = "light", className = "" }) {
|
|
3543
|
+
const isHorizontal = orientation === "horizontal";
|
|
3544
|
+
const dark = theme === "dark";
|
|
3545
|
+
if (!isHorizontal) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2995
3546
|
role: "group",
|
|
2996
3547
|
"aria-label": "Progress",
|
|
2997
|
-
className:
|
|
3548
|
+
className: cn("flex flex-col", className),
|
|
2998
3549
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ol", {
|
|
2999
3550
|
className: "flex flex-col",
|
|
3000
3551
|
children: steps.map((step, idx) => {
|
|
3001
3552
|
const state = resolveState(step.id, currentStep, completedSteps, disabledSteps);
|
|
3002
3553
|
const isCurrent = state === "current";
|
|
3003
3554
|
const isLast = idx === steps.length - 1;
|
|
3555
|
+
const labelColor = dark ? {
|
|
3556
|
+
completed: "text-[#2D8ACA]",
|
|
3557
|
+
current: "text-white font-bold",
|
|
3558
|
+
upcoming: "text-white/60",
|
|
3559
|
+
disabled: "text-white/20"
|
|
3560
|
+
} : {
|
|
3561
|
+
completed: "text-brand-blue",
|
|
3562
|
+
current: "text-brand-navy font-bold",
|
|
3563
|
+
upcoming: "text-slate-500",
|
|
3564
|
+
disabled: "text-slate-300"
|
|
3565
|
+
};
|
|
3566
|
+
const connectorColor = dark ? state === "completed" ? "bg-[#2D8ACA]/50" : "bg-white/15" : state === "completed" ? "bg-brand-blue/50" : "bg-slate-200";
|
|
3004
3567
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
3005
3568
|
"aria-current": isCurrent ? "step" : void 0,
|
|
3006
3569
|
className: "flex items-start gap-4 pb-8 last:pb-0",
|
|
@@ -3009,23 +3572,19 @@ function Stepper({ steps, currentStep, completedSteps = [], disabledSteps = [],
|
|
|
3009
3572
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StepCircle, {
|
|
3010
3573
|
state,
|
|
3011
3574
|
number: idx + 1,
|
|
3012
|
-
icon: step.icon
|
|
3575
|
+
icon: step.icon,
|
|
3576
|
+
dark
|
|
3013
3577
|
}), !isLast && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3014
|
-
className: `mt-1 w-0.5 flex-1 min-h-[32px] ${
|
|
3578
|
+
className: `mt-1 w-0.5 flex-1 min-h-[32px] ${connectorColor}`,
|
|
3015
3579
|
"aria-hidden": "true"
|
|
3016
3580
|
})]
|
|
3017
3581
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3018
3582
|
className: "min-w-0 flex-1 pt-1",
|
|
3019
3583
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3020
|
-
className: `text-sm leading-tight ${
|
|
3021
|
-
completed: "text-brand-blue",
|
|
3022
|
-
current: "text-brand-navy font-bold",
|
|
3023
|
-
upcoming: "text-slate-500",
|
|
3024
|
-
disabled: "text-slate-300"
|
|
3025
|
-
}[state]}`,
|
|
3584
|
+
className: `text-sm leading-tight ${labelColor[state]}`,
|
|
3026
3585
|
children: step.label
|
|
3027
3586
|
}), step.description && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3028
|
-
className:
|
|
3587
|
+
className: `mt-0.5 text-xs leading-tight ${dark ? "text-white/40" : "text-slate-400"}`,
|
|
3029
3588
|
children: step.description
|
|
3030
3589
|
})]
|
|
3031
3590
|
})]
|
|
@@ -3034,7 +3593,12 @@ function Stepper({ steps, currentStep, completedSteps = [], disabledSteps = [],
|
|
|
3034
3593
|
})
|
|
3035
3594
|
});
|
|
3036
3595
|
const resolvedStates = steps.map((step) => resolveState(step.id, currentStep, completedSteps, disabledSteps));
|
|
3037
|
-
const labelColor = {
|
|
3596
|
+
const labelColor = dark ? {
|
|
3597
|
+
completed: "text-[#2D8ACA]",
|
|
3598
|
+
current: "text-white font-bold",
|
|
3599
|
+
upcoming: "text-white/60",
|
|
3600
|
+
disabled: "text-white/20"
|
|
3601
|
+
} : {
|
|
3038
3602
|
completed: "text-brand-blue",
|
|
3039
3603
|
current: "text-brand-navy font-bold",
|
|
3040
3604
|
upcoming: "text-slate-500",
|
|
@@ -3043,7 +3607,7 @@ function Stepper({ steps, currentStep, completedSteps = [], disabledSteps = [],
|
|
|
3043
3607
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3044
3608
|
role: "group",
|
|
3045
3609
|
"aria-label": "Progress",
|
|
3046
|
-
className:
|
|
3610
|
+
className: cn("w-full", className),
|
|
3047
3611
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ol", {
|
|
3048
3612
|
className: "grid w-full",
|
|
3049
3613
|
style: { gridTemplateColumns: `repeat(${steps.length}, 1fr)` },
|
|
@@ -3052,6 +3616,9 @@ function Stepper({ steps, currentStep, completedSteps = [], disabledSteps = [],
|
|
|
3052
3616
|
const isCurrent = state === "current";
|
|
3053
3617
|
const isLast = idx === steps.length - 1;
|
|
3054
3618
|
const prevState = idx > 0 ? resolvedStates[idx - 1] : null;
|
|
3619
|
+
const connectorDone = prevState === "completed" || prevState === "current";
|
|
3620
|
+
const connectorLeft = dark ? connectorDone ? "bg-[#2D8ACA]/50" : "bg-white/15" : connectorDone ? "bg-brand-blue/50" : "bg-slate-200";
|
|
3621
|
+
const connectorRight = dark ? state === "completed" ? "bg-[#2D8ACA]/50" : "bg-white/15" : state === "completed" ? "bg-brand-blue/50" : "bg-slate-200";
|
|
3055
3622
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
3056
3623
|
"aria-current": isCurrent ? "step" : void 0,
|
|
3057
3624
|
className: "flex flex-col items-center",
|
|
@@ -3059,7 +3626,7 @@ function Stepper({ steps, currentStep, completedSteps = [], disabledSteps = [],
|
|
|
3059
3626
|
className: "flex w-full items-center",
|
|
3060
3627
|
children: [
|
|
3061
3628
|
idx > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3062
|
-
className: `h-0.5 flex-1 ${
|
|
3629
|
+
className: `h-0.5 flex-1 ${connectorLeft}`,
|
|
3063
3630
|
"aria-hidden": "true"
|
|
3064
3631
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3065
3632
|
className: "flex-1",
|
|
@@ -3070,11 +3637,12 @@ function Stepper({ steps, currentStep, completedSteps = [], disabledSteps = [],
|
|
|
3070
3637
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StepCircle, {
|
|
3071
3638
|
state,
|
|
3072
3639
|
number: idx + 1,
|
|
3073
|
-
icon: step.icon
|
|
3640
|
+
icon: step.icon,
|
|
3641
|
+
dark
|
|
3074
3642
|
})
|
|
3075
3643
|
}),
|
|
3076
3644
|
!isLast ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3077
|
-
className: `h-0.5 flex-1 ${
|
|
3645
|
+
className: `h-0.5 flex-1 ${connectorRight}`,
|
|
3078
3646
|
"aria-hidden": "true"
|
|
3079
3647
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3080
3648
|
className: "flex-1",
|
|
@@ -3087,7 +3655,7 @@ function Stepper({ steps, currentStep, completedSteps = [], disabledSteps = [],
|
|
|
3087
3655
|
className: `text-sm leading-tight ${labelColor[state]}`,
|
|
3088
3656
|
children: step.label
|
|
3089
3657
|
}), step.description && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3090
|
-
className:
|
|
3658
|
+
className: `mt-0.5 text-xs leading-tight ${dark ? "text-white/40" : "text-slate-400"}`,
|
|
3091
3659
|
children: step.description
|
|
3092
3660
|
})]
|
|
3093
3661
|
})]
|
|
@@ -3241,10 +3809,23 @@ function Dropdown({ trigger, isOpen, onToggle, onClose, children, triggerClassNa
|
|
|
3241
3809
|
const ref = (0, react.useRef)(null);
|
|
3242
3810
|
const triggerRef = (0, react.useRef)(null);
|
|
3243
3811
|
const panelId = `${(0, react.useId)().replace(/:/g, "")}-panel`;
|
|
3812
|
+
const [panelStyle, setPanelStyle] = (0, react.useState)({});
|
|
3813
|
+
(0, react.useEffect)(() => {
|
|
3814
|
+
if (!isOpen || !triggerRef.current) return;
|
|
3815
|
+
const rect = triggerRef.current.getBoundingClientRect();
|
|
3816
|
+
setPanelStyle({
|
|
3817
|
+
position: "fixed",
|
|
3818
|
+
top: rect.bottom + 4,
|
|
3819
|
+
left: rect.left,
|
|
3820
|
+
zIndex: 9999,
|
|
3821
|
+
minWidth: Math.max(rect.width, 180)
|
|
3822
|
+
});
|
|
3823
|
+
}, [isOpen]);
|
|
3244
3824
|
(0, react.useEffect)(() => {
|
|
3245
3825
|
if (!isOpen) return;
|
|
3246
3826
|
const handlePointerDown = (e) => {
|
|
3247
|
-
|
|
3827
|
+
const target = e.target;
|
|
3828
|
+
if (ref.current && !ref.current.contains(target)) {
|
|
3248
3829
|
onClose();
|
|
3249
3830
|
triggerRef.current?.focus();
|
|
3250
3831
|
}
|
|
@@ -3274,21 +3855,14 @@ function Dropdown({ trigger, isOpen, onToggle, onClose, children, triggerClassNa
|
|
|
3274
3855
|
"aria-expanded": isOpen,
|
|
3275
3856
|
"aria-haspopup": "true",
|
|
3276
3857
|
"aria-controls": isOpen ? panelId : void 0,
|
|
3277
|
-
className:
|
|
3278
|
-
"inline-flex items-center gap-2 rounded-lg px-4 py-2 text-sm font-bold text-slate-600 transition-all",
|
|
3279
|
-
"hover:bg-slate-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30",
|
|
3280
|
-
triggerClassName
|
|
3281
|
-
].filter(Boolean).join(" "),
|
|
3858
|
+
className: cn("inline-flex items-center gap-2 rounded-lg px-4 py-2 text-sm font-bold text-slate-600 transition-all", "hover:bg-slate-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30", triggerClassName),
|
|
3282
3859
|
children: trigger
|
|
3283
|
-
}), isOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3860
|
+
}), isOpen && (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3284
3861
|
id: panelId,
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
"rounded-xl border border-slate-200 bg-white shadow-lg",
|
|
3288
|
-
panelClassName
|
|
3289
|
-
].filter(Boolean).join(" "),
|
|
3862
|
+
style: panelStyle,
|
|
3863
|
+
className: cn("max-h-[280px] overflow-y-auto", "rounded-xl border border-slate-200 bg-white shadow-lg", panelClassName),
|
|
3290
3864
|
children
|
|
3291
|
-
})]
|
|
3865
|
+
}), document.body)]
|
|
3292
3866
|
});
|
|
3293
3867
|
}
|
|
3294
3868
|
//#endregion
|
|
@@ -3315,15 +3889,36 @@ function Dropdown({ trigger, isOpen, onToggle, onClose, children, triggerClassNa
|
|
|
3315
3889
|
* ]}
|
|
3316
3890
|
* />
|
|
3317
3891
|
*/
|
|
3318
|
-
function Menu({ trigger, items, ariaLabel = "Menu", align = "right" }) {
|
|
3319
|
-
const [
|
|
3892
|
+
function Menu({ trigger, items, ariaLabel = "Menu", align = "right", open: controlledOpen, onOpenChange, panelClassName = "" }) {
|
|
3893
|
+
const [internalOpen, setInternalOpen] = (0, react.useState)(false);
|
|
3894
|
+
const isControlled = controlledOpen !== void 0;
|
|
3895
|
+
const open = isControlled ? controlledOpen : internalOpen;
|
|
3896
|
+
const setOpen = (next) => {
|
|
3897
|
+
if (!isControlled) setInternalOpen(next);
|
|
3898
|
+
onOpenChange?.(next);
|
|
3899
|
+
};
|
|
3320
3900
|
const containerRef = (0, react.useRef)(null);
|
|
3321
3901
|
const triggerRef = (0, react.useRef)(null);
|
|
3902
|
+
const panelRef = (0, react.useRef)(null);
|
|
3322
3903
|
const itemRefs = (0, react.useRef)([]);
|
|
3904
|
+
const [panelStyle, setPanelStyle] = (0, react.useState)({});
|
|
3905
|
+
(0, react.useEffect)(() => {
|
|
3906
|
+
if (!open || !triggerRef.current) return;
|
|
3907
|
+
const rect = triggerRef.current.getBoundingClientRect();
|
|
3908
|
+
const PANEL_MIN_WIDTH = 176;
|
|
3909
|
+
setPanelStyle({
|
|
3910
|
+
position: "fixed",
|
|
3911
|
+
top: rect.bottom + 4,
|
|
3912
|
+
left: align === "left" ? rect.left : Math.max(0, rect.right - PANEL_MIN_WIDTH),
|
|
3913
|
+
zIndex: 9999,
|
|
3914
|
+
minWidth: Math.max(rect.width, PANEL_MIN_WIDTH)
|
|
3915
|
+
});
|
|
3916
|
+
}, [open, align]);
|
|
3323
3917
|
(0, react.useEffect)(() => {
|
|
3324
3918
|
if (!open) return;
|
|
3325
3919
|
const handlePointerDown = (e) => {
|
|
3326
|
-
|
|
3920
|
+
const target = e.target;
|
|
3921
|
+
if (containerRef.current && !containerRef.current.contains(target) && !(panelRef.current && panelRef.current.contains(target))) {
|
|
3327
3922
|
setOpen(false);
|
|
3328
3923
|
(triggerRef.current?.querySelector("button,a,[tabindex]"))?.focus();
|
|
3329
3924
|
}
|
|
@@ -3364,22 +3959,23 @@ function Menu({ trigger, items, ariaLabel = "Menu", align = "right" }) {
|
|
|
3364
3959
|
focusItem(items.length - 1);
|
|
3365
3960
|
}
|
|
3366
3961
|
};
|
|
3367
|
-
const panelAlign = align === "left" ? "left-0" : "right-0";
|
|
3368
3962
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3369
3963
|
className: "relative inline-block",
|
|
3370
3964
|
ref: containerRef,
|
|
3371
3965
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3372
3966
|
ref: triggerRef,
|
|
3373
|
-
onClick: () => setOpen(
|
|
3967
|
+
onClick: () => setOpen(!open),
|
|
3374
3968
|
className: "cursor-pointer",
|
|
3375
3969
|
children: (0, react.isValidElement)(trigger) ? (0, react.cloneElement)(trigger, {
|
|
3376
3970
|
"aria-expanded": open,
|
|
3377
3971
|
"aria-haspopup": "menu"
|
|
3378
3972
|
}) : trigger
|
|
3379
|
-
}), open && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3973
|
+
}), open && (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3974
|
+
ref: panelRef,
|
|
3380
3975
|
role: "menu",
|
|
3381
3976
|
"aria-label": ariaLabel,
|
|
3382
|
-
|
|
3977
|
+
style: panelStyle,
|
|
3978
|
+
className: cn("overflow-hidden rounded-xl border border-slate-200 bg-white py-1 shadow-xl", panelClassName),
|
|
3383
3979
|
children: items.map((item, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
3384
3980
|
ref: (node) => {
|
|
3385
3981
|
itemRefs.current[index] = node;
|
|
@@ -3405,7 +4001,7 @@ function Menu({ trigger, items, ariaLabel = "Menu", align = "right" }) {
|
|
|
3405
4001
|
children: item.icon
|
|
3406
4002
|
}), item.label]
|
|
3407
4003
|
}, item.label))
|
|
3408
|
-
})]
|
|
4004
|
+
}), document.body)]
|
|
3409
4005
|
});
|
|
3410
4006
|
}
|
|
3411
4007
|
//#endregion
|
|
@@ -3420,13 +4016,13 @@ function Card({ variant = "default", children, className = "", onClick }) {
|
|
|
3420
4016
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(onClick ? "button" : "div", {
|
|
3421
4017
|
type: onClick ? "button" : void 0,
|
|
3422
4018
|
onClick,
|
|
3423
|
-
className:
|
|
4019
|
+
className: cn("overflow-hidden rounded-lg", VARIANT_CLASS[variant], className),
|
|
3424
4020
|
children
|
|
3425
4021
|
});
|
|
3426
4022
|
}
|
|
3427
|
-
function CardHeader({ title, description, actions }) {
|
|
4023
|
+
function CardHeader({ title, description, actions, className = "" }) {
|
|
3428
4024
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3429
|
-
className: "flex items-start justify-between gap-3 px-5 py-4 border-b-2 border-slate-100",
|
|
4025
|
+
className: cn("flex items-start justify-between gap-3 px-5 py-4 border-b-2 border-slate-100", className),
|
|
3430
4026
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3431
4027
|
className: "min-w-0",
|
|
3432
4028
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
@@ -3444,13 +4040,13 @@ function CardHeader({ title, description, actions }) {
|
|
|
3444
4040
|
}
|
|
3445
4041
|
function CardContent({ children, className = "" }) {
|
|
3446
4042
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3447
|
-
className:
|
|
4043
|
+
className: cn("px-5 py-4", className),
|
|
3448
4044
|
children
|
|
3449
4045
|
});
|
|
3450
4046
|
}
|
|
3451
4047
|
function CardFooter({ children, className = "" }) {
|
|
3452
4048
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3453
|
-
className:
|
|
4049
|
+
className: cn("flex items-center justify-end gap-2 border-t border-slate-100 px-5 py-3", className),
|
|
3454
4050
|
children
|
|
3455
4051
|
});
|
|
3456
4052
|
}
|
|
@@ -3516,7 +4112,7 @@ function Accordion({ type = "single", defaultValue, value: controlledValue, onVa
|
|
|
3516
4112
|
toggle
|
|
3517
4113
|
},
|
|
3518
4114
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3519
|
-
className:
|
|
4115
|
+
className: cn("divide-y divide-slate-200 rounded-xl border border-slate-200", className),
|
|
3520
4116
|
children
|
|
3521
4117
|
})
|
|
3522
4118
|
});
|
|
@@ -3533,7 +4129,7 @@ function AccordionItem({ value, disabled = false, children, className = "" }) {
|
|
|
3533
4129
|
triggerId
|
|
3534
4130
|
},
|
|
3535
4131
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3536
|
-
className:
|
|
4132
|
+
className: cn(className),
|
|
3537
4133
|
children
|
|
3538
4134
|
})
|
|
3539
4135
|
});
|
|
@@ -3550,12 +4146,7 @@ function AccordionTrigger({ children, className = "" }) {
|
|
|
3550
4146
|
"aria-disabled": disabled || void 0,
|
|
3551
4147
|
disabled,
|
|
3552
4148
|
onClick: () => toggle(value),
|
|
3553
|
-
className:
|
|
3554
|
-
"flex w-full items-center justify-between px-5 py-4 text-left text-sm font-semibold",
|
|
3555
|
-
"transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-brand-blue/30",
|
|
3556
|
-
disabled ? "cursor-not-allowed text-slate-300" : "text-slate-800 hover:bg-slate-50",
|
|
3557
|
-
className
|
|
3558
|
-
].filter(Boolean).join(" "),
|
|
4149
|
+
className: cn("flex w-full items-center justify-between px-5 py-4 text-left text-sm font-semibold", "transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-brand-blue/30", disabled ? "cursor-not-allowed text-slate-300" : "text-slate-800 hover:bg-slate-50", className),
|
|
3559
4150
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronDown, {
|
|
3560
4151
|
size: 16,
|
|
3561
4152
|
"aria-hidden": "true",
|
|
@@ -3566,13 +4157,21 @@ function AccordionTrigger({ children, className = "" }) {
|
|
|
3566
4157
|
function AccordionContent({ children, className = "" }) {
|
|
3567
4158
|
const { openValues } = useAccordionContext();
|
|
3568
4159
|
const { value, panelId, triggerId } = useItemContext();
|
|
3569
|
-
|
|
4160
|
+
const isOpen = openValues.has(value);
|
|
3570
4161
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3571
4162
|
id: panelId,
|
|
3572
4163
|
role: "region",
|
|
3573
4164
|
"aria-labelledby": triggerId,
|
|
3574
|
-
|
|
3575
|
-
|
|
4165
|
+
"aria-hidden": !isOpen || void 0,
|
|
4166
|
+
"data-state": isOpen ? "open" : "closed",
|
|
4167
|
+
className: cn("grid overflow-hidden transition-[grid-template-rows] duration-200 ease-out", isOpen ? "grid-rows-[1fr]" : "grid-rows-[0fr]"),
|
|
4168
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4169
|
+
className: "min-h-0",
|
|
4170
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4171
|
+
className: cn("border-t border-slate-100 px-5 py-4 text-sm font-medium text-slate-600", className),
|
|
4172
|
+
children
|
|
4173
|
+
})
|
|
4174
|
+
})
|
|
3576
4175
|
});
|
|
3577
4176
|
}
|
|
3578
4177
|
//#endregion
|
|
@@ -3584,25 +4183,17 @@ var LAYOUT_CLASS = {
|
|
|
3584
4183
|
};
|
|
3585
4184
|
function DescriptionList({ layout = "vertical", children, className = "" }) {
|
|
3586
4185
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dl", {
|
|
3587
|
-
className:
|
|
4186
|
+
className: cn(LAYOUT_CLASS[layout], className),
|
|
3588
4187
|
children
|
|
3589
4188
|
});
|
|
3590
4189
|
}
|
|
3591
4190
|
function DescriptionItem({ label, value, fullWidth = false, className = "" }) {
|
|
3592
4191
|
const isEmpty = value === void 0 || value === null || value === "";
|
|
3593
4192
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", {
|
|
3594
|
-
className: [
|
|
3595
|
-
"text-xs font-bold uppercase tracking-[0.12em] text-slate-500",
|
|
3596
|
-
fullWidth ? "sm:col-span-2" : "",
|
|
3597
|
-
className
|
|
3598
|
-
].filter(Boolean).join(" "),
|
|
4193
|
+
className: cn("text-xs font-bold uppercase tracking-[0.12em] text-slate-500", fullWidth && "sm:col-span-2", className),
|
|
3599
4194
|
children: label
|
|
3600
4195
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", {
|
|
3601
|
-
className:
|
|
3602
|
-
"text-sm font-medium text-slate-800",
|
|
3603
|
-
fullWidth ? "sm:col-span-2" : "",
|
|
3604
|
-
isEmpty ? "text-slate-300" : ""
|
|
3605
|
-
].filter(Boolean).join(" "),
|
|
4196
|
+
className: cn("text-sm font-medium text-slate-800", fullWidth && "sm:col-span-2", isEmpty && "text-slate-300"),
|
|
3606
4197
|
children: isEmpty ? "—" : value
|
|
3607
4198
|
})] });
|
|
3608
4199
|
}
|
|
@@ -3612,7 +4203,7 @@ function ActivityFeed({ events, "aria-label": ariaLabel = "Activity feed", class
|
|
|
3612
4203
|
if (events.length === 0) return null;
|
|
3613
4204
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ol", {
|
|
3614
4205
|
"aria-label": ariaLabel,
|
|
3615
|
-
className:
|
|
4206
|
+
className: cn("space-y-0", className),
|
|
3616
4207
|
children: events.map((event, i) => {
|
|
3617
4208
|
const isLast = i === events.length - 1;
|
|
3618
4209
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
@@ -3693,7 +4284,7 @@ var STATUS_LABEL = {
|
|
|
3693
4284
|
function ApprovalTimeline({ steps, "aria-label": ariaLabel = "Approval workflow", className = "" }) {
|
|
3694
4285
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ol", {
|
|
3695
4286
|
"aria-label": ariaLabel,
|
|
3696
|
-
className:
|
|
4287
|
+
className: cn("space-y-0", className),
|
|
3697
4288
|
children: steps.map((step, i) => {
|
|
3698
4289
|
const isLast = i === steps.length - 1;
|
|
3699
4290
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
@@ -3745,13 +4336,13 @@ function ApprovalTimeline({ steps, "aria-label": ariaLabel = "Approval workflow"
|
|
|
3745
4336
|
//#region src/ui-library/gallery/timeline/Timeline.tsx
|
|
3746
4337
|
function Timeline({ children, className = "" }) {
|
|
3747
4338
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3748
|
-
className:
|
|
4339
|
+
className: cn("w-full space-y-0", className),
|
|
3749
4340
|
children
|
|
3750
4341
|
});
|
|
3751
4342
|
}
|
|
3752
|
-
function TimelineItem({ isFirst, isLast, children }) {
|
|
4343
|
+
function TimelineItem({ isFirst, isLast, children, className = "" }) {
|
|
3753
4344
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3754
|
-
className: "flex gap-3",
|
|
4345
|
+
className: cn("flex gap-3", className),
|
|
3755
4346
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3756
4347
|
className: "flex flex-col items-center shrink-0 pt-4",
|
|
3757
4348
|
"aria-hidden": "true",
|
|
@@ -3831,9 +4422,9 @@ function TimelineFooter({ reason, source }) {
|
|
|
3831
4422
|
}), source] })]
|
|
3832
4423
|
});
|
|
3833
4424
|
}
|
|
3834
|
-
function TimelineEmptyState({ icon, title, description }) {
|
|
4425
|
+
function TimelineEmptyState({ icon, title, description, className = "" }) {
|
|
3835
4426
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3836
|
-
className: "py-16 text-center",
|
|
4427
|
+
className: cn("py-16 text-center", className),
|
|
3837
4428
|
children: [
|
|
3838
4429
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3839
4430
|
className: "mx-auto mb-3 flex h-12 w-12 items-center justify-center rounded-xl bg-slate-50",
|
|
@@ -3858,12 +4449,7 @@ function TimelineEmptyState({ icon, title, description }) {
|
|
|
3858
4449
|
//#region src/ui-library/gallery/empty-state/EmptyState.tsx
|
|
3859
4450
|
function EmptyState({ title, hint, icon, action, className = "" }) {
|
|
3860
4451
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3861
|
-
className: [
|
|
3862
|
-
"flex flex-col items-center justify-center gap-2",
|
|
3863
|
-
"rounded-lg border border-brand-sky/20 bg-brand-sky/[0.04]",
|
|
3864
|
-
"px-6 py-12 text-center",
|
|
3865
|
-
className
|
|
3866
|
-
].filter(Boolean).join(" "),
|
|
4452
|
+
className: cn("flex flex-col items-center justify-center gap-2", "rounded-lg border border-brand-sky/20 bg-brand-sky/[0.04]", "px-6 py-12 text-center", className),
|
|
3867
4453
|
children: [
|
|
3868
4454
|
icon && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3869
4455
|
className: "mb-1 text-brand-sky/50",
|
|
@@ -3936,7 +4522,7 @@ function ActionButton({ action, variant }) {
|
|
|
3936
4522
|
function StatusPage({ eyebrow, title, description, code, icon, primaryAction, secondaryAction, className = "" }) {
|
|
3937
4523
|
const hasActions = primaryAction || secondaryAction;
|
|
3938
4524
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3939
|
-
className:
|
|
4525
|
+
className: cn("relative flex min-h-screen items-center justify-center bg-surface-page px-6 py-16 overflow-hidden", className),
|
|
3940
4526
|
children: [code ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3941
4527
|
"aria-hidden": "true",
|
|
3942
4528
|
className: "pointer-events-none absolute select-none font-heading font-bold text-brand-navy opacity-[0.07] text-[80px] sm:text-[120px] leading-none",
|
|
@@ -3982,7 +4568,7 @@ function StatusPage({ eyebrow, title, description, code, icon, primaryAction, se
|
|
|
3982
4568
|
//#region src/ui-library/gallery/employee-card/EmployeeCard.tsx
|
|
3983
4569
|
function EmployeeCard({ name, role, avatarSrc, status, meta, actions, className = "" }) {
|
|
3984
4570
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("article", {
|
|
3985
|
-
className:
|
|
4571
|
+
className: cn("rounded-2xl border border-slate-200 bg-white shadow-sm overflow-hidden", className),
|
|
3986
4572
|
"aria-label": `${name}${role ? ", " + role : ""}`,
|
|
3987
4573
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3988
4574
|
className: "h-1.5 bg-gradient-to-r from-brand-navy to-brand-blue",
|
|
@@ -4229,7 +4815,7 @@ function TreeView({ items, selectedId, onSelect, "aria-label": ariaLabel, classN
|
|
|
4229
4815
|
role: "tree",
|
|
4230
4816
|
"aria-label": ariaLabel,
|
|
4231
4817
|
onKeyDown: handleKeyDown,
|
|
4232
|
-
className:
|
|
4818
|
+
className: cn("flex flex-col gap-0.5 outline-none", className),
|
|
4233
4819
|
tabIndex: 0,
|
|
4234
4820
|
children: items.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TreeNode, {
|
|
4235
4821
|
item,
|
|
@@ -7432,23 +8018,26 @@ var TREND_CONFIG = {
|
|
|
7432
8018
|
label: "No change"
|
|
7433
8019
|
}
|
|
7434
8020
|
};
|
|
7435
|
-
function Statistic({ label, value, helper, icon: Icon, trend, trendLabel, variant = "default" }) {
|
|
7436
|
-
if (variant === "minimal") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7437
|
-
|
|
7438
|
-
|
|
7439
|
-
|
|
7440
|
-
|
|
7441
|
-
|
|
7442
|
-
|
|
7443
|
-
|
|
7444
|
-
|
|
7445
|
-
|
|
7446
|
-
|
|
7447
|
-
|
|
7448
|
-
|
|
7449
|
-
|
|
8021
|
+
function Statistic({ label, value, helper, icon: Icon, trend, trendLabel, variant = "default", className = "" }) {
|
|
8022
|
+
if (variant === "minimal") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
8023
|
+
className: cn(className),
|
|
8024
|
+
children: [
|
|
8025
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
8026
|
+
className: "text-[11px] font-bold uppercase tracking-[0.18em] text-brand-sky",
|
|
8027
|
+
children: label
|
|
8028
|
+
}),
|
|
8029
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
8030
|
+
className: "mt-1 font-heading text-4xl font-bold tabular-nums text-brand-navy",
|
|
8031
|
+
children: value
|
|
8032
|
+
}),
|
|
8033
|
+
helper && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
8034
|
+
className: "mt-1 text-xs font-medium text-slate-400",
|
|
8035
|
+
children: helper
|
|
8036
|
+
})
|
|
8037
|
+
]
|
|
8038
|
+
});
|
|
7450
8039
|
if (variant === "compact") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7451
|
-
className: "flex items-center gap-4 rounded-2xl border border-slate-200 bg-white px-5 py-4 shadow-sm",
|
|
8040
|
+
className: cn("flex items-center gap-4 rounded-2xl border border-slate-200 bg-white px-5 py-4 shadow-sm", className),
|
|
7452
8041
|
children: [Icon && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
7453
8042
|
className: "flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-brand-navy/[0.06] text-brand-navy",
|
|
7454
8043
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
@@ -7473,10 +8062,11 @@ function Statistic({ label, value, helper, icon: Icon, trend, trendLabel, varian
|
|
|
7473
8062
|
]
|
|
7474
8063
|
})]
|
|
7475
8064
|
});
|
|
8065
|
+
const stripColor = variant === "accent" ? "bg-brand-blue" : "bg-brand-navy";
|
|
7476
8066
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7477
|
-
className: "relative overflow-hidden rounded-2xl border border-slate-200 bg-white shadow-sm",
|
|
8067
|
+
className: cn("relative overflow-hidden rounded-2xl border border-slate-200 bg-white shadow-sm", className),
|
|
7478
8068
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
7479
|
-
className: `absolute inset-y-0 left-0 w-1 ${
|
|
8069
|
+
className: `absolute inset-y-0 left-0 w-1 ${stripColor}`,
|
|
7480
8070
|
"aria-hidden": "true"
|
|
7481
8071
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7482
8072
|
className: "px-6 py-5",
|
|
@@ -7522,6 +8112,18 @@ function Statistic({ label, value, helper, icon: Icon, trend, trendLabel, varian
|
|
|
7522
8112
|
}
|
|
7523
8113
|
//#endregion
|
|
7524
8114
|
//#region src/ui-library/gallery/status-dot/StatusDot.tsx
|
|
8115
|
+
/**
|
|
8116
|
+
* StatusDot — Design System Component
|
|
8117
|
+
*
|
|
8118
|
+
* A coloured glowing dot paired with a label. Used in tables to indicate
|
|
8119
|
+
* account or entity status at a glance.
|
|
8120
|
+
*
|
|
8121
|
+
* Self-contained: uses inline styles so it works without any external CSS.
|
|
8122
|
+
*
|
|
8123
|
+
* Usage:
|
|
8124
|
+
* <StatusDot status="active" label="Active" />
|
|
8125
|
+
* <StatusDot status="locked" label="Locked" />
|
|
8126
|
+
*/
|
|
7525
8127
|
var DOT_STYLES = {
|
|
7526
8128
|
active: {
|
|
7527
8129
|
bg: "#10b981",
|
|
@@ -7543,15 +8145,7 @@ var DOT_STYLES = {
|
|
|
7543
8145
|
function StatusDot({ status, label, className = "" }) {
|
|
7544
8146
|
const dot = DOT_STYLES[status];
|
|
7545
8147
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7546
|
-
|
|
7547
|
-
display: "inline-flex",
|
|
7548
|
-
alignItems: "center",
|
|
7549
|
-
gap: 8,
|
|
7550
|
-
fontWeight: 600,
|
|
7551
|
-
fontSize: "0.8125rem",
|
|
7552
|
-
color: "#334155"
|
|
7553
|
-
},
|
|
7554
|
-
className,
|
|
8148
|
+
className: cn("inline-flex items-center gap-2 text-[0.8125rem] font-semibold text-slate-700", className),
|
|
7555
8149
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7556
8150
|
"aria-hidden": "true",
|
|
7557
8151
|
style: {
|
|
@@ -7622,7 +8216,7 @@ function applyColumnOrder(columns, order) {
|
|
|
7622
8216
|
for (const col of columns) if (!seen.has(col.key)) result.push(col);
|
|
7623
8217
|
return result;
|
|
7624
8218
|
}
|
|
7625
|
-
function ColumnManager({ columns, hiddenColumnKeys, columnOrder, onHiddenColumnKeysChange, onColumnOrderChange, onReset, triggerLabel = "Columns" }) {
|
|
8219
|
+
function ColumnManager({ columns, hiddenColumnKeys, columnOrder, onHiddenColumnKeysChange, onColumnOrderChange, onReset, triggerLabel = "Columns", className = "" }) {
|
|
7626
8220
|
const [open, setOpen] = (0, react.useState)(false);
|
|
7627
8221
|
const triggerRef = (0, react.useRef)(null);
|
|
7628
8222
|
const panelRef = (0, react.useRef)(null);
|
|
@@ -7666,7 +8260,7 @@ function ColumnManager({ columns, hiddenColumnKeys, columnOrder, onHiddenColumnK
|
|
|
7666
8260
|
onColumnOrderChange(next.map((c) => c.key));
|
|
7667
8261
|
}
|
|
7668
8262
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7669
|
-
className: "relative inline-block",
|
|
8263
|
+
className: cn("relative inline-block", className),
|
|
7670
8264
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
7671
8265
|
ref: triggerRef,
|
|
7672
8266
|
type: "button",
|
|
@@ -8023,6 +8617,7 @@ exports.RadioGroup = RadioGroup;
|
|
|
8023
8617
|
exports.RadioItem = RadioItem;
|
|
8024
8618
|
exports.ReportTemplate = ReportTemplate;
|
|
8025
8619
|
exports.STATUS_PAGE_PRESETS = STATUS_PAGE_PRESETS;
|
|
8620
|
+
exports.Search = Search;
|
|
8026
8621
|
exports.Section = Section;
|
|
8027
8622
|
exports.SegmentedControl = SegmentedControl;
|
|
8028
8623
|
exports.Select = Select;
|