react-os-shell 4.15.0 → 4.20.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 (95) hide show
  1. package/README.md +46 -0
  2. package/dist/{Browser-XV34CJFP.js → Browser-2TRNQ4MK.js} +9 -6
  3. package/dist/Browser-2TRNQ4MK.js.map +1 -0
  4. package/dist/{Calculator-FFCYNYU3.js → Calculator-QCAJ6ITI.js} +8 -5
  5. package/dist/Calculator-QCAJ6ITI.js.map +1 -0
  6. package/dist/ConfirmDialog-WAM5OKAV.js +3 -0
  7. package/dist/{ConfirmDialog-CXHJCDUL.js.map → ConfirmDialog-WAM5OKAV.js.map} +1 -1
  8. package/dist/{CurrencyConverter-3NCK7WY6.js → CurrencyConverter-PCYC6RL5.js} +8 -5
  9. package/dist/CurrencyConverter-PCYC6RL5.js.map +1 -0
  10. package/dist/{Documents-S6EQOLUF.js → Documents-O5TEGSYH.js} +10 -7
  11. package/dist/Documents-O5TEGSYH.js.map +1 -0
  12. package/dist/{Files-TFZNYXAH.js → Files-CD3FTPLK.js} +16 -12
  13. package/dist/Files-CD3FTPLK.js.map +1 -0
  14. package/dist/{Notepad-EAA6CXZX.js → Notepad-5KZS7PUJ.js} +10 -7
  15. package/dist/Notepad-5KZS7PUJ.js.map +1 -0
  16. package/dist/{PomodoroTimer-BXI752XM.js → PomodoroTimer-YABFEPEV.js} +10 -7
  17. package/dist/PomodoroTimer-YABFEPEV.js.map +1 -0
  18. package/dist/{Preview-ZKYM7A5X.js → Preview-UUNXWQME.js} +11 -8
  19. package/dist/Preview-UUNXWQME.js.map +1 -0
  20. package/dist/{Sidebar-IMACKSHS.js → Sidebar-B3LY5T2U.js} +5 -4
  21. package/dist/Sidebar-B3LY5T2U.js.map +1 -0
  22. package/dist/{Spreadsheet-BXOPROMB.js → Spreadsheet-DB5GMW2X.js} +10 -7
  23. package/dist/Spreadsheet-DB5GMW2X.js.map +1 -0
  24. package/dist/{Stock-BEQCSYNT.js → Stock-IPLLRUOC.js} +8 -5
  25. package/dist/Stock-IPLLRUOC.js.map +1 -0
  26. package/dist/{Weather-QPV6G7UN.js → Weather-SI5QN7X2.js} +8 -5
  27. package/dist/Weather-SI5QN7X2.js.map +1 -0
  28. package/dist/{WorldClock-I3ZE57V4.js → WorldClock-WYDSMUWX.js} +8 -5
  29. package/dist/WorldClock-WYDSMUWX.js.map +1 -0
  30. package/dist/apps/index.js +22 -19
  31. package/dist/apps/index.js.map +1 -1
  32. package/dist/chunk-22BNPLM4.js +46 -0
  33. package/dist/chunk-22BNPLM4.js.map +1 -0
  34. package/dist/{chunk-IVLDAUYM.js → chunk-3CUSBZWG.js} +3 -56
  35. package/dist/chunk-3CUSBZWG.js.map +1 -0
  36. package/dist/{chunk-VENYVK3L.js → chunk-4AKVHY4B.js} +48 -12
  37. package/dist/chunk-4AKVHY4B.js.map +1 -0
  38. package/dist/chunk-C22EFSND.js +188 -0
  39. package/dist/chunk-C22EFSND.js.map +1 -0
  40. package/dist/{chunk-LFTHOYKW.js → chunk-CZH7CN3S.js} +5 -5
  41. package/dist/{chunk-LFTHOYKW.js.map → chunk-CZH7CN3S.js.map} +1 -1
  42. package/dist/chunk-EGM44WML.js +5008 -0
  43. package/dist/chunk-EGM44WML.js.map +1 -0
  44. package/dist/{chunk-H4IEH6FK.js → chunk-EWDYCO65.js} +3 -3
  45. package/dist/{chunk-H4IEH6FK.js.map → chunk-EWDYCO65.js.map} +1 -1
  46. package/dist/{chunk-DINNWYVB.js → chunk-HYAW6KIL.js} +4 -45
  47. package/dist/chunk-HYAW6KIL.js.map +1 -0
  48. package/dist/{chunk-H3HFM4JQ.js → chunk-IGPSRRL7.js} +262 -712
  49. package/dist/chunk-IGPSRRL7.js.map +1 -0
  50. package/dist/chunk-L2O6B5KT.js +394 -0
  51. package/dist/chunk-L2O6B5KT.js.map +1 -0
  52. package/dist/{chunk-YS4ZW2P7.js → chunk-OI253GOA.js} +4 -4
  53. package/dist/{chunk-YS4ZW2P7.js.map → chunk-OI253GOA.js.map} +1 -1
  54. package/dist/{chunk-ZPCGIR62.js → chunk-OQ7GRU7G.js} +3 -3
  55. package/dist/{chunk-ZPCGIR62.js.map → chunk-OQ7GRU7G.js.map} +1 -1
  56. package/dist/{chunk-7J3HTJJ3.js → chunk-Q3AOIJ7B.js} +3 -3
  57. package/dist/{chunk-7J3HTJJ3.js.map → chunk-Q3AOIJ7B.js.map} +1 -1
  58. package/dist/{chunk-N2UW3CAS.js → chunk-QJS6RBH4.js} +3 -3
  59. package/dist/{chunk-N2UW3CAS.js.map → chunk-QJS6RBH4.js.map} +1 -1
  60. package/dist/chunk-WNLPTLT6.js +56 -0
  61. package/dist/chunk-WNLPTLT6.js.map +1 -0
  62. package/dist/{chunk-QU5L5ZYM.js → chunk-X5PZYTAK.js} +3 -3
  63. package/dist/{chunk-QU5L5ZYM.js.map → chunk-X5PZYTAK.js.map} +1 -1
  64. package/dist/chunk-ZKQHO2Z7.js +7 -0
  65. package/dist/{chunk-F562QDVS.js.map → chunk-ZKQHO2Z7.js.map} +1 -1
  66. package/dist/index-C5c6FA0c.d.ts +1931 -0
  67. package/dist/index.d.ts +7 -1375
  68. package/dist/index.js +159 -4082
  69. package/dist/index.js.map +1 -1
  70. package/dist/shell.css +105 -0
  71. package/dist/styles.css +12 -501
  72. package/dist/ui/index.d.ts +3 -0
  73. package/dist/ui/index.js +11 -0
  74. package/dist/ui/index.js.map +1 -0
  75. package/dist/ui.css +425 -0
  76. package/package.json +7 -2
  77. package/dist/Browser-XV34CJFP.js.map +0 -1
  78. package/dist/Calculator-FFCYNYU3.js.map +0 -1
  79. package/dist/ConfirmDialog-CXHJCDUL.js +0 -4
  80. package/dist/CurrencyConverter-3NCK7WY6.js.map +0 -1
  81. package/dist/Documents-S6EQOLUF.js.map +0 -1
  82. package/dist/Files-TFZNYXAH.js.map +0 -1
  83. package/dist/Notepad-EAA6CXZX.js.map +0 -1
  84. package/dist/PomodoroTimer-BXI752XM.js.map +0 -1
  85. package/dist/Preview-ZKYM7A5X.js.map +0 -1
  86. package/dist/Sidebar-IMACKSHS.js.map +0 -1
  87. package/dist/Spreadsheet-BXOPROMB.js.map +0 -1
  88. package/dist/Stock-BEQCSYNT.js.map +0 -1
  89. package/dist/Weather-QPV6G7UN.js.map +0 -1
  90. package/dist/WorldClock-I3ZE57V4.js.map +0 -1
  91. package/dist/chunk-DINNWYVB.js.map +0 -1
  92. package/dist/chunk-F562QDVS.js +0 -7
  93. package/dist/chunk-H3HFM4JQ.js.map +0 -1
  94. package/dist/chunk-IVLDAUYM.js.map +0 -1
  95. package/dist/chunk-VENYVK3L.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,27 +1,35 @@
1
- import { PREVIEW_OPENED_EVENT, openPreviewFile } from './chunk-DINNWYVB.js';
2
- export { Breadcrumbs } from './chunk-DINNWYVB.js';
3
- import { subscribePomo, getPomoSnapshot } from './chunk-YS4ZW2P7.js';
4
- export { setShellTodoProvider } from './chunk-YS4ZW2P7.js';
5
- import { SidebarLayout } from './chunk-YG45DAUT.js';
6
- export { SidebarLayout } from './chunk-YG45DAUT.js';
7
- export { ImageAnnotator_default as ImageAnnotator } from './chunk-QU5L5ZYM.js';
8
- import { toast_default } from './chunk-VENYVK3L.js';
9
- export { toast_default as toast } from './chunk-VENYVK3L.js';
10
- import { publishDesktopFolders, requestFilesTrashView, FolderGlyph, requestFilesDesktopFolderView, FileIconTile } from './chunk-ZPCGIR62.js';
1
+ import { PREVIEW_OPENED_EVENT, openPreviewFile } from './chunk-22BNPLM4.js';
2
+ import { subscribePomo, getPomoSnapshot } from './chunk-OI253GOA.js';
3
+ export { setShellTodoProvider } from './chunk-OI253GOA.js';
4
+ export { ImageAnnotator_default as ImageAnnotator } from './chunk-X5PZYTAK.js';
5
+ import { publishDesktopFolders, requestFilesTrashView, FolderGlyph, requestFilesDesktopFolderView, FileIconTile } from './chunk-OQ7GRU7G.js';
6
+ import './chunk-4SQU5YV6.js';
11
7
  import { setPdfPreview } from './chunk-BV6SQ46N.js';
12
- import { playNotification, playStartup, soundsEnabled, getSoundConfig, SOUND_PACK_KEYS, SOUND_PACKS, SOUND_TYPES, SOUND_TYPE_LABELS, setSoundForType, previewSound, setAllSounds, playLogout } from './chunk-D7PYW2QS.js';
13
- import { useAuth } from './chunk-H4IEH6FK.js';
14
- export { setShellAuthBridge } from './chunk-H4IEH6FK.js';
8
+ import { CMD_K, CMD_DOT, CMD_A, ALT_SHIFT_N, ALT_SHIFT_E, CMD_ENTER, CMD_S, ALT_SHIFT_D, CMD_Z, CMD_SHIFT_Z, formatDate, useTheme, resolveTheme, useClickOutside, ListLoadError, ListFooter, firstEnabledIndex, Button_default, FormField, Input_default, applyThemePrefs, lastEnabledIndex, nextEnabledIndex, matchTypeahead } from './chunk-EGM44WML.js';
9
+ export { ALT, ALT_SHIFT_D, ALT_SHIFT_E, ALT_SHIFT_N, Accordion, AuthScreen, Avatar, AvatarGroup, Banner, BarChart, Button_default as Button, CMD_A, CMD_DOT, CMD_ENTER, CMD_K, CMD_S, Card, ChatTemplate, Checkbox_default as Checkbox, 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, Input_default as Input, InputNumber_default as InputNumber, Kanban, Label, ListFooter, ListLoadError, LoadingSpinner, MAX_FRACTION_DIGITS, MOD, Markdown, MediaUploadField, MediaUploadGrid, MetricBar, MilestoneTimeline, NativeSelect, NumericKeypad, PageHeader, Pagination, Paragraph, Radio_default as Radio, Result, SHIFT, SearchableSelect, Segmented, Select_default as Select, SidebarActionButton, SidebarGroupLabel, SidebarNavItem, Skeleton, Sparkline, Stack, StatCard, Statistic, StatusBadge, StatusBadgeProvider, Switch, Tabs, Text, Textarea_default as Textarea, TileButton_default as TileButton, Title, Tooltip, TopNav, appendKey, applyThemePrefs, backspace, formatDate, inputClasses, isMac, isSeverityTone, mediaFileName, resolveTheme, severityOf, toISODate, useClickOutside, useTheme } from './chunk-EGM44WML.js';
10
+ export { Breadcrumbs } from './chunk-HYAW6KIL.js';
15
11
  import { withSpareRow, EditableGrid } from './chunk-DC6BYGZ6.js';
16
12
  export { EditableGrid } from './chunk-DC6BYGZ6.js';
17
- import './chunk-4SQU5YV6.js';
18
- import { APP_VERSION, VERSION } from './chunk-F562QDVS.js';
19
- export { VERSION } from './chunk-F562QDVS.js';
20
- import { registerModalEscapeInterceptor, useIsMobile, cancelWindowFront, requestWindowFront, useWindowManager, PopupMenu, PopupMenuLabel, PopupMenuDivider, PopupMenuItem, WINDOW_REGISTRY, isPageEntry, Modal, setPerfCollecting, drainPerfEvents, useShellPrefs, markMenuOpen, SIDEBAR_STRIP_W, forgetMaximizedWindowBoxes, ModalActions, useModalActive, client_default, LoadingSpinner, isShellApiClientConfigured, useUndo, CancelButton, setWindowPosition } from './chunk-H3HFM4JQ.js';
21
- export { CancelButton, ConfirmProvider, CopyButton, DocFavStar, Modal, ModalActions, PopupMenu, PopupMenuDivider, PopupMenuItem, PopupMenuLabel, ShellPrefsProvider, UndoProvider, WindowCrashedFallback, WindowErrorBoundary, WindowManagerProvider, WindowTitle, beginWindowGesture, commitExposeHighlight, confirm, confirmDestructive, exitExposeMode, getActiveWindowRoute, getExposeHighlight, getWindowPosition, isEntityEntry, isPageEntry, markMenuOpen, prompt, registerModalEscapeInterceptor, requestWindowFront, setExposeHighlight, setShellApiClient, setShellWindowRegistry, setWindowDefaultPosition, setWindowPosition, subscribeExposeHighlight, toggleExposeMode, useIsActiveWindow, useLocalStoragePrefs, useModalActive, useShellPrefs, useUndo, useUndoable, useUndoableState, useWidgetSettings, useWindowDirty, useWindowManager, useWindowMenuItem, useWindowTitle } from './chunk-H3HFM4JQ.js';
22
- import { glassStyle, startMenuCategories, navVisible, navSections, isSection, GLASS_INPUT_BG, navIcons, isReachable, sectionIcons, useShellAuth, visibleChildren } from './chunk-IVLDAUYM.js';
23
- export { GLASS_DIVIDER, GLASS_INPUT_BG, ShellAuthProvider, glassStyle, setShellNavIcons, useShellAuth } from './chunk-IVLDAUYM.js';
24
- import { createContext, lazy, forwardRef, useCallback, useState, useRef, useId, useEffect, useLayoutEffect, useSyncExternalStore, useMemo, useContext, isValidElement, cloneElement, Suspense, Fragment as Fragment$1, Children } from 'react';
13
+ import { SidebarLayout } from './chunk-YG45DAUT.js';
14
+ export { SidebarLayout } from './chunk-YG45DAUT.js';
15
+ import { toast_default } from './chunk-4AKVHY4B.js';
16
+ export { toast_default as toast } from './chunk-4AKVHY4B.js';
17
+ import { APP_VERSION, VERSION } from './chunk-ZKQHO2Z7.js';
18
+ export { VERSION } from './chunk-ZKQHO2Z7.js';
19
+ import { playNotification, playStartup, soundsEnabled, getSoundConfig, SOUND_PACK_KEYS, SOUND_PACKS, SOUND_TYPES, SOUND_TYPE_LABELS, setSoundForType, previewSound, setAllSounds, playLogout } from './chunk-D7PYW2QS.js';
20
+ import { useAuth } from './chunk-EWDYCO65.js';
21
+ export { setShellAuthBridge } from './chunk-EWDYCO65.js';
22
+ import { cancelWindowFront, requestWindowFront, useWindowManager, WINDOW_REGISTRY, isPageEntry, Modal, setPerfCollecting, drainPerfEvents, markMenuOpen, SIDEBAR_STRIP_W, forgetMaximizedWindowBoxes, ModalActions, useModalActive, client_default, LoadingSpinner, isShellApiClientConfigured, useUndo, CancelButton, setWindowPosition } from './chunk-IGPSRRL7.js';
23
+ export { CancelButton, CopyButton, DocFavStar, Modal, ModalActions, UndoProvider, WindowCrashedFallback, WindowErrorBoundary, WindowManagerProvider, WindowTitle, beginWindowGesture, commitExposeHighlight, exitExposeMode, getActiveWindowRoute, getExposeHighlight, getWindowPosition, isEntityEntry, isPageEntry, markMenuOpen, requestWindowFront, setExposeHighlight, setShellApiClient, setShellWindowRegistry, setWindowDefaultPosition, setWindowPosition, subscribeExposeHighlight, toggleExposeMode, useIsActiveWindow, useModalActive, useUndo, useUndoable, useUndoableState, useWidgetSettings, useWindowDirty, useWindowManager, useWindowMenuItem, useWindowTitle } from './chunk-IGPSRRL7.js';
24
+ import { PopupMenu, PopupMenuLabel, PopupMenuDivider, PopupMenuItem, useShellPrefs, useIsMobile } from './chunk-C22EFSND.js';
25
+ export { PopupMenu, PopupMenuDivider, PopupMenuItem, PopupMenuLabel, ShellPrefsProvider, useIsMobile, useLocalStoragePrefs, useShellPrefs } from './chunk-C22EFSND.js';
26
+ import { registerModalEscapeInterceptor } from './chunk-L2O6B5KT.js';
27
+ export { ConfirmProvider, Dialog, confirm, confirmDestructive, prompt, registerModalEscapeInterceptor, useConfirm, useFocusTrap, useScrollLock } from './chunk-L2O6B5KT.js';
28
+ import { startMenuCategories, navVisible, navSections, isSection, navIcons, isReachable, sectionIcons, useShellAuth, visibleChildren } from './chunk-WNLPTLT6.js';
29
+ export { ShellAuthProvider, setShellNavIcons, useShellAuth } from './chunk-WNLPTLT6.js';
30
+ import { glassStyle, GLASS_INPUT_BG } from './chunk-3CUSBZWG.js';
31
+ export { GLASS_DIVIDER, GLASS_INPUT_BG, glassStyle } from './chunk-3CUSBZWG.js';
32
+ import { createContext, lazy, useSyncExternalStore, useState, useRef, useEffect, useCallback, useLayoutEffect, useMemo, useContext, isValidElement, cloneElement, Suspense, useId } from 'react';
25
33
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
26
34
  import { Dialog, DialogBackdrop, DialogPanel } from '@headlessui/react';
27
35
  import { createPortal } from 'react-dom';
@@ -199,23 +207,6 @@ function GlobalSearch({ providers, typeIcons, placeholder = "Search..." } = {})
199
207
  ] })
200
208
  ] });
201
209
  }
202
-
203
- // src/shell/Kbd.tsx
204
- var isMac = typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.platform || navigator.userAgent);
205
- var MOD = isMac ? "\u2318" : "Ctrl";
206
- var ALT = isMac ? "\u2325" : "Alt";
207
- var SHIFT = "\u21E7";
208
- var ENTER = "\u23CE";
209
- var CMD_ENTER = isMac ? "\u2318\u23CE" : "Ctrl\u23CE";
210
- var CMD_S = isMac ? "\u2318S" : "Ctrl+S";
211
- var CMD_K = isMac ? "\u2318K" : "Ctrl+K";
212
- var CMD_DOT = isMac ? "\u2318." : "Ctrl+.";
213
- var CMD_A = isMac ? "\u2318A" : "Ctrl+A";
214
- var CMD_Z = isMac ? "\u2318Z" : "Ctrl+Z";
215
- var CMD_SHIFT_Z = isMac ? "\u2318\u21E7Z" : "Ctrl+Shift+Z";
216
- var ALT_SHIFT_D = isMac ? "\u2325\u21E7D" : "Alt+Shift+D";
217
- var ALT_SHIFT_E = isMac ? "\u2325\u21E7E" : "Alt+Shift+E";
218
- var ALT_SHIFT_N = isMac ? "\u2325\u21E7N" : "Alt+Shift+N";
219
210
  var sections = [
220
211
  {
221
212
  title: "Global",
@@ -295,689 +286,6 @@ function ShortcutHelp() {
295
286
  ] }) })
296
287
  ] });
297
288
  }
