@kuraykaraaslan/kui-react 1.0.1

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 (47) hide show
  1. package/LICENSE +17 -0
  2. package/README.md +168 -0
  3. package/dist/AdvancedDataTable-F3DNXDKX.mjs +11 -0
  4. package/dist/DataTable-2G27T4E6.mjs +11 -0
  5. package/dist/DateRangePicker-AL32QB6L.mjs +11 -0
  6. package/dist/DropdownMenu-f5yV9dzM.d.mts +22 -0
  7. package/dist/DropdownMenu-f5yV9dzM.d.ts +22 -0
  8. package/dist/MapView-FERKPCDB.mjs +10 -0
  9. package/dist/ServerDataTable-RZV3K6KQ.mjs +11 -0
  10. package/dist/Tooltip-Bof5GvOc.d.mts +248 -0
  11. package/dist/Tooltip-Bof5GvOc.d.ts +248 -0
  12. package/dist/VideoPlayer-P3I6ESXJ.mjs +9 -0
  13. package/dist/app.d.mts +620 -0
  14. package/dist/app.d.ts +620 -0
  15. package/dist/app.js +7061 -0
  16. package/dist/app.mjs +100 -0
  17. package/dist/chunk-24BCQSLI.mjs +1 -0
  18. package/dist/chunk-45I3EDB2.mjs +90 -0
  19. package/dist/chunk-4IWCD7ID.mjs +1450 -0
  20. package/dist/chunk-5E2HXWFI.mjs +105 -0
  21. package/dist/chunk-C7AYI4XM.mjs +402 -0
  22. package/dist/chunk-J4D44TUA.mjs +1267 -0
  23. package/dist/chunk-KTEWZKNE.mjs +1020 -0
  24. package/dist/chunk-LMUQHL4Z.mjs +3829 -0
  25. package/dist/chunk-MD5OQ4J2.mjs +527 -0
  26. package/dist/chunk-MPJRPYIZ.mjs +1 -0
  27. package/dist/chunk-MPWUEQ7J.mjs +2422 -0
  28. package/dist/chunk-MTT5TKAJ.mjs +93 -0
  29. package/dist/chunk-RBDK7MWQ.mjs +46 -0
  30. package/dist/chunk-SVFQZPNZ.mjs +3648 -0
  31. package/dist/chunk-TZWBBMSG.mjs +1 -0
  32. package/dist/chunk-XA7J6PVJ.mjs +1488 -0
  33. package/dist/chunk-ZLYBRYWQ.mjs +726 -0
  34. package/dist/common.d.mts +921 -0
  35. package/dist/common.d.ts +921 -0
  36. package/dist/common.js +4991 -0
  37. package/dist/common.mjs +172 -0
  38. package/dist/index.d.mts +10 -0
  39. package/dist/index.d.ts +10 -0
  40. package/dist/index.js +17563 -0
  41. package/dist/index.mjs +349 -0
  42. package/dist/ui.d.mts +937 -0
  43. package/dist/ui.d.ts +937 -0
  44. package/dist/ui.js +10095 -0
  45. package/dist/ui.mjs +163 -0
  46. package/package.json +114 -0
  47. package/styles/index.css +129 -0
