react-os-shell 4.21.1 → 4.41.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/dist/{Browser-2GQLOOS7.js → Browser-TKBH4DZ3.js} +6 -6
  2. package/dist/{Browser-2GQLOOS7.js.map → Browser-TKBH4DZ3.js.map} +1 -1
  3. package/dist/{Calculator-QCAJ6ITI.js → Calculator-XERXEV35.js} +5 -5
  4. package/dist/{Calculator-QCAJ6ITI.js.map → Calculator-XERXEV35.js.map} +1 -1
  5. package/dist/ConfirmDialog-EEH4CWSW.js +3 -0
  6. package/dist/{ConfirmDialog-WAM5OKAV.js.map → ConfirmDialog-EEH4CWSW.js.map} +1 -1
  7. package/dist/{CurrencyConverter-PCYC6RL5.js → CurrencyConverter-OSZISLHT.js} +5 -5
  8. package/dist/{CurrencyConverter-PCYC6RL5.js.map → CurrencyConverter-OSZISLHT.js.map} +1 -1
  9. package/dist/{Documents-MOQNF6HE.js → Documents-YRTLDTLX.js} +6 -6
  10. package/dist/{Documents-MOQNF6HE.js.map → Documents-YRTLDTLX.js.map} +1 -1
  11. package/dist/{Files-YBH6XKXE.js → Files-WHYCDRYU.js} +7 -7
  12. package/dist/{Files-YBH6XKXE.js.map → Files-WHYCDRYU.js.map} +1 -1
  13. package/dist/{Notepad-E3L6AOGU.js → Notepad-6UAWRNZH.js} +6 -6
  14. package/dist/{Notepad-E3L6AOGU.js.map → Notepad-6UAWRNZH.js.map} +1 -1
  15. package/dist/{PomodoroTimer-YABFEPEV.js → PomodoroTimer-5ZSIG7SQ.js} +5 -5
  16. package/dist/{PomodoroTimer-YABFEPEV.js.map → PomodoroTimer-5ZSIG7SQ.js.map} +1 -1
  17. package/dist/{Preview-SNPHS4PS.js → Preview-VBIWWJLK.js} +6 -6
  18. package/dist/{Preview-SNPHS4PS.js.map → Preview-VBIWWJLK.js.map} +1 -1
  19. package/dist/{Spreadsheet-JXQCWZES.js → Spreadsheet-GRD4P3OI.js} +6 -6
  20. package/dist/{Spreadsheet-JXQCWZES.js.map → Spreadsheet-GRD4P3OI.js.map} +1 -1
  21. package/dist/{Stock-IPLLRUOC.js → Stock-4BORXILI.js} +5 -5
  22. package/dist/{Stock-IPLLRUOC.js.map → Stock-4BORXILI.js.map} +1 -1
  23. package/dist/{Weather-SI5QN7X2.js → Weather-ZPPSEUIY.js} +5 -5
  24. package/dist/{Weather-SI5QN7X2.js.map → Weather-ZPPSEUIY.js.map} +1 -1
  25. package/dist/{WorldClock-WYDSMUWX.js → WorldClock-LBKYWDWE.js} +5 -5
  26. package/dist/{WorldClock-WYDSMUWX.js.map → WorldClock-LBKYWDWE.js.map} +1 -1
  27. package/dist/apps/index.js +16 -16
  28. package/dist/{chunk-XRBJARD7.js → chunk-COTCIYLS.js} +4 -4
  29. package/dist/{chunk-XRBJARD7.js.map → chunk-COTCIYLS.js.map} +1 -1
  30. package/dist/{chunk-XBV7NUZK.js → chunk-D4WFGAUW.js} +556 -131
  31. package/dist/chunk-D4WFGAUW.js.map +1 -0
  32. package/dist/{chunk-IGPSRRL7.js → chunk-GUWAKATU.js} +10 -7
  33. package/dist/chunk-GUWAKATU.js.map +1 -0
  34. package/dist/{chunk-HYAW6KIL.js → chunk-HNH7FOYB.js} +16 -4
  35. package/dist/chunk-HNH7FOYB.js.map +1 -0
  36. package/dist/{chunk-L2O6B5KT.js → chunk-OXLLRKBC.js} +24 -4
  37. package/dist/chunk-OXLLRKBC.js.map +1 -0
  38. package/dist/chunk-TQFUNASX.js +7 -0
  39. package/dist/{chunk-CM2NWGYZ.js.map → chunk-TQFUNASX.js.map} +1 -1
  40. package/dist/{chunk-Q3AOIJ7B.js → chunk-X6DOWIY2.js} +3 -3
  41. package/dist/{chunk-Q3AOIJ7B.js.map → chunk-X6DOWIY2.js.map} +1 -1
  42. package/dist/{index-Czn1re5M.d.ts → index-B4RRJ5bp.d.ts} +300 -64
  43. package/dist/index.d.ts +53 -29
  44. package/dist/index.js +54 -146
  45. package/dist/index.js.map +1 -1
  46. package/dist/ui/index.d.ts +1 -1
  47. package/dist/ui/index.js +4 -4
  48. package/package.json +1 -1
  49. package/dist/ConfirmDialog-WAM5OKAV.js +0 -3
  50. package/dist/chunk-CM2NWGYZ.js +0 -7
  51. package/dist/chunk-HYAW6KIL.js.map +0 -1
  52. package/dist/chunk-IGPSRRL7.js.map +0 -1
  53. package/dist/chunk-L2O6B5KT.js.map +0 -1
  54. package/dist/chunk-XBV7NUZK.js.map +0 -1
@@ -1,22 +1,39 @@
1
+ import { Breadcrumbs } from './chunk-HNH7FOYB.js';
1
2
  import { SidebarLayout } from './chunk-YG45DAUT.js';
2
3
  import { useIsMobile, useShellPrefs } from './chunk-C22EFSND.js';
3
- import { registerModalEscapeInterceptor, useFocusTrap, useScrollLock } from './chunk-L2O6B5KT.js';
4
+ import { registerModalEscapeInterceptor, useFocusTrap, useScrollLock } from './chunk-OXLLRKBC.js';
4
5
  import { glassStyle } from './chunk-3CUSBZWG.js';
5
- import { forwardRef, useId, useCallback, useState, useRef, useEffect, createContext, useLayoutEffect, useMemo, Children, useContext, Fragment as Fragment$1, useSyncExternalStore } from 'react';
6
+ import { forwardRef, useId, useCallback, useState, useRef, useEffect, createContext, useLayoutEffect, useMemo, Children, isValidElement, cloneElement, useContext, Fragment as Fragment$1, useSyncExternalStore } from 'react';
6
7
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
7
8
  import { createPortal } from 'react-dom';
8
9
 