298
- var INLINE_RE = /!\[([^\]]*)\]\(([^)]+)\)|\[([^\]]+)\]\(([^)]+)\)|\*\*([^*]+)\*\*|`([^`]+)`|\*([^*]+)\*/g;
299
- function renderInline(text) {
300
- const out = [];
301
- let last = 0;
302
- let key = 0;
303
- let m;
304
- INLINE_RE.lastIndex = 0;
305
- while ((m = INLINE_RE.exec(text)) !== null) {
306
- if (m.index > last) out.push(text.slice(last, m.index));
307
- if (m[1] !== void 0) {
308
- out.push(
309
- /* @__PURE__ */ jsxs("span", { className: "text-gray-400", children: [
310
- "[",
311
- m[1],
312
- "]"
313
- ] }, key++)
314
- );
315
- } else if (m[3] !== void 0) {
316
- out.push(
317
- /* @__PURE__ */ jsx(
318
- "a",
319
- {
320
- href: m[4],
321
- target: "_blank",
322
- rel: "noreferrer",
323
- className: "text-blue-600 hover:underline",
324
- children: m[3]
325
- },
326
- key++
327
- )
328
- );
329
- } else if (m[5] !== void 0) {
330
- out.push(
331
- /* @__PURE__ */ jsx("strong", { className: "font-semibold text-gray-900", children: m[5] }, key++)
332
- );
333
- } else if (m[6] !== void 0) {
334
- out.push(
335
- /* @__PURE__ */ jsx(
336
- "code",
337
- {
338
- className: "rounded bg-gray-100 px-1 py-0.5 font-mono text-[0.85em] text-gray-800",
339
- children: m[6]
340
- },
341
- key++
342
- )
343
- );
344
- } else if (m[7] !== void 0) {
345
- out.push(/* @__PURE__ */ jsx("em", { children: m[7] }, key++));
346
- }
347
- last = INLINE_RE.lastIndex;
348
- }
349
- if (last < text.length) out.push(text.slice(last));
350
- return out;
351
- }
352
- function ScreenshotPlaceholder({ alt }) {
353
- const label = alt.replace(/^screenshot:\s*/i, "");
354
- return /* @__PURE__ */ jsxs("div", { className: "my-1 flex items-start gap-2 rounded-lg border border-dashed border-gray-300 bg-gray-50 px-3 py-2.5", children: [
355
- /* @__PURE__ */ jsx(
356
- "svg",
357
- {
358
- className: "mt-0.5 h-4 w-4 shrink-0 text-gray-400",
359
- viewBox: "0 0 20 20",
360
- fill: "currentColor",
361
- "aria-hidden": "true",
362
- children: /* @__PURE__ */ jsx("path", { d: "M3 6a1.5 1.5 0 0 1 1.5-1.5h1.1l.4-.9a1 1 0 0 1 .9-.6h6.2a1 1 0 0 1 .9.6l.4.9h1.1A1.5 1.5 0 0 1 18 6v8.5A1.5 1.5 0 0 1 16.5 16h-13A1.5 1.5 0 0 1 2 14.5V6Zm7.5 1.5a3.25 3.25 0 1 0 0 6.5 3.25 3.25 0 0 0 0-6.5Z" })
363
- }
364
- ),
365
- /* @__PURE__ */ jsxs("span", { className: "text-xs text-gray-500", children: [
366
- /* @__PURE__ */ jsx("span", { className: "font-medium text-gray-600", children: "Screenshot \u2014 " }),
367
- label
368
- ] })
369
- ] });
370
- }
371
- function collectItems(lines, itemRe) {
372
- const items = [];
373
- for (const line of lines) {
374
- const m = itemRe.exec(line);
375
- if (m) {
376
- items.push(m[1]);
377
- } else if (items.length) {
378
- items[items.length - 1] += " " + line.trim();
379
- }
380
- }
381
- return items;
382
- }
383
- var splitCells = (line) => line.replace(/^\s*\|/, "").replace(/\|\s*$/, "").split("|").map((c) => c.trim());
384
- function isTable(lines) {
385
- return lines.length >= 2 && lines[0].includes("|") && /^\s*\|?[\s:|-]*-[\s:|-]*\|?\s*$/.test(lines[1]) && lines[1].includes("-");
386
- }
387
- function renderTable(lines, key) {
388
- const header = splitCells(lines[0]);
389
- const rows = lines.slice(2).map(splitCells);
390
- return /* @__PURE__ */ jsx("div", { className: "my-1 overflow-x-auto rounded-lg border border-gray-200", children: /* @__PURE__ */ jsxs("table", { className: "w-full border-collapse text-sm", children: [
391
- /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { className: "bg-gray-50", children: header.map((h, i) => /* @__PURE__ */ jsx(
392
- "th",
393
- {
394
- className: "border-b border-gray-200 px-3 py-1.5 text-left font-semibold text-gray-700",
395
- children: renderInline(h)
396
- },
397
- i
398
- )) }) }),
399
- /* @__PURE__ */ jsx("tbody", { children: rows.map((row, r) => /* @__PURE__ */ jsx("tr", { className: "even:bg-gray-50/50", children: row.map((c, i) => /* @__PURE__ */ jsx("td", { className: "border-t border-gray-100 px-3 py-1.5 align-top text-gray-700", children: renderInline(c) }, i)) }, r)) })
400
- ] }) }, key);
401
- }
402
- function renderBlock(block, key) {
403
- const lines = block.split("\n");
404
- const first = lines[0];
405
- const heading = /^(#{2,4})\s+(.*)$/.exec(first);
406
- if (heading && lines.length === 1) {
407
- const level = heading[1].length;
408
- const Tag = `h${level}`;
409
- const cls = level === 2 ? "mt-2 text-[13px] font-semibold uppercase tracking-wide text-gray-500" : level === 3 ? "mt-1 text-base font-semibold text-gray-900" : "mt-1 text-sm font-semibold text-gray-900";
410
- return /* @__PURE__ */ jsx(Tag, { className: cls, children: renderInline(heading[2]) }, key);
411
- }
412
- if (lines.length === 1 && /^---+$/.test(first)) {
413
- return /* @__PURE__ */ jsx("hr", { className: "border-gray-200" }, key);
414
- }
415
- if (isTable(lines)) {
416
- return renderTable(lines, key);
417
- }
418
- if (/^>\s?/.test(first)) {
419
- const text = lines.map((l) => l.replace(/^>\s?/, "")).join(" ");
420
- return /* @__PURE__ */ jsx(
421
- "div",
422
- {
423
- className: "my-1 rounded-r-lg border-l-4 border-blue-300 bg-blue-50/60 px-3 py-2 text-gray-700",
424
- children: renderInline(text)
425
- },
426
- key
427
- );
428
- }
429
- const img = /^!\[([^\]]*)\]\(([^)]+)\)$/.exec(first);
430
- if (img && lines.length === 1) {
431
- return /* @__PURE__ */ jsx(ScreenshotPlaceholder, { alt: img[1] }, key);
432
- }
433
- if (/^[-*]\s+/.test(first)) {
434
- const items = collectItems(lines, /^[-*]\s+(.*)$/);
435
- return /* @__PURE__ */ jsx("ul", { className: "list-disc space-y-1 pl-5 marker:text-gray-400", children: items.map((it, j) => /* @__PURE__ */ jsx("li", { children: renderInline(it) }, j)) }, key);
436
- }
437
- const ordered = /^(\d+)\.\s+/.exec(first);
438
- if (ordered) {
439
- const items = collectItems(lines, /^\d+\.\s+(.*)$/);
440
- return /* @__PURE__ */ jsx(
441
- "ol",
442
- {
443
- start: Number(ordered[1]),
444
- className: "list-decimal space-y-1 pl-5 marker:text-gray-400",
445
- children: items.map((it, j) => /* @__PURE__ */ jsx("li", { className: "pl-1", children: renderInline(it) }, j))
446
- },
447
- key
448
- );
449
- }
450
- return /* @__PURE__ */ jsx("p", { children: renderInline(lines.join(" ")) }, key);
451
- }
452
- function Markdown({ children, className }) {
453
- const blocks = (children ?? "").replace(/\r\n/g, "\n").split(/\n{2,}/).map((b) => b.replace(/\s+$/, "")).filter((b) => b.trim() !== "");
454
- return /* @__PURE__ */ jsx("div", { className: `space-y-3 text-sm leading-relaxed text-gray-700 ${className ?? ""}`.trim(), children: blocks.map((block, i) => renderBlock(block, i)) });
455
- }
456
- var Chevron = ({ open }) => /* @__PURE__ */ jsx(
457
- "svg",
458
- {
459
- className: `h-3.5 w-3.5 shrink-0 text-gray-400 transition-transform ${open ? "rotate-90" : ""}`,
460
- viewBox: "0 0 20 20",
461
- fill: "currentColor",
462
- "aria-hidden": "true",
463
- children: /* @__PURE__ */ jsx(
464
- "path",
465
- {
466
- fillRule: "evenodd",
467
- d: "M7.21 14.77a.75.75 0 0 1 .02-1.06L11.168 10 7.23 6.29a.75.75 0 1 1 1.04-1.08l4.5 4.25a.75.75 0 0 1 0 1.08l-4.5 4.25a.75.75 0 0 1-1.06-.02Z",
468
- clipRule: "evenodd"
469
- }
470
- )
471
- }
472
- );
473
- function HelpCenter({
474
- docs,
475
- loading = false,
476
- categoryOrder,
477
- canEdit = false,
478
- onNew,
479
- onEdit,
480
- renderBody,
481
- emptyMessage = "No help articles yet."
482
- }) {
483
- const [query, setQuery] = useState("");
484
- const [selectedSlug, setSelectedSlug] = useState(null);
485
- const [expanded, setExpanded] = useState({});
486
- const q = query.trim().toLowerCase();
487
- const groups = useMemo(() => {
488
- const map = /* @__PURE__ */ new Map();
489
- for (const doc of docs) {
490
- let g = map.get(doc.category);
491
- if (!g) {
492
- g = { key: doc.category, label: doc.category_label || doc.category, docs: [] };
493
- map.set(doc.category, g);
494
- }
495
- g.docs.push(doc);
496
- }
497
- if (!categoryOrder?.length) return [...map.values()];
498
- const rank = new Map(categoryOrder.map((k, i) => [k, i]));
499
- return [...map.values()].sort(
500
- (a, b) => (rank.get(a.key) ?? Infinity) - (rank.get(b.key) ?? Infinity)
501
- );
502
- }, [docs, categoryOrder]);
503
- const matches2 = (doc) => !q || doc.title.toLowerCase().includes(q) || doc.body.toLowerCase().includes(q) || doc.category_label.toLowerCase().includes(q);
504
- const visibleGroups = useMemo(
505
- () => groups.map((g) => ({ ...g, docs: g.docs.filter(matches2) })).filter((g) => g.docs.length > 0),
506
- [groups, q]
507
- );
508
- const visibleDocs = useMemo(() => visibleGroups.flatMap((g) => g.docs), [visibleGroups]);
509
- const selected = (selectedSlug ? visibleDocs.find((d) => d.slug === selectedSlug) : void 0) ?? visibleDocs[0] ?? null;
510
- const isGroupOpen = (key) => q ? true : expanded[key] ?? key === selected?.category;
511
- const toggleGroup = (key) => setExpanded((prev) => ({ ...prev, [key]: !isGroupOpen(key) }));
512
- return /* @__PURE__ */ jsxs("div", { className: "flex h-full gap-4 px-4 py-3 min-h-0", children: [
513
- /* @__PURE__ */ jsxs("aside", { className: "w-64 shrink-0 flex flex-col bg-white rounded-lg shadow overflow-hidden", children: [
514
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 px-3 py-2 border-b border-gray-200 bg-gray-50 shrink-0", children: [
515
- /* @__PURE__ */ jsx("h2", { className: "text-sm font-semibold text-gray-900", children: "Help" }),
516
- canEdit && onNew && /* @__PURE__ */ jsx("button", { type: "button", onClick: onNew, className: "text-xs text-blue-600 hover:underline", children: "+ New" })
517
- ] }),
518
- /* @__PURE__ */ jsx("div", { className: "px-2.5 py-2 border-b border-gray-100 shrink-0", children: /* @__PURE__ */ jsxs("div", { className: "relative", children: [
519
- /* @__PURE__ */ jsx(
520
- "svg",
521
- {
522
- className: "pointer-events-none absolute left-2.5 top-1/2 -translate-y-1/2 h-4 w-4 text-gray-400",
523
- viewBox: "0 0 20 20",
524
- fill: "currentColor",
525
- "aria-hidden": "true",
526
- children: /* @__PURE__ */ jsx(
527
- "path",
528
- {
529
- fillRule: "evenodd",
530
- d: "M9 3.5a5.5 5.5 0 1 0 3.36 9.85l3.14 3.15a.75.75 0 1 0 1.06-1.06l-3.15-3.14A5.5 5.5 0 0 0 9 3.5ZM5 9a4 4 0 1 1 8 0 4 4 0 0 1-8 0Z",
531
- clipRule: "evenodd"
532
- }
533
- )
534
- }
535
- ),
536
- /* @__PURE__ */ jsx(
537
- "input",
538
- {
539
- type: "text",
540
- value: query,
541
- onChange: (e) => setQuery(e.target.value),
542
- placeholder: "Search help\u2026",
543
- className: "w-full rounded-md border border-gray-300 bg-white py-1.5 pl-8 pr-2 text-sm shadow-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
544
- }
545
- )
546
- ] }) }),
547
- /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-y-auto py-1", children: loading ? /* @__PURE__ */ jsx("p", { className: "px-3 py-6 text-center text-xs text-gray-400", children: "Loading\u2026" }) : visibleGroups.length === 0 ? /* @__PURE__ */ jsx("p", { className: "px-3 py-6 text-center text-xs text-gray-500", children: q ? "No articles match your search." : emptyMessage }) : visibleGroups.map((group2) => {
548
- const open = isGroupOpen(group2.key);
549
- return /* @__PURE__ */ jsxs("div", { className: "py-0.5", children: [
550
- /* @__PURE__ */ jsxs(
551
- "button",
552
- {
553
- type: "button",
554
- onClick: () => toggleGroup(group2.key),
555
- className: "w-full flex items-center gap-1.5 px-3 py-1.5 text-left text-[10px] font-semibold uppercase tracking-wide text-gray-400 hover:text-gray-600",
556
- children: [
557
- /* @__PURE__ */ jsx(Chevron, { open }),
558
- /* @__PURE__ */ jsx("span", { className: "truncate", children: group2.label })
559
- ]
560
- }
561
- ),
562
- open && group2.docs.map((doc) => {
563
- const active = selected?.slug === doc.slug;
564
- return /* @__PURE__ */ jsxs(
565
- "button",
566
- {
567
- type: "button",
568
- onClick: () => setSelectedSlug(doc.slug),
569
- className: `w-full text-left pl-8 pr-3 py-1.5 text-sm flex items-center gap-1.5 transition-colors ${active ? "bg-blue-50 text-blue-700 font-medium" : "text-gray-700 hover:bg-gray-50"}`,
570
- children: [
571
- /* @__PURE__ */ jsx("span", { className: "truncate", children: doc.title }),
572
- doc.is_published === false && /* @__PURE__ */ jsx("span", { className: "ml-auto shrink-0 text-[10px] text-amber-600", children: "Draft" })
573
- ]
574
- },
575
- doc.slug
576
- );
577
- })
578
- ] }, group2.key);
579
- }) })
580
- ] }),
581
- /* @__PURE__ */ jsx("main", { className: "flex-1 min-w-0 bg-white rounded-lg shadow overflow-hidden flex flex-col", children: selected ? /* @__PURE__ */ jsxs(Fragment, { children: [
582
- /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-4 px-5 py-3 border-b border-gray-200 bg-gray-50 shrink-0", children: [
583
- /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
584
- /* @__PURE__ */ jsx("h1", { className: "text-lg font-semibold text-gray-900 truncate", children: selected.title }),
585
- /* @__PURE__ */ jsxs("p", { className: "text-xs text-gray-500 mt-0.5", children: [
586
- selected.category_label,
587
- selected.is_published === false && /* @__PURE__ */ jsx("span", { className: "ml-2 text-amber-600", children: "\xB7 Draft" })
588
- ] })
589
- ] }),
590
- canEdit && onEdit && /* @__PURE__ */ jsx(
591
- "button",
592
- {
593
- type: "button",
594
- onClick: () => onEdit(selected),
595
- className: "shrink-0 text-sm text-blue-600 hover:underline",
596
- children: "Edit"
597
- }
598
- )
599
- ] }),
600
- /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-auto px-5 py-4", children: renderBody ? renderBody(selected) : selected.body ? /* @__PURE__ */ jsx(Markdown, { children: selected.body }) : /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-500 italic", children: "This article has no body yet." }) })
601
- ] }) : /* @__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." }) }) })
602
- ] });
603
- }
604
- var VARIANTS = {
605
- primary: "bg-blue-600 text-white hover:bg-blue-700",
606
- secondary: "bg-white text-gray-700 border border-gray-300 hover:bg-gray-50"
607
- };
608
- function SidebarActionButton({
609
- children,
610
- onClick,
611
- variant = "primary",
612
- hotkey,
613
- disabled = false,
614
- type = "button",
615
- title,
616
- className = ""
617
- }) {
618
- return /* @__PURE__ */ jsxs(
619
- "button",
620
- {
621
- type,
622
- onClick,
623
- disabled,
624
- title,
625
- 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}`,
626
- children: [
627
- children,
628
- 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 })
629
- ]
630
- }
631
- );
632
- }
633
-
634
- // src/forms/styles.ts
635
- var INPUT_BASE = "block w-full rounded-md border border-gray-300 bg-white px-3 py-1.5 text-sm text-gray-800 placeholder:text-gray-400 shadow-sm focus:border-blue-400 focus:outline-none focus:ring-2 focus:ring-blue-400/30";
636
- var INPUT_INVALID = "border-red-300 focus:border-red-400 focus:ring-red-400/30";
637
- var INPUT_DISABLED = "disabled:cursor-not-allowed disabled:opacity-60";
638
- function inputClasses(opts) {
639
- return [INPUT_BASE, opts?.invalid ? INPUT_INVALID : "", INPUT_DISABLED, opts?.className ?? ""].filter(Boolean).join(" ");
640
- }
641
- var POPUP_MAX_WIDTH = 448;
642
- var MENU_MAX_HEIGHT = 240;
643
- var MENU_GAP = 4;
644
- var VIEWPORT_MARGIN = 8;
645
- function useDropdownPosition(triggerRef, open) {
646
- const [pos, setPos] = useState(null);
647
- useLayoutEffect(() => {
648
- if (!open) {
649
- setPos(null);
650
- return;
651
- }
652
- let lastLeft = NaN, lastTop = NaN, lastRight = NaN, lastBottom = NaN;
653
- const compute = () => {
654
- const rect = triggerRef.current?.getBoundingClientRect();
655
- if (!rect) return;
656
- lastLeft = rect.left;
657
- lastTop = rect.top;
658
- lastRight = rect.right;
659
- lastBottom = rect.bottom;
660
- const spaceBelow = window.innerHeight - rect.bottom - MENU_GAP - VIEWPORT_MARGIN;
661
- const spaceAbove = rect.top - MENU_GAP - VIEWPORT_MARGIN;
662
- const placeAbove = spaceBelow < Math.min(MENU_MAX_HEIGHT, 160) && spaceAbove > spaceBelow;
663
- const maxHeight = Math.max(96, Math.min(MENU_MAX_HEIGHT, placeAbove ? spaceAbove : spaceBelow));
664
- const next = { minWidth: rect.width, maxHeight };
665
- if (window.innerWidth - rect.left < POPUP_MAX_WIDTH) {
666
- next.right = Math.max(VIEWPORT_MARGIN, window.innerWidth - rect.right);
667
- } else {
668
- next.left = Math.max(VIEWPORT_MARGIN, rect.left);
669
- }
670
- if (placeAbove) next.bottom = window.innerHeight - rect.top + MENU_GAP;
671
- else next.top = rect.bottom + MENU_GAP;
672
- setPos(next);
673
- };
674
- compute();
675
- let raf = requestAnimationFrame(function tick() {
676
- const rect = triggerRef.current?.getBoundingClientRect();
677
- if (rect && (rect.left !== lastLeft || rect.top !== lastTop || rect.right !== lastRight || rect.bottom !== lastBottom)) {
678
- compute();
679
- }
680
- raf = requestAnimationFrame(tick);
681
- });
682
- window.addEventListener("scroll", compute, true);
683
- window.addEventListener("resize", compute);
684
- return () => {
685
- cancelAnimationFrame(raf);
686
- window.removeEventListener("scroll", compute, true);
687
- window.removeEventListener("resize", compute);
688
- };
689
- }, [open, triggerRef]);
690
- return pos;
691
- }
692
- function ClearButton({ onClear, ariaLabel = "Clear selection" }) {
693
- return /* @__PURE__ */ jsx(
694
- "button",
695
- {
696
- type: "button",
697
- "aria-label": ariaLabel,
698
- onMouseDown: (e) => {
699
- e.preventDefault();
700
- e.stopPropagation();
701
- onClear();
702
- },
703
- className: "absolute right-2 top-1/2 -translate-y-1/2 text-gray-400 hover:text-red-500 opacity-0 group-hover:opacity-100 transition-opacity text-base leading-none",
704
- children: "\xD7"
705
- }
706
- );
707
- }
708
- function SearchableSelect({
709
- value,
710
- onChange,
711
- options,
712
- placeholder,
713
- emptyOptionLabel = "\u2014 None \u2014",
714
- className = "",
715
- disabled,
716
- id,
717
- allowFreeText = false,
718
- onSearchChange,
719
- rightAdornment
720
- }) {
721
- const [open, setOpen] = useState(false);
722
- const [search, setSearchState] = useState("");
723
- const setSearch = (next) => {
724
- setSearchState(next);
725
- onSearchChange?.(next);
726
- };
727
- const wrapRef = useRef(null);
728
- const triggerRef = useRef(null);
729
- const menuRef = useRef(null);
730
- const adornRef = useRef(null);
731
- const [adornPad, setAdornPad] = useState(null);
732
- useLayoutEffect(() => {
733
- if (!rightAdornment || open) {
734
- setAdornPad(null);
735
- return;
736
- }
737
- const w = adornRef.current?.offsetWidth ?? 0;
738
- setAdornPad(w ? w + (value && !disabled ? 32 : 8) + 6 : null);
739
- });
740
- const menuPos = useDropdownPosition(triggerRef, open);
741
- const selectedLabel = useMemo(() => {
742
- const match = options.find((o) => o.value === value);
743
- if (match) return match.label;
744
- return allowFreeText ? value : "";
745
- }, [options, value, allowFreeText]);
746
- useEffect(() => {
747
- if (!open) return;
748
- const handler = (e) => {
749
- const t = e.target;
750
- if (wrapRef.current?.contains(t) || menuRef.current?.contains(t)) return;
751
- if (allowFreeText && search.trim() && search.trim() !== value) {
752
- onChange(search.trim());
753
- }
754
- setOpen(false);
755
- setSearch("");
756
- };
757
- document.addEventListener("mousedown", handler);
758
- return () => document.removeEventListener("mousedown", handler);
759
- }, [open, allowFreeText, search, value, onChange]);
760
- const dedupedOptions = useMemo(() => {
761
- const seen = /* @__PURE__ */ new Set();
762
- const out = [];
763
- for (const o of options) {
764
- if (seen.has(o.value)) continue;
765
- seen.add(o.value);
766
- out.push(o);
767
- }
768
- return out;
769
- }, [options]);
770
- const filtered = useMemo(() => {
771
- if (onSearchChange) return dedupedOptions;
772
- const q = search.trim().toLowerCase();
773
- if (!q) return dedupedOptions;
774
- return dedupedOptions.filter(
775
- (o) => o.label.toLowerCase().includes(q) || (o.sublabel?.toLowerCase().includes(q) ?? false)
776
- );
777
- }, [dedupedOptions, search, onSearchChange]);
778
- return /* @__PURE__ */ jsxs("div", { ref: wrapRef, className: "relative group", children: [
779
- /* @__PURE__ */ jsx(
780
- "input",
781
- {
782
- id,
783
- ref: triggerRef,
784
- type: "text",
785
- autoComplete: "off",
786
- role: "combobox",
787
- "aria-expanded": open,
788
- value: open ? search : selectedLabel,
789
- onChange: (e) => {
790
- setSearch(e.target.value);
791
- setOpen(true);
792
- },
793
- onFocus: () => {
794
- if (!disabled) {
795
- setSearch("");
796
- setOpen(true);
797
- }
798
- },
799
- onKeyDown: (e) => {
800
- if (e.key === "Enter") {
801
- e.preventDefault();
802
- if (filtered.length === 1) {
803
- onChange(filtered[0].value);
804
- setOpen(false);
805
- setSearch("");
806
- } else if (allowFreeText && search.trim()) {
807
- onChange(search.trim());
808
- setOpen(false);
809
- setSearch("");
810
- triggerRef.current?.blur();
811
- }
812
- } else if (e.key === "Escape") {
813
- setOpen(false);
814
- setSearch("");
815
- triggerRef.current?.blur();
816
- } else if (e.key === "Tab") {
817
- if (allowFreeText && search.trim() && search.trim() !== value) {
818
- onChange(search.trim());
819
- }
820
- setOpen(false);
821
- setSearch("");
822
- }
823
- },
824
- placeholder: placeholder || (emptyOptionLabel || "Select\u2026"),
825
- className: `${INPUT_BASE} ${className} ${value ? "pr-8" : ""} ${disabled ? "bg-gray-50 text-gray-400 cursor-not-allowed" : ""} truncate`,
826
- style: adornPad ? { paddingRight: adornPad } : void 0,
827
- disabled
828
- }
829
- ),
830
- value && !disabled && /* @__PURE__ */ jsx(ClearButton, { onClear: () => {
831
- onChange("");
832
- setOpen(false);
833
- setSearch("");
834
- } }),
835
- rightAdornment && !open && /* @__PURE__ */ jsx("div", { ref: adornRef, className: `absolute top-1/2 -translate-y-1/2 ${value && !disabled ? "right-8" : "right-2"} flex items-center gap-1 flex-nowrap justify-end pointer-events-none`, children: rightAdornment }),
836
- open && createPortal(
837
- /* @__PURE__ */ jsx(
838
- "div",
839
- {
840
- ref: menuRef,
841
- className: "fixed z-[400] rounded-2xl overflow-hidden",
842
- style: {
843
- left: menuPos?.left,
844
- right: menuPos?.right,
845
- top: menuPos?.top,
846
- bottom: menuPos?.bottom,
847
- minWidth: menuPos?.minWidth,
848
- maxWidth: POPUP_MAX_WIDTH,
849
- width: "max-content",
850
- // Hidden for the first paint until the layout effect measures the
851
- // trigger, so the menu never flashes at (0,0).
852
- visibility: menuPos ? void 0 : "hidden",
853
- ...glassStyle()
854
- },
855
- children: /* @__PURE__ */ jsx("div", { className: "overflow-y-auto", style: { maxHeight: menuPos?.maxHeight ?? MENU_MAX_HEIGHT }, children: filtered.length === 0 ? /* @__PURE__ */ jsx("p", { className: "px-3 py-3 text-sm text-gray-400 text-center", children: "No matches" }) : filtered.map((o) => /* @__PURE__ */ jsxs(
856
- "button",
857
- {
858
- type: "button",
859
- onMouseDown: () => {
860
- onChange(o.value);
861
- setOpen(false);
862
- setSearch("");
863
- },
864
- className: `w-full text-left px-3 py-1.5 text-sm hover:bg-gray-50 flex items-center justify-between gap-2 whitespace-nowrap ${value === o.value ? "text-blue-600 font-medium bg-blue-50/50" : "text-gray-700"}`,
865
- children: [
866
- /* @__PURE__ */ jsx("span", { children: o.label }),
867
- o.sublabel && /* @__PURE__ */ jsx("span", { className: "text-xs text-gray-400 shrink-0", children: o.sublabel })
868
- ]
869
- },
870
- o.value
871
- )) })
872
- }
873
- ),
874
- document.body
875
- )
876
- ] });
877
- }
878
- function TopNav({ items, activeKey, onSelect, brand, actions, className }) {
879
- return /* @__PURE__ */ jsxs(
880
- "div",
881
- {
882
- className: `flex h-12 shrink-0 items-stretch gap-1 border-b border-gray-200 bg-white px-3 ${className ?? ""}`,
883
- children: [
884
- brand && /* @__PURE__ */ jsx("div", { className: "mr-1 flex items-center gap-2 pr-3 font-semibold text-gray-900", children: brand }),
885
- /* @__PURE__ */ jsx("nav", { className: "flex items-stretch gap-0.5", children: items.map((item) => {
886
- const active = item.key === activeKey;
887
- return /* @__PURE__ */ jsxs(
888
- "button",
889
- {
890
- type: "button",
891
- disabled: item.disabled,
892
- "aria-current": active ? "page" : void 0,
893
- onClick: () => !item.disabled && onSelect?.(item.key),
894
- className: `relative inline-flex items-center gap-1.5 px-3 text-sm font-medium transition-colors disabled:cursor-not-allowed disabled:opacity-40 ${active ? "text-blue-600" : "text-gray-600 hover:text-gray-900"}`,
895
- children: [
896
- item.icon && /* @__PURE__ */ jsx("span", { className: "h-4 w-4 shrink-0", children: item.icon }),
897
- /* @__PURE__ */ jsx("span", { className: "whitespace-nowrap", children: item.label }),
898
- item.badge != null && /* @__PURE__ */ jsx("span", { className: "ml-0.5 inline-flex min-w-[1.25rem] items-center justify-center rounded-full bg-gray-100 px-1.5 text-[11px] font-semibold leading-5 text-gray-600", children: item.badge }),
899
- active && /* @__PURE__ */ jsx("span", { className: "absolute inset-x-2 -bottom-px h-0.5 rounded-full bg-blue-600", "aria-hidden": true })
900
- ]
901
- },
902
- item.key
903
- );
904
- }) }),
905
- actions && /* @__PURE__ */ jsx("div", { className: "ml-auto flex items-center gap-2", children: actions })
906
- ]
907
- }
908
- );
909
- }
910
-
911
- // src/utils/date.ts
912
- var MONTH_ABBR = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
913
- var MONTH_FULL = [
914
- "January",
915
- "February",
916
- "March",
917
- "April",
918
- "May",
919
- "June",
920
- "July",
921
- "August",
922
- "September",
923
- "October",
924
- "November",
925
- "December"
926
- ];
927
- function monthAbbr(mm) {
928
- return MONTH_ABBR[parseInt(mm, 10) - 1] ?? mm;
929
- }
930
- function monthFull(mm) {
931
- return MONTH_FULL[parseInt(mm, 10) - 1] ?? mm;
932
- }
933
- function getUserDateFormat() {
934
- return localStorage.getItem("user_date_format") || "DD/MM/YYYY";
935
- }
936
- function formatDate(value) {
937
- if (!value) return "\u2014";
938
- const dateStr = value.includes("T") ? value.split("T")[0] : value;
939
- const [y, m, d] = dateStr.split("-").map(Number);
940
- if (!y || !m || !d) return value;
941
- const dd = String(d).padStart(2, "0");
942
- const mm = String(m).padStart(2, "0");
943
- const yyyy = String(y);
944
- const fmt = getUserDateFormat();
945
- switch (fmt) {
946
- case "MM/DD/YYYY":
947
- return `${mm}/${dd}/${yyyy}`;
948
- case "YYYY-MM-DD":
949
- return `${yyyy}-${mm}-${dd}`;
950
- case "DD-MM-YYYY":
951
- return `${dd}-${mm}-${yyyy}`;
952
- case "MM-DD-YYYY":
953
- return `${mm}-${dd}-${yyyy}`;
954
- case "DD.MM.YYYY":
955
- return `${dd}.${mm}.${yyyy}`;
956
- case "YYYY.MM.DD":
957
- return `${yyyy}.${mm}.${dd}`;
958
- case "YYYY/MM/DD":
959
- return `${yyyy}/${mm}/${dd}`;
960
- case "DD/MM/YY":
961
- return `${dd}/${mm}/${yyyy.slice(-2)}`;
962
- case "MM/DD/YY":
963
- return `${mm}/${dd}/${yyyy.slice(-2)}`;
964
- case "DD/MMM/YYYY":
965
- return `${dd}/${monthAbbr(mm)}/${yyyy}`;
966
- case "DD/MMM/YY":
967
- return `${dd}/${monthAbbr(mm)}/${yyyy.slice(-2)}`;
968
- case "DD MMM YYYY":
969
- return `${dd} ${monthAbbr(mm)} ${yyyy}`;
970
- case "MMM DD, YYYY":
971
- return `${monthAbbr(mm)} ${dd}, ${yyyy}`;
972
- case "DD MMMM YYYY":
973
- return `${dd} ${monthFull(mm)} ${yyyy}`;
974
- case "MMMM DD, YYYY":
975
- return `${monthFull(mm)} ${dd}, ${yyyy}`;
976
- case "DD/MM/YYYY":
977
- default:
978
- return `${dd}/${mm}/${yyyy}`;
979
- }
980
- }
981
289
  function timeAgo(dateStr) {
982
290
  const diff = Date.now() - new Date(dateStr).getTime();
983
291
  const mins = Math.floor(diff / 6e4);
@@ -1201,31 +509,6 @@ function NotificationBell({
1201
509
  )
1202
510
  ] });
1203
511
  }
1204
- var GROUP_COLORS = {
1205
- success: "bg-green-100 text-green-800",
1206
- active: "bg-blue-100 text-blue-800",
1207
- queued: "bg-indigo-100 text-indigo-800",
1208
- info: "bg-sky-100 text-sky-800",
1209
- pending: "bg-yellow-100 text-yellow-800",
1210
- warning: "bg-orange-100 text-orange-800",
1211
- danger: "bg-red-100 text-red-800",
1212
- draft: "bg-gray-300 text-gray-800",
1213
- neutral: "bg-gray-100 text-gray-800"
1214
- };
1215
- var StatusGroupsContext = createContext({});
1216
- function StatusBadgeProvider({
1217
- groups,
1218
- children
1219
- }) {
1220
- return /* @__PURE__ */ jsx(StatusGroupsContext.Provider, { value: groups, children });
1221
- }
1222
- function StatusBadge({ status }) {
1223
- const groups = useContext(StatusGroupsContext);
1224
- const group2 = groups[status] ?? "neutral";
1225
- const color = GROUP_COLORS[group2];
1226
- const label = status.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
1227
- return /* @__PURE__ */ jsx("span", { className: `inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium ${color}`, children: label });
1228
- }
1229
512
 
1230
513
  // src/utils/env.ts
1231
514
  var DEV_BANNER_TEXT = "LOCAL DEV ENVIRONMENT \u2014 running against your local backend; not connected to the live system";
@@ -3318,130 +2601,6 @@ function Desktop({ profile }) {
3318
2601
  }
3319
2602
  );
3320
2603
  }
3321
- function subscribeMediaQuery(cb) {
3322
- const mq = window.matchMedia("(prefers-color-scheme: dark)");
3323
- mq.addEventListener("change", cb);
3324
- return () => mq.removeEventListener("change", cb);
3325
- }
3326
- function getSystemIsDark() {
3327
- return window.matchMedia("(prefers-color-scheme: dark)").matches;
3328
- }
3329
- function hexToHsl(hex) {
3330
- const r = parseInt(hex.slice(1, 3), 16) / 255;
3331
- const g = parseInt(hex.slice(3, 5), 16) / 255;
3332
- const b = parseInt(hex.slice(5, 7), 16) / 255;
3333
- const max = Math.max(r, g, b), min = Math.min(r, g, b);
3334
- let h = 0, s = 0;
3335
- const l = (max + min) / 2;
3336
- if (max !== min) {
3337
- const d = max - min;
3338
- s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
3339
- if (max === r) h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
3340
- else if (max === g) h = ((b - r) / d + 2) / 6;
3341
- else h = ((r - g) / d + 4) / 6;
3342
- }
3343
- return [h * 360, s * 100, l * 100];
3344
- }
3345
- function hslToHex(h, s, l) {
3346
- s /= 100;
3347
- l /= 100;
3348
- const a = s * Math.min(l, 1 - l);
3349
- const f = (n) => {
3350
- const k = (n + h / 30) % 12;
3351
- return l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
3352
- };
3353
- const toHex = (x) => Math.round(x * 255).toString(16).padStart(2, "0");
3354
- return `#${toHex(f(0))}${toHex(f(8))}${toHex(f(4))}`;
3355
- }
3356
- function resolveTheme(theme) {
3357
- const t = theme || "system";
3358
- if (t === "system") return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
3359
- return t;
3360
- }
3361
- function applyAccent(accentColor) {
3362
- const root = document.documentElement;
3363
- if (accentColor && /^#[0-9a-fA-F]{6}$/.test(accentColor)) {
3364
- const [h, s, l] = hexToHsl(accentColor);
3365
- root.setAttribute("data-custom-accent", "true");
3366
- root.style.setProperty("--accent-600", accentColor);
3367
- root.style.setProperty("--accent-700", hslToHex(h, s, Math.max(l - 10, 5)));
3368
- root.style.setProperty("--accent-500", hslToHex(h, s, Math.min(l + 10, 95)));
3369
- root.style.setProperty("--accent-400", hslToHex(h, s, Math.min(l + 20, 95)));
3370
- root.style.setProperty("--accent-300", hslToHex(h, Math.min(s + 10, 100), Math.min(l + 30, 92)));
3371
- root.style.setProperty("--accent-200", hslToHex(h, Math.min(s + 15, 100), Math.min(l + 38, 94)));
3372
- root.style.setProperty("--accent-100", hslToHex(h, Math.min(s + 20, 100), Math.min(l + 42, 96)));
3373
- root.style.setProperty("--accent-50", hslToHex(h, Math.min(s + 20, 100), Math.min(l + 46, 98)));
3374
- } else {
3375
- root.removeAttribute("data-custom-accent");
3376
- ["--accent-600", "--accent-700", "--accent-500", "--accent-400", "--accent-300", "--accent-200", "--accent-100", "--accent-50"].forEach((p) => root.style.removeProperty(p));
3377
- }
3378
- }
3379
- function applyCustomColors(accentColor, customBgColor, customTitleColor, customWindowColor, customButtonColor) {
3380
- const root = document.documentElement;
3381
- if (accentColor) {
3382
- const hexToRgb = (hex) => {
3383
- const r = parseInt(hex.slice(1, 3), 16);
3384
- const g = parseInt(hex.slice(3, 5), 16);
3385
- const b = parseInt(hex.slice(5, 7), 16);
3386
- return `${r} ${g} ${b}`;
3387
- };
3388
- if (customBgColor) root.style.setProperty("--custom-bg-color", customBgColor);
3389
- if (customTitleColor) {
3390
- root.style.setProperty("--window-header-rgb", hexToRgb(customTitleColor));
3391
- root.style.setProperty("--window-footer-rgb", hexToRgb(customTitleColor));
3392
- }
3393
- if (customWindowColor) root.style.setProperty("--window-content-rgb", hexToRgb(customWindowColor));
3394
- if (customButtonColor) {
3395
- root.style.setProperty("--custom-button-color", customButtonColor);
3396
- const [h, s, l] = hexToHsl(customButtonColor);
3397
- root.style.setProperty("--custom-button-hover", hslToHex(h, s, Math.max(l - 10, 5)));
3398
- }
3399
- } else {
3400
- ["--custom-bg-color", "--custom-button-color", "--custom-button-hover"].forEach((p) => root.style.removeProperty(p));
3401
- }
3402
- }
3403
- function applyThemePrefs(p) {
3404
- document.documentElement.setAttribute("data-theme", resolveTheme(p.theme));
3405
- applyAccent(p.accent_color ?? null);
3406
- applyCustomColors(
3407
- p.accent_color ?? null,
3408
- p.custom_bg_color ?? null,
3409
- p.custom_title_color ?? null,
3410
- p.custom_window_color ?? null,
3411
- p.custom_button_color ?? null
3412
- );
3413
- }
3414
- function useTheme() {
3415
- const { prefs } = useShellPrefs();
3416
- const systemDark = useSyncExternalStore(subscribeMediaQuery, getSystemIsDark);
3417
- const saved = prefs.theme || "system";
3418
- const accentColor = prefs.accent_color || null;
3419
- const customBgColor = prefs.custom_bg_color || null;
3420
- const customTitleColor = prefs.custom_title_color || null;
3421
- const customWindowColor = prefs.custom_window_color || null;
3422
- const customButtonColor = prefs.custom_button_color || null;
3423
- const resolved = saved === "system" ? systemDark ? "dark" : "light" : saved;
3424
- useEffect(() => {
3425
- document.documentElement.setAttribute("data-theme", resolved);
3426
- return () => document.documentElement.removeAttribute("data-theme");
3427
- }, [resolved]);
3428
- useEffect(() => {
3429
- applyAccent(accentColor);
3430
- }, [accentColor]);
3431
- useEffect(() => {
3432
- applyCustomColors(accentColor, customBgColor, customTitleColor, customWindowColor, customButtonColor);
3433
- }, [accentColor, customBgColor, customTitleColor, customWindowColor, customButtonColor]);
3434
- return { theme: saved, resolved };
3435
- }
3436
- function useClickOutside(ref, onClose) {
3437
- useEffect(() => {
3438
- const handler = (e) => {
3439
- if (ref.current && !ref.current.contains(e.target)) onClose();
3440
- };
3441
- document.addEventListener("mousedown", handler);
3442
- return () => document.removeEventListener("mousedown", handler);
3443
- }, [ref, onClose]);
3444
- }
3445
2604
  function StartupAnimation({ onComplete, ready = false, productName = "react-os-shell", subtitle }) {
3446
2605
  const [phase, setPhase] = useState("logo");
3447
2606
  const [minTimePassed, setMinTimePassed] = useState(false);
@@ -4134,7 +3293,7 @@ function MobileAppLanding({
4134
3293
  }
4135
3294
  );
4136
3295
  }
4137
- var Sidebar = lazy(() => import('./Sidebar-IMACKSHS.js'));
3296
+ var Sidebar = lazy(() => import('./Sidebar-B3LY5T2U.js'));
4138
3297
  function useFavorites(wallpapers) {
4139
3298
  const { prefs, save } = useShellPrefs();
4140
3299
  const favorites = prefs.favorite_pages || [];
@@ -4202,10 +3361,10 @@ function TaskbarClock({ clockCalendar }) {
4202
3361
  })()
4203
3362
  ] });
4204
3363
  }