@@ -0,0 +1,3829 @@
1
+ "use client";
2
+ import {
3
+ Breadcrumb,
4
+ Drawer,
5
+ EmptyState,
6
+ Modal,
7
+ MultiSelect,
8
+ SkeletonAvatar,
9
+ SkeletonCard,
10
+ SkeletonLine,
11
+ SkeletonTableRow,
12
+ SkeletonText,
13
+ Stepper,
14
+ ToastProvider,
15
+ Tooltip,
16
+ toast,
17
+ useFocusTrap
18
+ } from "./chunk-XA7J6PVJ.mjs";
19
+ import {
20
+ isBrowser
21
+ } from "./chunk-45I3EDB2.mjs";
22
+ import {
23
+ AlertBanner,
24
+ Avatar,
25
+ Badge,
26
+ DropdownMenu,
27
+ Select,
28
+ TagInput
29
+ } from "./chunk-ZLYBRYWQ.mjs";
30
+ import {
31
+ SearchBar,
32
+ Spinner
33
+ } from "./chunk-5E2HXWFI.mjs";
34
+ import {
35
+ DateRangePicker
36
+ } from "./chunk-KTEWZKNE.mjs";
37
+ import {
38
+ Button
39
+ } from "./chunk-MTT5TKAJ.mjs";
40
+ import {
41
+ __objRest,
42
+ __spreadProps,
43
+ __spreadValues,
44
+ cn
45
+ } from "./chunk-RBDK7MWQ.mjs";
46
+
47
+ // modules/app/AppShell.tsx
48
+ import { useState } from "react";
49
+ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
50
+ import { faBars } from "@fortawesome/free-solid-svg-icons";
51
+ import { jsx, jsxs } from "react/jsx-runtime";
52
+ function AppShell(_a) {
53
+ var _b = _a, {
54
+ logo,
55
+ compactLogo,
56
+ sidebarCollapsed = false,
57
+ mobileSidebarTitle = "Navigation",
58
+ sidebar,
59
+ topbar,
60
+ asideClassName,
61
+ headerClassName,
62
+ mainClassName,
63
+ children,
64
+ className
65
+ } = _b, rest = __objRest(_b, [
66
+ "logo",
67
+ "compactLogo",
68
+ "sidebarCollapsed",
69
+ "mobileSidebarTitle",
70
+ "sidebar",
71
+ "topbar",
72
+ "asideClassName",
73
+ "headerClassName",
74
+ "mainClassName",
75
+ "children",
76
+ "className"
77
+ ]);
78
+ const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false);
79
+ const logoContent = sidebarCollapsed && compactLogo ? compactLogo : logo != null ? logo : compactLogo;
80
+ return /* @__PURE__ */ jsxs("div", __spreadProps(__spreadValues({ className: cn("flex h-screen overflow-hidden bg-surface-base", className) }, rest), { children: [
81
+ sidebar && /* @__PURE__ */ jsxs("aside", { className: cn("relative hidden lg:flex flex-col h-full min-h-0 shrink-0 border-r border-border bg-surface-raised", asideClassName), children: [
82
+ logoContent && /* @__PURE__ */ jsx("div", { className: cn(
83
+ "absolute inset-x-0 top-0 z-10 flex items-center h-14 border-b border-border bg-surface-raised overflow-hidden",
84
+ sidebarCollapsed && compactLogo ? "justify-center px-2" : "px-4"
85
+ ), children: logoContent }),
86
+ /* @__PURE__ */ jsx("div", { className: cn("flex min-h-0 flex-1", logoContent && "pt-14"), children: sidebar })
87
+ ] }),
88
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-col min-w-0 min-h-0", children: [
89
+ topbar && /* @__PURE__ */ jsxs("header", { className: cn("sticky top-0 z-30 flex items-center h-14 px-4 border-b border-border bg-surface-raised/90 backdrop-blur shrink-0", headerClassName), children: [
90
+ sidebar && /* @__PURE__ */ jsx(
91
+ "button",
92
+ {
93
+ type: "button",
94
+ onClick: () => setMobileSidebarOpen(true),
95
+ "aria-label": "Open sidebar",
96
+ className: "inline-flex lg:hidden items-center justify-center w-9 h-9 rounded-md text-text-secondary hover:text-text-primary hover:bg-surface-overlay transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus",
97
+ children: /* @__PURE__ */ jsx(FontAwesomeIcon, { icon: faBars, className: "w-4 h-4", "aria-hidden": "true" })
98
+ }
99
+ ),
100
+ /* @__PURE__ */ jsx("div", { className: "flex min-w-0 flex-1", children: topbar })
101
+ ] }),
102
+ /* @__PURE__ */ jsx("main", { id: "main-content", className: cn("flex-1 overflow-y-auto p-4 sm:p-6", mainClassName), children })
103
+ ] }),
104
+ sidebar && /* @__PURE__ */ jsx("div", { className: "lg:hidden", children: /* @__PURE__ */ jsx(
105
+ Drawer,
106
+ {
107
+ open: mobileSidebarOpen,
108
+ onClose: () => setMobileSidebarOpen(false),
109
+ title: mobileSidebarTitle,
110
+ side: "left",
111
+ className: "w-72",
112
+ children: /* @__PURE__ */ jsx("div", { className: "-mx-4 -my-4 h-[calc(100%+2rem)] flex flex-col", children: sidebar })
113
+ }
114
+ ) })
115
+ ] }));
116
+ }
117
+
118
+ // modules/app/AppSidebar.tsx
119
+ import { useEffect, useState as useState2 } from "react";
120
+ import { FontAwesomeIcon as FontAwesomeIcon2 } from "@fortawesome/react-fontawesome";
121
+ import { faChevronLeft, faChevronDown, faMagnifyingGlass } from "@fortawesome/free-solid-svg-icons";
122
+ import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
123
+ function AppSidebar({
124
+ navGroups,
125
+ navItems,
126
+ activeId,
127
+ onSelect,
128
+ collapsed,
129
+ defaultCollapsed = false,
130
+ onCollapsedChange,
131
+ footer,
132
+ searchable = true,
133
+ className
134
+ }) {
135
+ const [internalCollapsed, setInternalCollapsed] = useState2(defaultCollapsed);
136
+ const [searchQuery, setSearchQuery] = useState2("");
137
+ const [expandedGroups, setExpandedGroups] = useState2(() => {
138
+ var _a;
139
+ const resolvedGroups = navGroups != null ? navGroups : navItems ? [{ items: navItems }] : [];
140
+ const initial = /* @__PURE__ */ new Set();
141
+ for (const g of resolvedGroups) {
142
+ if (!g.collapsible) continue;
143
+ const key = (_a = g.label) != null ? _a : "";
144
+ const containsActive = g.items.some((i) => i.id === activeId);
145
+ if (containsActive || g.defaultExpanded) initial.add(key);
146
+ }
147
+ return initial;
148
+ });
149
+ const [isDesktop, setIsDesktop] = useState2(() => {
150
+ if (!isBrowser) {
151
+ return true;
152
+ }
153
+ return window.matchMedia("(min-width: 1024px)").matches;
154
+ });
155
+ useEffect(() => {
156
+ const mediaQuery = window.matchMedia("(min-width: 1024px)");
157
+ const handleChange = (event) => {
158
+ setIsDesktop(event.matches);
159
+ };
160
+ setIsDesktop(mediaQuery.matches);
161
+ mediaQuery.addEventListener("change", handleChange);
162
+ return () => {
163
+ mediaQuery.removeEventListener("change", handleChange);
164
+ };
165
+ }, []);
166
+ const isCollapsed = collapsed != null ? collapsed : internalCollapsed;
167
+ const effectiveCollapsed = isDesktop ? isCollapsed : false;
168
+ const q = searchQuery.trim().toLowerCase();
169
+ const rawGroups = navGroups != null ? navGroups : navItems ? [{ items: navItems }] : [];
170
+ const groups = q ? rawGroups.map((g) => __spreadProps(__spreadValues({}, g), { items: g.items.filter((i) => i.label.toLowerCase().includes(q)) })).filter((g) => g.items.length > 0) : rawGroups;
171
+ const footerContent = typeof footer === "function" ? footer({ collapsed: effectiveCollapsed }) : footer;
172
+ const setCollapsed = (next) => {
173
+ if (collapsed === void 0) {
174
+ setInternalCollapsed(next);
175
+ }
176
+ onCollapsedChange == null ? void 0 : onCollapsedChange(next);
177
+ };
178
+ function toggleGroup(key) {
179
+ setExpandedGroups((prev) => {
180
+ const next = new Set(prev);
181
+ if (next.has(key)) next.delete(key);
182
+ else next.add(key);
183
+ return next;
184
+ });
185
+ }
186
+ function isGroupExpanded(group) {
187
+ var _a;
188
+ if (!group.collapsible || effectiveCollapsed) return true;
189
+ return expandedGroups.has((_a = group.label) != null ? _a : "");
190
+ }
191
+ return /* @__PURE__ */ jsxs2(
192
+ "div",
193
+ {
194
+ "data-collapsed": effectiveCollapsed ? "true" : "false",
195
+ className: cn(
196
+ "flex flex-col flex-1 min-h-0 transition-all duration-200",
197
+ effectiveCollapsed ? "w-full lg:w-14" : "w-full lg:w-56",
198
+ className
199
+ ),
200
+ children: [
201
+ /* @__PURE__ */ jsx2("div", { className: cn("hidden lg:flex items-center px-2 py-2 border-b border-border shrink-0", effectiveCollapsed ? "justify-center" : "justify-end"), children: /* @__PURE__ */ jsx2(
202
+ "button",
203
+ {
204
+ type: "button",
205
+ onClick: () => setCollapsed(!isCollapsed),
206
+ "aria-label": isCollapsed ? "Expand sidebar" : "Collapse sidebar",
207
+ className: "p-1.5 rounded text-text-secondary hover:text-text-primary hover:bg-surface-overlay transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus",
208
+ children: /* @__PURE__ */ jsx2(FontAwesomeIcon2, { icon: faChevronLeft, className: cn("w-4 h-4 transition-transform", isCollapsed ? "rotate-180" : ""), "aria-hidden": "true" })
209
+ }
210
+ ) }),
211
+ searchable && !effectiveCollapsed && /* @__PURE__ */ jsx2("div", { className: "px-3 py-2 border-b border-border shrink-0", children: /* @__PURE__ */ jsxs2("div", { className: "relative", children: [
212
+ /* @__PURE__ */ jsx2(FontAwesomeIcon2, { icon: faMagnifyingGlass, className: "absolute left-2.5 top-1/2 -translate-y-1/2 w-3 h-3 text-text-disabled", "aria-hidden": "true" }),
213
+ /* @__PURE__ */ jsx2(
214
+ "input",
215
+ {
216
+ type: "search",
217
+ value: searchQuery,
218
+ onChange: (e) => setSearchQuery(e.target.value),
219
+ onKeyDown: (e) => e.key === "Escape" && setSearchQuery(""),
220
+ placeholder: "Search\u2026",
221
+ autoComplete: "off",
222
+ "aria-label": "Search navigation",
223
+ className: "w-full rounded-md border border-border bg-surface-base pl-7 pr-3 py-1.5 text-xs text-text-primary placeholder:text-text-disabled focus:outline-none focus:ring-2 focus:ring-border-focus"
224
+ }
225
+ )
226
+ ] }) }),
227
+ /* @__PURE__ */ jsx2("nav", { className: "flex-1 min-h-0 overflow-y-auto px-2 py-3 space-y-4 sidebar-scrollbar-hover", "aria-label": "Sidebar navigation", children: groups.map((group, gi) => {
228
+ var _a;
229
+ const groupKey = (_a = group.label) != null ? _a : String(gi);
230
+ const expanded = isGroupExpanded(group);
231
+ const hasActive = group.items.some((i) => i.id === activeId);
232
+ return /* @__PURE__ */ jsxs2("div", { children: [
233
+ group.label && !effectiveCollapsed && (group.collapsible ? /* @__PURE__ */ jsxs2(
234
+ "button",
235
+ {
236
+ type: "button",
237
+ onClick: () => toggleGroup(groupKey),
238
+ "aria-expanded": expanded,
239
+ className: cn(
240
+ "w-full flex items-center justify-between px-3 py-1 rounded-md mb-1 transition-colors",
241
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus",
242
+ hasActive ? "text-text-primary" : "text-text-disabled hover:text-text-secondary"
243
+ ),
244
+ children: [
245
+ /* @__PURE__ */ jsx2("span", { className: "text-[10px] font-semibold uppercase tracking-widest", children: group.label }),
246
+ /* @__PURE__ */ jsx2(
247
+ FontAwesomeIcon2,
248
+ {
249
+ icon: faChevronDown,
250
+ className: cn("w-3 h-3 transition-transform duration-200", expanded ? "rotate-0" : "-rotate-90"),
251
+ "aria-hidden": "true"
252
+ }
253
+ )
254
+ ]
255
+ }
256
+ ) : /* @__PURE__ */ jsx2("p", { className: "text-[10px] font-semibold uppercase tracking-widest text-text-disabled px-3 mb-1", children: group.label })),
257
+ expanded && /* @__PURE__ */ jsx2("div", { className: "space-y-0.5", children: group.items.map((item) => {
258
+ const itemClassName = cn(
259
+ "w-full flex items-center gap-2.5 rounded-lg text-sm transition-colors",
260
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus",
261
+ effectiveCollapsed ? "justify-center px-2 py-2" : "px-3 py-2 text-left",
262
+ item.id === activeId ? "bg-primary-subtle text-primary font-medium" : "text-text-secondary hover:text-text-primary hover:bg-surface-overlay"
263
+ );
264
+ const itemContent = /* @__PURE__ */ jsxs2(Fragment, { children: [
265
+ item.icon && /* @__PURE__ */ jsx2("span", { "aria-hidden": "true", className: "shrink-0 w-5 text-center text-[15px] leading-none", children: item.icon }),
266
+ !effectiveCollapsed && /* @__PURE__ */ jsx2("span", { className: "flex-1 truncate", children: item.label }),
267
+ !effectiveCollapsed && item.badge != null && item.badge > 0 && /* @__PURE__ */ jsx2(Badge, { variant: "primary", size: "sm", children: item.badge })
268
+ ] });
269
+ return item.href ? /* @__PURE__ */ jsx2(
270
+ "a",
271
+ {
272
+ href: item.href,
273
+ title: effectiveCollapsed ? item.label : void 0,
274
+ className: itemClassName,
275
+ children: itemContent
276
+ },
277
+ item.id
278
+ ) : /* @__PURE__ */ jsx2(
279
+ "button",
280
+ {
281
+ type: "button",
282
+ "aria-current": item.id === activeId ? "page" : void 0,
283
+ title: effectiveCollapsed ? item.label : void 0,
284
+ onClick: () => onSelect == null ? void 0 : onSelect(item.id),
285
+ className: itemClassName,
286
+ children: itemContent
287
+ },
288
+ item.id
289
+ );
290
+ }) })
291
+ ] }, groupKey);
292
+ }) }),
293
+ footerContent != null && /* @__PURE__ */ jsx2("div", { className: cn("border-t border-border shrink-0", effectiveCollapsed ? "flex justify-center px-2 py-3" : ""), children: footerContent })
294
+ ]
295
+ }
296
+ );
297
+ }
298
+
299
+ // modules/app/AppTopBar.tsx
300
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
301
+ function AppTopBar(_a) {
302
+ var _b = _a, {
303
+ logo,
304
+ children,
305
+ className
306
+ } = _b, rest = __objRest(_b, [
307
+ "logo",
308
+ "children",
309
+ "className"
310
+ ]);
311
+ return /* @__PURE__ */ jsxs3("div", __spreadProps(__spreadValues({ className: cn("flex items-center gap-3 flex-1", className) }, rest), { children: [
312
+ logo && /* @__PURE__ */ jsx3("div", { className: "shrink-0", children: logo }),
313
+ children
314
+ ] }));
315
+ }
316
+
317
+ // modules/app/AppDrawer.tsx
318
+ import { useState as useState3 } from "react";
319
+ import { FontAwesomeIcon as FontAwesomeIcon3 } from "@fortawesome/react-fontawesome";
320
+ import { faBars as faBars2 } from "@fortawesome/free-solid-svg-icons";
321
+ import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
322
+ function AppDrawer({
323
+ navGroups,
324
+ navItems,
325
+ activeId,
326
+ onSelect,
327
+ header,
328
+ footer,
329
+ searchable = true,
330
+ trigger,
331
+ title = "Navigation",
332
+ side = "left"
333
+ }) {
334
+ const [open, setOpen] = useState3(false);
335
+ const [query, setQuery] = useState3("");
336
+ const groups = navGroups != null ? navGroups : navItems ? [{ items: navItems }] : [];
337
+ const filtered = query.trim() ? groups.map((g) => __spreadProps(__spreadValues({}, g), {
338
+ items: g.items.filter(
339
+ (i) => i.label.toLowerCase().includes(query.toLowerCase())
340
+ )
341
+ })).filter((g) => g.items.length > 0) : groups;
342
+ function handleSelect(id) {
343
+ onSelect == null ? void 0 : onSelect(id);
344
+ setOpen(false);
345
+ setQuery("");
346
+ }
347
+ return /* @__PURE__ */ jsxs4(Fragment2, { children: [
348
+ /* @__PURE__ */ jsx4("div", { role: "none", onClick: () => setOpen(true), children: trigger != null ? trigger : /* @__PURE__ */ jsx4(Button, { variant: "outline", size: "sm", iconLeft: /* @__PURE__ */ jsx4(FontAwesomeIcon3, { icon: faBars2, className: "w-3.5 h-3.5", "aria-hidden": "true" }), children: "Open Navigation" }) }),
349
+ /* @__PURE__ */ jsxs4(
350
+ Drawer,
351
+ {
352
+ open,
353
+ onClose: () => {
354
+ setOpen(false);
355
+ setQuery("");
356
+ },
357
+ title,
358
+ side,
359
+ children: [
360
+ header && /* @__PURE__ */ jsx4("div", { className: "mb-4", children: header }),
361
+ searchable && /* @__PURE__ */ jsx4(
362
+ SearchBar,
363
+ {
364
+ id: "app-drawer-search",
365
+ placeholder: "Search navigation\u2026",
366
+ value: query,
367
+ onChange: setQuery,
368
+ className: "mb-4"
369
+ }
370
+ ),
371
+ /* @__PURE__ */ jsxs4("div", { className: "space-y-4", children: [
372
+ filtered.map((group, gi) => {
373
+ var _a;
374
+ return /* @__PURE__ */ jsxs4("div", { children: [
375
+ group.label && /* @__PURE__ */ jsx4("p", { className: "text-xs font-semibold text-text-disabled uppercase tracking-wider mb-1 px-1", children: group.label }),
376
+ /* @__PURE__ */ jsx4("div", { className: "space-y-0.5", children: group.items.map((item) => /* @__PURE__ */ jsxs4(
377
+ "button",
378
+ {
379
+ type: "button",
380
+ "aria-current": item.id === activeId ? "page" : void 0,
381
+ onClick: () => handleSelect(item.id),
382
+ className: cn(
383
+ "w-full flex items-center justify-between gap-2 px-3 py-2 rounded-md text-sm",
384
+ "transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus",
385
+ item.id === activeId ? "bg-primary-subtle text-primary font-semibold" : "text-text-primary hover:bg-surface-overlay"
386
+ ),
387
+ children: [
388
+ /* @__PURE__ */ jsxs4("span", { className: "flex items-center gap-2", children: [
389
+ item.icon && /* @__PURE__ */ jsx4("span", { "aria-hidden": "true", children: item.icon }),
390
+ item.label
391
+ ] }),
392
+ item.badge != null && item.badge > 0 && /* @__PURE__ */ jsx4(Badge, { variant: "neutral", size: "sm", children: item.badge })
393
+ ]
394
+ },
395
+ item.id
396
+ )) })
397
+ ] }, (_a = group.label) != null ? _a : gi);
398
+ }),
399
+ filtered.length === 0 && /* @__PURE__ */ jsxs4("p", { className: "text-sm text-text-secondary text-center py-4", children: [
400
+ 'No results for "',
401
+ query,
402
+ '"'
403
+ ] })
404
+ ] }),
405
+ footer && /* @__PURE__ */ jsx4("div", { className: "mt-4 pt-4 border-t border-border", children: footer })
406
+ ]
407
+ }
408
+ )
409
+ ] });
410
+ }
411
+
412
+ // modules/app/AppFooter.tsx
413
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
414
+ var STATUS_CONFIG = {
415
+ operational: { variant: "success", label: "Operational", dot: true },
416
+ degraded: { variant: "warning", label: "Degraded", dot: true },
417
+ outage: { variant: "error", label: "Outage", dot: true }
418
+ };
419
+ function AppFooter({
420
+ logo,
421
+ nav,
422
+ social,
423
+ version,
424
+ status,
425
+ copyright,
426
+ className
427
+ }) {
428
+ const statusCfg = status ? STATUS_CONFIG[status] : null;
429
+ return /* @__PURE__ */ jsxs5("footer", { className: cn("w-full border border-border rounded-xl bg-surface-raised overflow-hidden", className), children: [
430
+ /* @__PURE__ */ jsxs5("div", { className: "flex flex-wrap items-center justify-between gap-4 px-5 py-4 border-b border-border", children: [
431
+ /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-2", children: [
432
+ logo,
433
+ version && /* @__PURE__ */ jsxs5(Badge, { variant: "neutral", size: "md", children: [
434
+ "v",
435
+ version
436
+ ] })
437
+ ] }),
438
+ nav && /* @__PURE__ */ jsx5("nav", { "aria-label": "Footer navigation", className: "flex items-center gap-1", children: nav }),
439
+ statusCfg && /* @__PURE__ */ jsx5("div", { className: "flex items-center gap-3", children: /* @__PURE__ */ jsx5(Badge, { variant: statusCfg.variant, size: "md", dot: statusCfg.dot, children: statusCfg.label }) })
440
+ ] }),
441
+ /* @__PURE__ */ jsxs5("div", { className: "flex flex-wrap items-center justify-between gap-4 px-5 py-3 bg-surface-base", children: [
442
+ copyright && /* @__PURE__ */ jsx5("p", { className: "text-xs text-text-secondary", children: copyright }),
443
+ social && /* @__PURE__ */ jsx5("div", { className: "flex items-center gap-1", children: social })
444
+ ] })
445
+ ] });
446
+ }
447
+
448
+ // modules/app/AppBreadcrumbs.tsx
449
+ import { FontAwesomeIcon as FontAwesomeIcon4 } from "@fortawesome/react-fontawesome";
450
+ import { faHouse, faFile, faFolder, faChevronRight, faChevronDown as faChevronDown2 } from "@fortawesome/free-solid-svg-icons";
451
+ import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
452
+ function AppBreadcrumbs({
453
+ items = [],
454
+ title,
455
+ description,
456
+ badge,
457
+ className
458
+ }) {
459
+ const dropdownItems = items.map((item, i) => ({
460
+ label: item.label,
461
+ icon: i === 0 ? /* @__PURE__ */ jsx6(FontAwesomeIcon4, { icon: faHouse, className: "w-3 h-3" }) : i === items.length - 1 ? /* @__PURE__ */ jsx6(FontAwesomeIcon4, { icon: faFile, className: "w-3 h-3" }) : /* @__PURE__ */ jsx6(FontAwesomeIcon4, { icon: faFolder, className: "w-3 h-3" })
462
+ }));
463
+ return /* @__PURE__ */ jsxs6("div", { className: cn("w-full space-y-4 p-4 border border-border rounded-xl bg-surface-raised", className), children: [
464
+ (title || badge || description) && /* @__PURE__ */ jsxs6("div", { children: [
465
+ /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-2 flex-wrap", children: [
466
+ title && /* @__PURE__ */ jsx6("h1", { className: "text-2xl font-bold text-text-primary leading-tight", children: title }),
467
+ badge
468
+ ] }),
469
+ description && /* @__PURE__ */ jsx6("p", { className: "text-sm text-text-secondary mt-0.5", children: description })
470
+ ] }),
471
+ items.length > 0 && /* @__PURE__ */ jsxs6(Fragment3, { children: [
472
+ /* @__PURE__ */ jsx6("div", { className: "hidden sm:block", children: /* @__PURE__ */ jsx6("nav", { "aria-label": "Breadcrumb", className: "flex flex-wrap items-center gap-1 text-sm", children: items.map((item, i) => {
473
+ const isLast = i === items.length - 1;
474
+ const fullPath = items.slice(0, i + 1).map((b) => b.label).join(" \u203A ");
475
+ return /* @__PURE__ */ jsxs6("span", { className: "flex items-center gap-1", children: [
476
+ /* @__PURE__ */ jsx6(Tooltip, { content: fullPath, placement: "bottom", arrow: true, children: isLast ? /* @__PURE__ */ jsx6("span", { className: "text-text-primary font-medium px-1", children: item.label }) : item.href ? /* @__PURE__ */ jsx6(
477
+ "a",
478
+ {
479
+ href: item.href,
480
+ className: "text-text-secondary hover:text-text-primary transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus rounded px-1",
481
+ children: item.label
482
+ }
483
+ ) : /* @__PURE__ */ jsx6("span", { className: "text-text-secondary px-1", children: item.label }) }),
484
+ !isLast && /* @__PURE__ */ jsx6(FontAwesomeIcon4, { icon: faChevronRight, className: "w-2.5 h-2.5 text-text-disabled", "aria-hidden": "true" })
485
+ ] }, i);
486
+ }) }) }),
487
+ /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-2 sm:hidden", children: [
488
+ /* @__PURE__ */ jsx6(Breadcrumb, { items: [items[0], { label: "\u2026" }, items[items.length - 1]] }),
489
+ dropdownItems.length > 2 && /* @__PURE__ */ jsx6(
490
+ DropdownMenu,
491
+ {
492
+ trigger: /* @__PURE__ */ jsxs6(Button, { variant: "ghost", size: "xs", "aria-label": "View full path", children: [
493
+ "Full path ",
494
+ /* @__PURE__ */ jsx6(FontAwesomeIcon4, { icon: faChevronDown2, className: "w-2.5 h-2.5 ml-0.5" })
495
+ ] }),
496
+ items: dropdownItems,
497
+ align: "left"
498
+ }
499
+ )
500
+ ] })
501
+ ] })
502
+ ] });
503
+ }
504
+
505
+ // modules/app/SectionCard.tsx
506
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
507
+ function SectionCard({ title, children, className }) {
508
+ return /* @__PURE__ */ jsxs7("div", { className: `rounded-xl border border-border bg-surface-raised p-6 space-y-4 ${className != null ? className : ""}`, children: [
509
+ /* @__PURE__ */ jsx7("h3", { className: "text-sm font-semibold text-text-primary border-b border-border pb-3", children: title }),
510
+ children
511
+ ] });
512
+ }
513
+
514
+ // modules/app/NavDrawer.tsx
515
+ import { useState as useState4 } from "react";
516
+ import { Fragment as Fragment4, jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
517
+ function NavDrawer({
518
+ trigger,
519
+ title = "Menu",
520
+ side = "left",
521
+ footer,
522
+ children,
523
+ className
524
+ }) {
525
+ const [open, setOpen] = useState4(false);
526
+ return /* @__PURE__ */ jsxs8(Fragment4, { children: [
527
+ /* @__PURE__ */ jsx8(
528
+ "div",
529
+ {
530
+ role: "none",
531
+ onClick: () => setOpen(true),
532
+ className: cn("inline-flex", className),
533
+ children: trigger
534
+ }
535
+ ),
536
+ /* @__PURE__ */ jsx8(
537
+ Drawer,
538
+ {
539
+ open,
540
+ onClose: () => setOpen(false),
541
+ title,
542
+ side,
543
+ footer,
544
+ children
545
+ }
546
+ )
547
+ ] });
548
+ }
549
+
550
+ // modules/app/AppNav.tsx
551
+ import { FontAwesomeIcon as FontAwesomeIcon5 } from "@fortawesome/react-fontawesome";
552
+ import { faBars as faBars3 } from "@fortawesome/free-solid-svg-icons";
553
+ import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
554
+ function AppNav(_a) {
555
+ var _b = _a, {
556
+ logo,
557
+ navItems = [],
558
+ children,
559
+ sticky = false,
560
+ bordered = true,
561
+ className
562
+ } = _b, rest = __objRest(_b, [
563
+ "logo",
564
+ "navItems",
565
+ "children",
566
+ "sticky",
567
+ "bordered",
568
+ "className"
569
+ ]);
570
+ return /* @__PURE__ */ jsxs9(
571
+ "header",
572
+ __spreadProps(__spreadValues({
573
+ className: cn(
574
+ "w-full flex items-center gap-3 px-4 py-3 bg-surface-raised",
575
+ bordered && "border-b border-border",
576
+ sticky && "sticky top-0 z-40",
577
+ className
578
+ )
579
+ }, rest), {
580
+ children: [
581
+ /* @__PURE__ */ jsx9("div", { className: "md:hidden", children: /* @__PURE__ */ jsx9(
582
+ NavDrawer,
583
+ {
584
+ title: "Navigation",
585
+ side: "left",
586
+ trigger: /* @__PURE__ */ jsx9(Button, { variant: "ghost", size: "sm", iconOnly: true, "aria-label": "Open navigation menu", children: /* @__PURE__ */ jsx9(FontAwesomeIcon5, { icon: faBars3, className: "w-4 h-4", "aria-hidden": "true" }) }),
587
+ children: /* @__PURE__ */ jsx9("nav", { className: "flex flex-col gap-0.5 pt-1", "aria-label": "Mobile navigation", children: navItems.map((item) => {
588
+ var _a2;
589
+ return /* @__PURE__ */ jsx9(
590
+ "a",
591
+ {
592
+ href: (_a2 = item.href) != null ? _a2 : "#",
593
+ "aria-current": item.active ? "page" : void 0,
594
+ className: cn(
595
+ "flex items-center px-3 py-2.5 rounded-lg text-sm font-medium transition-colors",
596
+ item.active ? "bg-primary-subtle text-primary" : "text-text-primary hover:bg-surface-overlay"
597
+ ),
598
+ children: item.label
599
+ },
600
+ item.label
601
+ );
602
+ }) })
603
+ }
604
+ ) }),
605
+ logo && /* @__PURE__ */ jsx9("div", { className: "shrink-0", children: logo }),
606
+ /* @__PURE__ */ jsx9("nav", { className: "hidden md:flex items-center gap-0.5 flex-1", "aria-label": "Main navigation", children: navItems.map((item) => {
607
+ var _a2;
608
+ return /* @__PURE__ */ jsx9(
609
+ "a",
610
+ {
611
+ href: (_a2 = item.href) != null ? _a2 : "#",
612
+ "aria-current": item.active ? "page" : void 0,
613
+ className: cn(
614
+ "px-3 py-1.5 rounded-md text-sm font-medium transition-colors",
615
+ item.active ? "bg-primary-subtle text-primary" : "text-text-secondary hover:text-text-primary hover:bg-surface-overlay"
616
+ ),
617
+ children: item.label
618
+ },
619
+ item.label
620
+ );
621
+ }) }),
622
+ children && /* @__PURE__ */ jsx9("div", { className: "flex items-center gap-2 ml-auto shrink-0", children })
623
+ ]
624
+ })
625
+ );
626
+ }
627
+
628
+ // modules/app/GlobalSearch.tsx
629
+ import { useEffect as useEffect2, useRef, useState as useState5 } from "react";
630
+ import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
631
+ function GlobalSearch({
632
+ placeholder = "Search\u2026",
633
+ results = [],
634
+ onSearch,
635
+ onSelect,
636
+ loading = false,
637
+ className
638
+ }) {
639
+ const [query, setQuery] = useState5("");
640
+ const [open, setOpen] = useState5(false);
641
+ const containerRef = useRef(null);
642
+ const [highlighted, setHighlighted] = useState5(-1);
643
+ useEffect2(() => {
644
+ function onOutside(e) {
645
+ if (containerRef.current && !containerRef.current.contains(e.target)) {
646
+ setOpen(false);
647
+ }
648
+ }
649
+ document.addEventListener("mousedown", onOutside);
650
+ return () => document.removeEventListener("mousedown", onOutside);
651
+ }, []);
652
+ function handleChange(v) {
653
+ setQuery(v);
654
+ setHighlighted(-1);
655
+ onSearch(v);
656
+ setOpen(v.trim().length > 0);
657
+ }
658
+ function handleSelect(r) {
659
+ onSelect(r);
660
+ setQuery("");
661
+ setOpen(false);
662
+ }
663
+ function handleKeyDown(e) {
664
+ if (!open) return;
665
+ if (e.key === "ArrowDown") {
666
+ e.preventDefault();
667
+ setHighlighted((h) => Math.min(h + 1, results.length - 1));
668
+ } else if (e.key === "ArrowUp") {
669
+ e.preventDefault();
670
+ setHighlighted((h) => Math.max(h - 1, 0));
671
+ } else if (e.key === "Enter" && highlighted >= 0) {
672
+ e.preventDefault();
673
+ handleSelect(results[highlighted]);
674
+ } else if (e.key === "Escape") {
675
+ setOpen(false);
676
+ }
677
+ }
678
+ const grouped = results.reduce((acc, r) => {
679
+ var _a;
680
+ const cat = (_a = r.category) != null ? _a : "Results";
681
+ acc[cat] = acc[cat] ? [...acc[cat], r] : [r];
682
+ return acc;
683
+ }, {});
684
+ return /* @__PURE__ */ jsxs10("div", { ref: containerRef, className: cn("relative w-full max-w-md", className), children: [
685
+ /* @__PURE__ */ jsx10("div", { onKeyDown: handleKeyDown, role: "combobox", "aria-expanded": open, "aria-haspopup": "listbox", children: /* @__PURE__ */ jsx10(
686
+ SearchBar,
687
+ {
688
+ value: query,
689
+ onChange: handleChange,
690
+ placeholder
691
+ }
692
+ ) }),
693
+ open && /* @__PURE__ */ jsx10(
694
+ "div",
695
+ {
696
+ role: "listbox",
697
+ "aria-label": "Search results",
698
+ className: "absolute top-full mt-1.5 left-0 right-0 z-50 rounded-lg border border-border bg-surface-raised shadow-xl overflow-hidden max-h-72 overflow-y-auto",
699
+ children: loading ? /* @__PURE__ */ jsx10("div", { className: "px-4 py-6 text-center text-sm text-text-secondary animate-pulse", children: "Searching\u2026" }) : results.length === 0 ? /* @__PURE__ */ jsxs10("div", { className: "px-4 py-6 text-center text-sm text-text-secondary", children: [
700
+ "No results for ",
701
+ /* @__PURE__ */ jsxs10("strong", { className: "text-text-primary", children: [
702
+ '"',
703
+ query,
704
+ '"'
705
+ ] })
706
+ ] }) : Object.entries(grouped).map(([cat, items]) => /* @__PURE__ */ jsxs10("div", { children: [
707
+ /* @__PURE__ */ jsx10("p", { className: "px-3 pt-2 pb-1 text-[10px] font-semibold text-text-disabled uppercase tracking-wider", children: cat }),
708
+ items.map((r) => {
709
+ const idx = results.indexOf(r);
710
+ return /* @__PURE__ */ jsxs10(
711
+ "button",
712
+ {
713
+ type: "button",
714
+ role: "option",
715
+ "aria-selected": idx === highlighted,
716
+ onClick: () => handleSelect(r),
717
+ onMouseEnter: () => setHighlighted(idx),
718
+ className: cn(
719
+ "flex w-full items-center gap-3 px-3 py-2.5 text-left transition-colors",
720
+ "focus-visible:outline-none",
721
+ idx === highlighted ? "bg-primary-subtle text-primary" : "hover:bg-surface-overlay text-text-primary"
722
+ ),
723
+ children: [
724
+ r.icon && /* @__PURE__ */ jsx10("span", { "aria-hidden": "true", className: "shrink-0 text-text-disabled", children: r.icon }),
725
+ /* @__PURE__ */ jsxs10("div", { className: "min-w-0", children: [
726
+ /* @__PURE__ */ jsx10("p", { className: "text-sm font-medium truncate", children: r.label }),
727
+ r.description && /* @__PURE__ */ jsx10("p", { className: "text-xs text-text-secondary truncate", children: r.description })
728
+ ] })
729
+ ]
730
+ },
731
+ r.id
732
+ );
733
+ })
734
+ ] }, cat))
735
+ }
736
+ )
737
+ ] });
738
+ }
739
+
740
+ // modules/app/CommandPalette/index.tsx
741
+ import { useId, useMemo as useMemo3, useRef as useRef2, useState as useState6 } from "react";
742
+ import { FontAwesomeIcon as FontAwesomeIcon7 } from "@fortawesome/react-fontawesome";
743
+ import {
744
+ faHouse as faHouse2,
745
+ faFolder as faFolder2,
746
+ faUsers,
747
+ faGear,
748
+ faChartBar,
749
+ faPlus,
750
+ faEnvelope,
751
+ faFileExport,
752
+ faLock,
753
+ faClock
754
+ } from "@fortawesome/free-solid-svg-icons";
755
+
756
+ // modules/app/CommandPalette/hooks/useFuzzySearch.tsx
757
+ import { useMemo } from "react";
758
+ import { jsx as jsx11 } from "react/jsx-runtime";
759
+ function scoreString(haystack, needle) {
760
+ if (!needle) return { score: 0, matches: [] };
761
+ const hay = haystack.toLowerCase();
762
+ const need = needle.toLowerCase();
763
+ let score = 0;
764
+ let lastMatch = -1;
765
+ const matches = [];
766
+ let h = 0;
767
+ for (let n = 0; n < need.length; n++) {
768
+ const ch = need[n];
769
+ let found = -1;
770
+ for (let i = h; i < hay.length; i++) {
771
+ if (hay[i] === ch) {
772
+ found = i;
773
+ break;
774
+ }
775
+ }
776
+ if (found === -1) return null;
777
+ if (found === 0) score += 100;
778
+ else if (hay[found - 1] === " " || hay[found - 1] === "-" || hay[found - 1] === "/" || hay[found - 1] === "_") {
779
+ score += 30;
780
+ }
781
+ if (lastMatch !== -1 && found === lastMatch + 1) score += 10;
782
+ if (lastMatch !== -1) score -= found - lastMatch - 1;
783
+ matches.push(found);
784
+ lastMatch = found;
785
+ h = found + 1;
786
+ }
787
+ return { score, matches };
788
+ }
789
+ function scoreCommand(item, query) {
790
+ var _a;
791
+ if (!query.trim()) {
792
+ return { item, score: 0, matches: [] };
793
+ }
794
+ const labelScore = scoreString(item.label, query);
795
+ if (labelScore) {
796
+ return { item, score: labelScore.score, matches: labelScore.matches };
797
+ }
798
+ if ((_a = item.keywords) == null ? void 0 : _a.length) {
799
+ for (const kw of item.keywords) {
800
+ const s = scoreString(kw, query);
801
+ if (s) return { item, score: s.score * 0.5, matches: [] };
802
+ }
803
+ }
804
+ const catScore = scoreString(item.category, query);
805
+ if (catScore) {
806
+ return { item, score: catScore.score * 0.25, matches: [] };
807
+ }
808
+ return null;
809
+ }
810
+ function useFuzzySearch(items, query) {
811
+ return useMemo(() => {
812
+ if (!query.trim()) {
813
+ return items.map((item) => ({ item, score: 0, matches: [] }));
814
+ }
815
+ const out = [];
816
+ for (const item of items) {
817
+ const s = scoreCommand(item, query);
818
+ if (s) out.push(s);
819
+ }
820
+ return out.sort((a, b) => b.score - a.score);
821
+ }, [items, query]);
822
+ }
823
+ function highlightMatches(label, matches) {
824
+ if (!matches.length) return [label];
825
+ const set = new Set(matches);
826
+ const out = [];
827
+ let buf = "";
828
+ for (let i = 0; i < label.length; i++) {
829
+ if (set.has(i)) {
830
+ if (buf) {
831
+ out.push(buf);
832
+ buf = "";
833
+ }
834
+ out.push(
835
+ // eslint-disable-next-line react/no-array-index-key
836
+ /* @__PURE__ */ jsx11("mark", { className: "bg-warning-subtle text-text-primary rounded-sm px-0.5", children: label[i] }, `m-${i}`)
837
+ );
838
+ } else {
839
+ buf += label[i];
840
+ }
841
+ }
842
+ if (buf) out.push(buf);
843
+ return out;
844
+ }
845
+
846
+ // modules/app/CommandPalette/hooks/useCommandStore.ts
847
+ import { useEffect as useEffect3, useMemo as useMemo2, useSyncExternalStore } from "react";
848
+ var registry = /* @__PURE__ */ new Map();
849
+ var listeners = /* @__PURE__ */ new Set();
850
+ function subscribe(fn) {
851
+ listeners.add(fn);
852
+ return () => {
853
+ listeners.delete(fn);
854
+ };
855
+ }
856
+ function getSnapshot() {
857
+ return Array.from(registry.values());
858
+ }
859
+ function getServerSnapshot() {
860
+ return [];
861
+ }
862
+ function commandKey(cmd) {
863
+ var _a;
864
+ return (_a = cmd.id) != null ? _a : `${cmd.category}::${cmd.label}`;
865
+ }
866
+ function useCommandStore() {
867
+ return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
868
+ }
869
+ function useMergedCommands(defaults) {
870
+ const dynamic = useCommandStore();
871
+ return useMemo2(() => {
872
+ const seen = /* @__PURE__ */ new Map();
873
+ for (const c of defaults) seen.set(commandKey(c), c);
874
+ for (const c of dynamic) seen.set(commandKey(c), c);
875
+ return Array.from(seen.values());
876
+ }, [defaults, dynamic]);
877
+ }
878
+
879
+ // modules/app/CommandPalette/hooks/useShortcuts.ts
880
+ import { useEffect as useEffect4 } from "react";
881
+ function useShortcuts({ isOpen, onOpen, onClose }) {
882
+ useEffect4(() => {
883
+ function handler(e) {
884
+ if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "k") {
885
+ e.preventDefault();
886
+ if (isOpen) onClose();
887
+ else onOpen();
888
+ return;
889
+ }
890
+ if (e.key === "Escape" && isOpen) {
891
+ onClose();
892
+ }
893
+ }
894
+ document.addEventListener("keydown", handler);
895
+ return () => document.removeEventListener("keydown", handler);
896
+ }, [isOpen, onOpen, onClose]);
897
+ }
898
+
899
+ // modules/app/CommandPalette/parts/Input.tsx
900
+ import { forwardRef } from "react";
901
+ import { FontAwesomeIcon as FontAwesomeIcon6 } from "@fortawesome/react-fontawesome";
902
+ import { faMagnifyingGlass as faMagnifyingGlass2 } from "@fortawesome/free-solid-svg-icons";
903
+ import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
904
+ var CommandPaletteInput = forwardRef(
905
+ function CommandPaletteInput2({ id, value, onChange, onKeyDown, placeholder, listboxId, activeDescendantId }, ref) {
906
+ return /* @__PURE__ */ jsxs11("div", { className: "relative flex items-center", children: [
907
+ /* @__PURE__ */ jsx12(
908
+ "span",
909
+ {
910
+ "aria-hidden": "true",
911
+ className: "absolute left-3 text-text-disabled pointer-events-none text-sm",
912
+ children: /* @__PURE__ */ jsx12(FontAwesomeIcon6, { icon: faMagnifyingGlass2, className: "w-3.5 h-3.5" })
913
+ }
914
+ ),
915
+ /* @__PURE__ */ jsx12(
916
+ "input",
917
+ {
918
+ ref,
919
+ id,
920
+ type: "search",
921
+ role: "combobox",
922
+ "aria-expanded": "true",
923
+ "aria-autocomplete": "list",
924
+ "aria-controls": listboxId,
925
+ "aria-activedescendant": activeDescendantId,
926
+ value,
927
+ onChange: (e) => onChange(e.target.value),
928
+ onKeyDown,
929
+ placeholder,
930
+ autoComplete: "off",
931
+ className: cn(
932
+ "block w-full rounded-md border border-border bg-surface-base pl-9 pr-3 py-2 text-sm",
933
+ "text-text-primary placeholder:text-text-disabled",
934
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:border-border-focus",
935
+ "transition-colors"
936
+ )
937
+ }
938
+ )
939
+ ] });
940
+ }
941
+ );
942
+
943
+ // modules/app/CommandPalette/parts/ResultItem.tsx
944
+ import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
945
+ function ResultItem({ id, scored, active, onSelect, onHover }) {
946
+ const { item, matches } = scored;
947
+ return /* @__PURE__ */ jsxs12(
948
+ "button",
949
+ {
950
+ id,
951
+ type: "button",
952
+ role: "option",
953
+ "aria-selected": active,
954
+ tabIndex: -1,
955
+ onClick: onSelect,
956
+ onMouseEnter: onHover,
957
+ className: cn(
958
+ "w-full flex items-center justify-between gap-3 px-3 py-2 rounded-md text-sm text-text-primary",
959
+ "transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus",
960
+ active ? "bg-surface-overlay" : "hover:bg-surface-overlay"
961
+ ),
962
+ children: [
963
+ /* @__PURE__ */ jsxs12("span", { className: "flex items-center gap-2 min-w-0", children: [
964
+ item.icon && /* @__PURE__ */ jsx13("span", { "aria-hidden": "true", className: "shrink-0", children: item.icon }),
965
+ /* @__PURE__ */ jsx13("span", { className: "truncate", children: highlightMatches(item.label, matches) }),
966
+ item.description && /* @__PURE__ */ jsx13("span", { className: "text-text-secondary text-xs truncate", children: item.description })
967
+ ] }),
968
+ item.shortcut && /* @__PURE__ */ jsx13(Badge, { variant: "neutral", size: "sm", children: item.shortcut })
969
+ ]
970
+ }
971
+ );
972
+ }
973
+
974
+ // modules/app/CommandPalette/parts/ResultList.tsx
975
+ import { jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
976
+ function ResultList({
977
+ listboxId,
978
+ itemIdPrefix,
979
+ groups,
980
+ flat,
981
+ activeIndex,
982
+ onSelect,
983
+ onHover
984
+ }) {
985
+ return /* @__PURE__ */ jsx14("div", { id: listboxId, role: "listbox", className: "max-h-72 overflow-y-auto space-y-3", children: groups.map((group) => /* @__PURE__ */ jsxs13("div", { children: [
986
+ /* @__PURE__ */ jsx14("div", { className: "flex items-center gap-2 mb-1", children: /* @__PURE__ */ jsx14(Badge, { variant: "neutral", size: "sm", children: group.category }) }),
987
+ /* @__PURE__ */ jsx14("div", { className: "space-y-0.5", children: group.items.map((scored) => {
988
+ var _a;
989
+ const flatIndex = flat.indexOf(scored);
990
+ const itemId = `${itemIdPrefix}-${flatIndex}`;
991
+ return /* @__PURE__ */ jsx14(
992
+ ResultItem,
993
+ {
994
+ id: itemId,
995
+ scored,
996
+ active: flatIndex === activeIndex,
997
+ onSelect: () => onSelect(scored),
998
+ onHover: () => onHover(flatIndex)
999
+ },
1000
+ ((_a = scored.item.id) != null ? _a : scored.item.label) + "::" + group.category
1001
+ );
1002
+ }) })
1003
+ ] }, group.category)) });
1004
+ }
1005
+
1006
+ // modules/app/CommandPalette/parts/EmptyState.tsx
1007
+ import { Fragment as Fragment5, jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
1008
+ function EmptyState2({ query, suggestions, onSelect }) {
1009
+ return /* @__PURE__ */ jsxs14("div", { className: "py-4 text-center", children: [
1010
+ /* @__PURE__ */ jsxs14("p", { className: "text-sm text-text-secondary", children: [
1011
+ 'No commands found for "',
1012
+ query,
1013
+ '"'
1014
+ ] }),
1015
+ suggestions.length > 0 && /* @__PURE__ */ jsxs14(Fragment5, { children: [
1016
+ /* @__PURE__ */ jsx15("p", { className: "mt-3 text-xs text-text-disabled", children: "Try one of these instead:" }),
1017
+ /* @__PURE__ */ jsx15("div", { className: "mt-2 flex flex-wrap justify-center gap-1.5", children: suggestions.map((cmd) => {
1018
+ var _a;
1019
+ return /* @__PURE__ */ jsx15(
1020
+ "button",
1021
+ {
1022
+ type: "button",
1023
+ onClick: () => onSelect(cmd),
1024
+ className: cn(
1025
+ "rounded-full border border-border bg-surface-raised px-2.5 py-1 text-xs",
1026
+ "text-text-secondary hover:bg-surface-overlay transition-colors",
1027
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus"
1028
+ ),
1029
+ children: cmd.label
1030
+ },
1031
+ (_a = cmd.id) != null ? _a : cmd.label
1032
+ );
1033
+ }) })
1034
+ ] })
1035
+ ] });
1036
+ }
1037
+
1038
+ // modules/app/CommandPalette/parts/Footer.tsx
1039
+ import { jsx as jsx16, jsxs as jsxs15 } from "react/jsx-runtime";
1040
+ function Footer() {
1041
+ return /* @__PURE__ */ jsxs15("div", { className: "flex items-center gap-4 border-t border-border pt-3 text-[10px] text-text-disabled", children: [
1042
+ /* @__PURE__ */ jsxs15("span", { children: [
1043
+ /* @__PURE__ */ jsx16("kbd", { className: "rounded border border-border px-1 py-0.5 font-mono text-[9px]", children: "\u2191\u2193" }),
1044
+ " ",
1045
+ "Navigate"
1046
+ ] }),
1047
+ /* @__PURE__ */ jsxs15("span", { children: [
1048
+ /* @__PURE__ */ jsx16("kbd", { className: "rounded border border-border px-1 py-0.5 font-mono text-[9px]", children: "\u21B5" }),
1049
+ " ",
1050
+ "Select"
1051
+ ] }),
1052
+ /* @__PURE__ */ jsxs15("span", { children: [
1053
+ /* @__PURE__ */ jsx16("kbd", { className: "rounded border border-border px-1 py-0.5 font-mono text-[9px]", children: "Esc" }),
1054
+ " ",
1055
+ "Close"
1056
+ ] })
1057
+ ] });
1058
+ }
1059
+
1060
+ // modules/app/CommandPalette/index.tsx
1061
+ import { Fragment as Fragment6, jsx as jsx17, jsxs as jsxs16 } from "react/jsx-runtime";
1062
+ var DEFAULT_COMMANDS = [
1063
+ { id: "nav-dashboard", icon: /* @__PURE__ */ jsx17(FontAwesomeIcon7, { icon: faHouse2, className: "w-3.5 h-3.5", "aria-hidden": "true" }), label: "Go to Dashboard", shortcut: "G D", category: "Navigation" },
1064
+ { id: "nav-projects", icon: /* @__PURE__ */ jsx17(FontAwesomeIcon7, { icon: faFolder2, className: "w-3.5 h-3.5", "aria-hidden": "true" }), label: "Go to Projects", shortcut: "G P", category: "Navigation" },
1065
+ { id: "nav-team", icon: /* @__PURE__ */ jsx17(FontAwesomeIcon7, { icon: faUsers, className: "w-3.5 h-3.5", "aria-hidden": "true" }), label: "Go to Team", shortcut: "G T", category: "Navigation" },
1066
+ { id: "nav-settings", icon: /* @__PURE__ */ jsx17(FontAwesomeIcon7, { icon: faGear, className: "w-3.5 h-3.5", "aria-hidden": "true" }), label: "Go to Settings", shortcut: "G S", category: "Navigation" },
1067
+ { id: "nav-analytics", icon: /* @__PURE__ */ jsx17(FontAwesomeIcon7, { icon: faChartBar, className: "w-3.5 h-3.5", "aria-hidden": "true" }), label: "Go to Analytics", shortcut: "G A", category: "Navigation" },
1068
+ { id: "act-new-project", icon: /* @__PURE__ */ jsx17(FontAwesomeIcon7, { icon: faPlus, className: "w-3.5 h-3.5", "aria-hidden": "true" }), label: "New Project", shortcut: "\u2318N", category: "Actions" },
1069
+ { id: "act-invite", icon: /* @__PURE__ */ jsx17(FontAwesomeIcon7, { icon: faEnvelope, className: "w-3.5 h-3.5", "aria-hidden": "true" }), label: "Send Invite", shortcut: "\u2318I", category: "Actions" },
1070
+ { id: "act-export", icon: /* @__PURE__ */ jsx17(FontAwesomeIcon7, { icon: faFileExport, className: "w-3.5 h-3.5", "aria-hidden": "true" }), label: "Export Data", shortcut: "\u2318E", category: "Actions" },
1071
+ { id: "act-lock", icon: /* @__PURE__ */ jsx17(FontAwesomeIcon7, { icon: faLock, className: "w-3.5 h-3.5", "aria-hidden": "true" }), label: "Lock Screen", shortcut: "\u2318L", category: "Actions" },
1072
+ { id: "rec-alpha", icon: /* @__PURE__ */ jsx17(FontAwesomeIcon7, { icon: faClock, className: "w-3.5 h-3.5", "aria-hidden": "true" }), label: "Project Alpha", category: "Recent" },
1073
+ { id: "rec-q3", icon: /* @__PURE__ */ jsx17(FontAwesomeIcon7, { icon: faClock, className: "w-3.5 h-3.5", "aria-hidden": "true" }), label: "Q3 Report", category: "Recent" },
1074
+ { id: "rec-review", icon: /* @__PURE__ */ jsx17(FontAwesomeIcon7, { icon: faClock, className: "w-3.5 h-3.5", "aria-hidden": "true" }), label: "Design Review", category: "Recent" }
1075
+ ];
1076
+ function groupByCategory(scored) {
1077
+ const order = [];
1078
+ const map = /* @__PURE__ */ new Map();
1079
+ for (const s of scored) {
1080
+ if (!map.has(s.item.category)) {
1081
+ order.push(s.item.category);
1082
+ map.set(s.item.category, []);
1083
+ }
1084
+ map.get(s.item.category).push(s);
1085
+ }
1086
+ return order.map((category) => ({ category, items: map.get(category) }));
1087
+ }
1088
+ function CommandPalette({
1089
+ items = DEFAULT_COMMANDS,
1090
+ onSelect,
1091
+ trigger,
1092
+ placeholder = "Type a command or search\u2026"
1093
+ }) {
1094
+ const [open, setOpen] = useState6(false);
1095
+ const [query, setQuery] = useState6("");
1096
+ const [activeIndex, setActiveIndex] = useState6(0);
1097
+ const inputRef = useRef2(null);
1098
+ const reactId = useId();
1099
+ const listboxId = `${reactId}-list`;
1100
+ const itemIdPrefix = `${reactId}-opt`;
1101
+ const inputId = `${reactId}-input`;
1102
+ const merged = useMergedCommands(items);
1103
+ const scored = useFuzzySearch(merged, query);
1104
+ const groups = useMemo3(() => groupByCategory(scored), [scored]);
1105
+ const flat = scored;
1106
+ const hasResults = flat.length > 0;
1107
+ const activeDescendantId = hasResults ? `${itemIdPrefix}-${activeIndex}` : void 0;
1108
+ useShortcuts({
1109
+ isOpen: open,
1110
+ onOpen: () => setOpen(true),
1111
+ onClose: () => {
1112
+ setOpen(false);
1113
+ setQuery("");
1114
+ setActiveIndex(0);
1115
+ }
1116
+ });
1117
+ function handleSelect(cmd) {
1118
+ var _a;
1119
+ (_a = cmd.onClick) == null ? void 0 : _a.call(cmd);
1120
+ onSelect == null ? void 0 : onSelect(cmd);
1121
+ setOpen(false);
1122
+ setQuery("");
1123
+ setActiveIndex(0);
1124
+ }
1125
+ function handleKeyDown(e) {
1126
+ if (!hasResults) return;
1127
+ if (e.key === "ArrowDown") {
1128
+ e.preventDefault();
1129
+ setActiveIndex((i) => (i + 1) % flat.length);
1130
+ } else if (e.key === "ArrowUp") {
1131
+ e.preventDefault();
1132
+ setActiveIndex((i) => (i - 1 + flat.length) % flat.length);
1133
+ } else if (e.key === "Home") {
1134
+ e.preventDefault();
1135
+ setActiveIndex(0);
1136
+ } else if (e.key === "End") {
1137
+ e.preventDefault();
1138
+ setActiveIndex(flat.length - 1);
1139
+ } else if (e.key === "Enter") {
1140
+ e.preventDefault();
1141
+ const picked = flat[activeIndex];
1142
+ if (picked) handleSelect(picked.item);
1143
+ }
1144
+ }
1145
+ function handleQueryChange(next) {
1146
+ setQuery(next);
1147
+ setActiveIndex(0);
1148
+ }
1149
+ const suggestions = useMemo3(() => merged.slice(0, 4), [merged]);
1150
+ return /* @__PURE__ */ jsxs16(Fragment6, { children: [
1151
+ /* @__PURE__ */ jsx17("div", { role: "none", onClick: () => setOpen(true), children: trigger != null ? trigger : /* @__PURE__ */ jsx17(
1152
+ Button,
1153
+ {
1154
+ variant: "outline",
1155
+ size: "sm",
1156
+ iconRight: /* @__PURE__ */ jsx17(Badge, { variant: "neutral", size: "sm", children: "\u2318K" }),
1157
+ children: "Quick actions\u2026"
1158
+ }
1159
+ ) }),
1160
+ /* @__PURE__ */ jsx17(
1161
+ Modal,
1162
+ {
1163
+ open,
1164
+ onClose: () => {
1165
+ setOpen(false);
1166
+ setQuery("");
1167
+ setActiveIndex(0);
1168
+ },
1169
+ title: "Command Palette",
1170
+ description: "Search for actions, navigate, or run recent commands.",
1171
+ size: "lg",
1172
+ scrollable: true,
1173
+ children: /* @__PURE__ */ jsxs16("div", { className: "space-y-4", children: [
1174
+ /* @__PURE__ */ jsx17(
1175
+ CommandPaletteInput,
1176
+ {
1177
+ ref: inputRef,
1178
+ id: inputId,
1179
+ value: query,
1180
+ onChange: handleQueryChange,
1181
+ onKeyDown: handleKeyDown,
1182
+ placeholder,
1183
+ listboxId,
1184
+ activeDescendantId
1185
+ }
1186
+ ),
1187
+ /* @__PURE__ */ jsx17(
1188
+ AlertBanner,
1189
+ {
1190
+ variant: "info",
1191
+ message: "Pro tip: Press \u2318K from anywhere to open this palette."
1192
+ }
1193
+ ),
1194
+ hasResults ? /* @__PURE__ */ jsx17(
1195
+ ResultList,
1196
+ {
1197
+ listboxId,
1198
+ itemIdPrefix,
1199
+ groups,
1200
+ flat,
1201
+ activeIndex,
1202
+ onSelect: (s) => handleSelect(s.item),
1203
+ onHover: (i) => setActiveIndex(i)
1204
+ }
1205
+ ) : /* @__PURE__ */ jsx17(
1206
+ EmptyState2,
1207
+ {
1208
+ query,
1209
+ suggestions,
1210
+ onSelect: handleSelect
1211
+ }
1212
+ ),
1213
+ /* @__PURE__ */ jsx17(Footer, {})
1214
+ ] })
1215
+ }
1216
+ )
1217
+ ] });
1218
+ }
1219
+ var AppCommandBar = CommandPalette;
1220
+
1221
+ // modules/app/ContextMenu.tsx
1222
+ import { useState as useState7, useEffect as useEffect5, useRef as useRef3, useCallback, useId as useId2 } from "react";
1223
+ import { jsx as jsx18, jsxs as jsxs17 } from "react/jsx-runtime";
1224
+ function getActionItems(items) {
1225
+ return items.reduce((acc, item, i) => {
1226
+ if ((!item.type || item.type === "item") && !item.disabled) acc.push(i);
1227
+ return acc;
1228
+ }, []);
1229
+ }
1230
+ function ContextMenu({
1231
+ items,
1232
+ children,
1233
+ disabled = false,
1234
+ className,
1235
+ onOpenChange
1236
+ }) {
1237
+ const [menu, setMenu] = useState7(null);
1238
+ const [focusedActionIdx, setFocusedActionIdx] = useState7(-1);
1239
+ const menuRef = useRef3(null);
1240
+ const labelId = useId2();
1241
+ const isOpen = menu !== null;
1242
+ const open = useCallback(
1243
+ (clientX, clientY) => {
1244
+ setMenu({ rawX: clientX, rawY: clientY, adjX: clientX, adjY: clientY, measured: false });
1245
+ setFocusedActionIdx(-1);
1246
+ onOpenChange == null ? void 0 : onOpenChange(true);
1247
+ },
1248
+ [onOpenChange]
1249
+ );
1250
+ const close = useCallback(() => {
1251
+ setMenu(null);
1252
+ setFocusedActionIdx(-1);
1253
+ onOpenChange == null ? void 0 : onOpenChange(false);
1254
+ }, [onOpenChange]);
1255
+ const handleContextMenu = (e) => {
1256
+ if (disabled) return;
1257
+ e.preventDefault();
1258
+ e.stopPropagation();
1259
+ open(e.clientX, e.clientY);
1260
+ };
1261
+ useEffect5(() => {
1262
+ if (!menu || menu.measured || !menuRef.current) return;
1263
+ const el = menuRef.current;
1264
+ const { width, height } = el.getBoundingClientRect();
1265
+ const vw = window.innerWidth;
1266
+ const vh = window.innerHeight;
1267
+ const GAP = 8;
1268
+ const adjX = menu.rawX + width > vw - GAP ? Math.max(GAP, menu.rawX - width) : menu.rawX;
1269
+ const adjY = menu.rawY + height > vh - GAP ? Math.max(GAP, menu.rawY - height) : menu.rawY;
1270
+ setMenu((m) => m ? __spreadProps(__spreadValues({}, m), { adjX, adjY, measured: true }) : null);
1271
+ }, [menu]);
1272
+ useEffect5(() => {
1273
+ if (!isOpen) return;
1274
+ const onPointer = (e) => {
1275
+ var _a;
1276
+ if (!((_a = menuRef.current) == null ? void 0 : _a.contains(e.target))) close();
1277
+ };
1278
+ const onScroll = () => close();
1279
+ document.addEventListener("mousedown", onPointer);
1280
+ window.addEventListener("scroll", onScroll, { capture: true, passive: true });
1281
+ return () => {
1282
+ document.removeEventListener("mousedown", onPointer);
1283
+ window.removeEventListener("scroll", onScroll, { capture: true });
1284
+ };
1285
+ }, [isOpen, close]);
1286
+ useEffect5(() => {
1287
+ if (!isOpen) return;
1288
+ const actionIndices = getActionItems(items);
1289
+ const onKey = (e) => {
1290
+ if (e.key === "Escape") {
1291
+ e.preventDefault();
1292
+ close();
1293
+ return;
1294
+ }
1295
+ if (e.key === "ArrowDown") {
1296
+ e.preventDefault();
1297
+ setFocusedActionIdx((idx) => {
1298
+ var _a;
1299
+ const next = actionIndices.indexOf(idx) + 1;
1300
+ return (_a = actionIndices[next < actionIndices.length ? next : 0]) != null ? _a : -1;
1301
+ });
1302
+ return;
1303
+ }
1304
+ if (e.key === "ArrowUp") {
1305
+ e.preventDefault();
1306
+ setFocusedActionIdx((idx) => {
1307
+ var _a;
1308
+ const pos = actionIndices.indexOf(idx);
1309
+ const prev = pos <= 0 ? actionIndices.length - 1 : pos - 1;
1310
+ return (_a = actionIndices[prev]) != null ? _a : -1;
1311
+ });
1312
+ return;
1313
+ }
1314
+ if (e.key === "Enter" || e.key === " ") {
1315
+ e.preventDefault();
1316
+ const item = items[focusedActionIdx];
1317
+ if (item && (!item.type || item.type === "item") && item.onClick) {
1318
+ item.onClick();
1319
+ close();
1320
+ }
1321
+ }
1322
+ };
1323
+ window.addEventListener("keydown", onKey);
1324
+ return () => window.removeEventListener("keydown", onKey);
1325
+ }, [isOpen, items, focusedActionIdx, close]);
1326
+ useEffect5(() => {
1327
+ if (!menuRef.current || focusedActionIdx < 0) return;
1328
+ const btn = menuRef.current.querySelector(
1329
+ `[data-item-index="${focusedActionIdx}"]`
1330
+ );
1331
+ btn == null ? void 0 : btn.focus({ preventScroll: true });
1332
+ }, [focusedActionIdx]);
1333
+ return /* @__PURE__ */ jsxs17(
1334
+ "div",
1335
+ {
1336
+ className: cn("relative", className),
1337
+ onContextMenu: handleContextMenu,
1338
+ children: [
1339
+ children,
1340
+ menu && /* @__PURE__ */ jsx18(
1341
+ "div",
1342
+ {
1343
+ ref: menuRef,
1344
+ role: "menu",
1345
+ "aria-labelledby": labelId,
1346
+ style: {
1347
+ position: "fixed",
1348
+ top: menu.adjY,
1349
+ left: menu.adjX,
1350
+ visibility: menu.measured ? "visible" : "hidden",
1351
+ zIndex: 9999
1352
+ },
1353
+ className: cn(
1354
+ "min-w-[13rem] rounded-xl border border-border bg-surface-raised shadow-2xl py-1.5",
1355
+ "outline-none",
1356
+ // Subtle enter animation (opacity only, no layout shift)
1357
+ menu.measured && "animate-in fade-in-0 zoom-in-95 duration-100"
1358
+ ),
1359
+ children: items.map((item, i) => {
1360
+ if (item.type === "separator") {
1361
+ return /* @__PURE__ */ jsx18(
1362
+ "div",
1363
+ {
1364
+ role: "separator",
1365
+ "aria-orientation": "horizontal",
1366
+ className: "my-1 mx-2 border-t border-border"
1367
+ },
1368
+ i
1369
+ );
1370
+ }
1371
+ if (item.type === "group") {
1372
+ return /* @__PURE__ */ jsx18(
1373
+ "p",
1374
+ {
1375
+ role: "presentation",
1376
+ className: "px-3 pt-2 pb-0.5 text-[11px] font-semibold uppercase tracking-widest text-text-disabled select-none",
1377
+ children: item.label
1378
+ },
1379
+ i
1380
+ );
1381
+ }
1382
+ const isActive = i === focusedActionIdx;
1383
+ return /* @__PURE__ */ jsxs17(
1384
+ "button",
1385
+ {
1386
+ type: "button",
1387
+ role: "menuitem",
1388
+ tabIndex: isActive ? 0 : -1,
1389
+ "data-item-index": i,
1390
+ disabled: item.disabled,
1391
+ onClick: () => {
1392
+ var _a;
1393
+ if (!item.disabled) {
1394
+ (_a = item.onClick) == null ? void 0 : _a.call(item);
1395
+ close();
1396
+ }
1397
+ },
1398
+ onMouseEnter: () => setFocusedActionIdx(i),
1399
+ onMouseLeave: () => setFocusedActionIdx(-1),
1400
+ className: cn(
1401
+ "flex w-full items-center gap-2.5 px-3 py-2 text-sm text-left transition-colors select-none",
1402
+ "focus-visible:outline-none",
1403
+ item.danger ? "text-error hover:bg-error-subtle focus-visible:bg-error-subtle" : "text-text-primary hover:bg-surface-overlay focus-visible:bg-surface-overlay",
1404
+ item.disabled && "opacity-40 cursor-not-allowed pointer-events-none"
1405
+ ),
1406
+ children: [
1407
+ item.icon != null && /* @__PURE__ */ jsx18(
1408
+ "span",
1409
+ {
1410
+ "aria-hidden": "true",
1411
+ className: cn(
1412
+ "w-4 flex items-center justify-center shrink-0",
1413
+ item.danger ? "text-error" : "text-text-secondary"
1414
+ ),
1415
+ children: item.icon
1416
+ }
1417
+ ),
1418
+ /* @__PURE__ */ jsx18("span", { className: "flex-1 truncate", children: item.label }),
1419
+ item.shortcut && /* @__PURE__ */ jsx18("kbd", { className: "shrink-0 ml-6 text-[11px] font-mono text-text-disabled", children: item.shortcut })
1420
+ ]
1421
+ },
1422
+ i
1423
+ );
1424
+ })
1425
+ }
1426
+ )
1427
+ ]
1428
+ }
1429
+ );
1430
+ }
1431
+
1432
+ // modules/app/ImageGallery/index.tsx
1433
+ import { useState as useState9, useEffect as useEffect7, useCallback as useCallback4 } from "react";
1434
+
1435
+ // modules/app/ImageGallery/constants.ts
1436
+ var columnClasses = {
1437
+ 2: "grid-cols-2",
1438
+ 3: "grid-cols-2 sm:grid-cols-3",
1439
+ 4: "grid-cols-2 sm:grid-cols-3 md:grid-cols-4"
1440
+ };
1441
+ var gapClasses = {
1442
+ sm: "gap-1",
1443
+ md: "gap-2",
1444
+ lg: "gap-4"
1445
+ };
1446
+ var aspectClasses = {
1447
+ square: "aspect-square",
1448
+ video: "aspect-video",
1449
+ portrait: "aspect-[3/4]",
1450
+ auto: ""
1451
+ };
1452
+
1453
+ // modules/app/ImageGallery/parts/GalleryItem.tsx
1454
+ import { FontAwesomeIcon as FontAwesomeIcon8 } from "@fortawesome/react-fontawesome";
1455
+ import { faExpand, faGripVertical } from "@fortawesome/free-solid-svg-icons";
1456
+ import { jsx as jsx19, jsxs as jsxs18 } from "react/jsx-runtime";
1457
+ function GalleryItem({
1458
+ image,
1459
+ index,
1460
+ aspectClass,
1461
+ aspectIsAuto,
1462
+ reorderable,
1463
+ isDragging,
1464
+ isDropTarget,
1465
+ lightbox,
1466
+ showCaptions,
1467
+ onOpenLightbox,
1468
+ dragHandlers
1469
+ }) {
1470
+ return /* @__PURE__ */ jsxs18(
1471
+ "div",
1472
+ {
1473
+ role: "listitem",
1474
+ draggable: reorderable,
1475
+ onDragStart: reorderable ? () => dragHandlers.onDragStart(index) : void 0,
1476
+ onDragOver: reorderable ? (e) => dragHandlers.onDragOver(e, index) : void 0,
1477
+ onDragLeave: reorderable ? dragHandlers.onDragLeave : void 0,
1478
+ onDrop: reorderable ? () => dragHandlers.onDrop(index) : void 0,
1479
+ onDragEnd: reorderable ? dragHandlers.onDragEnd : void 0,
1480
+ className: cn(
1481
+ "group relative overflow-hidden rounded-lg bg-surface-sunken transition-all duration-200",
1482
+ !aspectIsAuto && aspectClass,
1483
+ reorderable && "cursor-grab active:cursor-grabbing",
1484
+ isDragging && "opacity-40 scale-95 ring-2 ring-[var(--primary)] ring-inset",
1485
+ isDropTarget && "ring-2 ring-[var(--primary)] shadow-lg scale-[1.02]"
1486
+ ),
1487
+ children: [
1488
+ /* @__PURE__ */ jsx19(
1489
+ "img",
1490
+ {
1491
+ src: image.src,
1492
+ alt: image.alt,
1493
+ loading: "lazy",
1494
+ draggable: false,
1495
+ className: cn(
1496
+ "w-full h-full object-cover transition-transform duration-300 group-hover:scale-105",
1497
+ aspectIsAuto && "aspect-square",
1498
+ isDragging && "pointer-events-none"
1499
+ )
1500
+ }
1501
+ ),
1502
+ reorderable && /* @__PURE__ */ jsx19(
1503
+ "div",
1504
+ {
1505
+ "aria-hidden": "true",
1506
+ className: "absolute top-1.5 left-1.5 z-10 w-6 h-6 flex items-center justify-center rounded bg-black/40 text-white opacity-0 group-hover:opacity-100 transition-opacity duration-200",
1507
+ children: /* @__PURE__ */ jsx19(FontAwesomeIcon8, { icon: faGripVertical, className: "w-3 h-3" })
1508
+ }
1509
+ ),
1510
+ lightbox && /* @__PURE__ */ jsxs18(
1511
+ "button",
1512
+ {
1513
+ onClick: () => onOpenLightbox(index),
1514
+ "aria-label": `Open ${image.alt} in lightbox`,
1515
+ className: cn(
1516
+ "absolute inset-0 flex flex-col items-center justify-center gap-2",
1517
+ "bg-black/0 group-hover:bg-black/40 transition-all duration-300",
1518
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:ring-inset"
1519
+ ),
1520
+ children: [
1521
+ /* @__PURE__ */ jsx19(
1522
+ FontAwesomeIcon8,
1523
+ {
1524
+ icon: faExpand,
1525
+ "aria-hidden": "true",
1526
+ className: "text-white text-xl opacity-0 group-hover:opacity-100 transition-opacity duration-300 drop-shadow-md"
1527
+ }
1528
+ ),
1529
+ image.caption && /* @__PURE__ */ jsx19("span", { className: "text-white text-xs font-medium px-2 text-center opacity-0 group-hover:opacity-100 transition-opacity duration-300 line-clamp-2 drop-shadow-md", children: image.caption })
1530
+ ]
1531
+ }
1532
+ ),
1533
+ showCaptions && image.caption && /* @__PURE__ */ jsx19("p", { className: "absolute bottom-0 inset-x-0 bg-black/50 text-white text-xs px-2 py-1 line-clamp-1 pointer-events-none", children: image.caption })
1534
+ ]
1535
+ }
1536
+ );
1537
+ }
1538
+
1539
+ // modules/app/ImageGallery/parts/GalleryGrid.tsx
1540
+ import { jsx as jsx20 } from "react/jsx-runtime";
1541
+ function GalleryGrid({
1542
+ images,
1543
+ columns,
1544
+ aspect,
1545
+ gap,
1546
+ reorderable,
1547
+ lightbox,
1548
+ showCaptions,
1549
+ dragFrom,
1550
+ dragOver,
1551
+ dragHandlers,
1552
+ onOpenLightbox,
1553
+ buildMenuItems,
1554
+ className
1555
+ }) {
1556
+ const aspectIsAuto = aspect === "auto";
1557
+ return /* @__PURE__ */ jsx20(
1558
+ "div",
1559
+ {
1560
+ className: cn("grid", columnClasses[columns], gapClasses[gap], className),
1561
+ role: "list",
1562
+ "aria-label": "Image gallery",
1563
+ children: images.map((img, i) => {
1564
+ const isDragging = dragFrom === i;
1565
+ const isDropTarget = dragOver === i && dragFrom !== null && dragFrom !== i;
1566
+ const tile = /* @__PURE__ */ jsx20(
1567
+ GalleryItem,
1568
+ {
1569
+ image: img,
1570
+ index: i,
1571
+ aspectClass: aspectClasses[aspect],
1572
+ aspectIsAuto,
1573
+ reorderable,
1574
+ isDragging,
1575
+ isDropTarget,
1576
+ lightbox,
1577
+ showCaptions,
1578
+ onOpenLightbox,
1579
+ dragHandlers
1580
+ }
1581
+ );
1582
+ return reorderable ? /* @__PURE__ */ jsx20(ContextMenu, { items: buildMenuItems(i), children: tile }, `${img.src}-${i}`) : /* @__PURE__ */ jsx20("div", { children: tile }, `${img.src}-${i}`);
1583
+ })
1584
+ }
1585
+ );
1586
+ }
1587
+
1588
+ // modules/app/ImageGallery/parts/Lightbox.tsx
1589
+ import { FontAwesomeIcon as FontAwesomeIcon9 } from "@fortawesome/react-fontawesome";
1590
+ import {
1591
+ faXmark,
1592
+ faChevronLeft as faChevronLeft2,
1593
+ faChevronRight as faChevronRight2,
1594
+ faMagnifyingGlassPlus,
1595
+ faMagnifyingGlassMinus
1596
+ } from "@fortawesome/free-solid-svg-icons";
1597
+
1598
+ // modules/app/ImageGallery/hooks/useLightboxKeyboard.ts
1599
+ import { useEffect as useEffect6 } from "react";
1600
+ function useLightboxKeyboard({ open, onClose, onPrev, onNext }) {
1601
+ useEffect6(() => {
1602
+ if (!open) return;
1603
+ const onKey = (e) => {
1604
+ if (e.key === "Escape") onClose();
1605
+ if (e.key === "ArrowLeft") onPrev();
1606
+ if (e.key === "ArrowRight") onNext();
1607
+ };
1608
+ window.addEventListener("keydown", onKey);
1609
+ return () => window.removeEventListener("keydown", onKey);
1610
+ }, [open, onClose, onPrev, onNext]);
1611
+ useEffect6(() => {
1612
+ document.body.style.overflow = open ? "hidden" : "";
1613
+ return () => {
1614
+ document.body.style.overflow = "";
1615
+ };
1616
+ }, [open]);
1617
+ }
1618
+
1619
+ // modules/app/ImageGallery/parts/Lightbox.tsx
1620
+ import { Fragment as Fragment7, jsx as jsx21, jsxs as jsxs19 } from "react/jsx-runtime";
1621
+ function Lightbox({
1622
+ images,
1623
+ activeIndex,
1624
+ zoomed,
1625
+ onClose,
1626
+ onPrev,
1627
+ onNext,
1628
+ onSelectIndex,
1629
+ onToggleZoom
1630
+ }) {
1631
+ useLightboxKeyboard({ open: true, onClose, onPrev, onNext });
1632
+ const activeImage = images[activeIndex];
1633
+ if (!activeImage) return null;
1634
+ return /* @__PURE__ */ jsxs19(
1635
+ "div",
1636
+ {
1637
+ role: "dialog",
1638
+ "aria-modal": "true",
1639
+ "aria-label": "Image lightbox",
1640
+ className: "fixed inset-0 z-50 flex flex-col bg-black/95 backdrop-blur-sm",
1641
+ children: [
1642
+ /* @__PURE__ */ jsxs19("div", { className: "flex items-center justify-between px-4 py-3 shrink-0", children: [
1643
+ /* @__PURE__ */ jsxs19("span", { className: "text-white/70 text-sm tabular-nums select-none", children: [
1644
+ activeIndex + 1,
1645
+ " / ",
1646
+ images.length
1647
+ ] }),
1648
+ /* @__PURE__ */ jsxs19("div", { className: "flex items-center gap-2", children: [
1649
+ /* @__PURE__ */ jsx21(
1650
+ "button",
1651
+ {
1652
+ onClick: onToggleZoom,
1653
+ "aria-label": zoomed ? "Zoom out" : "Zoom in",
1654
+ className: "w-9 h-9 flex items-center justify-center rounded-full text-white/70 hover:text-white hover:bg-white/10 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white",
1655
+ children: /* @__PURE__ */ jsx21(
1656
+ FontAwesomeIcon9,
1657
+ {
1658
+ icon: zoomed ? faMagnifyingGlassMinus : faMagnifyingGlassPlus,
1659
+ "aria-hidden": "true"
1660
+ }
1661
+ )
1662
+ }
1663
+ ),
1664
+ /* @__PURE__ */ jsx21(
1665
+ "button",
1666
+ {
1667
+ onClick: onClose,
1668
+ "aria-label": "Close lightbox",
1669
+ className: "w-9 h-9 flex items-center justify-center rounded-full text-white/70 hover:text-white hover:bg-white/10 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white",
1670
+ children: /* @__PURE__ */ jsx21(FontAwesomeIcon9, { icon: faXmark, "aria-hidden": "true" })
1671
+ }
1672
+ )
1673
+ ] })
1674
+ ] }),
1675
+ /* @__PURE__ */ jsxs19("div", { className: "relative flex-1 flex items-center justify-center overflow-hidden px-14", children: [
1676
+ /* @__PURE__ */ jsx21(
1677
+ "img",
1678
+ {
1679
+ src: activeImage.src,
1680
+ alt: activeImage.alt,
1681
+ className: cn(
1682
+ "max-h-full max-w-full object-contain transition-transform duration-300 select-none",
1683
+ zoomed ? "scale-150 cursor-zoom-out" : "cursor-zoom-in"
1684
+ ),
1685
+ onClick: onToggleZoom,
1686
+ draggable: false
1687
+ }
1688
+ ),
1689
+ images.length > 1 && /* @__PURE__ */ jsxs19(Fragment7, { children: [
1690
+ /* @__PURE__ */ jsx21(
1691
+ "button",
1692
+ {
1693
+ onClick: onPrev,
1694
+ "aria-label": "Previous image",
1695
+ className: "absolute left-3 w-10 h-10 flex items-center justify-center rounded-full bg-white/10 hover:bg-white/25 text-white transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white",
1696
+ children: /* @__PURE__ */ jsx21(FontAwesomeIcon9, { icon: faChevronLeft2, "aria-hidden": "true" })
1697
+ }
1698
+ ),
1699
+ /* @__PURE__ */ jsx21(
1700
+ "button",
1701
+ {
1702
+ onClick: onNext,
1703
+ "aria-label": "Next image",
1704
+ className: "absolute right-3 w-10 h-10 flex items-center justify-center rounded-full bg-white/10 hover:bg-white/25 text-white transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white",
1705
+ children: /* @__PURE__ */ jsx21(FontAwesomeIcon9, { icon: faChevronRight2, "aria-hidden": "true" })
1706
+ }
1707
+ )
1708
+ ] })
1709
+ ] }),
1710
+ activeImage.caption && /* @__PURE__ */ jsx21("p", { className: "shrink-0 text-center text-white/80 text-sm px-6 py-2", children: activeImage.caption }),
1711
+ images.length > 1 && /* @__PURE__ */ jsx21("div", { className: "shrink-0 flex gap-2 overflow-x-auto px-4 py-3 justify-center", children: images.map((img, i) => /* @__PURE__ */ jsx21(
1712
+ "button",
1713
+ {
1714
+ onClick: () => onSelectIndex(i),
1715
+ "aria-label": `View ${img.alt}`,
1716
+ "aria-pressed": i === activeIndex,
1717
+ className: cn(
1718
+ "shrink-0 w-12 h-12 rounded overflow-hidden transition-all duration-200",
1719
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white",
1720
+ i === activeIndex ? "ring-2 ring-white opacity-100 scale-105" : "opacity-40 hover:opacity-70"
1721
+ ),
1722
+ children: /* @__PURE__ */ jsx21(
1723
+ "img",
1724
+ {
1725
+ src: img.src,
1726
+ alt: img.alt,
1727
+ className: "w-full h-full object-cover",
1728
+ draggable: false
1729
+ }
1730
+ )
1731
+ },
1732
+ i
1733
+ )) }),
1734
+ /* @__PURE__ */ jsx21(
1735
+ "button",
1736
+ {
1737
+ onClick: onClose,
1738
+ "aria-label": "Close lightbox",
1739
+ className: "absolute inset-0 -z-10 cursor-default focus-visible:outline-none",
1740
+ tabIndex: -1
1741
+ }
1742
+ )
1743
+ ]
1744
+ }
1745
+ );
1746
+ }
1747
+
1748
+ // modules/app/ImageGallery/hooks/useReorder.ts
1749
+ import { useState as useState8, useCallback as useCallback2 } from "react";
1750
+ function useReorder({ images, setImages, onReorder }) {
1751
+ const [dragFrom, setDragFrom] = useState8(null);
1752
+ const [dragOver, setDragOver] = useState8(null);
1753
+ const onDragStart = useCallback2((i) => setDragFrom(i), []);
1754
+ const onDragOver = useCallback2(
1755
+ (e, i) => {
1756
+ e.preventDefault();
1757
+ if (i !== dragFrom) setDragOver(i);
1758
+ },
1759
+ [dragFrom]
1760
+ );
1761
+ const onDragLeave = useCallback2(() => setDragOver(null), []);
1762
+ const onDrop = useCallback2(
1763
+ (dropIdx) => {
1764
+ if (dragFrom === null || dragFrom === dropIdx) {
1765
+ setDragFrom(null);
1766
+ setDragOver(null);
1767
+ return;
1768
+ }
1769
+ const next = [...images];
1770
+ const [moved] = next.splice(dragFrom, 1);
1771
+ next.splice(dropIdx, 0, moved);
1772
+ setImages(next);
1773
+ onReorder == null ? void 0 : onReorder(next);
1774
+ setDragFrom(null);
1775
+ setDragOver(null);
1776
+ },
1777
+ [dragFrom, images, onReorder, setImages]
1778
+ );
1779
+ const onDragEnd = useCallback2(() => {
1780
+ setDragFrom(null);
1781
+ setDragOver(null);
1782
+ }, []);
1783
+ const moveToIndex = useCallback2(
1784
+ (from, to) => {
1785
+ const next = [...images];
1786
+ const [moved] = next.splice(from, 1);
1787
+ next.splice(to, 0, moved);
1788
+ setImages(next);
1789
+ onReorder == null ? void 0 : onReorder(next);
1790
+ },
1791
+ [images, onReorder, setImages]
1792
+ );
1793
+ const removeAt = useCallback2(
1794
+ (i, onRemove) => {
1795
+ onRemove == null ? void 0 : onRemove(i, images[i]);
1796
+ const next = images.filter((_, idx) => idx !== i);
1797
+ setImages(next);
1798
+ onReorder == null ? void 0 : onReorder(next);
1799
+ },
1800
+ [images, onReorder, setImages]
1801
+ );
1802
+ return {
1803
+ dragFrom,
1804
+ dragOver,
1805
+ handlers: { onDragStart, onDragOver, onDragLeave, onDrop, onDragEnd },
1806
+ moveToIndex,
1807
+ removeAt
1808
+ };
1809
+ }
1810
+
1811
+ // modules/app/ImageGallery/hooks/useContextMenu.tsx
1812
+ import { useCallback as useCallback3 } from "react";
1813
+ import { FontAwesomeIcon as FontAwesomeIcon10 } from "@fortawesome/react-fontawesome";
1814
+ import {
1815
+ faExpand as faExpand2,
1816
+ faCopy,
1817
+ faAnglesLeft,
1818
+ faAnglesRight,
1819
+ faTrash
1820
+ } from "@fortawesome/free-solid-svg-icons";
1821
+ import { jsx as jsx22 } from "react/jsx-runtime";
1822
+ function useContextMenu({
1823
+ images,
1824
+ openLightbox,
1825
+ moveToIndex,
1826
+ removeAt
1827
+ }) {
1828
+ const copyUrl = useCallback3((src) => {
1829
+ var _a;
1830
+ (_a = navigator.clipboard) == null ? void 0 : _a.writeText(src).catch(() => {
1831
+ });
1832
+ }, []);
1833
+ const buildItems = useCallback3(
1834
+ (i) => [
1835
+ {
1836
+ label: "Open in lightbox",
1837
+ icon: /* @__PURE__ */ jsx22(FontAwesomeIcon10, { icon: faExpand2, className: "w-3.5 h-3.5", "aria-hidden": "true" }),
1838
+ onClick: () => openLightbox(i)
1839
+ },
1840
+ {
1841
+ label: "Copy image URL",
1842
+ icon: /* @__PURE__ */ jsx22(FontAwesomeIcon10, { icon: faCopy, className: "w-3.5 h-3.5", "aria-hidden": "true" }),
1843
+ shortcut: "\u2318C",
1844
+ onClick: () => copyUrl(images[i].src)
1845
+ },
1846
+ { type: "separator" },
1847
+ { type: "group", label: "Reorder" },
1848
+ {
1849
+ label: "Move to first",
1850
+ icon: /* @__PURE__ */ jsx22(FontAwesomeIcon10, { icon: faAnglesLeft, className: "w-3.5 h-3.5", "aria-hidden": "true" }),
1851
+ disabled: i === 0,
1852
+ onClick: () => moveToIndex(i, 0)
1853
+ },
1854
+ {
1855
+ label: "Move to last",
1856
+ icon: /* @__PURE__ */ jsx22(FontAwesomeIcon10, { icon: faAnglesRight, className: "w-3.5 h-3.5", "aria-hidden": "true" }),
1857
+ disabled: i === images.length - 1,
1858
+ onClick: () => moveToIndex(i, images.length - 1)
1859
+ },
1860
+ { type: "separator" },
1861
+ {
1862
+ label: "Remove",
1863
+ icon: /* @__PURE__ */ jsx22(FontAwesomeIcon10, { icon: faTrash, className: "w-3.5 h-3.5", "aria-hidden": "true" }),
1864
+ danger: true,
1865
+ onClick: () => removeAt(i)
1866
+ }
1867
+ ],
1868
+ [images, openLightbox, moveToIndex, removeAt, copyUrl]
1869
+ );
1870
+ return { buildItems };
1871
+ }
1872
+
1873
+ // modules/app/ImageGallery/index.tsx
1874
+ import { Fragment as Fragment8, jsx as jsx23, jsxs as jsxs20 } from "react/jsx-runtime";
1875
+ function ImageGallery({
1876
+ images: imagesProp,
1877
+ columns = 3,
1878
+ aspect = "square",
1879
+ gap = "md",
1880
+ lightbox = true,
1881
+ showCaptions = false,
1882
+ reorderable = false,
1883
+ onReorder,
1884
+ onRemove,
1885
+ className
1886
+ }) {
1887
+ const [images, setImages] = useState9(imagesProp);
1888
+ useEffect7(() => {
1889
+ setImages(imagesProp);
1890
+ }, [imagesProp]);
1891
+ const [activeIndex, setActiveIndex] = useState9(null);
1892
+ const [zoomed, setZoomed] = useState9(false);
1893
+ const isOpen = activeIndex !== null;
1894
+ const openLightbox = useCallback4((i) => {
1895
+ setActiveIndex(i);
1896
+ setZoomed(false);
1897
+ }, []);
1898
+ const closeLightbox = useCallback4(() => {
1899
+ setActiveIndex(null);
1900
+ setZoomed(false);
1901
+ }, []);
1902
+ const prevImage = useCallback4(() => {
1903
+ setActiveIndex((i) => i === null ? null : (i - 1 + images.length) % images.length);
1904
+ setZoomed(false);
1905
+ }, [images.length]);
1906
+ const nextImage = useCallback4(() => {
1907
+ setActiveIndex((i) => i === null ? null : (i + 1) % images.length);
1908
+ setZoomed(false);
1909
+ }, [images.length]);
1910
+ const selectIndex = useCallback4((i) => {
1911
+ setActiveIndex(i);
1912
+ setZoomed(false);
1913
+ }, []);
1914
+ const toggleZoom = useCallback4(() => setZoomed((z) => !z), []);
1915
+ const { dragFrom, dragOver, handlers, moveToIndex, removeAt } = useReorder({
1916
+ images,
1917
+ setImages,
1918
+ onReorder
1919
+ });
1920
+ const { buildItems } = useContextMenu({
1921
+ images,
1922
+ openLightbox,
1923
+ moveToIndex,
1924
+ removeAt: (i) => removeAt(i, onRemove)
1925
+ });
1926
+ return /* @__PURE__ */ jsxs20(Fragment8, { children: [
1927
+ /* @__PURE__ */ jsx23(
1928
+ GalleryGrid,
1929
+ {
1930
+ images,
1931
+ columns,
1932
+ aspect,
1933
+ gap,
1934
+ reorderable,
1935
+ lightbox,
1936
+ showCaptions,
1937
+ dragFrom,
1938
+ dragOver,
1939
+ dragHandlers: handlers,
1940
+ onOpenLightbox: openLightbox,
1941
+ buildMenuItems: buildItems,
1942
+ className
1943
+ }
1944
+ ),
1945
+ lightbox && isOpen && /* @__PURE__ */ jsx23(
1946
+ Lightbox,
1947
+ {
1948
+ images,
1949
+ activeIndex,
1950
+ zoomed,
1951
+ onClose: closeLightbox,
1952
+ onPrev: prevImage,
1953
+ onNext: nextImage,
1954
+ onSelectIndex: selectIndex,
1955
+ onToggleZoom: toggleZoom
1956
+ }
1957
+ )
1958
+ ] });
1959
+ }
1960
+
1961
+ // modules/app/FormField.tsx
1962
+ import { useFormContext } from "react-hook-form";
1963
+ import { jsx as jsx24, jsxs as jsxs21 } from "react/jsx-runtime";
1964
+ function FormField({
1965
+ name,
1966
+ label,
1967
+ hint,
1968
+ required,
1969
+ rules,
1970
+ className,
1971
+ children
1972
+ }) {
1973
+ const { register, formState: { errors } } = useFormContext();
1974
+ const error = errors[name];
1975
+ const errorMessage = typeof (error == null ? void 0 : error.message) === "string" ? error.message : void 0;
1976
+ const hintId = hint ? `${name}-hint` : void 0;
1977
+ const errorId = errorMessage ? `${name}-error` : void 0;
1978
+ const describedBy = [hintId, errorId].filter(Boolean).join(" ") || void 0;
1979
+ void register(name, rules);
1980
+ return /* @__PURE__ */ jsxs21("div", { className: cn("flex flex-col gap-1.5", className), children: [
1981
+ /* @__PURE__ */ jsx24(
1982
+ "label",
1983
+ {
1984
+ htmlFor: name,
1985
+ className: cn(
1986
+ "text-sm font-medium text-text-primary",
1987
+ required && "after:content-['*'] after:ml-0.5 after:text-error"
1988
+ ),
1989
+ children: label
1990
+ }
1991
+ ),
1992
+ children({
1993
+ id: name,
1994
+ "aria-describedby": describedBy,
1995
+ "aria-invalid": !!errorMessage
1996
+ }),
1997
+ hint && !errorMessage && /* @__PURE__ */ jsx24("p", { id: hintId, className: "text-xs text-text-secondary", children: hint }),
1998
+ errorMessage && /* @__PURE__ */ jsx24("p", { id: errorId, role: "alert", className: "text-xs text-error", children: errorMessage })
1999
+ ] });
2000
+ }
2001
+
2002
+ // modules/app/FilterBar.tsx
2003
+ import { jsx as jsx25, jsxs as jsxs22 } from "react/jsx-runtime";
2004
+ function FilterBar({
2005
+ fields,
2006
+ values,
2007
+ onChange,
2008
+ onApply,
2009
+ onReset,
2010
+ applyLabel = "Apply",
2011
+ resetLabel = "Reset",
2012
+ className
2013
+ }) {
2014
+ return /* @__PURE__ */ jsxs22("div", { className: cn("flex flex-wrap items-end gap-3 p-4 bg-surface-raised border border-border rounded-xl", className), children: [
2015
+ fields.map((f) => {
2016
+ var _a, _b, _c, _d, _e, _f, _g;
2017
+ if (f.type === "select") {
2018
+ return /* @__PURE__ */ jsx25("div", { className: "min-w-36 flex-1", children: /* @__PURE__ */ jsx25(
2019
+ Select,
2020
+ {
2021
+ id: `filter-${f.id}`,
2022
+ label: f.label,
2023
+ options: f.options,
2024
+ placeholder: (_a = f.placeholder) != null ? _a : "All",
2025
+ value: (_b = values[f.id]) != null ? _b : "",
2026
+ onChange: (e) => onChange(f.id, e.target.value)
2027
+ }
2028
+ ) }, f.id);
2029
+ }
2030
+ if (f.type === "multiselect") {
2031
+ return /* @__PURE__ */ jsx25("div", { className: "min-w-44 flex-1", children: /* @__PURE__ */ jsx25(
2032
+ MultiSelect,
2033
+ {
2034
+ id: `filter-${f.id}`,
2035
+ label: f.label,
2036
+ options: f.options,
2037
+ value: (_c = values[f.id]) != null ? _c : [],
2038
+ onChange: (v) => onChange(f.id, v),
2039
+ placeholder: (_d = f.placeholder) != null ? _d : "Any"
2040
+ }
2041
+ ) }, f.id);
2042
+ }
2043
+ if (f.type === "daterange") {
2044
+ return /* @__PURE__ */ jsx25("div", { className: "min-w-56 flex-1", children: /* @__PURE__ */ jsx25(
2045
+ DateRangePicker,
2046
+ {
2047
+ id: `filter-${f.id}`,
2048
+ label: f.label,
2049
+ value: (_e = values[f.id]) != null ? _e : { start: null, end: null },
2050
+ onChange: (v) => onChange(f.id, v)
2051
+ }
2052
+ ) }, f.id);
2053
+ }
2054
+ if (f.type === "tags") {
2055
+ return /* @__PURE__ */ jsx25("div", { className: "min-w-44 flex-1", children: /* @__PURE__ */ jsx25(
2056
+ TagInput,
2057
+ {
2058
+ id: `filter-${f.id}`,
2059
+ label: f.label,
2060
+ value: (_f = values[f.id]) != null ? _f : [],
2061
+ onChange: (v) => onChange(f.id, v),
2062
+ placeholder: (_g = f.placeholder) != null ? _g : "Add tag\u2026"
2063
+ }
2064
+ ) }, f.id);
2065
+ }
2066
+ return null;
2067
+ }),
2068
+ /* @__PURE__ */ jsxs22("div", { className: "flex items-center gap-2 shrink-0 self-end pb-0.5", children: [
2069
+ onReset && /* @__PURE__ */ jsx25(Button, { variant: "ghost", size: "sm", onClick: onReset, children: resetLabel }),
2070
+ onApply && /* @__PURE__ */ jsx25(Button, { variant: "primary", size: "sm", onClick: onApply, children: applyLabel })
2071
+ ] })
2072
+ ] });
2073
+ }
2074
+
2075
+ // modules/app/StepFlow.tsx
2076
+ import { useState as useState10 } from "react";
2077
+ import { FontAwesomeIcon as FontAwesomeIcon11 } from "@fortawesome/react-fontawesome";
2078
+ import { faArrowRight } from "@fortawesome/free-solid-svg-icons";
2079
+ import { jsx as jsx26, jsxs as jsxs23 } from "react/jsx-runtime";
2080
+ function StepFlow({
2081
+ steps,
2082
+ onComplete,
2083
+ onCancel,
2084
+ completeLabel = "Finish",
2085
+ cancelLabel = "Cancel",
2086
+ nextLabel = "Next",
2087
+ prevLabel = "Back",
2088
+ initialValues = {},
2089
+ className
2090
+ }) {
2091
+ const [current, setCurrent] = useState10(0);
2092
+ const [values, setValues] = useState10(initialValues);
2093
+ const [stepError, setStepError] = useState10();
2094
+ const [completing, setCompleting] = useState10(false);
2095
+ function onChange(key, value) {
2096
+ setValues((v) => __spreadProps(__spreadValues({}, v), { [key]: value }));
2097
+ setStepError(void 0);
2098
+ }
2099
+ function handleNext() {
2100
+ var _a;
2101
+ const step = steps[current];
2102
+ const err = (_a = step.validate) == null ? void 0 : _a.call(step, values);
2103
+ if (err) {
2104
+ setStepError(err);
2105
+ return;
2106
+ }
2107
+ setStepError(void 0);
2108
+ setCurrent((c) => Math.min(c + 1, steps.length - 1));
2109
+ }
2110
+ async function handleComplete() {
2111
+ var _a;
2112
+ const step = steps[current];
2113
+ const err = (_a = step.validate) == null ? void 0 : _a.call(step, values);
2114
+ if (err) {
2115
+ setStepError(err);
2116
+ return;
2117
+ }
2118
+ setCompleting(true);
2119
+ try {
2120
+ await onComplete(values);
2121
+ } finally {
2122
+ setCompleting(false);
2123
+ }
2124
+ }
2125
+ function handlePrev() {
2126
+ setStepError(void 0);
2127
+ setCurrent((c) => Math.max(c - 1, 0));
2128
+ }
2129
+ const stepperItems = steps.map((s, i) => ({
2130
+ label: s.label,
2131
+ description: s.description,
2132
+ state: i < current ? "complete" : i === current ? "active" : "pending"
2133
+ }));
2134
+ const isLast = current === steps.length - 1;
2135
+ return /* @__PURE__ */ jsxs23("div", { className: cn("space-y-6", className), children: [
2136
+ /* @__PURE__ */ jsx26(Stepper, { steps: stepperItems }),
2137
+ /* @__PURE__ */ jsx26("div", { className: "min-h-[12rem]", children: steps[current].content({ values, onChange, error: stepError }) }),
2138
+ stepError && /* @__PURE__ */ jsx26(AlertBanner, { variant: "error", message: stepError }),
2139
+ /* @__PURE__ */ jsxs23("div", { className: "flex items-center justify-between gap-3 pt-4 border-t border-border", children: [
2140
+ /* @__PURE__ */ jsxs23("div", { children: [
2141
+ onCancel && current === 0 && /* @__PURE__ */ jsx26(Button, { variant: "ghost", onClick: onCancel, children: cancelLabel }),
2142
+ current > 0 && /* @__PURE__ */ jsx26(Button, { variant: "outline", onClick: handlePrev, children: prevLabel })
2143
+ ] }),
2144
+ /* @__PURE__ */ jsxs23("div", { className: "flex items-center gap-2", children: [
2145
+ /* @__PURE__ */ jsxs23("span", { className: "text-xs text-text-disabled", children: [
2146
+ current + 1,
2147
+ " / ",
2148
+ steps.length
2149
+ ] }),
2150
+ isLast ? /* @__PURE__ */ jsx26(Button, { variant: "primary", onClick: handleComplete, loading: completing, children: completeLabel }) : /* @__PURE__ */ jsx26(Button, { variant: "primary", onClick: handleNext, iconRight: /* @__PURE__ */ jsx26(FontAwesomeIcon11, { icon: faArrowRight, className: "w-3.5 h-3.5", "aria-hidden": "true" }), children: nextLabel })
2151
+ ] })
2152
+ ] })
2153
+ ] });
2154
+ }
2155
+
2156
+ // modules/app/StepShell.tsx
2157
+ import { FontAwesomeIcon as FontAwesomeIcon12 } from "@fortawesome/react-fontawesome";
2158
+ import { faCheck } from "@fortawesome/free-solid-svg-icons";
2159
+ import { jsx as jsx27, jsxs as jsxs24 } from "react/jsx-runtime";
2160
+ function StepShell({
2161
+ number,
2162
+ title,
2163
+ active,
2164
+ done,
2165
+ onEdit,
2166
+ summary,
2167
+ children,
2168
+ className
2169
+ }) {
2170
+ return /* @__PURE__ */ jsxs24(
2171
+ "div",
2172
+ {
2173
+ className: cn(
2174
+ "rounded-2xl border transition-all bg-surface-raised overflow-hidden",
2175
+ active ? "border-primary shadow-sm" : "border-border",
2176
+ className
2177
+ ),
2178
+ children: [
2179
+ /* @__PURE__ */ jsxs24("div", { className: "flex items-center gap-3 px-5 py-4", children: [
2180
+ /* @__PURE__ */ jsx27(
2181
+ "span",
2182
+ {
2183
+ className: cn(
2184
+ "flex h-7 w-7 shrink-0 items-center justify-center rounded-full text-xs font-bold transition-colors",
2185
+ done ? "bg-success text-white" : active ? "bg-primary text-primary-fg" : "bg-surface-overlay text-text-disabled"
2186
+ ),
2187
+ children: done ? /* @__PURE__ */ jsx27(FontAwesomeIcon12, { icon: faCheck, className: "w-3 h-3", "aria-hidden": "true" }) : number
2188
+ }
2189
+ ),
2190
+ /* @__PURE__ */ jsx27(
2191
+ "h2",
2192
+ {
2193
+ className: cn(
2194
+ "flex-1 text-sm font-semibold",
2195
+ active ? "text-text-primary" : done ? "text-text-secondary" : "text-text-disabled"
2196
+ ),
2197
+ dangerouslySetInnerHTML: { __html: title }
2198
+ }
2199
+ ),
2200
+ done && onEdit && /* @__PURE__ */ jsx27(Button, { variant: "ghost", size: "xs", onClick: onEdit, className: "text-primary shrink-0", children: "Edit" })
2201
+ ] }),
2202
+ done && summary && /* @__PURE__ */ jsx27("div", { className: "px-5 pb-4 border-t border-border pt-3 opacity-70", children: summary }),
2203
+ active && children && /* @__PURE__ */ jsx27("div", { className: "px-5 pb-5 border-t border-border pt-4", children })
2204
+ ]
2205
+ }
2206
+ );
2207
+ }
2208
+
2209
+ // modules/app/FileUploadSection/index.tsx
2210
+ import { useId as useId3, useMemo as useMemo4, useRef as useRef5, useState as useState11, useCallback as useCallback5 } from "react";
2211
+
2212
+ // modules/app/FileUploadSection/parts/DropZone.tsx
2213
+ import { useRef as useRef4 } from "react";
2214
+ import { FontAwesomeIcon as FontAwesomeIcon13 } from "@fortawesome/react-fontawesome";
2215
+ import { faFolderOpen } from "@fortawesome/free-solid-svg-icons";
2216
+ import { jsx as jsx28, jsxs as jsxs25 } from "react/jsx-runtime";
2217
+ function DropZone({
2218
+ id,
2219
+ accept,
2220
+ multiple,
2221
+ disabled,
2222
+ dragging,
2223
+ hint,
2224
+ dropHint,
2225
+ browseLabel,
2226
+ pasteHint,
2227
+ showPasteHint,
2228
+ onFiles,
2229
+ onDragOver,
2230
+ onDragLeave,
2231
+ onDrop
2232
+ }) {
2233
+ const inputRef = useRef4(null);
2234
+ return /* @__PURE__ */ jsxs25(
2235
+ "div",
2236
+ {
2237
+ className: cn(
2238
+ "relative rounded-lg border-2 border-dashed border-border bg-surface-base transition-colors",
2239
+ "flex flex-col items-center justify-center gap-2 px-6 py-8 text-center",
2240
+ dragging && "border-primary bg-primary-subtle",
2241
+ disabled && "opacity-50 cursor-not-allowed"
2242
+ ),
2243
+ onDragOver: (e) => {
2244
+ e.preventDefault();
2245
+ if (!disabled) onDragOver();
2246
+ },
2247
+ onDragLeave: () => onDragLeave(),
2248
+ onDrop: (e) => {
2249
+ e.preventDefault();
2250
+ if (!disabled) onDrop(e.dataTransfer);
2251
+ },
2252
+ children: [
2253
+ /* @__PURE__ */ jsx28(FontAwesomeIcon13, { icon: faFolderOpen, className: "w-8 h-8 text-text-disabled", "aria-hidden": "true" }),
2254
+ /* @__PURE__ */ jsxs25("p", { className: "text-sm text-text-secondary", children: [
2255
+ dropHint,
2256
+ " ",
2257
+ /* @__PURE__ */ jsx28(
2258
+ "button",
2259
+ {
2260
+ type: "button",
2261
+ disabled,
2262
+ onClick: () => {
2263
+ var _a;
2264
+ return (_a = inputRef.current) == null ? void 0 : _a.click();
2265
+ },
2266
+ className: "text-primary underline underline-offset-2 hover:opacity-70 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus rounded disabled:cursor-not-allowed",
2267
+ children: browseLabel
2268
+ }
2269
+ )
2270
+ ] }),
2271
+ showPasteHint && pasteHint && /* @__PURE__ */ jsx28("p", { className: "text-xs text-text-disabled", children: pasteHint }),
2272
+ hint && /* @__PURE__ */ jsx28("p", { className: "text-xs text-text-disabled", children: hint }),
2273
+ /* @__PURE__ */ jsx28(
2274
+ "input",
2275
+ {
2276
+ ref: inputRef,
2277
+ id,
2278
+ type: "file",
2279
+ multiple,
2280
+ accept,
2281
+ disabled,
2282
+ className: "sr-only",
2283
+ onChange: (e) => onFiles(e.target.files)
2284
+ }
2285
+ )
2286
+ ]
2287
+ }
2288
+ );
2289
+ }
2290
+
2291
+ // modules/app/FileUploadSection/parts/FileRow.tsx
2292
+ import { FontAwesomeIcon as FontAwesomeIcon14 } from "@fortawesome/react-fontawesome";
2293
+ import { faXmark as faXmark2, faImage } from "@fortawesome/free-solid-svg-icons";
2294
+ import { jsx as jsx29, jsxs as jsxs26 } from "react/jsx-runtime";
2295
+ function formatBytes(bytes) {
2296
+ if (bytes < 1024) return `${bytes} B`;
2297
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
2298
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
2299
+ }
2300
+ function FileRow({ item, removeLabel, onRemove }) {
2301
+ const isImage = item.file.type.startsWith("image/");
2302
+ return /* @__PURE__ */ jsxs26(
2303
+ "li",
2304
+ {
2305
+ className: cn(
2306
+ "flex items-center gap-3 rounded-md border px-3 py-2 text-sm",
2307
+ item.error ? "border-error bg-error-subtle text-error-fg" : "border-border bg-surface-raised text-text-primary"
2308
+ ),
2309
+ children: [
2310
+ isImage && /* @__PURE__ */ jsx29(FontAwesomeIcon14, { icon: faImage, className: "w-4 h-4 text-text-secondary shrink-0", "aria-hidden": "true" }),
2311
+ /* @__PURE__ */ jsxs26("span", { className: "flex-1 truncate min-w-0", children: [
2312
+ /* @__PURE__ */ jsx29("span", { className: "font-medium", children: item.file.name }),
2313
+ /* @__PURE__ */ jsx29("span", { className: "ml-2 text-xs text-text-secondary", children: formatBytes(item.file.size) })
2314
+ ] }),
2315
+ item.error && /* @__PURE__ */ jsx29("span", { className: "text-xs text-error shrink-0", children: item.error }),
2316
+ /* @__PURE__ */ jsx29(
2317
+ "button",
2318
+ {
2319
+ type: "button",
2320
+ "aria-label": `${removeLabel} ${item.file.name}`,
2321
+ onClick: onRemove,
2322
+ className: "shrink-0 hover:opacity-70 transition-opacity focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus rounded",
2323
+ children: /* @__PURE__ */ jsx29(FontAwesomeIcon14, { icon: faXmark2, className: "w-3 h-3" })
2324
+ }
2325
+ )
2326
+ ]
2327
+ }
2328
+ );
2329
+ }
2330
+
2331
+ // modules/app/FileUploadSection/hooks/usePaste.ts
2332
+ import { useEffect as useEffect8 } from "react";
2333
+ function usePaste(rootRef, enabled, onFiles) {
2334
+ useEffect8(() => {
2335
+ if (!enabled) return;
2336
+ const handler = (ev) => {
2337
+ var _a;
2338
+ const root = rootRef.current;
2339
+ if (!root) return;
2340
+ const active = document.activeElement;
2341
+ const inside = active && (root === active || root.contains(active));
2342
+ if (!inside) return;
2343
+ const items = (_a = ev.clipboardData) == null ? void 0 : _a.items;
2344
+ if (!items || items.length === 0) return;
2345
+ const out = [];
2346
+ for (let i = 0; i < items.length; i++) {
2347
+ const it = items[i];
2348
+ if (it.kind === "file") {
2349
+ const f = it.getAsFile();
2350
+ if (f) {
2351
+ const named = f.name && f.name !== "image.png" ? f : new File(
2352
+ [f],
2353
+ `pasted-${Date.now()}.${(f.type.split("/")[1] || "bin").replace("+xml", "")}`,
2354
+ { type: f.type, lastModified: Date.now() }
2355
+ );
2356
+ out.push(named);
2357
+ }
2358
+ }
2359
+ }
2360
+ if (out.length > 0) {
2361
+ ev.preventDefault();
2362
+ onFiles(out);
2363
+ }
2364
+ };
2365
+ document.addEventListener("paste", handler);
2366
+ return () => document.removeEventListener("paste", handler);
2367
+ }, [enabled, rootRef, onFiles]);
2368
+ }
2369
+
2370
+ // modules/app/FileUploadSection/types.ts
2371
+ var DEFAULT_FUS_MESSAGES = {
2372
+ dropHint: "Drag & drop files here, or",
2373
+ browseLabel: "browse",
2374
+ pasteHint: "or paste from clipboard",
2375
+ invalidSize: (limit) => `File exceeds ${limit} limit`,
2376
+ invalidType: "File type not allowed",
2377
+ tooMany: (max) => `Too many files \u2014 limit is ${max}`,
2378
+ emptyState: "No files selected yet.",
2379
+ remove: "Remove"
2380
+ };
2381
+
2382
+ // modules/app/FileUploadSection/index.tsx
2383
+ import { jsx as jsx30, jsxs as jsxs27 } from "react/jsx-runtime";
2384
+ function formatBytes2(bytes) {
2385
+ if (bytes < 1024) return `${bytes} B`;
2386
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
2387
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
2388
+ }
2389
+ function matchesAccept(file, accept) {
2390
+ if (!accept) return true;
2391
+ const patterns = accept.split(",").map((p) => p.trim().toLowerCase()).filter(Boolean);
2392
+ if (patterns.length === 0) return true;
2393
+ const name = file.name.toLowerCase();
2394
+ const mime = (file.type || "").toLowerCase();
2395
+ return patterns.some((p) => {
2396
+ if (p.startsWith(".")) return name.endsWith(p);
2397
+ if (p.endsWith("/*")) return mime.startsWith(p.slice(0, -1));
2398
+ return mime === p;
2399
+ });
2400
+ }
2401
+ function makeId(file) {
2402
+ return `${file.name}-${file.size}-${file.lastModified}-${Math.random().toString(36).slice(2, 8)}`;
2403
+ }
2404
+ function FileUploadSection({
2405
+ files,
2406
+ onFilesChange,
2407
+ accept,
2408
+ multiple = true,
2409
+ maxSizeBytes,
2410
+ maxFiles,
2411
+ disabled,
2412
+ enablePaste = false,
2413
+ title,
2414
+ hint,
2415
+ className,
2416
+ messages
2417
+ }) {
2418
+ const autoId = useId3();
2419
+ const inputId = `fus-${autoId}`;
2420
+ const rootRef = useRef5(null);
2421
+ const [internalFiles, setInternalFiles] = useState11([]);
2422
+ const isControlled = files !== void 0;
2423
+ const items = isControlled ? files : internalFiles;
2424
+ const [dragging, setDragging] = useState11(false);
2425
+ const [globalError, setGlobalError] = useState11("");
2426
+ const msg = useMemo4(
2427
+ () => __spreadValues(__spreadValues({}, DEFAULT_FUS_MESSAGES), messages),
2428
+ [messages]
2429
+ );
2430
+ const setItems = useCallback5(
2431
+ (next) => {
2432
+ if (!isControlled) setInternalFiles(next);
2433
+ onFilesChange == null ? void 0 : onFilesChange(next);
2434
+ },
2435
+ [isControlled, onFilesChange]
2436
+ );
2437
+ const validate = useCallback5(
2438
+ (file) => {
2439
+ if (maxSizeBytes && file.size > maxSizeBytes) {
2440
+ return msg.invalidSize(formatBytes2(maxSizeBytes));
2441
+ }
2442
+ if (accept && !matchesAccept(file, accept)) {
2443
+ return msg.invalidType;
2444
+ }
2445
+ return void 0;
2446
+ },
2447
+ [maxSizeBytes, accept, msg]
2448
+ );
2449
+ const addFiles = useCallback5(
2450
+ (incoming) => {
2451
+ if (!incoming) return;
2452
+ const arr = Array.from(incoming);
2453
+ if (arr.length === 0) return;
2454
+ const newItems = arr.map((file) => ({
2455
+ id: makeId(file),
2456
+ file,
2457
+ status: "idle",
2458
+ progress: 0,
2459
+ error: validate(file)
2460
+ }));
2461
+ const combined = multiple ? [...items, ...newItems] : newItems;
2462
+ if (maxFiles && combined.length > maxFiles) {
2463
+ setGlobalError(msg.tooMany(maxFiles));
2464
+ setItems(combined.slice(0, maxFiles));
2465
+ return;
2466
+ }
2467
+ setGlobalError("");
2468
+ setItems(combined);
2469
+ },
2470
+ [items, multiple, maxFiles, validate, msg, setItems]
2471
+ );
2472
+ function removeItem(id) {
2473
+ setItems(items.filter((it) => it.id !== id));
2474
+ setGlobalError("");
2475
+ }
2476
+ usePaste(rootRef, enablePaste && !disabled, addFiles);
2477
+ return /* @__PURE__ */ jsxs27(
2478
+ "section",
2479
+ {
2480
+ ref: rootRef,
2481
+ className: cn("space-y-3", className),
2482
+ tabIndex: enablePaste ? -1 : void 0,
2483
+ "aria-label": title || "File upload",
2484
+ children: [
2485
+ title && /* @__PURE__ */ jsx30("h3", { className: "text-sm font-medium text-text-primary", children: title }),
2486
+ /* @__PURE__ */ jsx30(
2487
+ DropZone,
2488
+ {
2489
+ id: inputId,
2490
+ accept,
2491
+ multiple,
2492
+ disabled,
2493
+ dragging,
2494
+ hint,
2495
+ dropHint: msg.dropHint,
2496
+ browseLabel: msg.browseLabel,
2497
+ pasteHint: msg.pasteHint,
2498
+ showPasteHint: enablePaste,
2499
+ onFiles: addFiles,
2500
+ onDragOver: () => setDragging(true),
2501
+ onDragLeave: () => setDragging(false),
2502
+ onDrop: (dt) => {
2503
+ setDragging(false);
2504
+ addFiles(dt.files);
2505
+ }
2506
+ }
2507
+ ),
2508
+ items.length === 0 ? /* @__PURE__ */ jsx30("p", { className: "text-xs text-text-disabled", children: msg.emptyState }) : /* @__PURE__ */ jsx30("ul", { className: "space-y-1.5", "aria-label": "Selected files", children: items.map((item) => /* @__PURE__ */ jsx30(
2509
+ FileRow,
2510
+ {
2511
+ item,
2512
+ removeLabel: msg.remove,
2513
+ onRemove: () => removeItem(item.id)
2514
+ },
2515
+ item.id
2516
+ )) }),
2517
+ globalError && /* @__PURE__ */ jsx30("p", { role: "alert", className: "text-sm text-error", children: globalError })
2518
+ ]
2519
+ }
2520
+ );
2521
+ }
2522
+
2523
+ // modules/app/DetailHeader.tsx
2524
+ import { useState as useState12 } from "react";
2525
+ import { jsx as jsx31, jsxs as jsxs28 } from "react/jsx-runtime";
2526
+ function DetailHeader({
2527
+ title,
2528
+ subtitle,
2529
+ status,
2530
+ statusVariant = "neutral",
2531
+ badge,
2532
+ children,
2533
+ tabs,
2534
+ defaultTab,
2535
+ onTabChange,
2536
+ className
2537
+ }) {
2538
+ var _a, _b;
2539
+ const [activeTab, setActiveTab] = useState12((_b = defaultTab != null ? defaultTab : (_a = tabs == null ? void 0 : tabs[0]) == null ? void 0 : _a.value) != null ? _b : "");
2540
+ function handleTab(v) {
2541
+ setActiveTab(v);
2542
+ onTabChange == null ? void 0 : onTabChange(v);
2543
+ }
2544
+ return /* @__PURE__ */ jsx31("div", { className: cn("border-b border-border bg-surface-raised", className), children: /* @__PURE__ */ jsxs28("div", { className: "px-6 pt-6 pb-0", children: [
2545
+ /* @__PURE__ */ jsxs28("div", { className: "flex items-start justify-between gap-4 pb-4", children: [
2546
+ /* @__PURE__ */ jsxs28("div", { className: "min-w-0", children: [
2547
+ /* @__PURE__ */ jsxs28("div", { className: "flex items-center gap-2 flex-wrap", children: [
2548
+ /* @__PURE__ */ jsx31("h1", { className: "text-2xl font-bold text-text-primary leading-tight", children: title }),
2549
+ status && /* @__PURE__ */ jsx31(Badge, { variant: statusVariant, children: status }),
2550
+ badge
2551
+ ] }),
2552
+ subtitle && /* @__PURE__ */ jsx31("p", { className: "text-sm text-text-secondary mt-0.5", children: subtitle })
2553
+ ] }),
2554
+ children && /* @__PURE__ */ jsx31("div", { className: "flex items-center gap-2 shrink-0 flex-wrap justify-end", children })
2555
+ ] }),
2556
+ tabs && tabs.length > 0 && /* @__PURE__ */ jsx31("div", { role: "tablist", "aria-label": "Detail navigation", className: "flex -mb-px", children: tabs.map((tab) => {
2557
+ const isActive = tab.value === activeTab;
2558
+ return /* @__PURE__ */ jsx31(
2559
+ "button",
2560
+ {
2561
+ role: "tab",
2562
+ "aria-selected": isActive,
2563
+ "aria-disabled": tab.disabled,
2564
+ onClick: () => !tab.disabled && handleTab(tab.value),
2565
+ className: cn(
2566
+ "px-4 py-2.5 text-sm font-medium border-b-2 transition-colors",
2567
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus",
2568
+ isActive ? "border-primary text-primary" : "border-transparent text-text-secondary hover:text-text-primary hover:border-border-strong",
2569
+ tab.disabled && "opacity-40 cursor-not-allowed pointer-events-none"
2570
+ ),
2571
+ children: tab.label
2572
+ },
2573
+ tab.value
2574
+ );
2575
+ }) })
2576
+ ] }) });
2577
+ }
2578
+
2579
+ // modules/app/InlineAlert.tsx
2580
+ import { FontAwesomeIcon as FontAwesomeIcon15 } from "@fortawesome/react-fontawesome";
2581
+ import { faCheck as faCheck2, faXmark as faXmark3, faCircleInfo, faTriangleExclamation } from "@fortawesome/free-solid-svg-icons";
2582
+ import { jsx as jsx32, jsxs as jsxs29 } from "react/jsx-runtime";
2583
+ var variantClasses = {
2584
+ success: "bg-success-subtle border-success text-success-fg",
2585
+ error: "bg-error-subtle border-error text-error",
2586
+ warning: "bg-warning-subtle border-warning text-text-primary",
2587
+ info: "bg-info-subtle border-info text-text-primary"
2588
+ };
2589
+ var variantIcons = {
2590
+ success: faCheck2,
2591
+ error: faXmark3,
2592
+ warning: faTriangleExclamation,
2593
+ info: faCircleInfo
2594
+ };
2595
+ function InlineAlert({ variant = "success", message, className }) {
2596
+ return /* @__PURE__ */ jsxs29(
2597
+ "div",
2598
+ {
2599
+ className: cn(
2600
+ "rounded-lg border px-4 py-2.5 text-sm font-medium flex items-center gap-1.5",
2601
+ variantClasses[variant],
2602
+ className
2603
+ ),
2604
+ children: [
2605
+ /* @__PURE__ */ jsx32(FontAwesomeIcon15, { icon: variantIcons[variant], className: "w-3.5 h-3.5 shrink-0", "aria-hidden": "true" }),
2606
+ /* @__PURE__ */ jsx32("span", { children: message })
2607
+ ]
2608
+ }
2609
+ );
2610
+ }
2611
+
2612
+ // modules/app/LoadingState.tsx
2613
+ import { jsx as jsx33, jsxs as jsxs30 } from "react/jsx-runtime";
2614
+ function LoadingState({
2615
+ variant = "spinner",
2616
+ rows = 5,
2617
+ cols = 4,
2618
+ cards = 3,
2619
+ className
2620
+ }) {
2621
+ if (variant === "spinner") {
2622
+ return /* @__PURE__ */ jsx33("div", { className: cn("flex items-center justify-center py-16", className), children: /* @__PURE__ */ jsx33(Spinner, { size: "lg" }) });
2623
+ }
2624
+ if (variant === "table") {
2625
+ return /* @__PURE__ */ jsx33("div", { className: cn("w-full overflow-x-auto rounded-lg border border-border", className), "aria-busy": "true", "aria-label": "Loading table", children: /* @__PURE__ */ jsxs30("table", { className: "w-full text-sm", children: [
2626
+ /* @__PURE__ */ jsx33("thead", { className: "bg-surface-sunken border-b border-border", children: /* @__PURE__ */ jsx33("tr", { children: Array.from({ length: cols }, (_, i) => /* @__PURE__ */ jsx33("th", { className: "px-4 py-3", children: /* @__PURE__ */ jsx33("div", { className: "h-3 rounded bg-surface-sunken animate-pulse w-16" }) }, i)) }) }),
2627
+ /* @__PURE__ */ jsx33("tbody", { className: "divide-y divide-border bg-surface-base", children: Array.from({ length: rows }, (_, i) => /* @__PURE__ */ jsx33(SkeletonTableRow, { cols }, i)) })
2628
+ ] }) });
2629
+ }
2630
+ if (variant === "cards") {
2631
+ return /* @__PURE__ */ jsx33("div", { className: cn("grid gap-4", cards >= 3 ? "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3" : "grid-cols-1 sm:grid-cols-2", className), children: Array.from({ length: cards }, (_, i) => /* @__PURE__ */ jsx33(SkeletonCard, {}, i)) });
2632
+ }
2633
+ if (variant === "list") {
2634
+ return /* @__PURE__ */ jsx33("ul", { className: cn("divide-y divide-border", className), "aria-busy": "true", "aria-label": "Loading list", children: Array.from({ length: rows }, (_, i) => /* @__PURE__ */ jsxs30("li", { className: "flex items-center gap-3 py-3 px-4", children: [
2635
+ /* @__PURE__ */ jsx33(SkeletonAvatar, {}),
2636
+ /* @__PURE__ */ jsxs30("div", { className: "flex-1 space-y-2", children: [
2637
+ /* @__PURE__ */ jsx33(SkeletonLine, { width: "w-1/3" }),
2638
+ /* @__PURE__ */ jsx33(SkeletonLine, { width: "w-2/3" })
2639
+ ] }),
2640
+ /* @__PURE__ */ jsx33("div", { className: "h-4 w-12 rounded bg-surface-sunken animate-pulse" })
2641
+ ] }, i)) });
2642
+ }
2643
+ if (variant === "detail") {
2644
+ return /* @__PURE__ */ jsxs30("div", { className: cn("space-y-6", className), "aria-busy": "true", "aria-label": "Loading detail", children: [
2645
+ /* @__PURE__ */ jsxs30("div", { className: "pb-4 border-b border-border space-y-3", children: [
2646
+ /* @__PURE__ */ jsx33(SkeletonLine, { width: "w-1/4" }),
2647
+ /* @__PURE__ */ jsx33(SkeletonLine, { width: "w-1/2" }),
2648
+ /* @__PURE__ */ jsx33("div", { className: "flex gap-2", children: Array.from({ length: 3 }, (_, i) => /* @__PURE__ */ jsx33("div", { className: "h-6 w-16 rounded-full bg-surface-sunken animate-pulse" }, i)) })
2649
+ ] }),
2650
+ /* @__PURE__ */ jsx33("div", { className: "grid sm:grid-cols-2 gap-4", children: Array.from({ length: 4 }, (_, i) => /* @__PURE__ */ jsxs30("div", { className: "space-y-2", children: [
2651
+ /* @__PURE__ */ jsx33(SkeletonLine, { width: "w-1/4" }),
2652
+ /* @__PURE__ */ jsx33(SkeletonLine, { width: "w-full" })
2653
+ ] }, i)) }),
2654
+ /* @__PURE__ */ jsx33(SkeletonText, { lines: 4 })
2655
+ ] });
2656
+ }
2657
+ if (variant === "form") {
2658
+ return /* @__PURE__ */ jsxs30("div", { className: cn("space-y-5", className), "aria-busy": "true", "aria-label": "Loading form", children: [
2659
+ Array.from({ length: rows }, (_, i) => /* @__PURE__ */ jsxs30("div", { className: "space-y-2", children: [
2660
+ /* @__PURE__ */ jsx33(SkeletonLine, { width: "w-1/4" }),
2661
+ /* @__PURE__ */ jsx33("div", { className: "h-9 rounded-md bg-surface-sunken animate-pulse w-full" })
2662
+ ] }, i)),
2663
+ /* @__PURE__ */ jsxs30("div", { className: "flex justify-end gap-2 pt-2", children: [
2664
+ /* @__PURE__ */ jsx33("div", { className: "h-9 w-20 rounded-md bg-surface-sunken animate-pulse" }),
2665
+ /* @__PURE__ */ jsx33("div", { className: "h-9 w-24 rounded-md bg-surface-sunken animate-pulse" })
2666
+ ] })
2667
+ ] });
2668
+ }
2669
+ return null;
2670
+ }
2671
+
2672
+ // modules/app/EmptyErrorState.tsx
2673
+ import { FontAwesomeIcon as FontAwesomeIcon16 } from "@fortawesome/react-fontawesome";
2674
+ import { faTriangleExclamation as faTriangleExclamation2, faMagnifyingGlass as faMagnifyingGlass3, faLock as faLock2, faArrowLeft, faRotateRight } from "@fortawesome/free-solid-svg-icons";
2675
+ import { jsx as jsx34, jsxs as jsxs31 } from "react/jsx-runtime";
2676
+ function ErrorState({
2677
+ title = "Something went wrong",
2678
+ message,
2679
+ onRetry,
2680
+ retryLabel = "Try again",
2681
+ className
2682
+ }) {
2683
+ return /* @__PURE__ */ jsxs31("div", { className: cn("space-y-4", className), children: [
2684
+ /* @__PURE__ */ jsx34(
2685
+ AlertBanner,
2686
+ {
2687
+ variant: "error",
2688
+ title,
2689
+ message,
2690
+ action: onRetry ? { label: retryLabel, onClick: onRetry } : void 0
2691
+ }
2692
+ ),
2693
+ onRetry && /* @__PURE__ */ jsx34("div", { className: "flex justify-center", children: /* @__PURE__ */ jsx34(
2694
+ EmptyState,
2695
+ {
2696
+ icon: /* @__PURE__ */ jsx34(FontAwesomeIcon16, { icon: faTriangleExclamation2, className: "w-5 h-5", "aria-hidden": "true" }),
2697
+ title: "Unable to load data",
2698
+ description: "There was a problem loading this content.",
2699
+ action: /* @__PURE__ */ jsx34(Button, { variant: "outline", size: "sm", onClick: onRetry, iconLeft: /* @__PURE__ */ jsx34(FontAwesomeIcon16, { icon: faRotateRight, className: "w-3.5 h-3.5", "aria-hidden": "true" }), children: retryLabel })
2700
+ }
2701
+ ) })
2702
+ ] });
2703
+ }
2704
+ function NotFoundState({
2705
+ title = "Page not found",
2706
+ description = "The page you're looking for doesn't exist or has been moved.",
2707
+ onGoBack,
2708
+ goBackLabel = "Go back",
2709
+ className
2710
+ }) {
2711
+ return /* @__PURE__ */ jsx34(
2712
+ EmptyState,
2713
+ {
2714
+ icon: /* @__PURE__ */ jsx34(FontAwesomeIcon16, { icon: faMagnifyingGlass3, className: "w-5 h-5", "aria-hidden": "true" }),
2715
+ title,
2716
+ description,
2717
+ action: onGoBack ? /* @__PURE__ */ jsx34(Button, { variant: "outline", size: "sm", onClick: onGoBack, iconLeft: /* @__PURE__ */ jsx34(FontAwesomeIcon16, { icon: faArrowLeft, className: "w-3.5 h-3.5", "aria-hidden": "true" }), children: goBackLabel }) : void 0,
2718
+ className
2719
+ }
2720
+ );
2721
+ }
2722
+ function NoAccessState({
2723
+ title = "Access denied",
2724
+ description = "You don't have permission to view this content.",
2725
+ onRequestAccess,
2726
+ className
2727
+ }) {
2728
+ return /* @__PURE__ */ jsx34(
2729
+ EmptyState,
2730
+ {
2731
+ icon: /* @__PURE__ */ jsx34(FontAwesomeIcon16, { icon: faLock2, className: "w-5 h-5", "aria-hidden": "true" }),
2732
+ title,
2733
+ description,
2734
+ action: onRequestAccess ? /* @__PURE__ */ jsx34(Button, { variant: "primary", size: "sm", onClick: onRequestAccess, children: "Request access" }) : void 0,
2735
+ className
2736
+ }
2737
+ );
2738
+ }
2739
+
2740
+ // modules/app/NotFoundPage.tsx
2741
+ import Link from "next/link";
2742
+ import { FontAwesomeIcon as FontAwesomeIcon17 } from "@fortawesome/react-fontawesome";
2743
+ import { faMagnifyingGlass as faMagnifyingGlass4, faArrowLeft as faArrowLeft2 } from "@fortawesome/free-solid-svg-icons";
2744
+ import { jsx as jsx35, jsxs as jsxs32 } from "react/jsx-runtime";
2745
+ function NotFoundPage({
2746
+ title = "Sayfa Bulunamad\u0131",
2747
+ description = "Arad\u0131\u011F\u0131n\u0131z sayfa kald\u0131r\u0131lm\u0131\u015F, ta\u015F\u0131nm\u0131\u015F ya da hi\xE7 var olmam\u0131\u015F olabilir.",
2748
+ homeHref = "/",
2749
+ homeLabel = "Ana Sayfa",
2750
+ backLabel = "Geri D\xF6n",
2751
+ icon = /* @__PURE__ */ jsx35(FontAwesomeIcon17, { icon: faMagnifyingGlass4, className: "w-8 h-8 text-primary-fg", "aria-hidden": "true" }),
2752
+ className
2753
+ }) {
2754
+ return /* @__PURE__ */ jsxs32("div", { className: cn("min-h-screen flex flex-col items-center justify-center px-4 bg-surface-base", className), children: [
2755
+ /* @__PURE__ */ jsx35(
2756
+ "div",
2757
+ {
2758
+ className: "select-none text-[120px] sm:text-[180px] font-black leading-none tabular-nums",
2759
+ style: {
2760
+ background: "linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%)",
2761
+ WebkitBackgroundClip: "text",
2762
+ WebkitTextFillColor: "transparent",
2763
+ backgroundClip: "text",
2764
+ opacity: 0.15
2765
+ },
2766
+ children: "404"
2767
+ }
2768
+ ),
2769
+ /* @__PURE__ */ jsx35(
2770
+ "div",
2771
+ {
2772
+ className: "flex h-20 w-20 -mt-8 mb-6 items-center justify-center rounded-2xl text-4xl shadow-lg",
2773
+ style: {
2774
+ background: "linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%)",
2775
+ boxShadow: "0 8px 32px color-mix(in srgb, var(--primary) 30%, transparent)"
2776
+ },
2777
+ children: icon
2778
+ }
2779
+ ),
2780
+ /* @__PURE__ */ jsx35("h1", { className: "text-2xl sm:text-3xl font-bold text-text-primary text-center", children: title }),
2781
+ /* @__PURE__ */ jsx35("p", { className: "mt-3 max-w-md text-center text-text-secondary text-sm sm:text-base leading-relaxed", children: description }),
2782
+ /* @__PURE__ */ jsxs32("div", { className: "mt-8 flex flex-wrap items-center justify-center gap-3", children: [
2783
+ /* @__PURE__ */ jsxs32(
2784
+ Link,
2785
+ {
2786
+ href: homeHref,
2787
+ className: "inline-flex items-center gap-2 px-6 py-2.5 rounded-xl text-sm font-semibold text-primary-fg transition-transform hover:scale-[1.02] active:scale-[0.98] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus",
2788
+ style: {
2789
+ background: "linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%)",
2790
+ boxShadow: "0 4px 16px color-mix(in srgb, var(--primary) 30%, transparent)"
2791
+ },
2792
+ children: [
2793
+ /* @__PURE__ */ jsx35(FontAwesomeIcon17, { icon: faArrowLeft2, className: "w-3.5 h-3.5", "aria-hidden": "true" }),
2794
+ homeLabel
2795
+ ]
2796
+ }
2797
+ ),
2798
+ /* @__PURE__ */ jsx35(
2799
+ "button",
2800
+ {
2801
+ onClick: () => history.back(),
2802
+ className: "inline-flex items-center gap-2 px-6 py-2.5 rounded-xl text-sm font-semibold text-text-primary border border-border transition-colors hover:bg-surface-overlay focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus",
2803
+ children: backLabel
2804
+ }
2805
+ )
2806
+ ] }),
2807
+ /* @__PURE__ */ jsx35("div", { className: "mt-16 flex items-center gap-2 opacity-20", "aria-hidden": true, children: [...Array(5)].map((_, i) => /* @__PURE__ */ jsx35(
2808
+ "span",
2809
+ {
2810
+ className: "rounded-full bg-primary",
2811
+ style: {
2812
+ width: i === 2 ? 10 : i === 1 || i === 3 ? 7 : 5,
2813
+ height: i === 2 ? 10 : i === 1 || i === 3 ? 7 : 5
2814
+ }
2815
+ },
2816
+ i
2817
+ )) })
2818
+ ] });
2819
+ }
2820
+
2821
+ // modules/app/SplashScreen.tsx
2822
+ import { jsx as jsx36, jsxs as jsxs33 } from "react/jsx-runtime";
2823
+ function SplashScreen({
2824
+ visible = true,
2825
+ logo,
2826
+ message,
2827
+ progress,
2828
+ className
2829
+ }) {
2830
+ return /* @__PURE__ */ jsxs33(
2831
+ "div",
2832
+ {
2833
+ role: "status",
2834
+ "aria-live": "polite",
2835
+ "aria-label": message != null ? message : "Loading",
2836
+ "aria-busy": visible,
2837
+ className: cn(
2838
+ "fixed inset-0 z-50 flex flex-col items-center justify-center gap-6",
2839
+ "bg-surface-base transition-opacity duration-500",
2840
+ visible ? "opacity-100 pointer-events-auto" : "opacity-0 pointer-events-none",
2841
+ className
2842
+ ),
2843
+ children: [
2844
+ logo ? /* @__PURE__ */ jsx36("div", { className: "flex items-center justify-center", children: logo }) : /* @__PURE__ */ jsx36(Spinner, { size: "xl" }),
2845
+ logo && /* @__PURE__ */ jsx36(Spinner, { size: "lg" }),
2846
+ progress !== void 0 && /* @__PURE__ */ jsx36("div", { className: "w-48 h-1 rounded-full bg-surface-sunken overflow-hidden", children: /* @__PURE__ */ jsx36(
2847
+ "div",
2848
+ {
2849
+ className: "h-full rounded-full bg-primary transition-all duration-300 ease-out",
2850
+ style: { width: `${Math.min(100, Math.max(0, progress))}%` },
2851
+ "aria-valuenow": progress,
2852
+ "aria-valuemin": 0,
2853
+ "aria-valuemax": 100,
2854
+ role: "progressbar"
2855
+ }
2856
+ ) }),
2857
+ message && /* @__PURE__ */ jsx36("p", { className: "text-sm text-text-secondary animate-pulse", children: message })
2858
+ ]
2859
+ }
2860
+ );
2861
+ }
2862
+
2863
+ // modules/app/NotificationSystem.tsx
2864
+ import { Fragment as Fragment9, jsx as jsx37, jsxs as jsxs34 } from "react/jsx-runtime";
2865
+ var notify = {
2866
+ success: (message, opts) => toast.success(message, opts),
2867
+ error: (message, opts) => toast.error(message, opts),
2868
+ warning: (message, opts) => toast.warning(message, opts),
2869
+ info: (message, opts) => toast.info(message, opts),
2870
+ loading: (message, opts) => toast.loading(message, opts),
2871
+ dismiss: (id) => toast.dismiss(id)
2872
+ };
2873
+ function NotificationProvider({
2874
+ children,
2875
+ position = "top-right"
2876
+ }) {
2877
+ return /* @__PURE__ */ jsxs34(Fragment9, { children: [
2878
+ children,
2879
+ /* @__PURE__ */ jsx37(ToastProvider, { position })
2880
+ ] });
2881
+ }
2882
+
2883
+ // modules/app/AccessibilityKit.tsx
2884
+ import { useCallback as useCallback6, useEffect as useEffect9, useRef as useRef6, useState as useState13 } from "react";
2885
+ import { createPortal } from "react-dom";
2886
+ import { Fragment as Fragment10, jsx as jsx38, jsxs as jsxs35 } from "react/jsx-runtime";
2887
+ function FocusTrap({ active = true, onEscape, className, children }) {
2888
+ const ref = useRef6(null);
2889
+ useFocusTrap(ref, { active, onEscape });
2890
+ return /* @__PURE__ */ jsx38("div", { ref, tabIndex: -1, className, children });
2891
+ }
2892
+ var listeners2 = /* @__PURE__ */ new Set();
2893
+ var queue = { polite: "", assertive: "" };
2894
+ function setQueue(next) {
2895
+ queue = next;
2896
+ for (const fn of listeners2) fn(next);
2897
+ }
2898
+ function useAnnounce() {
2899
+ return useCallback6((message, politeness = "polite") => {
2900
+ setQueue(__spreadProps(__spreadValues({}, queue), { [politeness]: "" }));
2901
+ window.setTimeout(() => {
2902
+ setQueue(__spreadProps(__spreadValues({}, queue), { [politeness]: message }));
2903
+ }, 16);
2904
+ }, []);
2905
+ }
2906
+ function AnnouncerOutlet() {
2907
+ const [{ polite, assertive }, setLocal] = useState13(queue);
2908
+ const [mounted, setMounted] = useState13(false);
2909
+ useEffect9(() => {
2910
+ setMounted(true);
2911
+ listeners2.add(setLocal);
2912
+ return () => {
2913
+ listeners2.delete(setLocal);
2914
+ };
2915
+ }, []);
2916
+ if (!mounted) return null;
2917
+ return createPortal(
2918
+ /* @__PURE__ */ jsxs35(Fragment10, { children: [
2919
+ /* @__PURE__ */ jsx38("div", { role: "status", "aria-live": "polite", "aria-atomic": "true", className: "sr-only", children: polite }),
2920
+ /* @__PURE__ */ jsx38("div", { role: "alert", "aria-live": "assertive", "aria-atomic": "true", className: "sr-only", children: assertive })
2921
+ ] }),
2922
+ document.body
2923
+ );
2924
+ }
2925
+
2926
+ // modules/app/MaintenancePage.tsx
2927
+ import { useEffect as useEffect10, useState as useState14 } from "react";
2928
+ import { FontAwesomeIcon as FontAwesomeIcon18 } from "@fortawesome/react-fontawesome";
2929
+ import { faScrewdriverWrench, faClock as faClock2, faArrowUpRightFromSquare } from "@fortawesome/free-solid-svg-icons";
2930
+ import { jsx as jsx39, jsxs as jsxs36 } from "react/jsx-runtime";
2931
+ function formatRemaining(targetMs, nowMs) {
2932
+ const ms = targetMs - nowMs;
2933
+ if (ms <= 0) return "00:00:00";
2934
+ const totalSec = Math.floor(ms / 1e3);
2935
+ const h = Math.floor(totalSec / 3600);
2936
+ const m = Math.floor(totalSec % 3600 / 60);
2937
+ const s = totalSec % 60;
2938
+ return [h, m, s].map((v) => String(v).padStart(2, "0")).join(":");
2939
+ }
2940
+ function MaintenancePage({
2941
+ title = "System Maintenance",
2942
+ description = "We're performing a short maintenance to improve service quality. We'll be back shortly.",
2943
+ eta,
2944
+ statusUrl,
2945
+ statusLabel = "Status Page",
2946
+ icon,
2947
+ className
2948
+ }) {
2949
+ const etaMs = eta ? new Date(eta).getTime() : null;
2950
+ const [nowMs, setNowMs] = useState14(() => Date.now());
2951
+ useEffect10(() => {
2952
+ if (etaMs === null) return;
2953
+ const id = window.setInterval(() => setNowMs(Date.now()), 1e3);
2954
+ return () => window.clearInterval(id);
2955
+ }, [etaMs]);
2956
+ const remaining = etaMs !== null ? formatRemaining(etaMs, nowMs) : null;
2957
+ return /* @__PURE__ */ jsxs36(
2958
+ "div",
2959
+ {
2960
+ role: "status",
2961
+ "aria-live": "polite",
2962
+ className: cn(
2963
+ "min-h-screen flex flex-col items-center justify-center px-4 bg-surface-base",
2964
+ className
2965
+ ),
2966
+ children: [
2967
+ /* @__PURE__ */ jsx39(
2968
+ "div",
2969
+ {
2970
+ className: "flex h-20 w-20 mb-6 items-center justify-center rounded-2xl text-4xl shadow-lg",
2971
+ style: {
2972
+ background: "linear-gradient(135deg, var(--warning) 0%, var(--primary) 100%)",
2973
+ boxShadow: "0 8px 32px color-mix(in srgb, var(--warning) 30%, transparent)"
2974
+ },
2975
+ children: icon != null ? icon : /* @__PURE__ */ jsx39(
2976
+ FontAwesomeIcon18,
2977
+ {
2978
+ icon: faScrewdriverWrench,
2979
+ className: "w-8 h-8 text-text-inverse",
2980
+ "aria-hidden": "true"
2981
+ }
2982
+ )
2983
+ }
2984
+ ),
2985
+ /* @__PURE__ */ jsx39("h1", { className: "text-2xl sm:text-3xl font-bold text-text-primary text-center", children: title }),
2986
+ /* @__PURE__ */ jsx39("p", { className: "mt-3 max-w-md text-center text-text-secondary text-sm sm:text-base leading-relaxed", children: description }),
2987
+ remaining && /* @__PURE__ */ jsxs36("div", { className: "mt-6 flex flex-col items-center gap-2", children: [
2988
+ /* @__PURE__ */ jsx39("span", { className: "text-xs uppercase tracking-wide text-text-disabled", children: "Estimated Return" }),
2989
+ /* @__PURE__ */ jsxs36(Badge, { variant: "warning", size: "lg", children: [
2990
+ /* @__PURE__ */ jsx39(FontAwesomeIcon18, { icon: faClock2, className: "w-3 h-3", "aria-hidden": "true" }),
2991
+ /* @__PURE__ */ jsx39("span", { className: "font-mono tabular-nums", children: remaining })
2992
+ ] })
2993
+ ] }),
2994
+ statusUrl && /* @__PURE__ */ jsxs36(
2995
+ "a",
2996
+ {
2997
+ href: statusUrl,
2998
+ target: "_blank",
2999
+ rel: "noopener noreferrer",
3000
+ className: cn(
3001
+ "mt-8 inline-flex items-center gap-2 px-6 py-2.5 rounded-xl text-sm font-semibold",
3002
+ "text-text-primary border border-border transition-colors hover:bg-surface-overlay",
3003
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus"
3004
+ ),
3005
+ children: [
3006
+ statusLabel,
3007
+ /* @__PURE__ */ jsx39(
3008
+ FontAwesomeIcon18,
3009
+ {
3010
+ icon: faArrowUpRightFromSquare,
3011
+ className: "w-3.5 h-3.5",
3012
+ "aria-hidden": "true"
3013
+ }
3014
+ )
3015
+ ]
3016
+ }
3017
+ ),
3018
+ /* @__PURE__ */ jsx39("div", { className: "mt-16 flex items-center gap-2 opacity-20", "aria-hidden": true, children: [...Array(5)].map((_, i) => /* @__PURE__ */ jsx39(
3019
+ "span",
3020
+ {
3021
+ className: "rounded-full bg-warning",
3022
+ style: {
3023
+ width: i === 2 ? 10 : i === 1 || i === 3 ? 7 : 5,
3024
+ height: i === 2 ? 10 : i === 1 || i === 3 ? 7 : 5
3025
+ }
3026
+ },
3027
+ i
3028
+ )) })
3029
+ ]
3030
+ }
3031
+ );
3032
+ }
3033
+
3034
+ // modules/app/ShareDialog.tsx
3035
+ import { useId as useId4, useMemo as useMemo5, useState as useState15 } from "react";
3036
+ import { FontAwesomeIcon as FontAwesomeIcon19 } from "@fortawesome/react-fontawesome";
3037
+ import {
3038
+ faCopy as faCopy2,
3039
+ faCheck as faCheck3,
3040
+ faPaperPlane,
3041
+ faXmark as faXmark4,
3042
+ faLink,
3043
+ faEnvelope as faEnvelope2
3044
+ } from "@fortawesome/free-solid-svg-icons";
3045
+ import { jsx as jsx40, jsxs as jsxs37 } from "react/jsx-runtime";
3046
+ var DEFAULT_PERMISSIONS = [
3047
+ { value: "viewer", label: "Viewer" },
3048
+ { value: "commenter", label: "Commenter" },
3049
+ { value: "editor", label: "Editor" }
3050
+ ];
3051
+ var permissionBadgeClass = {
3052
+ viewer: "bg-surface-sunken text-text-secondary",
3053
+ commenter: "bg-info-subtle text-info-fg",
3054
+ editor: "bg-primary-subtle text-primary",
3055
+ owner: "bg-warning-subtle text-warning-fg"
3056
+ };
3057
+ function ShareDialog({
3058
+ open,
3059
+ onClose,
3060
+ title = "Share",
3061
+ description = "Invite people or copy the link.",
3062
+ shareUrl,
3063
+ invitees = [],
3064
+ permissions = DEFAULT_PERMISSIONS,
3065
+ defaultPermission = "viewer",
3066
+ onInvite,
3067
+ onRemove,
3068
+ onPermissionChange,
3069
+ portalTarget
3070
+ }) {
3071
+ const linkId = useId4();
3072
+ const emailId = useId4();
3073
+ const permId = useId4();
3074
+ const [email, setEmail] = useState15("");
3075
+ const [permission, setPermission] = useState15(defaultPermission);
3076
+ const [copied, setCopied] = useState15(false);
3077
+ const [inviting, setInviting] = useState15(false);
3078
+ const [error, setError] = useState15(null);
3079
+ const emailValid = useMemo5(() => /.+@.+\..+/.test(email.trim()), [email]);
3080
+ async function copyLink() {
3081
+ try {
3082
+ await navigator.clipboard.writeText(shareUrl);
3083
+ setCopied(true);
3084
+ window.setTimeout(() => setCopied(false), 1500);
3085
+ } catch (e) {
3086
+ setError("Couldn't copy link.");
3087
+ }
3088
+ }
3089
+ async function handleInvite() {
3090
+ if (!emailValid) {
3091
+ setError("Enter a valid email address.");
3092
+ return;
3093
+ }
3094
+ setError(null);
3095
+ setInviting(true);
3096
+ try {
3097
+ await (onInvite == null ? void 0 : onInvite(email.trim(), permission));
3098
+ setEmail("");
3099
+ } finally {
3100
+ setInviting(false);
3101
+ }
3102
+ }
3103
+ return /* @__PURE__ */ jsx40(Modal, { open, onClose, title, description, size: "lg", portalTarget, children: /* @__PURE__ */ jsxs37("div", { className: "space-y-5", children: [
3104
+ /* @__PURE__ */ jsxs37("div", { className: "space-y-1.5", children: [
3105
+ /* @__PURE__ */ jsx40("label", { htmlFor: linkId, className: "block text-sm font-medium text-text-primary", children: "Shareable Link" }),
3106
+ /* @__PURE__ */ jsxs37("div", { className: "flex gap-2", children: [
3107
+ /* @__PURE__ */ jsxs37("div", { className: "flex flex-1 items-center gap-2 rounded-md border border-border bg-surface-base px-3 py-2 text-sm", children: [
3108
+ /* @__PURE__ */ jsx40(
3109
+ FontAwesomeIcon19,
3110
+ {
3111
+ icon: faLink,
3112
+ className: "w-3.5 h-3.5 text-text-disabled shrink-0",
3113
+ "aria-hidden": "true"
3114
+ }
3115
+ ),
3116
+ /* @__PURE__ */ jsx40(
3117
+ "input",
3118
+ {
3119
+ id: linkId,
3120
+ type: "text",
3121
+ value: shareUrl,
3122
+ readOnly: true,
3123
+ onFocus: (e) => e.currentTarget.select(),
3124
+ className: "flex-1 bg-transparent text-text-primary focus-visible:outline-none truncate"
3125
+ }
3126
+ )
3127
+ ] }),
3128
+ /* @__PURE__ */ jsx40(
3129
+ Button,
3130
+ {
3131
+ variant: copied ? "secondary" : "primary",
3132
+ onClick: copyLink,
3133
+ iconLeft: /* @__PURE__ */ jsx40(
3134
+ FontAwesomeIcon19,
3135
+ {
3136
+ icon: copied ? faCheck3 : faCopy2,
3137
+ className: "w-3.5 h-3.5",
3138
+ "aria-hidden": "true"
3139
+ }
3140
+ ),
3141
+ children: copied ? "Copied" : "Copy"
3142
+ }
3143
+ )
3144
+ ] })
3145
+ ] }),
3146
+ /* @__PURE__ */ jsxs37("div", { className: "space-y-2", children: [
3147
+ /* @__PURE__ */ jsx40("label", { htmlFor: emailId, className: "block text-sm font-medium text-text-primary", children: "Invite People" }),
3148
+ /* @__PURE__ */ jsxs37("div", { className: "flex items-center gap-2 rounded-md border border-border bg-surface-base px-3 py-2 text-sm focus-within:ring-2 focus-within:ring-border-focus", children: [
3149
+ /* @__PURE__ */ jsx40(
3150
+ FontAwesomeIcon19,
3151
+ {
3152
+ icon: faEnvelope2,
3153
+ className: "w-3.5 h-3.5 text-text-disabled shrink-0",
3154
+ "aria-hidden": "true"
3155
+ }
3156
+ ),
3157
+ /* @__PURE__ */ jsx40(
3158
+ "input",
3159
+ {
3160
+ id: emailId,
3161
+ type: "email",
3162
+ value: email,
3163
+ onChange: (e) => {
3164
+ setEmail(e.target.value);
3165
+ if (error) setError(null);
3166
+ },
3167
+ placeholder: "name@example.com",
3168
+ "aria-invalid": !!error,
3169
+ "aria-describedby": error ? `${emailId}-error` : void 0,
3170
+ className: "w-full bg-transparent text-text-primary placeholder:text-text-disabled focus-visible:outline-none"
3171
+ }
3172
+ )
3173
+ ] }),
3174
+ /* @__PURE__ */ jsxs37("div", { className: "flex items-center justify-end gap-2", children: [
3175
+ /* @__PURE__ */ jsx40(
3176
+ "select",
3177
+ {
3178
+ id: permId,
3179
+ value: permission,
3180
+ onChange: (e) => setPermission(e.target.value),
3181
+ "aria-label": "Select permission",
3182
+ className: cn(
3183
+ "rounded-md border border-border bg-surface-base px-3 py-2 text-sm text-text-primary",
3184
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus"
3185
+ ),
3186
+ children: permissions.map((p) => /* @__PURE__ */ jsx40("option", { value: p.value, children: p.label }, p.value))
3187
+ }
3188
+ ),
3189
+ /* @__PURE__ */ jsx40(
3190
+ Button,
3191
+ {
3192
+ onClick: handleInvite,
3193
+ loading: inviting,
3194
+ disabled: !emailValid || inviting,
3195
+ iconLeft: /* @__PURE__ */ jsx40(FontAwesomeIcon19, { icon: faPaperPlane, className: "w-3.5 h-3.5", "aria-hidden": "true" }),
3196
+ children: "Invite"
3197
+ }
3198
+ )
3199
+ ] }),
3200
+ error && /* @__PURE__ */ jsx40("p", { id: `${emailId}-error`, className: "text-xs text-error", role: "alert", children: error })
3201
+ ] }),
3202
+ invitees.length > 0 && /* @__PURE__ */ jsxs37("div", { className: "space-y-2", children: [
3203
+ /* @__PURE__ */ jsxs37("p", { className: "text-xs uppercase tracking-wide text-text-disabled font-medium", children: [
3204
+ "People with access (",
3205
+ invitees.length,
3206
+ ")"
3207
+ ] }),
3208
+ /* @__PURE__ */ jsx40("ul", { className: "divide-y divide-border rounded-md border border-border bg-surface-base", children: invitees.map((inv) => {
3209
+ var _a;
3210
+ return /* @__PURE__ */ jsxs37("li", { className: "flex items-center gap-3 px-3 py-2", children: [
3211
+ /* @__PURE__ */ jsx40(Avatar, { src: (_a = inv.avatarUrl) != null ? _a : void 0, name: inv.name, size: "sm" }),
3212
+ /* @__PURE__ */ jsxs37("div", { className: "flex-1 min-w-0", children: [
3213
+ /* @__PURE__ */ jsx40("p", { className: "text-sm font-medium text-text-primary truncate", children: inv.name }),
3214
+ /* @__PURE__ */ jsx40("p", { className: "text-xs text-text-secondary truncate", children: inv.email })
3215
+ ] }),
3216
+ inv.permission === "owner" ? /* @__PURE__ */ jsx40(
3217
+ "span",
3218
+ {
3219
+ className: cn(
3220
+ "inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium",
3221
+ permissionBadgeClass.owner
3222
+ ),
3223
+ children: "Owner"
3224
+ }
3225
+ ) : /* @__PURE__ */ jsx40(
3226
+ "select",
3227
+ {
3228
+ value: inv.permission,
3229
+ onChange: (e) => onPermissionChange == null ? void 0 : onPermissionChange(inv.id, e.target.value),
3230
+ "aria-label": `Permission for ${inv.name}`,
3231
+ className: cn(
3232
+ "rounded-md border border-border bg-surface-base px-2 py-1 text-xs text-text-primary",
3233
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus"
3234
+ ),
3235
+ children: permissions.map((p) => /* @__PURE__ */ jsx40("option", { value: p.value, children: p.label }, p.value))
3236
+ }
3237
+ ),
3238
+ inv.permission !== "owner" && onRemove && /* @__PURE__ */ jsx40(
3239
+ "button",
3240
+ {
3241
+ type: "button",
3242
+ onClick: () => onRemove(inv.id),
3243
+ "aria-label": `Remove ${inv.name}'s access`,
3244
+ className: cn(
3245
+ "shrink-0 inline-flex items-center justify-center w-7 h-7 rounded-md text-text-disabled",
3246
+ "hover:text-error hover:bg-error-subtle transition-colors",
3247
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus"
3248
+ ),
3249
+ children: /* @__PURE__ */ jsx40(FontAwesomeIcon19, { icon: faXmark4, className: "w-3 h-3", "aria-hidden": "true" })
3250
+ }
3251
+ )
3252
+ ] }, inv.id);
3253
+ }) })
3254
+ ] })
3255
+ ] }) });
3256
+ }
3257
+
3258
+ // modules/app/CommentThread.tsx
3259
+ import { useState as useState16, useId as useId5 } from "react";
3260
+ import { FontAwesomeIcon as FontAwesomeIcon20 } from "@fortawesome/react-fontawesome";
3261
+ import {
3262
+ faReply,
3263
+ faHeart,
3264
+ faTrash as faTrash2,
3265
+ faPaperPlane as faPaperPlane2,
3266
+ faXmark as faXmark5
3267
+ } from "@fortawesome/free-solid-svg-icons";
3268
+ import { jsx as jsx41, jsxs as jsxs38 } from "react/jsx-runtime";
3269
+ function defaultFormat(value) {
3270
+ const date = value instanceof Date ? value : new Date(value);
3271
+ const diff = (Date.now() - date.getTime()) / 1e3;
3272
+ if (diff < 60) return `${Math.floor(diff)}s`;
3273
+ if (diff < 3600) return `${Math.floor(diff / 60)}m`;
3274
+ if (diff < 86400) return `${Math.floor(diff / 3600)}h`;
3275
+ return date.toLocaleDateString();
3276
+ }
3277
+ function CommentNode({
3278
+ comment,
3279
+ depth,
3280
+ maxDepth,
3281
+ currentUserId,
3282
+ onReply,
3283
+ onDelete,
3284
+ onLike,
3285
+ formatTimestamp,
3286
+ placeholder
3287
+ }) {
3288
+ var _a, _b;
3289
+ const [replying, setReplying] = useState16(false);
3290
+ const [draft, setDraft] = useState16("");
3291
+ const [submitting, setSubmitting] = useState16(false);
3292
+ const replyId = useId5();
3293
+ const canReply = onReply && depth < maxDepth;
3294
+ const isOwn = currentUserId && comment.author.id === currentUserId;
3295
+ async function submitReply() {
3296
+ if (!draft.trim() || !onReply) return;
3297
+ setSubmitting(true);
3298
+ try {
3299
+ await onReply(comment.id, draft.trim());
3300
+ setDraft("");
3301
+ setReplying(false);
3302
+ } finally {
3303
+ setSubmitting(false);
3304
+ }
3305
+ }
3306
+ return /* @__PURE__ */ jsxs38("li", { className: "flex gap-3", children: [
3307
+ /* @__PURE__ */ jsx41(Avatar, { src: (_a = comment.author.avatarUrl) != null ? _a : void 0, name: comment.author.name, size: "sm" }),
3308
+ /* @__PURE__ */ jsxs38("div", { className: "flex-1 min-w-0", children: [
3309
+ /* @__PURE__ */ jsxs38("div", { className: "rounded-lg bg-surface-overlay px-3 py-2", children: [
3310
+ /* @__PURE__ */ jsxs38("div", { className: "flex items-center justify-between gap-2", children: [
3311
+ /* @__PURE__ */ jsx41("span", { className: "text-sm font-semibold text-text-primary truncate", children: comment.author.name }),
3312
+ /* @__PURE__ */ jsx41(
3313
+ "time",
3314
+ {
3315
+ dateTime: comment.createdAt instanceof Date ? comment.createdAt.toISOString() : String(comment.createdAt),
3316
+ className: "text-xs text-text-disabled shrink-0",
3317
+ children: formatTimestamp(comment.createdAt)
3318
+ }
3319
+ )
3320
+ ] }),
3321
+ /* @__PURE__ */ jsx41("p", { className: "mt-1 text-sm text-text-primary whitespace-pre-wrap break-words", children: comment.body })
3322
+ ] }),
3323
+ /* @__PURE__ */ jsxs38("div", { className: "mt-1 flex items-center gap-3 px-1 text-xs text-text-secondary", children: [
3324
+ onLike && /* @__PURE__ */ jsxs38(
3325
+ "button",
3326
+ {
3327
+ type: "button",
3328
+ onClick: () => onLike(comment.id, !comment.likedByMe),
3329
+ "aria-pressed": !!comment.likedByMe,
3330
+ className: cn(
3331
+ "inline-flex items-center gap-1 transition-colors hover:text-primary",
3332
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus rounded",
3333
+ comment.likedByMe && "text-primary"
3334
+ ),
3335
+ children: [
3336
+ /* @__PURE__ */ jsx41(FontAwesomeIcon20, { icon: faHeart, className: "w-3 h-3", "aria-hidden": "true" }),
3337
+ (_b = comment.likeCount) != null ? _b : 0
3338
+ ]
3339
+ }
3340
+ ),
3341
+ canReply && /* @__PURE__ */ jsxs38(
3342
+ "button",
3343
+ {
3344
+ type: "button",
3345
+ onClick: () => setReplying((v) => !v),
3346
+ "aria-expanded": replying,
3347
+ className: cn(
3348
+ "inline-flex items-center gap-1 transition-colors hover:text-primary",
3349
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus rounded"
3350
+ ),
3351
+ children: [
3352
+ /* @__PURE__ */ jsx41(FontAwesomeIcon20, { icon: faReply, className: "w-3 h-3", "aria-hidden": "true" }),
3353
+ "Reply"
3354
+ ]
3355
+ }
3356
+ ),
3357
+ isOwn && onDelete && /* @__PURE__ */ jsxs38(
3358
+ "button",
3359
+ {
3360
+ type: "button",
3361
+ onClick: () => onDelete(comment.id),
3362
+ className: cn(
3363
+ "inline-flex items-center gap-1 ml-auto transition-colors hover:text-error",
3364
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus rounded"
3365
+ ),
3366
+ children: [
3367
+ /* @__PURE__ */ jsx41(FontAwesomeIcon20, { icon: faTrash2, className: "w-3 h-3", "aria-hidden": "true" }),
3368
+ "Delete"
3369
+ ]
3370
+ }
3371
+ )
3372
+ ] }),
3373
+ replying && /* @__PURE__ */ jsxs38("div", { className: "mt-2 flex flex-col gap-2", children: [
3374
+ /* @__PURE__ */ jsxs38("label", { htmlFor: replyId, className: "sr-only", children: [
3375
+ "Reply to ",
3376
+ comment.author.name
3377
+ ] }),
3378
+ /* @__PURE__ */ jsx41(
3379
+ "textarea",
3380
+ {
3381
+ id: replyId,
3382
+ value: draft,
3383
+ onChange: (e) => setDraft(e.target.value),
3384
+ placeholder,
3385
+ rows: 2,
3386
+ className: cn(
3387
+ "block w-full rounded-md border border-border bg-surface-base px-3 py-2 text-sm text-text-primary",
3388
+ "placeholder:text-text-disabled resize-y",
3389
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:border-border-focus"
3390
+ )
3391
+ }
3392
+ ),
3393
+ /* @__PURE__ */ jsxs38("div", { className: "flex items-center justify-end gap-2", children: [
3394
+ /* @__PURE__ */ jsx41(
3395
+ Button,
3396
+ {
3397
+ variant: "ghost",
3398
+ size: "sm",
3399
+ onClick: () => {
3400
+ setReplying(false);
3401
+ setDraft("");
3402
+ },
3403
+ iconLeft: /* @__PURE__ */ jsx41(FontAwesomeIcon20, { icon: faXmark5, className: "w-3 h-3", "aria-hidden": "true" }),
3404
+ children: "Cancel"
3405
+ }
3406
+ ),
3407
+ /* @__PURE__ */ jsx41(
3408
+ Button,
3409
+ {
3410
+ size: "sm",
3411
+ onClick: submitReply,
3412
+ loading: submitting,
3413
+ disabled: !draft.trim() || submitting,
3414
+ iconLeft: /* @__PURE__ */ jsx41(FontAwesomeIcon20, { icon: faPaperPlane2, className: "w-3 h-3", "aria-hidden": "true" }),
3415
+ children: "Reply"
3416
+ }
3417
+ )
3418
+ ] })
3419
+ ] }),
3420
+ comment.replies && comment.replies.length > 0 && /* @__PURE__ */ jsx41("ul", { className: "mt-3 space-y-3 border-l border-border pl-4", children: comment.replies.map((child) => /* @__PURE__ */ jsx41(
3421
+ CommentNode,
3422
+ {
3423
+ comment: child,
3424
+ depth: depth + 1,
3425
+ maxDepth,
3426
+ currentUserId,
3427
+ onReply,
3428
+ onDelete,
3429
+ onLike,
3430
+ formatTimestamp,
3431
+ placeholder
3432
+ },
3433
+ child.id
3434
+ )) })
3435
+ ] })
3436
+ ] });
3437
+ }
3438
+ function CommentThread({
3439
+ comments,
3440
+ currentUserId,
3441
+ maxDepth = 3,
3442
+ onReply,
3443
+ onDelete,
3444
+ onLike,
3445
+ formatTimestamp = defaultFormat,
3446
+ emptyMessage = "No comments yet. Be the first to comment.",
3447
+ placeholder = "Write a comment\u2026",
3448
+ className
3449
+ }) {
3450
+ const [draft, setDraft] = useState16("");
3451
+ const [submitting, setSubmitting] = useState16(false);
3452
+ const rootId = useId5();
3453
+ async function submitRoot() {
3454
+ if (!draft.trim() || !onReply) return;
3455
+ setSubmitting(true);
3456
+ try {
3457
+ await onReply(null, draft.trim());
3458
+ setDraft("");
3459
+ } finally {
3460
+ setSubmitting(false);
3461
+ }
3462
+ }
3463
+ return /* @__PURE__ */ jsxs38("section", { className: cn("space-y-4", className), "aria-label": "Comments", children: [
3464
+ onReply && /* @__PURE__ */ jsxs38("div", { className: "flex flex-col gap-2", children: [
3465
+ /* @__PURE__ */ jsx41("label", { htmlFor: rootId, className: "sr-only", children: "New comment" }),
3466
+ /* @__PURE__ */ jsx41(
3467
+ "textarea",
3468
+ {
3469
+ id: rootId,
3470
+ value: draft,
3471
+ onChange: (e) => setDraft(e.target.value),
3472
+ placeholder,
3473
+ rows: 3,
3474
+ className: cn(
3475
+ "block w-full rounded-md border border-border bg-surface-base px-3 py-2 text-sm text-text-primary",
3476
+ "placeholder:text-text-disabled resize-y",
3477
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:border-border-focus"
3478
+ )
3479
+ }
3480
+ ),
3481
+ /* @__PURE__ */ jsx41("div", { className: "flex items-center justify-end", children: /* @__PURE__ */ jsx41(
3482
+ Button,
3483
+ {
3484
+ onClick: submitRoot,
3485
+ loading: submitting,
3486
+ disabled: !draft.trim() || submitting,
3487
+ iconLeft: /* @__PURE__ */ jsx41(FontAwesomeIcon20, { icon: faPaperPlane2, className: "w-3.5 h-3.5", "aria-hidden": "true" }),
3488
+ children: "Post Comment"
3489
+ }
3490
+ ) })
3491
+ ] }),
3492
+ comments.length === 0 ? /* @__PURE__ */ jsx41("p", { className: "rounded-md border border-dashed border-border bg-surface-base px-4 py-6 text-center text-sm text-text-secondary", children: emptyMessage }) : /* @__PURE__ */ jsx41("ul", { className: "space-y-4", children: comments.map((c) => /* @__PURE__ */ jsx41(
3493
+ CommentNode,
3494
+ {
3495
+ comment: c,
3496
+ depth: 0,
3497
+ maxDepth,
3498
+ currentUserId,
3499
+ onReply,
3500
+ onDelete,
3501
+ onLike,
3502
+ formatTimestamp,
3503
+ placeholder
3504
+ },
3505
+ c.id
3506
+ )) })
3507
+ ] });
3508
+ }
3509
+
3510
+ // modules/app/MentionPicker.tsx
3511
+ import { useEffect as useEffect11, useMemo as useMemo6, useRef as useRef7, useState as useState17 } from "react";
3512
+ import { FontAwesomeIcon as FontAwesomeIcon21 } from "@fortawesome/react-fontawesome";
3513
+ import { faAt } from "@fortawesome/free-solid-svg-icons";
3514
+ import { jsx as jsx42, jsxs as jsxs39 } from "react/jsx-runtime";
3515
+ function defaultFilter(user, query) {
3516
+ var _a;
3517
+ if (!query) return true;
3518
+ const q = query.toLowerCase();
3519
+ return user.name.toLowerCase().includes(q) || ((_a = user.handle) != null ? _a : "").toLowerCase().includes(q);
3520
+ }
3521
+ function MentionPicker({
3522
+ users,
3523
+ query = "",
3524
+ open = true,
3525
+ position,
3526
+ maxItems = 6,
3527
+ emptyMessage = "No matching users",
3528
+ onSelect,
3529
+ onCancel,
3530
+ filter = defaultFilter,
3531
+ className
3532
+ }) {
3533
+ const [active, setActive] = useState17(0);
3534
+ const containerRef = useRef7(null);
3535
+ const itemRefs = useRef7([]);
3536
+ const filtered = useMemo6(
3537
+ () => users.filter((u) => filter(u, query)).slice(0, maxItems),
3538
+ [users, query, filter, maxItems]
3539
+ );
3540
+ const safeActive = filtered.length === 0 ? 0 : Math.min(active, filtered.length - 1);
3541
+ useEffect11(() => {
3542
+ var _a;
3543
+ (_a = itemRefs.current[safeActive]) == null ? void 0 : _a.scrollIntoView({ block: "nearest" });
3544
+ }, [safeActive]);
3545
+ useEffect11(() => {
3546
+ if (!open) return;
3547
+ function onKey(e) {
3548
+ if (e.key === "ArrowDown") {
3549
+ e.preventDefault();
3550
+ setActive((a) => filtered.length ? (a + 1) % filtered.length : 0);
3551
+ } else if (e.key === "ArrowUp") {
3552
+ e.preventDefault();
3553
+ setActive((a) => filtered.length ? (a - 1 + filtered.length) % filtered.length : 0);
3554
+ } else if (e.key === "Enter" || e.key === "Tab") {
3555
+ if (filtered.length > 0) {
3556
+ e.preventDefault();
3557
+ const idx = Math.min(safeActive, filtered.length - 1);
3558
+ onSelect(filtered[idx]);
3559
+ }
3560
+ } else if (e.key === "Escape") {
3561
+ e.preventDefault();
3562
+ onCancel == null ? void 0 : onCancel();
3563
+ }
3564
+ }
3565
+ window.addEventListener("keydown", onKey);
3566
+ return () => window.removeEventListener("keydown", onKey);
3567
+ }, [open, filtered, safeActive, onSelect, onCancel]);
3568
+ useEffect11(() => {
3569
+ if (!open || !onCancel) return;
3570
+ function onClick(e) {
3571
+ if (containerRef.current && !containerRef.current.contains(e.target)) {
3572
+ onCancel == null ? void 0 : onCancel();
3573
+ }
3574
+ }
3575
+ document.addEventListener("mousedown", onClick);
3576
+ return () => document.removeEventListener("mousedown", onClick);
3577
+ }, [open, onCancel]);
3578
+ if (!open) return null;
3579
+ const style = position ? { position: "absolute", top: position.top, left: position.left } : void 0;
3580
+ return /* @__PURE__ */ jsxs39(
3581
+ "div",
3582
+ {
3583
+ ref: containerRef,
3584
+ role: "listbox",
3585
+ "aria-label": "Users to mention",
3586
+ style,
3587
+ className: cn(
3588
+ "z-50 w-72 rounded-lg border border-border bg-surface-raised shadow-lg overflow-hidden",
3589
+ className
3590
+ ),
3591
+ children: [
3592
+ /* @__PURE__ */ jsxs39("div", { className: "flex items-center gap-2 border-b border-border px-3 py-2 text-xs text-text-secondary", children: [
3593
+ /* @__PURE__ */ jsx42(FontAwesomeIcon21, { icon: faAt, className: "w-3 h-3 text-text-disabled", "aria-hidden": "true" }),
3594
+ /* @__PURE__ */ jsx42("span", { className: "font-medium", children: query ? `"${query}"` : "Mention\u2026" })
3595
+ ] }),
3596
+ filtered.length === 0 ? /* @__PURE__ */ jsx42("p", { className: "px-3 py-4 text-sm text-center text-text-secondary", children: emptyMessage }) : /* @__PURE__ */ jsx42("ul", { className: "max-h-64 overflow-y-auto py-1", children: filtered.map((user, i) => {
3597
+ var _a, _b;
3598
+ const isActive = i === safeActive;
3599
+ return /* @__PURE__ */ jsxs39(
3600
+ "li",
3601
+ {
3602
+ ref: (node) => {
3603
+ itemRefs.current[i] = node;
3604
+ },
3605
+ role: "option",
3606
+ "aria-selected": isActive,
3607
+ onMouseEnter: () => setActive(i),
3608
+ onMouseDown: (e) => {
3609
+ e.preventDefault();
3610
+ onSelect(user);
3611
+ },
3612
+ className: cn(
3613
+ "flex items-center gap-3 px-3 py-2 cursor-pointer transition-colors",
3614
+ isActive ? "bg-surface-overlay" : "hover:bg-surface-overlay"
3615
+ ),
3616
+ children: [
3617
+ /* @__PURE__ */ jsx42(Avatar, { src: (_a = user.avatarUrl) != null ? _a : void 0, name: user.name, size: "sm" }),
3618
+ /* @__PURE__ */ jsxs39("div", { className: "flex-1 min-w-0", children: [
3619
+ /* @__PURE__ */ jsx42("p", { className: "text-sm font-medium text-text-primary truncate", children: user.name }),
3620
+ /* @__PURE__ */ jsx42("p", { className: "text-xs text-text-secondary truncate", children: user.handle ? `@${user.handle}` : (_b = user.subtitle) != null ? _b : "" })
3621
+ ] })
3622
+ ]
3623
+ },
3624
+ user.id
3625
+ );
3626
+ }) })
3627
+ ]
3628
+ }
3629
+ );
3630
+ }
3631
+
3632
+ // modules/app/OnboardingWizard.tsx
3633
+ import { useState as useState18 } from "react";
3634
+ import { FontAwesomeIcon as FontAwesomeIcon22 } from "@fortawesome/react-fontawesome";
3635
+ import { faArrowRight as faArrowRight2, faArrowLeft as faArrowLeft3, faCheck as faCheck4, faXmark as faXmark6 } from "@fortawesome/free-solid-svg-icons";
3636
+ import { jsx as jsx43, jsxs as jsxs40 } from "react/jsx-runtime";
3637
+ function ProgressBar({ value, total }) {
3638
+ const pct = total > 0 ? Math.min(100, Math.max(0, (value + 1) / total * 100)) : 0;
3639
+ return /* @__PURE__ */ jsx43(
3640
+ "div",
3641
+ {
3642
+ className: "h-1 w-full rounded-full bg-surface-sunken overflow-hidden",
3643
+ role: "progressbar",
3644
+ "aria-valuemin": 0,
3645
+ "aria-valuemax": total,
3646
+ "aria-valuenow": value + 1,
3647
+ children: /* @__PURE__ */ jsx43(
3648
+ "div",
3649
+ {
3650
+ className: "h-full bg-primary transition-[width] duration-300",
3651
+ style: { width: `${pct}%` }
3652
+ }
3653
+ )
3654
+ }
3655
+ );
3656
+ }
3657
+ function ProgressDots({ value, total }) {
3658
+ return /* @__PURE__ */ jsx43(
3659
+ "div",
3660
+ {
3661
+ className: "flex items-center gap-2",
3662
+ role: "progressbar",
3663
+ "aria-valuemin": 0,
3664
+ "aria-valuemax": total,
3665
+ "aria-valuenow": value + 1,
3666
+ children: Array.from({ length: total }).map((_, i) => /* @__PURE__ */ jsx43(
3667
+ "span",
3668
+ {
3669
+ className: cn(
3670
+ "h-2 rounded-full transition-all",
3671
+ i === value ? "w-6 bg-primary" : i < value ? "w-2 bg-primary" : "w-2 bg-surface-sunken"
3672
+ ),
3673
+ "aria-hidden": "true"
3674
+ },
3675
+ i
3676
+ ))
3677
+ }
3678
+ );
3679
+ }
3680
+ function OnboardingWizard({
3681
+ steps,
3682
+ mode = "page",
3683
+ open = true,
3684
+ initialStep = 0,
3685
+ title = "Welcome",
3686
+ allowSkip = true,
3687
+ onStepChange,
3688
+ onComplete,
3689
+ onSkip,
3690
+ onClose,
3691
+ nextLabel = "Next",
3692
+ prevLabel = "Back",
3693
+ skipLabel = "Skip",
3694
+ completeLabel = "Finish",
3695
+ indicator = "dots",
3696
+ className
3697
+ }) {
3698
+ const [current, setCurrent] = useState18(Math.min(Math.max(initialStep, 0), steps.length - 1));
3699
+ const [completing, setCompleting] = useState18(false);
3700
+ function setStep(next) {
3701
+ const clamped = Math.min(Math.max(next, 0), steps.length - 1);
3702
+ setCurrent(clamped);
3703
+ onStepChange == null ? void 0 : onStepChange(clamped);
3704
+ }
3705
+ function goNext() {
3706
+ setStep(current + 1);
3707
+ }
3708
+ function goPrev() {
3709
+ setStep(current - 1);
3710
+ }
3711
+ async function complete() {
3712
+ setCompleting(true);
3713
+ try {
3714
+ await (onComplete == null ? void 0 : onComplete());
3715
+ } finally {
3716
+ setCompleting(false);
3717
+ }
3718
+ }
3719
+ const step = steps[current];
3720
+ if (!step) return null;
3721
+ const isLast = current === steps.length - 1;
3722
+ const isFirst = current === 0;
3723
+ const body = /* @__PURE__ */ jsxs40("div", { className: cn("flex flex-col gap-6", className), children: [
3724
+ /* @__PURE__ */ jsxs40("div", { className: "flex items-center gap-3", children: [
3725
+ /* @__PURE__ */ jsxs40("span", { className: "text-xs uppercase tracking-wide text-text-disabled font-medium shrink-0", children: [
3726
+ current + 1,
3727
+ " / ",
3728
+ steps.length
3729
+ ] }),
3730
+ /* @__PURE__ */ jsx43("div", { className: "flex-1", children: indicator === "bar" ? /* @__PURE__ */ jsx43(ProgressBar, { value: current, total: steps.length }) : /* @__PURE__ */ jsx43(ProgressDots, { value: current, total: steps.length }) })
3731
+ ] }),
3732
+ /* @__PURE__ */ jsxs40("div", { children: [
3733
+ /* @__PURE__ */ jsx43("h2", { className: "text-lg font-semibold text-text-primary", children: step.title }),
3734
+ step.description && /* @__PURE__ */ jsx43("p", { className: "mt-1 text-sm text-text-secondary", children: step.description })
3735
+ ] }),
3736
+ /* @__PURE__ */ jsx43("div", { className: "min-h-[8rem]", children: typeof step.content === "function" ? step.content({ goNext, goPrev }) : step.content }),
3737
+ /* @__PURE__ */ jsxs40("div", { className: "flex items-center justify-between gap-3 pt-4 border-t border-border", children: [
3738
+ /* @__PURE__ */ jsx43("div", { children: !isFirst && /* @__PURE__ */ jsx43(
3739
+ Button,
3740
+ {
3741
+ variant: "outline",
3742
+ onClick: goPrev,
3743
+ iconLeft: /* @__PURE__ */ jsx43(FontAwesomeIcon22, { icon: faArrowLeft3, className: "w-3.5 h-3.5", "aria-hidden": "true" }),
3744
+ children: prevLabel
3745
+ }
3746
+ ) }),
3747
+ /* @__PURE__ */ jsxs40("div", { className: "flex items-center gap-2", children: [
3748
+ allowSkip && !isLast && /* @__PURE__ */ jsx43(
3749
+ Button,
3750
+ {
3751
+ variant: "ghost",
3752
+ onClick: () => onSkip == null ? void 0 : onSkip(),
3753
+ iconLeft: /* @__PURE__ */ jsx43(FontAwesomeIcon22, { icon: faXmark6, className: "w-3.5 h-3.5", "aria-hidden": "true" }),
3754
+ children: skipLabel
3755
+ }
3756
+ ),
3757
+ isLast ? /* @__PURE__ */ jsx43(
3758
+ Button,
3759
+ {
3760
+ onClick: complete,
3761
+ loading: completing,
3762
+ iconLeft: /* @__PURE__ */ jsx43(FontAwesomeIcon22, { icon: faCheck4, className: "w-3.5 h-3.5", "aria-hidden": "true" }),
3763
+ children: completeLabel
3764
+ }
3765
+ ) : /* @__PURE__ */ jsx43(
3766
+ Button,
3767
+ {
3768
+ onClick: goNext,
3769
+ iconRight: /* @__PURE__ */ jsx43(FontAwesomeIcon22, { icon: faArrowRight2, className: "w-3.5 h-3.5", "aria-hidden": "true" }),
3770
+ children: nextLabel
3771
+ }
3772
+ )
3773
+ ] })
3774
+ ] })
3775
+ ] });
3776
+ if (mode === "modal") {
3777
+ return /* @__PURE__ */ jsx43(
3778
+ Modal,
3779
+ {
3780
+ open,
3781
+ onClose: onClose != null ? onClose : (() => void 0),
3782
+ title,
3783
+ size: "lg",
3784
+ closeOnBackdropClick: false,
3785
+ children: body
3786
+ }
3787
+ );
3788
+ }
3789
+ return /* @__PURE__ */ jsx43("div", { className: "mx-auto w-full max-w-2xl rounded-xl border border-border bg-surface-raised p-6 shadow-sm", children: body });
3790
+ }
3791
+
3792
+ export {
3793
+ AppShell,
3794
+ AppSidebar,
3795
+ AppTopBar,
3796
+ AppDrawer,
3797
+ AppFooter,
3798
+ AppBreadcrumbs,
3799
+ SectionCard,
3800
+ NavDrawer,
3801
+ AppNav,
3802
+ GlobalSearch,
3803
+ AppCommandBar,
3804
+ ContextMenu,
3805
+ ImageGallery,
3806
+ FormField,
3807
+ FilterBar,
3808
+ StepFlow,
3809
+ StepShell,
3810
+ FileUploadSection,
3811
+ DetailHeader,
3812
+ InlineAlert,
3813
+ LoadingState,
3814
+ ErrorState,
3815
+ NotFoundState,
3816
+ NoAccessState,
3817
+ NotFoundPage,
3818
+ SplashScreen,
3819
+ notify,
3820
+ NotificationProvider,
3821
+ FocusTrap,
3822
+ useAnnounce,
3823
+ AnnouncerOutlet,
3824
+ MaintenancePage,
3825
+ ShareDialog,
3826
+ CommentThread,
3827
+ MentionPicker,
3828
+ OnboardingWizard
3829
+ };