@diwauhris/ui 1.7.3 → 1.7.6
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 +168 -7
- package/lib/index.cjs +906 -335
- package/lib/index.mjs +906 -335
- 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/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 -2
package/lib/index.mjs
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
-
import React, { cloneElement, createContext, createElement, isValidElement, use, useCallback, useContext, useEffect, useId, useMemo, useRef, useState } from "react";
|
|
3
1
|
import { clsx } from "clsx";
|
|
4
2
|
import { twMerge } from "tailwind-merge";
|
|
5
|
-
import
|
|
3
|
+
import * as React$1 from "react";
|
|
4
|
+
import React, { cloneElement, createContext, createElement, isValidElement, use, useCallback, useContext, useEffect, useId, useMemo, useRef, useState } from "react";
|
|
5
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
import { createPortal } from "react-dom";
|
|
7
|
+
import { AlertCircle, AlertTriangle, Archive, ArchiveRestore, ArrowDown, Ban, CalendarDays, Check, CheckCircle2, ChevronDown, ChevronLeft, ChevronRight, Clock, Command, File, Home, Info, Minus, RotateCcw, Search as Search$1, ShieldCheck, TrendingDown, TrendingUp, UploadCloud, X, ZoomIn, ZoomOut } from "lucide-react";
|
|
6
8
|
import Calendar$1 from "react-calendar";
|
|
7
9
|
import { format, isValid, parse } from "date-fns";
|
|
8
10
|
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
|
9
|
-
import { createPortal } from "react-dom";
|
|
10
11
|
import * as PopoverPrimitive from "@radix-ui/react-popover";
|
|
11
12
|
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
|
12
13
|
import { Link } from "react-router-dom";
|
|
@@ -14,17 +15,169 @@ import * as d3 from "d3";
|
|
|
14
15
|
import { Toaster, toast } from "sonner";
|
|
15
16
|
export * from "lucide-react";
|
|
16
17
|
//#region \0rolldown/runtime.js
|
|
17
|
-
var __defProp = Object.defineProperty;
|
|
18
|
+
var __defProp$2 = Object.defineProperty;
|
|
18
19
|
var __exportAll = (all, no_symbols) => {
|
|
19
20
|
let target = {};
|
|
20
|
-
for (var name in all) __defProp(target, name, {
|
|
21
|
+
for (var name in all) __defProp$2(target, name, {
|
|
21
22
|
get: all[name],
|
|
22
23
|
enumerable: true
|
|
23
24
|
});
|
|
24
|
-
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
25
|
+
if (!no_symbols) __defProp$2(target, Symbol.toStringTag, { value: "Module" });
|
|
25
26
|
return target;
|
|
26
27
|
};
|
|
27
28
|
//#endregion
|
|
29
|
+
//#region src/lib/utils.ts
|
|
30
|
+
function cn(...inputs) {
|
|
31
|
+
return twMerge(clsx(inputs));
|
|
32
|
+
}
|
|
33
|
+
//#endregion
|
|
34
|
+
//#region node_modules/@radix-ui/react-compose-refs/dist/index.mjs
|
|
35
|
+
var __defProp$1 = Object.defineProperty;
|
|
36
|
+
var __name$1 = (target, value) => __defProp$1(target, "name", {
|
|
37
|
+
value,
|
|
38
|
+
configurable: true
|
|
39
|
+
});
|
|
40
|
+
function setRef(ref, value) {
|
|
41
|
+
if (typeof ref === "function") return ref(value);
|
|
42
|
+
else if (ref !== null && ref !== void 0) ref.current = value;
|
|
43
|
+
}
|
|
44
|
+
__name$1(setRef, "setRef");
|
|
45
|
+
function composeRefs$1(...refs) {
|
|
46
|
+
return (node) => {
|
|
47
|
+
let hasCleanup = false;
|
|
48
|
+
const cleanups = refs.map((ref) => {
|
|
49
|
+
const cleanup = setRef(ref, node);
|
|
50
|
+
if (!hasCleanup && typeof cleanup == "function") hasCleanup = true;
|
|
51
|
+
return cleanup;
|
|
52
|
+
});
|
|
53
|
+
if (hasCleanup) return () => {
|
|
54
|
+
for (let i = 0; i < cleanups.length; i++) {
|
|
55
|
+
const cleanup = cleanups[i];
|
|
56
|
+
if (typeof cleanup == "function") cleanup();
|
|
57
|
+
else setRef(refs[i], null);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
__name$1(composeRefs$1, "composeRefs");
|
|
63
|
+
function useComposedRefs(...refs) {
|
|
64
|
+
return React$1.useCallback(composeRefs$1(...refs), refs);
|
|
65
|
+
}
|
|
66
|
+
__name$1(useComposedRefs, "useComposedRefs");
|
|
67
|
+
//#endregion
|
|
68
|
+
//#region node_modules/@radix-ui/react-slot/dist/index.mjs
|
|
69
|
+
var __defProp = Object.defineProperty;
|
|
70
|
+
var __name = (target, value) => __defProp(target, "name", {
|
|
71
|
+
value,
|
|
72
|
+
configurable: true
|
|
73
|
+
});
|
|
74
|
+
// @__NO_SIDE_EFFECTS__
|
|
75
|
+
function createSlot(ownerName) {
|
|
76
|
+
const Slot2 = React$1.forwardRef((props, forwardedRef) => {
|
|
77
|
+
let { children, ...slotProps } = props;
|
|
78
|
+
let slottableElement = null;
|
|
79
|
+
let hasSlottable = false;
|
|
80
|
+
const newChildren = [];
|
|
81
|
+
if (isLazyComponent(children) && typeof use$1 === "function") children = use$1(children._payload);
|
|
82
|
+
React$1.Children.forEach(children, (maybeSlottable) => {
|
|
83
|
+
if (isSlottable(maybeSlottable)) {
|
|
84
|
+
hasSlottable = true;
|
|
85
|
+
const slottable = maybeSlottable;
|
|
86
|
+
let child = "child" in slottable.props ? slottable.props.child : slottable.props.children;
|
|
87
|
+
if (isLazyComponent(child) && typeof use$1 === "function") child = use$1(child._payload);
|
|
88
|
+
slottableElement = getSlottableElementFromSlottable(slottable, child);
|
|
89
|
+
newChildren.push(slottableElement?.props?.children);
|
|
90
|
+
} else newChildren.push(maybeSlottable);
|
|
91
|
+
});
|
|
92
|
+
if (slottableElement) slottableElement = React$1.cloneElement(slottableElement, void 0, newChildren);
|
|
93
|
+
else if (!hasSlottable && React$1.Children.count(children) === 1 && React$1.isValidElement(children)) slottableElement = children;
|
|
94
|
+
const slottableElementRef = slottableElement ? getElementRef(slottableElement) : void 0;
|
|
95
|
+
const composedRef = useComposedRefs(forwardedRef, slottableElementRef);
|
|
96
|
+
if (!slottableElement) {
|
|
97
|
+
if (children || children === 0) throw new Error(hasSlottable ? createSlottableError(ownerName) : createSlotError(ownerName));
|
|
98
|
+
return children;
|
|
99
|
+
}
|
|
100
|
+
const mergedProps = mergeProps(slotProps, slottableElement.props ?? {});
|
|
101
|
+
if (slottableElement.type !== React$1.Fragment) mergedProps.ref = forwardedRef ? composedRef : slottableElementRef;
|
|
102
|
+
return React$1.cloneElement(slottableElement, mergedProps);
|
|
103
|
+
});
|
|
104
|
+
Slot2.displayName = `${ownerName}.Slot`;
|
|
105
|
+
return Slot2;
|
|
106
|
+
}
|
|
107
|
+
__name(createSlot, "createSlot");
|
|
108
|
+
var Slot = /* @__PURE__ */ createSlot("Slot");
|
|
109
|
+
var SLOTTABLE_IDENTIFIER = Symbol.for("radix.slottable");
|
|
110
|
+
// @__NO_SIDE_EFFECTS__
|
|
111
|
+
function createSlottable(ownerName) {
|
|
112
|
+
const Slottable2 = /* @__PURE__ */ __name((props) => "child" in props ? props.children(props.child) : props.children, "Slottable");
|
|
113
|
+
Slottable2.displayName = `${ownerName}.Slottable`;
|
|
114
|
+
Slottable2.__radixId = SLOTTABLE_IDENTIFIER;
|
|
115
|
+
return Slottable2;
|
|
116
|
+
}
|
|
117
|
+
__name(createSlottable, "createSlottable");
|
|
118
|
+
var getSlottableElementFromSlottable = /* @__PURE__ */ __name((slottable, child) => {
|
|
119
|
+
if ("child" in slottable.props) {
|
|
120
|
+
const child2 = slottable.props.child;
|
|
121
|
+
if (!React$1.isValidElement(child2)) return null;
|
|
122
|
+
return React$1.cloneElement(child2, void 0, slottable.props.children(child2.props.children));
|
|
123
|
+
}
|
|
124
|
+
return React$1.isValidElement(child) ? child : null;
|
|
125
|
+
}, "getSlottableElementFromSlottable");
|
|
126
|
+
function mergeProps(slotProps, childProps) {
|
|
127
|
+
const overrideProps = { ...childProps };
|
|
128
|
+
for (const propName in childProps) {
|
|
129
|
+
const slotPropValue = slotProps[propName];
|
|
130
|
+
const childPropValue = childProps[propName];
|
|
131
|
+
if (/^on[A-Z]/.test(propName)) {
|
|
132
|
+
if (slotPropValue && childPropValue) overrideProps[propName] = (...args) => {
|
|
133
|
+
const result = childPropValue(...args);
|
|
134
|
+
slotPropValue(...args);
|
|
135
|
+
return result;
|
|
136
|
+
};
|
|
137
|
+
else if (slotPropValue) overrideProps[propName] = slotPropValue;
|
|
138
|
+
} else if (propName === "style") overrideProps[propName] = {
|
|
139
|
+
...slotPropValue,
|
|
140
|
+
...childPropValue
|
|
141
|
+
};
|
|
142
|
+
else if (propName === "className") overrideProps[propName] = [slotPropValue, childPropValue].filter(Boolean).join(" ");
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
...slotProps,
|
|
146
|
+
...overrideProps
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
__name(mergeProps, "mergeProps");
|
|
150
|
+
function getElementRef(element) {
|
|
151
|
+
let getter = Object.getOwnPropertyDescriptor(element.props, "ref")?.get;
|
|
152
|
+
let mayWarn = getter && "isReactWarning" in getter && getter.isReactWarning;
|
|
153
|
+
if (mayWarn) return element.ref;
|
|
154
|
+
getter = Object.getOwnPropertyDescriptor(element, "ref")?.get;
|
|
155
|
+
mayWarn = getter && "isReactWarning" in getter && getter.isReactWarning;
|
|
156
|
+
if (mayWarn) return element.props.ref;
|
|
157
|
+
return element.props.ref || element.ref;
|
|
158
|
+
}
|
|
159
|
+
__name(getElementRef, "getElementRef");
|
|
160
|
+
function isSlottable(child) {
|
|
161
|
+
return React$1.isValidElement(child) && typeof child.type === "function" && "__radixId" in child.type && child.type.__radixId === SLOTTABLE_IDENTIFIER;
|
|
162
|
+
}
|
|
163
|
+
__name(isSlottable, "isSlottable");
|
|
164
|
+
var REACT_LAZY_TYPE = Symbol.for("react.lazy");
|
|
165
|
+
function isLazyComponent(element) {
|
|
166
|
+
return element != null && typeof element === "object" && "$$typeof" in element && element.$$typeof === REACT_LAZY_TYPE && "_payload" in element && isPromiseLike(element._payload);
|
|
167
|
+
}
|
|
168
|
+
__name(isLazyComponent, "isLazyComponent");
|
|
169
|
+
function isPromiseLike(value) {
|
|
170
|
+
return typeof value === "object" && value !== null && "then" in value;
|
|
171
|
+
}
|
|
172
|
+
__name(isPromiseLike, "isPromiseLike");
|
|
173
|
+
var createSlotError = /* @__PURE__ */ __name((ownerName) => {
|
|
174
|
+
return `${ownerName} failed to slot onto its children. Expected a single React element child or \`Slottable\`.`;
|
|
175
|
+
}, "createSlotError");
|
|
176
|
+
var createSlottableError = /* @__PURE__ */ __name((ownerName) => {
|
|
177
|
+
return `${ownerName} failed to slot onto its \`Slottable\`. Expected \`Slottable\` to receive a single React element child.`;
|
|
178
|
+
}, "createSlottableError");
|
|
179
|
+
var use$1 = React$1[" use ".trim().toString()];
|
|
180
|
+
//#endregion
|
|
28
181
|
//#region src/ui-library/gallery/button/Button.tsx
|
|
29
182
|
var VARIANT_STYLES$1 = {
|
|
30
183
|
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",
|
|
@@ -39,23 +192,17 @@ var SIZE_STYLES$1 = {
|
|
|
39
192
|
lg: "h-11 gap-2.5 rounded-md px-6 text-base"
|
|
40
193
|
};
|
|
41
194
|
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";
|
|
42
|
-
function Button({ variant = "primary", size = "md", loading = false, leftIcon, rightIcon, fullWidth = false, children, className = "", disabled, ...props }) {
|
|
43
|
-
return /* @__PURE__ */
|
|
44
|
-
type: "button",
|
|
45
|
-
disabled: disabled || loading,
|
|
46
|
-
className: [
|
|
47
|
-
BASE$1,
|
|
48
|
-
VARIANT_STYLES$1[variant],
|
|
49
|
-
SIZE_STYLES$1[size],
|
|
50
|
-
fullWidth ? "w-full" : "",
|
|
51
|
-
className
|
|
52
|
-
].filter(Boolean).join(" "),
|
|
195
|
+
function Button({ variant = "primary", size = "md", loading = false, leftIcon, rightIcon, fullWidth = false, children, className = "", disabled, asChild = false, ...props }) {
|
|
196
|
+
return /* @__PURE__ */ jsx(asChild ? Slot : "button", {
|
|
197
|
+
type: asChild ? void 0 : "button",
|
|
198
|
+
disabled: asChild ? void 0 : disabled || loading,
|
|
199
|
+
className: cn(BASE$1, VARIANT_STYLES$1[variant], SIZE_STYLES$1[size], fullWidth && "w-full", className),
|
|
53
200
|
...props,
|
|
54
|
-
children: [
|
|
201
|
+
children: asChild ? children : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
55
202
|
leftIcon && !loading && leftIcon,
|
|
56
203
|
children,
|
|
57
204
|
rightIcon && !loading && rightIcon
|
|
58
|
-
]
|
|
205
|
+
] })
|
|
59
206
|
});
|
|
60
207
|
}
|
|
61
208
|
//#endregion
|
|
@@ -66,14 +213,10 @@ var VARIANT_STYLES = {
|
|
|
66
213
|
edit: "hover:bg-blue-50 hover:text-brand-blue focus-visible:ring-brand-blue/20",
|
|
67
214
|
danger: "hover:bg-rose-50 hover:text-rose-600 focus-visible:ring-rose-100"
|
|
68
215
|
};
|
|
69
|
-
function IconButton({ icon, variant = "default", className = "", ...props }) {
|
|
70
|
-
return /* @__PURE__ */ jsx("button", {
|
|
71
|
-
type: "button",
|
|
72
|
-
className: [
|
|
73
|
-
BASE,
|
|
74
|
-
VARIANT_STYLES[variant],
|
|
75
|
-
className
|
|
76
|
-
].filter(Boolean).join(" "),
|
|
216
|
+
function IconButton({ icon, variant = "default", className = "", asChild = false, ...props }) {
|
|
217
|
+
return /* @__PURE__ */ jsx(asChild ? Slot : "button", {
|
|
218
|
+
type: asChild ? void 0 : "button",
|
|
219
|
+
className: cn(BASE, VARIANT_STYLES[variant], className),
|
|
77
220
|
...props,
|
|
78
221
|
children: icon
|
|
79
222
|
});
|
|
@@ -103,18 +246,28 @@ var SIZE_STYLES = {
|
|
|
103
246
|
};
|
|
104
247
|
function Badge({ tone = "neutral", size = "md", icon, children, className = "" }) {
|
|
105
248
|
return /* @__PURE__ */ jsxs("span", {
|
|
106
|
-
className: [
|
|
107
|
-
"inline-flex items-center border font-semibold uppercase tracking-wide",
|
|
108
|
-
STATUS_TONES.has(tone) ? "rounded" : "rounded-full",
|
|
109
|
-
TONE_STYLES$1[tone],
|
|
110
|
-
SIZE_STYLES[size],
|
|
111
|
-
className
|
|
112
|
-
].filter(Boolean).join(" "),
|
|
249
|
+
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),
|
|
113
250
|
children: [icon, children]
|
|
114
251
|
});
|
|
115
252
|
}
|
|
116
253
|
//#endregion
|
|
117
254
|
//#region src/ui-library/gallery/spinner/Spinner.tsx
|
|
255
|
+
/**
|
|
256
|
+
* Spinner — Design System Component
|
|
257
|
+
*
|
|
258
|
+
* An indeterminate loading indicator. Purely presentational.
|
|
259
|
+
*
|
|
260
|
+
* Sizes: sm | md | lg
|
|
261
|
+
*
|
|
262
|
+
* Usage:
|
|
263
|
+
* - Inline: inside a button or alongside text
|
|
264
|
+
* - Standalone: centered in a container while content loads
|
|
265
|
+
*
|
|
266
|
+
* Accessibility:
|
|
267
|
+
* - Renders a <span role="status"> that announces label to screen readers
|
|
268
|
+
* - label is required and should describe what is loading
|
|
269
|
+
* - Respects prefers-reduced-motion: animation is suppressed, label stays
|
|
270
|
+
*/
|
|
118
271
|
var SIZE_TRACK = {
|
|
119
272
|
sm: "h-4 w-4 border-2",
|
|
120
273
|
md: "h-6 w-6 border-2",
|
|
@@ -128,7 +281,7 @@ var SIZE_TEXT = {
|
|
|
128
281
|
function Spinner({ size = "md", label = "Loading…", labelHidden = false, className = "" }) {
|
|
129
282
|
return /* @__PURE__ */ jsxs("span", {
|
|
130
283
|
role: "status",
|
|
131
|
-
className:
|
|
284
|
+
className: cn("inline-flex items-center gap-2", className),
|
|
132
285
|
children: [/* @__PURE__ */ jsx("span", {
|
|
133
286
|
"aria-hidden": "true",
|
|
134
287
|
className: ["inline-block animate-spin rounded-full border-slate-200 border-t-brand-blue motion-reduce:animate-none", SIZE_TRACK[size]].join(" ")
|
|
@@ -140,6 +293,25 @@ function Spinner({ size = "md", label = "Loading…", labelHidden = false, class
|
|
|
140
293
|
}
|
|
141
294
|
//#endregion
|
|
142
295
|
//#region src/ui-library/gallery/divider/Divider.tsx
|
|
296
|
+
/**
|
|
297
|
+
* Divider — Design System Component
|
|
298
|
+
*
|
|
299
|
+
* A thin visual separator between content areas.
|
|
300
|
+
*
|
|
301
|
+
* Decorative usage (default, aria-hidden="true"):
|
|
302
|
+
* Used purely for visual grouping. Screen readers skip it.
|
|
303
|
+
* This is the appropriate choice for most cases.
|
|
304
|
+
*
|
|
305
|
+
* Semantic usage (role="separator"):
|
|
306
|
+
* Use when the separation is meaningful to AT users, e.g. between
|
|
307
|
+
* logically distinct regions where the boundary matters.
|
|
308
|
+
* Provide aria-label when context is needed.
|
|
309
|
+
*
|
|
310
|
+
* Orientation:
|
|
311
|
+
* - horizontal (default): full-width horizontal line, displayed as block.
|
|
312
|
+
* - vertical: full-height vertical line, displayed inline. The parent
|
|
313
|
+
* container must give it an explicit height or use flex/grid context.
|
|
314
|
+
*/
|
|
143
315
|
function Divider({ orientation = "horizontal", decorative = true, "aria-label": ariaLabel, className = "" }) {
|
|
144
316
|
const isHorizontal = orientation === "horizontal";
|
|
145
317
|
return /* @__PURE__ */ jsx("div", {
|
|
@@ -148,7 +320,7 @@ function Divider({ orientation = "horizontal", decorative = true, "aria-label":
|
|
|
148
320
|
"aria-label": ariaLabel,
|
|
149
321
|
"aria-orientation": orientation
|
|
150
322
|
},
|
|
151
|
-
className:
|
|
323
|
+
className: cn(isHorizontal ? "w-full border-t border-slate-200" : "self-stretch border-l border-slate-200", className)
|
|
152
324
|
});
|
|
153
325
|
}
|
|
154
326
|
//#endregion
|
|
@@ -212,12 +384,7 @@ function Avatar({ name, src, size = "md", color, className = "" }) {
|
|
|
212
384
|
const bg = color ?? deriveColor(name);
|
|
213
385
|
const initials = getInitials(name);
|
|
214
386
|
const label = name || "Unknown";
|
|
215
|
-
const baseClass = [
|
|
216
|
-
"inline-flex items-center justify-center rounded-full shrink-0",
|
|
217
|
-
"font-bold text-white select-none overflow-hidden",
|
|
218
|
-
SIZE_CLASSES[size],
|
|
219
|
-
className
|
|
220
|
-
].filter(Boolean).join(" ");
|
|
387
|
+
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);
|
|
221
388
|
if (showImage) return /* @__PURE__ */ jsx("img", {
|
|
222
389
|
src,
|
|
223
390
|
alt: label,
|
|
@@ -238,7 +405,7 @@ function Section({ title, description, actions, level = 2, children, className =
|
|
|
238
405
|
const Tag = `h${level}`;
|
|
239
406
|
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";
|
|
240
407
|
return /* @__PURE__ */ jsxs("section", {
|
|
241
|
-
className:
|
|
408
|
+
className: cn("space-y-4", className),
|
|
242
409
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
243
410
|
className: "flex items-start justify-between gap-4",
|
|
244
411
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
@@ -261,7 +428,7 @@ function Section({ title, description, actions, level = 2, children, className =
|
|
|
261
428
|
//#region src/ui-library/gallery/page-header/PageHeader.tsx
|
|
262
429
|
function PageHeader({ title, subtitle, icon, actions, className = "" }) {
|
|
263
430
|
return /* @__PURE__ */ jsx("div", {
|
|
264
|
-
className,
|
|
431
|
+
className: cn(className),
|
|
265
432
|
children: /* @__PURE__ */ jsxs("div", {
|
|
266
433
|
className: "flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between",
|
|
267
434
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
@@ -290,11 +457,7 @@ function PageHeader({ title, subtitle, icon, actions, className = "" }) {
|
|
|
290
457
|
//#region src/ui-library/gallery/toolbar/Toolbar.tsx
|
|
291
458
|
function Toolbar({ left, right, className = "" }) {
|
|
292
459
|
return /* @__PURE__ */ jsxs("div", {
|
|
293
|
-
className:
|
|
294
|
-
"w-full flex flex-wrap items-center justify-between gap-4",
|
|
295
|
-
"bg-white p-2 border border-slate-100 rounded-2xl shadow-sm",
|
|
296
|
-
className
|
|
297
|
-
].filter(Boolean).join(" "),
|
|
460
|
+
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),
|
|
298
461
|
children: [left, right]
|
|
299
462
|
});
|
|
300
463
|
}
|
|
@@ -322,7 +485,7 @@ function useFieldContext() {
|
|
|
322
485
|
* FieldInput inside this wrapper automatically receive the correct id,
|
|
323
486
|
* aria-required, aria-invalid, and aria-describedby — with no prop threading.
|
|
324
487
|
*/
|
|
325
|
-
function Field({ label, required = false, error, children, inputId }) {
|
|
488
|
+
function Field({ label, required = false, error, children, inputId, className = "" }) {
|
|
326
489
|
const generatedId = useId();
|
|
327
490
|
const fieldId = inputId ?? generatedId;
|
|
328
491
|
const errorId = `${fieldId}-error`;
|
|
@@ -334,7 +497,7 @@ function Field({ label, required = false, error, children, inputId }) {
|
|
|
334
497
|
invalid: !!error
|
|
335
498
|
},
|
|
336
499
|
children: /* @__PURE__ */ jsxs("div", {
|
|
337
|
-
className:
|
|
500
|
+
className: cn("flex flex-col gap-1.5", error && "shake", className),
|
|
338
501
|
children: [
|
|
339
502
|
/* @__PURE__ */ jsxs("label", {
|
|
340
503
|
htmlFor: fieldId,
|
|
@@ -361,11 +524,6 @@ function Field({ label, required = false, error, children, inputId }) {
|
|
|
361
524
|
});
|
|
362
525
|
}
|
|
363
526
|
//#endregion
|
|
364
|
-
//#region src/lib/utils.ts
|
|
365
|
-
function cn(...inputs) {
|
|
366
|
-
return twMerge(clsx(inputs));
|
|
367
|
-
}
|
|
368
|
-
//#endregion
|
|
369
527
|
//#region src/ui-library/gallery/field-input/FieldInput.tsx
|
|
370
528
|
/**
|
|
371
529
|
* FieldInput — Gallery-native context-aware <input> wrapper.
|
|
@@ -373,13 +531,14 @@ function cn(...inputs) {
|
|
|
373
531
|
* Updated to import FieldContext from gallery-local field directory
|
|
374
532
|
* and to carry full DIWA visual styling.
|
|
375
533
|
*/
|
|
376
|
-
function FieldInput({ id, "aria-required": ariaRequired, "aria-invalid": ariaInvalid, "aria-describedby": ariaDescribedBy, className = "", disabled, ...rest }) {
|
|
534
|
+
function FieldInput({ id, "aria-required": ariaRequired, "aria-invalid": ariaInvalid, "aria-describedby": ariaDescribedBy, className = "", disabled, ref, ...rest }) {
|
|
377
535
|
const ctx = useFieldContext();
|
|
378
536
|
const resolvedId = id ?? ctx?.inputId;
|
|
379
537
|
const resolvedRequired = ariaRequired ?? (ctx?.required ? "true" : void 0);
|
|
380
538
|
const resolvedInvalid = ariaInvalid ?? (ctx?.invalid ? "true" : void 0);
|
|
381
539
|
const resolvedDescribedBy = ariaDescribedBy ?? (ctx?.invalid && ctx?.errorId ? ctx.errorId : void 0);
|
|
382
540
|
return /* @__PURE__ */ jsx("input", {
|
|
541
|
+
ref,
|
|
383
542
|
id: resolvedId,
|
|
384
543
|
"aria-required": resolvedRequired,
|
|
385
544
|
"aria-invalid": resolvedInvalid,
|
|
@@ -414,22 +573,18 @@ var FIELD_HELPER_COLOR = {
|
|
|
414
573
|
};
|
|
415
574
|
//#endregion
|
|
416
575
|
//#region src/ui-library/gallery/input/Input.tsx
|
|
417
|
-
function Input({ status = "default", helper, prefix, suffix, disabled, className = "", ...props }) {
|
|
576
|
+
function Input({ status = "default", helper, prefix, suffix, disabled, className = "", ref, ...props }) {
|
|
418
577
|
return /* @__PURE__ */ jsxs("div", {
|
|
419
578
|
className: "w-full",
|
|
420
579
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
421
|
-
className: [
|
|
422
|
-
"flex w-full items-center rounded-md border bg-white transition",
|
|
423
|
-
FIELD_STATUS_RING_WITHIN[status],
|
|
424
|
-
disabled ? "cursor-not-allowed bg-slate-50 opacity-60" : "",
|
|
425
|
-
className
|
|
426
|
-
].filter(Boolean).join(" "),
|
|
580
|
+
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),
|
|
427
581
|
children: [
|
|
428
582
|
prefix && /* @__PURE__ */ jsx("span", {
|
|
429
583
|
className: "flex shrink-0 items-center pl-3.5 text-slate-400",
|
|
430
584
|
children: prefix
|
|
431
585
|
}),
|
|
432
586
|
/* @__PURE__ */ jsx("input", {
|
|
587
|
+
ref,
|
|
433
588
|
disabled,
|
|
434
589
|
className: [
|
|
435
590
|
"w-full bg-transparent py-2.5 text-sm font-medium text-slate-900 outline-none placeholder:text-slate-400",
|
|
@@ -463,7 +618,7 @@ var RESIZE_CLASS = {
|
|
|
463
618
|
vertical: "resize-y",
|
|
464
619
|
both: "resize"
|
|
465
620
|
};
|
|
466
|
-
function Textarea({ status = "default", helper, maxLength, resize = "vertical", disabled, value, defaultValue, onChange, className = "", ...props }) {
|
|
621
|
+
function Textarea({ status = "default", helper, maxLength, resize = "vertical", disabled, value, defaultValue, onChange, className = "", ref, ...props }) {
|
|
467
622
|
const isControlled = value !== void 0;
|
|
468
623
|
const [uncontrolledLength, setUncontrolledLength] = useState(typeof defaultValue === "string" ? defaultValue.length : 0);
|
|
469
624
|
const currentLength = isControlled ? typeof value === "string" ? value.length : 0 : uncontrolledLength;
|
|
@@ -474,18 +629,13 @@ function Textarea({ status = "default", helper, maxLength, resize = "vertical",
|
|
|
474
629
|
return /* @__PURE__ */ jsxs("div", {
|
|
475
630
|
className: "w-full",
|
|
476
631
|
children: [/* @__PURE__ */ jsx("textarea", {
|
|
632
|
+
ref,
|
|
477
633
|
maxLength,
|
|
478
634
|
disabled,
|
|
479
635
|
value,
|
|
480
636
|
defaultValue,
|
|
481
637
|
onChange: handleChange,
|
|
482
|
-
className: [
|
|
483
|
-
"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",
|
|
484
|
-
FIELD_STATUS_RING_DIRECT[status],
|
|
485
|
-
RESIZE_CLASS[resize],
|
|
486
|
-
disabled ? "cursor-not-allowed bg-slate-50 opacity-60" : "",
|
|
487
|
-
className
|
|
488
|
-
].filter(Boolean).join(" "),
|
|
638
|
+
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),
|
|
489
639
|
...props
|
|
490
640
|
}), (helper || maxLength !== void 0) && /* @__PURE__ */ jsxs("div", {
|
|
491
641
|
className: "mt-1.5 flex items-start justify-between gap-2",
|
|
@@ -519,23 +669,227 @@ function Textarea({ status = "default", helper, maxLength, resize = "vertical",
|
|
|
519
669
|
* - For searchable/multi-select use cases, use Combobox (Phase 3).
|
|
520
670
|
*
|
|
521
671
|
* The label is NOT included. The consuming feature pairs a <label>.
|
|
672
|
+
*
|
|
673
|
+
* When renderOption is provided, a custom dropdown is rendered instead
|
|
674
|
+
* of the native <select>. Only use this when native option rendering
|
|
675
|
+
* is insufficient (e.g., icons, avatars, multi-line content).
|
|
522
676
|
*/
|
|
523
677
|
function isGroup(item) {
|
|
524
678
|
return "group" in item;
|
|
525
679
|
}
|
|
526
|
-
function
|
|
680
|
+
function CustomDropdown({ items, value, placeholder, status, disabled, renderOption, onChange, id, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, className = "" }) {
|
|
681
|
+
const [open, setOpen] = useState(false);
|
|
682
|
+
const [highlightIndex, setHighlightIndex] = useState(-1);
|
|
683
|
+
const containerRef = useRef(null);
|
|
684
|
+
const triggerRef = useRef(null);
|
|
685
|
+
const listboxId = useId();
|
|
686
|
+
const triggerId = useId();
|
|
687
|
+
const optionId = (index) => `${listboxId}-opt-${index}`;
|
|
688
|
+
const [panelStyle, setPanelStyle] = useState({});
|
|
689
|
+
const flatItems = [];
|
|
690
|
+
for (const item of items) if (isGroup(item)) {
|
|
691
|
+
flatItems.push({
|
|
692
|
+
type: "group",
|
|
693
|
+
label: item.group
|
|
694
|
+
});
|
|
695
|
+
for (const opt of item.options) flatItems.push({
|
|
696
|
+
type: "option",
|
|
697
|
+
opt
|
|
698
|
+
});
|
|
699
|
+
} else flatItems.push({
|
|
700
|
+
type: "option",
|
|
701
|
+
opt: item
|
|
702
|
+
});
|
|
703
|
+
const flatOptions = flatItems.filter((fi) => fi.type === "option").map((fi) => fi.opt);
|
|
704
|
+
const selectedOption = flatOptions.find((o) => String(o.value) === String(value ?? ""));
|
|
705
|
+
const displayLabel = selectedOption?.label ?? placeholder ?? "";
|
|
706
|
+
const hasValue = Boolean(selectedOption);
|
|
707
|
+
useEffect(() => {
|
|
708
|
+
if (!open) setHighlightIndex(-1);
|
|
709
|
+
}, [open]);
|
|
710
|
+
const listRef = useRef(null);
|
|
711
|
+
useEffect(() => {
|
|
712
|
+
if (highlightIndex >= 0 && listRef.current) listRef.current.querySelector(`[id="${optionId(highlightIndex)}"]`)?.scrollIntoView({ block: "nearest" });
|
|
713
|
+
}, [highlightIndex]);
|
|
714
|
+
useEffect(() => {
|
|
715
|
+
if (!open) return;
|
|
716
|
+
const handlePointerDown = (e) => {
|
|
717
|
+
if (containerRef.current && !containerRef.current.contains(e.target)) setOpen(false);
|
|
718
|
+
};
|
|
719
|
+
document.addEventListener("mousedown", handlePointerDown);
|
|
720
|
+
return () => document.removeEventListener("mousedown", handlePointerDown);
|
|
721
|
+
}, [open]);
|
|
722
|
+
useEffect(() => {
|
|
723
|
+
if (!open || !triggerRef.current) return;
|
|
724
|
+
const rect = triggerRef.current.getBoundingClientRect();
|
|
725
|
+
setPanelStyle({
|
|
726
|
+
position: "fixed",
|
|
727
|
+
top: rect.bottom + 4,
|
|
728
|
+
left: rect.left,
|
|
729
|
+
zIndex: 9999,
|
|
730
|
+
width: rect.width
|
|
731
|
+
});
|
|
732
|
+
}, [open]);
|
|
733
|
+
const handleSelect = useCallback((opt) => {
|
|
734
|
+
if (opt.disabled) return;
|
|
735
|
+
const syntheticEvent = {
|
|
736
|
+
target: { value: opt.value },
|
|
737
|
+
currentTarget: { value: opt.value }
|
|
738
|
+
};
|
|
739
|
+
onChange?.(syntheticEvent);
|
|
740
|
+
setOpen(false);
|
|
741
|
+
triggerRef.current?.focus();
|
|
742
|
+
}, [onChange]);
|
|
743
|
+
const handleKeyDown = (e) => {
|
|
744
|
+
const nextEnabled = (from, dir) => {
|
|
745
|
+
let idx = from;
|
|
746
|
+
while (idx >= 0 && idx < flatOptions.length) {
|
|
747
|
+
if (!flatOptions[idx]?.disabled) return idx;
|
|
748
|
+
idx += dir;
|
|
749
|
+
}
|
|
750
|
+
return -1;
|
|
751
|
+
};
|
|
752
|
+
switch (e.key) {
|
|
753
|
+
case "ArrowDown":
|
|
754
|
+
e.preventDefault();
|
|
755
|
+
if (!open) {
|
|
756
|
+
setOpen(true);
|
|
757
|
+
const first = nextEnabled(0, 1);
|
|
758
|
+
setHighlightIndex(first);
|
|
759
|
+
} else setHighlightIndex((prev) => {
|
|
760
|
+
const next = nextEnabled(prev + 1, 1);
|
|
761
|
+
return next === -1 ? prev : next;
|
|
762
|
+
});
|
|
763
|
+
break;
|
|
764
|
+
case "ArrowUp":
|
|
765
|
+
e.preventDefault();
|
|
766
|
+
if (open) setHighlightIndex((prev) => {
|
|
767
|
+
const next = nextEnabled(prev - 1, -1);
|
|
768
|
+
return next === -1 ? prev : next;
|
|
769
|
+
});
|
|
770
|
+
break;
|
|
771
|
+
case "Home":
|
|
772
|
+
e.preventDefault();
|
|
773
|
+
if (open) {
|
|
774
|
+
const first = nextEnabled(0, 1);
|
|
775
|
+
setHighlightIndex(first);
|
|
776
|
+
}
|
|
777
|
+
break;
|
|
778
|
+
case "End":
|
|
779
|
+
e.preventDefault();
|
|
780
|
+
if (open) {
|
|
781
|
+
const last = nextEnabled(flatOptions.length - 1, -1);
|
|
782
|
+
setHighlightIndex(last);
|
|
783
|
+
}
|
|
784
|
+
break;
|
|
785
|
+
case "Enter":
|
|
786
|
+
case " ":
|
|
787
|
+
e.preventDefault();
|
|
788
|
+
if (!open) setOpen(true);
|
|
789
|
+
else if (highlightIndex >= 0 && flatOptions[highlightIndex] && !flatOptions[highlightIndex].disabled) handleSelect(flatOptions[highlightIndex]);
|
|
790
|
+
else setOpen(false);
|
|
791
|
+
break;
|
|
792
|
+
case "Escape":
|
|
793
|
+
e.preventDefault();
|
|
794
|
+
setOpen(false);
|
|
795
|
+
}
|
|
796
|
+
};
|
|
797
|
+
const effectiveTriggerId = id ?? triggerId;
|
|
798
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
799
|
+
className: "relative",
|
|
800
|
+
ref: containerRef,
|
|
801
|
+
children: [
|
|
802
|
+
/* @__PURE__ */ jsx("button", {
|
|
803
|
+
ref: triggerRef,
|
|
804
|
+
id: effectiveTriggerId,
|
|
805
|
+
type: "button",
|
|
806
|
+
role: "combobox",
|
|
807
|
+
"aria-haspopup": "listbox",
|
|
808
|
+
"aria-expanded": open,
|
|
809
|
+
"aria-controls": listboxId,
|
|
810
|
+
"aria-activedescendant": open && highlightIndex >= 0 ? optionId(highlightIndex) : void 0,
|
|
811
|
+
"aria-label": ariaLabel,
|
|
812
|
+
"aria-labelledby": ariaLabelledBy,
|
|
813
|
+
disabled,
|
|
814
|
+
onClick: () => setOpen((v) => !v),
|
|
815
|
+
onKeyDown: handleKeyDown,
|
|
816
|
+
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),
|
|
817
|
+
children: displayLabel
|
|
818
|
+
}),
|
|
819
|
+
/* @__PURE__ */ jsx("span", {
|
|
820
|
+
className: "pointer-events-none absolute right-3.5 top-1/2 -translate-y-1/2 text-slate-400",
|
|
821
|
+
"aria-hidden": "true",
|
|
822
|
+
children: /* @__PURE__ */ jsx(ChevronDown, {
|
|
823
|
+
size: 14,
|
|
824
|
+
className: open ? "rotate-180 transition-transform" : "transition-transform"
|
|
825
|
+
})
|
|
826
|
+
}),
|
|
827
|
+
open && !disabled && createPortal(/* @__PURE__ */ jsx("ul", {
|
|
828
|
+
ref: listRef,
|
|
829
|
+
id: listboxId,
|
|
830
|
+
role: "listbox",
|
|
831
|
+
"aria-labelledby": effectiveTriggerId,
|
|
832
|
+
style: panelStyle,
|
|
833
|
+
className: "overflow-y-auto max-h-60 rounded-xl border border-slate-200 bg-white py-1 shadow-xl",
|
|
834
|
+
children: flatItems.map((fi, i) => {
|
|
835
|
+
if (fi.type === "group") return /* @__PURE__ */ jsx("li", {
|
|
836
|
+
role: "presentation",
|
|
837
|
+
className: "px-3 py-1.5 text-[10px] font-bold uppercase tracking-widest text-slate-400",
|
|
838
|
+
children: fi.label
|
|
839
|
+
}, `group-${fi.label}-${i}`);
|
|
840
|
+
const opt = fi.opt;
|
|
841
|
+
const optIndex = flatOptions.indexOf(opt);
|
|
842
|
+
const isSelected = String(opt.value) === String(value ?? "");
|
|
843
|
+
const isHighlighted = optIndex === highlightIndex;
|
|
844
|
+
return /* @__PURE__ */ jsx("li", {
|
|
845
|
+
id: optionId(optIndex),
|
|
846
|
+
role: "option",
|
|
847
|
+
"aria-selected": isSelected,
|
|
848
|
+
"aria-disabled": opt.disabled,
|
|
849
|
+
onClick: () => handleSelect(opt),
|
|
850
|
+
className: [
|
|
851
|
+
"cursor-pointer px-3 py-2 text-sm transition-colors",
|
|
852
|
+
opt.disabled ? "cursor-not-allowed opacity-50" : "",
|
|
853
|
+
isHighlighted && !opt.disabled ? "bg-blue-50 font-semibold text-brand-blue" : "",
|
|
854
|
+
isSelected && !isHighlighted ? "bg-blue-50 font-semibold text-brand-blue" : "",
|
|
855
|
+
!isHighlighted && !isSelected && !opt.disabled ? "hover:bg-slate-50 text-slate-700" : "",
|
|
856
|
+
opt.disabled ? "text-slate-400" : ""
|
|
857
|
+
].filter(Boolean).join(" "),
|
|
858
|
+
children: renderOption(opt)
|
|
859
|
+
}, opt.value);
|
|
860
|
+
})
|
|
861
|
+
}), document.body)
|
|
862
|
+
]
|
|
863
|
+
});
|
|
864
|
+
}
|
|
865
|
+
function Select({ items, placeholder, status = "default", helper, disabled, className = "", renderOption, ref, ...props }) {
|
|
866
|
+
if (renderOption) return /* @__PURE__ */ jsxs("div", {
|
|
867
|
+
className: "w-full",
|
|
868
|
+
children: [/* @__PURE__ */ jsx(CustomDropdown, {
|
|
869
|
+
items,
|
|
870
|
+
value: props.value,
|
|
871
|
+
placeholder,
|
|
872
|
+
status,
|
|
873
|
+
disabled,
|
|
874
|
+
renderOption,
|
|
875
|
+
onChange: props.onChange,
|
|
876
|
+
id: props.id,
|
|
877
|
+
"aria-label": props["aria-label"],
|
|
878
|
+
"aria-labelledby": props["aria-labelledby"],
|
|
879
|
+
className
|
|
880
|
+
}), helper && /* @__PURE__ */ jsx("p", {
|
|
881
|
+
className: `mt-1.5 text-xs font-medium ${FIELD_HELPER_COLOR[status]}`,
|
|
882
|
+
children: helper
|
|
883
|
+
})]
|
|
884
|
+
});
|
|
527
885
|
return /* @__PURE__ */ jsxs("div", {
|
|
528
886
|
className: "w-full",
|
|
529
887
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
530
888
|
className: "relative",
|
|
531
889
|
children: [/* @__PURE__ */ jsxs("select", {
|
|
890
|
+
ref,
|
|
532
891
|
disabled,
|
|
533
|
-
className: [
|
|
534
|
-
"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",
|
|
535
|
-
FIELD_STATUS_RING_DIRECT[status],
|
|
536
|
-
disabled ? "cursor-not-allowed bg-slate-50 opacity-60" : "cursor-pointer",
|
|
537
|
-
className
|
|
538
|
-
].filter(Boolean).join(" "),
|
|
892
|
+
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),
|
|
539
893
|
...props,
|
|
540
894
|
children: [placeholder && /* @__PURE__ */ jsx("option", {
|
|
541
895
|
value: "",
|
|
@@ -565,6 +919,24 @@ function Select({ items, placeholder, status = "default", helper, disabled, clas
|
|
|
565
919
|
});
|
|
566
920
|
}
|
|
567
921
|
//#endregion
|
|
922
|
+
//#region src/lib/composeRefs.ts
|
|
923
|
+
/**
|
|
924
|
+
* Composes multiple React refs into one callback ref.
|
|
925
|
+
* Used when a component needs both an internal ref and a forwarded external ref.
|
|
926
|
+
*
|
|
927
|
+
* Example:
|
|
928
|
+
* <input ref={composeRefs(internalRef, forwardedRef)} />
|
|
929
|
+
*/
|
|
930
|
+
function composeRefs(...refs) {
|
|
931
|
+
return (node) => {
|
|
932
|
+
refs.forEach((ref) => {
|
|
933
|
+
if (!ref) return;
|
|
934
|
+
if (typeof ref === "function") ref(node);
|
|
935
|
+
else ref.current = node;
|
|
936
|
+
});
|
|
937
|
+
};
|
|
938
|
+
}
|
|
939
|
+
//#endregion
|
|
568
940
|
//#region src/ui-library/gallery/checkbox/Checkbox.tsx
|
|
569
941
|
/**
|
|
570
942
|
* Checkbox — Design System Component
|
|
@@ -575,14 +947,14 @@ function Select({ items, placeholder, status = "default", helper, disabled, clas
|
|
|
575
947
|
*
|
|
576
948
|
* States: unchecked | checked | indeterminate | disabled | error
|
|
577
949
|
*/
|
|
578
|
-
function Checkbox({ label, description, indeterminate = false, error = false, helper, disabled, checked, id: externalId, className = "", ...props }) {
|
|
950
|
+
function Checkbox({ label, description, indeterminate = false, error = false, helper, disabled, checked, id: externalId, className = "", ref, ...props }) {
|
|
579
951
|
const generatedId = useId();
|
|
580
952
|
const id = externalId ?? generatedId;
|
|
581
953
|
const attachIndeterminate = (el) => {
|
|
582
954
|
if (el) el.indeterminate = indeterminate;
|
|
583
955
|
};
|
|
584
956
|
return /* @__PURE__ */ jsxs("div", {
|
|
585
|
-
className:
|
|
957
|
+
className: cn("flex flex-col gap-1", className),
|
|
586
958
|
children: [/* @__PURE__ */ jsxs("label", {
|
|
587
959
|
htmlFor: id,
|
|
588
960
|
className: `group inline-flex cursor-pointer items-start gap-2.5 ${disabled ? "cursor-not-allowed opacity-60" : ""}`,
|
|
@@ -590,7 +962,7 @@ function Checkbox({ label, description, indeterminate = false, error = false, he
|
|
|
590
962
|
className: "relative mt-0.5 shrink-0",
|
|
591
963
|
children: [
|
|
592
964
|
/* @__PURE__ */ jsx("input", {
|
|
593
|
-
ref: attachIndeterminate,
|
|
965
|
+
ref: composeRefs(attachIndeterminate, ref),
|
|
594
966
|
id,
|
|
595
967
|
type: "checkbox",
|
|
596
968
|
checked,
|
|
@@ -685,26 +1057,23 @@ function RadioGroup({ name: externalName, defaultValue = "", value: controlledVa
|
|
|
685
1057
|
"aria-labelledby": ariaLabelledby,
|
|
686
1058
|
"aria-required": required || void 0,
|
|
687
1059
|
"aria-disabled": disabled || void 0,
|
|
688
|
-
className:
|
|
1060
|
+
className: cn(orientation === "horizontal" ? "flex flex-wrap gap-4" : "flex flex-col gap-3", className),
|
|
689
1061
|
children
|
|
690
1062
|
})
|
|
691
1063
|
});
|
|
692
1064
|
}
|
|
693
|
-
function RadioItem({ value, label, description, disabled: itemDisabled = false, className = "" }) {
|
|
1065
|
+
function RadioItem({ value, label, description, disabled: itemDisabled = false, className = "", ref }) {
|
|
694
1066
|
const { name, activeValue, select, disabled: groupDisabled, required } = useRadioGroupContext();
|
|
695
1067
|
const id = useId();
|
|
696
1068
|
const isDisabled = groupDisabled || itemDisabled;
|
|
697
1069
|
const isChecked = activeValue === value;
|
|
698
1070
|
return /* @__PURE__ */ jsxs("label", {
|
|
699
1071
|
htmlFor: id,
|
|
700
|
-
className:
|
|
701
|
-
"group flex cursor-pointer items-start gap-3",
|
|
702
|
-
isDisabled ? "cursor-not-allowed opacity-50" : "",
|
|
703
|
-
className
|
|
704
|
-
].filter(Boolean).join(" "),
|
|
1072
|
+
className: cn("group flex cursor-pointer items-start gap-3", isDisabled && "cursor-not-allowed opacity-50", className),
|
|
705
1073
|
children: [
|
|
706
1074
|
/* @__PURE__ */ jsx("input", {
|
|
707
1075
|
type: "radio",
|
|
1076
|
+
ref,
|
|
708
1077
|
id,
|
|
709
1078
|
name,
|
|
710
1079
|
value,
|
|
@@ -740,14 +1109,24 @@ function RadioItem({ value, label, description, disabled: itemDisabled = false,
|
|
|
740
1109
|
}
|
|
741
1110
|
//#endregion
|
|
742
1111
|
//#region src/ui-library/gallery/switch/Switch.tsx
|
|
743
|
-
function Switch({ checked, onChange, label, description, disabled = false, loading = false, id: externalId }) {
|
|
1112
|
+
function Switch({ checked: controlledChecked, defaultChecked = false, onChange, label, description, disabled = false, loading = false, id: externalId, className = "", ref }) {
|
|
1113
|
+
const [uncontrolled, setUncontrolled] = useState(defaultChecked);
|
|
1114
|
+
const isControlled = controlledChecked !== void 0;
|
|
1115
|
+
const checked = isControlled ? controlledChecked : uncontrolled;
|
|
744
1116
|
const generatedId = useId();
|
|
745
1117
|
const id = externalId ?? generatedId;
|
|
746
1118
|
const labelId = label ? `${id}-label` : void 0;
|
|
747
1119
|
const isInteractive = !disabled && !loading;
|
|
1120
|
+
const handleClick = () => {
|
|
1121
|
+
if (!isInteractive) return;
|
|
1122
|
+
const next = !checked;
|
|
1123
|
+
if (!isControlled) setUncontrolled(next);
|
|
1124
|
+
onChange?.(next);
|
|
1125
|
+
};
|
|
748
1126
|
return /* @__PURE__ */ jsxs("div", {
|
|
749
|
-
className: "flex items-start gap-3",
|
|
1127
|
+
className: cn("flex items-start gap-3", className),
|
|
750
1128
|
children: [/* @__PURE__ */ jsx("button", {
|
|
1129
|
+
ref,
|
|
751
1130
|
id,
|
|
752
1131
|
type: "button",
|
|
753
1132
|
role: "switch",
|
|
@@ -756,7 +1135,7 @@ function Switch({ checked, onChange, label, description, disabled = false, loadi
|
|
|
756
1135
|
"aria-label": !label ? "Toggle" : void 0,
|
|
757
1136
|
"aria-disabled": !isInteractive || void 0,
|
|
758
1137
|
disabled: !isInteractive,
|
|
759
|
-
onClick:
|
|
1138
|
+
onClick: handleClick,
|
|
760
1139
|
className: [
|
|
761
1140
|
"relative inline-flex h-6 w-11 shrink-0 items-center rounded-full border-2 border-transparent transition-colors duration-200",
|
|
762
1141
|
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30 focus-visible:ring-offset-1",
|
|
@@ -793,13 +1172,17 @@ function Switch({ checked, onChange, label, description, disabled = false, loadi
|
|
|
793
1172
|
*/
|
|
794
1173
|
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";
|
|
795
1174
|
var fieldInputErrorClass = "border-rose-300 focus:border-rose-400 focus:ring-rose-100";
|
|
796
|
-
function Combobox({ value, onChange, options, placeholder, className, disabled, loading, error, "aria-label": ariaLabel }) {
|
|
1175
|
+
function Combobox({ value: controlledValue, defaultValue = "", onChange, options, placeholder, className, disabled, loading, error, "aria-label": ariaLabel, onBlur, ref }) {
|
|
1176
|
+
const isControlled = controlledValue !== void 0;
|
|
1177
|
+
const [internalValue, setInternalValue] = useState(defaultValue);
|
|
1178
|
+
const value = isControlled ? controlledValue : internalValue;
|
|
797
1179
|
const [isOpen, setIsOpen] = useState(false);
|
|
798
1180
|
const [search, setSearch] = useState(value);
|
|
799
1181
|
const [highlightIndex, setHighlightIndex] = useState(-1);
|
|
800
1182
|
const inputRef = useRef(null);
|
|
801
1183
|
const listRef = useRef(null);
|
|
802
1184
|
const wrapperRef = useRef(null);
|
|
1185
|
+
const [panelStyle, setPanelStyle] = useState({});
|
|
803
1186
|
const uid = useId();
|
|
804
1187
|
const listboxId = `${uid}-listbox`;
|
|
805
1188
|
const optionId = (index) => `${uid}-option-${index}`;
|
|
@@ -816,11 +1199,23 @@ function Combobox({ value, onChange, options, placeholder, className, disabled,
|
|
|
816
1199
|
}, [value]);
|
|
817
1200
|
useEffect(() => {
|
|
818
1201
|
function handleClickOutside(e) {
|
|
819
|
-
|
|
1202
|
+
const target = e.target;
|
|
1203
|
+
if (wrapperRef.current && !wrapperRef.current.contains(target) && !(listRef.current && listRef.current.contains(target))) setIsOpen(false);
|
|
820
1204
|
}
|
|
821
1205
|
document.addEventListener("mousedown", handleClickOutside);
|
|
822
1206
|
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
823
1207
|
}, []);
|
|
1208
|
+
useEffect(() => {
|
|
1209
|
+
if (!isOpen || !wrapperRef.current) return;
|
|
1210
|
+
const rect = wrapperRef.current.getBoundingClientRect();
|
|
1211
|
+
setPanelStyle({
|
|
1212
|
+
position: "fixed",
|
|
1213
|
+
top: rect.bottom + 4,
|
|
1214
|
+
left: rect.left,
|
|
1215
|
+
zIndex: 9999,
|
|
1216
|
+
width: rect.width
|
|
1217
|
+
});
|
|
1218
|
+
}, [isOpen]);
|
|
824
1219
|
useEffect(() => {
|
|
825
1220
|
if (!isOpen) setHighlightIndex(-1);
|
|
826
1221
|
}, [isOpen]);
|
|
@@ -828,7 +1223,9 @@ function Combobox({ value, onChange, options, placeholder, className, disabled,
|
|
|
828
1223
|
if (highlightIndex >= 0 && listRef.current) listRef.current.children[highlightIndex]?.scrollIntoView({ block: "nearest" });
|
|
829
1224
|
}, [highlightIndex]);
|
|
830
1225
|
const selectOption = (opt) => {
|
|
831
|
-
|
|
1226
|
+
if (opt.disabled) return;
|
|
1227
|
+
if (!isControlled) setInternalValue(opt.value);
|
|
1228
|
+
onChange?.(opt.value);
|
|
832
1229
|
setSearch(opt.label);
|
|
833
1230
|
setIsOpen(false);
|
|
834
1231
|
inputRef.current?.focus();
|
|
@@ -836,37 +1233,60 @@ function Combobox({ value, onChange, options, placeholder, className, disabled,
|
|
|
836
1233
|
const handleInputChange = (e) => {
|
|
837
1234
|
const val = e.target.value;
|
|
838
1235
|
setSearch(val);
|
|
839
|
-
|
|
1236
|
+
if (!isControlled) setInternalValue(val);
|
|
840
1237
|
setIsOpen(true);
|
|
841
1238
|
setHighlightIndex(-1);
|
|
842
1239
|
};
|
|
843
1240
|
const handleKeyDown = (e) => {
|
|
1241
|
+
const nextEnabled = (from, dir) => {
|
|
1242
|
+
let idx = from;
|
|
1243
|
+
while (idx >= 0 && idx < displayed.length) {
|
|
1244
|
+
if (!displayed[idx]?.disabled) return idx;
|
|
1245
|
+
idx += dir;
|
|
1246
|
+
}
|
|
1247
|
+
return -1;
|
|
1248
|
+
};
|
|
844
1249
|
if (e.key === "ArrowDown") {
|
|
845
1250
|
e.preventDefault();
|
|
846
1251
|
if (!isOpen) {
|
|
847
1252
|
setIsOpen(true);
|
|
848
1253
|
return;
|
|
849
1254
|
}
|
|
850
|
-
setHighlightIndex((prev) =>
|
|
1255
|
+
setHighlightIndex((prev) => {
|
|
1256
|
+
const next = nextEnabled(prev + 1, 1);
|
|
1257
|
+
return next === -1 ? prev : next;
|
|
1258
|
+
});
|
|
851
1259
|
} else if (e.key === "ArrowUp") {
|
|
852
1260
|
e.preventDefault();
|
|
853
|
-
setHighlightIndex((prev) =>
|
|
1261
|
+
setHighlightIndex((prev) => {
|
|
1262
|
+
const next = nextEnabled(prev - 1, -1);
|
|
1263
|
+
return next === -1 ? prev : next;
|
|
1264
|
+
});
|
|
854
1265
|
} else if (e.key === "Enter") {
|
|
855
1266
|
e.preventDefault();
|
|
856
|
-
if (highlightIndex >= 0 && displayed[highlightIndex]) selectOption(displayed[highlightIndex]);
|
|
1267
|
+
if (highlightIndex >= 0 && displayed[highlightIndex] && !displayed[highlightIndex].disabled) selectOption(displayed[highlightIndex]);
|
|
857
1268
|
else if (isOpen) setIsOpen(false);
|
|
858
1269
|
} else if (e.key === "Escape") {
|
|
859
1270
|
e.preventDefault();
|
|
860
1271
|
setIsOpen(false);
|
|
861
1272
|
}
|
|
862
1273
|
};
|
|
1274
|
+
const handleBlur = () => {
|
|
1275
|
+
requestAnimationFrame(() => {
|
|
1276
|
+
const active = document.activeElement;
|
|
1277
|
+
if (!wrapperRef.current?.contains(active) && !listRef.current?.contains(active)) {
|
|
1278
|
+
setIsOpen(false);
|
|
1279
|
+
onBlur?.();
|
|
1280
|
+
}
|
|
1281
|
+
});
|
|
1282
|
+
};
|
|
863
1283
|
return /* @__PURE__ */ jsxs("div", {
|
|
864
1284
|
ref: wrapperRef,
|
|
865
1285
|
className: "relative",
|
|
866
1286
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
867
1287
|
className: "relative",
|
|
868
1288
|
children: [/* @__PURE__ */ jsx("input", {
|
|
869
|
-
ref: inputRef,
|
|
1289
|
+
ref: composeRefs(inputRef, ref),
|
|
870
1290
|
type: "text",
|
|
871
1291
|
role: "combobox",
|
|
872
1292
|
"aria-expanded": isOpen,
|
|
@@ -878,20 +1298,22 @@ function Combobox({ value, onChange, options, placeholder, className, disabled,
|
|
|
878
1298
|
value: search,
|
|
879
1299
|
onChange: handleInputChange,
|
|
880
1300
|
onFocus: () => setIsOpen(true),
|
|
1301
|
+
onBlur: handleBlur,
|
|
881
1302
|
onKeyDown: handleKeyDown,
|
|
882
1303
|
placeholder,
|
|
883
1304
|
disabled,
|
|
884
|
-
className:
|
|
1305
|
+
className: cn(fieldInputClass, "pr-8", error && fieldInputErrorClass, className)
|
|
885
1306
|
}), /* @__PURE__ */ jsx(ChevronDown, {
|
|
886
1307
|
size: 16,
|
|
887
1308
|
"aria-hidden": "true",
|
|
888
1309
|
className: `absolute right-3 top-1/2 -translate-y-1/2 text-slate-300 pointer-events-none transition-transform ${isOpen ? "rotate-180" : ""}`
|
|
889
1310
|
})]
|
|
890
|
-
}), isOpen && !disabled && /* @__PURE__ */ jsx("div", {
|
|
1311
|
+
}), isOpen && !disabled && createPortal(/* @__PURE__ */ jsx("div", {
|
|
891
1312
|
ref: listRef,
|
|
892
1313
|
id: listboxId,
|
|
893
1314
|
role: "listbox",
|
|
894
|
-
|
|
1315
|
+
style: panelStyle,
|
|
1316
|
+
className: "bg-white border border-slate-200 rounded-xl shadow-lg max-h-60 overflow-y-auto",
|
|
895
1317
|
children: error ? /* @__PURE__ */ jsx("div", {
|
|
896
1318
|
className: "px-3 py-2 text-sm text-rose-500 font-semibold",
|
|
897
1319
|
children: error
|
|
@@ -905,11 +1327,12 @@ function Combobox({ value, onChange, options, placeholder, className, disabled,
|
|
|
905
1327
|
id: optionId(i),
|
|
906
1328
|
role: "option",
|
|
907
1329
|
"aria-selected": opt.value === value,
|
|
1330
|
+
"aria-disabled": opt.disabled,
|
|
908
1331
|
onMouseDown: (e) => {
|
|
909
1332
|
e.preventDefault();
|
|
910
|
-
selectOption(opt);
|
|
1333
|
+
if (!opt.disabled) selectOption(opt);
|
|
911
1334
|
},
|
|
912
|
-
className: `px-3 py-2 text-sm cursor-
|
|
1335
|
+
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" : ""}`,
|
|
913
1336
|
children: opt.label
|
|
914
1337
|
}, opt.value)), filtered.length > DISPLAY_LIMIT && /* @__PURE__ */ jsxs("div", {
|
|
915
1338
|
className: "px-3 py-2 text-xs text-slate-400 font-semibold text-center border-t border-slate-100",
|
|
@@ -919,7 +1342,7 @@ function Combobox({ value, onChange, options, placeholder, className, disabled,
|
|
|
919
1342
|
" more results — refine your search"
|
|
920
1343
|
]
|
|
921
1344
|
})] })
|
|
922
|
-
})]
|
|
1345
|
+
}), document.body)]
|
|
923
1346
|
});
|
|
924
1347
|
}
|
|
925
1348
|
//#endregion
|
|
@@ -931,10 +1354,9 @@ function Combobox({ value, onChange, options, placeholder, className, disabled,
|
|
|
931
1354
|
* Uses react-calendar and date-fns.
|
|
932
1355
|
* Note: CSS for .pis-datepicker-popover and .pis-rc-calendar is in index.css.
|
|
933
1356
|
*/
|
|
934
|
-
var MAX_DATE = new Date(2026, 11, 31);
|
|
935
1357
|
var POPOVER_WIDTH = 352;
|
|
936
1358
|
var CALENDAR_HEIGHT = 360;
|
|
937
|
-
function DatePicker({ value, onChange, placeholder, className, disabled, error, minDate, id, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, required }) {
|
|
1359
|
+
function DatePicker({ value, onChange, placeholder, className, disabled, error, minDate, maxDate, id, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, required }) {
|
|
938
1360
|
const [isOpen, setIsOpen] = useState(false);
|
|
939
1361
|
const [popupStyle, setPopupStyle] = useState({});
|
|
940
1362
|
const wrapperRef = useRef(null);
|
|
@@ -1015,12 +1437,7 @@ function DatePicker({ value, onChange, placeholder, className, disabled, error,
|
|
|
1015
1437
|
else openPopup();
|
|
1016
1438
|
}
|
|
1017
1439
|
}
|
|
1018
|
-
const inputClasses =
|
|
1019
|
-
"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",
|
|
1020
|
-
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",
|
|
1021
|
-
disabled ? "cursor-not-allowed opacity-60" : "",
|
|
1022
|
-
className ?? ""
|
|
1023
|
-
].filter(Boolean).join(" ");
|
|
1440
|
+
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);
|
|
1024
1441
|
return /* @__PURE__ */ jsxs("div", {
|
|
1025
1442
|
ref: wrapperRef,
|
|
1026
1443
|
className: "relative",
|
|
@@ -1069,7 +1486,7 @@ function DatePicker({ value, onChange, placeholder, className, disabled, error,
|
|
|
1069
1486
|
},
|
|
1070
1487
|
defaultActiveStartDate: selectedDate ?? minDate,
|
|
1071
1488
|
minDate,
|
|
1072
|
-
maxDate
|
|
1489
|
+
maxDate,
|
|
1073
1490
|
defaultView: "month",
|
|
1074
1491
|
showNeighboringDecade: true,
|
|
1075
1492
|
showNeighboringCentury: true,
|
|
@@ -1158,7 +1575,7 @@ function OtpInput({ length = 6, value, onChange, disabled = false, error = false
|
|
|
1158
1575
|
].join(" ");
|
|
1159
1576
|
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`;
|
|
1160
1577
|
return /* @__PURE__ */ jsxs("div", {
|
|
1161
|
-
className:
|
|
1578
|
+
className: cn("space-y-2", className),
|
|
1162
1579
|
children: [/* @__PURE__ */ jsx("div", {
|
|
1163
1580
|
role: "group",
|
|
1164
1581
|
"aria-label": ariaLabel,
|
|
@@ -1206,14 +1623,20 @@ function OtpInput({ length = 6, value, onChange, disabled = false, error = false
|
|
|
1206
1623
|
* - Keyboard: Tab to focus the input; Enter/Space to open the picker.
|
|
1207
1624
|
* - Disabled: input has disabled attr; zone has aria-disabled="true".
|
|
1208
1625
|
*/
|
|
1209
|
-
function FileUpload({ onFiles, accept, multiple = false, disabled = false, "aria-label": ariaLabel = "Upload file", className = "" }) {
|
|
1626
|
+
function FileUpload({ onFiles, accept, multiple = false, disabled = false, "aria-label": ariaLabel = "Upload file", className = "", value, onChange, ref }) {
|
|
1210
1627
|
const inputRef = useRef(null);
|
|
1211
1628
|
const [dragging, setDragging] = useState(false);
|
|
1212
|
-
const [
|
|
1629
|
+
const [internalFiles, setInternalFiles] = useState([]);
|
|
1630
|
+
const isControlled = value !== void 0;
|
|
1631
|
+
const selected = isControlled ? value : internalFiles;
|
|
1632
|
+
const setFiles = (next) => {
|
|
1633
|
+
if (!isControlled) setInternalFiles(next);
|
|
1634
|
+
onChange?.(next);
|
|
1635
|
+
};
|
|
1213
1636
|
const handleFiles = (files) => {
|
|
1214
1637
|
if (!files || files.length === 0) return;
|
|
1215
1638
|
const arr = Array.from(files);
|
|
1216
|
-
|
|
1639
|
+
setFiles(multiple ? arr : [arr[0]]);
|
|
1217
1640
|
onFiles?.(multiple ? arr : [arr[0]]);
|
|
1218
1641
|
};
|
|
1219
1642
|
const handleChange = (e) => {
|
|
@@ -1232,15 +1655,9 @@ function FileUpload({ onFiles, accept, multiple = false, disabled = false, "aria
|
|
|
1232
1655
|
};
|
|
1233
1656
|
const handleDragLeave = () => setDragging(false);
|
|
1234
1657
|
const removeFile = (i) => {
|
|
1235
|
-
|
|
1658
|
+
setFiles(selected.filter((_, idx) => idx !== i));
|
|
1236
1659
|
};
|
|
1237
|
-
const zoneClass = [
|
|
1238
|
-
"flex flex-col items-center justify-center gap-3",
|
|
1239
|
-
"rounded-2xl border-2 border-dashed p-8 text-center cursor-pointer",
|
|
1240
|
-
"transition-colors focus-within:outline-none focus-within:ring-2 focus-within:ring-brand-blue/20",
|
|
1241
|
-
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]",
|
|
1242
|
-
className
|
|
1243
|
-
].filter(Boolean).join(" ");
|
|
1660
|
+
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);
|
|
1244
1661
|
return /* @__PURE__ */ jsxs("div", {
|
|
1245
1662
|
className: "space-y-3",
|
|
1246
1663
|
children: [/* @__PURE__ */ jsxs("label", {
|
|
@@ -1251,7 +1668,7 @@ function FileUpload({ onFiles, accept, multiple = false, disabled = false, "aria
|
|
|
1251
1668
|
"aria-disabled": disabled,
|
|
1252
1669
|
children: [
|
|
1253
1670
|
/* @__PURE__ */ jsx("input", {
|
|
1254
|
-
ref: inputRef,
|
|
1671
|
+
ref: composeRefs(inputRef, ref),
|
|
1255
1672
|
type: "file",
|
|
1256
1673
|
accept,
|
|
1257
1674
|
multiple,
|
|
@@ -1325,13 +1742,7 @@ function FileUpload({ onFiles, accept, multiple = false, disabled = false, "aria
|
|
|
1325
1742
|
*/
|
|
1326
1743
|
function FilterChip({ label, onRemove, disabled = false, removeLabel = "Remove filter", className = "" }) {
|
|
1327
1744
|
return /* @__PURE__ */ jsxs("span", {
|
|
1328
|
-
className: [
|
|
1329
|
-
"inline-flex max-w-[20rem] items-center gap-1.5 rounded-full border",
|
|
1330
|
-
"py-1 pl-3 text-xs font-semibold",
|
|
1331
|
-
onRemove ? "pr-1.5" : "pr-3",
|
|
1332
|
-
disabled ? "border-slate-200 bg-slate-50 text-slate-400" : "border-brand-blue/30 bg-blue-50 text-brand-blue",
|
|
1333
|
-
className
|
|
1334
|
-
].filter(Boolean).join(" "),
|
|
1745
|
+
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),
|
|
1335
1746
|
children: [/* @__PURE__ */ jsx("span", {
|
|
1336
1747
|
className: "truncate",
|
|
1337
1748
|
children: label
|
|
@@ -1340,11 +1751,7 @@ function FilterChip({ label, onRemove, disabled = false, removeLabel = "Remove f
|
|
|
1340
1751
|
onClick: !disabled ? onRemove : void 0,
|
|
1341
1752
|
disabled,
|
|
1342
1753
|
"aria-label": removeLabel,
|
|
1343
|
-
className:
|
|
1344
|
-
"flex h-4 w-4 shrink-0 items-center justify-center rounded-full transition",
|
|
1345
|
-
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30",
|
|
1346
|
-
disabled ? "cursor-not-allowed text-slate-300" : "text-brand-blue hover:bg-blue-100 hover:text-brand-navy"
|
|
1347
|
-
].filter(Boolean).join(" "),
|
|
1754
|
+
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"),
|
|
1348
1755
|
children: /* @__PURE__ */ jsx(X, {
|
|
1349
1756
|
size: 10,
|
|
1350
1757
|
"aria-hidden": "true",
|
|
@@ -1354,6 +1761,52 @@ function FilterChip({ label, onRemove, disabled = false, removeLabel = "Remove f
|
|
|
1354
1761
|
});
|
|
1355
1762
|
}
|
|
1356
1763
|
//#endregion
|
|
1764
|
+
//#region src/ui-library/gallery/search/Search.tsx
|
|
1765
|
+
/**
|
|
1766
|
+
* Search — Design System Component
|
|
1767
|
+
*
|
|
1768
|
+
* A search input with a built-in search icon prefix and optional clear button.
|
|
1769
|
+
* Built for filtering and discovery use cases — not for form submission fields
|
|
1770
|
+
* (use Input for those).
|
|
1771
|
+
*
|
|
1772
|
+
* Features:
|
|
1773
|
+
* - Search icon in prefix by default
|
|
1774
|
+
* - Optional × clear button when value is non-empty (pass onClear)
|
|
1775
|
+
* - Controlled via value + onChange (standard input event)
|
|
1776
|
+
* - Forwards ref to the underlying <input>
|
|
1777
|
+
* - All native <input> attributes are forwarded
|
|
1778
|
+
*/
|
|
1779
|
+
function Search({ value, onChange, onClear, placeholder = "Search…", disabled = false, className = "", ref, ...props }) {
|
|
1780
|
+
const hasValue = value !== void 0 && value !== "";
|
|
1781
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1782
|
+
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),
|
|
1783
|
+
children: [
|
|
1784
|
+
/* @__PURE__ */ jsx("span", {
|
|
1785
|
+
className: "flex shrink-0 items-center pl-3.5 text-slate-400",
|
|
1786
|
+
"aria-hidden": "true",
|
|
1787
|
+
children: /* @__PURE__ */ jsx(Search$1, { size: 15 })
|
|
1788
|
+
}),
|
|
1789
|
+
/* @__PURE__ */ jsx("input", {
|
|
1790
|
+
ref,
|
|
1791
|
+
type: "search",
|
|
1792
|
+
value,
|
|
1793
|
+
onChange,
|
|
1794
|
+
placeholder,
|
|
1795
|
+
disabled,
|
|
1796
|
+
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",
|
|
1797
|
+
...props
|
|
1798
|
+
}),
|
|
1799
|
+
onClear && hasValue && !disabled && /* @__PURE__ */ jsx("button", {
|
|
1800
|
+
type: "button",
|
|
1801
|
+
onClick: onClear,
|
|
1802
|
+
"aria-label": "Clear search",
|
|
1803
|
+
className: "flex shrink-0 items-center pr-3 text-slate-400 transition-colors hover:text-slate-600",
|
|
1804
|
+
children: /* @__PURE__ */ jsx(X, { size: 14 })
|
|
1805
|
+
})
|
|
1806
|
+
]
|
|
1807
|
+
});
|
|
1808
|
+
}
|
|
1809
|
+
//#endregion
|
|
1357
1810
|
//#region src/ui-library/gallery/segmented-control/SegmentedControl.tsx
|
|
1358
1811
|
/**
|
|
1359
1812
|
* SegmentedControl — Design System Component
|
|
@@ -1421,11 +1874,7 @@ function SegmentedControl({ options, defaultValue, value: controlledValue, onVal
|
|
|
1421
1874
|
"aria-labelledby": ariaLabelledby,
|
|
1422
1875
|
"aria-disabled": disabled || void 0,
|
|
1423
1876
|
onKeyDown: handleKeyDown,
|
|
1424
|
-
className:
|
|
1425
|
-
"inline-flex rounded-xl bg-slate-100 p-1",
|
|
1426
|
-
disabled ? "opacity-50 pointer-events-none" : "",
|
|
1427
|
-
className
|
|
1428
|
-
].filter(Boolean).join(" "),
|
|
1877
|
+
className: cn("inline-flex rounded-xl bg-slate-100 p-1", disabled && "opacity-50 pointer-events-none", className),
|
|
1429
1878
|
children: options.map((opt) => {
|
|
1430
1879
|
const isSelected = opt.value === activeValue;
|
|
1431
1880
|
const isDisabled = disabled || opt.disabled;
|
|
@@ -1450,6 +1899,33 @@ function SegmentedControl({ options, defaultValue, value: controlledValue, onVal
|
|
|
1450
1899
|
}
|
|
1451
1900
|
//#endregion
|
|
1452
1901
|
//#region src/ui-library/gallery/validation-summary/ValidationSummary.tsx
|
|
1902
|
+
/**
|
|
1903
|
+
* ValidationSummary — Design System Component
|
|
1904
|
+
*
|
|
1905
|
+
* Renders a list of validation errors or warnings before a form is submitted.
|
|
1906
|
+
* Matches the production implementation in shared/components/ValidationSummary.tsx.
|
|
1907
|
+
*
|
|
1908
|
+
* Accessibility:
|
|
1909
|
+
* - role="alert" causes immediate announcement when injected into the DOM.
|
|
1910
|
+
* - <ul> with <li> items provides proper list semantics.
|
|
1911
|
+
* - The heading is a <div role="heading" aria-level="3"> to avoid disrupting
|
|
1912
|
+
* page outline; callers may override via headingLevel.
|
|
1913
|
+
* - Each issue is identified by field name + message.
|
|
1914
|
+
*
|
|
1915
|
+
* Usage:
|
|
1916
|
+
* const [issues, setIssues] = useState<ValidationIssue[]>([]);
|
|
1917
|
+
* // ... after validation:
|
|
1918
|
+
* setIssues([{ field: 'Employee ID', message: 'is required' }]);
|
|
1919
|
+
* // Render:
|
|
1920
|
+
* <ValidationSummary issues={issues} />
|
|
1921
|
+
*
|
|
1922
|
+
* // With scroll-to-field (wizard steps):
|
|
1923
|
+
* <ValidationSummary
|
|
1924
|
+
* issues={issues}
|
|
1925
|
+
* fieldIds={{ 'Employee ID': 'employee-id-input', 'Position': 'position-select' }}
|
|
1926
|
+
* fieldLabels={{ 'Employee ID': 'Employee ID', 'Position': 'Position Title' }}
|
|
1927
|
+
* />
|
|
1928
|
+
*/
|
|
1453
1929
|
function scrollToField(domId) {
|
|
1454
1930
|
const el = document.getElementById(domId);
|
|
1455
1931
|
if (!el) return;
|
|
@@ -1464,7 +1940,7 @@ function ValidationSummary({ issues, heading = "Review these fields before savin
|
|
|
1464
1940
|
if (issues.length === 0) return null;
|
|
1465
1941
|
return /* @__PURE__ */ jsxs("div", {
|
|
1466
1942
|
role: "alert",
|
|
1467
|
-
className:
|
|
1943
|
+
className: cn("rounded-xl border border-amber-200 bg-amber-50 px-4 py-3 text-sm text-amber-900", className),
|
|
1468
1944
|
children: [/* @__PURE__ */ jsx("div", {
|
|
1469
1945
|
className: "font-bold",
|
|
1470
1946
|
children: heading
|
|
@@ -1528,7 +2004,7 @@ function Alert({ tone, title, children, icon = true, onDismiss, subtle = false,
|
|
|
1528
2004
|
const colorClass = subtle ? TONE_SUBTLE[tone] : TONE_FULL[tone];
|
|
1529
2005
|
return /* @__PURE__ */ jsxs("div", {
|
|
1530
2006
|
role: "alert",
|
|
1531
|
-
className:
|
|
2007
|
+
className: cn("flex gap-3 rounded-md pl-4 pr-4 py-3.5", colorClass, className),
|
|
1532
2008
|
children: [
|
|
1533
2009
|
icon && /* @__PURE__ */ jsx(Icon, {
|
|
1534
2010
|
size: 20,
|
|
@@ -1569,12 +2045,33 @@ function ErrorBanner({ message, tone = "error", className = "" }) {
|
|
|
1569
2045
|
return /* @__PURE__ */ jsx("div", {
|
|
1570
2046
|
role: tone === "error" ? "alert" : void 0,
|
|
1571
2047
|
"aria-live": tone !== "error" ? "polite" : void 0,
|
|
1572
|
-
className:
|
|
2048
|
+
className: cn("rounded-2xl border px-5 py-4 text-sm font-bold", STYLES[tone], className),
|
|
1573
2049
|
children: message
|
|
1574
2050
|
});
|
|
1575
2051
|
}
|
|
1576
2052
|
//#endregion
|
|
1577
2053
|
//#region src/ui-library/gallery/progress/Progress.tsx
|
|
2054
|
+
/**
|
|
2055
|
+
* Progress — Design System Component
|
|
2056
|
+
*
|
|
2057
|
+
* Determinate and indeterminate linear progress bar.
|
|
2058
|
+
*
|
|
2059
|
+
* Accessibility:
|
|
2060
|
+
* - role="progressbar"
|
|
2061
|
+
* - aria-valuenow, aria-valuemin, aria-valuemax (determinate)
|
|
2062
|
+
* - aria-label for screen-reader context
|
|
2063
|
+
* - When indeterminate: aria-valuenow is omitted
|
|
2064
|
+
*
|
|
2065
|
+
* Usage:
|
|
2066
|
+
* // Determinate (0–100)
|
|
2067
|
+
* <Progress value={65} aria-label="Upload progress" />
|
|
2068
|
+
*
|
|
2069
|
+
* // Indeterminate
|
|
2070
|
+
* <Progress aria-label="Loading" />
|
|
2071
|
+
*
|
|
2072
|
+
* // Custom max
|
|
2073
|
+
* <Progress value={3} max={5} aria-label="Step 3 of 5" />
|
|
2074
|
+
*/
|
|
1578
2075
|
function Progress({ value, max = 100, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledby, className = "" }) {
|
|
1579
2076
|
const isIndeterminate = value === void 0;
|
|
1580
2077
|
const clamped = isIndeterminate ? 0 : Math.min(Math.max(value, 0), max);
|
|
@@ -1586,7 +2083,7 @@ function Progress({ value, max = 100, "aria-label": ariaLabel, "aria-labelledby"
|
|
|
1586
2083
|
"aria-valuemax": max,
|
|
1587
2084
|
"aria-label": ariaLabel,
|
|
1588
2085
|
"aria-labelledby": ariaLabelledby,
|
|
1589
|
-
className:
|
|
2086
|
+
className: cn("h-2 w-full overflow-hidden rounded-full bg-slate-200", className),
|
|
1590
2087
|
children: /* @__PURE__ */ jsx("div", {
|
|
1591
2088
|
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(" "),
|
|
1592
2089
|
style: isIndeterminate ? void 0 : { width: `${percent}%` },
|
|
@@ -1596,17 +2093,20 @@ function Progress({ value, max = 100, "aria-label": ariaLabel, "aria-labelledby"
|
|
|
1596
2093
|
}
|
|
1597
2094
|
//#endregion
|
|
1598
2095
|
//#region src/ui-library/gallery/skeleton/Skeleton.tsx
|
|
1599
|
-
function Skeleton({ className = "" }) {
|
|
2096
|
+
function Skeleton({ className = "", variant = "pulse" }) {
|
|
1600
2097
|
return /* @__PURE__ */ jsx("div", {
|
|
1601
2098
|
"aria-hidden": "true",
|
|
1602
|
-
className:
|
|
2099
|
+
className: cn("rounded-lg", variant === "shimmer" ? "skeleton-shimmer" : "animate-pulse bg-slate-200", className)
|
|
1603
2100
|
});
|
|
1604
2101
|
}
|
|
1605
|
-
function SkeletonText({ lines = 3, className = "" }) {
|
|
2102
|
+
function SkeletonText({ lines = 3, className = "", variant = "pulse" }) {
|
|
1606
2103
|
return /* @__PURE__ */ jsx("div", {
|
|
1607
|
-
className:
|
|
2104
|
+
className: cn("space-y-2", className),
|
|
1608
2105
|
"aria-hidden": "true",
|
|
1609
|
-
children: Array.from({ length: lines }, (_, i) => /* @__PURE__ */ jsx(Skeleton, {
|
|
2106
|
+
children: Array.from({ length: lines }, (_, i) => /* @__PURE__ */ jsx(Skeleton, {
|
|
2107
|
+
variant,
|
|
2108
|
+
className: `h-3.5 ${i === lines - 1 && lines > 1 ? "w-2/3" : "w-full"}`
|
|
2109
|
+
}, i))
|
|
1610
2110
|
});
|
|
1611
2111
|
}
|
|
1612
2112
|
var AVATAR_SIZE = {
|
|
@@ -1614,17 +2114,23 @@ var AVATAR_SIZE = {
|
|
|
1614
2114
|
md: "h-10 w-10",
|
|
1615
2115
|
lg: "h-14 w-14"
|
|
1616
2116
|
};
|
|
1617
|
-
function SkeletonAvatar({ size = "md", className = "" }) {
|
|
2117
|
+
function SkeletonAvatar({ size = "md", className = "", variant = "pulse" }) {
|
|
1618
2118
|
return /* @__PURE__ */ jsx("div", {
|
|
1619
2119
|
"aria-hidden": "true",
|
|
1620
|
-
className:
|
|
2120
|
+
className: cn("rounded-full shrink-0", variant === "shimmer" ? "skeleton-shimmer" : "animate-pulse bg-slate-200", AVATAR_SIZE[size], className)
|
|
1621
2121
|
});
|
|
1622
2122
|
}
|
|
1623
|
-
function SkeletonCard({ className = "", children }) {
|
|
2123
|
+
function SkeletonCard({ className = "", children, variant = "pulse" }) {
|
|
1624
2124
|
return /* @__PURE__ */ jsx("div", {
|
|
1625
|
-
className:
|
|
2125
|
+
className: cn("rounded-2xl border border-slate-100 bg-white p-5 space-y-3", className),
|
|
1626
2126
|
"aria-hidden": "true",
|
|
1627
|
-
children: children ?? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Skeleton, {
|
|
2127
|
+
children: children ?? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Skeleton, {
|
|
2128
|
+
variant,
|
|
2129
|
+
className: "h-4 w-1/3"
|
|
2130
|
+
}), /* @__PURE__ */ jsx(SkeletonText, {
|
|
2131
|
+
variant,
|
|
2132
|
+
lines: 2
|
|
2133
|
+
})] })
|
|
1628
2134
|
});
|
|
1629
2135
|
}
|
|
1630
2136
|
function SkeletonTableRow({ rows = 5, columns = [
|
|
@@ -1632,12 +2138,15 @@ function SkeletonTableRow({ rows = 5, columns = [
|
|
|
1632
2138
|
"w-24",
|
|
1633
2139
|
"w-20",
|
|
1634
2140
|
"w-28"
|
|
1635
|
-
] }) {
|
|
2141
|
+
], variant = "pulse" }) {
|
|
1636
2142
|
return /* @__PURE__ */ jsx(Fragment, { children: Array.from({ length: rows }, (_, r) => /* @__PURE__ */ jsx("tr", {
|
|
1637
2143
|
"aria-hidden": "true",
|
|
1638
2144
|
children: columns.map((width, c) => /* @__PURE__ */ jsx("td", {
|
|
1639
2145
|
className: "px-5 py-3.5",
|
|
1640
|
-
children: /* @__PURE__ */ jsx(Skeleton, {
|
|
2146
|
+
children: /* @__PURE__ */ jsx(Skeleton, {
|
|
2147
|
+
variant,
|
|
2148
|
+
className: `h-3.5 ${width}`
|
|
2149
|
+
})
|
|
1641
2150
|
}, c))
|
|
1642
2151
|
}, r)) });
|
|
1643
2152
|
}
|
|
@@ -1653,12 +2162,7 @@ var TONE_STYLES = {
|
|
|
1653
2162
|
};
|
|
1654
2163
|
function StatusBadge({ tone = "neutral", children, className = "" }) {
|
|
1655
2164
|
return /* @__PURE__ */ jsx("span", {
|
|
1656
|
-
className: [
|
|
1657
|
-
"inline-flex items-center rounded-lg border px-2.5 py-1",
|
|
1658
|
-
"text-[10px] font-bold uppercase tracking-wider",
|
|
1659
|
-
TONE_STYLES[tone],
|
|
1660
|
-
className
|
|
1661
|
-
].filter(Boolean).join(" "),
|
|
2165
|
+
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),
|
|
1662
2166
|
children
|
|
1663
2167
|
});
|
|
1664
2168
|
}
|
|
@@ -1735,7 +2239,7 @@ var typography = {
|
|
|
1735
2239
|
* - Close button styling
|
|
1736
2240
|
* - Danger tone (rose badge, rose title, rose submit button)
|
|
1737
2241
|
*/
|
|
1738
|
-
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" }) {
|
|
2242
|
+
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 }) {
|
|
1739
2243
|
const open = isOpen !== false;
|
|
1740
2244
|
const defaultFooter = onSubmit ? /* @__PURE__ */ jsxs("div", {
|
|
1741
2245
|
className: "flex items-center justify-end gap-3",
|
|
@@ -1768,13 +2272,17 @@ function Modal({ isOpen, onClose, title, children, icon, subtitle, headerMeta, f
|
|
|
1768
2272
|
return /* @__PURE__ */ jsx(DialogPrimitive.Root, {
|
|
1769
2273
|
open,
|
|
1770
2274
|
onOpenChange: (isOpen) => {
|
|
1771
|
-
if (!isOpen) onClose();
|
|
2275
|
+
if (!isOpen && !preventClose) onClose();
|
|
1772
2276
|
},
|
|
1773
|
-
children: /* @__PURE__ */ jsxs(DialogPrimitive.Portal, { children: [/* @__PURE__ */ jsx(DialogPrimitive.Overlay, { className: "fixed inset-0 z-50 bg-brand-navy/50 backdrop-blur-sm" }), /* @__PURE__ */ jsxs(DialogPrimitive.Content, {
|
|
1774
|
-
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", "
|
|
2277
|
+
children: /* @__PURE__ */ jsxs(DialogPrimitive.Portal, { children: [/* @__PURE__ */ jsx(DialogPrimitive.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__ */ jsxs(DialogPrimitive.Content, {
|
|
2278
|
+
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"),
|
|
1775
2279
|
style: { padding: 0 },
|
|
2280
|
+
onOpenAutoFocus,
|
|
2281
|
+
onCloseAutoFocus,
|
|
2282
|
+
onEscapeKeyDown: preventClose ? (e) => e.preventDefault() : onEscapeKeyDown,
|
|
2283
|
+
onInteractOutside: preventClose ? (e) => e.preventDefault() : onInteractOutside,
|
|
1776
2284
|
children: [
|
|
1777
|
-
/* @__PURE__ */ jsx(DialogPrimitive.Close, {
|
|
2285
|
+
!preventClose && /* @__PURE__ */ jsx(DialogPrimitive.Close, {
|
|
1778
2286
|
asChild: true,
|
|
1779
2287
|
children: /* @__PURE__ */ jsx("button", {
|
|
1780
2288
|
type: "button",
|
|
@@ -1865,17 +2373,17 @@ var SIZE_CLASS = {
|
|
|
1865
2373
|
};
|
|
1866
2374
|
function DialogBody({ children, className = "" }) {
|
|
1867
2375
|
return /* @__PURE__ */ jsx("div", {
|
|
1868
|
-
className:
|
|
2376
|
+
className: cn("min-h-0 overflow-y-auto overscroll-contain px-6 py-4", className),
|
|
1869
2377
|
children
|
|
1870
2378
|
});
|
|
1871
2379
|
}
|
|
1872
2380
|
function DialogFooter({ children, className = "" }) {
|
|
1873
2381
|
return /* @__PURE__ */ jsx("div", {
|
|
1874
|
-
className:
|
|
2382
|
+
className: cn("flex items-center justify-end gap-3 border-t border-slate-100 px-6 py-4", className),
|
|
1875
2383
|
children
|
|
1876
2384
|
});
|
|
1877
2385
|
}
|
|
1878
|
-
function Dialog({ open, onClose, title, description, preventClose = false, size = "md", children }) {
|
|
2386
|
+
function Dialog({ open, onClose, title, description, preventClose = false, size = "md", children, onOpenAutoFocus, onCloseAutoFocus, onEscapeKeyDown, onInteractOutside }) {
|
|
1879
2387
|
const hasTitle = Boolean(title);
|
|
1880
2388
|
const hasDesc = Boolean(description);
|
|
1881
2389
|
return /* @__PURE__ */ jsx(DialogPrimitive.Root, {
|
|
@@ -1883,10 +2391,12 @@ function Dialog({ open, onClose, title, description, preventClose = false, size
|
|
|
1883
2391
|
onOpenChange: (isOpen) => {
|
|
1884
2392
|
if (!isOpen && !preventClose) onClose();
|
|
1885
2393
|
},
|
|
1886
|
-
children: /* @__PURE__ */ jsxs(DialogPrimitive.Portal, { children: [/* @__PURE__ */ jsx(DialogPrimitive.Overlay, { className: "fixed inset-0 z-50 bg-brand-navy/50 backdrop-blur-sm" }), /* @__PURE__ */ jsxs(DialogPrimitive.Content, {
|
|
1887
|
-
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", "
|
|
1888
|
-
onEscapeKeyDown: preventClose ? (e) => e.preventDefault() :
|
|
1889
|
-
onInteractOutside: preventClose ? (e) => e.preventDefault() :
|
|
2394
|
+
children: /* @__PURE__ */ jsxs(DialogPrimitive.Portal, { children: [/* @__PURE__ */ jsx(DialogPrimitive.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__ */ jsxs(DialogPrimitive.Content, {
|
|
2395
|
+
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"),
|
|
2396
|
+
onEscapeKeyDown: preventClose ? (e) => e.preventDefault() : onEscapeKeyDown,
|
|
2397
|
+
onInteractOutside: preventClose ? (e) => e.preventDefault() : onInteractOutside,
|
|
2398
|
+
onOpenAutoFocus,
|
|
2399
|
+
onCloseAutoFocus,
|
|
1890
2400
|
style: { padding: 0 },
|
|
1891
2401
|
children: [
|
|
1892
2402
|
hasTitle ? /* @__PURE__ */ jsxs("div", {
|
|
@@ -1936,7 +2446,7 @@ var PLACEMENT_PANEL_CLASS = {
|
|
|
1936
2446
|
right: "fixed top-0 right-0 bottom-0 flex flex-col",
|
|
1937
2447
|
bottom: "fixed bottom-0 left-0 right-0 flex flex-col rounded-t-xl"
|
|
1938
2448
|
};
|
|
1939
|
-
function Drawer({ open, onClose, title, description, placement = "right", width = "w-80", maxHeight = "max-h-[85vh]", closeLabel = "Close drawer", children }) {
|
|
2449
|
+
function Drawer({ open, onClose, title, description, placement = "right", width = "w-80", maxHeight = "max-h-[85vh]", closeLabel = "Close drawer", children, className = "" }) {
|
|
1940
2450
|
const panelRef = useRef(null);
|
|
1941
2451
|
const titleId = useId();
|
|
1942
2452
|
const [rendered, setRendered] = useState(open);
|
|
@@ -1997,14 +2507,7 @@ function Drawer({ open, onClose, title, description, placement = "right", width
|
|
|
1997
2507
|
return () => document.removeEventListener("keydown", handleKeyDown);
|
|
1998
2508
|
}, [open, onClose]);
|
|
1999
2509
|
if (!rendered) return null;
|
|
2000
|
-
const panelClass = [
|
|
2001
|
-
PLACEMENT_PANEL_CLASS[placement],
|
|
2002
|
-
placement === "right" ? width : maxHeight,
|
|
2003
|
-
"bg-white shadow-xl focus:outline-none",
|
|
2004
|
-
placement === "bottom" ? "overflow-y-auto" : "overflow-hidden",
|
|
2005
|
-
"transition-transform duration-300 ease-out",
|
|
2006
|
-
placement === "right" ? visible ? "translate-x-0" : "translate-x-full" : visible ? "translate-y-0" : "translate-y-full"
|
|
2007
|
-
].filter(Boolean).join(" ");
|
|
2510
|
+
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);
|
|
2008
2511
|
return createPortal(/* @__PURE__ */ jsxs("div", {
|
|
2009
2512
|
className: "fixed inset-0 z-50",
|
|
2010
2513
|
children: [/* @__PURE__ */ jsx("div", {
|
|
@@ -2115,6 +2618,30 @@ function toRadixPlacement(placement) {
|
|
|
2115
2618
|
side: "top",
|
|
2116
2619
|
align: "end"
|
|
2117
2620
|
};
|
|
2621
|
+
case "left-start": return {
|
|
2622
|
+
side: "left",
|
|
2623
|
+
align: "start"
|
|
2624
|
+
};
|
|
2625
|
+
case "left": return {
|
|
2626
|
+
side: "left",
|
|
2627
|
+
align: "center"
|
|
2628
|
+
};
|
|
2629
|
+
case "left-end": return {
|
|
2630
|
+
side: "left",
|
|
2631
|
+
align: "end"
|
|
2632
|
+
};
|
|
2633
|
+
case "right-start": return {
|
|
2634
|
+
side: "right",
|
|
2635
|
+
align: "start"
|
|
2636
|
+
};
|
|
2637
|
+
case "right": return {
|
|
2638
|
+
side: "right",
|
|
2639
|
+
align: "center"
|
|
2640
|
+
};
|
|
2641
|
+
case "right-end": return {
|
|
2642
|
+
side: "right",
|
|
2643
|
+
align: "end"
|
|
2644
|
+
};
|
|
2118
2645
|
}
|
|
2119
2646
|
}
|
|
2120
2647
|
function Popover({ open, onOpenChange, defaultOpen = false, placement = "bottom-start", children }) {
|
|
@@ -2139,12 +2666,12 @@ var PopoverPlacementContext = createContext({
|
|
|
2139
2666
|
function usePlacement() {
|
|
2140
2667
|
return useContext(PopoverPlacementContext);
|
|
2141
2668
|
}
|
|
2142
|
-
function PopoverTrigger({ children }) {
|
|
2669
|
+
function PopoverTrigger({ children, className = "" }) {
|
|
2143
2670
|
return /* @__PURE__ */ jsx(PopoverPrimitive.Trigger, {
|
|
2144
2671
|
asChild: true,
|
|
2145
2672
|
children: /* @__PURE__ */ jsx("button", {
|
|
2146
2673
|
type: "button",
|
|
2147
|
-
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",
|
|
2674
|
+
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),
|
|
2148
2675
|
children
|
|
2149
2676
|
})
|
|
2150
2677
|
});
|
|
@@ -2155,7 +2682,7 @@ function PopoverContent({ title, children, maxWidth = "max-w-xs", className = ""
|
|
|
2155
2682
|
side,
|
|
2156
2683
|
align,
|
|
2157
2684
|
sideOffset: 8,
|
|
2158
|
-
className: cn("z-50 w-max rounded-xl border border-slate-200 bg-white shadow-lg", "outline-none", maxWidth, className),
|
|
2685
|
+
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),
|
|
2159
2686
|
children: [title && /* @__PURE__ */ jsx("div", {
|
|
2160
2687
|
className: "border-b border-slate-100 px-4 py-3",
|
|
2161
2688
|
children: /* @__PURE__ */ jsx("h3", {
|
|
@@ -2173,8 +2700,8 @@ function PopoverContent({ title, children, maxWidth = "max-w-xs", className = ""
|
|
|
2173
2700
|
/**
|
|
2174
2701
|
* Tooltip — Design System Component
|
|
2175
2702
|
*
|
|
2176
|
-
* A short
|
|
2177
|
-
* an element.
|
|
2703
|
+
* A short label or non-interactive formatted content that appears on hover
|
|
2704
|
+
* or focus to describe an element.
|
|
2178
2705
|
*
|
|
2179
2706
|
* Behavior is provided by @radix-ui/react-tooltip, which handles:
|
|
2180
2707
|
* - Trigger scoping (fires only on the direct trigger, not descendants)
|
|
@@ -2191,14 +2718,16 @@ function PopoverContent({ title, children, maxWidth = "max-w-xs", className = ""
|
|
|
2191
2718
|
* - Typography and spacing
|
|
2192
2719
|
*
|
|
2193
2720
|
* Rules:
|
|
2194
|
-
* - Tooltip content must be
|
|
2721
|
+
* - Tooltip content must be non-interactive. Interactive elements (links,
|
|
2722
|
+
* buttons) inside `content` will be unclickable due to `pointer-events-none`
|
|
2723
|
+
* on the tooltip panel.
|
|
2195
2724
|
* - Do not put ESSENTIAL information only in a tooltip.
|
|
2196
2725
|
* - Use for supplementary descriptions: icon button labels, abbrev. meanings.
|
|
2197
2726
|
*
|
|
2198
2727
|
* Placement: 'top' | 'bottom' | 'left' | 'right'
|
|
2199
2728
|
*
|
|
2200
2729
|
* Difference from Popover:
|
|
2201
|
-
* - Tooltip is
|
|
2730
|
+
* - Tooltip is for non-interactive content, triggered by hover/focus.
|
|
2202
2731
|
* - Popover contains interactive content, triggered by explicit click.
|
|
2203
2732
|
*/
|
|
2204
2733
|
function Tooltip({ content, placement = "top", delayMs = 400, children, className = "" }) {
|
|
@@ -2214,7 +2743,7 @@ function Tooltip({ content, placement = "top", delayMs = 400, children, classNam
|
|
|
2214
2743
|
}), /* @__PURE__ */ jsx(TooltipPrimitive.Portal, { children: /* @__PURE__ */ jsx(TooltipPrimitive.Content, {
|
|
2215
2744
|
side: placement,
|
|
2216
2745
|
sideOffset: 8,
|
|
2217
|
-
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"),
|
|
2746
|
+
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"),
|
|
2218
2747
|
children: content
|
|
2219
2748
|
}) })] })
|
|
2220
2749
|
});
|
|
@@ -2274,7 +2803,7 @@ var BTN_CANCEL = [
|
|
|
2274
2803
|
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/15",
|
|
2275
2804
|
"disabled:cursor-not-allowed disabled:opacity-60"
|
|
2276
2805
|
].join(" ");
|
|
2277
|
-
function ConfirmDialog({ open, onClose, onConfirm, title, description, confirmLabel = "Confirm", cancelLabel = "Cancel", tone = "danger" }) {
|
|
2806
|
+
function ConfirmDialog({ open, onClose, onConfirm, title, description, confirmLabel = "Confirm", cancelLabel = "Cancel", tone = "danger", className = "" }) {
|
|
2278
2807
|
const [loading, setLoading] = useState(false);
|
|
2279
2808
|
const handleConfirm = async () => {
|
|
2280
2809
|
setLoading(true);
|
|
@@ -2292,17 +2821,20 @@ function ConfirmDialog({ open, onClose, onConfirm, title, description, confirmLa
|
|
|
2292
2821
|
title,
|
|
2293
2822
|
description: typeof description === "string" ? description : void 0,
|
|
2294
2823
|
size: "sm",
|
|
2295
|
-
children: [/* @__PURE__ */ jsx(DialogBody, {
|
|
2296
|
-
className
|
|
2297
|
-
children:
|
|
2298
|
-
className:
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2824
|
+
children: [/* @__PURE__ */ jsx(DialogBody, {
|
|
2825
|
+
className,
|
|
2826
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
2827
|
+
className: "flex items-start gap-3",
|
|
2828
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
2829
|
+
className: `mt-0.5 flex h-9 w-9 shrink-0 items-center justify-center rounded-md ${ICON_WRAPPER[tone]}`,
|
|
2830
|
+
"aria-hidden": "true",
|
|
2831
|
+
children: tone === "danger" ? /* @__PURE__ */ jsx(AlertTriangle, { size: 20 }) : /* @__PURE__ */ jsx(Info, { size: 20 })
|
|
2832
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
2833
|
+
className: "text-sm font-medium leading-relaxed text-slate-600",
|
|
2834
|
+
children: description
|
|
2835
|
+
})]
|
|
2836
|
+
})
|
|
2837
|
+
}), /* @__PURE__ */ jsxs(DialogFooter, { children: [/* @__PURE__ */ jsx("button", {
|
|
2306
2838
|
type: "button",
|
|
2307
2839
|
className: BTN_CANCEL,
|
|
2308
2840
|
onClick: onClose,
|
|
@@ -2396,7 +2928,7 @@ var radius = {
|
|
|
2396
2928
|
* - Use gallery Button component (Phase 11 — replaces legacy inlined btn* strings)
|
|
2397
2929
|
* - Use a simple inline ErrorBanner instead of importing from ui.tsx
|
|
2398
2930
|
*/
|
|
2399
|
-
function ConfirmAction({ title, message, label, description, confirmLabel = "Confirm", tone = "danger", disabled = false, onConfirm, children }) {
|
|
2931
|
+
function ConfirmAction({ title, message, label, description, confirmLabel = "Confirm", tone = "danger", disabled = false, onConfirm, children, className = "" }) {
|
|
2400
2932
|
const [open, setOpen] = useState(false);
|
|
2401
2933
|
const [pending, setPending] = useState(false);
|
|
2402
2934
|
const [error, setError] = useState("");
|
|
@@ -2420,7 +2952,7 @@ function ConfirmAction({ title, message, label, description, confirmLabel = "Con
|
|
|
2420
2952
|
},
|
|
2421
2953
|
disabled
|
|
2422
2954
|
}) : /* @__PURE__ */ jsx("span", {
|
|
2423
|
-
className: "inline-flex",
|
|
2955
|
+
className: cn("inline-flex", className),
|
|
2424
2956
|
onClick: () => {
|
|
2425
2957
|
if (!disabled) setOpen(true);
|
|
2426
2958
|
},
|
|
@@ -2480,7 +3012,7 @@ function ConfirmAction({ title, message, label, description, confirmLabel = "Con
|
|
|
2480
3012
|
* This component is presentation-only: callers provide commands and
|
|
2481
3013
|
* handle the query / selection logic.
|
|
2482
3014
|
*/
|
|
2483
|
-
function CommandPalette({ open, onClose, items, query, onQueryChange, placeholder = "Search commands…", "aria-label": ariaLabel = "Command palette" }) {
|
|
3015
|
+
function CommandPalette({ open, onClose, items, query, onQueryChange, placeholder = "Search commands…", "aria-label": ariaLabel = "Command palette", panelClassName = "" }) {
|
|
2484
3016
|
const inputRef = useRef(null);
|
|
2485
3017
|
const panelRef = useRef(null);
|
|
2486
3018
|
const listboxId = useId();
|
|
@@ -2514,6 +3046,23 @@ function CommandPalette({ open, onClose, items, query, onQueryChange, placeholde
|
|
|
2514
3046
|
e.preventDefault();
|
|
2515
3047
|
items[activeIndex]?.onSelect();
|
|
2516
3048
|
onClose();
|
|
3049
|
+
} else if (e.key === "Tab") {
|
|
3050
|
+
const panel = panelRef.current;
|
|
3051
|
+
if (!panel) return;
|
|
3052
|
+
const focusable = Array.from(panel.querySelectorAll("a[href],button:not([disabled]),textarea:not([disabled]),input:not([disabled]),select:not([disabled]),[tabindex]:not([tabindex=\"-1\"])"));
|
|
3053
|
+
if (!focusable.length) {
|
|
3054
|
+
e.preventDefault();
|
|
3055
|
+
return;
|
|
3056
|
+
}
|
|
3057
|
+
const first = focusable[0];
|
|
3058
|
+
const last = focusable[focusable.length - 1];
|
|
3059
|
+
if (e.shiftKey && document.activeElement === first) {
|
|
3060
|
+
e.preventDefault();
|
|
3061
|
+
last.focus();
|
|
3062
|
+
} else if (!e.shiftKey && document.activeElement === last) {
|
|
3063
|
+
e.preventDefault();
|
|
3064
|
+
first.focus();
|
|
3065
|
+
}
|
|
2517
3066
|
}
|
|
2518
3067
|
};
|
|
2519
3068
|
document.addEventListener("keydown", handle);
|
|
@@ -2543,12 +3092,12 @@ function CommandPalette({ open, onClose, items, query, onQueryChange, placeholde
|
|
|
2543
3092
|
role: "dialog",
|
|
2544
3093
|
"aria-modal": "true",
|
|
2545
3094
|
"aria-label": ariaLabel,
|
|
2546
|
-
className: "relative z-10 w-full max-w-lg rounded-2xl border border-slate-200 bg-white shadow-2xl overflow-hidden",
|
|
3095
|
+
className: cn("relative z-10 w-full max-w-lg rounded-2xl border border-slate-200 bg-white shadow-2xl overflow-hidden", panelClassName),
|
|
2547
3096
|
children: [
|
|
2548
3097
|
/* @__PURE__ */ jsxs("div", {
|
|
2549
3098
|
className: "flex items-center gap-3 px-4 py-3 border-b border-slate-100",
|
|
2550
3099
|
children: [
|
|
2551
|
-
/* @__PURE__ */ jsx(Search, {
|
|
3100
|
+
/* @__PURE__ */ jsx(Search$1, {
|
|
2552
3101
|
size: 16,
|
|
2553
3102
|
className: "shrink-0 text-slate-400",
|
|
2554
3103
|
"aria-hidden": "true"
|
|
@@ -2654,10 +3203,11 @@ function CommandPalette({ open, onClose, items, query, onQueryChange, placeholde
|
|
|
2654
3203
|
* ]}
|
|
2655
3204
|
* />
|
|
2656
3205
|
*/
|
|
2657
|
-
function Breadcrumb({ items, showHome = true, ariaLabel = "Breadcrumb" }) {
|
|
3206
|
+
function Breadcrumb({ items, showHome = true, ariaLabel = "Breadcrumb", className = "" }) {
|
|
2658
3207
|
if (items.length === 0) return null;
|
|
2659
3208
|
return /* @__PURE__ */ jsx("nav", {
|
|
2660
3209
|
"aria-label": ariaLabel,
|
|
3210
|
+
className: cn(className),
|
|
2661
3211
|
children: /* @__PURE__ */ jsxs("ol", {
|
|
2662
3212
|
className: "flex flex-wrap items-center gap-1 text-sm font-semibold text-slate-400",
|
|
2663
3213
|
children: [showHome && /* @__PURE__ */ jsx("li", {
|
|
@@ -2798,7 +3348,7 @@ function TabList({ "aria-label": ariaLabel, children, className = "" }) {
|
|
|
2798
3348
|
role: "tablist",
|
|
2799
3349
|
"aria-label": ariaLabel,
|
|
2800
3350
|
onKeyDown: handleKeyDown,
|
|
2801
|
-
className:
|
|
3351
|
+
className: cn("flex border-b-2 border-slate-200", className),
|
|
2802
3352
|
children
|
|
2803
3353
|
});
|
|
2804
3354
|
}
|
|
@@ -2818,12 +3368,7 @@ function Tab({ value, disabled = false, children, className = "" }) {
|
|
|
2818
3368
|
disabled,
|
|
2819
3369
|
tabIndex: isActive ? 0 : -1,
|
|
2820
3370
|
onClick: () => activate(value),
|
|
2821
|
-
className:
|
|
2822
|
-
"relative px-5 py-3 text-sm font-semibold font-heading border-b-4 transition-colors",
|
|
2823
|
-
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-brand-blue/30",
|
|
2824
|
-
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",
|
|
2825
|
-
className
|
|
2826
|
-
].filter(Boolean).join(" "),
|
|
3371
|
+
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),
|
|
2827
3372
|
children
|
|
2828
3373
|
});
|
|
2829
3374
|
}
|
|
@@ -2841,7 +3386,7 @@ function TabPanel({ value, children, className = "" }) {
|
|
|
2841
3386
|
role: "tabpanel",
|
|
2842
3387
|
"aria-labelledby": tabId(baseId, value),
|
|
2843
3388
|
tabIndex: 0,
|
|
2844
|
-
className:
|
|
3389
|
+
className: cn("focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-brand-blue/30 rounded-sm", className),
|
|
2845
3390
|
children
|
|
2846
3391
|
});
|
|
2847
3392
|
}
|
|
@@ -2874,15 +3419,28 @@ function getPageNumbers(current, total) {
|
|
|
2874
3419
|
pages.push(total);
|
|
2875
3420
|
return pages;
|
|
2876
3421
|
}
|
|
2877
|
-
function Pagination({ page, totalPages, onPageChange, ariaLabel = "Pagination", totalItems, pageSize, itemLabel = "items" }) {
|
|
3422
|
+
function Pagination({ page, totalPages, onPageChange, ariaLabel = "Pagination", totalItems, pageSize, itemLabel = "items", className = "" }) {
|
|
2878
3423
|
if (totalPages <= 1 && !totalItems) return null;
|
|
2879
3424
|
const showRecordCount = typeof totalItems === "number" && typeof pageSize === "number";
|
|
2880
3425
|
const rangeStart = showRecordCount ? (page - 1) * pageSize + 1 : null;
|
|
2881
3426
|
const rangeEnd = showRecordCount ? Math.min(page * pageSize, totalItems) : null;
|
|
2882
3427
|
const pageNumbers = getPageNumbers(page, totalPages);
|
|
2883
3428
|
return /* @__PURE__ */ jsxs("div", {
|
|
2884
|
-
className: "flex
|
|
2885
|
-
children: [
|
|
3429
|
+
className: cn("flex items-center justify-between gap-4", className),
|
|
3430
|
+
children: [showRecordCount ? /* @__PURE__ */ jsxs("p", {
|
|
3431
|
+
className: "text-[11px] font-bold tabular-nums text-slate-400",
|
|
3432
|
+
"aria-live": "polite",
|
|
3433
|
+
children: [
|
|
3434
|
+
"Showing ",
|
|
3435
|
+
rangeStart,
|
|
3436
|
+
"–",
|
|
3437
|
+
rangeEnd,
|
|
3438
|
+
" of ",
|
|
3439
|
+
totalItems,
|
|
3440
|
+
" ",
|
|
3441
|
+
itemLabel
|
|
3442
|
+
]
|
|
3443
|
+
}) : /* @__PURE__ */ jsx("span", {}), totalPages > 1 && /* @__PURE__ */ jsx("nav", {
|
|
2886
3444
|
"aria-label": ariaLabel,
|
|
2887
3445
|
children: /* @__PURE__ */ jsxs("ol", {
|
|
2888
3446
|
className: "list-none flex items-center gap-1",
|
|
@@ -2926,19 +3484,6 @@ function Pagination({ page, totalPages, onPageChange, ariaLabel = "Pagination",
|
|
|
2926
3484
|
}) })
|
|
2927
3485
|
]
|
|
2928
3486
|
})
|
|
2929
|
-
}), showRecordCount && /* @__PURE__ */ jsxs("p", {
|
|
2930
|
-
className: "text-[11px] font-bold tabular-nums text-slate-400",
|
|
2931
|
-
"aria-live": "polite",
|
|
2932
|
-
children: [
|
|
2933
|
-
"Showing ",
|
|
2934
|
-
rangeStart,
|
|
2935
|
-
"–",
|
|
2936
|
-
rangeEnd,
|
|
2937
|
-
" of ",
|
|
2938
|
-
totalItems,
|
|
2939
|
-
" ",
|
|
2940
|
-
itemLabel
|
|
2941
|
-
]
|
|
2942
3487
|
})]
|
|
2943
3488
|
});
|
|
2944
3489
|
}
|
|
@@ -2969,7 +3514,7 @@ function Stepper({ steps, currentStep, completedSteps = [], disabledSteps = [],
|
|
|
2969
3514
|
if (!(orientation === "horizontal")) return /* @__PURE__ */ jsx("div", {
|
|
2970
3515
|
role: "group",
|
|
2971
3516
|
"aria-label": "Progress",
|
|
2972
|
-
className:
|
|
3517
|
+
className: cn("flex flex-col", className),
|
|
2973
3518
|
children: /* @__PURE__ */ jsx("ol", {
|
|
2974
3519
|
className: "flex flex-col",
|
|
2975
3520
|
children: steps.map((step, idx) => {
|
|
@@ -3018,7 +3563,7 @@ function Stepper({ steps, currentStep, completedSteps = [], disabledSteps = [],
|
|
|
3018
3563
|
return /* @__PURE__ */ jsx("div", {
|
|
3019
3564
|
role: "group",
|
|
3020
3565
|
"aria-label": "Progress",
|
|
3021
|
-
className:
|
|
3566
|
+
className: cn("w-full", className),
|
|
3022
3567
|
children: /* @__PURE__ */ jsx("ol", {
|
|
3023
3568
|
className: "grid w-full",
|
|
3024
3569
|
style: { gridTemplateColumns: `repeat(${steps.length}, 1fr)` },
|
|
@@ -3216,10 +3761,23 @@ function Dropdown({ trigger, isOpen, onToggle, onClose, children, triggerClassNa
|
|
|
3216
3761
|
const ref = useRef(null);
|
|
3217
3762
|
const triggerRef = useRef(null);
|
|
3218
3763
|
const panelId = `${useId().replace(/:/g, "")}-panel`;
|
|
3764
|
+
const [panelStyle, setPanelStyle] = useState({});
|
|
3765
|
+
useEffect(() => {
|
|
3766
|
+
if (!isOpen || !triggerRef.current) return;
|
|
3767
|
+
const rect = triggerRef.current.getBoundingClientRect();
|
|
3768
|
+
setPanelStyle({
|
|
3769
|
+
position: "fixed",
|
|
3770
|
+
top: rect.bottom + 4,
|
|
3771
|
+
left: rect.left,
|
|
3772
|
+
zIndex: 9999,
|
|
3773
|
+
minWidth: Math.max(rect.width, 180)
|
|
3774
|
+
});
|
|
3775
|
+
}, [isOpen]);
|
|
3219
3776
|
useEffect(() => {
|
|
3220
3777
|
if (!isOpen) return;
|
|
3221
3778
|
const handlePointerDown = (e) => {
|
|
3222
|
-
|
|
3779
|
+
const target = e.target;
|
|
3780
|
+
if (ref.current && !ref.current.contains(target)) {
|
|
3223
3781
|
onClose();
|
|
3224
3782
|
triggerRef.current?.focus();
|
|
3225
3783
|
}
|
|
@@ -3249,21 +3807,14 @@ function Dropdown({ trigger, isOpen, onToggle, onClose, children, triggerClassNa
|
|
|
3249
3807
|
"aria-expanded": isOpen,
|
|
3250
3808
|
"aria-haspopup": "true",
|
|
3251
3809
|
"aria-controls": isOpen ? panelId : void 0,
|
|
3252
|
-
className:
|
|
3253
|
-
"inline-flex items-center gap-2 rounded-lg px-4 py-2 text-sm font-bold text-slate-600 transition-all",
|
|
3254
|
-
"hover:bg-slate-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30",
|
|
3255
|
-
triggerClassName
|
|
3256
|
-
].filter(Boolean).join(" "),
|
|
3810
|
+
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),
|
|
3257
3811
|
children: trigger
|
|
3258
|
-
}), isOpen && /* @__PURE__ */ jsx("div", {
|
|
3812
|
+
}), isOpen && createPortal(/* @__PURE__ */ jsx("div", {
|
|
3259
3813
|
id: panelId,
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
"rounded-xl border border-slate-200 bg-white shadow-lg",
|
|
3263
|
-
panelClassName
|
|
3264
|
-
].filter(Boolean).join(" "),
|
|
3814
|
+
style: panelStyle,
|
|
3815
|
+
className: cn("max-h-[280px] overflow-y-auto", "rounded-xl border border-slate-200 bg-white shadow-lg", panelClassName),
|
|
3265
3816
|
children
|
|
3266
|
-
})]
|
|
3817
|
+
}), document.body)]
|
|
3267
3818
|
});
|
|
3268
3819
|
}
|
|
3269
3820
|
//#endregion
|
|
@@ -3290,15 +3841,36 @@ function Dropdown({ trigger, isOpen, onToggle, onClose, children, triggerClassNa
|
|
|
3290
3841
|
* ]}
|
|
3291
3842
|
* />
|
|
3292
3843
|
*/
|
|
3293
|
-
function Menu({ trigger, items, ariaLabel = "Menu", align = "right" }) {
|
|
3294
|
-
const [
|
|
3844
|
+
function Menu({ trigger, items, ariaLabel = "Menu", align = "right", open: controlledOpen, onOpenChange, panelClassName = "" }) {
|
|
3845
|
+
const [internalOpen, setInternalOpen] = useState(false);
|
|
3846
|
+
const isControlled = controlledOpen !== void 0;
|
|
3847
|
+
const open = isControlled ? controlledOpen : internalOpen;
|
|
3848
|
+
const setOpen = (next) => {
|
|
3849
|
+
if (!isControlled) setInternalOpen(next);
|
|
3850
|
+
onOpenChange?.(next);
|
|
3851
|
+
};
|
|
3295
3852
|
const containerRef = useRef(null);
|
|
3296
3853
|
const triggerRef = useRef(null);
|
|
3854
|
+
const panelRef = useRef(null);
|
|
3297
3855
|
const itemRefs = useRef([]);
|
|
3856
|
+
const [panelStyle, setPanelStyle] = useState({});
|
|
3857
|
+
useEffect(() => {
|
|
3858
|
+
if (!open || !triggerRef.current) return;
|
|
3859
|
+
const rect = triggerRef.current.getBoundingClientRect();
|
|
3860
|
+
const PANEL_MIN_WIDTH = 176;
|
|
3861
|
+
setPanelStyle({
|
|
3862
|
+
position: "fixed",
|
|
3863
|
+
top: rect.bottom + 4,
|
|
3864
|
+
left: align === "left" ? rect.left : Math.max(0, rect.right - PANEL_MIN_WIDTH),
|
|
3865
|
+
zIndex: 9999,
|
|
3866
|
+
minWidth: Math.max(rect.width, PANEL_MIN_WIDTH)
|
|
3867
|
+
});
|
|
3868
|
+
}, [open, align]);
|
|
3298
3869
|
useEffect(() => {
|
|
3299
3870
|
if (!open) return;
|
|
3300
3871
|
const handlePointerDown = (e) => {
|
|
3301
|
-
|
|
3872
|
+
const target = e.target;
|
|
3873
|
+
if (containerRef.current && !containerRef.current.contains(target) && !(panelRef.current && panelRef.current.contains(target))) {
|
|
3302
3874
|
setOpen(false);
|
|
3303
3875
|
(triggerRef.current?.querySelector("button,a,[tabindex]"))?.focus();
|
|
3304
3876
|
}
|
|
@@ -3339,22 +3911,23 @@ function Menu({ trigger, items, ariaLabel = "Menu", align = "right" }) {
|
|
|
3339
3911
|
focusItem(items.length - 1);
|
|
3340
3912
|
}
|
|
3341
3913
|
};
|
|
3342
|
-
const panelAlign = align === "left" ? "left-0" : "right-0";
|
|
3343
3914
|
return /* @__PURE__ */ jsxs("div", {
|
|
3344
3915
|
className: "relative inline-block",
|
|
3345
3916
|
ref: containerRef,
|
|
3346
3917
|
children: [/* @__PURE__ */ jsx("div", {
|
|
3347
3918
|
ref: triggerRef,
|
|
3348
|
-
onClick: () => setOpen(
|
|
3919
|
+
onClick: () => setOpen(!open),
|
|
3349
3920
|
className: "cursor-pointer",
|
|
3350
3921
|
children: isValidElement(trigger) ? cloneElement(trigger, {
|
|
3351
3922
|
"aria-expanded": open,
|
|
3352
3923
|
"aria-haspopup": "menu"
|
|
3353
3924
|
}) : trigger
|
|
3354
|
-
}), open && /* @__PURE__ */ jsx("div", {
|
|
3925
|
+
}), open && createPortal(/* @__PURE__ */ jsx("div", {
|
|
3926
|
+
ref: panelRef,
|
|
3355
3927
|
role: "menu",
|
|
3356
3928
|
"aria-label": ariaLabel,
|
|
3357
|
-
|
|
3929
|
+
style: panelStyle,
|
|
3930
|
+
className: cn("overflow-hidden rounded-xl border border-slate-200 bg-white py-1 shadow-xl", panelClassName),
|
|
3358
3931
|
children: items.map((item, index) => /* @__PURE__ */ jsxs("button", {
|
|
3359
3932
|
ref: (node) => {
|
|
3360
3933
|
itemRefs.current[index] = node;
|
|
@@ -3380,7 +3953,7 @@ function Menu({ trigger, items, ariaLabel = "Menu", align = "right" }) {
|
|
|
3380
3953
|
children: item.icon
|
|
3381
3954
|
}), item.label]
|
|
3382
3955
|
}, item.label))
|
|
3383
|
-
})]
|
|
3956
|
+
}), document.body)]
|
|
3384
3957
|
});
|
|
3385
3958
|
}
|
|
3386
3959
|
//#endregion
|
|
@@ -3395,13 +3968,13 @@ function Card({ variant = "default", children, className = "", onClick }) {
|
|
|
3395
3968
|
return /* @__PURE__ */ jsx(onClick ? "button" : "div", {
|
|
3396
3969
|
type: onClick ? "button" : void 0,
|
|
3397
3970
|
onClick,
|
|
3398
|
-
className:
|
|
3971
|
+
className: cn("overflow-hidden rounded-lg", VARIANT_CLASS[variant], className),
|
|
3399
3972
|
children
|
|
3400
3973
|
});
|
|
3401
3974
|
}
|
|
3402
|
-
function CardHeader({ title, description, actions }) {
|
|
3975
|
+
function CardHeader({ title, description, actions, className = "" }) {
|
|
3403
3976
|
return /* @__PURE__ */ jsxs("div", {
|
|
3404
|
-
className: "flex items-start justify-between gap-3 px-5 py-4 border-b-2 border-slate-100",
|
|
3977
|
+
className: cn("flex items-start justify-between gap-3 px-5 py-4 border-b-2 border-slate-100", className),
|
|
3405
3978
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
3406
3979
|
className: "min-w-0",
|
|
3407
3980
|
children: [/* @__PURE__ */ jsx("h3", {
|
|
@@ -3419,13 +3992,13 @@ function CardHeader({ title, description, actions }) {
|
|
|
3419
3992
|
}
|
|
3420
3993
|
function CardContent({ children, className = "" }) {
|
|
3421
3994
|
return /* @__PURE__ */ jsx("div", {
|
|
3422
|
-
className:
|
|
3995
|
+
className: cn("px-5 py-4", className),
|
|
3423
3996
|
children
|
|
3424
3997
|
});
|
|
3425
3998
|
}
|
|
3426
3999
|
function CardFooter({ children, className = "" }) {
|
|
3427
4000
|
return /* @__PURE__ */ jsx("div", {
|
|
3428
|
-
className:
|
|
4001
|
+
className: cn("flex items-center justify-end gap-2 border-t border-slate-100 px-5 py-3", className),
|
|
3429
4002
|
children
|
|
3430
4003
|
});
|
|
3431
4004
|
}
|
|
@@ -3491,7 +4064,7 @@ function Accordion({ type = "single", defaultValue, value: controlledValue, onVa
|
|
|
3491
4064
|
toggle
|
|
3492
4065
|
},
|
|
3493
4066
|
children: /* @__PURE__ */ jsx("div", {
|
|
3494
|
-
className:
|
|
4067
|
+
className: cn("divide-y divide-slate-200 rounded-xl border border-slate-200", className),
|
|
3495
4068
|
children
|
|
3496
4069
|
})
|
|
3497
4070
|
});
|
|
@@ -3508,7 +4081,7 @@ function AccordionItem({ value, disabled = false, children, className = "" }) {
|
|
|
3508
4081
|
triggerId
|
|
3509
4082
|
},
|
|
3510
4083
|
children: /* @__PURE__ */ jsx("div", {
|
|
3511
|
-
className:
|
|
4084
|
+
className: cn(className),
|
|
3512
4085
|
children
|
|
3513
4086
|
})
|
|
3514
4087
|
});
|
|
@@ -3525,12 +4098,7 @@ function AccordionTrigger({ children, className = "" }) {
|
|
|
3525
4098
|
"aria-disabled": disabled || void 0,
|
|
3526
4099
|
disabled,
|
|
3527
4100
|
onClick: () => toggle(value),
|
|
3528
|
-
className:
|
|
3529
|
-
"flex w-full items-center justify-between px-5 py-4 text-left text-sm font-semibold",
|
|
3530
|
-
"transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-brand-blue/30",
|
|
3531
|
-
disabled ? "cursor-not-allowed text-slate-300" : "text-slate-800 hover:bg-slate-50",
|
|
3532
|
-
className
|
|
3533
|
-
].filter(Boolean).join(" "),
|
|
4101
|
+
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),
|
|
3534
4102
|
children: [/* @__PURE__ */ jsx("span", { children }), /* @__PURE__ */ jsx(ChevronDown, {
|
|
3535
4103
|
size: 16,
|
|
3536
4104
|
"aria-hidden": "true",
|
|
@@ -3541,13 +4109,21 @@ function AccordionTrigger({ children, className = "" }) {
|
|
|
3541
4109
|
function AccordionContent({ children, className = "" }) {
|
|
3542
4110
|
const { openValues } = useAccordionContext();
|
|
3543
4111
|
const { value, panelId, triggerId } = useItemContext();
|
|
3544
|
-
|
|
4112
|
+
const isOpen = openValues.has(value);
|
|
3545
4113
|
return /* @__PURE__ */ jsx("div", {
|
|
3546
4114
|
id: panelId,
|
|
3547
4115
|
role: "region",
|
|
3548
4116
|
"aria-labelledby": triggerId,
|
|
3549
|
-
|
|
3550
|
-
|
|
4117
|
+
"aria-hidden": !isOpen || void 0,
|
|
4118
|
+
"data-state": isOpen ? "open" : "closed",
|
|
4119
|
+
className: cn("grid overflow-hidden transition-[grid-template-rows] duration-200 ease-out", isOpen ? "grid-rows-[1fr]" : "grid-rows-[0fr]"),
|
|
4120
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
4121
|
+
className: "min-h-0",
|
|
4122
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
4123
|
+
className: cn("border-t border-slate-100 px-5 py-4 text-sm font-medium text-slate-600", className),
|
|
4124
|
+
children
|
|
4125
|
+
})
|
|
4126
|
+
})
|
|
3551
4127
|
});
|
|
3552
4128
|
}
|
|
3553
4129
|
//#endregion
|
|
@@ -3559,25 +4135,17 @@ var LAYOUT_CLASS = {
|
|
|
3559
4135
|
};
|
|
3560
4136
|
function DescriptionList({ layout = "vertical", children, className = "" }) {
|
|
3561
4137
|
return /* @__PURE__ */ jsx("dl", {
|
|
3562
|
-
className:
|
|
4138
|
+
className: cn(LAYOUT_CLASS[layout], className),
|
|
3563
4139
|
children
|
|
3564
4140
|
});
|
|
3565
4141
|
}
|
|
3566
4142
|
function DescriptionItem({ label, value, fullWidth = false, className = "" }) {
|
|
3567
4143
|
const isEmpty = value === void 0 || value === null || value === "";
|
|
3568
4144
|
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("dt", {
|
|
3569
|
-
className: [
|
|
3570
|
-
"text-xs font-bold uppercase tracking-[0.12em] text-slate-500",
|
|
3571
|
-
fullWidth ? "sm:col-span-2" : "",
|
|
3572
|
-
className
|
|
3573
|
-
].filter(Boolean).join(" "),
|
|
4145
|
+
className: cn("text-xs font-bold uppercase tracking-[0.12em] text-slate-500", fullWidth && "sm:col-span-2", className),
|
|
3574
4146
|
children: label
|
|
3575
4147
|
}), /* @__PURE__ */ jsx("dd", {
|
|
3576
|
-
className:
|
|
3577
|
-
"text-sm font-medium text-slate-800",
|
|
3578
|
-
fullWidth ? "sm:col-span-2" : "",
|
|
3579
|
-
isEmpty ? "text-slate-300" : ""
|
|
3580
|
-
].filter(Boolean).join(" "),
|
|
4148
|
+
className: cn("text-sm font-medium text-slate-800", fullWidth && "sm:col-span-2", isEmpty && "text-slate-300"),
|
|
3581
4149
|
children: isEmpty ? "—" : value
|
|
3582
4150
|
})] });
|
|
3583
4151
|
}
|
|
@@ -3587,7 +4155,7 @@ function ActivityFeed({ events, "aria-label": ariaLabel = "Activity feed", class
|
|
|
3587
4155
|
if (events.length === 0) return null;
|
|
3588
4156
|
return /* @__PURE__ */ jsx("ol", {
|
|
3589
4157
|
"aria-label": ariaLabel,
|
|
3590
|
-
className:
|
|
4158
|
+
className: cn("space-y-0", className),
|
|
3591
4159
|
children: events.map((event, i) => {
|
|
3592
4160
|
const isLast = i === events.length - 1;
|
|
3593
4161
|
return /* @__PURE__ */ jsxs("li", {
|
|
@@ -3668,7 +4236,7 @@ var STATUS_LABEL = {
|
|
|
3668
4236
|
function ApprovalTimeline({ steps, "aria-label": ariaLabel = "Approval workflow", className = "" }) {
|
|
3669
4237
|
return /* @__PURE__ */ jsx("ol", {
|
|
3670
4238
|
"aria-label": ariaLabel,
|
|
3671
|
-
className:
|
|
4239
|
+
className: cn("space-y-0", className),
|
|
3672
4240
|
children: steps.map((step, i) => {
|
|
3673
4241
|
const isLast = i === steps.length - 1;
|
|
3674
4242
|
return /* @__PURE__ */ jsxs("li", {
|
|
@@ -3720,13 +4288,13 @@ function ApprovalTimeline({ steps, "aria-label": ariaLabel = "Approval workflow"
|
|
|
3720
4288
|
//#region src/ui-library/gallery/timeline/Timeline.tsx
|
|
3721
4289
|
function Timeline({ children, className = "" }) {
|
|
3722
4290
|
return /* @__PURE__ */ jsx("div", {
|
|
3723
|
-
className:
|
|
4291
|
+
className: cn("w-full space-y-0", className),
|
|
3724
4292
|
children
|
|
3725
4293
|
});
|
|
3726
4294
|
}
|
|
3727
|
-
function TimelineItem({ isFirst, isLast, children }) {
|
|
4295
|
+
function TimelineItem({ isFirst, isLast, children, className = "" }) {
|
|
3728
4296
|
return /* @__PURE__ */ jsxs("div", {
|
|
3729
|
-
className: "flex gap-3",
|
|
4297
|
+
className: cn("flex gap-3", className),
|
|
3730
4298
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
3731
4299
|
className: "flex flex-col items-center shrink-0 pt-4",
|
|
3732
4300
|
"aria-hidden": "true",
|
|
@@ -3806,9 +4374,9 @@ function TimelineFooter({ reason, source }) {
|
|
|
3806
4374
|
}), source] })]
|
|
3807
4375
|
});
|
|
3808
4376
|
}
|
|
3809
|
-
function TimelineEmptyState({ icon, title, description }) {
|
|
4377
|
+
function TimelineEmptyState({ icon, title, description, className = "" }) {
|
|
3810
4378
|
return /* @__PURE__ */ jsxs("div", {
|
|
3811
|
-
className: "py-16 text-center",
|
|
4379
|
+
className: cn("py-16 text-center", className),
|
|
3812
4380
|
children: [
|
|
3813
4381
|
/* @__PURE__ */ jsx("div", {
|
|
3814
4382
|
className: "mx-auto mb-3 flex h-12 w-12 items-center justify-center rounded-xl bg-slate-50",
|
|
@@ -3833,12 +4401,7 @@ function TimelineEmptyState({ icon, title, description }) {
|
|
|
3833
4401
|
//#region src/ui-library/gallery/empty-state/EmptyState.tsx
|
|
3834
4402
|
function EmptyState({ title, hint, icon, action, className = "" }) {
|
|
3835
4403
|
return /* @__PURE__ */ jsxs("div", {
|
|
3836
|
-
className: [
|
|
3837
|
-
"flex flex-col items-center justify-center gap-2",
|
|
3838
|
-
"rounded-lg border border-brand-sky/20 bg-brand-sky/[0.04]",
|
|
3839
|
-
"px-6 py-12 text-center",
|
|
3840
|
-
className
|
|
3841
|
-
].filter(Boolean).join(" "),
|
|
4404
|
+
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),
|
|
3842
4405
|
children: [
|
|
3843
4406
|
icon && /* @__PURE__ */ jsx("div", {
|
|
3844
4407
|
className: "mb-1 text-brand-sky/50",
|
|
@@ -3911,7 +4474,7 @@ function ActionButton({ action, variant }) {
|
|
|
3911
4474
|
function StatusPage({ eyebrow, title, description, code, icon, primaryAction, secondaryAction, className = "" }) {
|
|
3912
4475
|
const hasActions = primaryAction || secondaryAction;
|
|
3913
4476
|
return /* @__PURE__ */ jsxs("div", {
|
|
3914
|
-
className:
|
|
4477
|
+
className: cn("relative flex min-h-screen items-center justify-center bg-surface-page px-6 py-16 overflow-hidden", className),
|
|
3915
4478
|
children: [code ? /* @__PURE__ */ jsx("span", {
|
|
3916
4479
|
"aria-hidden": "true",
|
|
3917
4480
|
className: "pointer-events-none absolute select-none font-heading font-bold text-brand-navy opacity-[0.07] text-[80px] sm:text-[120px] leading-none",
|
|
@@ -3957,7 +4520,7 @@ function StatusPage({ eyebrow, title, description, code, icon, primaryAction, se
|
|
|
3957
4520
|
//#region src/ui-library/gallery/employee-card/EmployeeCard.tsx
|
|
3958
4521
|
function EmployeeCard({ name, role, avatarSrc, status, meta, actions, className = "" }) {
|
|
3959
4522
|
return /* @__PURE__ */ jsxs("article", {
|
|
3960
|
-
className:
|
|
4523
|
+
className: cn("rounded-2xl border border-slate-200 bg-white shadow-sm overflow-hidden", className),
|
|
3961
4524
|
"aria-label": `${name}${role ? ", " + role : ""}`,
|
|
3962
4525
|
children: [/* @__PURE__ */ jsx("div", {
|
|
3963
4526
|
className: "h-1.5 bg-gradient-to-r from-brand-navy to-brand-blue",
|
|
@@ -4204,7 +4767,7 @@ function TreeView({ items, selectedId, onSelect, "aria-label": ariaLabel, classN
|
|
|
4204
4767
|
role: "tree",
|
|
4205
4768
|
"aria-label": ariaLabel,
|
|
4206
4769
|
onKeyDown: handleKeyDown,
|
|
4207
|
-
className:
|
|
4770
|
+
className: cn("flex flex-col gap-0.5 outline-none", className),
|
|
4208
4771
|
tabIndex: 0,
|
|
4209
4772
|
children: items.map((item) => /* @__PURE__ */ jsx(TreeNode, {
|
|
4210
4773
|
item,
|
|
@@ -7407,23 +7970,26 @@ var TREND_CONFIG = {
|
|
|
7407
7970
|
label: "No change"
|
|
7408
7971
|
}
|
|
7409
7972
|
};
|
|
7410
|
-
function Statistic({ label, value, helper, icon: Icon, trend, trendLabel, variant = "default" }) {
|
|
7411
|
-
if (variant === "minimal") return /* @__PURE__ */ jsxs("div", {
|
|
7412
|
-
|
|
7413
|
-
|
|
7414
|
-
|
|
7415
|
-
|
|
7416
|
-
|
|
7417
|
-
|
|
7418
|
-
|
|
7419
|
-
|
|
7420
|
-
|
|
7421
|
-
|
|
7422
|
-
|
|
7423
|
-
|
|
7424
|
-
|
|
7973
|
+
function Statistic({ label, value, helper, icon: Icon, trend, trendLabel, variant = "default", className = "" }) {
|
|
7974
|
+
if (variant === "minimal") return /* @__PURE__ */ jsxs("div", {
|
|
7975
|
+
className: cn(className),
|
|
7976
|
+
children: [
|
|
7977
|
+
/* @__PURE__ */ jsx("p", {
|
|
7978
|
+
className: "text-[11px] font-bold uppercase tracking-[0.18em] text-brand-sky",
|
|
7979
|
+
children: label
|
|
7980
|
+
}),
|
|
7981
|
+
/* @__PURE__ */ jsx("p", {
|
|
7982
|
+
className: "mt-1 font-heading text-4xl font-bold tabular-nums text-brand-navy",
|
|
7983
|
+
children: value
|
|
7984
|
+
}),
|
|
7985
|
+
helper && /* @__PURE__ */ jsx("p", {
|
|
7986
|
+
className: "mt-1 text-xs font-medium text-slate-400",
|
|
7987
|
+
children: helper
|
|
7988
|
+
})
|
|
7989
|
+
]
|
|
7990
|
+
});
|
|
7425
7991
|
if (variant === "compact") return /* @__PURE__ */ jsxs("div", {
|
|
7426
|
-
className: "flex items-center gap-4 rounded-2xl border border-slate-200 bg-white px-5 py-4 shadow-sm",
|
|
7992
|
+
className: cn("flex items-center gap-4 rounded-2xl border border-slate-200 bg-white px-5 py-4 shadow-sm", className),
|
|
7427
7993
|
children: [Icon && /* @__PURE__ */ jsx("div", {
|
|
7428
7994
|
className: "flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-brand-navy/[0.06] text-brand-navy",
|
|
7429
7995
|
children: /* @__PURE__ */ jsx(Icon, {
|
|
@@ -7448,10 +8014,11 @@ function Statistic({ label, value, helper, icon: Icon, trend, trendLabel, varian
|
|
|
7448
8014
|
]
|
|
7449
8015
|
})]
|
|
7450
8016
|
});
|
|
8017
|
+
const stripColor = variant === "accent" ? "bg-brand-blue" : "bg-brand-navy";
|
|
7451
8018
|
return /* @__PURE__ */ jsxs("div", {
|
|
7452
|
-
className: "relative overflow-hidden rounded-2xl border border-slate-200 bg-white shadow-sm",
|
|
8019
|
+
className: cn("relative overflow-hidden rounded-2xl border border-slate-200 bg-white shadow-sm", className),
|
|
7453
8020
|
children: [/* @__PURE__ */ jsx("div", {
|
|
7454
|
-
className: `absolute inset-y-0 left-0 w-1 ${
|
|
8021
|
+
className: `absolute inset-y-0 left-0 w-1 ${stripColor}`,
|
|
7455
8022
|
"aria-hidden": "true"
|
|
7456
8023
|
}), /* @__PURE__ */ jsxs("div", {
|
|
7457
8024
|
className: "px-6 py-5",
|
|
@@ -7497,6 +8064,18 @@ function Statistic({ label, value, helper, icon: Icon, trend, trendLabel, varian
|
|
|
7497
8064
|
}
|
|
7498
8065
|
//#endregion
|
|
7499
8066
|
//#region src/ui-library/gallery/status-dot/StatusDot.tsx
|
|
8067
|
+
/**
|
|
8068
|
+
* StatusDot — Design System Component
|
|
8069
|
+
*
|
|
8070
|
+
* A coloured glowing dot paired with a label. Used in tables to indicate
|
|
8071
|
+
* account or entity status at a glance.
|
|
8072
|
+
*
|
|
8073
|
+
* Self-contained: uses inline styles so it works without any external CSS.
|
|
8074
|
+
*
|
|
8075
|
+
* Usage:
|
|
8076
|
+
* <StatusDot status="active" label="Active" />
|
|
8077
|
+
* <StatusDot status="locked" label="Locked" />
|
|
8078
|
+
*/
|
|
7500
8079
|
var DOT_STYLES = {
|
|
7501
8080
|
active: {
|
|
7502
8081
|
bg: "#10b981",
|
|
@@ -7518,15 +8097,7 @@ var DOT_STYLES = {
|
|
|
7518
8097
|
function StatusDot({ status, label, className = "" }) {
|
|
7519
8098
|
const dot = DOT_STYLES[status];
|
|
7520
8099
|
return /* @__PURE__ */ jsxs("div", {
|
|
7521
|
-
|
|
7522
|
-
display: "inline-flex",
|
|
7523
|
-
alignItems: "center",
|
|
7524
|
-
gap: 8,
|
|
7525
|
-
fontWeight: 600,
|
|
7526
|
-
fontSize: "0.8125rem",
|
|
7527
|
-
color: "#334155"
|
|
7528
|
-
},
|
|
7529
|
-
className,
|
|
8100
|
+
className: cn("inline-flex items-center gap-2 text-[0.8125rem] font-semibold text-slate-700", className),
|
|
7530
8101
|
children: [/* @__PURE__ */ jsx("span", {
|
|
7531
8102
|
"aria-hidden": "true",
|
|
7532
8103
|
style: {
|
|
@@ -7597,7 +8168,7 @@ function applyColumnOrder(columns, order) {
|
|
|
7597
8168
|
for (const col of columns) if (!seen.has(col.key)) result.push(col);
|
|
7598
8169
|
return result;
|
|
7599
8170
|
}
|
|
7600
|
-
function ColumnManager({ columns, hiddenColumnKeys, columnOrder, onHiddenColumnKeysChange, onColumnOrderChange, onReset, triggerLabel = "Columns" }) {
|
|
8171
|
+
function ColumnManager({ columns, hiddenColumnKeys, columnOrder, onHiddenColumnKeysChange, onColumnOrderChange, onReset, triggerLabel = "Columns", className = "" }) {
|
|
7601
8172
|
const [open, setOpen] = useState(false);
|
|
7602
8173
|
const triggerRef = useRef(null);
|
|
7603
8174
|
const panelRef = useRef(null);
|
|
@@ -7641,7 +8212,7 @@ function ColumnManager({ columns, hiddenColumnKeys, columnOrder, onHiddenColumnK
|
|
|
7641
8212
|
onColumnOrderChange(next.map((c) => c.key));
|
|
7642
8213
|
}
|
|
7643
8214
|
return /* @__PURE__ */ jsxs("div", {
|
|
7644
|
-
className: "relative inline-block",
|
|
8215
|
+
className: cn("relative inline-block", className),
|
|
7645
8216
|
children: [/* @__PURE__ */ jsxs("button", {
|
|
7646
8217
|
ref: triggerRef,
|
|
7647
8218
|
type: "button",
|
|
@@ -7930,4 +8501,4 @@ var shadow = {
|
|
|
7930
8501
|
overlay: "shadow-xl"
|
|
7931
8502
|
};
|
|
7932
8503
|
//#endregion
|
|
7933
|
-
export { Accordion, AccordionContent, AccordionItem, AccordionTrigger, ActivityFeed, Alert, ApprovalTimeline, AreaChart, Avatar, Badge, BarChart, BarList, Breadcrumb, Button, CHART_PALETTE, Calendar, Card, CardContent, CardFooter, CardHeader, Checkbox, ColumnManager, Combobox, CommandPalette, ConfirmAction, ConfirmDialog, DatePicker, DescriptionItem, DescriptionList, Dialog, DialogBody, DialogFooter, Divider, Drawer, Dropdown, EmployeeCard, EmptyState, ErrorBanner, Field, FieldContext, FieldInput, FileUpload, FilterChip, Heatmap, IconButton, Input, IsoCubeBlock, Kanban, LineChart, MaskedValue, Menu, Modal, OrgChart, OrgUnitTree, OtpInput, PafTemplate, PageHeader, Pagination, PayslipTemplate, PermissionMatrix, Popover, PopoverContent, PopoverTrigger, Progress, RadioGroup, RadioItem, ReportTemplate, STATUS_PAGE_PRESETS, Section, SegmentedControl, Select, SidebarGroup, SidebarItem, SidebarNav, SidebarSection, Skeleton, SkeletonAvatar, SkeletonCard, SkeletonTableRow, SkeletonText, Spinner, Statistic, StatusBadge, StatusDot, StatusPage, Stepper, Switch, Tab, TabList, TabPanel, TabPanels, Tabs, Textarea, TimekeepingTemplate, Timeline, TimelineEmptyState, TimelineFooter, TimelineItem, TimelineValueCard, Toaster, Toolbar, Tooltip, TreeView, ValidationSummary, applyColumnOrder, colors_exports as colors, dimension_exports as dimension, radius_exports as radius, shadow_exports as shadow, statusMenuItems, toast, typography_exports as typography, useFieldContext };
|
|
8504
|
+
export { Accordion, AccordionContent, AccordionItem, AccordionTrigger, ActivityFeed, Alert, ApprovalTimeline, AreaChart, Avatar, Badge, BarChart, BarList, Breadcrumb, Button, CHART_PALETTE, Calendar, Card, CardContent, CardFooter, CardHeader, Checkbox, ColumnManager, Combobox, CommandPalette, ConfirmAction, ConfirmDialog, DatePicker, DescriptionItem, DescriptionList, Dialog, DialogBody, DialogFooter, Divider, Drawer, Dropdown, EmployeeCard, EmptyState, ErrorBanner, Field, FieldContext, FieldInput, FileUpload, FilterChip, Heatmap, IconButton, Input, IsoCubeBlock, Kanban, LineChart, MaskedValue, Menu, Modal, OrgChart, OrgUnitTree, OtpInput, PafTemplate, PageHeader, Pagination, PayslipTemplate, PermissionMatrix, Popover, PopoverContent, PopoverTrigger, Progress, RadioGroup, RadioItem, ReportTemplate, STATUS_PAGE_PRESETS, Search, Section, SegmentedControl, Select, SidebarGroup, SidebarItem, SidebarNav, SidebarSection, Skeleton, SkeletonAvatar, SkeletonCard, SkeletonTableRow, SkeletonText, Spinner, Statistic, StatusBadge, StatusDot, StatusPage, Stepper, Switch, Tab, TabList, TabPanel, TabPanels, Tabs, Textarea, TimekeepingTemplate, Timeline, TimelineEmptyState, TimelineFooter, TimelineItem, TimelineValueCard, Toaster, Toolbar, Tooltip, TreeView, ValidationSummary, applyColumnOrder, colors_exports as colors, dimension_exports as dimension, radius_exports as radius, shadow_exports as shadow, statusMenuItems, toast, typography_exports as typography, useFieldContext };
|