4205
- var toISODate = (d) => `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
3364
+ var toISODate2 = (d) => `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
4206
3365
  function CalendarPopup({ now, config, close }) {
4207
3366
  const [month, setMonth] = useState(() => new Date(now.getFullYear(), now.getMonth(), 1));
4208
- const [selected, setSelected] = useState(() => toISODate(now));
3367
+ const [selected, setSelected] = useState(() => toISODate2(now));
4209
3368
  const interactive = !!config?.renderDay;
4210
3369
  const markedSet = useMemo(() => new Set(config?.markedDates ?? []), [config?.markedDates]);
4211
3370
  const monthLabel = month.toLocaleDateString(void 0, { month: "long", year: "numeric" });
@@ -4245,7 +3404,7 @@ function CalendarPopup({ now, config, close }) {
4245
3404
  /* @__PURE__ */ jsx("div", { className: "grid grid-cols-7 mb-1 text-[10px] font-semibold text-gray-400 text-center uppercase tracking-wide", children: ["S", "M", "T", "W", "T", "F", "S"].map((d, i) => /* @__PURE__ */ jsx("div", { className: "py-1", children: d }, i)) }),
4246
3405
  /* @__PURE__ */ jsx("div", { className: "grid grid-cols-7 gap-px text-center", children: cells.map((cell, i) => {
4247
3406
  const todayCell = cell.thisMonth && isToday(cell.date);
4248
- const iso = toISODate(cell.date);
3407
+ const iso = toISODate2(cell.date);
4249
3408
  const isSelected = interactive && selected === iso;
4250
3409
  const look = isSelected ? "bg-blue-500 text-white font-semibold" : todayCell ? interactive ? "text-blue-600 font-bold hover:bg-gray-100" : "bg-blue-500 text-white font-semibold" : cell.thisMonth ? "text-gray-700 hover:bg-gray-100" : interactive ? "text-gray-300 hover:bg-gray-100" : "text-gray-300";
4251
3410
  const dayClass = `relative text-[12px] py-1.5 rounded-md transition-colors ${look}`;
@@ -6058,85 +5217,6 @@ function ResizableTable({
6058
5217
  ] })
6059
5218
  ] });
6060
5219
  }
6061
- function ListFooter({
6062
- selectedCount,
6063
- loadedCount,
6064
- totalCount,
6065
- label,
6066
- isFetchingMore,
6067
- extra
6068
- }) {
6069
- const total = totalCount ?? loadedCount;
6070
- const allLoaded = loadedCount >= total;
6071
- return /* @__PURE__ */ jsxs(Fragment, { children: [
6072
- extra,
6073
- selectedCount > 0 ? `${selectedCount} selected \u2014 ` : "",
6074
- allLoaded ? totalCount != null ? `All ${total.toLocaleString()} ${label} loaded` : `${total.toLocaleString()} ${label}` : `${loadedCount.toLocaleString()} of ${total.toLocaleString()} ${label}`,
6075
- isFetchingMore ? " \xB7 Loading more..." : ""
6076
- ] });
6077
- }
6078
- 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";
6079
- var VARIANTS2 = {
6080
- primary: "bg-blue-600 text-white shadow-sm hover:bg-blue-700 focus:ring-blue-400/40",
6081
- secondary: "border border-gray-300 bg-white text-gray-700 shadow-sm hover:bg-gray-50 focus:ring-blue-400/30",
6082
- ghost: "text-gray-700 hover:bg-gray-100 focus:ring-blue-400/30",
6083
- danger: "bg-red-600 text-white shadow-sm hover:bg-red-700 focus:ring-red-400/40"
6084
- };
6085
- var SIZES = {
6086
- sm: "gap-1 px-2.5 py-1 text-xs",
6087
- md: "gap-1.5 px-3 py-1.5 text-sm"
6088
- };
6089
- function Spinner() {
6090
- return /* @__PURE__ */ jsxs("svg", { className: "h-3.5 w-3.5 animate-spin", viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: [
6091
- /* @__PURE__ */ jsx("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }),
6092
- /* @__PURE__ */ jsx("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z" })
6093
- ] });
6094
- }
6095
- var Button = forwardRef(function Button2({
6096
- variant = "primary",
6097
- size = "md",
6098
- loading = false,
6099
- leftIcon,
6100
- rightIcon,
6101
- block = false,
6102
- disabled,
6103
- type = "button",
6104
- children,
6105
- className = "",
6106
- ...rest
6107
- }, ref) {
6108
- return /* @__PURE__ */ jsxs(
6109
- "button",
6110
- {
6111
- ref,
6112
- type,
6113
- disabled: disabled || loading,
6114
- "aria-busy": loading || void 0,
6115
- className: [BASE, VARIANTS2[variant], SIZES[size], block ? "w-full" : "", className].filter(Boolean).join(" "),
6116
- ...rest,
6117
- children: [
6118
- loading ? /* @__PURE__ */ jsx(Spinner, {}) : leftIcon,
6119
- children,
6120
- !loading && rightIcon
6121
- ]
6122
- }
6123
- );
6124
- });
6125
- var Button_default = Button;
6126
- function ListLoadError({
6127
- title = "Couldn't load this list",
6128
- message = "Something went wrong fetching these records. Check your connection and try again.",
6129
- onRetry,
6130
- variant = "dashed"
6131
- }) {
6132
- const frame = variant === "card" ? "rounded-md border border-gray-200 bg-white px-6 py-12" : variant === "none" ? "px-6 py-12" : "rounded-lg border-2 border-dashed border-red-200 px-6 py-12";
6133
- return /* @__PURE__ */ jsxs("div", { className: `flex flex-col items-center justify-center text-center ${frame}`, role: "alert", children: [
6134
- /* @__PURE__ */ jsx("svg", { className: "h-10 w-10 text-red-300 mb-3", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1, "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M12 9v3.75m0 3.75h.008M10.34 3.94l-8.4 14.55A1.5 1.5 0 003.24 21h17.52a1.5 1.5 0 001.3-2.51l-8.4-14.55a1.5 1.5 0 00-2.6 0z" }) }),
6135
- /* @__PURE__ */ jsx("h3", { className: "text-sm font-medium text-gray-900", children: title }),
6136
- /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-500 mt-2", children: message }),
6137
- onRetry && /* @__PURE__ */ jsx("div", { className: "mt-4", children: /* @__PURE__ */ jsx(Button_default, { variant: "secondary", size: "sm", onClick: onRetry, children: "Try again" }) })
6138
- ] });
6139
- }
6140
5220
  function EntityList(props) {
6141
5221
  const {
6142
5222
  items,
@@ -6343,198 +5423,6 @@ function EntityList(props) {
6343
5423
  ] })
6344
5424
  ] });
6345
5425
  }
