@diwauhris/ui 1.7.3 → 1.7.6

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