9
- var BASE = "inline-flex items-center justify-center rounded-md font-medium transition-colors focus:outline-none focus:ring-2 disabled:cursor-not-allowed disabled:opacity-60";
10
- var VARIANTS = {
10
+ var BUTTON_BASE = "inline-flex items-center justify-center rounded-md font-medium transition-colors focus:outline-none focus:ring-2 disabled:cursor-not-allowed disabled:opacity-60";
11
+ var BUTTON_VARIANTS = {
11
12
  primary: "bg-blue-600 text-white shadow-sm hover:bg-blue-700 focus:ring-blue-400/40",
12
13
  secondary: "border border-gray-300 bg-white text-gray-700 shadow-sm hover:bg-gray-50 focus:ring-blue-400/30",
13
14
  ghost: "text-gray-700 hover:bg-gray-100 focus:ring-blue-400/30",
14
15
  "ghost-danger": "text-red-600 hover:bg-red-50 focus:ring-red-400/30",
15
- danger: "bg-red-600 text-white shadow-sm hover:bg-red-700 focus:ring-red-400/40"
16
+ danger: "bg-red-600 text-white shadow-sm hover:bg-red-700 focus:ring-red-400/40",
17
+ /**
18
+ * A button that reads as a link: no box, no padding, underlined on hover.
19
+ * For an action that belongs in running text or beside a field — "Forgot
20
+ * your password?", "Use a different address" — where a boxed button would
21
+ * claim more of the page than the action deserves.
22
+ *
23
+ * It is still a <button>. Anything that NAVIGATES should be an anchor, so it
24
+ * can be middle-clicked and read off the status bar; this is for actions
25
+ * that merely look quiet.
26
+ */
27
+ link: "text-blue-600 underline-offset-4 hover:underline focus:ring-blue-400/30"
16
28
  };
17
29
  var SIZES = {
18
30
  sm: "gap-1 px-2.5 py-1 text-xs",
19
31
  md: "gap-1.5 px-3 py-1.5 text-sm",
32
+ // `py-2.5`, not `py-2`: this rung has to come out 40px to match IconButton's
33
+ // `h-10`, which is the promise IconButton's docblock makes for every rung.
34
+ // `py-2` gives 36px (20px line box + 8px twice) and puts a 4px step between a
35
+ // Button and an IconButton sitting in the same row.
36
+ lg: "gap-2 px-4 py-2.5 text-sm",
20
37
  // 44px is the WCAG 2.5.5 floor, and a floor is not a comfortable size. Use it
21
38
  // ONLY for chrome that sits outside the task — switching screens, ending a
22
39
  // shift — and never on the path someone is actually working through. It
@@ -30,6 +47,16 @@ var SIZES = {
30
47
  "touch-xl": "gap-3 h-20 px-8 text-2xl"
31
48
  // 80px — keypad keys, tender
32
49
  };
50
+ var LINK_BASE = "inline-flex items-center font-medium transition-colors focus:outline-none focus:ring-2 disabled:cursor-not-allowed disabled:opacity-60";
51
+ var LINK_SIZES = {
52
+ sm: "gap-1 text-xs",
53
+ md: "gap-1.5 text-sm",
54
+ lg: "gap-2 text-sm",
55
+ "touch-sm": "gap-1.5 text-base",
56
+ "touch": "gap-2 text-base",
57
+ "touch-lg": "gap-2 text-lg",
58
+ "touch-xl": "gap-3 text-2xl"
59
+ };
33
60
  function Spinner() {
34
61
  return /* @__PURE__ */ jsxs("svg", { className: "h-3.5 w-3.5 animate-spin", viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: [
35
62
  /* @__PURE__ */ jsx("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }),
@@ -62,7 +89,18 @@ var Button = forwardRef(function Button2({
62
89
  disabled: isDisabled,
63
90
  "aria-busy": loading || void 0,
64
91
  "aria-describedby": reasonId,
65
- className: [BASE, VARIANTS[variant], SIZES[size], block ? "w-full" : "", className].filter(Boolean).join(" "),
92
+ className: [
93
+ // `link` takes the base WITHOUT the box: no rounding, no padding from
94
+ // the size table. It cannot cancel those by overriding — two padding
95
+ // utilities in one class attribute resolve by compiled-stylesheet
96
+ // order, not by the order they were written — so it never receives
97
+ // them, and keeps only the size's gap and text size.
98
+ variant === "link" ? LINK_BASE : BUTTON_BASE,
99
+ BUTTON_VARIANTS[variant],
100
+ variant === "link" ? LINK_SIZES[size] : SIZES[size],
101
+ block ? "w-full" : "",
102
+ className
103
+ ].filter(Boolean).join(" "),
66
104
  ...rest,
67
105
  children: [
68
106
  loading ? /* @__PURE__ */ jsx(Spinner, {}) : leftIcon,
@@ -78,12 +116,39 @@ var Button = forwardRef(function Button2({
78
116
  ] });
79
117
  });
80
118
  var Button_default = Button;
119
+ var SQUARE = {
120
+ sm: "h-6 w-6",
121
+ md: "h-8 w-8",
122
+ lg: "h-10 w-10",
123
+ "touch-sm": "h-11 w-11",
124
+ "touch": "h-14 w-14",
125
+ "touch-lg": "h-16 w-16",
126
+ "touch-xl": "h-20 w-20"
127
+ };
128
+ var IconButton = forwardRef(function IconButton2({ children, variant = "ghost", size = "md", type = "button", className = "", ...rest }, ref) {
129
+ return /* @__PURE__ */ jsx(
130
+ "button",
131
+ {
132
+ ref,
133
+ type,
134
+ className: [BUTTON_BASE, BUTTON_VARIANTS[variant], SQUARE[size], "shrink-0 p-0", className].filter(Boolean).join(" "),
135
+ ...rest,
136
+ children
137
+ }
138
+ );
139
+ });
140
+ var IconButton_default = IconButton;
81
141
 
82
142
  // src/forms/styles.ts
83
143
  var INPUT_SHAPE_PRE = "block w-full rounded-md border border-gray-300 bg-white";
84
144
  var INPUT_SHAPE_POST = "text-gray-800 placeholder:text-gray-400 shadow-sm focus:border-blue-400 focus:outline-none focus:ring-2 focus:ring-blue-400/30";
85
145
  var INPUT_SIZES = {
146
+ // The desktop rungs carry no explicit height: a text control's height should
147
+ // follow its own font, or a long label wraps out of a box that was pinned.
148
+ // The touch rung does carry one, for the same reason Button's does.
149
+ sm: "px-2.5 py-1 text-xs",
86
150
  md: "px-3 py-1.5 text-sm",
151
+ lg: "px-3.5 py-2 text-base",
87
152
  // 56px, matching Button's `touch`. `text-base` is 16px BY AGREEMENT with the
88
153
  // rule in ui.css that forces `font-size: 16px !important` on every form
89
154
  // control under `(pointer: coarse)` — the rule that stops iOS zooming the
@@ -108,6 +173,7 @@ var Input = forwardRef(function Input2({ invalid, leftIcon, rightAdornment, size
108
173
  "input",
109
174
  {
110
175
  ref,
176
+ "aria-invalid": invalid || void 0,
111
177
  className: inputClasses({ invalid, size, className: [pad2, className].filter(Boolean).join(" ") }),
112
178
  ...rest
113
179
  }
@@ -121,7 +187,7 @@ var Input = forwardRef(function Input2({ invalid, leftIcon, rightAdornment, size
121
187
  ] });
122
188
  });
123
189
  var Input_default = Input;
124
- var Textarea = forwardRef(function Textarea2({ invalid, autoGrow, className = "", onInput, rows = 3, ...rest }, ref) {
190
+ var Textarea = forwardRef(function Textarea2({ invalid, autoGrow, className = "", onInput, rows = 3, size, ...rest }, ref) {
125
191
  const handleInput = useCallback((e) => {
126
192
  if (autoGrow) {
127
193
  const el = e.currentTarget;
@@ -134,9 +200,10 @@ var Textarea = forwardRef(function Textarea2({ invalid, autoGrow, className = ""
134
200
  "textarea",
135
201
  {
136
202
  ref,
203
+ "aria-invalid": invalid || void 0,
137
204
  rows,
138
205
  onInput: handleInput,
139
- className: inputClasses({ invalid, className: `${autoGrow ? "resize-none overflow-hidden" : ""} ${className}`.trim() }),
206
+ className: inputClasses({ invalid, size, className: `${autoGrow ? "resize-none overflow-hidden" : ""} ${className}`.trim() }),
140
207
  ...rest
141
208
  }
142
209
  );
@@ -179,14 +246,15 @@ function matchTypeahead(options, buffer, fromIndex) {
179
246
  var MENU_GAP = 4;
180
247
  var VIEWPORT_MARGIN = 8;
181
248
  var MENU_MAX_HEIGHT = 240;
182
- var NativeSelect = forwardRef(function NativeSelect2({ value, onChange, options, placeholder, invalid, className = "", ...rest }, ref) {
249
+ var NativeSelect = forwardRef(function NativeSelect2({ value, onChange, options, placeholder, invalid, className = "", size, ...rest }, ref) {
183
250
  return /* @__PURE__ */ jsxs(
184
251
  "select",
185
252
  {
186
253
  ref,
187
254
  value,
188
255
  onChange: (e) => onChange(e.target.value),
189
- className: inputClasses({ invalid, className: `pr-8 ${className}`.trim() }),
256
+ "aria-invalid": invalid || void 0,
257
+ className: inputClasses({ invalid, size, className: `pr-8 ${className}`.trim() }),
190
258
  ...rest,
191
259
  children: [
192
260
  placeholder !== void 0 && /* @__PURE__ */ jsx("option", { value: "", disabled: true, children: placeholder }),
@@ -235,7 +303,21 @@ function useAnchoredPosition(triggerRef, open) {
235
303
  }, [open, triggerRef]);
236
304
  return pos;
237
305
  }
238
- var ListboxSelect = forwardRef(function ListboxSelect2({ value, onChange, options, placeholder, invalid, className = "", id, disabled, ...rest }, ref) {
306
+ var ListboxSelect = forwardRef(function ListboxSelect2({
307
+ value,
308
+ onChange,
309
+ options,
310
+ placeholder,
311
+ invalid,
312
+ className = "",
313
+ id,
314
+ disabled,
315
+ size,
316
+ "aria-describedby": describedBy,
317
+ "aria-label": ariaLabel,
318
+ "aria-labelledby": labelledBy,
319
+ ...rest
320
+ }, ref) {
239
321
  const [open, setOpen] = useState(false);
240
322
  const [active, setActive] = useState(-1);
241
323
  const wrapRef = useRef(null);
@@ -369,10 +451,13 @@ var ListboxSelect = forwardRef(function ListboxSelect2({ value, onChange, option
369
451
  "aria-controls": open ? listboxId : void 0,
370
452
  "aria-activedescendant": open && active >= 0 ? optionId(active) : void 0,
371
453
  "aria-invalid": invalid || void 0,
454
+ "aria-describedby": describedBy,
455
+ "aria-label": ariaLabel,
456
+ "aria-labelledby": labelledBy,
372
457
  disabled,
373
458
  onClick: () => open ? close() : openList(),
374
459
  onKeyDown,
375
- className: inputClasses({ invalid, className: `pr-8 text-left ${className}`.trim() }),
460
+ className: inputClasses({ invalid, size, className: `pr-8 text-left ${className}`.trim() }),
376
461
  children: /* @__PURE__ */ jsx("span", { className: `block truncate ${selectedLabel === void 0 ? "text-gray-400" : ""}`, children: selectedLabel ?? placeholder ?? "\xA0" })
377
462
  }
378
463
  ),
@@ -451,34 +536,33 @@ var Select = forwardRef(function Select2(props, ref) {
451
536
  });
452
537
  var Select_default = Select;
453
538
  var BOX = "h-4 w-4 shrink-0 rounded border-gray-300 accent-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-400/30 disabled:cursor-not-allowed disabled:opacity-60";
454
- var Checkbox = forwardRef(function Checkbox2({ checked, onChange, label, description, disabled, className = "", ...rest }, ref) {
539
+ var Checkbox = forwardRef(function Checkbox2({ checked, onChange, indeterminate = false, label, description, disabled, className = "", ...rest }, ref) {
540
+ const inner = useRef(null);
541
+ useEffect(() => {
542
+ if (inner.current) inner.current.indeterminate = indeterminate;
543
+ }, [indeterminate, checked]);
544
+ const attachRef = useCallback((node) => {
545
+ inner.current = node;
546
+ if (typeof ref === "function") ref(node);
547
+ else if (ref) ref.current = node;
548
+ }, [ref]);
549
+ const input = (extra = "") => /* @__PURE__ */ jsx(
550
+ "input",
551
+ {
552
+ ref: attachRef,
553
+ type: "checkbox",
554
+ checked,
555
+ disabled,
556
+ onChange: (e) => onChange(e.target.checked),
557
+ className: `${BOX} ${extra}`.trim(),
558
+ ...rest
559
+ }
560
+ );
455
561
  if (!label && !description) {
456
- return /* @__PURE__ */ jsx(
457
- "input",
458
- {
459
- ref,
460
- type: "checkbox",
461
- checked,
462
- disabled,
463
- onChange: (e) => onChange(e.target.checked),
464
- className: `${BOX} ${className}`.trim(),
465
- ...rest
466
- }
467
- );
562
+ return input(className);
468
563
  }
469
564
  return /* @__PURE__ */ jsxs("label", { className: `flex items-start gap-2 ${disabled ? "cursor-not-allowed opacity-60" : "cursor-pointer"} ${className}`.trim(), children: [
470
- /* @__PURE__ */ jsx(
471
- "input",
472
- {
473
- ref,
474
- type: "checkbox",
475
- checked,
476
- disabled,
477
- onChange: (e) => onChange(e.target.checked),
478
- className: `${BOX} mt-0.5`,
479
- ...rest
480
- }
481
- ),
565
+ input("mt-0.5"),
482
566
  /* @__PURE__ */ jsxs("span", { className: "text-sm leading-tight", children: [
483
567
  label && /* @__PURE__ */ jsx("span", { className: "font-medium text-gray-700", children: label }),
484
568
  description && /* @__PURE__ */ jsx("span", { className: "mt-0.5 block text-xs text-gray-500", children: description })
@@ -1372,6 +1456,37 @@ function DateRangePicker({
1372
1456
  )
1373
1457
  ] });
1374
1458
  }
1459
+ var ISO_DATE = /^\d{4}-\d{2}-\d{2}$/;
1460
+ function toInputValue(value) {
1461
+ if (value === null || value === void 0 || value === "") return "";
1462
+ if (value instanceof Date) return Number.isNaN(value.getTime()) ? "" : toISODate(value);
1463
+ if (ISO_DATE.test(value)) return value;
1464
+ const parsed = new Date(value);
1465
+ return Number.isNaN(parsed.getTime()) ? "" : toISODate(parsed);
1466
+ }
1467
+ function fromInputValue(raw) {
1468
+ if (!ISO_DATE.test(raw)) return null;
1469
+ const [year, month, day] = raw.split("-").map(Number);
1470
+ const date = new Date(year, month - 1, day);
1471
+ return date.getMonth() === month - 1 && date.getDate() === day ? date : null;
1472
+ }
1473
+ var DatePicker = forwardRef(function DatePicker2({ value, onChange, min, max, invalid, size, className = "", ...rest }, ref) {
1474
+ return /* @__PURE__ */ jsx(
1475
+ "input",
1476
+ {
1477
+ ref,
1478
+ type: "date",
1479
+ "aria-invalid": invalid || void 0,
1480
+ value: toInputValue(value),
1481
+ min: toInputValue(min) || void 0,
1482
+ max: toInputValue(max) || void 0,
1483
+ onChange: (event) => onChange?.(fromInputValue(event.target.value)),
1484
+ className: inputClasses({ invalid, size, className }),
1485
+ ...rest
1486
+ }
1487
+ );
1488
+ });
1489
+ var DatePicker_default = DatePicker;
1375
1490
 
1376
1491
  // src/forms/keypadInput.ts
1377
1492
  var MAX_FRACTION_DIGITS = 2;
@@ -1559,7 +1674,7 @@ var TRAVEL = {
1559
1674
  md: "translate-x-5",
1560
1675
  touch: "translate-x-6"
1561
1676
  };
1562
- function Switch({
1677
+ var Switch = forwardRef(function Switch2({
1563
1678
  checked,
1564
1679
  onChange,
1565
1680
  disabled = false,
@@ -1567,14 +1682,16 @@ function Switch({
1567
1682
  label,
1568
1683
  hint,
1569
1684
  id,
1570
- className = ""
1571
- }) {
1685
+ className = "",
1686
+ ...rest
1687
+ }, ref) {
1572
1688
  const generated = useId();
1573
1689
  const switchId = id ?? generated;
1574
1690
  const hintId = hint ? `${switchId}-hint` : void 0;
1575
1691
  const control = /* @__PURE__ */ jsx(
1576
1692
  "button",
1577
1693
  {
1694
+ ref,
1578
1695
  id: switchId,
1579
1696
  type: "button",
1580
1697
  role: "switch",
@@ -1582,6 +1699,7 @@ function Switch({
1582
1699
  "aria-describedby": hintId,
1583
1700
  disabled,
1584
1701
  onClick: () => onChange(!checked),
1702
+ ...rest,
1585
1703
  className: [
1586
1704
  "relative inline-flex shrink-0 items-center rounded-full p-0.5 transition-colors",
1587
1705
  "focus:outline-none focus:ring-2 focus:ring-blue-400/40",
@@ -1609,7 +1727,8 @@ function Switch({
1609
1727
  hint && /* @__PURE__ */ jsx("span", { id: hintId, className: "mt-0.5 block text-xs text-gray-500", children: hint })
1610
1728
  ] })
1611
1729
  ] });
1612
- }
1730
+ });
1731
+ var Switch_default = Switch;
1613
1732
  var format = (v, precision) => v == null ? "" : precision != null ? v.toFixed(precision) : String(v);
1614
1733
  function parse(text) {
1615
1734
  const t = text.trim();
@@ -1646,6 +1765,23 @@ var InputNumber = forwardRef(function InputNumber2({
1646
1765
  setText(next);
1647
1766
  onChange(parse(next));
1648
1767
  };
1768
+ const bump = (by) => {
1769
+ const from = parse(textRef.current) ?? 0;
1770
+ let next = from + by;
1771
+ if (min != null && next < min) next = min;
1772
+ if (max != null && next > max) next = max;
1773
+ const places = precision ?? String(step ?? 1).split(".")[1]?.length ?? 0;
1774
+ next = Number(next.toFixed(places));
1775
+ setText(format(next, precision));
1776
+ onChange(next);
1777
+ };
1778
+ const handleKeyDown = (event) => {
1779
+ const unit = step ?? 1;
1780
+ const by = event.key === "ArrowUp" ? unit : event.key === "ArrowDown" ? -unit : event.key === "PageUp" ? unit * 10 : event.key === "PageDown" ? -unit * 10 : null;
1781
+ if (by === null || disabled) return;
1782
+ event.preventDefault();
1783
+ bump(by);
1784
+ };
1649
1785
  const handleBlur = () => {
1650
1786
  const parsed = parse(text);
1651
1787
  if (parsed == null) {
@@ -1655,6 +1791,7 @@ var InputNumber = forwardRef(function InputNumber2({
1655
1791
  let v = parsed;
1656
1792
  if (min != null && v < min) v = min;
1657
1793
  if (max != null && v > max) v = max;
1794
+ if (precision != null) v = Number(v.toFixed(precision));
1658
1795
  setText(format(v, precision));
1659
1796
  if (v !== parsed) onChange(v);
1660
1797
  }
@@ -1672,8 +1809,14 @@ var InputNumber = forwardRef(function InputNumber2({
1672
1809
  disabled,
1673
1810
  placeholder,
1674
1811
  step,
1812
+ role: "spinbutton",
1813
+ "aria-valuenow": value ?? void 0,
1814
+ "aria-valuemin": min,
1815
+ "aria-valuemax": max,
1816
+ "aria-valuetext": value == null ? void 0 : format(value, precision),
1675
1817
  "aria-invalid": invalid || void 0,
1676
1818
  onChange: (e) => handleChange(e.target.value),
1819
+ onKeyDown: handleKeyDown,
1677
1820
  onBlur: handleBlur,
1678
1821
  className: inputClasses({
1679
1822
  invalid,
@@ -2202,6 +2345,7 @@ function Divider({
2202
2345
  spacing = "md",
2203
2346
  className = ""
2204
2347
  }) {
2348
+ const labelId = useId();
2205
2349
  if (orientation === "vertical") {
2206
2350
  return /* @__PURE__ */ jsx(
2207
2351
  "span",
@@ -2216,11 +2360,31 @@ function Divider({
2216
2360
  }
2217
2361
  const before = align === "left" ? "w-4 shrink-0" : "flex-1";
2218
2362
  const after = align === "right" ? "w-4 shrink-0" : "flex-1";
2219
- return /* @__PURE__ */ jsxs("div", { className: `flex items-center gap-3 ${SPACING[spacing]} ${className}`.trim(), children: [
2220
- /* @__PURE__ */ jsx("span", { className: `${before} border-t border-gray-200` }),
2221
- /* @__PURE__ */ jsx("span", { className: "shrink-0 text-xs font-medium text-gray-500", children }),
2222
- /* @__PURE__ */ jsx("span", { className: `${after} border-t border-gray-200` })
2223
- ] });
2363
+ return (
2364
+ // `role="separator"` because the <hr> above cannot be used once there is a
2365
+ // label to place inside the rule and losing the element must not mean
2366
+ // losing what it meant. The two rules either side are decoration.
2367
+ //
2368
+ // The name is pointed at explicitly. `separator` takes its name from the
2369
+ // AUTHOR only — it is not one of the roles that names itself from its
2370
+ // contents — so the label sitting inside the element did NOT become the
2371
+ // separator's accessible name, and the rule was announced unnamed with the
2372
+ // text loose beside it. `aria-labelledby` is what actually attaches it.
2373
+ /* @__PURE__ */ jsxs(
2374
+ "div",
2375
+ {
2376
+ role: "separator",
2377
+ "aria-orientation": "horizontal",
2378
+ "aria-labelledby": labelId,
2379
+ className: `flex items-center gap-3 ${SPACING[spacing]} ${className}`.trim(),
2380
+ children: [
2381
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: `${before} border-t border-gray-200` }),
2382
+ /* @__PURE__ */ jsx("span", { id: labelId, className: "shrink-0 text-xs font-medium text-gray-500", children }),
2383
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: `${after} border-t border-gray-200` })
2384
+ ]
2385
+ }
2386
+ )
2387
+ );
2224
2388
  }
2225
2389
  var SHAPE = {
2226
2390
  text: "rounded",
@@ -2260,7 +2424,8 @@ function DescriptionList({
2260
2424
  bordered = false,
2261
2425
  size = "md",
2262
2426
  title,
2263
- className = ""
2427
+ className = "",
2428
+ emptyText: empty = "\u2014"
2264
2429
  }) {
2265
2430
  const resolved = typeof columns === "number" ? { base: columns } : columns;
2266
2431
  const base = resolved.base ?? 1;
@@ -2285,7 +2450,7 @@ function DescriptionList({
2285
2450
  ].filter(Boolean).join(" "),
2286
2451
  children: [
2287
2452
  /* @__PURE__ */ jsx("dt", { className: `${text} text-gray-500`, children: item.label }),
2288
- /* @__PURE__ */ jsx("dd", { className: `mt-0.5 min-w-0 break-words ${text} text-gray-900`, children: item.value })
2453
+ /* @__PURE__ */ jsx("dd", { className: `mt-0.5 min-w-0 break-words ${text} text-gray-900`, children: item.value === null || item.value === void 0 || item.value === "" ? empty : item.value })
2289
2454
  ]
2290
2455
  },
2291
2456
  item.key ?? i
@@ -2337,13 +2502,21 @@ function Result({
2337
2502
  className = ""
2338
2503
  }) {
2339
2504
  const tone = STATUS_TONE[status];
2340
- return /* @__PURE__ */ jsxs("div", { className: `flex flex-col items-center justify-center px-6 py-12 text-center ${className}`.trim(), children: [
2341
- /* @__PURE__ */ jsx("div", { className: `flex h-14 w-14 items-center justify-center rounded-full ${TONE_CLASS[tone]}`, children: icon ?? /* @__PURE__ */ jsx(StatusIcon, { tone }) }),
2342
- /* @__PURE__ */ jsx("h2", { className: "mt-4 text-lg font-semibold text-gray-900", children: title ?? DEFAULT_TITLE[status] ?? "" }),
2343
- subTitle && /* @__PURE__ */ jsx("p", { className: "mt-1 max-w-md text-sm text-gray-500", children: subTitle }),
2344
- children && /* @__PURE__ */ jsx("div", { className: "mt-4 w-full max-w-md text-left", children }),
2345
- extra && /* @__PURE__ */ jsx("div", { className: "mt-6 flex flex-wrap items-center justify-center gap-3", children: extra })
2346
- ] });
2505
+ const announces = tone === "error";
2506
+ return /* @__PURE__ */ jsxs(
2507
+ "div",
2508
+ {
2509
+ role: announces ? "alert" : void 0,
2510
+ className: `flex flex-col items-center justify-center px-6 py-12 text-center ${className}`.trim(),
2511
+ children: [
2512
+ /* @__PURE__ */ jsx("div", { className: `flex h-14 w-14 items-center justify-center rounded-full ${TONE_CLASS[tone]}`, children: icon ?? /* @__PURE__ */ jsx(StatusIcon, { tone }) }),
2513
+ /* @__PURE__ */ jsx("h2", { className: "mt-4 text-lg font-semibold text-gray-900", children: title ?? DEFAULT_TITLE[status] ?? "" }),
2514
+ subTitle && /* @__PURE__ */ jsx("p", { className: "mt-1 max-w-md text-sm text-gray-500", children: subTitle }),
2515
+ children && /* @__PURE__ */ jsx("div", { className: "mt-4 w-full max-w-md text-left", children }),
2516
+ extra && /* @__PURE__ */ jsx("div", { className: "mt-6 flex flex-wrap items-center justify-center gap-3", children: extra })
2517
+ ]
2518
+ }
2519
+ );
2347
2520
  }
2348
2521
  var TONE2 = {
2349
2522
  danger: "bg-red-600 text-white",
@@ -2429,23 +2602,49 @@ function initials(name) {
2429
2602
  function Avatar({ src, name, size = "md", status, className = "" }) {
2430
2603
  const px = SIZE_PX[size];
2431
2604
  const dot = Math.max(8, Math.round(px * 0.28));
2432
- return /* @__PURE__ */ jsxs("span", { className: `relative inline-flex shrink-0 ${className}`.trim(), style: { width: px, height: px }, children: [
2433
- src ? /* @__PURE__ */ jsx("img", { src, alt: name ?? "", className: "h-full w-full rounded-full object-cover" }) : /* @__PURE__ */ jsx(
2434
- "span",
2435
- {
2436
- className: "flex h-full w-full items-center justify-center rounded-full bg-gray-100 font-medium text-gray-600",
2437
- style: { fontSize: Math.round(px * 0.4) },
2438
- children: initials(name)
2439
- }
2440
- ),
2441
- status && /* @__PURE__ */ jsx(
2605
+ const [broken, setBroken] = useState(false);
2606
+ useEffect(() => {
2607
+ setBroken(false);
2608
+ }, [src]);
2609
+ const showImage = Boolean(src) && !broken;
2610
+ return (
2611
+ // The role and the name live on the wrapper rather than on the <img>, so
2612
+ // that an avatar showing initials is announced the same way as one showing
2613
+ // a photo. The inner image is then decoration and takes an empty alt.
2614
+ /* @__PURE__ */ jsxs(
2442
2615
  "span",
2443
2616
  {
2444
- className: `absolute bottom-0 right-0 rounded-full ring-2 ring-white ${STATUS_COLOR[status]}`,
2445
- style: { width: dot, height: dot }
2617
+ className: `relative inline-flex shrink-0 ${className}`.trim(),
2618
+ style: { width: px, height: px },
2619
+ ...name ? { role: "img", "aria-label": name } : { "aria-hidden": true },
2620
+ children: [
2621
+ showImage ? /* @__PURE__ */ jsx(
2622
+ "img",
2623
+ {
2624
+ src,
2625
+ alt: "",
2626
+ onError: () => setBroken(true),
2627
+ className: "h-full w-full rounded-full object-cover"
2628
+ }
2629
+ ) : /* @__PURE__ */ jsx(
2630
+ "span",
2631
+ {
2632
+ className: "flex h-full w-full items-center justify-center rounded-full bg-gray-100 font-medium text-gray-600",
2633
+ style: { fontSize: Math.round(px * 0.4) },
2634
+ children: initials(name)
2635
+ }
2636
+ ),
2637
+ status && /* @__PURE__ */ jsx(
2638
+ "span",
2639
+ {
2640
+ className: `absolute bottom-0 right-0 rounded-full ring-2 ring-white ${STATUS_COLOR[status]}`,
2641
+ style: { width: dot, height: dot }
2642
+ }
2643
+ )
2644
+ ]
2446
2645
  }
2447
2646
  )
2448
- ] });
2647
+ );
2449
2648
  }
2450
2649
  function AvatarGroup({ children, max, size = "md", className = "" }) {
2451
2650
  const items = Children.toArray(children);
@@ -2532,49 +2731,89 @@ function Banner({
2532
2731
  }
2533
2732
  );
2534
2733
  }
2535
- function Tabs({ items, value, onChange, variant = "underline", className = "" }) {
2734
+ var tabButtonId = (prefix, id) => `${prefix}-tab-${id}`;
2735
+ var tabPanelId = (prefix, id) => `${prefix}-panel-${id}`;
2736
+ var LIST_CLASS = {
2737
+ underline: "flex items-center gap-1 border-b border-gray-200",
2738
+ pill: "inline-flex items-center gap-1 rounded-lg bg-gray-100 p-1"
2739
+ };
2740
+ function tabClass(variant, active) {
2536
2741
  if (variant === "pill") {
2537
- return /* @__PURE__ */ jsx("div", { role: "tablist", className: `inline-flex items-center gap-1 rounded-lg bg-gray-100 p-1 ${className}`.trim(), children: items.map((t) => {
2538
- const active = t.id === value;
2539
- return /* @__PURE__ */ jsxs(
2540
- "button",
2541
- {
2542
- type: "button",
2543
- role: "tab",
2544
- "aria-selected": active,
2545
- tabIndex: active ? 0 : -1,
2546
- disabled: t.disabled,
2547
- onClick: () => onChange(t.id),
2548
- className: `inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-sm font-medium transition-colors disabled:cursor-not-allowed disabled:opacity-50 ${active ? "bg-blue-600 text-white shadow-sm" : "text-gray-600 hover:text-gray-900"}`,
2549
- children: [
2550
- t.icon,
2551
- t.label
2552
- ]
2553
- },
2554
- t.id
2555
- );
2556
- }) });
2742
+ return `inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-sm font-medium transition-colors disabled:cursor-not-allowed disabled:opacity-50 ${active ? "bg-blue-600 text-white shadow-sm" : "text-gray-600 hover:text-gray-900"}`;
2557
2743
  }
2558
- return /* @__PURE__ */ jsx("div", { role: "tablist", className: `flex items-center gap-1 border-b border-gray-200 ${className}`.trim(), children: items.map((t) => {
2559
- const active = t.id === value;
2560
- return /* @__PURE__ */ jsxs(
2561
- "button",
2562
- {
2563
- type: "button",
2564
- role: "tab",
2565
- "aria-selected": active,
2566
- tabIndex: active ? 0 : -1,
2567
- disabled: t.disabled,
2568
- onClick: () => onChange(t.id),
2569
- className: `-mb-px inline-flex items-center gap-1.5 border-b-2 px-3 py-2 text-sm font-medium transition-colors disabled:cursor-not-allowed disabled:opacity-50 ${active ? "border-blue-600 text-blue-600" : "border-transparent text-gray-600 hover:border-gray-300 hover:text-gray-900"}`,
2570
- children: [
2571
- t.icon,
2572
- t.label
2573
- ]
2574
- },
2575
- t.id
2576
- );
2577
- }) });
2744
+ return `-mb-px inline-flex items-center gap-1.5 border-b-2 px-3 py-2 text-sm font-medium transition-colors disabled:cursor-not-allowed disabled:opacity-50 ${active ? "border-blue-600 text-blue-600" : "border-transparent text-gray-600 hover:border-gray-300 hover:text-gray-900"}`;
2745
+ }
2746
+ function Tabs({
2747
+ items,
2748
+ value,
2749
+ onChange,
2750
+ variant = "underline",
2751
+ idPrefix,
2752
+ className = "",
2753
+ "aria-label": ariaLabel,
2754
+ "aria-labelledby": ariaLabelledBy
2755
+ }) {
2756
+ const buttons = useRef([]);
2757
+ const step = (from, direction) => {
2758
+ for (let i = 1; i <= items.length; i += 1) {
2759
+ const at = ((from + direction * i) % items.length + items.length) % items.length;
2760
+ if (!items[at]?.disabled) return at;
2761
+ }
2762
+ return null;
2763
+ };
2764
+ const edge = (direction) => {
2765
+ const order = direction === 1 ? items.map((_, i) => i) : items.map((_, i) => items.length - 1 - i);
2766
+ for (const i of order) if (!items[i]?.disabled) return i;
2767
+ return null;
2768
+ };
2769
+ const onKeyDown = (event) => {
2770
+ const current = items.findIndex((t) => t.id === value);
2771
+ let next = null;
2772
+ if (event.key === "ArrowRight" || event.key === "ArrowDown") next = step(current, 1);
2773
+ else if (event.key === "ArrowLeft" || event.key === "ArrowUp") next = step(current, -1);
2774
+ else if (event.key === "Home") next = edge(1);
2775
+ else if (event.key === "End") next = edge(-1);
2776
+ else return;
2777
+ event.preventDefault();
2778
+ if (next === null || next === current) return;
2779
+ onChange(items[next].id);
2780
+ buttons.current[next]?.focus();
2781
+ };
2782
+ return /* @__PURE__ */ jsx(
2783
+ "div",
2784
+ {
2785
+ role: "tablist",
2786
+ "aria-label": ariaLabel,
2787
+ "aria-labelledby": ariaLabelledBy,
2788
+ onKeyDown,
2789
+ className: `${LIST_CLASS[variant]} ${className}`.trim(),
2790
+ children: items.map((t, i) => {
2791
+ const active = t.id === value;
2792
+ return /* @__PURE__ */ jsxs(
2793
+ "button",
2794
+ {
2795
+ ref: (el) => {
2796
+ buttons.current[i] = el;
2797
+ },
2798
+ type: "button",
2799
+ role: "tab",
2800
+ id: idPrefix ? tabButtonId(idPrefix, t.id) : void 0,
2801
+ "aria-controls": idPrefix ? tabPanelId(idPrefix, t.id) : void 0,
2802
+ "aria-selected": active,
2803
+ tabIndex: active ? 0 : -1,
2804
+ disabled: t.disabled,
2805
+ onClick: () => onChange(t.id),
2806
+ className: tabClass(variant, active),
2807
+ children: [
2808
+ t.icon && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "inline-flex shrink-0", children: t.icon }),
2809
+ t.label
2810
+ ]
2811
+ },
2812
+ t.id
2813
+ );
2814
+ })
2815
+ }
2816
+ );
2578
2817
  }
2579
2818
  function Accordion({
2580
2819
  items,
@@ -2642,17 +2881,29 @@ function Tooltip({ content, side = "top", delay = 200, children }) {
2642
2881
  setShow(false);
2643
2882
  };
2644
2883
  useEffect(() => () => window.clearTimeout(timer.current), []);
2884
+ useEffect(() => {
2885
+ if (!show) return;
2886
+ return registerModalEscapeInterceptor((event) => {
2887
+ if (event.key !== "Escape") return false;
2888
+ close();
2889
+ return true;
2890
+ });
2891
+ }, [show]);
2892
+ const describedBy = show ? id : void 0;
2893
+ const trigger = isValidElement(children) ? cloneElement(children, {
2894
+ "aria-describedby": [children.props["aria-describedby"], describedBy].filter(Boolean).join(" ") || void 0
2895
+ }) : children;
2645
2896
  return /* @__PURE__ */ jsxs(
2646
2897
  "span",
2647
2898
  {
2648
2899
  className: "relative inline-flex",
2649
- "aria-describedby": show ? id : void 0,
2900
+ "aria-describedby": isValidElement(children) ? void 0 : describedBy,
2650
2901
  onMouseEnter: open,
2651
2902
  onMouseLeave: close,
2652
2903
  onFocus: open,
2653
2904
  onBlur: close,
2654
2905
  children: [
2655
- children,
2906
+ trigger,
2656
2907
  show && /* @__PURE__ */ jsx(
2657
2908
  "span",
2658
2909
  {
@@ -2667,11 +2918,6 @@ function Tooltip({ content, side = "top", delay = 200, children }) {
2667
2918
  }
2668
2919
  );
2669
2920
  }
2670
- function ColoredBadge({ colorClass, children, size = "sm", capitalize = false }) {
2671
- const sizeCls = size === "xs" ? "px-2 py-0.5 text-[10px]" : size === "md" ? "px-2.5 py-0.5 text-xs" : "px-2 py-0.5 text-xs";
2672
- const capCls = capitalize ? "capitalize" : "";
2673
- return /* @__PURE__ */ jsx("span", { className: `inline-flex items-center rounded-full font-medium ${sizeCls} ${capCls} ${colorClass}`, children });
2674
- }
2675
2921
  var GROUP_COLORS = {
2676
2922
  success: "bg-green-100 text-green-800",
2677
2923
  active: "bg-blue-100 text-blue-800",
@@ -2697,6 +2943,36 @@ function StatusBadge({ status }) {
2697
2943
  const label = status.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
2698
2944
  return /* @__PURE__ */ jsx("span", { className: `inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium ${color}`, children: label });
2699
2945
  }
2946
+ function ColoredBadge({
2947
+ colorClass,
2948
+ tone,
2949
+ children,
2950
+ size = "sm",
2951
+ capitalize = false,
2952
+ closable = false,
2953
+ onClose,
2954
+ closeLabel,
2955
+ className = ""
2956
+ }) {
2957
+ const colour = colorClass ?? GROUP_COLORS[tone ?? "neutral"];
2958
+ const sizeCls = size === "xs" ? "px-2 py-0.5 text-[10px]" : size === "md" ? "px-2.5 py-0.5 text-xs" : "px-2 py-0.5 text-xs";
2959
+ const capCls = capitalize ? "capitalize" : "";
2960
+ const closeName = closeLabel ?? (typeof children === "string" ? `Remove ${children}` : "Remove");
2961
+ const closeSize = size === "xs" ? "h-2.5 w-2.5" : "h-3 w-3";
2962
+ return /* @__PURE__ */ jsxs("span", { className: ["inline-flex items-center rounded-full font-medium", sizeCls, capCls, colour, className].filter(Boolean).join(" "), children: [
2963
+ children,
2964
+ closable && /* @__PURE__ */ jsx(
2965
+ "button",
2966
+ {
2967
+ type: "button",
2968
+ onClick: onClose,
2969
+ "aria-label": closeName,
2970
+ className: "-mr-0.5 ml-1 inline-flex shrink-0 items-center justify-center rounded-full opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-blue-400/40",
2971
+ children: /* @__PURE__ */ jsx("svg", { className: closeSize, viewBox: "0 0 20 20", fill: "currentColor", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z" }) })
2972
+ }
2973
+ )
2974
+ ] });
2975
+ }
2700
2976
  function EmptyState({
2701
2977
  title,
2702
2978
  message,
@@ -2708,27 +2984,43 @@ function EmptyState({
2708
2984
  children
2709
2985
  }) {
2710
2986
  const v = frameless ? "none" : variant ?? "dashed";
2711
- const showIcon = icon ?? v !== "card";
2987
+ const custom = typeof icon !== "boolean" && icon != null ? icon : null;
2988
+ const showIcon = custom ? false : icon ?? v !== "card";
2712
2989
  const primary = message ?? (title ? void 0 : "Nothing here yet.");
2713
2990
  const secondary = description ?? hint;
2714
2991
  const frame = v === "card" ? "rounded-md border border-gray-200 bg-white px-6 py-12" : v === "none" ? "px-6 py-12" : "rounded-lg border-2 border-dashed border-gray-300 px-6 py-12";
2715
2992
  return /* @__PURE__ */ jsxs("div", { className: `flex flex-col items-center justify-center text-center ${frame}`, children: [
2716
- showIcon && /* @__PURE__ */ jsx("svg", { className: "h-10 w-10 text-gray-300 mb-3", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4" }) }),
2993
+ custom && /* @__PURE__ */ jsx("div", { className: "mb-3 flex h-10 w-10 items-center justify-center text-gray-300", "aria-hidden": "true", children: custom }),
2994
+ showIcon && /* @__PURE__ */ jsx("svg", { className: "h-10 w-10 text-gray-300 mb-3", "aria-hidden": "true", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4" }) }),
2717
2995
  title && /* @__PURE__ */ jsx("h3", { className: "text-sm font-medium text-gray-900", children: title }),
2718
2996
  primary && /* @__PURE__ */ jsx("p", { className: `text-sm text-gray-500 ${title ? "mt-2" : ""}`, children: primary }),
2719
2997
  secondary && /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-400 mt-1", children: secondary }),
2720
2998
  children && /* @__PURE__ */ jsx("div", { className: "mt-4", children })
2721
2999
  ] });
2722
3000
  }
2723
- function PageHeader({ title, description, subtitle, actions, children }) {
3001
+ function PageHeader({
3002
+ title,
3003
+ description,
3004
+ subtitle,
3005
+ actions,
3006
+ children,
3007
+ icon,
3008
+ breadcrumbs
3009
+ }) {
2724
3010
  const desc = description ?? subtitle;
2725
3011
  const right = actions ?? children;
2726
- return /* @__PURE__ */ jsxs("div", { className: "mb-6 flex items-start justify-between gap-4", children: [
2727
- /* @__PURE__ */ jsxs("div", { children: [
2728
- /* @__PURE__ */ jsx("h1", { className: "text-xl font-semibold text-gray-900", children: title }),
2729
- desc && /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-gray-500", children: desc })
2730
- ] }),
2731
- right && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: right })
3012
+ return /* @__PURE__ */ jsxs("div", { className: "mb-6", children: [
3013
+ breadcrumbs && breadcrumbs.length > 0 && /* @__PURE__ */ jsx(Breadcrumbs, { items: breadcrumbs, className: "mb-3" }),
3014
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-4", children: [
3015
+ /* @__PURE__ */ jsxs("div", { children: [
3016
+ /* @__PURE__ */ jsxs("h1", { className: "flex items-center gap-2 text-xl font-semibold text-gray-900", children: [
3017
+ icon && /* @__PURE__ */ jsx("span", { className: "shrink-0 text-gray-400", children: icon }),
3018
+ title
3019
+ ] }),
3020
+ desc && /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-gray-500", children: desc })
3021
+ ] }),
3022
+ right && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: right })
3023
+ ] })
2732
3024
  ] });
2733
3025
  }
2734
3026
  function LoadingSpinner({ size = "md", padding = "py-12", label, className = "" }) {
@@ -3627,7 +3919,7 @@ function HelpCenter({
3627
3919
  ] }) : /* @__PURE__ */ jsx("div", { className: "flex-1 flex items-center justify-center px-6 text-center", children: /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-500", children: loading ? "Loading\u2026" : "Pick a help article from the left." }) }) })
3628
3920
  ] });
3629
3921
  }
3630
- var VARIANTS2 = {
3922
+ var VARIANTS = {
3631
3923
  primary: "bg-blue-600 text-white hover:bg-blue-700",
3632
3924
  secondary: "bg-white text-gray-700 border border-gray-300 hover:bg-gray-50"
3633
3925
  };
@@ -3648,7 +3940,7 @@ function SidebarActionButton({
3648
3940
  onClick,
3649
3941
  disabled,
3650
3942
  title,
3651
- className: `w-full inline-flex items-center justify-center gap-2 rounded-lg px-4 py-2 text-sm font-medium transition-colors disabled:opacity-50 ${VARIANTS2[variant]} ${className}`,
3943
+ className: `w-full inline-flex items-center justify-center gap-2 rounded-lg px-4 py-2 text-sm font-medium transition-colors disabled:opacity-50 ${VARIANTS[variant]} ${className}`,
3652
3944
  children: [
3653
3945
  children,
3654
3946
  hotkey && /* @__PURE__ */ jsx("kbd", { className: "rounded border border-blue-400/50 bg-blue-500/30 px-1.5 py-0.5 text-[10px] font-medium", children: hotkey })
@@ -3737,6 +4029,7 @@ function Drawer({
3737
4029
  className = ""
3738
4030
  }) {
3739
4031
  const panelRef = useRef(null);
4032
+ const bodyId = useId();
3740
4033
  useFocusTrap(panelRef, open, initialFocus);
3741
4034
  useScrollLock(open);
3742
4035
  useEffect(() => {
@@ -3759,6 +4052,7 @@ function Drawer({
3759
4052
  role: "dialog",
3760
4053
  "aria-modal": "true",
3761
4054
  "aria-label": typeof title === "string" ? title : void 0,
4055
+ "aria-describedby": children ? bodyId : void 0,
3762
4056
  style: panelStyle,
3763
4057
  className: [
3764
4058
  "fixed flex flex-col bg-white shadow-xl",
@@ -3780,13 +4074,144 @@ function Drawer({
3780
4074
  }
3781
4075
  )
3782
4076
  ] }),
3783
- /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 overflow-y-auto px-4 py-4", children }),
4077
+ /* @__PURE__ */ jsx("div", { id: bodyId, className: "min-h-0 flex-1 overflow-y-auto px-4 py-4", children }),
3784
4078
  footer && /* @__PURE__ */ jsx("div", { className: "flex shrink-0 justify-end gap-3 border-t border-gray-100 px-4 py-3", children: footer })
3785
4079
  ]
3786
4080
  }
3787
4081
  )
3788
4082
  ] });
3789
4083
  }
4084
+ var DEFAULT_MAGNIFIER = "M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z";
4085
+ function GlobalSearch({ providers, typeIcons, placeholder = "Search...", onSelect } = {}) {
4086
+ const [open, setOpen] = useState(false);
4087
+ const [query, setQuery] = useState("");
4088
+ const [results, setResults] = useState([]);
4089
+ const [loading, setLoading] = useState(false);
4090
+ const [activeIdx, setActiveIdx] = useState(0);
4091
+ const inputRef = useRef(null);
4092
+ const debounceRef = useRef();
4093
+ useEffect(() => {
4094
+ const handler = (e) => {
4095
+ if ((e.ctrlKey || e.metaKey) && e.key === "k") {
4096
+ e.preventDefault();
4097
+ setOpen((prev) => !prev);
4098
+ }
4099
+ if (e.key === "Escape" && open) {
4100
+ setOpen(false);
4101
+ }
4102
+ };
4103
+ window.addEventListener("keydown", handler);
4104
+ return () => window.removeEventListener("keydown", handler);
4105
+ }, [open]);
4106
+ useEffect(() => {
4107
+ if (open) {
4108
+ setTimeout(() => inputRef.current?.focus(), 50);
4109
+ } else {
4110
+ setQuery("");
4111
+ setResults([]);
4112
+ }
4113
+ }, [open]);
4114
+ useEffect(() => {
4115
+ if (debounceRef.current) clearTimeout(debounceRef.current);
4116
+ if (query.length < 2) {
4117
+ setResults([]);
4118
+ return;
4119
+ }
4120
+ if (!providers || providers.length === 0) {
4121
+ setResults([]);
4122
+ return;
4123
+ }
4124
+ debounceRef.current = setTimeout(async () => {
4125
+ setLoading(true);
4126
+ try {
4127
+ const responses = await Promise.all(providers.map((p) => p(query).catch(() => [])));
4128
+ setResults(responses.flat());
4129
+ setActiveIdx(0);
4130
+ } catch {
4131
+ setResults([]);
4132
+ }
4133
+ setLoading(false);
4134
+ }, 250);
4135
+ return () => {
4136
+ if (debounceRef.current) clearTimeout(debounceRef.current);
4137
+ };
4138
+ }, [query, providers]);
4139
+ const handleSelect = useCallback((result) => {
4140
+ setOpen(false);
4141
+ onSelect?.(result);
4142
+ }, [onSelect]);
4143
+ const handleKeyDown = (e) => {
4144
+ if (e.key === "ArrowDown") {
4145
+ e.preventDefault();
4146
+ setActiveIdx((i) => Math.min(i + 1, results.length - 1));
4147
+ } else if (e.key === "ArrowUp") {
4148
+ e.preventDefault();
4149
+ setActiveIdx((i) => Math.max(i - 1, 0));
4150
+ } else if (e.key === "Enter" && results[activeIdx]) {
4151
+ handleSelect(results[activeIdx]);
4152
+ }
4153
+ };
4154
+ if (!open) return null;
4155
+ return /* @__PURE__ */ jsxs("div", { className: "fixed inset-0 z-[200] flex items-start justify-center pt-[15vh]", onClick: () => setOpen(false), children: [
4156
+ /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-black/50 backdrop-blur-sm" }),
4157
+ /* @__PURE__ */ jsxs("div", { className: "relative z-10 w-full max-w-xl rounded-2xl overflow-hidden", onClick: (e) => e.stopPropagation(), style: glassStyle(), children: [
4158
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 px-4 py-3 border-b border-gray-200", children: [
4159
+ /* @__PURE__ */ jsx("svg", { className: "h-5 w-5 text-gray-400 shrink-0", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: DEFAULT_MAGNIFIER }) }),
4160
+ /* @__PURE__ */ jsx(
4161
+ "input",
4162
+ {
4163
+ ref: inputRef,
4164
+ type: "text",
4165
+ value: query,
4166
+ onChange: (e) => setQuery(e.target.value),
4167
+ onKeyDown: handleKeyDown,
4168
+ placeholder,
4169
+ className: "flex-1 text-sm bg-transparent border-0 outline-none placeholder-gray-400"
4170
+ }
4171
+ ),
4172
+ /* @__PURE__ */ jsx("kbd", { className: "hidden sm:inline-flex items-center rounded border border-gray-300 px-1.5 py-0.5 text-[10px] font-medium text-gray-400", children: "ESC" })
4173
+ ] }),
4174
+ /* @__PURE__ */ jsxs("div", { className: "max-h-[50vh] overflow-y-auto", children: [
4175
+ loading && /* @__PURE__ */ jsx("div", { className: "px-4 py-6 text-center text-sm text-gray-400", children: "Searching..." }),
4176
+ !loading && query.length >= 2 && results.length === 0 && /* @__PURE__ */ jsx("div", { className: "px-4 py-6 text-center text-sm text-gray-400", children: "No results found." }),
4177
+ !loading && results.length > 0 && /* @__PURE__ */ jsx("ul", { className: "py-2", children: results.map((r, i) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
4178
+ "button",
4179
+ {
4180
+ className: `flex items-center gap-3 w-full px-4 py-2.5 text-left transition-colors ${i === activeIdx ? "bg-blue-50" : "hover:bg-gray-50"}`,
4181
+ onClick: () => handleSelect(r),
4182
+ onMouseEnter: () => setActiveIdx(i),
4183
+ children: [
4184
+ /* @__PURE__ */ jsx("svg", { className: "h-4 w-4 text-gray-400 shrink-0", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: typeIcons?.[r.type] || DEFAULT_MAGNIFIER }) }),
4185
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
4186
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-gray-900 truncate", children: r.label }),
4187
+ r.sub && /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-400 truncate", children: r.sub })
4188
+ ] }),
4189
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 rounded-full bg-gray-100 px-2 py-0.5 text-[10px] font-medium text-gray-500", children: r.type })
4190
+ ]
4191
+ }
4192
+ ) }, `${r.type}-${r.label}-${i}`)) }),
4193
+ !loading && query.length < 2 && /* @__PURE__ */ jsx("div", { className: "px-4 py-6 text-center text-sm text-gray-400", children: "Type at least 2 characters to search..." })
4194
+ ] }),
4195
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-4 py-2 border-t border-gray-100 bg-gray-50 text-[11px] text-gray-400", children: [
4196
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
4197
+ /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1", children: [
4198
+ /* @__PURE__ */ jsx("kbd", { className: "rounded border border-gray-300 px-1 py-0.5 font-medium", children: "\u2191" }),
4199
+ /* @__PURE__ */ jsx("kbd", { className: "rounded border border-gray-300 px-1 py-0.5 font-medium", children: "\u2193" }),
4200
+ " navigate"
4201
+ ] }),
4202
+ /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1", children: [
4203
+ /* @__PURE__ */ jsx("kbd", { className: "rounded border border-gray-300 px-1 py-0.5 font-medium", children: "Enter" }),
4204
+ " open"
4205
+ ] })
4206
+ ] }),
4207
+ /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1", children: [
4208
+ /* @__PURE__ */ jsx("kbd", { className: "rounded border border-gray-300 px-1 py-0.5 font-medium", children: "Esc" }),
4209
+ " close"
4210
+ ] })
4211
+ ] })
4212
+ ] })
4213
+ ] });
4214
+ }
3790
4215
  function Kanban({
3791
4216
  items,
3792
4217
  columns,
@@ -5013,6 +5438,6 @@ var ALT_SHIFT_D = isMac ? "\u2325\u21E7D" : "Alt+Shift+D";
5013
5438
  var ALT_SHIFT_E = isMac ? "\u2325\u21E7E" : "Alt+Shift+E";
5014
5439
  var ALT_SHIFT_N = isMac ? "\u2325\u21E7N" : "Alt+Shift+N";
5015
5440
 
5016
- export { ALT, ALT_SHIFT_D, ALT_SHIFT_E, ALT_SHIFT_N, Accordion, AuthScreen, Avatar, AvatarGroup, Banner, BarChart, Button_default, CMD_A, CMD_DOT, CMD_ENTER, CMD_K, CMD_S, CMD_SHIFT_Z, CMD_Z, Card, ChatTemplate, Checkbox_default, CheckoutTemplate, ColoredBadge, ContainerFillChart, CountBadge, DashboardTemplate, DataTable, DataTablePage, DateRangePicker, DescriptionList, Divider, DonutChart, Drawer, ENTER, EmailTemplate, EmptyState, ErrorPage, FilePicker, FormField, FormLayoutPage, GalleryTemplate, Grid, HelpCenter, INPUT_BASE, INPUT_SIZES, Inline, InputNumber_default, Input_default, Kanban, Label, ListFooter, ListLoadError, LoadingSpinner, MAX_FRACTION_DIGITS, MOD, Markdown, MediaUploadField, MediaUploadGrid, MetricBar, MilestoneTimeline, NativeSelect, NumericKeypad, PageHeader, Pagination, Paragraph, Radio_default, Result, SHIFT, SearchableSelect, Segmented, Select_default, SidebarActionButton, SidebarGroupLabel, SidebarNavItem, Skeleton, Sparkline, Stack, StatCard, Statistic, StatusBadge, StatusBadgeProvider, Switch, Tabs, Text, Textarea_default, TileButton_default, Title, Tooltip, TopNav, appendKey, applyThemePrefs, backspace, firstEnabledIndex, formatDate, inputClasses, isMac, isSeverityTone, lastEnabledIndex, matchTypeahead, mediaFileName, nextEnabledIndex, resolveTheme, severityOf, toISODate, useClickOutside, useTheme };
5017
- //# sourceMappingURL=chunk-XBV7NUZK.js.map
5018
- //# sourceMappingURL=chunk-XBV7NUZK.js.map
5441
+ export { ALT, ALT_SHIFT_D, ALT_SHIFT_E, ALT_SHIFT_N, Accordion, AuthScreen, Avatar, AvatarGroup, Banner, BarChart, Button_default, CMD_A, CMD_DOT, CMD_ENTER, CMD_K, CMD_S, CMD_SHIFT_Z, CMD_Z, Card, ChatTemplate, Checkbox_default, CheckoutTemplate, ColoredBadge, ContainerFillChart, CountBadge, DashboardTemplate, DataTable, DataTablePage, DatePicker_default, DateRangePicker, DescriptionList, Divider, DonutChart, Drawer, ENTER, EmailTemplate, EmptyState, ErrorPage, FilePicker, FormField, FormLayoutPage, GROUP_COLORS, GalleryTemplate, GlobalSearch, Grid, HelpCenter, INPUT_BASE, INPUT_SIZES, IconButton_default, Inline, InputNumber_default, Input_default, Kanban, Label, ListFooter, ListLoadError, LoadingSpinner, MAX_FRACTION_DIGITS, MOD, Markdown, MediaUploadField, MediaUploadGrid, MetricBar, MilestoneTimeline, NativeSelect, NumericKeypad, PageHeader, Pagination, Paragraph, Radio_default, Result, SHIFT, SearchableSelect, Segmented, Select_default, SidebarActionButton, SidebarGroupLabel, SidebarNavItem, Skeleton, Sparkline, Stack, StatCard, Statistic, StatusBadge, StatusBadgeProvider, Switch_default, Tabs, Text, Textarea_default, TileButton_default, Title, Tooltip, TopNav, appendKey, applyThemePrefs, backspace, firstEnabledIndex, formatDate, inputClasses, isMac, isSeverityTone, lastEnabledIndex, matchTypeahead, mediaFileName, nextEnabledIndex, resolveTheme, severityOf, tabButtonId, tabPanelId, toISODate, useClickOutside, useTheme };
5442
+ //# sourceMappingURL=chunk-D4WFGAUW.js.map
5443
+ //# sourceMappingURL=chunk-D4WFGAUW.js.map