6346
- function Kanban({
6347
- items,
6348
- columns,
6349
- columnOf,
6350
- getId,
6351
- onMove,
6352
- renderCard,
6353
- onCardClick,
6354
- sortInColumn,
6355
- isLoading = false,
6356
- loadingText = "Loading\u2026",
6357
- emptyState,
6358
- showColumnsWhenEmpty = false,
6359
- columnEmptyText = "Drop here",
6360
- onAddItem,
6361
- addItemText = "Add item"
6362
- }) {
6363
- const [dragId, setDragId] = useState(null);
6364
- const [over, setOver] = useState(null);
6365
- const grouped = useMemo(() => {
6366
- const map = {};
6367
- for (const c of columns) map[c.value] = [];
6368
- for (const it of items) (map[columnOf(it)] ??= []).push(it);
6369
- if (sortInColumn) for (const k of Object.keys(map)) map[k].sort(sortInColumn);
6370
- return map;
6371
- }, [items, columns, columnOf, sortInColumn]);
6372
- const reset = () => {
6373
- setDragId(null);
6374
- setOver(null);
6375
- };
6376
- const boardRef = useRef(null);
6377
- const prevRects = useRef(/* @__PURE__ */ new Map());
6378
- useLayoutEffect(() => {
6379
- if (dragId !== null) return;
6380
- const board = boardRef.current;
6381
- if (!board) return;
6382
- const next = /* @__PURE__ */ new Map();
6383
- const moved = [];
6384
- board.querySelectorAll("[data-kanban-card]").forEach((el) => {
6385
- const id = el.dataset.kanbanCard;
6386
- const rect = el.getBoundingClientRect();
6387
- next.set(id, rect);
6388
- const prev = prevRects.current.get(id);
6389
- if (prev) {
6390
- const dx = prev.left - rect.left;
6391
- const dy = prev.top - rect.top;
6392
- if (dx || dy) moved.push([el, dx, dy]);
6393
- }
6394
- });
6395
- prevRects.current = next;
6396
- if (!moved.length) return;
6397
- for (const [el, dx, dy] of moved) {
6398
- el.style.transition = "none";
6399
- el.style.transform = `translate(${dx}px, ${dy}px)`;
6400
- }
6401
- requestAnimationFrame(() => {
6402
- for (const [el] of moved) {
6403
- el.style.transition = "transform 200ms cubic-bezier(0.2, 0, 0, 1)";
6404
- el.style.transform = "";
6405
- el.addEventListener(
6406
- "transitionend",
6407
- () => {
6408
- el.style.transition = "";
6409
- el.style.transform = "";
6410
- },
6411
- { once: true }
6412
- );
6413
- }
6414
- });
6415
- }, [grouped, dragId]);
6416
- const commitMove = (col) => {
6417
- if (dragId && over && over.col === col) {
6418
- const colItems = grouped[col] ?? [];
6419
- const dp = colItems.findIndex((it) => getId(it) === dragId);
6420
- const sameCol = dp !== -1;
6421
- const noop = sameCol && (over.index === dp || over.index === dp + 1);
6422
- if (!noop) {
6423
- const toIndex = sameCol && over.index > dp ? over.index - 1 : over.index;
6424
- onMove(dragId, col, toIndex);
6425
- }
6426
- }
6427
- reset();
6428
- };
6429
- if (isLoading) return /* @__PURE__ */ jsx("div", { className: "text-sm text-gray-500 p-4", children: loadingText });
6430
- if (items.length === 0 && !showColumnsWhenEmpty) {
6431
- return /* @__PURE__ */ jsx(Fragment, { children: emptyState ?? /* @__PURE__ */ jsx("div", { className: "text-sm text-gray-500 p-4", children: "No items." }) });
6432
- }
6433
- return /* @__PURE__ */ jsx("div", { ref: boardRef, className: "flex-1 overflow-x-auto grid-scroll", children: /* @__PURE__ */ jsx("div", { className: "flex gap-3 h-full min-w-max pb-2", children: columns.map((col) => {
6434
- const colItems = grouped[col.value] ?? [];
6435
- const isOver = over !== null && over.col === col.value;
6436
- const dp = dragId !== null ? colItems.findIndex((it) => getId(it) === dragId) : -1;
6437
- const lineAt = isOver && !(dp !== -1 && (over.index === dp || over.index === dp + 1)) ? over.index : -1;
6438
- return /* @__PURE__ */ jsxs(
6439
- "div",
6440
- {
6441
- className: `group flex flex-col w-72 shrink-0 rounded-xl bg-gray-50 border transition-colors ${isOver ? "border-blue-400 ring-2 ring-blue-300/60" : "border-gray-200"}`,
6442
- onDragOver: (e) => {
6443
- e.preventDefault();
6444
- try {
6445
- e.dataTransfer.dropEffect = "move";
6446
- } catch {
6447
- }
6448
- },
6449
- onDrop: (e) => {
6450
- e.preventDefault();
6451
- commitMove(col.value);
6452
- },
6453
- children: [
6454
- /* @__PURE__ */ jsxs(
6455
- "div",
6456
- {
6457
- className: `flex items-center justify-between px-3 py-2 rounded-t-xl text-sm font-medium ${col.accent ?? "text-gray-700 bg-gray-100"}`,
6458
- children: [
6459
- /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
6460
- /* @__PURE__ */ jsx("span", { className: `h-2 w-2 rounded-full ${col.dot ?? "bg-gray-400"}` }),
6461
- col.label
6462
- ] }),
6463
- /* @__PURE__ */ jsx("span", { className: "text-xs opacity-70", children: colItems.length })
6464
- ]
6465
- }
6466
- ),
6467
- /* @__PURE__ */ jsxs(
6468
- "div",
6469
- {
6470
- className: "flex-1 overflow-y-auto p-2 space-y-2 min-h-[120px]",
6471
- onDragEnter: () => setOver(
6472
- (prev) => prev && prev.col === col.value && prev.index === colItems.length ? prev : { col: col.value, index: colItems.length }
6473
- ),
6474
- children: [
6475
- colItems.map((item, index) => {
6476
- const id = getId(item);
6477
- const isDragged = id === dragId;
6478
- return /* @__PURE__ */ jsxs(Fragment$1, { children: [
6479
- lineAt === index && /* @__PURE__ */ jsx("div", { className: "h-0.5 rounded-full bg-blue-500", "aria-hidden": true }),
6480
- /* @__PURE__ */ jsx(
6481
- "div",
6482
- {
6483
- "data-kanban-card": id,
6484
- draggable: true,
6485
- onDragStart: (e) => {
6486
- setDragId(id);
6487
- try {
6488
- e.dataTransfer.effectAllowed = "move";
6489
- } catch {
6490
- }
6491
- },
6492
- onDragEnter: (e) => {
6493
- e.stopPropagation();
6494
- const draggedPos = dragId !== null ? colItems.findIndex((it) => getId(it) === dragId) : -1;
6495
- const target = draggedPos !== -1 && draggedPos < index ? index + 1 : index;
6496
- setOver(
6497
- (prev) => prev && prev.col === col.value && prev.index === target ? prev : { col: col.value, index: target }
6498
- );
6499
- },
6500
- onDragEnd: reset,
6501
- onClick: onCardClick ? () => onCardClick(item) : void 0,
6502
- style: dragId === null ? void 0 : { opacity: isDragged ? 0.4 : 1, transition: "opacity 120ms ease" },
6503
- className: `rounded-lg bg-white border border-gray-200 p-3 shadow-sm hover:border-blue-400 hover:shadow transition ${onCardClick ? "cursor-pointer" : ""}`,
6504
- children: renderCard(item)
6505
- }
6506
- )
6507
- ] }, id);
6508
- }),
6509
- colItems.length > 0 && lineAt === colItems.length && /* @__PURE__ */ jsx("div", { className: "h-0.5 rounded-full bg-blue-500", "aria-hidden": true }),
6510
- colItems.length === 0 && /* @__PURE__ */ jsx(
6511
- "div",
6512
- {
6513
- className: `text-[11px] text-center rounded-lg transition-all duration-150 ${isOver ? "border-2 border-dashed border-blue-300 bg-blue-50/50 text-blue-400 py-8" : "text-gray-400 py-6"}`,
6514
- children: columnEmptyText
6515
- }
6516
- )
6517
- ]
6518
- }
6519
- ),
6520
- onAddItem && /* @__PURE__ */ jsx("div", { className: "p-2 pt-0", children: /* @__PURE__ */ jsxs(
6521
- "button",
6522
- {
6523
- type: "button",
6524
- onClick: () => onAddItem(col.value),
6525
- className: "flex w-full items-center justify-center gap-1 rounded-lg px-2 py-1.5 text-xs font-medium text-gray-500 opacity-0 transition hover:bg-gray-100 hover:text-gray-700 focus-visible:opacity-100 group-hover:opacity-100",
6526
- children: [
6527
- /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "text-sm leading-none", children: "+" }),
6528
- addItemText
6529
- ]
6530
- }
6531
- ) })
6532
- ]
6533
- },
6534
- col.value
6535
- );
6536
- }) }) });
6537
- }
6538
5426
  function useInfiniteScroll({
6539
5427
  queryKey,
6540
5428
  fetchFn,
@@ -6667,191 +5555,40 @@ function useSort(defaultField, defaultDir = "asc", tableId) {
6667
5555
  const ordering = sort.direction === "desc" ? `-${sort.field}` : sort.field;
6668
5556
  return { sort, onSort, ordering };
6669
5557
  }
6670
- function buildPages(page, pageCount, sibling) {
6671
- const wanted = /* @__PURE__ */ new Set([1, pageCount]);
6672
- for (let i = page - sibling; i <= page + sibling; i++) {
6673
- if (i >= 1 && i <= pageCount) wanted.add(i);
6674
- }
6675
- const sorted = [...wanted].sort((a, b) => a - b);
6676
- const out = [];
6677
- let prev = 0;
6678
- for (const p of sorted) {
6679
- if (prev && p - prev > 1) out.push("\u2026");
6680
- out.push(p);
6681
- prev = p;
6682
- }
6683
- return out;
6684
- }
6685
- var BTN = "inline-flex h-8 min-w-8 items-center justify-center rounded-md px-2 text-sm transition-colors disabled:cursor-not-allowed disabled:opacity-40";
6686
- function Pagination({
6687
- page,
6688
- pageCount,
6689
- onPageChange,
6690
- siblingCount = 1,
6691
- showEdges = false,
6692
- className = ""
6693
- }) {
6694
- if (pageCount <= 1) return null;
6695
- const pages = buildPages(page, pageCount, siblingCount);
6696
- const go = (p) => {
6697
- if (p >= 1 && p <= pageCount && p !== page) onPageChange(p);
6698
- };
6699
- return /* @__PURE__ */ jsxs("nav", { "aria-label": "Pagination", className: `flex items-center gap-1 ${className}`.trim(), children: [
6700
- showEdges && /* @__PURE__ */ jsx("button", { type: "button", "aria-label": "First page", disabled: page === 1, onClick: () => go(1), className: `${BTN} text-gray-600 hover:bg-gray-100`, children: "\xAB" }),
6701
- /* @__PURE__ */ jsx("button", { type: "button", "aria-label": "Previous page", disabled: page === 1, onClick: () => go(page - 1), className: `${BTN} text-gray-600 hover:bg-gray-100`, children: "\u2039" }),
6702
- pages.map(
6703
- (p, i) => p === "\u2026" ? /* @__PURE__ */ jsx("span", { className: "px-1 text-gray-400", children: "\u2026" }, `gap-${i}`) : /* @__PURE__ */ jsx(
6704
- "button",
5558
+ function FilterBar({ filters, values, onChange, onClear, children }) {
5559
+ const activeCount = Object.values(values).filter(Boolean).length;
5560
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 flex-wrap", children: [
5561
+ filters.map((f) => {
5562
+ const isSearchable = f.searchable ?? f.options.length > 8;
5563
+ return isSearchable ? /* @__PURE__ */ jsx(
5564
+ SearchableFilter,
6705
5565
  {
6706
- type: "button",
6707
- "aria-current": p === page ? "page" : void 0,
6708
- onClick: () => go(p),
6709
- className: `${BTN} ${p === page ? "bg-blue-600 font-medium text-white" : "text-gray-700 hover:bg-gray-100"}`,
6710
- children: p
5566
+ filter: f,
5567
+ value: values[f.field] || "",
5568
+ onChange: (v) => onChange(f.field, v)
6711
5569
  },
6712
- p
6713
- )
6714
- ),
6715
- /* @__PURE__ */ jsx("button", { type: "button", "aria-label": "Next page", disabled: page === pageCount, onClick: () => go(page + 1), className: `${BTN} text-gray-600 hover:bg-gray-100`, children: "\u203A" }),
6716
- showEdges && /* @__PURE__ */ jsx("button", { type: "button", "aria-label": "Last page", disabled: page === pageCount, onClick: () => go(pageCount), className: `${BTN} text-gray-600 hover:bg-gray-100`, children: "\xBB" })
6717
- ] });
6718
- }
6719
- var Input = forwardRef(function Input2({ invalid, leftIcon, rightAdornment, className = "", ...rest }, ref) {
6720
- const pad2 = `${leftIcon ? "pl-9" : ""} ${rightAdornment ? "pr-9" : ""}`.trim();
6721
- const field = /* @__PURE__ */ jsx(
6722
- "input",
6723
- {
6724
- ref,
6725
- className: inputClasses({ invalid, className: [pad2, className].filter(Boolean).join(" ") }),
6726
- ...rest
6727
- }
6728
- );
6729
- if (!leftIcon && !rightAdornment) return field;
6730
- return /* @__PURE__ */ jsxs("div", { className: "relative", children: [
6731
- leftIcon && /* @__PURE__ */ jsx("span", { className: "pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-gray-400", children: leftIcon }),
6732
- field,
6733
- rightAdornment && /* @__PURE__ */ jsx("span", { className: "absolute right-3 top-1/2 -translate-y-1/2 text-gray-400", children: rightAdornment })
5570
+ f.field
5571
+ ) : /* @__PURE__ */ jsx(
5572
+ PlainFilter,
5573
+ {
5574
+ filter: f,
5575
+ value: values[f.field] || "",
5576
+ onChange: (v) => onChange(f.field, v)
5577
+ },
5578
+ f.field
5579
+ );
5580
+ }),
5581
+ children,
5582
+ activeCount > 0 && /* @__PURE__ */ jsxs("button", { onClick: onClear, className: "flex items-center gap-1 text-sm text-gray-500 hover:text-gray-700", children: [
5583
+ /* @__PURE__ */ jsx(XMarkIcon, { className: "h-4 w-4" }),
5584
+ "Clear filters"
5585
+ ] })
6734
5586
  ] });
6735
- });
6736
- var Input_default = Input;
6737
- var Textarea = forwardRef(function Textarea2({ invalid, autoGrow, className = "", onInput, rows = 3, ...rest }, ref) {
6738
- const handleInput = useCallback((e) => {
6739
- if (autoGrow) {
6740
- const el = e.currentTarget;
6741
- el.style.height = "auto";
6742
- el.style.height = `${el.scrollHeight}px`;
6743
- }
6744
- onInput?.(e);
6745
- }, [autoGrow, onInput]);
6746
- return /* @__PURE__ */ jsx(
6747
- "textarea",
6748
- {
6749
- ref,
6750
- rows,
6751
- onInput: handleInput,
6752
- className: inputClasses({ invalid, className: `${autoGrow ? "resize-none overflow-hidden" : ""} ${className}`.trim() }),
6753
- ...rest
6754
- }
6755
- );
6756
- });
6757
- var Textarea_default = Textarea;
6758
-
6759
- // src/forms/selectNav.ts
6760
- function firstEnabledIndex(options) {
6761
- for (let i = 0; i < options.length; i++) {
6762
- if (!options[i].disabled) return i;
6763
- }
6764
- return -1;
6765
- }
6766
- function lastEnabledIndex(options) {
6767
- for (let i = options.length - 1; i >= 0; i--) {
6768
- if (!options[i].disabled) return i;
6769
- }
6770
- return -1;
6771
- }
6772
- function nextEnabledIndex(options, from, dir) {
6773
- let i = from + dir;
6774
- while (i >= 0 && i < options.length) {
6775
- if (!options[i].disabled) return i;
6776
- i += dir;
6777
- }
6778
- if (from >= 0 && from < options.length && !options[from].disabled) return from;
6779
- return dir > 0 ? firstEnabledIndex(options) : lastEnabledIndex(options);
6780
- }
6781
- function matchTypeahead(options, buffer, fromIndex) {
6782
- const q = buffer.toLowerCase();
6783
- const n = options.length;
6784
- if (!q || n === 0) return -1;
6785
- for (let step = 1; step <= n; step++) {
6786
- const i = ((fromIndex + step) % n + n) % n;
6787
- const o = options[i];
6788
- if (!o.disabled && o.label.toLowerCase().startsWith(q)) return i;
6789
- }
6790
- return -1;
6791
- }
6792
- var MENU_GAP2 = 4;
6793
- var VIEWPORT_MARGIN2 = 8;
6794
- var MENU_MAX_HEIGHT2 = 240;
6795
- var NativeSelect = forwardRef(function NativeSelect2({ value, onChange, options, placeholder, invalid, className = "", ...rest }, ref) {
6796
- return /* @__PURE__ */ jsxs(
6797
- "select",
6798
- {
6799
- ref,
6800
- value,
6801
- onChange: (e) => onChange(e.target.value),
6802
- className: inputClasses({ invalid, className: `pr-8 ${className}`.trim() }),
6803
- ...rest,
6804
- children: [
6805
- placeholder !== void 0 && /* @__PURE__ */ jsx("option", { value: "", disabled: true, children: placeholder }),
6806
- options.map((o) => /* @__PURE__ */ jsx("option", { value: o.value, disabled: o.disabled, children: o.label }, o.value))
6807
- ]
6808
- }
6809
- );
6810
- });
6811
- function useAnchoredPosition(triggerRef, open) {
6812
- const [pos, setPos] = useState(null);
6813
- useLayoutEffect(() => {
6814
- if (!open) {
6815
- setPos(null);
6816
- return;
6817
- }
6818
- let lastLeft = NaN, lastTop = NaN, lastBottom = NaN;
6819
- const compute = () => {
6820
- const rect = triggerRef.current?.getBoundingClientRect();
6821
- if (!rect) return;
6822
- lastLeft = rect.left;
6823
- lastTop = rect.top;
6824
- lastBottom = rect.bottom;
6825
- const spaceBelow = window.innerHeight - rect.bottom - MENU_GAP2 - VIEWPORT_MARGIN2;
6826
- const spaceAbove = rect.top - MENU_GAP2 - VIEWPORT_MARGIN2;
6827
- const placeAbove = spaceBelow < Math.min(MENU_MAX_HEIGHT2, 160) && spaceAbove > spaceBelow;
6828
- const maxHeight = Math.max(96, Math.min(MENU_MAX_HEIGHT2, placeAbove ? spaceAbove : spaceBelow));
6829
- const left = Math.max(VIEWPORT_MARGIN2, Math.min(rect.left, window.innerWidth - rect.width - VIEWPORT_MARGIN2));
6830
- const next = { left, minWidth: rect.width, maxHeight };
6831
- if (placeAbove) next.bottom = window.innerHeight - rect.top + MENU_GAP2;
6832
- else next.top = rect.bottom + MENU_GAP2;
6833
- setPos(next);
6834
- };
6835
- compute();
6836
- let raf = requestAnimationFrame(function tick() {
6837
- const rect = triggerRef.current?.getBoundingClientRect();
6838
- if (rect && (rect.left !== lastLeft || rect.top !== lastTop || rect.bottom !== lastBottom)) compute();
6839
- raf = requestAnimationFrame(tick);
6840
- });
6841
- window.addEventListener("scroll", compute, true);
6842
- window.addEventListener("resize", compute);
6843
- return () => {
6844
- cancelAnimationFrame(raf);
6845
- window.removeEventListener("scroll", compute, true);
6846
- window.removeEventListener("resize", compute);
6847
- };
6848
- }, [open, triggerRef]);
6849
- return pos;
6850
5587
  }
6851
- var ListboxSelect = forwardRef(function ListboxSelect2({ value, onChange, options, placeholder, invalid, className = "", id, disabled, ...rest }, ref) {
5588
+ function PlainFilter({ filter, value, onChange }) {
6852
5589
  const [open, setOpen] = useState(false);
6853
5590
  const [active, setActive] = useState(-1);
6854
- const wrapRef = useRef(null);
5591
+ const ref = useRef(null);
6855
5592
  const triggerRef = useRef(null);
6856
5593
  const listRef = useRef(null);
6857
5594
  const openRef = useRef(open);
@@ -6860,49 +5597,40 @@ var ListboxSelect = forwardRef(function ListboxSelect2({ value, onChange, option
6860
5597
  const baseId = useId();
6861
5598
  const listboxId = `${baseId}-listbox`;
6862
5599
  const optionId = (i) => `${baseId}-opt-${i}`;
6863
- const menuPos = useAnchoredPosition(triggerRef, open);
6864
- const selectedIndex = options.findIndex((o) => o.value === value);
6865
- const selectedLabel = selectedIndex >= 0 ? options[selectedIndex].label : void 0;
5600
+ const items = [{ value: "", label: filter.label }, ...filter.options];
5601
+ const selectedIndex = value ? items.findIndex((o) => o.value === value) : 0;
5602
+ const selectedLabel = value ? filter.options.find((o) => o.value === value)?.label : null;
6866
5603
  const close = useCallback((refocus = true) => {
6867
5604
  setOpen(false);
6868
5605
  setActive(-1);
6869
5606
  if (refocus) triggerRef.current?.focus();
6870
5607
  }, []);
6871
5608
  const openList = useCallback(() => {
6872
- if (disabled) return;
6873
- setActive(selectedIndex >= 0 ? selectedIndex : firstEnabledIndex(options));
5609
+ setActive(selectedIndex >= 0 ? selectedIndex : firstEnabledIndex(items));
6874
5610
  setOpen(true);
6875
- }, [disabled, options, selectedIndex]);
5611
+ }, [selectedIndex, items]);
6876
5612
  const commit = useCallback((i) => {
6877
- const o = options[i];
6878
- if (!o || o.disabled) return;
5613
+ const o = items[i];
5614
+ if (!o) return;
6879
5615
  onChange(o.value);
6880
5616
  close();
6881
- }, [options, onChange, close]);
5617
+ }, [items, onChange, close]);
5618
+ useClickOutside(ref, useCallback(() => {
5619
+ if (open) close(false);
5620
+ }, [open, close]));
6882
5621
  useEffect(() => {
6883
5622
  if (!open) return;
6884
- const unregister = registerModalEscapeInterceptor((e) => {
5623
+ return registerModalEscapeInterceptor((e) => {
6885
5624
  if (e.key !== "Escape" || !openRef.current) return false;
6886
5625
  close();
6887
5626
  return true;
6888
5627
  });
6889
- return unregister;
6890
- }, [open, close]);
6891
- useEffect(() => {
6892
- if (!open) return;
6893
- const handler = (e) => {
6894
- const t = e.target;
6895
- if (wrapRef.current?.contains(t) || listRef.current?.contains(t)) return;
6896
- close(false);
6897
- };
6898
- document.addEventListener("pointerdown", handler);
6899
- return () => document.removeEventListener("pointerdown", handler);
6900
5628
  }, [open, close]);
6901
5629
  useEffect(() => {
6902
5630
  if (!open || active < 0) return;
6903
5631
  listRef.current?.querySelector(`#${CSS.escape(optionId(active))}`)?.scrollIntoView({ block: "nearest" });
6904
5632
  });
6905
- const runTypeahead = useCallback((char) => {
5633
+ const runTypeahead = (char) => {
6906
5634
  const ta = typeahead.current;
6907
5635
  if (ta.timer) clearTimeout(ta.timer);
6908
5636
  ta.buffer += char;
@@ -6910,35 +5638,33 @@ var ListboxSelect = forwardRef(function ListboxSelect2({ value, onChange, option
6910
5638
  ta.buffer = "";
6911
5639
  ta.timer = null;
6912
5640
  }, 500);
6913
- const from = open ? active : selectedIndex;
6914
- const hit = matchTypeahead(options, ta.buffer, from);
5641
+ const hit = matchTypeahead(items, ta.buffer, open ? active : selectedIndex);
6915
5642
  if (hit < 0) return;
6916
5643
  if (open) setActive(hit);
6917
5644
  else commit(hit);
6918
- }, [open, active, selectedIndex, options, commit]);
5645
+ };
6919
5646
  const onKeyDown = (e) => {
6920
- if (disabled) return;
6921
5647
  switch (e.key) {
6922
5648
  case "ArrowDown":
6923
5649
  e.preventDefault();
6924
5650
  if (!open) openList();
6925
- else setActive((a) => nextEnabledIndex(options, a, 1));
5651
+ else setActive((a) => nextEnabledIndex(items, a, 1));
6926
5652
  return;
6927
5653
  case "ArrowUp":
6928
5654
  e.preventDefault();
6929
5655
  if (!open) openList();
6930
- else setActive((a) => nextEnabledIndex(options, a, -1));
5656
+ else setActive((a) => nextEnabledIndex(items, a, -1));
6931
5657
  return;
6932
5658
  case "Home":
6933
5659
  if (open) {
6934
5660
  e.preventDefault();
6935
- setActive(firstEnabledIndex(options));
5661
+ setActive(firstEnabledIndex(items));
6936
5662
  }
6937
5663
  return;
6938
5664
  case "End":
6939
5665
  if (open) {
6940
5666
  e.preventDefault();
6941
- setActive(lastEnabledIndex(options));
5667
+ setActive(lastEnabledIndex(items));
6942
5668
  }
6943
5669
  return;
6944
5670
  case "Enter":
@@ -6969,1521 +5695,84 @@ var ListboxSelect = forwardRef(function ListboxSelect2({ value, onChange, option
6969
5695
  }
6970
5696
  }
6971
5697
  };
6972
- return /* @__PURE__ */ jsxs("div", { ref: wrapRef, className: "relative", children: [
6973
- /* @__PURE__ */ jsx(
5698
+ return /* @__PURE__ */ jsxs("div", { ref, className: "relative", children: [
5699
+ /* @__PURE__ */ jsxs(
6974
5700
  "button",
6975
5701
  {
6976
5702
  ref: triggerRef,
6977
- id,
6978
5703
  type: "button",
6979
5704
  role: "combobox",
6980
5705
  "aria-haspopup": "listbox",
6981
5706
  "aria-expanded": open,
6982
5707
  "aria-controls": open ? listboxId : void 0,
6983
5708
  "aria-activedescendant": open && active >= 0 ? optionId(active) : void 0,
6984
- "aria-invalid": invalid || void 0,
6985
- disabled,
6986
5709
  onClick: () => open ? close() : openList(),
6987
5710
  onKeyDown,
6988
- className: inputClasses({ invalid, className: `pr-8 text-left ${className}`.trim() }),
6989
- children: /* @__PURE__ */ jsx("span", { className: `block truncate ${selectedLabel === void 0 ? "text-gray-400" : ""}`, children: selectedLabel ?? placeholder ?? "\xA0" })
5711
+ className: `flex items-center gap-1.5 border rounded-lg px-2.5 py-1.5 text-sm transition-colors ${value ? "border-blue-400 bg-blue-50 text-blue-700" : "border-gray-300 text-gray-600 hover:border-gray-400"}`,
5712
+ children: [
5713
+ /* @__PURE__ */ jsx("span", { className: "truncate max-w-[140px]", children: selectedLabel || filter.label }),
5714
+ value ? (
5715
+ // A span, not a button: this lives INSIDE the combobox <button>, and
5716
+ // `<button>` inside `<button>` is invalid HTML — React logs "cannot
5717
+ // be a descendant of" and warns of a hydration error. It stays out of
5718
+ // the a11y tree because clearing is already a first-class keyboard
5719
+ // action: index 0 of the listbox is the "All" entry (value ''). A
5720
+ // nested control here would only pad the combobox's accessible name
5721
+ // with an affordance Tab can't reach.
5722
+ /* @__PURE__ */ jsx(
5723
+ "span",
5724
+ {
5725
+ "aria-hidden": "true",
5726
+ onClick: (e) => {
5727
+ e.stopPropagation();
5728
+ onChange("");
5729
+ },
5730
+ className: "cursor-pointer text-blue-400 hover:text-blue-600",
5731
+ children: /* @__PURE__ */ jsx(XMarkIcon, { className: "h-3.5 w-3.5" })
5732
+ }
5733
+ )
5734
+ ) : /* @__PURE__ */ jsx("svg", { className: `h-3.5 w-3.5 text-gray-400 transition-transform ${open ? "rotate-180" : ""}`, fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M19.5 8.25l-7.5 7.5-7.5-7.5" }) })
5735
+ ]
6990
5736
  }
6991
5737
  ),
6992
- /* @__PURE__ */ jsx(
6993
- "svg",
5738
+ open && /* @__PURE__ */ jsx("div", { ref: listRef, id: listboxId, role: "listbox", className: "absolute top-full left-0 mt-1 w-56 rounded-2xl z-[400] max-h-52 overflow-y-auto py-1", style: glassStyle(), children: items.map((o, i) => /* @__PURE__ */ jsx(
5739
+ "div",
6994
5740
  {
6995
- "aria-hidden": "true",
6996
- className: "pointer-events-none absolute right-2.5 top-1/2 h-4 w-4 -translate-y-1/2 text-gray-400",
6997
- fill: "none",
6998
- viewBox: "0 0 24 24",
6999
- stroke: "currentColor",
7000
- strokeWidth: 2,
7001
- children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M8.25 9l3.75-3.75L15.75 9M8.25 15l3.75 3.75L15.75 15" })
7002
- }
7003
- ),
5741
+ id: optionId(i),
5742
+ role: "option",
5743
+ "aria-selected": i === selectedIndex,
5744
+ onPointerEnter: () => setActive(i),
5745
+ onClick: () => commit(i),
5746
+ className: `mx-1 cursor-pointer truncate rounded-lg px-3 py-1.5 text-sm
5747
+ ${i === active ? "bg-blue-50 text-blue-700" : i === selectedIndex ? "font-medium text-blue-600" : "text-gray-700"}`,
5748
+ children: o.label
5749
+ },
5750
+ o.value || "__all__"
5751
+ )) })
5752
+ ] });
5753
+ }
5754
+ function SearchableFilter({ filter, value, onChange }) {
5755
+ const [open, setOpen] = useState(false);
5756
+ const [search, setSearch] = useState("");
5757
+ const ref = useRef(null);
5758
+ const inputRef = useRef(null);
5759
+ const selectedLabel = value ? filter.options.find((o) => o.value === value)?.label : null;
5760
+ useClickOutside(ref, useCallback(() => {
5761
+ if (open) {
5762
+ setOpen(false);
5763
+ setSearch("");
5764
+ }
5765
+ }, [open]));
5766
+ useEffect(() => {
5767
+ if (open) setTimeout(() => inputRef.current?.focus(), 50);
5768
+ }, [open]);
5769
+ const filtered = search ? filter.options.filter((o) => o.label.toLowerCase().includes(search.toLowerCase())) : filter.options;
5770
+ return /* @__PURE__ */ jsxs("div", { ref, className: "relative", children: [
7004
5771
  /* @__PURE__ */ jsxs(
7005
- "select",
5772
+ "button",
7006
5773
  {
7007
- ref,
7008
- value,
7009
- onChange: (e) => onChange(e.target.value),
7010
- disabled,
7011
- "aria-hidden": "true",
7012
- tabIndex: -1,
7013
- className: "sr-only",
7014
- ...rest,
7015
- children: [
7016
- placeholder !== void 0 && /* @__PURE__ */ jsx("option", { value: "", disabled: true, children: placeholder }),
7017
- options.map((o) => /* @__PURE__ */ jsx("option", { value: o.value, disabled: o.disabled, children: o.label }, o.value))
7018
- ]
7019
- }
7020
- ),
7021
- open && createPortal(
7022
- /* @__PURE__ */ jsx(
7023
- "div",
7024
- {
7025
- ref: listRef,
7026
- id: listboxId,
7027
- role: "listbox",
7028
- className: "fixed z-[400] overflow-y-auto rounded-2xl py-1",
7029
- style: {
7030
- left: menuPos?.left,
7031
- top: menuPos?.top,
7032
- bottom: menuPos?.bottom,
7033
- minWidth: menuPos?.minWidth,
7034
- maxHeight: menuPos?.maxHeight ?? MENU_MAX_HEIGHT2,
7035
- visibility: menuPos ? void 0 : "hidden",
7036
- ...glassStyle()
7037
- },
7038
- children: options.map((o, i) => /* @__PURE__ */ jsx(
7039
- "div",
7040
- {
7041
- id: optionId(i),
7042
- role: "option",
7043
- "aria-selected": value === o.value,
7044
- "aria-disabled": o.disabled || void 0,
7045
- onPointerEnter: () => {
7046
- if (!o.disabled) setActive(i);
7047
- },
7048
- onClick: () => commit(i),
7049
- className: `mx-1 cursor-pointer truncate rounded-lg px-3 py-1.5 text-sm
7050
- ${o.disabled ? "cursor-not-allowed text-gray-400" : i === active ? "bg-blue-50 text-blue-700" : value === o.value ? "font-medium text-blue-600" : "text-gray-700"}`,
7051
- children: o.label
7052
- },
7053
- o.value
7054
- ))
7055
- }
7056
- ),
7057
- document.body
7058
- )
7059
- ] });
7060
- });
7061
- var Select = forwardRef(function Select2(props, ref) {
7062
- const isMobile = useIsMobile();
7063
- return isMobile ? /* @__PURE__ */ jsx(NativeSelect, { ref, ...props }) : /* @__PURE__ */ jsx(ListboxSelect, { ref, ...props });
7064
- });
7065
- var Select_default = Select;
7066
- 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";
7067
- var Checkbox = forwardRef(function Checkbox2({ checked, onChange, label, description, disabled, className = "", ...rest }, ref) {
7068
- if (!label && !description) {
7069
- return /* @__PURE__ */ jsx(
7070
- "input",
7071
- {
7072
- ref,
7073
- type: "checkbox",
7074
- checked,
7075
- disabled,
7076
- onChange: (e) => onChange(e.target.checked),
7077
- className: `${BOX} ${className}`.trim(),
7078
- ...rest
7079
- }
7080
- );
7081
- }
7082
- return /* @__PURE__ */ jsxs("label", { className: `flex items-start gap-2 ${disabled ? "cursor-not-allowed opacity-60" : "cursor-pointer"} ${className}`.trim(), children: [
7083
- /* @__PURE__ */ jsx(
7084
- "input",
7085
- {
7086
- ref,
7087
- type: "checkbox",
7088
- checked,
7089
- disabled,
7090
- onChange: (e) => onChange(e.target.checked),
7091
- className: `${BOX} mt-0.5`,
7092
- ...rest
7093
- }
7094
- ),
7095
- /* @__PURE__ */ jsxs("span", { className: "text-sm leading-tight", children: [
7096
- label && /* @__PURE__ */ jsx("span", { className: "font-medium text-gray-700", children: label }),
7097
- description && /* @__PURE__ */ jsx("span", { className: "mt-0.5 block text-xs text-gray-500", children: description })
7098
- ] })
7099
- ] });
7100
- });
7101
- var Checkbox_default = Checkbox;
7102
- var DOT = "h-4 w-4 shrink-0 border-gray-300 accent-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-400/30 disabled:cursor-not-allowed disabled:opacity-60";
7103
- var Radio = forwardRef(function Radio2({ checked, onChange, label, description, disabled, className = "", ...rest }, ref) {
7104
- if (!label && !description) {
7105
- return /* @__PURE__ */ jsx(
7106
- "input",
7107
- {
7108
- ref,
7109
- type: "radio",
7110
- checked,
7111
- disabled,
7112
- onChange: (e) => onChange(e.target.checked),
7113
- className: `${DOT} ${className}`.trim(),
7114
- ...rest
7115
- }
7116
- );
7117
- }
7118
- return /* @__PURE__ */ jsxs("label", { className: `flex items-start gap-2 ${disabled ? "cursor-not-allowed opacity-60" : "cursor-pointer"} ${className}`.trim(), children: [
7119
- /* @__PURE__ */ jsx(
7120
- "input",
7121
- {
7122
- ref,
7123
- type: "radio",
7124
- checked,
7125
- disabled,
7126
- onChange: (e) => onChange(e.target.checked),
7127
- className: `${DOT} mt-0.5`,
7128
- ...rest
7129
- }
7130
- ),
7131
- /* @__PURE__ */ jsxs("span", { className: "text-sm leading-tight", children: [
7132
- label && /* @__PURE__ */ jsx("span", { className: "font-medium text-gray-700", children: label }),
7133
- description && /* @__PURE__ */ jsx("span", { className: "mt-0.5 block text-xs text-gray-500", children: description })
7134
- ] })
7135
- ] });
7136
- });
7137
- var Radio_default = Radio;
7138
- function FormField({
7139
- label,
7140
- htmlFor,
7141
- labelId,
7142
- hint,
7143
- error,
7144
- required,
7145
- className = "",
7146
- children
7147
- }) {
7148
- return /* @__PURE__ */ jsxs("div", { className, children: [
7149
- label && /* @__PURE__ */ jsxs("label", { id: labelId, htmlFor, className: "mb-1 block text-xs font-medium text-gray-600", children: [
7150
- label,
7151
- required && /* @__PURE__ */ jsx("span", { className: "ml-0.5 text-red-500", children: "*" })
7152
- ] }),
7153
- children,
7154
- error ? /* @__PURE__ */ jsx("p", { id: htmlFor ? `${htmlFor}-error` : void 0, className: "mt-1 text-[11px] text-red-600", children: error }) : hint ? /* @__PURE__ */ jsx("p", { id: htmlFor ? `${htmlFor}-hint` : void 0, className: "mt-1 text-[11px] text-gray-400", children: hint }) : null
7155
- ] });
7156
- }
7157
- function Label({ required, children, className = "", ...rest }) {
7158
- return /* @__PURE__ */ jsxs("label", { className: `mb-1 block text-xs font-medium text-gray-600 ${className}`.trim(), ...rest, children: [
7159
- children,
7160
- required && /* @__PURE__ */ jsx("span", { className: "ml-0.5 text-red-500", children: "*" })
7161
- ] });
7162
- }
7163
- var DATA_URL_RE = /^data:([a-z0-9!#$&^_.+-]+)\/([a-z0-9!#$&^_.+-]+)/i;
7164
- function dataUrlName(url) {
7165
- const m = DATA_URL_RE.exec(url);
7166
- if (!m) return "media";
7167
- const [, type, subtype] = m;
7168
- const ext = subtype.split("+")[0].split(".").pop().replace(/^x-/i, "");
7169
- return ext ? `${type.toLowerCase()}.${ext.toLowerCase()}` : "media";
7170
- }
7171
- function mediaFileName(url) {
7172
- if (/^data:/i.test(url || "")) return dataUrlName(url);
7173
- const path = (url || "").split("?")[0].split("#")[0];
7174
- const last = path.substring(path.lastIndexOf("/") + 1);
7175
- try {
7176
- return decodeURIComponent(last.replace(/^[0-9a-f]{32}_/i, "")) || last || "media";
7177
- } catch {
7178
- return last || "media";
7179
- }
7180
- }
7181
- var VIDEO_EXT_RE = /\.(mp4|webm|ogg|ogv|mov|m4v)(\?|#|$)/i;
7182
- function isVideoUrl(url, accept = "") {
7183
- if (!url) return false;
7184
- const data = DATA_URL_RE.exec(url);
7185
- if (data) return data[1].toLowerCase() === "video";
7186
- const acceptsVideo = accept.includes("video");
7187
- const acceptsImage = accept.includes("image") || accept === "*" || accept === "";
7188
- return VIDEO_EXT_RE.test(url) || acceptsVideo && !acceptsImage;
7189
- }
7190
- function Spinner2({ className = "h-5 w-5" }) {
7191
- return /* @__PURE__ */ jsxs("svg", { className: `${className} animate-spin text-gray-400`, viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: [
7192
- /* @__PURE__ */ jsx("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }),
7193
- /* @__PURE__ */ jsx("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z" })
7194
- ] });
7195
- }
7196
- function UploadGlyph({ className = "h-6 w-6" }) {
7197
- return /* @__PURE__ */ jsxs(
7198
- "svg",
7199
- {
7200
- viewBox: "0 0 24 24",
7201
- fill: "none",
7202
- stroke: "currentColor",
7203
- strokeWidth: 1.6,
7204
- strokeLinecap: "round",
7205
- strokeLinejoin: "round",
7206
- "aria-hidden": "true",
7207
- className,
7208
- children: [
7209
- /* @__PURE__ */ jsx("path", { d: "M12 15V4" }),
7210
- /* @__PURE__ */ jsx("path", { d: "M8 8l4-4 4 4" }),
7211
- /* @__PURE__ */ jsx("path", { d: "M4 15v3a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-3" })
7212
- ]
7213
- }
7214
- );
7215
- }
7216
- function MediaUploadField({
7217
- value,
7218
- onChange,
7219
- onPick,
7220
- label,
7221
- hint,
7222
- error,
7223
- required,
7224
- className,
7225
- accept = "image/*",
7226
- fit = "cover",
7227
- height = 112,
7228
- placeholder,
7229
- cta = "Choose from library or upload",
7230
- acceptHint,
7231
- showFilename = true,
7232
- allowReplace = true,
7233
- allowRemove = true,
7234
- replaceLabel = "Replace",
7235
- removeLabel = "Remove",
7236
- busy = false,
7237
- busyLabel = "Uploading\u2026",
7238
- disabled = false
7239
- }) {
7240
- const inputRef = useRef(null);
7241
- const [dragOver, setDragOver] = useState(false);
7242
- const [focused, setFocused] = useState(false);
7243
- const fieldId = useId();
7244
- const objectUrlRef = useRef(null);
7245
- const [videoBlobUrl, setVideoBlobUrl] = useState(null);
7246
- useEffect(() => () => {
7247
- if (objectUrlRef.current) URL.revokeObjectURL(objectUrlRef.current);
7248
- }, []);
7249
- const locked = disabled || busy;
7250
- const acceptsVideo = accept.includes("video");
7251
- const acceptsImage = accept.includes("image") || accept === "*" || accept === "";
7252
- const isVideo = !!value && (isVideoUrl(value, accept) || value === videoBlobUrl);
7253
- const kindWord = acceptsVideo && !acceptsImage ? "video" : acceptsImage && !acceptsVideo ? "image" : "file";
7254
- const dimLine = placeholder ?? `Upload ${kindWord === "image" ? "an image" : `a ${kindWord}`}`;
7255
- const emitObjectUrl = (file) => {
7256
- if (objectUrlRef.current) URL.revokeObjectURL(objectUrlRef.current);
7257
- const url = URL.createObjectURL(file);
7258
- objectUrlRef.current = url;
7259
- setVideoBlobUrl(file.type.startsWith("video/") ? url : null);
7260
- onChange(url);
7261
- };
7262
- const clearValue = () => {
7263
- if (objectUrlRef.current) {
7264
- URL.revokeObjectURL(objectUrlRef.current);
7265
- objectUrlRef.current = null;
7266
- }
7267
- setVideoBlobUrl(null);
7268
- onChange("");
7269
- };
7270
- const pick = (file) => {
7271
- if (locked) return;
7272
- if (onPick) {
7273
- onPick(file ?? void 0);
7274
- return;
7275
- }
7276
- if (file) {
7277
- emitObjectUrl(file);
7278
- return;
7279
- }
7280
- inputRef.current?.click();
7281
- };
7282
- const onNativeFile = (e) => {
7283
- const file = e.target.files?.[0];
7284
- if (file) emitObjectUrl(file);
7285
- if (inputRef.current) inputRef.current.value = "";
7286
- };
7287
- const dropHandlers = locked ? {} : {
7288
- onDragOver: (e) => {
7289
- e.preventDefault();
7290
- setDragOver(true);
7291
- },
7292
- onDragLeave: () => setDragOver(false),
7293
- onDrop: (e) => {
7294
- e.preventDefault();
7295
- setDragOver(false);
7296
- const file = e.dataTransfer.files?.[0];
7297
- if (file) pick(file);
7298
- }
7299
- };
7300
- const previewName = value ? mediaFileName(value) : "";
7301
- const filled = /* @__PURE__ */ jsxs("div", { children: [
7302
- /* @__PURE__ */ jsxs(
7303
- "div",
7304
- {
7305
- ...dropHandlers,
7306
- className: [
7307
- "relative overflow-hidden rounded-md border bg-gray-100 transition-colors",
7308
- dragOver ? "border-blue-500" : "border-gray-200"
7309
- ].join(" "),
7310
- style: { height },
7311
- children: [
7312
- isVideo ? /* @__PURE__ */ jsx(
7313
- "video",
7314
- {
7315
- src: value ?? void 0,
7316
- muted: true,
7317
- playsInline: true,
7318
- preload: "metadata",
7319
- "aria-label": previewName || void 0,
7320
- className: "h-full w-full",
7321
- style: { objectFit: fit }
7322
- }
7323
- ) : /* @__PURE__ */ jsx("img", { src: value ?? void 0, alt: previewName, className: "h-full w-full", style: { objectFit: fit } }),
7324
- showFilename && value && /* @__PURE__ */ jsx(
7325
- "span",
7326
- {
7327
- className: "absolute inset-x-1 bottom-1 truncate rounded px-1.5 py-0.5 text-xs text-white",
7328
- style: { background: "rgba(0,0,0,0.6)" },
7329
- children: previewName
7330
- }
7331
- ),
7332
- busy && /* @__PURE__ */ jsxs(
7333
- "div",
7334
- {
7335
- role: "status",
7336
- className: "absolute inset-0 flex items-center justify-center gap-2",
7337
- style: { background: "rgba(255,255,255,0.6)" },
7338
- children: [
7339
- /* @__PURE__ */ jsx(Spinner2, {}),
7340
- /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-gray-600", children: busyLabel })
7341
- ]
7342
- }
7343
- )
7344
- ]
7345
- }
7346
- ),
7347
- !disabled && (allowReplace || allowRemove) && /* @__PURE__ */ jsxs("div", { className: "mt-2 flex gap-2", children: [
7348
- allowReplace && /* @__PURE__ */ jsx(Button_default, { id: fieldId, variant: "secondary", size: "sm", onClick: () => pick(), disabled: busy, children: replaceLabel }),
7349
- allowRemove && /* @__PURE__ */ jsx(Button_default, { variant: "ghost", size: "sm", onClick: clearValue, disabled: busy, children: removeLabel })
7350
- ] })
7351
- ] });
7352
- const empty = /* @__PURE__ */ jsx(
7353
- "button",
7354
- {
7355
- type: "button",
7356
- id: fieldId,
7357
- onClick: () => pick(),
7358
- disabled: locked,
7359
- "aria-label": typeof dimLine === "string" ? dimLine : "Upload media",
7360
- "aria-busy": busy || void 0,
7361
- "aria-invalid": error ? true : void 0,
7362
- "aria-required": required || void 0,
7363
- "aria-describedby": error ? `${fieldId}-error` : hint ? `${fieldId}-hint` : void 0,
7364
- onFocus: () => setFocused(true),
7365
- onBlur: () => setFocused(false),
7366
- ...dropHandlers,
7367
- className: [
7368
- "flex w-full flex-col items-center justify-center gap-1.5 rounded-lg border-2 border-dashed text-center transition-colors",
7369
- locked ? "cursor-not-allowed opacity-60" : "cursor-pointer",
7370
- dragOver ? "border-blue-500 bg-blue-50" : "border-gray-300 bg-gray-50 hover:bg-gray-100"
7371
- ].join(" "),
7372
- style: {
7373
- minHeight: height,
7374
- outline: "none",
7375
- boxShadow: focused && !locked ? "0 0 0 2px rgba(59,130,246,0.45)" : void 0
7376
- },
7377
- children: busy ? /* @__PURE__ */ jsxs("span", { role: "status", className: "flex flex-col items-center gap-1.5", children: [
7378
- /* @__PURE__ */ jsx(Spinner2, {}),
7379
- /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-gray-500", children: busyLabel })
7380
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
7381
- /* @__PURE__ */ jsx("span", { className: "text-gray-400", children: /* @__PURE__ */ jsx(UploadGlyph, {}) }),
7382
- /* @__PURE__ */ jsx("span", { className: "px-3 text-xs text-gray-500", children: dimLine }),
7383
- /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-blue-600", children: cta }),
7384
- acceptHint && /* @__PURE__ */ jsx("span", { className: "text-xs text-gray-400", children: acceptHint })
7385
- ] })
7386
- }
7387
- );
7388
- return /* @__PURE__ */ jsxs(FormField, { label, htmlFor: fieldId, hint, error, required, className, children: [
7389
- value ? filled : empty,
7390
- !onPick && /* @__PURE__ */ jsx("input", { ref: inputRef, type: "file", accept, className: "hidden", onChange: onNativeFile })
7391
- ] });
7392
- }
7393
- var REMOVE_ICON = /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", "aria-hidden": "true", className: "h-3 w-3", children: /* @__PURE__ */ jsx("path", { d: "M6 6l12 12M18 6L6 18" }) });
7394
- function MediaUploadGrid({
7395
- items,
7396
- onPick,
7397
- onRemove,
7398
- onReorder,
7399
- label,
7400
- hint,
7401
- error,
7402
- required,
7403
- className,
7404
- accept = "image/*",
7405
- fit = "cover",
7406
- thumbSize = 96,
7407
- placeholder,
7408
- cta = "Choose from library or upload",
7409
- acceptHint,
7410
- showCover = false,
7411
- coverLabel = "Cover",
7412
- busy = false,
7413
- busyLabel = "Uploading\u2026",
7414
- disabled = false,
7415
- readOnly = false
7416
- }) {
7417
- const gridId = useId();
7418
- const [zoneDragOver, setZoneDragOver] = useState(false);
7419
- const [draggingIndex, setDraggingIndex] = useState(null);
7420
- const [focusedKey, setFocusedKey] = useState(null);
7421
- const ringStyle = (key) => ({
7422
- outline: "none",
7423
- ...focusedKey === key && !locked ? { boxShadow: "0 0 0 2px rgba(59,130,246,0.45)" } : {}
7424
- });
7425
- const blurKey = (key) => setFocusedKey((k) => k === key ? null : k);
7426
- const locked = disabled || busy;
7427
- const canAdd = !!onPick && !locked && !readOnly;
7428
- const canRemove = !!onRemove && !locked && !readOnly;
7429
- const reorderEnabled = !!onReorder && !locked && !readOnly;
7430
- const acceptsVideo = accept.includes("video");
7431
- const acceptsImage = accept.includes("image") || accept === "*" || accept === "";
7432
- const kindWord = acceptsVideo && !acceptsImage ? "video" : acceptsImage && !acceptsVideo ? "image" : "file";
7433
- const dimLine = placeholder ?? `Upload ${kindWord === "image" ? "an image" : `a ${kindWord}`}`;
7434
- const pick = (file) => {
7435
- if (!canAdd) return;
7436
- onPick(file ?? void 0);
7437
- };
7438
- const isFileDrag = (e) => Array.from(e.dataTransfer.types || []).includes("Files");
7439
- const zoneHandlers = canAdd ? {
7440
- onDragOver: (e) => {
7441
- if (!isFileDrag(e)) return;
7442
- e.preventDefault();
7443
- setZoneDragOver(true);
7444
- },
7445
- onDragLeave: () => setZoneDragOver(false),
7446
- onDrop: (e) => {
7447
- const file = e.dataTransfer.files?.[0];
7448
- if (!file) return;
7449
- e.preventDefault();
7450
- setZoneDragOver(false);
7451
- pick(file);
7452
- }
7453
- } : {};
7454
- const square = { width: thumbSize, height: thumbSize };
7455
- const thumb = (item, index) => {
7456
- const video = item.kind ? item.kind === "video" : isVideoUrl(item.url, accept);
7457
- const name = mediaFileName(item.url);
7458
- const isCover = showCover && (item.cover ?? index === 0);
7459
- return /* @__PURE__ */ jsxs(
7460
- "div",
7461
- {
7462
- draggable: reorderEnabled || void 0,
7463
- onDragStart: reorderEnabled ? (e) => {
7464
- e.dataTransfer.effectAllowed = "move";
7465
- e.dataTransfer.setData("text/plain", String(index));
7466
- setDraggingIndex(index);
7467
- } : void 0,
7468
- onDragEnd: reorderEnabled ? () => setDraggingIndex(null) : void 0,
7469
- onDragOver: reorderEnabled ? (e) => {
7470
- if (draggingIndex == null) return;
7471
- e.preventDefault();
7472
- e.stopPropagation();
7473
- } : void 0,
7474
- onDrop: reorderEnabled ? (e) => {
7475
- if (draggingIndex == null) return;
7476
- e.preventDefault();
7477
- e.stopPropagation();
7478
- if (draggingIndex !== index) onReorder(draggingIndex, index);
7479
- setDraggingIndex(null);
7480
- } : void 0,
7481
- tabIndex: reorderEnabled ? 0 : void 0,
7482
- "aria-label": reorderEnabled ? `${name}, item ${index + 1} of ${items.length}. Use arrow keys to reorder.` : void 0,
7483
- onKeyDown: reorderEnabled ? (e) => {
7484
- if (e.key === "ArrowLeft" || e.key === "ArrowUp") {
7485
- if (index > 0) {
7486
- e.preventDefault();
7487
- onReorder(index, index - 1);
7488
- }
7489
- } else if (e.key === "ArrowRight" || e.key === "ArrowDown") {
7490
- if (index < items.length - 1) {
7491
- e.preventDefault();
7492
- onReorder(index, index + 1);
7493
- }
7494
- }
7495
- } : void 0,
7496
- onFocus: reorderEnabled ? (e) => {
7497
- if (e.target === e.currentTarget) setFocusedKey(`thumb:${item.id}`);
7498
- } : void 0,
7499
- onBlur: reorderEnabled ? (e) => {
7500
- if (e.target === e.currentTarget) blurKey(`thumb:${item.id}`);
7501
- } : void 0,
7502
- className: [
7503
- "relative shrink-0 overflow-hidden rounded-md border border-gray-200 bg-gray-100",
7504
- draggingIndex === index ? "opacity-50" : ""
7505
- ].join(" "),
7506
- style: { ...square, cursor: reorderEnabled ? "move" : void 0, ...ringStyle(`thumb:${item.id}`) },
7507
- children: [
7508
- video ? /* @__PURE__ */ jsx("video", { src: item.url, muted: true, playsInline: true, preload: "metadata", "aria-label": name, className: "h-full w-full", style: { objectFit: fit } }) : /* @__PURE__ */ jsx("img", { src: item.url, alt: name, className: "h-full w-full", style: { objectFit: fit } }),
7509
- isCover && /* @__PURE__ */ jsx("span", { className: "absolute left-1 top-1 rounded bg-blue-600 px-1.5 py-0.5 text-xs font-medium text-white", children: coverLabel }),
7510
- item.caption != null && /* @__PURE__ */ jsx(
7511
- "span",
7512
- {
7513
- className: "absolute inset-x-1 bottom-1 truncate rounded px-1.5 py-0.5 text-xs text-white",
7514
- style: { background: "rgba(0,0,0,0.6)" },
7515
- children: item.caption
7516
- }
7517
- ),
7518
- canRemove && /* @__PURE__ */ jsx(
7519
- "button",
7520
- {
7521
- type: "button",
7522
- onClick: () => onRemove(item.id),
7523
- "aria-label": `Remove ${name}`,
7524
- onFocus: () => setFocusedKey(`rm:${item.id}`),
7525
- onBlur: () => blurKey(`rm:${item.id}`),
7526
- className: "absolute right-1 top-1 flex h-5 w-5 items-center justify-center rounded-full text-white",
7527
- style: { background: "rgba(0,0,0,0.55)", ...ringStyle(`rm:${item.id}`) },
7528
- children: REMOVE_ICON
7529
- }
7530
- )
7531
- ]
7532
- },
7533
- item.id
7534
- );
7535
- };
7536
- const addTile = canAdd && /* @__PURE__ */ jsxs(
7537
- "button",
7538
- {
7539
- type: "button",
7540
- onClick: () => pick(),
7541
- "aria-label": "Add media",
7542
- onFocus: () => setFocusedKey("add"),
7543
- onBlur: () => blurKey("add"),
7544
- className: "flex shrink-0 flex-col items-center justify-center gap-1 rounded-md border-2 border-dashed border-gray-300 bg-gray-50 text-gray-400 transition-colors hover:bg-gray-100 hover:text-gray-500",
7545
- style: { ...square, ...ringStyle("add") },
7546
- children: [
7547
- /* @__PURE__ */ jsx(UploadGlyph, { className: "h-5 w-5" }),
7548
- /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-gray-500", children: "Add" })
7549
- ]
7550
- }
7551
- );
7552
- const emptyDropzone = canAdd ? /* @__PURE__ */ jsxs(
7553
- "button",
7554
- {
7555
- type: "button",
7556
- onClick: () => pick(),
7557
- "aria-label": typeof dimLine === "string" ? dimLine : "Upload media",
7558
- onFocus: () => setFocusedKey("zone"),
7559
- onBlur: () => blurKey("zone"),
7560
- className: [
7561
- "flex w-full flex-col items-center justify-center gap-1.5 rounded-lg border-2 border-dashed text-center transition-colors cursor-pointer",
7562
- zoneDragOver ? "border-blue-500 bg-blue-50" : "border-gray-300 bg-gray-50 hover:bg-gray-100"
7563
- ].join(" "),
7564
- style: { minHeight: thumbSize + 32, ...ringStyle("zone") },
7565
- children: [
7566
- /* @__PURE__ */ jsx("span", { className: "text-gray-400", children: /* @__PURE__ */ jsx(UploadGlyph, {}) }),
7567
- /* @__PURE__ */ jsx("span", { className: "px-3 text-xs text-gray-500", children: dimLine }),
7568
- /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-blue-600", children: cta }),
7569
- acceptHint && /* @__PURE__ */ jsx("span", { className: "text-xs text-gray-400", children: acceptHint })
7570
- ]
7571
- }
7572
- ) : /* @__PURE__ */ jsx(
7573
- "div",
7574
- {
7575
- className: "flex w-full items-center justify-center rounded-lg border border-gray-200 bg-gray-50 px-3 text-xs text-gray-400",
7576
- style: { minHeight: thumbSize + 32 },
7577
- children: "No media."
7578
- }
7579
- );
7580
- return /* @__PURE__ */ jsx(FormField, { label, labelId: label ? `${gridId}-label` : void 0, htmlFor: gridId, hint, error, required, className, children: /* @__PURE__ */ jsxs(
7581
- "div",
7582
- {
7583
- id: gridId,
7584
- role: "group",
7585
- "aria-labelledby": label ? `${gridId}-label` : void 0,
7586
- "aria-describedby": error ? `${gridId}-error` : hint ? `${gridId}-hint` : void 0,
7587
- "aria-busy": busy || void 0,
7588
- ...zoneHandlers,
7589
- className: "relative rounded-lg transition-colors",
7590
- style: {
7591
- ...locked ? { opacity: 0.6 } : {},
7592
- // Inline box-shadow, not `ring-*` — the kit's shipped stylesheet has no
7593
- // Tailwind ring machinery (see MediaUploadField's focus ring).
7594
- ...zoneDragOver ? { boxShadow: "0 0 0 2px rgba(59,130,246,0.5)" } : {}
7595
- },
7596
- children: [
7597
- items.length === 0 ? emptyDropzone : /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2", children: [
7598
- items.map(thumb),
7599
- addTile
7600
- ] }),
7601
- busy && /* @__PURE__ */ jsxs(
7602
- "div",
7603
- {
7604
- role: "status",
7605
- className: "absolute inset-0 flex items-center justify-center gap-2 rounded-lg",
7606
- style: { background: "rgba(255,255,255,0.6)" },
7607
- children: [
7608
- /* @__PURE__ */ jsx(Spinner2, {}),
7609
- /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-gray-600", children: busyLabel })
7610
- ]
7611
- }
7612
- )
7613
- ]
7614
- }
7615
- ) });
7616
- }
7617
- var PRESET_LABELS = ["Last 2 Weeks", "Last Month", "Last 3 Months", "Last 6 Months", "Last 12 Months"];
7618
- var MONTH_NAMES = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
7619
- var YEARS_PER_PAGE = 12;
7620
- var yearPageStart = (y) => Math.floor(y / YEARS_PER_PAGE) * YEARS_PER_PAGE;
7621
- var pad = (n) => String(n).padStart(2, "0");
7622
- var EDGE_MARGIN = 8;
7623
- function clipBounds(el) {
7624
- for (let node = el.parentElement; node; node = node.parentElement) {
7625
- const style = getComputedStyle(node);
7626
- const overflowX = style.overflowX || style.overflow;
7627
- if (overflowX && overflowX !== "visible") {
7628
- const rect = node.getBoundingClientRect();
7629
- if (rect.width > 0) return { left: rect.left, right: rect.right };
7630
- }
7631
- }
7632
- return { left: 0, right: window.innerWidth };
7633
- }
7634
- function toISODate2(d) {
7635
- return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
7636
- }
7637
- function parseDate(s) {
7638
- const [y, m, d] = s.split("-").map(Number);
7639
- return new Date(y, m - 1, d);
7640
- }
7641
- function defaultFormatDisplay(s) {
7642
- const [y, m, d] = s.split("-");
7643
- return y && m && d ? `${d}/${m}/${y}` : s;
7644
- }
7645
- function DateRangePicker({
7646
- from,
7647
- to,
7648
- onChange,
7649
- formatDisplay = defaultFormatDisplay,
7650
- clearable = true,
7651
- placeholder = "Date Range"
7652
- }) {
7653
- const [open, setOpen] = useState(false);
7654
- const [tempFrom, setTempFrom] = useState(from);
7655
- const [tempTo, setTempTo] = useState(to);
7656
- const [activePreset, setActivePreset] = useState(null);
7657
- const ref = useRef(null);
7658
- const panelRef = useRef(null);
7659
- const [align, setAlign] = useState("left");
7660
- const now = /* @__PURE__ */ new Date();
7661
- const [month, setMonth] = useState(from ? parseDate(from).getMonth() : now.getMonth());
7662
- const [year, setYear] = useState(from ? parseDate(from).getFullYear() : now.getFullYear());
7663
- const [view, setView] = useState("days");
7664
- const [yearPage, setYearPage] = useState(() => yearPageStart(year));
7665
- const displayDate = (s) => s ? formatDisplay(s) : "";
7666
- useClickOutside(ref, useCallback(() => {
7667
- if (open) setOpen(false);
7668
- }, [open]));
7669
- useLayoutEffect(() => {
7670
- if (!open) return;
7671
- const place = () => {
7672
- const anchor = ref.current, panel = panelRef.current;
7673
- if (!anchor || !panel) return;
7674
- const trigger = anchor.getBoundingClientRect();
7675
- const width = panel.getBoundingClientRect().width;
7676
- const bounds = clipBounds(anchor);
7677
- const fitsLeftAligned = trigger.left + width <= bounds.right - EDGE_MARGIN;
7678
- const fitsRightAligned = trigger.right - width >= bounds.left + EDGE_MARGIN;
7679
- setAlign(fitsLeftAligned || !fitsRightAligned ? "left" : "right");
7680
- };
7681
- place();
7682
- window.addEventListener("resize", place);
7683
- return () => window.removeEventListener("resize", place);
7684
- }, [open]);
7685
- const handleOpen = () => {
7686
- setTempFrom(from);
7687
- setTempTo(to);
7688
- setActivePreset(null);
7689
- const anchor = from ? parseDate(from) : now;
7690
- setMonth(anchor.getMonth());
7691
- setYear(anchor.getFullYear());
7692
- setYearPage(yearPageStart(anchor.getFullYear()));
7693
- setView("days");
7694
- setOpen(true);
7695
- };
7696
- const handlePreset = (label) => {
7697
- let start, end;
7698
- switch (label) {
7699
- case "Last 2 Weeks":
7700
- end = /* @__PURE__ */ new Date();
7701
- start = /* @__PURE__ */ new Date();
7702
- start.setDate(start.getDate() - 14);
7703
- break;
7704
- case "Last Month":
7705
- start = new Date(now.getFullYear(), now.getMonth() - 1, 1);
7706
- end = new Date(now.getFullYear(), now.getMonth(), 0);
7707
- break;
7708
- case "Last 3 Months":
7709
- start = new Date(now.getFullYear(), now.getMonth() - 3, 1);
7710
- end = new Date(now.getFullYear(), now.getMonth(), 0);
7711
- break;
7712
- case "Last 6 Months":
7713
- start = new Date(now.getFullYear(), now.getMonth() - 6, 1);
7714
- end = new Date(now.getFullYear(), now.getMonth(), 0);
7715
- break;
7716
- case "Last 12 Months":
7717
- start = new Date(now.getFullYear(), now.getMonth() - 12, 1);
7718
- end = new Date(now.getFullYear(), now.getMonth(), 0);
7719
- break;
7720
- default:
7721
- return;
7722
- }
7723
- setTempFrom(toISODate2(start));
7724
- setTempTo(toISODate2(end));
7725
- setActivePreset(label);
7726
- setMonth(start.getMonth());
7727
- setYear(start.getFullYear());
7728
- setYearPage(yearPageStart(start.getFullYear()));
7729
- setView("days");
7730
- };
7731
- const handleCalendarSelect = (date) => {
7732
- setActivePreset("Custom");
7733
- if (!tempFrom || tempFrom && tempTo) {
7734
- setTempFrom(date);
7735
- setTempTo("");
7736
- } else {
7737
- if (date < tempFrom) {
7738
- setTempTo(tempFrom);
7739
- setTempFrom(date);
7740
- } else {
7741
- setTempTo(date);
7742
- }
7743
- }
7744
- };
7745
- const handleApply = () => {
7746
- onChange(tempFrom, tempTo);
7747
- setOpen(false);
7748
- };
7749
- const handleClear = () => {
7750
- onChange("", "");
7751
- setOpen(false);
7752
- };
7753
- const changeMonth = (delta) => {
7754
- let m = month + delta, y = year;
7755
- if (m < 0) {
7756
- m = 11;
7757
- y--;
7758
- } else if (m > 11) {
7759
- m = 0;
7760
- y++;
7761
- }
7762
- setMonth(m);
7763
- setYear(y);
7764
- };
7765
- const handleStep = (delta) => {
7766
- if (view === "days") changeMonth(delta);
7767
- else if (view === "months") setYear(year + delta);
7768
- else setYearPage(yearPage + delta * YEARS_PER_PAGE);
7769
- };
7770
- const selectMonth = (m) => {
7771
- setMonth(m);
7772
- setView("days");
7773
- };
7774
- const selectYear = (y) => {
7775
- setYear(y);
7776
- setView("months");
7777
- };
7778
- const openYears = () => {
7779
- setYearPage(yearPageStart(year));
7780
- setView("years");
7781
- };
7782
- const firstDay = new Date(year, month, 1).getDay();
7783
- const daysInMonth = new Date(year, month + 1, 0).getDate();
7784
- const prevDays = new Date(year, month, 0).getDate();
7785
- const monthName = new Date(year, month).toLocaleString("default", { month: "long" });
7786
- const cells = [];
7787
- for (let i = firstDay - 1; i >= 0; i--) {
7788
- const d = new Date(year, month - 1, prevDays - i);
7789
- cells.push({ day: prevDays - i, current: false, date: toISODate2(d) });
7790
- }
7791
- for (let d = 1; d <= daysInMonth; d++) {
7792
- cells.push({ day: d, current: true, date: toISODate2(new Date(year, month, d)) });
7793
- }
7794
- const remaining = 42 - cells.length;
7795
- for (let d = 1; d <= remaining; d++) {
7796
- cells.push({ day: d, current: false, date: toISODate2(new Date(year, month + 1, d)) });
7797
- }
7798
- const isInRange = (date) => {
7799
- if (!tempFrom || !tempTo) return false;
7800
- const s = tempFrom < tempTo ? tempFrom : tempTo;
7801
- const e = tempFrom < tempTo ? tempTo : tempFrom;
7802
- return date > s && date < e;
7803
- };
7804
- const displayText = from && to ? `${displayDate(from)} \u2014 ${displayDate(to)}` : from ? `From ${displayDate(from)}` : to ? `To ${displayDate(to)}` : "";
7805
- return /* @__PURE__ */ jsxs("div", { className: "relative", ref, children: [
7806
- /* @__PURE__ */ jsxs(
7807
- "div",
7808
- {
7809
- className: `inline-flex items-center gap-2 border rounded-lg px-2.5 py-1.5 text-sm focus-within:border-blue-500 focus-within:ring-1 focus-within:ring-blue-500 ${from || to ? "border-blue-400 bg-blue-50 text-blue-700" : "border-gray-300 text-gray-500"}`,
7810
- children: [
7811
- /* @__PURE__ */ jsxs(
7812
- "button",
7813
- {
7814
- type: "button",
7815
- onClick: handleOpen,
7816
- "aria-haspopup": "dialog",
7817
- "aria-expanded": open,
7818
- className: "inline-flex items-center gap-2 focus:outline-none",
7819
- children: [
7820
- /* @__PURE__ */ jsx("svg", { className: "w-4 h-4 shrink-0", fill: "none", stroke: "currentColor", strokeWidth: "2", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" }) }),
7821
- displayText || placeholder
7822
- ]
7823
- }
7824
- ),
7825
- clearable && (from || to) && /* @__PURE__ */ jsx(
7826
- "button",
7827
- {
7828
- type: "button",
7829
- onClick: handleClear,
7830
- "aria-label": "Clear date range",
7831
- className: "text-blue-400 hover:text-red-500 focus:outline-none focus-visible:ring-1 focus-visible:ring-blue-500 rounded",
7832
- children: "\xD7"
7833
- }
7834
- )
7835
- ]
7836
- }
7837
- ),
7838
- open && /* @__PURE__ */ jsxs(
7839
- "div",
7840
- {
7841
- ref: panelRef,
7842
- role: "dialog",
7843
- "aria-label": "Date range",
7844
- className: "absolute z-50 mt-1 rounded-2xl p-4",
7845
- style: { ...align === "right" ? { right: 0 } : { left: 0 }, ...glassStyle() },
7846
- children: [
7847
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 mb-3", children: [
7848
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
7849
- /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-gray-500", children: "From" }),
7850
- /* @__PURE__ */ jsx("span", { className: `text-sm px-2 py-1 rounded border min-w-[90px] ${tempFrom ? "border-blue-300 bg-blue-50 text-blue-700" : "border-gray-200 bg-gray-50 text-gray-400"}`, children: tempFrom ? displayDate(tempFrom) : "Start" })
7851
- ] }),
7852
- /* @__PURE__ */ jsx("span", { className: "text-gray-300", children: "\u2014" }),
7853
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
7854
- /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-gray-500", children: "To" }),
7855
- /* @__PURE__ */ jsx("span", { className: `text-sm px-2 py-1 rounded border min-w-[90px] ${tempTo ? "border-blue-300 bg-blue-50 text-blue-700" : "border-gray-200 bg-gray-50 text-gray-400"}`, children: tempTo ? displayDate(tempTo) : "End" })
7856
- ] })
7857
- ] }),
7858
- /* @__PURE__ */ jsxs("div", { className: "flex gap-4", children: [
7859
- /* @__PURE__ */ jsxs("div", { className: "w-64", children: [
7860
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-2 px-1", children: [
7861
- /* @__PURE__ */ jsx("button", { type: "button", onClick: () => handleStep(-1), "aria-label": "Previous", className: "p-1 rounded-full hover:bg-gray-100 text-gray-600", children: /* @__PURE__ */ jsx("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", strokeWidth: "2", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M15 19l-7-7 7-7" }) }) }),
7862
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 text-sm font-semibold text-gray-800", children: [
7863
- view === "days" && /* @__PURE__ */ jsx(
7864
- "button",
7865
- {
7866
- type: "button",
7867
- onClick: () => setView("months"),
7868
- className: "px-1.5 py-0.5 rounded-md hover:bg-gray-100 transition-colors",
7869
- children: monthName
7870
- }
7871
- ),
7872
- view === "years" ? /* @__PURE__ */ jsxs("span", { className: "px-1.5 py-0.5", children: [
7873
- yearPage,
7874
- " \u2013 ",
7875
- yearPage + YEARS_PER_PAGE - 1
7876
- ] }) : /* @__PURE__ */ jsx(
7877
- "button",
7878
- {
7879
- type: "button",
7880
- onClick: openYears,
7881
- className: "px-1.5 py-0.5 rounded-md hover:bg-gray-100 transition-colors",
7882
- children: year
7883
- }
7884
- )
7885
- ] }),
7886
- /* @__PURE__ */ jsx("button", { type: "button", onClick: () => handleStep(1), "aria-label": "Next", className: "p-1 rounded-full hover:bg-gray-100 text-gray-600", children: /* @__PURE__ */ jsx("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", strokeWidth: "2", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M9 5l7 7-7 7" }) }) })
7887
- ] }),
7888
- /* @__PURE__ */ jsxs("div", { className: "min-h-[13.75rem]", children: [
7889
- view === "days" && /* @__PURE__ */ jsxs(Fragment, { children: [
7890
- /* @__PURE__ */ jsx("div", { className: "grid grid-cols-7 text-center text-xs font-medium text-gray-500 mb-1", children: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"].map((d) => /* @__PURE__ */ jsx("div", { className: "py-1", children: d }, d)) }),
7891
- /* @__PURE__ */ jsx("div", { className: "grid grid-cols-7 text-center text-sm", children: cells.map((c, i) => {
7892
- const isStart = c.date === tempFrom;
7893
- const isEnd = c.date === tempTo;
7894
- const isSelected = isStart || isEnd;
7895
- const inRange = isInRange(c.date);
7896
- return /* @__PURE__ */ jsx(
7897
- "button",
7898
- {
7899
- type: "button",
7900
- onClick: () => handleCalendarSelect(c.date),
7901
- className: `py-1.5 rounded-md transition-colors ${!c.current ? "text-gray-300" : isSelected ? "bg-blue-600 text-white font-semibold" : inRange ? "bg-blue-100 text-blue-800" : "text-gray-700 hover:bg-gray-100"}`,
7902
- children: c.day
7903
- },
7904
- i
7905
- );
7906
- }) })
7907
- ] }),
7908
- view === "months" && /* @__PURE__ */ jsx("div", { className: "grid grid-cols-3 gap-1 text-center text-sm", children: MONTH_NAMES.map((name, m) => {
7909
- const isCurrent = m === month;
7910
- const isThisMonth = m === now.getMonth() && year === now.getFullYear();
7911
- return /* @__PURE__ */ jsx(
7912
- "button",
7913
- {
7914
- type: "button",
7915
- onClick: () => selectMonth(m),
7916
- className: `py-4 rounded-md transition-colors ${isCurrent ? "bg-blue-600 text-white font-semibold" : isThisMonth ? "text-blue-600 font-medium hover:bg-gray-100" : "text-gray-700 hover:bg-gray-100"}`,
7917
- children: name
7918
- },
7919
- name
7920
- );
7921
- }) }),
7922
- view === "years" && /* @__PURE__ */ jsx("div", { className: "grid grid-cols-3 gap-1 text-center text-sm", children: Array.from({ length: YEARS_PER_PAGE }, (_, i) => yearPage + i).map((y) => {
7923
- const isCurrent = y === year;
7924
- const isThisYear = y === now.getFullYear();
7925
- return /* @__PURE__ */ jsx(
7926
- "button",
7927
- {
7928
- type: "button",
7929
- onClick: () => selectYear(y),
7930
- className: `py-4 rounded-md transition-colors ${isCurrent ? "bg-blue-600 text-white font-semibold" : isThisYear ? "text-blue-600 font-medium hover:bg-gray-100" : "text-gray-700 hover:bg-gray-100"}`,
7931
- children: y
7932
- },
7933
- y
7934
- );
7935
- }) })
7936
- ] })
7937
- ] }),
7938
- /* @__PURE__ */ jsxs("div", { className: "border-l border-gray-200 pl-4 flex flex-col gap-1 min-w-[130px]", children: [
7939
- PRESET_LABELS.map((label) => /* @__PURE__ */ jsx(
7940
- "button",
7941
- {
7942
- type: "button",
7943
- onClick: () => handlePreset(label),
7944
- className: `text-left px-3 py-1.5 text-sm rounded-md transition-colors ${activePreset === label ? "bg-blue-50 text-blue-700 font-medium" : "text-gray-700 hover:bg-gray-50"}`,
7945
- children: label
7946
- },
7947
- label
7948
- )),
7949
- /* @__PURE__ */ jsx(
7950
- "button",
7951
- {
7952
- type: "button",
7953
- onClick: () => setActivePreset("Custom"),
7954
- className: `text-left px-3 py-1.5 text-sm rounded-md transition-colors ${activePreset === "Custom" ? "bg-blue-50 text-blue-700 font-medium" : "text-gray-700 hover:bg-gray-50"}`,
7955
- children: "Custom"
7956
- }
7957
- )
7958
- ] })
7959
- ] }),
7960
- /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2 mt-4 pt-3 border-t border-gray-200", children: [
7961
- clearable && /* @__PURE__ */ jsx("button", { type: "button", onClick: handleClear, className: "px-3 py-1.5 text-sm text-gray-500 hover:text-gray-700", children: "Clear" }),
7962
- /* @__PURE__ */ jsx("button", { type: "button", onClick: () => setOpen(false), className: "px-3 py-1.5 text-sm text-gray-600 border border-gray-300 rounded-md hover:bg-gray-50", children: "Cancel" }),
7963
- /* @__PURE__ */ jsx(
7964
- "button",
7965
- {
7966
- type: "button",
7967
- onClick: handleApply,
7968
- disabled: !tempFrom || !tempTo,
7969
- className: "px-4 py-1.5 text-sm font-medium text-white bg-blue-600 rounded-md hover:bg-blue-700 disabled:opacity-50",
7970
- children: "Apply"
7971
- }
7972
- )
7973
- ] })
7974
- ]
7975
- }
7976
- )
7977
- ] });
7978
- }
7979
- function Card({ children, header, footer, padded = true, className = "" }) {
7980
- return /* @__PURE__ */ jsxs("div", { className: `rounded-lg border border-gray-200 bg-white shadow-sm ${className}`.trim(), children: [
7981
- header && /* @__PURE__ */ jsx("div", { className: "border-b border-gray-100 px-4 py-3 text-sm font-semibold text-gray-900", children: header }),
7982
- /* @__PURE__ */ jsx("div", { className: padded ? "p-4" : "", children }),
7983
- footer && /* @__PURE__ */ jsx("div", { className: "border-t border-gray-100 px-4 py-3", children: footer })
7984
- ] });
7985
- }
7986
- function StatCard({ label, value, icon, delta, className = "" }) {
7987
- const deltaColor = delta?.direction === "up" ? "text-green-600" : delta?.direction === "down" ? "text-red-600" : "text-gray-500";
7988
- const arrow = delta?.direction === "up" ? "\u25B2" : delta?.direction === "down" ? "\u25BC" : "\u2192";
7989
- return /* @__PURE__ */ jsxs("div", { className: `rounded-lg border border-gray-200 bg-white p-4 shadow-sm ${className}`.trim(), children: [
7990
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
7991
- /* @__PURE__ */ jsx("span", { className: "text-xs font-medium uppercase tracking-wide text-gray-500", children: label }),
7992
- icon && /* @__PURE__ */ jsx("span", { className: "text-gray-400", children: icon })
7993
- ] }),
7994
- /* @__PURE__ */ jsxs("div", { className: "mt-2 flex items-baseline gap-2", children: [
7995
- /* @__PURE__ */ jsx("span", { className: "text-2xl font-semibold text-gray-900", children: value }),
7996
- delta && /* @__PURE__ */ jsxs("span", { className: `text-xs font-medium ${deltaColor}`, children: [
7997
- arrow,
7998
- " ",
7999
- delta.value
8000
- ] })
8001
- ] })
8002
- ] });
8003
- }
8004
- var SIZE_PX = { xs: 24, sm: 32, md: 40, lg: 56 };
8005
- var STATUS_COLOR = {
8006
- online: "bg-green-500",
8007
- offline: "bg-gray-400",
8008
- busy: "bg-red-500",
8009
- away: "bg-amber-500"
8010
- };
8011
- function initials(name) {
8012
- if (!name) return "?";
8013
- const parts = name.trim().split(/\s+/).filter(Boolean);
8014
- if (parts.length === 0) return "?";
8015
- const first = parts[0][0] ?? "";
8016
- const last = parts.length > 1 ? parts[parts.length - 1][0] : "";
8017
- return (first + last).toUpperCase() || "?";
8018
- }
8019
- function Avatar({ src, name, size = "md", status, className = "" }) {
8020
- const px = SIZE_PX[size];
8021
- const dot = Math.max(8, Math.round(px * 0.28));
8022
- return /* @__PURE__ */ jsxs("span", { className: `relative inline-flex shrink-0 ${className}`.trim(), style: { width: px, height: px }, children: [
8023
- src ? /* @__PURE__ */ jsx("img", { src, alt: name ?? "", className: "h-full w-full rounded-full object-cover" }) : /* @__PURE__ */ jsx(
8024
- "span",
8025
- {
8026
- className: "flex h-full w-full items-center justify-center rounded-full bg-gray-100 font-medium text-gray-600",
8027
- style: { fontSize: Math.round(px * 0.4) },
8028
- children: initials(name)
8029
- }
8030
- ),
8031
- status && /* @__PURE__ */ jsx(
8032
- "span",
8033
- {
8034
- className: `absolute bottom-0 right-0 rounded-full ring-2 ring-white ${STATUS_COLOR[status]}`,
8035
- style: { width: dot, height: dot }
8036
- }
8037
- )
8038
- ] });
8039
- }
8040
- function AvatarGroup({ children, max, size = "md", className = "" }) {
8041
- const items = Children.toArray(children);
8042
- const shown = max ? items.slice(0, max) : items;
8043
- const overflow = max ? items.length - shown.length : 0;
8044
- const px = SIZE_PX[size];
8045
- const overlap = Math.round(px * 0.3);
8046
- return /* @__PURE__ */ jsxs("div", { className: `flex items-center ${className}`.trim(), children: [
8047
- shown.map((child, i) => /* @__PURE__ */ jsx("span", { className: "rounded-full ring-2 ring-white", style: { marginLeft: i === 0 ? 0 : -overlap }, children: child }, i)),
8048
- overflow > 0 && /* @__PURE__ */ jsxs(
8049
- "span",
8050
- {
8051
- className: "flex items-center justify-center rounded-full bg-gray-200 font-medium text-gray-600 ring-2 ring-white",
8052
- style: { width: px, height: px, marginLeft: -overlap, fontSize: Math.round(px * 0.36) },
8053
- children: [
8054
- "+",
8055
- overflow
8056
- ]
8057
- }
8058
- )
8059
- ] });
8060
- }
8061
- var TONE = {
8062
- info: { box: "bg-blue-50 border-blue-200", icon: "text-blue-600" },
8063
- success: { box: "bg-green-50 border-green-200", icon: "text-green-600" },
8064
- warning: { box: "bg-amber-50 border-amber-200", icon: "text-amber-600" },
8065
- danger: { box: "bg-red-50 border-red-200", icon: "text-red-600" }
8066
- };
8067
- function ToneIcon({ tone }) {
8068
- const common = { className: "h-5 w-5", viewBox: "0 0 20 20", fill: "currentColor", "aria-hidden": true };
8069
- switch (tone) {
8070
- case "success":
8071
- return /* @__PURE__ */ jsx("svg", { ...common, children: /* @__PURE__ */ jsx("path", { fillRule: "evenodd", d: "M10 18a8 8 0 100-16 8 8 0 000 16zm3.7-9.3a1 1 0 00-1.4-1.4L9 10.6 7.7 9.3a1 1 0 00-1.4 1.4l2 2a1 1 0 001.4 0l4-4z", clipRule: "evenodd" }) });
8072
- case "warning":
8073
- return /* @__PURE__ */ jsx("svg", { ...common, children: /* @__PURE__ */ jsx("path", { fillRule: "evenodd", d: "M8.3 2.8a2 2 0 013.4 0l6 10A2 2 0 0116 16H4a2 2 0 01-1.7-3.2l6-10zM10 7a1 1 0 00-1 1v3a1 1 0 102 0V8a1 1 0 00-1-1zm0 7a1 1 0 100 2 1 1 0 000-2z", clipRule: "evenodd" }) });
8074
- case "danger":
8075
- return /* @__PURE__ */ jsx("svg", { ...common, children: /* @__PURE__ */ jsx("path", { fillRule: "evenodd", d: "M10 18a8 8 0 100-16 8 8 0 000 16zM8.7 7.3a1 1 0 00-1.4 1.4L8.6 10l-1.3 1.3a1 1 0 101.4 1.4L10 11.4l1.3 1.3a1 1 0 001.4-1.4L11.4 10l1.3-1.3a1 1 0 10-1.4-1.4L10 8.6 8.7 7.3z", clipRule: "evenodd" }) });
8076
- default:
8077
- return /* @__PURE__ */ jsx("svg", { ...common, children: /* @__PURE__ */ jsx("path", { fillRule: "evenodd", d: "M10 18a8 8 0 100-16 8 8 0 000 16zM9 9a1 1 0 012 0v4a1 1 0 11-2 0V9zm1-4a1 1 0 100 2 1 1 0 000-2z", clipRule: "evenodd" }) });
8078
- }
8079
- }
8080
- function Banner({ tone = "info", title, children, icon, onDismiss, className = "" }) {
8081
- const t = TONE[tone];
8082
- return /* @__PURE__ */ jsxs("div", { role: tone === "danger" ? "alert" : "status", className: `flex items-start gap-3 rounded-lg border px-4 py-3 ${t.box} ${className}`.trim(), children: [
8083
- /* @__PURE__ */ jsx("span", { className: `mt-0.5 shrink-0 ${t.icon}`, children: icon ?? /* @__PURE__ */ jsx(ToneIcon, { tone }) }),
8084
- /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1 text-sm", children: [
8085
- title && /* @__PURE__ */ jsx("div", { className: "font-semibold text-gray-900", children: title }),
8086
- children && /* @__PURE__ */ jsx("div", { className: `text-gray-700 ${title ? "mt-0.5" : ""}`.trim(), children })
8087
- ] }),
8088
- onDismiss && /* @__PURE__ */ jsx("button", { type: "button", onClick: onDismiss, "aria-label": "Dismiss", className: "shrink-0 text-gray-400 hover:text-gray-600", children: /* @__PURE__ */ jsx("svg", { className: "h-4 w-4", viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M6 6l8 8M14 6l-8 8" }) }) })
8089
- ] });
8090
- }
8091
- function Tabs({ items, value, onChange, variant = "underline", className = "" }) {
8092
- if (variant === "pill") {
8093
- 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) => {
8094
- const active = t.id === value;
8095
- return /* @__PURE__ */ jsxs(
8096
- "button",
8097
- {
8098
- type: "button",
8099
- role: "tab",
8100
- "aria-selected": active,
8101
- tabIndex: active ? 0 : -1,
8102
- disabled: t.disabled,
8103
- onClick: () => onChange(t.id),
8104
- 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"}`,
8105
- children: [
8106
- t.icon,
8107
- t.label
8108
- ]
8109
- },
8110
- t.id
8111
- );
8112
- }) });
8113
- }
8114
- return /* @__PURE__ */ jsx("div", { role: "tablist", className: `flex items-center gap-1 border-b border-gray-200 ${className}`.trim(), children: items.map((t) => {
8115
- const active = t.id === value;
8116
- return /* @__PURE__ */ jsxs(
8117
- "button",
8118
- {
8119
- type: "button",
8120
- role: "tab",
8121
- "aria-selected": active,
8122
- tabIndex: active ? 0 : -1,
8123
- disabled: t.disabled,
8124
- onClick: () => onChange(t.id),
8125
- 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"}`,
8126
- children: [
8127
- t.icon,
8128
- t.label
8129
- ]
8130
- },
8131
- t.id
8132
- );
8133
- }) });
8134
- }
8135
- function Accordion({
8136
- items,
8137
- defaultOpenIds = [],
8138
- openIds,
8139
- onOpenChange,
8140
- allowMultiple = false,
8141
- className = ""
8142
- }) {
8143
- const [internal, setInternal] = useState(defaultOpenIds);
8144
- const open = openIds ?? internal;
8145
- const toggle = (id) => {
8146
- const isOpen = open.includes(id);
8147
- const next = isOpen ? open.filter((x) => x !== id) : allowMultiple ? [...open, id] : [id];
8148
- if (openIds === void 0) setInternal(next);
8149
- onOpenChange?.(next);
8150
- };
8151
- return /* @__PURE__ */ jsx("div", { className: `divide-y divide-gray-100 overflow-hidden rounded-lg border border-gray-200 ${className}`.trim(), children: items.map((item) => {
8152
- const isOpen = open.includes(item.id);
8153
- return /* @__PURE__ */ jsxs("div", { children: [
8154
- /* @__PURE__ */ jsxs(
8155
- "button",
8156
- {
8157
- type: "button",
8158
- disabled: item.disabled,
8159
- "aria-expanded": isOpen,
8160
- onClick: () => toggle(item.id),
8161
- className: "flex w-full items-center justify-between gap-3 px-4 py-3 text-left text-sm font-medium text-gray-900 hover:bg-gray-50 disabled:cursor-not-allowed disabled:opacity-50",
8162
- children: [
8163
- /* @__PURE__ */ jsx("span", { children: item.title }),
8164
- /* @__PURE__ */ jsx(
8165
- "svg",
8166
- {
8167
- className: `h-4 w-4 shrink-0 text-gray-400 transition-transform ${isOpen ? "rotate-180" : ""}`,
8168
- viewBox: "0 0 20 20",
8169
- fill: "none",
8170
- stroke: "currentColor",
8171
- strokeWidth: "2",
8172
- "aria-hidden": "true",
8173
- children: /* @__PURE__ */ jsx("path", { d: "M6 8l4 4 4-4", strokeLinecap: "round", strokeLinejoin: "round" })
8174
- }
8175
- )
8176
- ]
8177
- }
8178
- ),
8179
- isOpen && /* @__PURE__ */ jsx("div", { className: "px-4 pb-3 text-sm text-gray-600", children: item.content })
8180
- ] }, item.id);
8181
- }) });
8182
- }
8183
- var SIDE_POS = {
8184
- top: "bottom-full left-1/2 -translate-x-1/2 mb-1.5",
8185
- bottom: "top-full left-1/2 -translate-x-1/2 mt-1.5",
8186
- left: "right-full top-1/2 -translate-y-1/2 mr-1.5",
8187
- right: "left-full top-1/2 -translate-y-1/2 ml-1.5"
8188
- };
8189
- function Tooltip({ content, side = "top", delay = 200, children }) {
8190
- const [show, setShow] = useState(false);
8191
- const timer = useRef(void 0);
8192
- const id = useId();
8193
- const open = () => {
8194
- timer.current = window.setTimeout(() => setShow(true), delay);
8195
- };
8196
- const close = () => {
8197
- window.clearTimeout(timer.current);
8198
- setShow(false);
8199
- };
8200
- useEffect(() => () => window.clearTimeout(timer.current), []);
8201
- return /* @__PURE__ */ jsxs(
8202
- "span",
8203
- {
8204
- className: "relative inline-flex",
8205
- "aria-describedby": show ? id : void 0,
8206
- onMouseEnter: open,
8207
- onMouseLeave: close,
8208
- onFocus: open,
8209
- onBlur: close,
8210
- children: [
8211
- children,
8212
- show && /* @__PURE__ */ jsx(
8213
- "span",
8214
- {
8215
- id,
8216
- role: "tooltip",
8217
- className: `pointer-events-none absolute z-[300] whitespace-nowrap rounded-md px-2 py-1 text-xs text-gray-800 ${SIDE_POS[side]}`,
8218
- style: glassStyle(),
8219
- children: content
8220
- }
8221
- )
8222
- ]
8223
- }
8224
- );
8225
- }
8226
- function ColoredBadge({ colorClass, children, size = "sm", capitalize = false }) {
8227
- 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";
8228
- const capCls = capitalize ? "capitalize" : "";
8229
- return /* @__PURE__ */ jsx("span", { className: `inline-flex items-center rounded-full font-medium ${sizeCls} ${capCls} ${colorClass}`, children });
8230
- }
8231
- function EmptyState({
8232
- title,
8233
- message,
8234
- description,
8235
- hint,
8236
- variant,
8237
- frameless,
8238
- icon,
8239
- children
8240
- }) {
8241
- const v = frameless ? "none" : variant ?? "dashed";
8242
- const showIcon = icon ?? v !== "card";
8243
- const primary = message ?? (title ? void 0 : "Nothing here yet.");
8244
- const secondary = description ?? hint;
8245
- 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";
8246
- return /* @__PURE__ */ jsxs("div", { className: `flex flex-col items-center justify-center text-center ${frame}`, children: [
8247
- 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" }) }),
8248
- title && /* @__PURE__ */ jsx("h3", { className: "text-sm font-medium text-gray-900", children: title }),
8249
- primary && /* @__PURE__ */ jsx("p", { className: `text-sm text-gray-500 ${title ? "mt-2" : ""}`, children: primary }),
8250
- secondary && /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-400 mt-1", children: secondary }),
8251
- children && /* @__PURE__ */ jsx("div", { className: "mt-4", children })
8252
- ] });
8253
- }
8254
- function PageHeader({ title, description, subtitle, actions, children }) {
8255
- const desc = description ?? subtitle;
8256
- const right = actions ?? children;
8257
- return /* @__PURE__ */ jsxs("div", { className: "mb-6 flex items-start justify-between gap-4", children: [
8258
- /* @__PURE__ */ jsxs("div", { children: [
8259
- /* @__PURE__ */ jsx("h1", { className: "text-xl font-semibold text-gray-900", children: title }),
8260
- desc && /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-gray-500", children: desc })
8261
- ] }),
8262
- right && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: right })
8263
- ] });
8264
- }
8265
- function LoadingSpinner2({ size = "md", padding = "py-12", className = "" }) {
8266
- const dim = size === "sm" ? "h-5 w-5" : size === "lg" ? "h-12 w-12" : "h-8 w-8";
8267
- return /* @__PURE__ */ jsx("div", { className: `flex items-center justify-center ${padding} ${className}`, children: /* @__PURE__ */ jsx("div", { className: `${dim} animate-spin rounded-full border-2 border-gray-200 border-t-blue-600` }) });
8268
- }
8269
- function FilterBar({ filters, values, onChange, onClear, children }) {
8270
- const activeCount = Object.values(values).filter(Boolean).length;
8271
- return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 flex-wrap", children: [
8272
- filters.map((f) => {
8273
- const isSearchable = f.searchable ?? f.options.length > 8;
8274
- return isSearchable ? /* @__PURE__ */ jsx(
8275
- SearchableFilter,
8276
- {
8277
- filter: f,
8278
- value: values[f.field] || "",
8279
- onChange: (v) => onChange(f.field, v)
8280
- },
8281
- f.field
8282
- ) : /* @__PURE__ */ jsx(
8283
- PlainFilter,
8284
- {
8285
- filter: f,
8286
- value: values[f.field] || "",
8287
- onChange: (v) => onChange(f.field, v)
8288
- },
8289
- f.field
8290
- );
8291
- }),
8292
- children,
8293
- activeCount > 0 && /* @__PURE__ */ jsxs("button", { onClick: onClear, className: "flex items-center gap-1 text-sm text-gray-500 hover:text-gray-700", children: [
8294
- /* @__PURE__ */ jsx(XMarkIcon, { className: "h-4 w-4" }),
8295
- "Clear filters"
8296
- ] })
8297
- ] });
8298
- }
8299
- function PlainFilter({ filter, value, onChange }) {
8300
- const [open, setOpen] = useState(false);
8301
- const [active, setActive] = useState(-1);
8302
- const ref = useRef(null);
8303
- const triggerRef = useRef(null);
8304
- const listRef = useRef(null);
8305
- const openRef = useRef(open);
8306
- openRef.current = open;
8307
- const typeahead = useRef({ buffer: "", timer: null });
8308
- const baseId = useId();
8309
- const listboxId = `${baseId}-listbox`;
8310
- const optionId = (i) => `${baseId}-opt-${i}`;
8311
- const items = [{ value: "", label: filter.label }, ...filter.options];
8312
- const selectedIndex = value ? items.findIndex((o) => o.value === value) : 0;
8313
- const selectedLabel = value ? filter.options.find((o) => o.value === value)?.label : null;
8314
- const close = useCallback((refocus = true) => {
8315
- setOpen(false);
8316
- setActive(-1);
8317
- if (refocus) triggerRef.current?.focus();
8318
- }, []);
8319
- const openList = useCallback(() => {
8320
- setActive(selectedIndex >= 0 ? selectedIndex : firstEnabledIndex(items));
8321
- setOpen(true);
8322
- }, [selectedIndex, items]);
8323
- const commit = useCallback((i) => {
8324
- const o = items[i];
8325
- if (!o) return;
8326
- onChange(o.value);
8327
- close();
8328
- }, [items, onChange, close]);
8329
- useClickOutside(ref, useCallback(() => {
8330
- if (open) close(false);
8331
- }, [open, close]));
8332
- useEffect(() => {
8333
- if (!open) return;
8334
- return registerModalEscapeInterceptor((e) => {
8335
- if (e.key !== "Escape" || !openRef.current) return false;
8336
- close();
8337
- return true;
8338
- });
8339
- }, [open, close]);
8340
- useEffect(() => {
8341
- if (!open || active < 0) return;
8342
- listRef.current?.querySelector(`#${CSS.escape(optionId(active))}`)?.scrollIntoView({ block: "nearest" });
8343
- });
8344
- const runTypeahead = (char) => {
8345
- const ta = typeahead.current;
8346
- if (ta.timer) clearTimeout(ta.timer);
8347
- ta.buffer += char;
8348
- ta.timer = setTimeout(() => {
8349
- ta.buffer = "";
8350
- ta.timer = null;
8351
- }, 500);
8352
- const hit = matchTypeahead(items, ta.buffer, open ? active : selectedIndex);
8353
- if (hit < 0) return;
8354
- if (open) setActive(hit);
8355
- else commit(hit);
8356
- };
8357
- const onKeyDown = (e) => {
8358
- switch (e.key) {
8359
- case "ArrowDown":
8360
- e.preventDefault();
8361
- if (!open) openList();
8362
- else setActive((a) => nextEnabledIndex(items, a, 1));
8363
- return;
8364
- case "ArrowUp":
8365
- e.preventDefault();
8366
- if (!open) openList();
8367
- else setActive((a) => nextEnabledIndex(items, a, -1));
8368
- return;
8369
- case "Home":
8370
- if (open) {
8371
- e.preventDefault();
8372
- setActive(firstEnabledIndex(items));
8373
- }
8374
- return;
8375
- case "End":
8376
- if (open) {
8377
- e.preventDefault();
8378
- setActive(lastEnabledIndex(items));
8379
- }
8380
- return;
8381
- case "Enter":
8382
- if (open) {
8383
- e.preventDefault();
8384
- commit(active);
8385
- }
8386
- return;
8387
- case " ":
8388
- e.preventDefault();
8389
- if (!open) openList();
8390
- else commit(active);
8391
- return;
8392
- case "Escape":
8393
- if (open) {
8394
- e.preventDefault();
8395
- e.stopPropagation();
8396
- close();
8397
- }
8398
- return;
8399
- case "Tab":
8400
- if (open) close(false);
8401
- return;
8402
- default:
8403
- if (e.key.length === 1 && !e.metaKey && !e.ctrlKey && !e.altKey) {
8404
- e.preventDefault();
8405
- runTypeahead(e.key);
8406
- }
8407
- }
8408
- };
8409
- return /* @__PURE__ */ jsxs("div", { ref, className: "relative", children: [
8410
- /* @__PURE__ */ jsxs(
8411
- "button",
8412
- {
8413
- ref: triggerRef,
8414
- type: "button",
8415
- role: "combobox",
8416
- "aria-haspopup": "listbox",
8417
- "aria-expanded": open,
8418
- "aria-controls": open ? listboxId : void 0,
8419
- "aria-activedescendant": open && active >= 0 ? optionId(active) : void 0,
8420
- onClick: () => open ? close() : openList(),
8421
- onKeyDown,
8422
- className: `flex items-center gap-1.5 border rounded-lg px-2.5 py-1.5 text-sm transition-colors ${value ? "border-blue-400 bg-blue-50 text-blue-700" : "border-gray-300 text-gray-600 hover:border-gray-400"}`,
8423
- children: [
8424
- /* @__PURE__ */ jsx("span", { className: "truncate max-w-[140px]", children: selectedLabel || filter.label }),
8425
- value ? (
8426
- // A span, not a button: this lives INSIDE the combobox <button>, and
8427
- // `<button>` inside `<button>` is invalid HTML — React logs "cannot
8428
- // be a descendant of" and warns of a hydration error. It stays out of
8429
- // the a11y tree because clearing is already a first-class keyboard
8430
- // action: index 0 of the listbox is the "All" entry (value ''). A
8431
- // nested control here would only pad the combobox's accessible name
8432
- // with an affordance Tab can't reach.
8433
- /* @__PURE__ */ jsx(
8434
- "span",
8435
- {
8436
- "aria-hidden": "true",
8437
- onClick: (e) => {
8438
- e.stopPropagation();
8439
- onChange("");
8440
- },
8441
- className: "cursor-pointer text-blue-400 hover:text-blue-600",
8442
- children: /* @__PURE__ */ jsx(XMarkIcon, { className: "h-3.5 w-3.5" })
8443
- }
8444
- )
8445
- ) : /* @__PURE__ */ jsx("svg", { className: `h-3.5 w-3.5 text-gray-400 transition-transform ${open ? "rotate-180" : ""}`, fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M19.5 8.25l-7.5 7.5-7.5-7.5" }) })
8446
- ]
8447
- }
8448
- ),
8449
- open && /* @__PURE__ */ jsx("div", { ref: listRef, id: listboxId, role: "listbox", className: "absolute top-full left-0 mt-1 w-56 rounded-2xl z-[400] max-h-52 overflow-y-auto py-1", style: glassStyle(), children: items.map((o, i) => /* @__PURE__ */ jsx(
8450
- "div",
8451
- {
8452
- id: optionId(i),
8453
- role: "option",
8454
- "aria-selected": i === selectedIndex,
8455
- onPointerEnter: () => setActive(i),
8456
- onClick: () => commit(i),
8457
- className: `mx-1 cursor-pointer truncate rounded-lg px-3 py-1.5 text-sm
8458
- ${i === active ? "bg-blue-50 text-blue-700" : i === selectedIndex ? "font-medium text-blue-600" : "text-gray-700"}`,
8459
- children: o.label
8460
- },
8461
- o.value || "__all__"
8462
- )) })
8463
- ] });
8464
- }
8465
- function SearchableFilter({ filter, value, onChange }) {
8466
- const [open, setOpen] = useState(false);
8467
- const [search, setSearch] = useState("");
8468
- const ref = useRef(null);
8469
- const inputRef = useRef(null);
8470
- const selectedLabel = value ? filter.options.find((o) => o.value === value)?.label : null;
8471
- useClickOutside(ref, useCallback(() => {
8472
- if (open) {
8473
- setOpen(false);
8474
- setSearch("");
8475
- }
8476
- }, [open]));
8477
- useEffect(() => {
8478
- if (open) setTimeout(() => inputRef.current?.focus(), 50);
8479
- }, [open]);
8480
- const filtered = search ? filter.options.filter((o) => o.label.toLowerCase().includes(search.toLowerCase())) : filter.options;
8481
- return /* @__PURE__ */ jsxs("div", { ref, className: "relative", children: [
8482
- /* @__PURE__ */ jsxs(
8483
- "button",
8484
- {
8485
- onClick: () => setOpen(!open),
8486
- className: `flex items-center gap-1.5 border rounded-lg px-2.5 py-1.5 text-sm transition-colors ${value ? "border-blue-400 bg-blue-50 text-blue-700" : "border-gray-300 text-gray-600 hover:border-gray-400"}`,
5774
+ onClick: () => setOpen(!open),
5775
+ className: `flex items-center gap-1.5 border rounded-lg px-2.5 py-1.5 text-sm transition-colors ${value ? "border-blue-400 bg-blue-50 text-blue-700" : "border-gray-300 text-gray-600 hover:border-gray-400"}`,
8487
5776
  children: [
8488
5777
  /* @__PURE__ */ jsx("span", { className: "truncate max-w-[140px]", children: selectedLabel || filter.label }),
8489
5778
  value ? (
@@ -8565,203 +5854,6 @@ function useFilters() {
8565
5854
  return { filterValues, setFilter, clearFilters, filterParams };
8566
5855
  }
8567
5856
 
8568
- // src/shell/severity.ts
8569
- var SEVERITY_TONES = ["success", "warning", "danger"];
8570
- function isSeverityTone(value) {
8571
- return typeof value === "string" && SEVERITY_TONES.includes(value);
8572
- }
8573
- function severityOf(value, warn, crit) {
8574
- if (value == null || !Number.isFinite(value)) return null;
8575
- const warnAt = boundOf(warn);
8576
- const critAt = boundOf(crit);
8577
- if (warnAt == null && critAt == null) return null;
8578
- if (critAt != null && value >= critAt) return "danger";
8579
- if (warnAt != null && value >= warnAt) return "warning";
8580
- return "success";
8581
- }
8582
- function boundOf(value) {
8583
- return value != null && Number.isFinite(value) ? value : null;
8584
- }
8585
- var SEVERITY_FILL = {
8586
- success: "bg-green-500",
8587
- warning: "bg-amber-500",
8588
- danger: "bg-red-500"
8589
- };
8590
- var SEVERITY_INK = {
8591
- success: "text-gray-900",
8592
- warning: "text-amber-600",
8593
- danger: "text-red-600"
8594
- };
8595
- var SEVERITY_WORD = {
8596
- success: "ok",
8597
- warning: "warning",
8598
- danger: "critical"
8599
- };
8600
- var SEVERITY_UNKNOWN_FILL = "bg-gray-300 border border-red-500";
8601
- var SEVERITY_UNKNOWN_INK = "text-gray-900";
8602
- var TONE_STYLES = {
8603
- success: { tone: "success", fill: SEVERITY_FILL.success, ink: SEVERITY_INK.success, word: SEVERITY_WORD.success },
8604
- warning: { tone: "warning", fill: SEVERITY_FILL.warning, ink: SEVERITY_INK.warning, word: SEVERITY_WORD.warning },
8605
- danger: { tone: "danger", fill: SEVERITY_FILL.danger, ink: SEVERITY_INK.danger, word: SEVERITY_WORD.danger }
8606
- };
8607
- function styleForTone(tone) {
8608
- return tone == null ? null : TONE_STYLES[tone];
8609
- }
8610
- function describeToken(value) {
8611
- const raw = typeof value === "string" ? JSON.stringify(value) : typeof value === "number" || typeof value === "boolean" || typeof value === "bigint" ? String(value) : Object.prototype.toString.call(value);
8612
- return raw.length > 32 ? `${raw.slice(0, 31)}\u2026` : raw;
8613
- }
8614
- var reported = /* @__PURE__ */ new Set();
8615
- function reportUnknownSeverity(component, described) {
8616
- const key = `${component}:${described}`;
8617
- if (reported.has(key)) return;
8618
- reported.add(key);
8619
- console.error(
8620
- `[react-os-shell] ${component}: severity ${described} is not one of ${SEVERITY_TONES.join(" | ")}. Rendering an "unknown severity" marker \u2014 a health marker must never silently disappear. If this came from a backend rollup, map it to the shell's vocabulary at the fetch boundary (see isSeverityTone).`
8621
- );
8622
- }
8623
- function resolveSeverity(severity, component) {
8624
- if (severity == null) return null;
8625
- if (isSeverityTone(severity)) return TONE_STYLES[severity];
8626
- const described = describeToken(severity);
8627
- reportUnknownSeverity(component, described);
8628
- return {
8629
- tone: null,
8630
- fill: SEVERITY_UNKNOWN_FILL,
8631
- ink: SEVERITY_UNKNOWN_INK,
8632
- word: `unrecognised severity ${described}`
8633
- };
8634
- }
8635
- function warnOnce(key, message) {
8636
- if (reported.has(key)) return;
8637
- reported.add(key);
8638
- console.error(message);
8639
- }
8640
- function SidebarGroupLabel({ children }) {
8641
- return /* @__PURE__ */ jsx("p", { className: "px-2.5 pb-1 pt-1 text-[11px] font-semibold uppercase tracking-wide text-gray-400", children });
8642
- }
8643
- function SidebarNavItem({ label, count, active, onClick, severity }) {
8644
- const marker = resolveSeverity(severity, "SidebarNavItem");
8645
- return /* @__PURE__ */ jsxs(
8646
- "button",
8647
- {
8648
- type: "button",
8649
- onClick,
8650
- className: `flex w-full items-center justify-between gap-2 rounded-lg px-2.5 py-1.5 text-sm text-left ${active ? "bg-blue-50 font-medium text-blue-700" : "text-gray-700 hover:bg-gray-100"}`,
8651
- children: [
8652
- marker && // Decorative in the a11y tree — the word itself follows the label, so
8653
- // the item reads "Warehouses, warning, 12" in that order instead of
8654
- // leading with a tone before anyone knows what it belongs to.
8655
- /* @__PURE__ */ jsx(
8656
- "span",
8657
- {
8658
- "aria-hidden": "true",
8659
- title: marker.word,
8660
- className: `shrink-0 h-1.5 w-1.5 rounded-full ${marker.fill}`
8661
- }
8662
- ),
8663
- /* @__PURE__ */ jsx("span", { className: marker ? "truncate mr-auto" : "truncate", children: label }),
8664
- marker && /* @__PURE__ */ jsx("span", { className: "sr-only", children: marker.word }),
8665
- count != null && count > 0 && /* @__PURE__ */ jsx("span", { className: `shrink-0 inline-flex items-center justify-center min-w-[1.25rem] px-1.5 h-5 rounded-full text-[11px] font-medium ${active ? "bg-blue-100 text-blue-700" : "bg-gray-100 text-gray-600"}`, children: count })
8666
- ]
8667
- }
8668
- );
8669
- }
8670
- function Tick({ at, title, className }) {
8671
- return /* @__PURE__ */ jsx(
8672
- "div",
8673
- {
8674
- title,
8675
- className: `absolute -top-[3px] h-3 w-0.5 rounded-[1px] ${className}`,
8676
- style: { left: `${at}%` }
8677
- }
8678
- );
8679
- }
8680
- var clampPct = (n) => Math.min(100, Math.max(0, n));
8681
- function MetricBar({
8682
- label,
8683
- value,
8684
- max = 100,
8685
- warn,
8686
- crit,
8687
- severity,
8688
- detail,
8689
- formatValue,
8690
- emptyLabel = "no data",
8691
- size = "sm",
8692
- ariaLabel,
8693
- className = ""
8694
- }) {
8695
- const reading = value != null && Number.isFinite(value) ? value : null;
8696
- const scale = Number.isFinite(max) && max > 0 ? max : null;
8697
- if (scale == null) {
8698
- warnOnce(
8699
- `MetricBar:max:${String(max)}`,
8700
- `[react-os-shell] MetricBar: max=${String(max)} is not a positive finite number, so there is no scale to draw a proportional bar against. Printing the value without a bar. Pass the real capacity, or leave the reading out entirely (value={null}) when it is unknown \u2014 do not pass 0.`
8701
- );
8702
- }
8703
- const supplied = resolveSeverity(severity, "MetricBar");
8704
- const paint = reading != null ? supplied ?? styleForTone(severityOf(reading, warn, crit)) : null;
8705
- const warnAt = boundOf(warn);
8706
- const critAt = boundOf(crit);
8707
- const width = reading != null && scale != null ? clampPct(reading / scale * 100) : 0;
8708
- const format = formatValue ?? ((v) => scale === 100 ? `${v.toFixed(1)}%` : String(v));
8709
- const printed = reading != null ? format(reading) : "\u2014";
8710
- const showTicks = reading != null && scale != null && (warnAt != null || critAt != null);
8711
- const meterLabel = ariaLabel ?? (typeof label === "string" ? label : void 0);
8712
- const barHeight = size === "md" ? "h-2" : "h-1.5";
8713
- const isMeter = reading != null && scale != null;
8714
- const bar = /* @__PURE__ */ jsxs(
8715
- "div",
8716
- {
8717
- ...isMeter ? {
8718
- role: "meter",
8719
- "aria-label": meterLabel,
8720
- "aria-valuemin": 0,
8721
- "aria-valuemax": scale,
8722
- // Kept inside the declared range: the bar clamps, and a valuenow
8723
- // past its own valuemax is an invalid widget. `aria-valuetext`
8724
- // still carries the unclamped truth, and takes precedence in the
8725
- // announcement.
8726
- "aria-valuenow": Math.min(scale, Math.max(0, reading)),
8727
- "aria-valuetext": printed
8728
- } : { "aria-hidden": true },
8729
- className: isMeter && paint ? `relative mt-2 ${barHeight} rounded-full bg-gray-200` : `relative mt-2 ${barHeight} rounded-full border border-dashed border-gray-300 bg-transparent`,
8730
- children: [
8731
- isMeter && // Grey when unjudged: the magnitude is real, the verdict is not ours.
8732
- /* @__PURE__ */ jsx(
8733
- "div",
8734
- {
8735
- className: `absolute inset-y-0 left-0 rounded-full ${paint ? paint.fill : "bg-gray-400"}`,
8736
- style: { width: `${width}%` }
8737
- }
8738
- ),
8739
- showTicks && scale != null && warnAt != null && /* @__PURE__ */ jsx(Tick, { at: clampPct(warnAt / scale * 100), title: `warning \u2265 ${warnAt}`, className: "bg-gray-400 opacity-75" }),
8740
- showTicks && scale != null && critAt != null && /* @__PURE__ */ jsx(Tick, { at: clampPct(critAt / scale * 100), title: `critical \u2265 ${critAt}`, className: "bg-gray-500" })
8741
- ]
8742
- }
8743
- );
8744
- const valueInk = paint ? paint.ink : reading != null ? "text-gray-900" : "text-gray-400";
8745
- if (size === "md") {
8746
- return /* @__PURE__ */ jsxs("div", { className, children: [
8747
- label != null && /* @__PURE__ */ jsx("div", { className: "text-[10px] font-medium uppercase tracking-wide text-gray-500", children: label }),
8748
- /* @__PURE__ */ jsxs("div", { className: "mt-1 flex items-baseline gap-2", children: [
8749
- /* @__PURE__ */ jsx("span", { className: `text-2xl font-semibold leading-[30px] tabular-nums ${valueInk}`, children: printed }),
8750
- /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate text-[11px] tabular-nums text-gray-400", children: reading != null ? detail : emptyLabel })
8751
- ] }),
8752
- bar
8753
- ] });
8754
- }
8755
- return /* @__PURE__ */ jsxs("div", { className, children: [
8756
- /* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-2", children: [
8757
- label != null && /* @__PURE__ */ jsx("span", { className: "w-14 shrink-0 truncate text-[10px] font-medium uppercase tracking-wide text-gray-500", children: label }),
8758
- /* @__PURE__ */ jsx("span", { className: `text-sm font-semibold tabular-nums ${valueInk}`, children: printed }),
8759
- /* @__PURE__ */ jsx("span", { className: "ml-auto min-w-0 truncate text-[10px] tabular-nums text-gray-400", children: reading != null ? detail : emptyLabel })
8760
- ] }),
8761
- bar
8762
- ] });
8763
- }
8764
-
8765
5857
  // src/shell/perfDescribe.ts
8766
5858
  var TOP_GROUPS = 3;
8767
5859
  var fps = (n) => n > 0 ? `${n.toFixed(0)} fps` : "stalled";
@@ -9395,7 +6487,7 @@ function BulkImportGrid({ columns, onImport, description, mergeDuplicates = fals
9395
6487
  error && /* @__PURE__ */ jsx("p", { className: "text-xs text-red-600 px-4 py-1", children: error })
9396
6488
  ] });
9397
6489
  }
9398
- var BTN2 = "text-xs text-gray-500 hover:text-gray-700 disabled:opacity-40 disabled:hover:text-gray-500 disabled:cursor-default";
6490
+ var BTN = "text-xs text-gray-500 hover:text-gray-700 disabled:opacity-40 disabled:hover:text-gray-500 disabled:cursor-default";
9399
6491
  function UndoControls({ className = "" }) {
9400
6492
  const { undo, redo, canUndo, canRedo, undoLabel, redoLabel, enabled } = useUndo();
9401
6493
  if (!enabled) return null;
@@ -9407,131 +6499,21 @@ function UndoControls({ className = "" }) {
9407
6499
  onClick: undo,
9408
6500
  disabled: !canUndo,
9409
6501
  title: canUndo ? `Undo ${undoLabel} (${CMD_Z})` : "Nothing to undo",
9410
- className: BTN2,
9411
- children: "Undo"
9412
- }
9413
- ),
9414
- /* @__PURE__ */ jsx(
9415
- "button",
9416
- {
9417
- type: "button",
9418
- onClick: redo,
9419
- disabled: !canRedo,
9420
- title: canRedo ? `Redo ${redoLabel} (${CMD_SHIFT_Z})` : "Nothing to redo",
9421
- className: BTN2,
9422
- children: "Redo"
9423
- }
9424
- )
9425
- ] });
9426
- }
9427
- var C20 = 33;
9428
- var C40 = 67;
9429
- var toInt = (v) => typeof v === "number" ? v : parseInt(String(v ?? "")) || 0;
9430
- function ContainerFillChart({
9431
- items,
9432
- getVolume,
9433
- qtyField = "instruction",
9434
- showNewIndicator = false,
9435
- getInstructionQty = (item) => item.quantity,
9436
- getActualQty = (item) => item.actual_qty,
9437
- isFilled,
9438
- isNew = (item) => Boolean(item._isNew),
9439
- className,
9440
- style
9441
- }) {
9442
- const filledLines = isFilled ? items.filter(isFilled) : items;
9443
- const qtyInstr = (l) => toInt(getInstructionQty(l));
9444
- const qtyActual = (l) => toInt(getActualQty(l));
9445
- const hasActual = filledLines.some((l) => getActualQty(l) != null && qtyActual(l) > 0);
9446
- const qtyOf = (l) => {
9447
- if (hasActual) return qtyInstr(l);
9448
- return qtyField === "actual" ? getActualQty(l) ?? getInstructionQty(l) ?? 0 : getInstructionQty(l) ?? 0;
9449
- };
9450
- const totalQty = filledLines.reduce((s, l) => s + toInt(qtyOf(l)), 0);
9451
- const newCount = filledLines.filter((l) => isNew(l)).length;
9452
- const newQty = filledLines.filter((l) => isNew(l)).reduce((s, l) => s + toInt(qtyOf(l)), 0);
9453
- const totalQtyInstr = filledLines.reduce((s, l) => s + qtyInstr(l), 0);
9454
- const totalQtyActual = filledLines.reduce((s, l) => s + qtyActual(l), 0);
9455
- const totalVolumeInstr = filledLines.reduce((s, l) => s + getVolume(l) * qtyInstr(l), 0);
9456
- const totalVolumeActual = filledLines.reduce((s, l) => s + getVolume(l) * qtyActual(l), 0);
9457
- const totalVolume = hasActual ? Math.max(totalVolumeInstr, totalVolumeActual) : qtyField === "actual" ? totalVolumeActual || totalVolumeInstr : totalVolumeInstr;
9458
- const useType = totalVolume <= C20 * 1.05 ? "20ft" : "40ft";
9459
- const containerCap = useType === "20ft" ? C20 : C40;
9460
- const totalContainers = totalVolume > 0 ? Math.max(1, Math.ceil(totalVolume / containerCap)) : 0;
9461
- const totalCap = totalContainers * containerCap;
9462
- const isOptimal = totalVolume > 0 && totalVolume >= totalCap * 0.95 && totalVolume <= totalCap * 1.1;
9463
- if (filledLines.length === 0) return null;
9464
- const fillFor = (volume, idx) => containerCap > 0 ? Math.max(0, Math.min((volume - idx * containerCap) / containerCap * 100, 100)) : 0;
9465
- return /* @__PURE__ */ jsxs("div", { className: `mt-2 px-1 space-y-2 ${className ?? ""}`.trim(), style, children: [
9466
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between text-xs text-gray-500", children: [
9467
- /* @__PURE__ */ jsxs("span", { children: [
9468
- filledLines.length,
9469
- " items \xB7 ",
9470
- totalQty,
9471
- " pcs",
9472
- hasActual && totalQtyInstr !== totalQtyActual && /* @__PURE__ */ jsxs("span", { className: "ml-1 text-gray-400", children: [
9473
- "(",
9474
- totalQtyInstr,
9475
- " instr / ",
9476
- totalQtyActual,
9477
- " loaded)"
9478
- ] })
9479
- ] }),
9480
- showNewIndicator && newCount > 0 && /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1 text-green-600", children: [
9481
- /* @__PURE__ */ jsx("span", { className: "w-1.5 h-1.5 rounded-full bg-green-500" }),
9482
- newCount,
9483
- " new (",
9484
- newQty,
9485
- " pcs)"
9486
- ] })
9487
- ] }),
9488
- totalVolume > 0 ? /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
9489
- Array.from({ length: totalContainers }).map((_, i) => {
9490
- const instrPct = fillFor(totalVolumeInstr, i);
9491
- const actualPct = fillFor(totalVolumeActual, i);
9492
- const dual = hasActual;
9493
- const longerPct = Math.max(instrPct, actualPct);
9494
- const shorterPct = Math.min(instrPct, actualPct);
9495
- const longerIsInstr = instrPct >= actualPct;
9496
- const longerColor = dual ? longerIsInstr ? "bg-blue-500" : "bg-green-500" : "bg-blue-500";
9497
- const shorterColor = dual ? longerIsInstr ? "bg-green-500" : "bg-blue-500" : "";
9498
- return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
9499
- /* @__PURE__ */ jsx("span", { className: "text-[10px] text-gray-400 w-8 shrink-0", children: useType }),
9500
- /* @__PURE__ */ jsxs("div", { className: "relative flex-1 h-4 bg-gray-100 rounded-full overflow-hidden border border-gray-200", children: [
9501
- /* @__PURE__ */ jsx("div", { className: `absolute inset-y-0 left-0 ${longerColor} transition-all`, style: { width: `${longerPct}%` } }),
9502
- dual && shorterPct > 0 && /* @__PURE__ */ jsx("div", { className: `absolute inset-y-0 left-0 ${shorterColor} transition-all`, style: { width: `${shorterPct}%` } })
9503
- ] }),
9504
- /* @__PURE__ */ jsx("span", { className: "text-[10px] text-gray-500 w-16 text-right", children: dual ? `${Math.min(totalVolumeActual - i * containerCap, containerCap).toFixed(1)} / ${containerCap} m\xB3` : `${Math.min(totalVolumeInstr - i * containerCap, containerCap).toFixed(1)} / ${containerCap} m\xB3` })
9505
- ] }, `bar-${i}`);
9506
- }),
9507
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between text-xs pt-0.5", children: [
9508
- /* @__PURE__ */ jsxs("span", { className: "text-gray-500 inline-flex items-center gap-1", children: [
9509
- "Total:",
9510
- hasActual ? /* @__PURE__ */ jsxs(Fragment, { children: [
9511
- /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1 text-blue-700", children: [
9512
- /* @__PURE__ */ jsx("span", { className: "inline-block w-2 h-2 rounded-full bg-blue-500" }),
9513
- totalVolumeInstr.toFixed(2),
9514
- " m\xB3 instruction"
9515
- ] }),
9516
- /* @__PURE__ */ jsx("span", { className: "text-gray-400", children: "/" }),
9517
- /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1 text-green-700", children: [
9518
- /* @__PURE__ */ jsx("span", { className: "inline-block w-2 h-2 rounded-full bg-green-500" }),
9519
- totalVolumeActual.toFixed(2),
9520
- " m\xB3 loaded"
9521
- ] })
9522
- ] }) : /* @__PURE__ */ jsxs("b", { className: "text-gray-700", children: [
9523
- totalVolume.toFixed(2),
9524
- " m\xB3"
9525
- ] })
9526
- ] }),
9527
- /* @__PURE__ */ jsxs("span", { className: `font-medium ${isOptimal ? "text-green-600" : "text-gray-500"}`, children: [
9528
- isOptimal && "\u2713 ",
9529
- totalContainers,
9530
- " \xD7 ",
9531
- useType
9532
- ] })
9533
- ] })
9534
- ] }) : /* @__PURE__ */ jsx("div", { className: "text-xs text-gray-400", children: "Volume: N/A (no volume data for these part numbers)" })
6502
+ className: BTN,
6503
+ children: "Undo"
6504
+ }
6505
+ ),
6506
+ /* @__PURE__ */ jsx(
6507
+ "button",
6508
+ {
6509
+ type: "button",
6510
+ onClick: redo,
6511
+ disabled: !canRedo,
6512
+ title: canRedo ? `Redo ${redoLabel} (${CMD_SHIFT_Z})` : "Nothing to redo",
6513
+ className: BTN,
6514
+ children: "Redo"
6515
+ }
6516
+ )
9535
6517
  ] });
