@diwauhris/ui 1.7.4 → 1.7.7

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