@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.
- package/LICENSE +17 -0
- package/README.md +168 -0
- package/dist/AdvancedDataTable-F3DNXDKX.mjs +11 -0
- package/dist/DataTable-2G27T4E6.mjs +11 -0
- package/dist/DateRangePicker-AL32QB6L.mjs +11 -0
- package/dist/DropdownMenu-f5yV9dzM.d.mts +22 -0
- package/dist/DropdownMenu-f5yV9dzM.d.ts +22 -0
- package/dist/MapView-FERKPCDB.mjs +10 -0
- package/dist/ServerDataTable-RZV3K6KQ.mjs +11 -0
- package/dist/Tooltip-Bof5GvOc.d.mts +248 -0
- package/dist/Tooltip-Bof5GvOc.d.ts +248 -0
- package/dist/VideoPlayer-P3I6ESXJ.mjs +9 -0
- package/dist/app.d.mts +620 -0
- package/dist/app.d.ts +620 -0
- package/dist/app.js +7061 -0
- package/dist/app.mjs +100 -0
- package/dist/chunk-24BCQSLI.mjs +1 -0
- package/dist/chunk-45I3EDB2.mjs +90 -0
- package/dist/chunk-4IWCD7ID.mjs +1450 -0
- package/dist/chunk-5E2HXWFI.mjs +105 -0
- package/dist/chunk-C7AYI4XM.mjs +402 -0
- package/dist/chunk-J4D44TUA.mjs +1267 -0
- package/dist/chunk-KTEWZKNE.mjs +1020 -0
- package/dist/chunk-LMUQHL4Z.mjs +3829 -0
- package/dist/chunk-MD5OQ4J2.mjs +527 -0
- package/dist/chunk-MPJRPYIZ.mjs +1 -0
- package/dist/chunk-MPWUEQ7J.mjs +2422 -0
- package/dist/chunk-MTT5TKAJ.mjs +93 -0
- package/dist/chunk-RBDK7MWQ.mjs +46 -0
- package/dist/chunk-SVFQZPNZ.mjs +3648 -0
- package/dist/chunk-TZWBBMSG.mjs +1 -0
- package/dist/chunk-XA7J6PVJ.mjs +1488 -0
- package/dist/chunk-ZLYBRYWQ.mjs +726 -0
- package/dist/common.d.mts +921 -0
- package/dist/common.d.ts +921 -0
- package/dist/common.js +4991 -0
- package/dist/common.mjs +172 -0
- package/dist/index.d.mts +10 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +17563 -0
- package/dist/index.mjs +349 -0
- package/dist/ui.d.mts +937 -0
- package/dist/ui.d.ts +937 -0
- package/dist/ui.js +10095 -0
- package/dist/ui.mjs +163 -0
- package/package.json +114 -0
- 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
|
+
};
|