9536
6518
  }
9537
6519
  var DEFAULT_POLL_MS = 15e3;
@@ -9965,911 +6947,6 @@ function PdfActionButton({
9965
6947
  )
9966
6948
  ] });
9967
6949
  }
9968
- var DAY_MS = 864e5;
9969
- function toDayMs(dateStr) {
9970
- if (!dateStr) return null;
9971
- const t = new Date(dateStr).getTime();
9972
- return Number.isFinite(t) ? t : null;
9973
- }
9974
- function fmtSliderDate(ms) {
9975
- return formatDate(new Date(ms).toISOString().slice(0, 10));
9976
- }
9977
- var KIND_STYLES = {
9978
- default: {
9979
- base: "h-2.5 w-2.5 rounded-full bg-blue-500 border-white",
9980
- active: "bg-blue-700",
9981
- placeholder: "h-2.5 w-2.5 rounded-full bg-white border-gray-300 hover:border-gray-500"
9982
- },
9983
- dfm: {
9984
- base: "h-2.5 w-2.5 rounded-sm bg-amber-500 border-white",
9985
- active: "bg-amber-600",
9986
- placeholder: "h-2.5 w-2.5 rounded-sm bg-white border-amber-300 hover:border-amber-500"
9987
- },
9988
- shipment: {
9989
- base: "h-2.5 w-2.5 rotate-45 bg-emerald-500 border-white",
9990
- active: "bg-emerald-600",
9991
- placeholder: "h-2.5 w-2.5 rotate-45 bg-white border-emerald-300 hover:border-emerald-500"
9992
- },
9993
- testing: {
9994
- base: "h-3 w-3 rounded-full bg-violet-500 border-white",
9995
- active: "bg-violet-700",
9996
- placeholder: "h-3 w-3 rounded-full bg-white border-violet-300 hover:border-violet-500",
9997
- inner: /* @__PURE__ */ jsx("span", { className: "block h-1 w-1 rounded-full bg-white" })
9998
- },
9999
- completion: {
10000
- base: "h-3.5 w-3.5 rounded-full bg-green-600 border-white",
10001
- active: "bg-green-700",
10002
- placeholder: "h-3.5 w-3.5 rounded-full bg-white border-green-300 hover:border-green-500",
10003
- inner: /* @__PURE__ */ jsx("svg", { className: "h-2 w-2 text-white", fill: "none", stroke: "currentColor", strokeWidth: "3", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M5 13l4 4L19 7" }) })
10004
- }
10005
- };
10006
- function MilestoneTimeline({ title, milestones, summary, endDate, phaseLabels }) {
10007
- const [hoveredKey, setHoveredKey] = useState(null);
10008
- const [today] = useState(() => Date.now());
10009
- const realDates = milestones.map((m) => toDayMs(m.date ?? null)).filter((t) => t !== null);
10010
- const explicitEnd = toDayMs(endDate ?? null);
10011
- let startMs;
10012
- let endMs;
10013
- if (realDates.length === 0) {
10014
- endMs = explicitEnd ?? today;
10015
- startMs = endMs - 30 * DAY_MS;
10016
- } else {
10017
- startMs = Math.min(...realDates);
10018
- endMs = explicitEnd ?? Math.max(...realDates);
10019
- if (endMs - startMs < DAY_MS) endMs = startMs + DAY_MS;
10020
- }
10021
- const span = Math.max(endMs - startMs, DAY_MS);
10022
- const totalLeadDays = realDates.length > 0 ? Math.max(0, Math.round((endMs - startMs) / DAY_MS)) : 0;
10023
- const autoSummary = totalLeadDays > 0 ? `${totalLeadDays.toLocaleString()} day${totalLeadDays === 1 ? "" : "s"} lead time` : void 0;
10024
- const renderSummary = summary ?? autoSummary;
10025
- const placed = milestones.map((m, i) => {
10026
- const ms = toDayMs(m.date ?? null);
10027
- if (ms !== null) return { ...m, ms, isPlaceholder: false };
10028
- let prevMs = null;
10029
- for (let j = i - 1; j >= 0; j--) {
10030
- const v = toDayMs(milestones[j].date ?? null);
10031
- if (v !== null) {
10032
- prevMs = v;
10033
- break;
10034
- }
10035
- }
10036
- let nextMs = null;
10037
- for (let j = i + 1; j < milestones.length; j++) {
10038
- const v = toDayMs(milestones[j].date ?? null);
10039
- if (v !== null) {
10040
- nextMs = v;
10041
- break;
10042
- }
10043
- }
10044
- let fallback;
10045
- if (prevMs !== null && nextMs !== null) fallback = (prevMs + nextMs) / 2;
10046
- else if (prevMs !== null) fallback = Math.min(prevMs + DAY_MS, endMs);
10047
- else if (nextMs !== null) fallback = Math.max(nextMs - DAY_MS, startMs);
10048
- else fallback = startMs + span * (i + 1) / (milestones.length + 1);
10049
- return { ...m, ms: fallback, isPlaceholder: true };
10050
- });
10051
- const todayMs = today;
10052
- const phases = (() => {
10053
- const groups = {};
10054
- for (const m of placed) {
10055
- if (!m.phase) continue;
10056
- (groups[m.phase] = groups[m.phase] || []).push(m);
10057
- }
10058
- return Object.entries(groups).filter(([, ms]) => ms.length >= 2).map(([key, ms]) => ({
10059
- key,
10060
- label: phaseLabels?.[key] ?? key,
10061
- minMs: Math.min(...ms.map((m) => m.ms)),
10062
- maxMs: Math.max(...ms.map((m) => m.ms))
10063
- }));
10064
- })();
10065
- return /* @__PURE__ */ jsxs("div", { className: "shrink-0", children: [
10066
- /* @__PURE__ */ jsx("div", { className: "flex items-end justify-between mb-1 gap-2", children: /* @__PURE__ */ jsxs("h4", { className: "text-xs font-semibold text-gray-500 uppercase tracking-wide", children: [
10067
- title,
10068
- renderSummary && /* @__PURE__ */ jsx("span", { className: "ml-2 text-gray-400 normal-case", children: renderSummary })
10069
- ] }) }),
10070
- /* @__PURE__ */ jsx("div", { className: "border border-gray-200 rounded-lg bg-gray-50", children: /* @__PURE__ */ jsx("div", { className: "select-none", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 px-3 py-3", children: [
10071
- placed.length > 0 && (() => {
10072
- const m = placed[0];
10073
- return /* @__PURE__ */ jsxs("div", { className: `text-[10px] leading-tight whitespace-nowrap text-right shrink-0 pointer-events-none ${m.isPlaceholder ? "text-gray-400 italic" : "text-gray-700"}`, children: [
10074
- /* @__PURE__ */ jsx("div", { className: "font-medium", children: m.label }),
10075
- !m.isPlaceholder && /* @__PURE__ */ jsx("div", { className: "text-[9px] text-gray-400 font-normal", children: fmtSliderDate(m.ms) })
10076
- ] });
10077
- })(),
10078
- /* @__PURE__ */ jsxs("div", { className: "relative flex-1 flex flex-col justify-center", children: [
10079
- /* @__PURE__ */ jsx("div", { className: "relative h-7 mb-1", children: placed.map((m, i) => {
10080
- if (i === 0 || i === placed.length - 1) return null;
10081
- if (i % 2 !== 0) return null;
10082
- const p = (m.ms - startMs) / span * 100;
10083
- if (p < -0.5 || p > 100.5) return null;
10084
- const isHovered = m.key === hoveredKey;
10085
- return /* @__PURE__ */ jsxs(
10086
- "div",
10087
- {
10088
- className: `absolute bottom-0 -translate-x-1/2 text-center text-[10px] leading-tight whitespace-nowrap pointer-events-none ${m.isPlaceholder ? "text-gray-400 italic" : isHovered ? "text-blue-700 font-semibold" : "text-gray-700"}`,
10089
- style: { left: `${Math.max(0, Math.min(100, p))}%` },
10090
- children: [
10091
- /* @__PURE__ */ jsx("div", { children: m.label }),
10092
- !m.isPlaceholder && /* @__PURE__ */ jsx("div", { className: "text-[9px] text-gray-400 font-normal", children: fmtSliderDate(m.ms) })
10093
- ]
10094
- },
10095
- `above-${m.key}`
10096
- );
10097
- }) }),
10098
- /* @__PURE__ */ jsxs("div", { className: "relative h-2 bg-gray-200 rounded-full", children: [
10099
- realDates.length > 0 && (() => {
10100
- const lastReal = Math.min(Math.max(...realDates), todayMs);
10101
- const fillPct = (lastReal - startMs) / span * 100;
10102
- return /* @__PURE__ */ jsx(
10103
- "div",
10104
- {
10105
- className: "absolute top-0 left-0 h-full bg-blue-300 rounded-full pointer-events-none",
10106
- style: { width: `${Math.max(0, Math.min(100, fillPct))}%` }
10107
- }
10108
- );
10109
- })(),
10110
- todayMs > startMs && todayMs < endMs && /* @__PURE__ */ jsx(
10111
- "div",
10112
- {
10113
- className: "absolute -top-1 h-4 w-px bg-gray-400 pointer-events-none",
10114
- style: { left: `${(todayMs - startMs) / span * 100}%` },
10115
- title: `Today \u2014 ${fmtSliderDate(todayMs)}`
10116
- }
10117
- ),
10118
- placed.map((m) => {
10119
- const p = (m.ms - startMs) / span * 100;
10120
- if (p < -0.5 || p > 100.5) return null;
10121
- const isHovered = m.key === hoveredKey;
10122
- const style = KIND_STYLES[m.kind ?? "default"];
10123
- const dotClass = m.isPlaceholder ? style.placeholder : isHovered ? `${style.base} ${style.active}` : style.base;
10124
- const Tag = m.onClick ? "button" : "div";
10125
- return /* @__PURE__ */ jsx(
10126
- Tag,
10127
- {
10128
- type: m.onClick ? "button" : void 0,
10129
- onMouseEnter: () => setHoveredKey(m.key),
10130
- onMouseLeave: () => setHoveredKey((prev) => prev === m.key ? null : prev),
10131
- onClick: m.onClick,
10132
- className: `absolute top-1/2 -translate-x-1/2 -translate-y-1/2 border-2 shadow flex items-center justify-center ${dotClass} ${m.onClick ? "cursor-pointer" : "cursor-default"}`,
10133
- style: { left: `${Math.max(0, Math.min(100, p))}%` },
10134
- "aria-label": `${m.label}${m.isPlaceholder ? " (not reached yet)" : ` on ${fmtSliderDate(m.ms)}`}`,
10135
- title: `${m.label}${m.isPlaceholder ? " \u2014 not reached yet" : ` \u2022 ${fmtSliderDate(m.ms)}`}${m.detail ? ` \u2014 ${m.detail}` : ""}`,
10136
- children: !m.isPlaceholder && style.inner
10137
- },
10138
- m.key
10139
- );
10140
- })
10141
- ] }),
10142
- /* @__PURE__ */ jsx("div", { className: "relative h-7 mt-1", children: placed.map((m, i) => {
10143
- if (i === 0 || i === placed.length - 1) return null;
10144
- if (i % 2 === 0) return null;
10145
- const p = (m.ms - startMs) / span * 100;
10146
- if (p < -0.5 || p > 100.5) return null;
10147
- const isHovered = m.key === hoveredKey;
10148
- return /* @__PURE__ */ jsxs(
10149
- "div",
10150
- {
10151
- className: `absolute top-0 -translate-x-1/2 text-center text-[10px] leading-tight whitespace-nowrap pointer-events-none ${m.isPlaceholder ? "text-gray-400 italic" : isHovered ? "text-blue-700 font-semibold" : "text-gray-700"}`,
10152
- style: { left: `${Math.max(0, Math.min(100, p))}%` },
10153
- children: [
10154
- /* @__PURE__ */ jsx("div", { children: m.label }),
10155
- !m.isPlaceholder && /* @__PURE__ */ jsx("div", { className: "text-[9px] text-gray-400 font-normal", children: fmtSliderDate(m.ms) })
10156
- ]
10157
- },
10158
- `below-${m.key}`
10159
- );
10160
- }) }),
10161
- phases.length > 0 && /* @__PURE__ */ jsx("div", { className: "relative h-5 mt-2", children: phases.map((ph) => {
10162
- const leftPct = Math.max(0, (ph.minMs - startMs) / span * 100);
10163
- const rightPct = Math.min(100, (ph.maxMs - startMs) / span * 100);
10164
- const widthPct = Math.max(rightPct - leftPct, 0.5);
10165
- const centerPct = leftPct + widthPct / 2;
10166
- return /* @__PURE__ */ jsxs("div", { children: [
10167
- /* @__PURE__ */ jsx(
10168
- "div",
10169
- {
10170
- className: "absolute border-l border-r border-b border-slate-300 rounded-b-md pointer-events-none",
10171
- style: { left: `${leftPct}%`, width: `${widthPct}%`, top: 0, height: 8 }
10172
- }
10173
- ),
10174
- /* @__PURE__ */ jsxs(
10175
- "div",
10176
- {
10177
- className: "absolute -translate-x-1/2 text-[9px] italic text-slate-500 whitespace-nowrap pointer-events-none",
10178
- style: { left: `${centerPct}%`, top: 9 },
10179
- children: [
10180
- ph.label,
10181
- " ",
10182
- /* @__PURE__ */ jsx("span", { className: "text-slate-400 not-italic", children: "\xB7 parallel" })
10183
- ]
10184
- }
10185
- )
10186
- ] }, `phase-${ph.key}`);
10187
- }) })
10188
- ] }),
10189
- placed.length > 1 && (() => {
10190
- const m = placed[placed.length - 1];
10191
- return /* @__PURE__ */ jsxs("div", { className: `text-[10px] leading-tight whitespace-nowrap text-left shrink-0 pointer-events-none ${m.isPlaceholder ? "text-gray-400 italic" : "text-gray-700"}`, children: [
10192
- /* @__PURE__ */ jsx("div", { className: "font-medium", children: m.label }),
10193
- !m.isPlaceholder && /* @__PURE__ */ jsx("div", { className: "text-[9px] text-gray-400 font-normal", children: fmtSliderDate(m.ms) })
10194
- ] });
10195
- })()
10196
- ] }) }) })
10197
- ] });
10198
- }
10199
- function Sparkline({
10200
- data,
10201
- width = 120,
10202
- height = 32,
10203
- stroke = "currentColor",
10204
- fill,
10205
- strokeWidth = 1.5,
10206
- showDots = false,
10207
- className,
10208
- style
10209
- }) {
10210
- if (data.length === 0) return null;
10211
- const max = Math.max(...data);
10212
- const min = Math.min(...data);
10213
- const span = max - min || 1;
10214
- const pad2 = strokeWidth + (showDots ? 2 : 0);
10215
- const y = (v) => height - pad2 - (v - min) / span * (height - pad2 * 2);
10216
- const pts = data.length === 1 ? [[0, height / 2], [width, height / 2]] : data.map((v, i) => [i * width / (data.length - 1), y(v)]);
10217
- const line = pts.map(([x, yy], i) => `${i === 0 ? "M" : "L"}${x.toFixed(2)},${yy.toFixed(2)}`).join(" ");
10218
- const area = `${line} L${width.toFixed(2)},${height} L0,${height} Z`;
10219
- return /* @__PURE__ */ jsxs(
10220
- "svg",
10221
- {
10222
- className,
10223
- style,
10224
- width,
10225
- height,
10226
- viewBox: `0 0 ${width} ${height}`,
10227
- preserveAspectRatio: "none",
10228
- role: "img",
10229
- "aria-hidden": "true",
10230
- children: [
10231
- fill && /* @__PURE__ */ jsx("path", { d: area, fill, stroke: "none" }),
10232
- /* @__PURE__ */ jsx("path", { d: line, fill: "none", stroke, strokeWidth, strokeLinejoin: "round", strokeLinecap: "round" }),
10233
- showDots && pts.map(([x, yy], i) => /* @__PURE__ */ jsx("circle", { cx: x, cy: yy, r: strokeWidth + 0.5, fill: stroke }, i))
10234
- ]
10235
- }
10236
- );
10237
- }
10238
- function BarChart({
10239
- data,
10240
- labels,
10241
- height = 120,
10242
- color = "currentColor",
10243
- colors,
10244
- max,
10245
- gap = 6,
10246
- className,
10247
- style
10248
- }) {
10249
- if (data.length === 0) return null;
10250
- const peak = max ?? (Math.max(...data, 0) || 1);
10251
- return /* @__PURE__ */ jsxs("div", { className, style, children: [
10252
- /* @__PURE__ */ jsx("div", { className: "flex items-end", style: { height, gap }, children: data.map((v, i) => /* @__PURE__ */ jsx("div", { className: "flex flex-1 flex-col justify-end", style: { height: "100%" }, children: /* @__PURE__ */ jsx(
10253
- "div",
10254
- {
10255
- className: "w-full rounded-t",
10256
- style: {
10257
- height: `${Math.max(0, v / peak * 100)}%`,
10258
- minHeight: v > 0 ? 2 : 0,
10259
- backgroundColor: colors?.[i] ?? color
10260
- },
10261
- title: labels?.[i] != null ? `${labels[i]}: ${v}` : String(v)
10262
- }
10263
- ) }, i)) }),
10264
- labels && /* @__PURE__ */ jsx("div", { className: "mt-1 flex", style: { gap }, children: labels.map((l, i) => /* @__PURE__ */ jsx("div", { className: "flex-1 truncate text-center text-[10px] text-gray-400", children: l }, i)) })
10265
- ] });
10266
- }
10267
- var PALETTE = ["#3b82f6", "#22c55e", "#f59e0b", "#ef4444", "#6366f1", "#14b8a6", "#a855f7", "#64748b"];
10268
- function DonutChart({ segments, size = 120, thickness = 16, centerLabel, className, style }) {
10269
- const total = segments.reduce((s, x) => s + Math.max(0, x.value), 0) || 1;
10270
- const r = (size - thickness) / 2;
10271
- const c = size / 2;
10272
- const circ = 2 * Math.PI * r;
10273
- let offset = 0;
10274
- return /* @__PURE__ */ jsxs(
10275
- "div",
10276
- {
10277
- className: `relative inline-flex items-center justify-center ${className ?? ""}`.trim(),
10278
- style: { width: size, height: size, ...style },
10279
- children: [
10280
- /* @__PURE__ */ jsxs("svg", { width: size, height: size, viewBox: `0 0 ${size} ${size}`, role: "img", "aria-hidden": "true", style: { transform: "rotate(-90deg)" }, children: [
10281
- /* @__PURE__ */ jsx("circle", { cx: c, cy: c, r, fill: "none", stroke: "rgba(148,163,184,0.25)", strokeWidth: thickness }),
10282
- segments.map((s, i) => {
10283
- const len = Math.max(0, s.value) / total * circ;
10284
- const el = /* @__PURE__ */ jsx(
10285
- "circle",
10286
- {
10287
- cx: c,
10288
- cy: c,
10289
- r,
10290
- fill: "none",
10291
- stroke: s.color ?? PALETTE[i % PALETTE.length],
10292
- strokeWidth: thickness,
10293
- strokeDasharray: `${len} ${circ - len}`,
10294
- strokeDashoffset: -offset
10295
- },
10296
- i
10297
- );
10298
- offset += len;
10299
- return el;
10300
- })
10301
- ] }),
10302
- centerLabel != null && /* @__PURE__ */ jsx("div", { className: "absolute text-center text-sm font-semibold text-gray-900", children: centerLabel })
10303
- ]
10304
- }
10305
- );
10306
- }
10307
- var STATUS_GROUPS = {
10308
- paid: "success",
10309
- shipped: "active",
10310
- processing: "queued",
10311
- refunded: "neutral",
10312
- overdue: "danger"
10313
- };
10314
- var REVENUE = [12, 18, 14, 22, 19, 27, 24, 31, 28, 35, 30, 38];
10315
- var MONTHS = ["J", "F", "M", "A", "M", "J", "J", "A", "S", "O", "N", "D"];
10316
- var ORDERS = [
10317
- { id: "#3201", customer: "Alice Nguyen", total: "$1,204", status: "paid" },
10318
- { id: "#3202", customer: "Marco Reyes", total: "$642", status: "shipped" },
10319
- { id: "#3203", customer: "Priya Patel", total: "$2,980", status: "processing" },
10320
- { id: "#3204", customer: "Tom Becker", total: "$318", status: "overdue" },
10321
- { id: "#3205", customer: "Sara Lind", total: "$540", status: "refunded" }
10322
- ];
10323
- function DashboardTemplate() {
10324
- return /* @__PURE__ */ jsx(StatusBadgeProvider, { groups: STATUS_GROUPS, children: /* @__PURE__ */ jsx("div", { className: "h-full overflow-auto bg-gray-50 p-6", children: /* @__PURE__ */ jsxs("div", { className: "mx-auto max-w-6xl space-y-6", children: [
10325
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-4", children: [
10326
- /* @__PURE__ */ jsxs("div", { children: [
10327
- /* @__PURE__ */ jsx("h1", { className: "text-xl font-semibold text-gray-900", children: "Dashboard" }),
10328
- /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-500", children: "Overview of your store this month" })
10329
- ] }),
10330
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
10331
- /* @__PURE__ */ jsx(Button_default, { variant: "secondary", size: "sm", children: "Export" }),
10332
- /* @__PURE__ */ jsx(Button_default, { size: "sm", children: "New report" })
10333
- ] })
10334
- ] }),
10335
- /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4", children: [
10336
- /* @__PURE__ */ jsx(StatCard, { label: "Revenue", value: "$38.2k", delta: { value: "12%", direction: "up" } }),
10337
- /* @__PURE__ */ jsx(StatCard, { label: "Orders", value: "1,204", delta: { value: "4%", direction: "up" } }),
10338
- /* @__PURE__ */ jsx(StatCard, { label: "Customers", value: "892", delta: { value: "2%", direction: "down" } }),
10339
- /* @__PURE__ */ jsx(StatCard, { label: "Refunds", value: "$1.1k", delta: { value: "0%", direction: "flat" } })
10340
- ] }),
10341
- /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 gap-4 lg:grid-cols-3", children: [
10342
- /* @__PURE__ */ jsx(Card, { header: "Revenue", className: "lg:col-span-2", children: /* @__PURE__ */ jsx("div", { className: "text-blue-600", children: /* @__PURE__ */ jsx(BarChart, { data: REVENUE, labels: MONTHS, height: 160 }) }) }),
10343
- /* @__PURE__ */ jsx(Card, { header: "Traffic sources", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-4", children: [
10344
- /* @__PURE__ */ jsx(
10345
- DonutChart,
10346
- {
10347
- size: 120,
10348
- segments: [
10349
- { label: "Direct", value: 45 },
10350
- { label: "Search", value: 30 },
10351
- { label: "Social", value: 15 },
10352
- { label: "Email", value: 10 }
10353
- ],
10354
- centerLabel: /* @__PURE__ */ jsx("span", { className: "text-gray-900", children: "100%" })
10355
- }
10356
- ),
10357
- /* @__PURE__ */ jsxs("ul", { className: "space-y-1.5 text-sm text-gray-600", children: [
10358
- /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-2", children: [
10359
- /* @__PURE__ */ jsx(Dot, { c: "#3b82f6" }),
10360
- "Direct \xB7 45%"
10361
- ] }),
10362
- /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-2", children: [
10363
- /* @__PURE__ */ jsx(Dot, { c: "#22c55e" }),
10364
- "Search \xB7 30%"
10365
- ] }),
10366
- /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-2", children: [
10367
- /* @__PURE__ */ jsx(Dot, { c: "#f59e0b" }),
10368
- "Social \xB7 15%"
10369
- ] }),
10370
- /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-2", children: [
10371
- /* @__PURE__ */ jsx(Dot, { c: "#ef4444" }),
10372
- "Email \xB7 10%"
10373
- ] })
10374
- ] })
10375
- ] }) })
10376
- ] }),
10377
- /* @__PURE__ */ jsx(Card, { header: "Recent orders", padded: false, children: /* @__PURE__ */ jsxs("table", { className: "w-full text-sm", children: [
10378
- /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { className: "border-b border-gray-100 text-left text-xs uppercase tracking-wide text-gray-400", children: [
10379
- /* @__PURE__ */ jsx("th", { className: "px-4 py-2 font-medium", children: "Order" }),
10380
- /* @__PURE__ */ jsx("th", { className: "px-4 py-2 font-medium", children: "Customer" }),
10381
- /* @__PURE__ */ jsx("th", { className: "px-4 py-2 font-medium", children: "Total" }),
10382
- /* @__PURE__ */ jsx("th", { className: "px-4 py-2 font-medium", children: "Status" })
10383
- ] }) }),
10384
- /* @__PURE__ */ jsx("tbody", { className: "divide-y divide-gray-100", children: ORDERS.map((o) => /* @__PURE__ */ jsxs("tr", { className: "text-gray-700", children: [
10385
- /* @__PURE__ */ jsx("td", { className: "px-4 py-2 font-mono text-xs text-gray-500", children: o.id }),
10386
- /* @__PURE__ */ jsx("td", { className: "px-4 py-2", children: /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
10387
- /* @__PURE__ */ jsx(Avatar, { size: "xs", name: o.customer }),
10388
- o.customer
10389
- ] }) }),
10390
- /* @__PURE__ */ jsx("td", { className: "px-4 py-2", children: o.total }),
10391
- /* @__PURE__ */ jsx("td", { className: "px-4 py-2", children: /* @__PURE__ */ jsx(StatusBadge, { status: o.status }) })
10392
- ] }, o.id)) })
10393
- ] }) }),
10394
- /* @__PURE__ */ jsxs("p", { className: "flex items-center gap-2 text-xs text-gray-400", children: [
10395
- /* @__PURE__ */ jsx("span", { className: "text-emerald-600", children: /* @__PURE__ */ jsx(Sparkline, { data: REVENUE, width: 80, height: 20 }) }),
10396
- "Trending up 12% vs last month"
10397
- ] })
10398
- ] }) }) });
10399
- }
10400
- function Dot({ c }) {
10401
- return /* @__PURE__ */ jsx("span", { className: "inline-block h-2.5 w-2.5 rounded-full", style: { backgroundColor: c } });
10402
- }
10403
- var GROUPS = {
10404
- active: "success",
10405
- invited: "queued",
10406
- suspended: "danger",
10407
- pending: "pending"
10408
- };
10409
- var ROWS = [
10410
- { name: "Alice Nguyen", email: "alice@acme.co", role: "Admin", status: "active" },
10411
- { name: "Marco Reyes", email: "marco@acme.co", role: "Editor", status: "invited" },
10412
- { name: "Priya Patel", email: "priya@acme.co", role: "Editor", status: "active" },
10413
- { name: "Tom Becker", email: "tom@acme.co", role: "Viewer", status: "suspended" },
10414
- { name: "Sara Lind", email: "sara@acme.co", role: "Viewer", status: "pending" },
10415
- { name: "Yuki Tanaka", email: "yuki@acme.co", role: "Editor", status: "active" }
10416
- ];
10417
- function DataTablePage() {
10418
- const [page, setPage] = useState(1);
10419
- const [role, setRole] = useState("");
10420
- const [query, setQuery] = useState("");
10421
- return /* @__PURE__ */ jsx(StatusBadgeProvider, { groups: GROUPS, children: /* @__PURE__ */ jsx("div", { className: "h-full overflow-auto bg-gray-50 p-6", children: /* @__PURE__ */ jsxs("div", { className: "mx-auto max-w-5xl space-y-4", children: [
10422
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-4", children: [
10423
- /* @__PURE__ */ jsx("h1", { className: "text-xl font-semibold text-gray-900", children: "Members" }),
10424
- /* @__PURE__ */ jsx(Button_default, { size: "sm", children: "Invite member" })
10425
- ] }),
10426
- /* @__PURE__ */ jsxs(Card, { padded: false, children: [
10427
- /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2 border-b border-gray-100 p-3", children: [
10428
- /* @__PURE__ */ jsx("div", { className: "min-w-48 flex-1", children: /* @__PURE__ */ jsx(Input_default, { value: query, onChange: (e) => setQuery(e.target.value), placeholder: "Search members\u2026", leftIcon: /* @__PURE__ */ jsx(SearchIcon, {}) }) }),
10429
- /* @__PURE__ */ jsx("div", { className: "w-40", children: /* @__PURE__ */ jsx(
10430
- Select_default,
10431
- {
10432
- value: role,
10433
- onChange: setRole,
10434
- placeholder: "All roles",
10435
- options: [
10436
- { value: "admin", label: "Admin" },
10437
- { value: "editor", label: "Editor" },
10438
- { value: "viewer", label: "Viewer" }
10439
- ]
10440
- }
10441
- ) }),
10442
- /* @__PURE__ */ jsx(Button_default, { variant: "secondary", size: "sm", children: "Filter" })
10443
- ] }),
10444
- /* @__PURE__ */ jsxs("table", { className: "w-full text-sm", children: [
10445
- /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { className: "border-b border-gray-100 text-left text-xs uppercase tracking-wide text-gray-400", children: [
10446
- /* @__PURE__ */ jsx("th", { className: "px-4 py-2 font-medium", children: "Name" }),
10447
- /* @__PURE__ */ jsx("th", { className: "px-4 py-2 font-medium", children: "Role" }),
10448
- /* @__PURE__ */ jsx("th", { className: "px-4 py-2 font-medium", children: "Status" }),
10449
- /* @__PURE__ */ jsx("th", { className: "px-4 py-2 font-medium text-right", children: "Actions" })
10450
- ] }) }),
10451
- /* @__PURE__ */ jsx("tbody", { className: "divide-y divide-gray-100", children: ROWS.map((r) => /* @__PURE__ */ jsxs("tr", { className: "text-gray-700 hover:bg-gray-50", children: [
10452
- /* @__PURE__ */ jsxs("td", { className: "px-4 py-2.5", children: [
10453
- /* @__PURE__ */ jsx("div", { className: "font-medium text-gray-900", children: r.name }),
10454
- /* @__PURE__ */ jsx("div", { className: "text-xs text-gray-400", children: r.email })
10455
- ] }),
10456
- /* @__PURE__ */ jsx("td", { className: "px-4 py-2.5", children: r.role }),
10457
- /* @__PURE__ */ jsx("td", { className: "px-4 py-2.5", children: /* @__PURE__ */ jsx(StatusBadge, { status: r.status }) }),
10458
- /* @__PURE__ */ jsx("td", { className: "px-4 py-2.5 text-right", children: /* @__PURE__ */ jsx(Button_default, { variant: "ghost", size: "sm", children: "Edit" }) })
10459
- ] }, r.email)) })
10460
- ] }),
10461
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-t border-gray-100 p-3", children: [
10462
- /* @__PURE__ */ jsx("span", { className: "text-xs text-gray-400", children: "Showing 1\u20136 of 48" }),
10463
- /* @__PURE__ */ jsx(Pagination, { page, pageCount: 8, onPageChange: setPage, showEdges: true })
10464
- ] })
10465
- ] })
10466
- ] }) }) });
10467
- }
10468
- function SearchIcon() {
10469
- return /* @__PURE__ */ jsxs("svg", { className: "h-4 w-4", viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", strokeWidth: "2", "aria-hidden": "true", children: [
10470
- /* @__PURE__ */ jsx("circle", { cx: "9", cy: "9", r: "6" }),
10471
- /* @__PURE__ */ jsx("path", { d: "M14 14l3 3", strokeLinecap: "round" })
10472
- ] });
10473
- }
10474
- function FormLayoutPage() {
10475
- const [plan, setPlan] = useState("pro");
10476
- const [newsletter, setNewsletter] = useState(true);
10477
- const [country, setCountry] = useState("us");
10478
- return /* @__PURE__ */ jsx("div", { className: "h-full overflow-auto bg-gray-50 p-6", children: /* @__PURE__ */ jsxs("form", { className: "mx-auto max-w-2xl space-y-5", onSubmit: (e) => e.preventDefault(), children: [
10479
- /* @__PURE__ */ jsxs("div", { children: [
10480
- /* @__PURE__ */ jsx("h1", { className: "text-xl font-semibold text-gray-900", children: "Account settings" }),
10481
- /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-500", children: "Update your profile and preferences." })
10482
- ] }),
10483
- /* @__PURE__ */ jsx(Card, { header: "Profile", children: /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
10484
- /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 gap-4 sm:grid-cols-2", children: [
10485
- /* @__PURE__ */ jsx(FormField, { label: "First name", htmlFor: "fn", required: true, children: /* @__PURE__ */ jsx(Input_default, { id: "fn", defaultValue: "Alice" }) }),
10486
- /* @__PURE__ */ jsx(FormField, { label: "Last name", htmlFor: "ln", required: true, children: /* @__PURE__ */ jsx(Input_default, { id: "ln", defaultValue: "Nguyen" }) })
10487
- ] }),
10488
- /* @__PURE__ */ jsx(FormField, { label: "Email", htmlFor: "email", hint: "We'll never share your email.", children: /* @__PURE__ */ jsx(Input_default, { id: "email", type: "email", defaultValue: "alice@acme.co" }) }),
10489
- /* @__PURE__ */ jsx(FormField, { label: "Bio", htmlFor: "bio", hint: "Brief description for your profile.", children: /* @__PURE__ */ jsx(Textarea_default, { id: "bio", autoGrow: true, defaultValue: "Product designer at Acme." }) })
10490
- ] }) }),
10491
- /* @__PURE__ */ jsx(Card, { header: "Preferences", children: /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
10492
- /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 gap-4 sm:grid-cols-2", children: [
10493
- /* @__PURE__ */ jsx(FormField, { label: "Country", htmlFor: "country", children: /* @__PURE__ */ jsx(
10494
- Select_default,
10495
- {
10496
- value: country,
10497
- onChange: setCountry,
10498
- options: [
10499
- { value: "us", label: "United States" },
10500
- { value: "de", label: "Germany" },
10501
- { value: "jp", label: "Japan" }
10502
- ]
10503
- }
10504
- ) }),
10505
- /* @__PURE__ */ jsx(FormField, { label: "Plan", children: /* @__PURE__ */ jsxs("div", { className: "space-y-1.5 pt-1", children: [
10506
- /* @__PURE__ */ jsx(Radio_default, { name: "plan", checked: plan === "free", onChange: () => setPlan("free"), label: "Free" }),
10507
- /* @__PURE__ */ jsx(Radio_default, { name: "plan", checked: plan === "pro", onChange: () => setPlan("pro"), label: "Pro" }),
10508
- /* @__PURE__ */ jsx(Radio_default, { name: "plan", checked: plan === "team", onChange: () => setPlan("team"), label: "Team" })
10509
- ] }) })
10510
- ] }),
10511
- /* @__PURE__ */ jsx(Checkbox_default, { checked: newsletter, onChange: setNewsletter, label: "Email me product updates", description: "Occasional news \u2014 no more than once a month." })
10512
- ] }) }),
10513
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-2", children: [
10514
- /* @__PURE__ */ jsx(Button_default, { variant: "secondary", children: "Cancel" }),
10515
- /* @__PURE__ */ jsx(Button_default, { type: "submit", children: "Save changes" })
10516
- ] })
10517
- ] }) });
10518
- }
10519
- var ITEMS = [
10520
- { name: "Mechanical keyboard", qty: 1, price: 129 },
10521
- { name: "USB-C cable (2m)", qty: 2, price: 12 },
10522
- { name: "Laptop stand", qty: 1, price: 48 }
10523
- ];
10524
- function CheckoutTemplate() {
10525
- const [sameAddress, setSameAddress] = useState(true);
10526
- const [country, setCountry] = useState("us");
10527
- const subtotal = ITEMS.reduce((s, i) => s + i.qty * i.price, 0);
10528
- const shipping = 9;
10529
- const total = subtotal + shipping;
10530
- return /* @__PURE__ */ jsx("div", { className: "h-full overflow-auto bg-gray-50 p-6", children: /* @__PURE__ */ jsxs("div", { className: "mx-auto max-w-5xl space-y-5", children: [
10531
- /* @__PURE__ */ jsx("h1", { className: "text-xl font-semibold text-gray-900", children: "Checkout" }),
10532
- /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 gap-5 lg:grid-cols-3", children: [
10533
- /* @__PURE__ */ jsxs("form", { className: "space-y-5 lg:col-span-2", onSubmit: (e) => e.preventDefault(), children: [
10534
- /* @__PURE__ */ jsx(Card, { header: "Contact", children: /* @__PURE__ */ jsx("div", { className: "space-y-4", children: /* @__PURE__ */ jsx(FormField, { label: "Email", htmlFor: "ck-email", required: true, children: /* @__PURE__ */ jsx(Input_default, { id: "ck-email", type: "email", placeholder: "you@example.com" }) }) }) }),
10535
- /* @__PURE__ */ jsx(Card, { header: "Shipping address", children: /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
10536
- /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 gap-4 sm:grid-cols-2", children: [
10537
- /* @__PURE__ */ jsx(FormField, { label: "First name", required: true, children: /* @__PURE__ */ jsx(Input_default, { placeholder: "Alice" }) }),
10538
- /* @__PURE__ */ jsx(FormField, { label: "Last name", required: true, children: /* @__PURE__ */ jsx(Input_default, { placeholder: "Nguyen" }) })
10539
- ] }),
10540
- /* @__PURE__ */ jsx(FormField, { label: "Address", required: true, children: /* @__PURE__ */ jsx(Input_default, { placeholder: "123 Market St" }) }),
10541
- /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 gap-4 sm:grid-cols-3", children: [
10542
- /* @__PURE__ */ jsx(FormField, { label: "City", children: /* @__PURE__ */ jsx(Input_default, { placeholder: "San Francisco" }) }),
10543
- /* @__PURE__ */ jsx(FormField, { label: "ZIP", children: /* @__PURE__ */ jsx(Input_default, { placeholder: "94103" }) }),
10544
- /* @__PURE__ */ jsx(FormField, { label: "Country", children: /* @__PURE__ */ jsx(
10545
- Select_default,
10546
- {
10547
- value: country,
10548
- onChange: setCountry,
10549
- options: [
10550
- { value: "us", label: "United States" },
10551
- { value: "de", label: "Germany" },
10552
- { value: "jp", label: "Japan" }
10553
- ]
10554
- }
10555
- ) })
10556
- ] }),
10557
- /* @__PURE__ */ jsx(Checkbox_default, { checked: sameAddress, onChange: setSameAddress, label: "Billing address same as shipping" })
10558
- ] }) }),
10559
- /* @__PURE__ */ jsx(Card, { header: "Payment", children: /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
10560
- /* @__PURE__ */ jsx(FormField, { label: "Card number", required: true, children: /* @__PURE__ */ jsx(Input_default, { placeholder: "1234 5678 9012 3456" }) }),
10561
- /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
10562
- /* @__PURE__ */ jsx(FormField, { label: "Expiry", required: true, children: /* @__PURE__ */ jsx(Input_default, { placeholder: "MM / YY" }) }),
10563
- /* @__PURE__ */ jsx(FormField, { label: "CVC", required: true, children: /* @__PURE__ */ jsx(Input_default, { placeholder: "123" }) })
10564
- ] })
10565
- ] }) })
10566
- ] }),
10567
- /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
10568
- /* @__PURE__ */ jsxs(Card, { header: "Order summary", children: [
10569
- /* @__PURE__ */ jsx("ul", { className: "space-y-2 text-sm", children: ITEMS.map((i) => /* @__PURE__ */ jsxs("li", { className: "flex items-center justify-between gap-2 text-gray-700", children: [
10570
- /* @__PURE__ */ jsxs("span", { children: [
10571
- i.name,
10572
- " ",
10573
- /* @__PURE__ */ jsxs("span", { className: "text-gray-400", children: [
10574
- "\xD7",
10575
- i.qty
10576
- ] })
10577
- ] }),
10578
- /* @__PURE__ */ jsxs("span", { children: [
10579
- "$",
10580
- i.qty * i.price
10581
- ] })
10582
- ] }, i.name)) }),
10583
- /* @__PURE__ */ jsxs("div", { className: "mt-3 space-y-1.5 border-t border-gray-100 pt-3 text-sm text-gray-600", children: [
10584
- /* @__PURE__ */ jsxs("div", { className: "flex justify-between", children: [
10585
- /* @__PURE__ */ jsx("span", { children: "Subtotal" }),
10586
- /* @__PURE__ */ jsxs("span", { children: [
10587
- "$",
10588
- subtotal
10589
- ] })
10590
- ] }),
10591
- /* @__PURE__ */ jsxs("div", { className: "flex justify-between", children: [
10592
- /* @__PURE__ */ jsx("span", { children: "Shipping" }),
10593
- /* @__PURE__ */ jsxs("span", { children: [
10594
- "$",
10595
- shipping
10596
- ] })
10597
- ] }),
10598
- /* @__PURE__ */ jsxs("div", { className: "flex justify-between text-base font-semibold text-gray-900", children: [
10599
- /* @__PURE__ */ jsx("span", { children: "Total" }),
10600
- /* @__PURE__ */ jsxs("span", { children: [
10601
- "$",
10602
- total
10603
- ] })
10604
- ] })
10605
- ] }),
10606
- /* @__PURE__ */ jsxs(Button_default, { block: true, className: "mt-4", children: [
10607
- "Pay $",
10608
- total
10609
- ] })
10610
- ] }),
10611
- /* @__PURE__ */ jsx(Banner, { tone: "success", title: "Promo applied", children: "Free returns within 30 days." })
10612
- ] })
10613
- ] })
10614
- ] }) });
10615
- }
10616
- var FOLDERS = [
10617
- { id: "inbox", label: "Inbox", count: 12 },
10618
- { id: "starred", label: "Starred", count: 3 },
10619
- { id: "sent", label: "Sent" },
10620
- { id: "drafts", label: "Drafts", count: 1 },
10621
- { id: "archive", label: "Archive" },
10622
- { id: "trash", label: "Trash" }
10623
- ];
10624
- var MESSAGES = [
10625
- { id: 1, from: "Marco Reyes", subject: "Q3 roadmap review", preview: "Sharing the deck ahead of Thursday \u2014 let me know\u2026", time: "9:14", unread: true },
10626
- { id: 2, from: "Priya Patel", subject: "Design handoff", preview: "The Figma file is ready for engineering. I exported\u2026", time: "8:02", unread: true },
10627
- { id: 3, from: "GitHub", subject: "[acme/web] PR #482 merged", preview: "Your pull request was merged into main by\u2026", time: "Yest" },
10628
- { id: 4, from: "Sara Lind", subject: "Lunch Friday?", preview: "A few of us are heading to the new place on 5th\u2026", time: "Yest" }
10629
- ];
10630
- function EmailTemplate() {
10631
- const [active, setActive] = useState(1);
10632
- const selected = MESSAGES.find((m) => m.id === active) ?? MESSAGES[0];
10633
- return /* @__PURE__ */ jsx("div", { className: "h-full", children: /* @__PURE__ */ jsx(
10634
- SidebarLayout,
10635
- {
10636
- side: "left",
10637
- defaultWidth: 200,
10638
- sidebar: /* @__PURE__ */ jsxs("div", { className: "flex h-full flex-col p-2", children: [
10639
- /* @__PURE__ */ jsx(Button_default, { block: true, className: "mb-2", children: "Compose" }),
10640
- FOLDERS.map((f, i) => /* @__PURE__ */ jsxs(
10641
- "button",
10642
- {
10643
- className: `flex items-center justify-between rounded-md px-3 py-2 text-left text-sm ${i === 0 ? "bg-blue-50 font-medium text-blue-700" : "text-gray-700 hover:bg-gray-100"}`,
10644
- children: [
10645
- /* @__PURE__ */ jsx("span", { children: f.label }),
10646
- f.count != null && /* @__PURE__ */ jsx("span", { className: "text-xs text-gray-400", children: f.count })
10647
- ]
10648
- },
10649
- f.id
10650
- ))
10651
- ] }),
10652
- children: /* @__PURE__ */ jsxs("div", { className: "flex h-full", children: [
10653
- /* @__PURE__ */ jsx("div", { className: "w-72 shrink-0 overflow-auto border-r border-gray-200", children: MESSAGES.map((m) => /* @__PURE__ */ jsxs(
10654
- "button",
10655
- {
10656
- onClick: () => setActive(m.id),
10657
- className: `block w-full border-b border-gray-100 px-4 py-3 text-left hover:bg-gray-50 ${m.id === active ? "bg-blue-50/50" : ""}`,
10658
- children: [
10659
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
10660
- /* @__PURE__ */ jsx("span", { className: `truncate text-sm ${m.unread ? "font-semibold text-gray-900" : "text-gray-700"}`, children: m.from }),
10661
- /* @__PURE__ */ jsx("span", { className: "shrink-0 text-[11px] text-gray-400", children: m.time })
10662
- ] }),
10663
- /* @__PURE__ */ jsx("div", { className: `truncate text-sm ${m.unread ? "text-gray-800" : "text-gray-600"}`, children: m.subject }),
10664
- /* @__PURE__ */ jsx("div", { className: "truncate text-xs text-gray-400", children: m.preview })
10665
- ]
10666
- },
10667
- m.id
10668
- )) }),
10669
- /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col overflow-auto", children: [
10670
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3 border-b border-gray-200 p-4", children: [
10671
- /* @__PURE__ */ jsx("h2", { className: "truncate text-base font-semibold text-gray-900", children: selected.subject }),
10672
- /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 gap-1.5", children: [
10673
- /* @__PURE__ */ jsx(Button_default, { variant: "ghost", size: "sm", children: "Archive" }),
10674
- /* @__PURE__ */ jsx(Button_default, { variant: "secondary", size: "sm", children: "Reply" })
10675
- ] })
10676
- ] }),
10677
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 border-b border-gray-100 p-4", children: [
10678
- /* @__PURE__ */ jsx(Avatar, { name: selected.from }),
10679
- /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
10680
- /* @__PURE__ */ jsx("div", { className: "text-sm font-medium text-gray-900", children: selected.from }),
10681
- /* @__PURE__ */ jsxs("div", { className: "text-xs text-gray-400", children: [
10682
- "to me \xB7 ",
10683
- selected.time
10684
- ] })
10685
- ] })
10686
- ] }),
10687
- /* @__PURE__ */ jsxs("div", { className: "space-y-3 p-4 text-sm leading-relaxed text-gray-700", children: [
10688
- /* @__PURE__ */ jsx("p", { children: "Hi," }),
10689
- /* @__PURE__ */ jsxs("p", { children: [
10690
- selected.preview,
10691
- " The full details are attached \u2014 happy to walk through anything on a quick call."
10692
- ] }),
10693
- /* @__PURE__ */ jsxs("p", { children: [
10694
- "Best,",
10695
- /* @__PURE__ */ jsx("br", {}),
10696
- selected.from
10697
- ] })
10698
- ] })
10699
- ] })
10700
- ] })
10701
- }
10702
- ) });
10703
- }
10704
- var CONVOS = [
10705
- { id: 1, name: "Priya Patel", last: "Sounds good \u2014 shipping it now", time: "9:41", status: "online" },
10706
- { id: 2, name: "Design team", last: "Marco: updated the tokens", time: "9:12", status: "away" },
10707
- { id: 3, name: "Tom Becker", last: "Thanks!", time: "Yest", status: "offline" },
10708
- { id: 4, name: "Sara Lind", last: "See you Friday \u{1F389}", time: "Yest", status: "busy" }
10709
- ];
10710
- var THREAD = [
10711
- { mine: false, text: "Hey! Did the latest build go out?" },
10712
- { mine: true, text: "Just merged \u2014 deploying now." },
10713
- { mine: false, text: "Nice. Any breaking changes for the portal?" },
10714
- { mine: true, text: "Nope, fully additive. New components only." },
10715
- { mine: false, text: "Sounds good \u2014 shipping it now" }
10716
- ];
10717
- function ChatTemplate() {
10718
- const [active, setActive] = useState(1);
10719
- const convo = CONVOS.find((c) => c.id === active) ?? CONVOS[0];
10720
- return /* @__PURE__ */ jsx("div", { className: "h-full", children: /* @__PURE__ */ jsx(
10721
- SidebarLayout,
10722
- {
10723
- side: "left",
10724
- defaultWidth: 260,
10725
- sidebar: /* @__PURE__ */ jsx("div", { className: "h-full overflow-auto", children: CONVOS.map((c) => /* @__PURE__ */ jsxs(
10726
- "button",
10727
- {
10728
- onClick: () => setActive(c.id),
10729
- className: `flex w-full items-center gap-3 border-b border-gray-100 px-3 py-3 text-left hover:bg-gray-50 ${c.id === active ? "bg-blue-50/50" : ""}`,
10730
- children: [
10731
- /* @__PURE__ */ jsx(Avatar, { name: c.name, status: c.status }),
10732
- /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
10733
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
10734
- /* @__PURE__ */ jsx("span", { className: "truncate text-sm font-medium text-gray-900", children: c.name }),
10735
- /* @__PURE__ */ jsx("span", { className: "shrink-0 text-[11px] text-gray-400", children: c.time })
10736
- ] }),
10737
- /* @__PURE__ */ jsx("div", { className: "truncate text-xs text-gray-500", children: c.last })
10738
- ] })
10739
- ]
10740
- },
10741
- c.id
10742
- )) }),
10743
- children: /* @__PURE__ */ jsxs("div", { className: "flex h-full flex-col", children: [
10744
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 border-b border-gray-200 p-3", children: [
10745
- /* @__PURE__ */ jsx(Avatar, { name: convo.name, status: convo.status, size: "sm" }),
10746
- /* @__PURE__ */ jsxs("div", { children: [
10747
- /* @__PURE__ */ jsx("div", { className: "text-sm font-medium text-gray-900", children: convo.name }),
10748
- /* @__PURE__ */ jsx("div", { className: "text-xs text-gray-400 capitalize", children: convo.status })
10749
- ] })
10750
- ] }),
10751
- /* @__PURE__ */ jsx("div", { className: "flex flex-1 flex-col gap-2 overflow-auto bg-gray-50 p-4", children: THREAD.map((m, i) => /* @__PURE__ */ jsx("div", { className: `flex ${m.mine ? "justify-end" : "justify-start"}`, children: /* @__PURE__ */ jsx(
10752
- "div",
10753
- {
10754
- style: { maxWidth: "75%" },
10755
- className: `rounded-2xl px-3 py-2 text-sm ${m.mine ? "bg-blue-600 text-white" : "bg-white text-gray-800 shadow-sm"}`,
10756
- children: m.text
10757
- }
10758
- ) }, i)) }),
10759
- /* @__PURE__ */ jsxs("form", { className: "flex items-center gap-2 border-t border-gray-200 p-3", onSubmit: (e) => e.preventDefault(), children: [
10760
- /* @__PURE__ */ jsx("div", { className: "flex-1", children: /* @__PURE__ */ jsx(Input_default, { placeholder: "Type a message\u2026" }) }),
10761
- /* @__PURE__ */ jsx(Button_default, { type: "submit", children: "Send" })
10762
- ] })
10763
- ] })
10764
- }
10765
- ) });
10766
- }
10767
- var GROUPS2 = { featured: "success", new: "active", draft: "draft" };
10768
- var GRADIENTS = [
10769
- "linear-gradient(135deg,#60a5fa,#a78bfa)",
10770
- "linear-gradient(135deg,#34d399,#10b981)",
10771
- "linear-gradient(135deg,#fbbf24,#f59e0b)",
10772
- "linear-gradient(135deg,#f472b6,#ec4899)",
10773
- "linear-gradient(135deg,#38bdf8,#6366f1)",
10774
- "linear-gradient(135deg,#fb7185,#ef4444)",
10775
- "linear-gradient(135deg,#4ade80,#22c55e)",
10776
- "linear-gradient(135deg,#c084fc,#8b5cf6)"
10777
- ];
10778
- var ITEMS2 = GRADIENTS.map((g, i) => ({
10779
- id: i,
10780
- title: ["Sunrise", "Forest", "Desert", "Bloom", "Ocean", "Ember", "Meadow", "Dusk"][i],
10781
- tag: ["featured", "new", "draft"][i % 3],
10782
- bg: g
10783
- }));
10784
- function GalleryTemplate() {
10785
- const [filter, setFilter] = useState("all");
10786
- const shown = filter === "all" ? ITEMS2 : ITEMS2.filter((i) => i.tag === filter);
10787
- return /* @__PURE__ */ jsx(StatusBadgeProvider, { groups: GROUPS2, children: /* @__PURE__ */ jsx("div", { className: "h-full overflow-auto bg-gray-50 p-6", children: /* @__PURE__ */ jsxs("div", { className: "mx-auto max-w-5xl space-y-4", children: [
10788
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-4", children: [
10789
- /* @__PURE__ */ jsx("h1", { className: "text-xl font-semibold text-gray-900", children: "Gallery" }),
10790
- /* @__PURE__ */ jsx(Button_default, { size: "sm", children: "Upload" })
10791
- ] }),
10792
- /* @__PURE__ */ jsx(
10793
- Tabs,
10794
- {
10795
- value: filter,
10796
- onChange: setFilter,
10797
- variant: "pill",
10798
- items: [
10799
- { id: "all", label: "All" },
10800
- { id: "featured", label: "Featured" },
10801
- { id: "new", label: "New" },
10802
- { id: "draft", label: "Drafts" }
10803
- ]
10804
- }
10805
- ),
10806
- /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 gap-4 sm:grid-cols-3 lg:grid-cols-4", children: shown.map((item) => /* @__PURE__ */ jsxs(Card, { padded: false, className: "overflow-hidden", children: [
10807
- /* @__PURE__ */ jsx("div", { className: "relative h-28", style: { background: item.bg }, children: /* @__PURE__ */ jsx("span", { className: "absolute right-2 top-2", children: /* @__PURE__ */ jsx(StatusBadge, { status: item.tag }) }) }),
10808
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 p-3", children: [
10809
- /* @__PURE__ */ jsx("span", { className: "truncate text-sm font-medium text-gray-900", children: item.title }),
10810
- /* @__PURE__ */ jsx("span", { className: "text-xs text-gray-400", children: "2.4 MB" })
10811
- ] })
10812
- ] }, item.id)) })
10813
- ] }) }) });
10814
- }
10815
- var COPY = {
10816
- login: { title: "Welcome back", subtitle: "Sign in to your account", cta: "Sign in" },
10817
- register: { title: "Create your account", subtitle: "Start your 14-day free trial", cta: "Create account" },
10818
- forgot: { title: "Reset password", subtitle: "We'll email you a reset link", cta: "Send reset link" }
10819
- };
10820
- function AuthScreen({ mode = "login" }) {
10821
- const [remember, setRemember] = useState(true);
10822
- const c = COPY[mode];
10823
- return /* @__PURE__ */ jsx("div", { className: "flex h-full items-center justify-center overflow-auto bg-gray-50 p-6", children: /* @__PURE__ */ jsxs("div", { className: "w-full max-w-sm", children: [
10824
- /* @__PURE__ */ jsxs("div", { className: "mb-6 text-center", children: [
10825
- /* @__PURE__ */ jsx("div", { className: "mx-auto mb-3 flex h-10 w-10 items-center justify-center rounded-lg bg-blue-600 text-sm font-bold text-white", children: "A" }),
10826
- /* @__PURE__ */ jsx("h1", { className: "text-lg font-semibold text-gray-900", children: c.title }),
10827
- /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-500", children: c.subtitle })
10828
- ] }),
10829
- /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsxs("form", { className: "space-y-4", onSubmit: (e) => e.preventDefault(), children: [
10830
- mode === "register" && /* @__PURE__ */ jsx(FormField, { label: "Name", htmlFor: "au-name", children: /* @__PURE__ */ jsx(Input_default, { id: "au-name", placeholder: "Alice Nguyen" }) }),
10831
- /* @__PURE__ */ jsx(FormField, { label: "Email", htmlFor: "au-email", children: /* @__PURE__ */ jsx(Input_default, { id: "au-email", type: "email", placeholder: "you@example.com" }) }),
10832
- mode !== "forgot" && /* @__PURE__ */ jsx(
10833
- FormField,
10834
- {
10835
- label: "Password",
10836
- htmlFor: "au-pass",
10837
- hint: mode === "register" ? "At least 8 characters." : void 0,
10838
- children: /* @__PURE__ */ jsx(Input_default, { id: "au-pass", type: "password", placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" })
10839
- }
10840
- ),
10841
- mode === "login" && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
10842
- /* @__PURE__ */ jsx(Checkbox_default, { checked: remember, onChange: setRemember, label: "Remember me" }),
10843
- /* @__PURE__ */ jsx("a", { href: "#", className: "text-sm text-blue-600 hover:underline", children: "Forgot?" })
10844
- ] }),
10845
- /* @__PURE__ */ jsx(Button_default, { type: "submit", block: true, children: c.cta })
10846
- ] }) }),
10847
- /* @__PURE__ */ jsx("p", { className: "mt-4 text-center text-sm text-gray-500", children: mode === "register" ? /* @__PURE__ */ jsxs(Fragment, { children: [
10848
- "Already have an account? ",
10849
- /* @__PURE__ */ jsx("a", { href: "#", className: "text-blue-600 hover:underline", children: "Sign in" })
10850
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
10851
- "New here? ",
10852
- /* @__PURE__ */ jsx("a", { href: "#", className: "text-blue-600 hover:underline", children: "Create an account" })
10853
- ] }) })
10854
- ] }) });
10855
- }
10856
- var COPY2 = {
10857
- 403: { title: "Access denied", body: "You don't have permission to view this page." },
10858
- 404: { title: "Page not found", body: "The page you're looking for doesn't exist or was moved." },
10859
- 500: { title: "Something went wrong", body: "An unexpected error occurred. Please try again." }
10860
- };
10861
- function ErrorPage({ code = 404 }) {
10862
- const c = COPY2[code];
10863
- return /* @__PURE__ */ jsxs("div", { className: "flex h-full flex-col items-center justify-center bg-gray-50 p-6 text-center", children: [
10864
- /* @__PURE__ */ jsx("div", { className: "text-7xl font-bold tracking-tight text-gray-400", children: code }),
10865
- /* @__PURE__ */ jsx("h1", { className: "mt-2 text-xl font-semibold text-gray-900", children: c.title }),
10866
- /* @__PURE__ */ jsx("p", { className: "mt-1 max-w-sm text-sm text-gray-500", children: c.body }),
10867
- /* @__PURE__ */ jsxs("div", { className: "mt-6 flex items-center gap-2", children: [
10868
- /* @__PURE__ */ jsx(Button_default, { variant: "secondary", children: "Go back" }),
10869
- /* @__PURE__ */ jsx(Button_default, { children: "Take me home" })
10870
- ] })
10871
- ] });
10872
- }
10873
6950
  var EntityFetcherContext = createContext(null);
10874
6951
  function ShellEntityFetcherProvider({
10875
6952
  value,
@@ -10918,6 +6995,6 @@ function useEditHotkey(callback) {
10918
6995
  }, [callback, isActive]);
10919
6996
  }
10920
6997
 
10921
- export { ALT, ALT_SHIFT_D, ALT_SHIFT_E, ALT_SHIFT_N, Accordion, AuthScreen, Avatar, AvatarGroup, BLOCKED_PCT_CPU, Banner, BarChart, BehaviorPanel, BulkImportGrid, Button_default as Button, CMD_A, CMD_DOT, CMD_ENTER, CMD_K, CMD_S, Card, ChangePasswordForm, ChatTemplate, Checkbox_default as Checkbox, CheckoutTemplate, ColoredBadge, ContainerFillChart, Customization, DEV_BANNER_TEXT, DashboardTemplate, DataTablePage, DateRangePicker, Desktop, DesktopHostProvider, DevIndicator, DonutChart, ENTER, EmailTemplate, EmptyState, EntityList, ErrorPage, FilterBar, FormField, FormLayoutPage, GalleryTemplate, GlobalSearch, HelpCenter, INPUT_BASE, Input_default as Input, Kanban, LOG_CAP, Label, Layout, ListFooter, ListLoadError, LoadingSpinner2 as LoadingSpinner, MIN_EVENT_SAMPLES, MIN_GROUP_SAMPLES, MOD, Markdown, MediaUploadField, MediaUploadGrid, MetricBar, MilestoneTimeline, NativeSelect, NotificationBell, PageHeader, Pagination, PdfActionButton, PerfStats, Radio_default as Radio, ResizableTable, SHIFT, SMOOTH_FPS, SearchableSelect, Select_default as Select, ServerStatusIndicator, ShellEntityFetcherProvider, ShortcutHelp, SidebarActionButton, SidebarGroupLabel, SidebarNavItem, SoundsPanel, Sparkline, StartMenu, StatCard, StatusBadge, StatusBadgeProvider, SystemPreferences, Tabs, Textarea_default as Textarea, Tooltip, TopNav, UndoControls, WidgetManager, appendRecord as appendPerfRecord, applyDevTitle, classifyActivity, classify as classifyPerf, createWindowRegistry, createWindowTarget, describeMachine, describePerfReport, findDuplicateKeys, formatDate, inputClasses, isDevEnv, isInteracting, isMac, isSeverityTone, mediaFileName, mergeBulkItems, toCsv as perfLogToCsv, perfReportFile, readEnvironment as readPerfEnvironment, requestAsyncEnvironment, severityOf, summariseFrames, summariseLog as summarisePerfLog, toISODate2 as toISODate, useClickOutside, useColumnConfig, useDesktopHost, useEditHotkey, useFilters, useInfiniteScroll, useNewHotkey, useShellEntityFetcher, useSort, useTableNav, useWindowTarget };
6998
+ export { BLOCKED_PCT_CPU, BehaviorPanel, BulkImportGrid, ChangePasswordForm, Customization, DEV_BANNER_TEXT, Desktop, DesktopHostProvider, DevIndicator, EntityList, FilterBar, GlobalSearch, LOG_CAP, Layout, MIN_EVENT_SAMPLES, MIN_GROUP_SAMPLES, NotificationBell, PdfActionButton, PerfStats, ResizableTable, SMOOTH_FPS, ServerStatusIndicator, ShellEntityFetcherProvider, ShortcutHelp, SoundsPanel, StartMenu, SystemPreferences, UndoControls, WidgetManager, appendRecord as appendPerfRecord, applyDevTitle, classifyActivity, classify as classifyPerf, createWindowRegistry, createWindowTarget, describeMachine, describePerfReport, findDuplicateKeys, isDevEnv, isInteracting, mergeBulkItems, toCsv as perfLogToCsv, perfReportFile, readEnvironment as readPerfEnvironment, requestAsyncEnvironment, summariseFrames, summariseLog as summarisePerfLog, useColumnConfig, useDesktopHost, useEditHotkey, useFilters, useInfiniteScroll, useNewHotkey, useShellEntityFetcher, useSort, useTableNav, useWindowTarget };
10922
6999
  //# sourceMappingURL=index.js.map
10923
7000
  //# sourceMappingURL=index.js.map