@cboxdk/cbox-ui 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/BRAND.md +77 -0
- package/LICENSE +21 -0
- package/README.md +156 -0
- package/TRADEMARKS.md +5 -0
- package/assets/favicon.svg +6 -0
- package/assets/monograms/atlas.svg +14 -0
- package/assets/monograms/cortex.svg +14 -0
- package/assets/monograms/id.svg +14 -0
- package/assets/monograms/track.svg +14 -0
- package/assets/og-template.svg +50 -0
- package/assets/wordmark-mono.svg +14 -0
- package/assets/wordmark.svg +18 -0
- package/dist/index.d.ts +1383 -0
- package/dist/index.js +3671 -0
- package/dist/index.js.map +1 -0
- package/package.json +69 -0
- package/tailwind.preset.js +98 -0
- package/tokens/cbox.css +274 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,3671 @@
|
|
|
1
|
+
import { createContext, useContext, useEffect, useState, useRef, useMemo, useCallback, useId } from 'react';
|
|
2
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
3
|
+
import clsx from 'clsx';
|
|
4
|
+
import { twMerge } from 'tailwind-merge';
|
|
5
|
+
import { X, Menu, PanelLeftOpen, PanelLeftClose } from 'lucide-react';
|
|
6
|
+
|
|
7
|
+
// src/context/cbox-id.tsx
|
|
8
|
+
var CboxIdContext = createContext(null);
|
|
9
|
+
function CboxIdProvider({
|
|
10
|
+
value,
|
|
11
|
+
children
|
|
12
|
+
}) {
|
|
13
|
+
return /* @__PURE__ */ jsx(CboxIdContext.Provider, { value, children });
|
|
14
|
+
}
|
|
15
|
+
function useCboxId() {
|
|
16
|
+
const ctx = useContext(CboxIdContext);
|
|
17
|
+
if (ctx === null) {
|
|
18
|
+
throw new Error(
|
|
19
|
+
"@cboxdk/cbox-ui: useCboxId() must be called inside <CboxIdProvider>. Wrap your app root and populate it from the id-client SDK (or, in id itself, from Inertia shared props)."
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
return ctx;
|
|
23
|
+
}
|
|
24
|
+
function useCboxIdOptional() {
|
|
25
|
+
return useContext(CboxIdContext);
|
|
26
|
+
}
|
|
27
|
+
function cn(...inputs) {
|
|
28
|
+
return twMerge(clsx(inputs));
|
|
29
|
+
}
|
|
30
|
+
function AppContent({ children, className, ...props }) {
|
|
31
|
+
return /* @__PURE__ */ jsx(
|
|
32
|
+
"main",
|
|
33
|
+
{
|
|
34
|
+
...props,
|
|
35
|
+
className: cn(
|
|
36
|
+
"canvas-gradient relative flex h-svh flex-1 flex-col overflow-y-auto",
|
|
37
|
+
className
|
|
38
|
+
),
|
|
39
|
+
children
|
|
40
|
+
}
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
function AppShell({ children }) {
|
|
44
|
+
return /* @__PURE__ */ jsx("div", { className: "flex h-svh w-full bg-canvas text-foreground", children });
|
|
45
|
+
}
|
|
46
|
+
function CboxBrand({
|
|
47
|
+
appKey,
|
|
48
|
+
submark,
|
|
49
|
+
assetBase = "/brand",
|
|
50
|
+
tone = "dark",
|
|
51
|
+
collapsed = false,
|
|
52
|
+
className
|
|
53
|
+
}) {
|
|
54
|
+
if (collapsed) {
|
|
55
|
+
return /* @__PURE__ */ jsx(
|
|
56
|
+
"img",
|
|
57
|
+
{
|
|
58
|
+
src: `${assetBase}/cbox-icon.png`,
|
|
59
|
+
srcSet: `${assetBase}/cbox-icon.png 1x, ${assetBase}/cbox-icon@2x.png 2x`,
|
|
60
|
+
alt: "Cbox",
|
|
61
|
+
width: 36,
|
|
62
|
+
height: 36,
|
|
63
|
+
className: cn("h-9 w-9 rounded-md", className)
|
|
64
|
+
}
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
const wordmark = tone === "light" ? `${assetBase}/cbox-logo-white.png` : `${assetBase}/cbox-logo.png`;
|
|
68
|
+
const wordmarkRetina = tone === "light" ? `${assetBase}/cbox-logo-white@2x.png` : `${assetBase}/cbox-logo@2x.png`;
|
|
69
|
+
const submarkText = submark === void 0 ? appKey : submark;
|
|
70
|
+
return /* @__PURE__ */ jsxs(
|
|
71
|
+
"div",
|
|
72
|
+
{
|
|
73
|
+
className: cn("inline-flex items-center gap-2", className),
|
|
74
|
+
"aria-label": `Cbox \xB7 ${appKey}`,
|
|
75
|
+
children: [
|
|
76
|
+
/* @__PURE__ */ jsx(
|
|
77
|
+
"img",
|
|
78
|
+
{
|
|
79
|
+
src: wordmark,
|
|
80
|
+
srcSet: `${wordmark} 1x, ${wordmarkRetina} 2x`,
|
|
81
|
+
alt: "Cbox",
|
|
82
|
+
height: 22,
|
|
83
|
+
className: "h-[22px] w-auto select-none",
|
|
84
|
+
draggable: false
|
|
85
|
+
}
|
|
86
|
+
),
|
|
87
|
+
submarkText !== null && submarkText !== "" ? /* @__PURE__ */ jsx(
|
|
88
|
+
"span",
|
|
89
|
+
{
|
|
90
|
+
className: cn(
|
|
91
|
+
"text-mono text-[10px] font-semibold tracking-[0.22em] uppercase",
|
|
92
|
+
tone === "light" ? "text-white/60" : "text-muted-foreground/80"
|
|
93
|
+
),
|
|
94
|
+
children: submarkText
|
|
95
|
+
}
|
|
96
|
+
) : null
|
|
97
|
+
]
|
|
98
|
+
}
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
function CboxAppSidebar({
|
|
102
|
+
appKey,
|
|
103
|
+
brandSubmark,
|
|
104
|
+
assetBase,
|
|
105
|
+
brandHref = "/",
|
|
106
|
+
sections,
|
|
107
|
+
currentPath,
|
|
108
|
+
LinkComponent,
|
|
109
|
+
collapsed = false,
|
|
110
|
+
className
|
|
111
|
+
}) {
|
|
112
|
+
return /* @__PURE__ */ jsxs(
|
|
113
|
+
"aside",
|
|
114
|
+
{
|
|
115
|
+
"data-slot": "cbox-app-sidebar",
|
|
116
|
+
"data-collapsed": collapsed ? "true" : "false",
|
|
117
|
+
className: cn(
|
|
118
|
+
"hidden h-svh shrink-0 flex-col border-r border-sidebar-border bg-sidebar transition-[width] duration-150 ease-out lg:flex",
|
|
119
|
+
collapsed ? "w-[60px]" : "w-[240px]",
|
|
120
|
+
className
|
|
121
|
+
),
|
|
122
|
+
children: [
|
|
123
|
+
/* @__PURE__ */ jsx(
|
|
124
|
+
LinkComponent,
|
|
125
|
+
{
|
|
126
|
+
href: brandHref,
|
|
127
|
+
"aria-label": "Home",
|
|
128
|
+
className: cn(
|
|
129
|
+
"flex h-14 shrink-0 items-center border-b border-sidebar-border/70",
|
|
130
|
+
collapsed ? "justify-center px-0" : "px-4"
|
|
131
|
+
),
|
|
132
|
+
children: /* @__PURE__ */ jsx(
|
|
133
|
+
CboxBrand,
|
|
134
|
+
{
|
|
135
|
+
appKey,
|
|
136
|
+
submark: brandSubmark,
|
|
137
|
+
assetBase,
|
|
138
|
+
collapsed
|
|
139
|
+
}
|
|
140
|
+
)
|
|
141
|
+
}
|
|
142
|
+
),
|
|
143
|
+
/* @__PURE__ */ jsx(
|
|
144
|
+
"nav",
|
|
145
|
+
{
|
|
146
|
+
className: cn(
|
|
147
|
+
"flex-1 overflow-y-auto pt-5 pb-3",
|
|
148
|
+
collapsed ? "space-y-3 px-2" : "space-y-5 px-3"
|
|
149
|
+
),
|
|
150
|
+
children: sections.map((section) => /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
|
|
151
|
+
collapsed ? null : /* @__PURE__ */ jsx("p", { className: "text-eyebrow px-3 pb-1", children: section.label }),
|
|
152
|
+
/* @__PURE__ */ jsx("ul", { className: "space-y-0.5", children: section.items.map((item) => {
|
|
153
|
+
const active = item.matchPrefix === true ? currentPath === item.href || currentPath.startsWith(item.href + "/") : currentPath === item.href;
|
|
154
|
+
const Icon = item.icon;
|
|
155
|
+
return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
|
|
156
|
+
LinkComponent,
|
|
157
|
+
{
|
|
158
|
+
href: item.href,
|
|
159
|
+
prefetch: true,
|
|
160
|
+
"aria-label": collapsed ? item.label : void 0,
|
|
161
|
+
title: collapsed ? item.label : void 0,
|
|
162
|
+
className: cn(
|
|
163
|
+
collapsed ? "group mx-auto flex h-9 w-9 items-center justify-center rounded-md transition-colors" : "group flex items-center gap-2.5 rounded-md px-3 py-1.5 text-[13px] transition-colors",
|
|
164
|
+
active ? "bg-sidebar-accent font-semibold text-sidebar-accent-foreground" : "text-sidebar-foreground/80 hover:bg-sidebar-accent hover:text-foreground"
|
|
165
|
+
),
|
|
166
|
+
children: [
|
|
167
|
+
/* @__PURE__ */ jsx(
|
|
168
|
+
Icon,
|
|
169
|
+
{
|
|
170
|
+
className: cn(
|
|
171
|
+
"h-4 w-4 shrink-0",
|
|
172
|
+
active ? "text-foreground" : "text-sidebar-muted"
|
|
173
|
+
),
|
|
174
|
+
strokeWidth: 1.7
|
|
175
|
+
}
|
|
176
|
+
),
|
|
177
|
+
collapsed ? null : /* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: item.label })
|
|
178
|
+
]
|
|
179
|
+
}
|
|
180
|
+
) }, item.label);
|
|
181
|
+
}) })
|
|
182
|
+
] }, section.label))
|
|
183
|
+
}
|
|
184
|
+
)
|
|
185
|
+
]
|
|
186
|
+
}
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
function CboxMobileNavDrawer({
|
|
190
|
+
open,
|
|
191
|
+
onClose,
|
|
192
|
+
appKey,
|
|
193
|
+
brandSubmark,
|
|
194
|
+
assetBase,
|
|
195
|
+
brandHref = "/",
|
|
196
|
+
sections,
|
|
197
|
+
currentPath,
|
|
198
|
+
LinkComponent
|
|
199
|
+
}) {
|
|
200
|
+
useEffect(() => {
|
|
201
|
+
if (!open) {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
const onKey = (e) => {
|
|
205
|
+
if (e.key === "Escape") {
|
|
206
|
+
onClose();
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
document.addEventListener("keydown", onKey);
|
|
210
|
+
const prevOverflow = document.body.style.overflow;
|
|
211
|
+
document.body.style.overflow = "hidden";
|
|
212
|
+
return () => {
|
|
213
|
+
document.removeEventListener("keydown", onKey);
|
|
214
|
+
document.body.style.overflow = prevOverflow;
|
|
215
|
+
};
|
|
216
|
+
}, [open, onClose]);
|
|
217
|
+
return /* @__PURE__ */ jsxs(
|
|
218
|
+
"div",
|
|
219
|
+
{
|
|
220
|
+
className: cn(
|
|
221
|
+
"fixed inset-0 z-40 lg:hidden",
|
|
222
|
+
open ? "" : "pointer-events-none"
|
|
223
|
+
),
|
|
224
|
+
"aria-hidden": open ? "false" : "true",
|
|
225
|
+
children: [
|
|
226
|
+
/* @__PURE__ */ jsx(
|
|
227
|
+
"div",
|
|
228
|
+
{
|
|
229
|
+
onClick: onClose,
|
|
230
|
+
className: cn(
|
|
231
|
+
"absolute inset-0 bg-black/40 transition-opacity duration-200",
|
|
232
|
+
open ? "opacity-100" : "opacity-0"
|
|
233
|
+
)
|
|
234
|
+
}
|
|
235
|
+
),
|
|
236
|
+
/* @__PURE__ */ jsxs(
|
|
237
|
+
"aside",
|
|
238
|
+
{
|
|
239
|
+
role: "dialog",
|
|
240
|
+
"aria-modal": "true",
|
|
241
|
+
"aria-label": "Navigation",
|
|
242
|
+
className: cn(
|
|
243
|
+
"absolute inset-y-0 left-0 flex w-[280px] max-w-[80vw] flex-col border-r border-sidebar-border bg-sidebar shadow-xl transition-transform duration-200 ease-out",
|
|
244
|
+
open ? "translate-x-0" : "-translate-x-full"
|
|
245
|
+
),
|
|
246
|
+
children: [
|
|
247
|
+
/* @__PURE__ */ jsxs("div", { className: "flex h-14 shrink-0 items-center justify-between gap-2 border-b border-sidebar-border/70 px-4", children: [
|
|
248
|
+
/* @__PURE__ */ jsx(
|
|
249
|
+
LinkComponent,
|
|
250
|
+
{
|
|
251
|
+
href: brandHref,
|
|
252
|
+
"aria-label": "Home",
|
|
253
|
+
className: "flex items-center",
|
|
254
|
+
children: /* @__PURE__ */ jsx(
|
|
255
|
+
CboxBrand,
|
|
256
|
+
{
|
|
257
|
+
appKey,
|
|
258
|
+
submark: brandSubmark,
|
|
259
|
+
assetBase,
|
|
260
|
+
collapsed: false
|
|
261
|
+
}
|
|
262
|
+
)
|
|
263
|
+
}
|
|
264
|
+
),
|
|
265
|
+
/* @__PURE__ */ jsx(
|
|
266
|
+
"button",
|
|
267
|
+
{
|
|
268
|
+
type: "button",
|
|
269
|
+
onClick: onClose,
|
|
270
|
+
"aria-label": "Close navigation",
|
|
271
|
+
className: "inline-flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-secondary hover:text-foreground",
|
|
272
|
+
children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4", strokeWidth: 1.8 })
|
|
273
|
+
}
|
|
274
|
+
)
|
|
275
|
+
] }),
|
|
276
|
+
/* @__PURE__ */ jsx("nav", { className: "flex-1 space-y-5 overflow-y-auto px-3 pt-5 pb-3", children: sections.map((section) => /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
|
|
277
|
+
/* @__PURE__ */ jsx("p", { className: "text-eyebrow px-3 pb-1", children: section.label }),
|
|
278
|
+
/* @__PURE__ */ jsx("ul", { className: "space-y-0.5", children: section.items.map((item) => {
|
|
279
|
+
const active = item.matchPrefix === true ? currentPath === item.href || currentPath.startsWith(
|
|
280
|
+
item.href + "/"
|
|
281
|
+
) : currentPath === item.href;
|
|
282
|
+
const Icon = item.icon;
|
|
283
|
+
return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
|
|
284
|
+
LinkComponent,
|
|
285
|
+
{
|
|
286
|
+
href: item.href,
|
|
287
|
+
prefetch: true,
|
|
288
|
+
className: cn(
|
|
289
|
+
"group flex items-center gap-2.5 rounded-md px-3 py-2 text-[14px] transition-colors",
|
|
290
|
+
active ? "bg-sidebar-accent font-semibold text-sidebar-accent-foreground" : "text-sidebar-foreground/80 hover:bg-sidebar-accent hover:text-foreground"
|
|
291
|
+
),
|
|
292
|
+
children: [
|
|
293
|
+
/* @__PURE__ */ jsx(
|
|
294
|
+
Icon,
|
|
295
|
+
{
|
|
296
|
+
className: cn(
|
|
297
|
+
"h-4 w-4 shrink-0",
|
|
298
|
+
active ? "text-foreground" : "text-sidebar-muted"
|
|
299
|
+
),
|
|
300
|
+
strokeWidth: 1.7
|
|
301
|
+
}
|
|
302
|
+
),
|
|
303
|
+
/* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: item.label })
|
|
304
|
+
]
|
|
305
|
+
}
|
|
306
|
+
) }, item.label);
|
|
307
|
+
}) })
|
|
308
|
+
] }, section.label)) })
|
|
309
|
+
]
|
|
310
|
+
}
|
|
311
|
+
)
|
|
312
|
+
]
|
|
313
|
+
}
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
function AppSwitcher({ className, onLockedAppClick }) {
|
|
317
|
+
const cboxId = useCboxIdOptional();
|
|
318
|
+
const [open, setOpen] = useState(false);
|
|
319
|
+
const [query, setQuery] = useState("");
|
|
320
|
+
const [activeIndex, setActiveIndex] = useState(0);
|
|
321
|
+
const containerRef = useRef(null);
|
|
322
|
+
const searchRef = useRef(null);
|
|
323
|
+
useEffect(() => {
|
|
324
|
+
if (open) {
|
|
325
|
+
setQuery("");
|
|
326
|
+
setActiveIndex(0);
|
|
327
|
+
const id = requestAnimationFrame(() => searchRef.current?.focus());
|
|
328
|
+
return () => cancelAnimationFrame(id);
|
|
329
|
+
}
|
|
330
|
+
}, [open]);
|
|
331
|
+
useEffect(() => {
|
|
332
|
+
if (!open) {
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
function onPointerDown(event) {
|
|
336
|
+
if (containerRef.current !== null && !containerRef.current.contains(event.target)) {
|
|
337
|
+
setOpen(false);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
function onKey(event) {
|
|
341
|
+
if (event.key === "Escape") {
|
|
342
|
+
setOpen(false);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
window.addEventListener("pointerdown", onPointerDown);
|
|
346
|
+
window.addEventListener("keydown", onKey);
|
|
347
|
+
return () => {
|
|
348
|
+
window.removeEventListener("pointerdown", onPointerDown);
|
|
349
|
+
window.removeEventListener("keydown", onKey);
|
|
350
|
+
};
|
|
351
|
+
}, [open]);
|
|
352
|
+
if (cboxId === null || cboxId.apps.length === 0) {
|
|
353
|
+
return null;
|
|
354
|
+
}
|
|
355
|
+
const apps = cboxId.apps;
|
|
356
|
+
const orgSlug = cboxId.currentOrganization?.slug;
|
|
357
|
+
const currentApp = apps.find((a) => a.current === true) ?? null;
|
|
358
|
+
const { entitled, locked, navigableHrefs } = useMemo(() => {
|
|
359
|
+
const q = query.trim().toLowerCase();
|
|
360
|
+
const matches = (app) => {
|
|
361
|
+
if (q === "") {
|
|
362
|
+
return true;
|
|
363
|
+
}
|
|
364
|
+
return app.name.toLowerCase().includes(q) || app.key.toLowerCase().includes(q) || (app.description?.toLowerCase().includes(q) ?? false);
|
|
365
|
+
};
|
|
366
|
+
const entitledList = [];
|
|
367
|
+
const lockedList = [];
|
|
368
|
+
for (const app of apps) {
|
|
369
|
+
if (!matches(app)) {
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
const isEntitled = app.entitled !== false && app.comingSoon !== true;
|
|
373
|
+
(isEntitled ? entitledList : lockedList).push(app);
|
|
374
|
+
}
|
|
375
|
+
const navigable = [];
|
|
376
|
+
for (const app of entitledList) {
|
|
377
|
+
if (app.current !== true) {
|
|
378
|
+
navigable.push({ app, isLocked: false });
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
for (const app of lockedList) {
|
|
382
|
+
if (app.comingSoon !== true) {
|
|
383
|
+
navigable.push({ app, isLocked: true });
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
return {
|
|
387
|
+
entitled: entitledList,
|
|
388
|
+
locked: lockedList,
|
|
389
|
+
navigableHrefs: navigable
|
|
390
|
+
};
|
|
391
|
+
}, [apps, query]);
|
|
392
|
+
const totalMatches = entitled.length + locked.length;
|
|
393
|
+
useEffect(() => {
|
|
394
|
+
if (activeIndex >= navigableHrefs.length) {
|
|
395
|
+
setActiveIndex(0);
|
|
396
|
+
}
|
|
397
|
+
}, [navigableHrefs.length, activeIndex]);
|
|
398
|
+
const triggerMonogram = currentApp?.monogram ?? currentApp?.name.slice(0, 1).toUpperCase() ?? "\u2B1B";
|
|
399
|
+
const triggerName = currentApp?.name ?? "Apps";
|
|
400
|
+
const triggerAccent = currentApp?.accentColor;
|
|
401
|
+
function onSearchKeyDown(event) {
|
|
402
|
+
if (event.key === "ArrowDown") {
|
|
403
|
+
event.preventDefault();
|
|
404
|
+
if (navigableHrefs.length === 0) {
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
setActiveIndex((i) => (i + 1) % navigableHrefs.length);
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
if (event.key === "ArrowUp") {
|
|
411
|
+
event.preventDefault();
|
|
412
|
+
if (navigableHrefs.length === 0) {
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
setActiveIndex((i) => (i - 1 + navigableHrefs.length) % navigableHrefs.length);
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
if (event.key === "Enter") {
|
|
419
|
+
event.preventDefault();
|
|
420
|
+
const target = navigableHrefs[activeIndex];
|
|
421
|
+
if (target === void 0) {
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
if (target.isLocked) {
|
|
425
|
+
onLockedAppClick?.(target.app);
|
|
426
|
+
setOpen(false);
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
const href = orgSlug !== void 0 && target.app.current !== true ? buildOrgUrl(target.app.url, orgSlug) : target.app.url;
|
|
430
|
+
window.location.href = href;
|
|
431
|
+
setOpen(false);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: cn("relative", className), children: [
|
|
435
|
+
/* @__PURE__ */ jsxs(
|
|
436
|
+
"button",
|
|
437
|
+
{
|
|
438
|
+
type: "button",
|
|
439
|
+
"aria-haspopup": "true",
|
|
440
|
+
"aria-expanded": open,
|
|
441
|
+
onClick: () => setOpen((v) => !v),
|
|
442
|
+
className: "flex items-center gap-2 rounded-md border border-border bg-card px-2.5 py-1.5 text-sm font-medium text-foreground hover:bg-secondary",
|
|
443
|
+
children: [
|
|
444
|
+
/* @__PURE__ */ jsx(
|
|
445
|
+
"span",
|
|
446
|
+
{
|
|
447
|
+
style: triggerAccent !== void 0 ? {
|
|
448
|
+
backgroundColor: triggerAccent,
|
|
449
|
+
color: "#fff",
|
|
450
|
+
borderColor: triggerAccent
|
|
451
|
+
} : void 0,
|
|
452
|
+
className: "flex h-5 w-5 shrink-0 items-center justify-center rounded-sm border border-border bg-secondary text-[10px] font-bold",
|
|
453
|
+
children: currentApp?.iconUrl ? /* @__PURE__ */ jsx(
|
|
454
|
+
"img",
|
|
455
|
+
{
|
|
456
|
+
src: currentApp.iconUrl,
|
|
457
|
+
alt: "",
|
|
458
|
+
className: "h-3.5 w-3.5",
|
|
459
|
+
loading: "eager"
|
|
460
|
+
}
|
|
461
|
+
) : triggerMonogram
|
|
462
|
+
}
|
|
463
|
+
),
|
|
464
|
+
/* @__PURE__ */ jsx("span", { className: "max-w-[140px] truncate", children: triggerName }),
|
|
465
|
+
/* @__PURE__ */ jsx(Chevron, {})
|
|
466
|
+
]
|
|
467
|
+
}
|
|
468
|
+
),
|
|
469
|
+
open ? /* @__PURE__ */ jsxs(
|
|
470
|
+
"div",
|
|
471
|
+
{
|
|
472
|
+
role: "dialog",
|
|
473
|
+
"aria-label": "Switch app",
|
|
474
|
+
className: "absolute left-0 top-full z-50 mt-1 w-[min(22rem,calc(100vw-1.5rem))] overflow-hidden rounded-lg border border-border bg-popover shadow-lg",
|
|
475
|
+
children: [
|
|
476
|
+
/* @__PURE__ */ jsx("div", { className: "border-b border-border/70 px-3 pt-2.5 pb-1.5", children: /* @__PURE__ */ jsx("p", { className: "text-mono text-[10px] font-semibold tracking-[0.18em] text-muted-foreground uppercase", children: "Switch app" }) }),
|
|
477
|
+
/* @__PURE__ */ jsx("div", { className: "border-b border-border/70 px-2 py-1.5", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 rounded-md px-2 py-1 focus-within:bg-secondary", children: [
|
|
478
|
+
/* @__PURE__ */ jsx(SearchIcon, {}),
|
|
479
|
+
/* @__PURE__ */ jsx(
|
|
480
|
+
"input",
|
|
481
|
+
{
|
|
482
|
+
ref: searchRef,
|
|
483
|
+
type: "text",
|
|
484
|
+
value: query,
|
|
485
|
+
onChange: (e) => setQuery(e.target.value),
|
|
486
|
+
onKeyDown: onSearchKeyDown,
|
|
487
|
+
placeholder: "Filter apps\u2026",
|
|
488
|
+
"aria-label": "Filter apps",
|
|
489
|
+
autoComplete: "off",
|
|
490
|
+
spellCheck: false,
|
|
491
|
+
className: "w-full bg-transparent text-sm text-foreground placeholder:text-muted-foreground/60 focus:outline-none"
|
|
492
|
+
}
|
|
493
|
+
)
|
|
494
|
+
] }) }),
|
|
495
|
+
/* @__PURE__ */ jsxs("div", { className: "max-h-[60vh] overflow-y-auto py-1", children: [
|
|
496
|
+
totalMatches === 0 ? /* @__PURE__ */ jsxs("p", { className: "px-3 py-6 text-center text-[12px] text-muted-foreground", children: [
|
|
497
|
+
"No apps match",
|
|
498
|
+
" ",
|
|
499
|
+
/* @__PURE__ */ jsxs("span", { className: "text-mono text-foreground", children: [
|
|
500
|
+
"\u201C",
|
|
501
|
+
query,
|
|
502
|
+
"\u201D"
|
|
503
|
+
] })
|
|
504
|
+
] }) : null,
|
|
505
|
+
entitled.length > 0 ? /* @__PURE__ */ jsx("ul", { className: "space-y-0.5 p-1", children: entitled.map((app) => {
|
|
506
|
+
const navIndex = findNavIndex(navigableHrefs, app);
|
|
507
|
+
return /* @__PURE__ */ jsx(
|
|
508
|
+
AppRow,
|
|
509
|
+
{
|
|
510
|
+
app,
|
|
511
|
+
orgSlug,
|
|
512
|
+
highlighted: navIndex === activeIndex,
|
|
513
|
+
onMouseEnter: () => {
|
|
514
|
+
if (navIndex !== -1) {
|
|
515
|
+
setActiveIndex(navIndex);
|
|
516
|
+
}
|
|
517
|
+
},
|
|
518
|
+
onLockedAppClick,
|
|
519
|
+
onNavigate: () => setOpen(false)
|
|
520
|
+
},
|
|
521
|
+
app.key
|
|
522
|
+
);
|
|
523
|
+
}) }) : null,
|
|
524
|
+
locked.length > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
525
|
+
entitled.length > 0 ? /* @__PURE__ */ jsx("div", { className: "my-1 border-t border-border/50" }) : null,
|
|
526
|
+
/* @__PURE__ */ jsx("p", { className: "text-mono px-3 py-1.5 text-[10px] font-semibold tracking-[0.18em] text-muted-foreground/70 uppercase", children: "Available" }),
|
|
527
|
+
/* @__PURE__ */ jsx("ul", { className: "space-y-0.5 p-1 pt-0", children: locked.map((app) => {
|
|
528
|
+
const navIndex = findNavIndex(navigableHrefs, app);
|
|
529
|
+
return /* @__PURE__ */ jsx(
|
|
530
|
+
AppRow,
|
|
531
|
+
{
|
|
532
|
+
app,
|
|
533
|
+
orgSlug,
|
|
534
|
+
highlighted: navIndex === activeIndex,
|
|
535
|
+
onMouseEnter: () => {
|
|
536
|
+
if (navIndex !== -1) {
|
|
537
|
+
setActiveIndex(navIndex);
|
|
538
|
+
}
|
|
539
|
+
},
|
|
540
|
+
onLockedAppClick,
|
|
541
|
+
onNavigate: () => setOpen(false)
|
|
542
|
+
},
|
|
543
|
+
app.key
|
|
544
|
+
);
|
|
545
|
+
}) })
|
|
546
|
+
] }) : null
|
|
547
|
+
] })
|
|
548
|
+
]
|
|
549
|
+
}
|
|
550
|
+
) : null
|
|
551
|
+
] });
|
|
552
|
+
}
|
|
553
|
+
function findNavIndex(navigable, app) {
|
|
554
|
+
return navigable.findIndex((n) => n.app.key === app.key);
|
|
555
|
+
}
|
|
556
|
+
function AppRow({
|
|
557
|
+
app,
|
|
558
|
+
orgSlug,
|
|
559
|
+
highlighted,
|
|
560
|
+
onMouseEnter,
|
|
561
|
+
onLockedAppClick,
|
|
562
|
+
onNavigate
|
|
563
|
+
}) {
|
|
564
|
+
const isComingSoon = app.comingSoon === true;
|
|
565
|
+
const isLocked = app.entitled === false || isComingSoon;
|
|
566
|
+
const monogram = app.monogram ?? app.name.slice(0, 1).toUpperCase();
|
|
567
|
+
const accent = app.accentColor;
|
|
568
|
+
const tileStyle = accent !== void 0 && app.iconUrl === void 0 ? { backgroundColor: accent, color: "#fff", borderColor: accent } : void 0;
|
|
569
|
+
const href = orgSlug !== void 0 && app.current !== true ? buildOrgUrl(app.url, orgSlug) : app.url;
|
|
570
|
+
const tile = /* @__PURE__ */ jsx(
|
|
571
|
+
"div",
|
|
572
|
+
{
|
|
573
|
+
style: tileStyle,
|
|
574
|
+
className: cn(
|
|
575
|
+
"flex h-9 w-9 shrink-0 items-center justify-center rounded-md border border-border bg-card text-[13px] font-semibold text-foreground",
|
|
576
|
+
app.current && "ring-2 ring-accent-edge ring-offset-1 ring-offset-popover",
|
|
577
|
+
isLocked && "opacity-60 grayscale"
|
|
578
|
+
),
|
|
579
|
+
children: app.iconUrl ? /* @__PURE__ */ jsx("img", { src: app.iconUrl, alt: "", className: "h-5 w-5", loading: "lazy" }) : /* @__PURE__ */ jsx("span", { children: monogram })
|
|
580
|
+
}
|
|
581
|
+
);
|
|
582
|
+
const body = /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col leading-tight", children: [
|
|
583
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
584
|
+
/* @__PURE__ */ jsx(
|
|
585
|
+
"span",
|
|
586
|
+
{
|
|
587
|
+
className: cn(
|
|
588
|
+
"truncate text-sm font-medium text-foreground",
|
|
589
|
+
isLocked && "text-muted-foreground"
|
|
590
|
+
),
|
|
591
|
+
children: app.name
|
|
592
|
+
}
|
|
593
|
+
),
|
|
594
|
+
app.current ? /* @__PURE__ */ jsx("span", { className: "text-mono shrink-0 rounded-sm border border-accent-edge/40 bg-accent-soft px-1 text-[9px] font-semibold tracking-[0.14em] text-accent uppercase", children: "current" }) : null
|
|
595
|
+
] }),
|
|
596
|
+
app.description ? /* @__PURE__ */ jsx(
|
|
597
|
+
"span",
|
|
598
|
+
{
|
|
599
|
+
className: cn(
|
|
600
|
+
"truncate text-[11px]",
|
|
601
|
+
isLocked ? "text-muted-foreground/70" : "text-muted-foreground"
|
|
602
|
+
),
|
|
603
|
+
children: app.description
|
|
604
|
+
}
|
|
605
|
+
) : null
|
|
606
|
+
] });
|
|
607
|
+
const trailing = isComingSoon ? /* @__PURE__ */ jsx("span", { className: "text-mono shrink-0 rounded-sm border border-border bg-secondary px-1.5 py-0.5 text-[9px] font-semibold tracking-[0.14em] text-muted-foreground uppercase", children: "soon" }) : isLocked ? /* @__PURE__ */ jsxs("span", { className: "flex shrink-0 items-center gap-1 text-[10px] font-medium text-muted-foreground", children: [
|
|
608
|
+
/* @__PURE__ */ jsx(Lock, {}),
|
|
609
|
+
"Add"
|
|
610
|
+
] }) : null;
|
|
611
|
+
if (isComingSoon) {
|
|
612
|
+
return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
|
|
613
|
+
"div",
|
|
614
|
+
{
|
|
615
|
+
"aria-disabled": true,
|
|
616
|
+
className: "flex w-full cursor-not-allowed items-center gap-3 rounded-md px-2.5 py-2",
|
|
617
|
+
title: `${app.name} \u2014 coming soon`,
|
|
618
|
+
children: [
|
|
619
|
+
tile,
|
|
620
|
+
body,
|
|
621
|
+
trailing
|
|
622
|
+
]
|
|
623
|
+
}
|
|
624
|
+
) });
|
|
625
|
+
}
|
|
626
|
+
if (isLocked) {
|
|
627
|
+
return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
|
|
628
|
+
"button",
|
|
629
|
+
{
|
|
630
|
+
type: "button",
|
|
631
|
+
onMouseEnter,
|
|
632
|
+
onClick: () => {
|
|
633
|
+
onLockedAppClick?.(app);
|
|
634
|
+
onNavigate();
|
|
635
|
+
},
|
|
636
|
+
className: cn(
|
|
637
|
+
"group flex w-full items-center gap-3 rounded-md px-2.5 py-2 text-left transition-colors",
|
|
638
|
+
highlighted && "bg-secondary",
|
|
639
|
+
onLockedAppClick !== void 0 ? "hover:bg-secondary" : "cursor-default"
|
|
640
|
+
),
|
|
641
|
+
title: `Add ${app.name} to your plan`,
|
|
642
|
+
children: [
|
|
643
|
+
tile,
|
|
644
|
+
body,
|
|
645
|
+
trailing
|
|
646
|
+
]
|
|
647
|
+
}
|
|
648
|
+
) });
|
|
649
|
+
}
|
|
650
|
+
if (app.current === true) {
|
|
651
|
+
return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
|
|
652
|
+
"div",
|
|
653
|
+
{
|
|
654
|
+
onMouseEnter,
|
|
655
|
+
className: "flex w-full items-center gap-3 rounded-md bg-accent-soft px-2.5 py-2",
|
|
656
|
+
children: [
|
|
657
|
+
tile,
|
|
658
|
+
body,
|
|
659
|
+
trailing
|
|
660
|
+
]
|
|
661
|
+
}
|
|
662
|
+
) });
|
|
663
|
+
}
|
|
664
|
+
return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
|
|
665
|
+
"a",
|
|
666
|
+
{
|
|
667
|
+
href,
|
|
668
|
+
onMouseEnter,
|
|
669
|
+
onClick: onNavigate,
|
|
670
|
+
className: cn(
|
|
671
|
+
"group flex w-full items-center gap-3 rounded-md px-2.5 py-2 transition-colors hover:bg-secondary",
|
|
672
|
+
highlighted && "bg-secondary"
|
|
673
|
+
),
|
|
674
|
+
children: [
|
|
675
|
+
tile,
|
|
676
|
+
body,
|
|
677
|
+
trailing
|
|
678
|
+
]
|
|
679
|
+
}
|
|
680
|
+
) });
|
|
681
|
+
}
|
|
682
|
+
function buildOrgUrl(url, orgSlug) {
|
|
683
|
+
if (url === "" || orgSlug === "") {
|
|
684
|
+
return url;
|
|
685
|
+
}
|
|
686
|
+
try {
|
|
687
|
+
const u = new URL(url);
|
|
688
|
+
u.pathname = `/org/${encodeURIComponent(orgSlug)}`;
|
|
689
|
+
return u.toString();
|
|
690
|
+
} catch {
|
|
691
|
+
return `${url.replace(/\/$/, "")}/org/${encodeURIComponent(orgSlug)}`;
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
function Chevron() {
|
|
695
|
+
return /* @__PURE__ */ jsx(
|
|
696
|
+
"svg",
|
|
697
|
+
{
|
|
698
|
+
width: "10",
|
|
699
|
+
height: "10",
|
|
700
|
+
viewBox: "0 0 10 10",
|
|
701
|
+
fill: "none",
|
|
702
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
703
|
+
"aria-hidden": true,
|
|
704
|
+
children: /* @__PURE__ */ jsx(
|
|
705
|
+
"path",
|
|
706
|
+
{
|
|
707
|
+
d: "M2 4l3 3 3-3",
|
|
708
|
+
stroke: "currentColor",
|
|
709
|
+
strokeWidth: "1.4",
|
|
710
|
+
strokeLinecap: "round",
|
|
711
|
+
strokeLinejoin: "round"
|
|
712
|
+
}
|
|
713
|
+
)
|
|
714
|
+
}
|
|
715
|
+
);
|
|
716
|
+
}
|
|
717
|
+
function SearchIcon() {
|
|
718
|
+
return /* @__PURE__ */ jsxs(
|
|
719
|
+
"svg",
|
|
720
|
+
{
|
|
721
|
+
width: "12",
|
|
722
|
+
height: "12",
|
|
723
|
+
viewBox: "0 0 12 12",
|
|
724
|
+
fill: "none",
|
|
725
|
+
stroke: "currentColor",
|
|
726
|
+
strokeWidth: "1.4",
|
|
727
|
+
strokeLinecap: "round",
|
|
728
|
+
strokeLinejoin: "round",
|
|
729
|
+
"aria-hidden": true,
|
|
730
|
+
className: "shrink-0 text-muted-foreground",
|
|
731
|
+
children: [
|
|
732
|
+
/* @__PURE__ */ jsx("circle", { cx: "5.25", cy: "5.25", r: "3.25" }),
|
|
733
|
+
/* @__PURE__ */ jsx("path", { d: "M8 8l2 2" })
|
|
734
|
+
]
|
|
735
|
+
}
|
|
736
|
+
);
|
|
737
|
+
}
|
|
738
|
+
function Lock() {
|
|
739
|
+
return /* @__PURE__ */ jsxs(
|
|
740
|
+
"svg",
|
|
741
|
+
{
|
|
742
|
+
width: "10",
|
|
743
|
+
height: "10",
|
|
744
|
+
viewBox: "0 0 12 12",
|
|
745
|
+
fill: "none",
|
|
746
|
+
stroke: "currentColor",
|
|
747
|
+
strokeWidth: "1.6",
|
|
748
|
+
strokeLinecap: "round",
|
|
749
|
+
strokeLinejoin: "round",
|
|
750
|
+
"aria-hidden": true,
|
|
751
|
+
children: [
|
|
752
|
+
/* @__PURE__ */ jsx("rect", { x: "2.5", y: "5", width: "7", height: "5", rx: "1" }),
|
|
753
|
+
/* @__PURE__ */ jsx("path", { d: "M4 5V3.5a2 2 0 1 1 4 0V5" })
|
|
754
|
+
]
|
|
755
|
+
}
|
|
756
|
+
);
|
|
757
|
+
}
|
|
758
|
+
function EmptyState({
|
|
759
|
+
icon: Icon,
|
|
760
|
+
title,
|
|
761
|
+
description,
|
|
762
|
+
action,
|
|
763
|
+
className
|
|
764
|
+
}) {
|
|
765
|
+
return /* @__PURE__ */ jsxs(
|
|
766
|
+
"div",
|
|
767
|
+
{
|
|
768
|
+
className: cn(
|
|
769
|
+
"flex flex-col items-center justify-center gap-3 px-6 py-12 text-center",
|
|
770
|
+
className
|
|
771
|
+
),
|
|
772
|
+
children: [
|
|
773
|
+
Icon ? /* @__PURE__ */ jsx("div", { className: "flex h-10 w-10 items-center justify-center rounded-md bg-secondary text-muted-foreground", children: /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4", strokeWidth: 1.6 }) }) : null,
|
|
774
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
|
|
775
|
+
/* @__PURE__ */ jsx("h3", { className: "text-base font-semibold text-foreground", children: title }),
|
|
776
|
+
description ? /* @__PURE__ */ jsx("p", { className: "max-w-sm text-sm text-muted-foreground", children: description }) : null
|
|
777
|
+
] }),
|
|
778
|
+
action ? /* @__PURE__ */ jsx("div", { className: "pt-1", children: action }) : null
|
|
779
|
+
]
|
|
780
|
+
}
|
|
781
|
+
);
|
|
782
|
+
}
|
|
783
|
+
var toneStyles = {
|
|
784
|
+
neutral: "border-border bg-secondary text-secondary-foreground",
|
|
785
|
+
info: "border-info/20 bg-info-soft text-info",
|
|
786
|
+
success: "border-success/20 bg-success-soft text-success",
|
|
787
|
+
warning: "border-warning/20 bg-warning-soft text-warning",
|
|
788
|
+
destructive: "border-destructive/20 bg-destructive-soft text-destructive",
|
|
789
|
+
muted: "border-border bg-card text-muted-foreground"
|
|
790
|
+
};
|
|
791
|
+
var dotStyles = {
|
|
792
|
+
neutral: "bg-muted-foreground",
|
|
793
|
+
info: "bg-info",
|
|
794
|
+
success: "bg-success",
|
|
795
|
+
warning: "bg-warning",
|
|
796
|
+
destructive: "bg-destructive",
|
|
797
|
+
muted: "bg-muted-foreground/60"
|
|
798
|
+
};
|
|
799
|
+
function StatusPill({
|
|
800
|
+
children,
|
|
801
|
+
tone = "neutral",
|
|
802
|
+
className,
|
|
803
|
+
dot = false
|
|
804
|
+
}) {
|
|
805
|
+
return /* @__PURE__ */ jsxs(
|
|
806
|
+
"span",
|
|
807
|
+
{
|
|
808
|
+
className: cn(
|
|
809
|
+
"inline-flex items-center gap-1.5 rounded-full border px-2 py-0.5 text-[11px] leading-none font-medium",
|
|
810
|
+
toneStyles[tone],
|
|
811
|
+
className
|
|
812
|
+
),
|
|
813
|
+
children: [
|
|
814
|
+
dot ? /* @__PURE__ */ jsx(
|
|
815
|
+
"span",
|
|
816
|
+
{
|
|
817
|
+
className: cn("h-1.5 w-1.5 rounded-full", dotStyles[tone])
|
|
818
|
+
}
|
|
819
|
+
) : null,
|
|
820
|
+
children
|
|
821
|
+
]
|
|
822
|
+
}
|
|
823
|
+
);
|
|
824
|
+
}
|
|
825
|
+
function SurfaceModal({
|
|
826
|
+
open,
|
|
827
|
+
onClose,
|
|
828
|
+
title,
|
|
829
|
+
eyebrow,
|
|
830
|
+
headerActions,
|
|
831
|
+
footer,
|
|
832
|
+
size = "wide",
|
|
833
|
+
children
|
|
834
|
+
}) {
|
|
835
|
+
const dialogRef = useRef(null);
|
|
836
|
+
const previousFocusRef = useRef(null);
|
|
837
|
+
useEffect(() => {
|
|
838
|
+
if (!open) return;
|
|
839
|
+
const original = document.body.style.overflow;
|
|
840
|
+
const gutter = window.innerWidth - document.documentElement.clientWidth;
|
|
841
|
+
document.body.style.overflow = "hidden";
|
|
842
|
+
if (gutter > 0) {
|
|
843
|
+
document.body.style.paddingRight = `${gutter}px`;
|
|
844
|
+
}
|
|
845
|
+
return () => {
|
|
846
|
+
document.body.style.overflow = original;
|
|
847
|
+
document.body.style.paddingRight = "";
|
|
848
|
+
};
|
|
849
|
+
}, [open]);
|
|
850
|
+
useEffect(() => {
|
|
851
|
+
if (!open) return;
|
|
852
|
+
previousFocusRef.current = document.activeElement;
|
|
853
|
+
const onKey = (e) => {
|
|
854
|
+
if (e.key === "Escape") {
|
|
855
|
+
e.preventDefault();
|
|
856
|
+
onClose();
|
|
857
|
+
return;
|
|
858
|
+
}
|
|
859
|
+
if (e.key === "Tab" && dialogRef.current !== null) {
|
|
860
|
+
const focusable = dialogRef.current.querySelectorAll(
|
|
861
|
+
'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])'
|
|
862
|
+
);
|
|
863
|
+
if (focusable.length === 0) return;
|
|
864
|
+
const first = focusable[0];
|
|
865
|
+
const last = focusable[focusable.length - 1];
|
|
866
|
+
if (first === void 0 || last === void 0) return;
|
|
867
|
+
if (e.shiftKey && document.activeElement === first) {
|
|
868
|
+
e.preventDefault();
|
|
869
|
+
last.focus();
|
|
870
|
+
} else if (!e.shiftKey && document.activeElement === last) {
|
|
871
|
+
e.preventDefault();
|
|
872
|
+
first.focus();
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
};
|
|
876
|
+
document.addEventListener("keydown", onKey);
|
|
877
|
+
const id = window.setTimeout(() => {
|
|
878
|
+
const focusable = dialogRef.current?.querySelector(
|
|
879
|
+
'input, button, [tabindex]:not([tabindex="-1"])'
|
|
880
|
+
);
|
|
881
|
+
focusable?.focus();
|
|
882
|
+
}, 50);
|
|
883
|
+
return () => {
|
|
884
|
+
document.removeEventListener("keydown", onKey);
|
|
885
|
+
window.clearTimeout(id);
|
|
886
|
+
previousFocusRef.current?.focus?.();
|
|
887
|
+
};
|
|
888
|
+
}, [open, onClose]);
|
|
889
|
+
const onBackdrop = useCallback(
|
|
890
|
+
(e) => {
|
|
891
|
+
if (e.target === e.currentTarget) onClose();
|
|
892
|
+
},
|
|
893
|
+
[onClose]
|
|
894
|
+
);
|
|
895
|
+
if (!open) return null;
|
|
896
|
+
return /* @__PURE__ */ jsx(
|
|
897
|
+
"div",
|
|
898
|
+
{
|
|
899
|
+
role: "presentation",
|
|
900
|
+
onMouseDown: onBackdrop,
|
|
901
|
+
className: cn(
|
|
902
|
+
"fixed inset-0 z-50 flex animate-in fade-in duration-150",
|
|
903
|
+
size === "spotlight" ? "items-start justify-center pt-[10vh]" : "items-center justify-center px-4 py-[5vh]",
|
|
904
|
+
"bg-foreground/40 backdrop-blur-sm"
|
|
905
|
+
),
|
|
906
|
+
children: /* @__PURE__ */ jsxs(
|
|
907
|
+
"div",
|
|
908
|
+
{
|
|
909
|
+
ref: dialogRef,
|
|
910
|
+
role: "dialog",
|
|
911
|
+
"aria-modal": "true",
|
|
912
|
+
"aria-label": typeof title === "string" ? title : void 0,
|
|
913
|
+
className: cn(
|
|
914
|
+
"flex max-h-full w-full flex-col overflow-hidden rounded-lg border border-border bg-background text-foreground shadow-2xl",
|
|
915
|
+
"animate-in zoom-in-95 slide-in-from-bottom-2 duration-150",
|
|
916
|
+
size === "spotlight" ? "max-w-[640px]" : "max-w-[960px]"
|
|
917
|
+
),
|
|
918
|
+
children: [
|
|
919
|
+
/* @__PURE__ */ jsxs("header", { className: "flex shrink-0 items-start gap-3 border-b border-border/70 bg-background/80 px-5 py-4 backdrop-blur-md", children: [
|
|
920
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
921
|
+
eyebrow !== void 0 ? /* @__PURE__ */ jsx("p", { className: "text-mono text-[10px] font-semibold tracking-[0.18em] uppercase text-muted-foreground", children: eyebrow }) : null,
|
|
922
|
+
/* @__PURE__ */ jsx("h2", { className: "text-base font-semibold tracking-tight text-foreground", children: title })
|
|
923
|
+
] }),
|
|
924
|
+
headerActions !== void 0 ? /* @__PURE__ */ jsx("div", { className: "flex shrink-0 items-center gap-2", children: headerActions }) : null,
|
|
925
|
+
/* @__PURE__ */ jsx(
|
|
926
|
+
"button",
|
|
927
|
+
{
|
|
928
|
+
type: "button",
|
|
929
|
+
onClick: onClose,
|
|
930
|
+
className: "ml-1 inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-secondary hover:text-foreground",
|
|
931
|
+
"aria-label": "Close",
|
|
932
|
+
children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4", strokeWidth: 1.8 })
|
|
933
|
+
}
|
|
934
|
+
)
|
|
935
|
+
] }),
|
|
936
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1 overflow-y-auto", children }),
|
|
937
|
+
footer !== void 0 ? /* @__PURE__ */ jsx("footer", { className: "flex shrink-0 items-center justify-between gap-3 border-t border-border/70 bg-background/80 px-5 py-3 text-xs text-muted-foreground backdrop-blur-md", children: footer }) : null
|
|
938
|
+
]
|
|
939
|
+
}
|
|
940
|
+
)
|
|
941
|
+
}
|
|
942
|
+
);
|
|
943
|
+
}
|
|
944
|
+
var TONES = {
|
|
945
|
+
info: "info",
|
|
946
|
+
warning: "warning",
|
|
947
|
+
critical: "destructive"
|
|
948
|
+
};
|
|
949
|
+
function InboxSurface({ open, onClose }) {
|
|
950
|
+
const transport = useTransport();
|
|
951
|
+
const cboxId = useCboxIdOptional();
|
|
952
|
+
const [inbox, setInbox] = useState(null);
|
|
953
|
+
const [loading, setLoading] = useState(false);
|
|
954
|
+
const [error, setError] = useState(null);
|
|
955
|
+
const [filter, setFilter] = useState("all");
|
|
956
|
+
const userId = cboxId?.user.id ?? null;
|
|
957
|
+
const baseUrl = transport.urls.notifications ?? null;
|
|
958
|
+
useEffect(() => {
|
|
959
|
+
if (!open || userId === null || baseUrl === null) return;
|
|
960
|
+
let cancelled = false;
|
|
961
|
+
const load = async () => {
|
|
962
|
+
setLoading(true);
|
|
963
|
+
setError(null);
|
|
964
|
+
try {
|
|
965
|
+
const headers = await authHeaders(transport, "notifications");
|
|
966
|
+
const response = await fetch(
|
|
967
|
+
`${baseUrl}/api/v1/recipients/${userId}/inbox?limit=100`,
|
|
968
|
+
{ headers, credentials: "include" }
|
|
969
|
+
);
|
|
970
|
+
if (!response.ok) {
|
|
971
|
+
throw new Error(`HTTP ${response.status}`);
|
|
972
|
+
}
|
|
973
|
+
const body = await response.json();
|
|
974
|
+
if (!cancelled) setInbox(body);
|
|
975
|
+
} catch (e) {
|
|
976
|
+
if (!cancelled) {
|
|
977
|
+
setError(e instanceof Error ? e.message : "Load failed");
|
|
978
|
+
}
|
|
979
|
+
} finally {
|
|
980
|
+
if (!cancelled) setLoading(false);
|
|
981
|
+
}
|
|
982
|
+
};
|
|
983
|
+
void load();
|
|
984
|
+
return () => {
|
|
985
|
+
cancelled = true;
|
|
986
|
+
};
|
|
987
|
+
}, [open, userId, baseUrl, transport]);
|
|
988
|
+
const markRead = async (id) => {
|
|
989
|
+
if (inbox === null || userId === null || baseUrl === null) return;
|
|
990
|
+
const prev = inbox;
|
|
991
|
+
setInbox({
|
|
992
|
+
...inbox,
|
|
993
|
+
items: inbox.items.map(
|
|
994
|
+
(it) => it.id === id ? { ...it, read_at: (/* @__PURE__ */ new Date()).toISOString() } : it
|
|
995
|
+
),
|
|
996
|
+
unread_count: Math.max(0, inbox.unread_count - 1)
|
|
997
|
+
});
|
|
998
|
+
try {
|
|
999
|
+
const headers = await authHeaders(transport, "notifications");
|
|
1000
|
+
const csrf = transport.csrfToken?.();
|
|
1001
|
+
if (csrf !== void 0 && csrf !== "") {
|
|
1002
|
+
headers["X-CSRF-TOKEN"] = csrf;
|
|
1003
|
+
headers["X-Requested-With"] = "XMLHttpRequest";
|
|
1004
|
+
}
|
|
1005
|
+
const response = await fetch(
|
|
1006
|
+
`${baseUrl}/api/v1/recipients/${userId}/notifications/${id}/read`,
|
|
1007
|
+
{
|
|
1008
|
+
method: "POST",
|
|
1009
|
+
headers,
|
|
1010
|
+
credentials: "include"
|
|
1011
|
+
}
|
|
1012
|
+
);
|
|
1013
|
+
if (!response.ok) {
|
|
1014
|
+
throw new Error(`HTTP ${response.status}`);
|
|
1015
|
+
}
|
|
1016
|
+
} catch {
|
|
1017
|
+
setInbox(prev);
|
|
1018
|
+
}
|
|
1019
|
+
};
|
|
1020
|
+
const items = inbox?.items ?? [];
|
|
1021
|
+
const filtered = filter === "unread" ? items.filter((i) => i.read_at === null) : items;
|
|
1022
|
+
const unread = inbox?.unread_count ?? 0;
|
|
1023
|
+
return /* @__PURE__ */ jsx(
|
|
1024
|
+
SurfaceModal,
|
|
1025
|
+
{
|
|
1026
|
+
open,
|
|
1027
|
+
onClose,
|
|
1028
|
+
eyebrow: "Notifications",
|
|
1029
|
+
title: unread > 0 ? `Inbox \xB7 ${unread} unread` : "Inbox \xB7 all caught up",
|
|
1030
|
+
headerActions: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
1031
|
+
/* @__PURE__ */ jsx(
|
|
1032
|
+
FilterTab,
|
|
1033
|
+
{
|
|
1034
|
+
active: filter === "all",
|
|
1035
|
+
onClick: () => setFilter("all"),
|
|
1036
|
+
children: "All"
|
|
1037
|
+
}
|
|
1038
|
+
),
|
|
1039
|
+
/* @__PURE__ */ jsx(
|
|
1040
|
+
FilterTab,
|
|
1041
|
+
{
|
|
1042
|
+
active: filter === "unread",
|
|
1043
|
+
onClick: () => setFilter("unread"),
|
|
1044
|
+
children: "Unread"
|
|
1045
|
+
}
|
|
1046
|
+
)
|
|
1047
|
+
] }),
|
|
1048
|
+
footer: inbox !== null && baseUrl !== null ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1049
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
1050
|
+
"Showing ",
|
|
1051
|
+
filtered.length,
|
|
1052
|
+
" of ",
|
|
1053
|
+
items.length,
|
|
1054
|
+
"."
|
|
1055
|
+
] }),
|
|
1056
|
+
/* @__PURE__ */ jsx(
|
|
1057
|
+
"a",
|
|
1058
|
+
{
|
|
1059
|
+
href: `${baseUrl}/inbox`,
|
|
1060
|
+
target: "_blank",
|
|
1061
|
+
rel: "noreferrer",
|
|
1062
|
+
className: "font-medium text-foreground hover:underline",
|
|
1063
|
+
children: "Open in new tab \u2197"
|
|
1064
|
+
}
|
|
1065
|
+
)
|
|
1066
|
+
] }) : /* @__PURE__ */ jsx("span", { children: "\u2014" }),
|
|
1067
|
+
children: error !== null ? /* @__PURE__ */ jsxs("div", { className: "px-5 py-8 text-center", children: [
|
|
1068
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-destructive", children: "Couldn't load inbox" }),
|
|
1069
|
+
/* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: error })
|
|
1070
|
+
] }) : loading && inbox === null ? /* @__PURE__ */ jsx("div", { className: "px-5 py-12 text-center text-sm text-muted-foreground", children: "Loading\u2026" }) : filtered.length === 0 ? /* @__PURE__ */ jsx("div", { className: "px-5 py-12", children: /* @__PURE__ */ jsx(
|
|
1071
|
+
EmptyState,
|
|
1072
|
+
{
|
|
1073
|
+
title: filter === "unread" ? "No unread notifications" : "No notifications yet",
|
|
1074
|
+
description: "When a Cbox app sends you something \u2014 invitation accepted, billing event, security alert \u2014 it lands here."
|
|
1075
|
+
}
|
|
1076
|
+
) }) : /* @__PURE__ */ jsx("ul", { className: "divide-y divide-border/70", children: filtered.map((item) => {
|
|
1077
|
+
const data = item.data ?? {};
|
|
1078
|
+
const ctaUrl = data.cta_url;
|
|
1079
|
+
const InnerWrapper = ({
|
|
1080
|
+
children
|
|
1081
|
+
}) => ctaUrl !== void 0 && ctaUrl !== "" ? /* @__PURE__ */ jsx(
|
|
1082
|
+
"a",
|
|
1083
|
+
{
|
|
1084
|
+
href: ctaUrl,
|
|
1085
|
+
className: "block transition-colors hover:bg-secondary/40",
|
|
1086
|
+
children
|
|
1087
|
+
}
|
|
1088
|
+
) : /* @__PURE__ */ jsx("div", { children });
|
|
1089
|
+
return /* @__PURE__ */ jsx(
|
|
1090
|
+
"li",
|
|
1091
|
+
{
|
|
1092
|
+
className: item.read_at === null ? "bg-secondary/20" : "",
|
|
1093
|
+
children: /* @__PURE__ */ jsx(InnerWrapper, { children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3 px-5 py-3", children: [
|
|
1094
|
+
/* @__PURE__ */ jsx(
|
|
1095
|
+
StatusPill,
|
|
1096
|
+
{
|
|
1097
|
+
tone: TONES[item.severity],
|
|
1098
|
+
dot: true,
|
|
1099
|
+
children: item.severity
|
|
1100
|
+
}
|
|
1101
|
+
),
|
|
1102
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
1103
|
+
/* @__PURE__ */ jsx("p", { className: "truncate text-sm font-medium text-foreground", children: data.title ?? item.event_type }),
|
|
1104
|
+
(data.body ?? "") !== "" ? /* @__PURE__ */ jsx("p", { className: "mt-0.5 line-clamp-2 text-xs text-muted-foreground", children: data.body }) : null,
|
|
1105
|
+
/* @__PURE__ */ jsxs("p", { className: "text-mono mt-1 text-[10px] text-muted-foreground", children: [
|
|
1106
|
+
item.event_type,
|
|
1107
|
+
" \xB7 ",
|
|
1108
|
+
new Date(
|
|
1109
|
+
item.created_at
|
|
1110
|
+
).toLocaleString()
|
|
1111
|
+
] })
|
|
1112
|
+
] }),
|
|
1113
|
+
item.read_at === null ? /* @__PURE__ */ jsx(
|
|
1114
|
+
"button",
|
|
1115
|
+
{
|
|
1116
|
+
type: "button",
|
|
1117
|
+
onClick: (e) => {
|
|
1118
|
+
e.preventDefault();
|
|
1119
|
+
e.stopPropagation();
|
|
1120
|
+
void markRead(item.id);
|
|
1121
|
+
},
|
|
1122
|
+
className: "shrink-0 rounded-md border border-border bg-card px-2 py-1 text-xs font-medium text-foreground transition-colors hover:border-border-strong",
|
|
1123
|
+
"aria-label": "Mark as read",
|
|
1124
|
+
children: "Mark read"
|
|
1125
|
+
}
|
|
1126
|
+
) : null
|
|
1127
|
+
] }) })
|
|
1128
|
+
},
|
|
1129
|
+
item.id
|
|
1130
|
+
);
|
|
1131
|
+
}) })
|
|
1132
|
+
}
|
|
1133
|
+
);
|
|
1134
|
+
}
|
|
1135
|
+
async function authHeaders(transport, audience) {
|
|
1136
|
+
const headers = {
|
|
1137
|
+
Accept: "application/json"
|
|
1138
|
+
};
|
|
1139
|
+
try {
|
|
1140
|
+
const { token } = await transport.getToken(audience);
|
|
1141
|
+
if (token !== "") {
|
|
1142
|
+
headers.Authorization = `Bearer ${token}`;
|
|
1143
|
+
}
|
|
1144
|
+
} catch {
|
|
1145
|
+
}
|
|
1146
|
+
return headers;
|
|
1147
|
+
}
|
|
1148
|
+
function FilterTab({
|
|
1149
|
+
active,
|
|
1150
|
+
onClick,
|
|
1151
|
+
children
|
|
1152
|
+
}) {
|
|
1153
|
+
return /* @__PURE__ */ jsx(
|
|
1154
|
+
"button",
|
|
1155
|
+
{
|
|
1156
|
+
type: "button",
|
|
1157
|
+
onClick,
|
|
1158
|
+
className: active ? "rounded-md bg-secondary px-2 py-1 text-xs font-medium text-foreground" : "rounded-md px-2 py-1 text-xs font-medium text-muted-foreground hover:bg-secondary/60 hover:text-foreground",
|
|
1159
|
+
"aria-pressed": active,
|
|
1160
|
+
children
|
|
1161
|
+
}
|
|
1162
|
+
);
|
|
1163
|
+
}
|
|
1164
|
+
var TransportContext = createContext(null);
|
|
1165
|
+
var SurfaceContext = createContext(null);
|
|
1166
|
+
function CboxSurfaces({
|
|
1167
|
+
transport,
|
|
1168
|
+
surfaces,
|
|
1169
|
+
config,
|
|
1170
|
+
children
|
|
1171
|
+
}) {
|
|
1172
|
+
const enabled = useMemo(() => new Set(surfaces), [surfaces]);
|
|
1173
|
+
const [active, setActive] = useState(null);
|
|
1174
|
+
const open = useCallback(
|
|
1175
|
+
(id, args) => {
|
|
1176
|
+
if (!enabled.has(id)) {
|
|
1177
|
+
console.warn(
|
|
1178
|
+
`[cbox-ui] surface "${id}" is not enabled in this host. Add it to <CboxSurfaces surfaces={[\u2026]}>.`
|
|
1179
|
+
);
|
|
1180
|
+
return;
|
|
1181
|
+
}
|
|
1182
|
+
setActive({ id, args });
|
|
1183
|
+
},
|
|
1184
|
+
[enabled]
|
|
1185
|
+
);
|
|
1186
|
+
const close = useCallback(() => setActive(null), []);
|
|
1187
|
+
const controller = useMemo(
|
|
1188
|
+
() => ({
|
|
1189
|
+
activeSurface: active?.id ?? null,
|
|
1190
|
+
open,
|
|
1191
|
+
close
|
|
1192
|
+
}),
|
|
1193
|
+
[active, open, close]
|
|
1194
|
+
);
|
|
1195
|
+
return /* @__PURE__ */ jsx(TransportContext.Provider, { value: transport, children: /* @__PURE__ */ jsxs(SurfaceContext.Provider, { value: controller, children: [
|
|
1196
|
+
children,
|
|
1197
|
+
/* @__PURE__ */ jsx(
|
|
1198
|
+
InboxSurface,
|
|
1199
|
+
{
|
|
1200
|
+
open: active?.id === "inbox",
|
|
1201
|
+
onClose: close
|
|
1202
|
+
}
|
|
1203
|
+
)
|
|
1204
|
+
] }) });
|
|
1205
|
+
}
|
|
1206
|
+
function useCboxSurfaces() {
|
|
1207
|
+
const ctx = useContext(SurfaceContext);
|
|
1208
|
+
if (ctx === null) {
|
|
1209
|
+
throw new Error(
|
|
1210
|
+
"[cbox-ui] useCboxSurfaces must be used inside <CboxSurfaces>. Did the host forget to mount it?"
|
|
1211
|
+
);
|
|
1212
|
+
}
|
|
1213
|
+
return ctx;
|
|
1214
|
+
}
|
|
1215
|
+
function useCboxSurfacesOptional() {
|
|
1216
|
+
return useContext(SurfaceContext);
|
|
1217
|
+
}
|
|
1218
|
+
function useTransport() {
|
|
1219
|
+
const t = useContext(TransportContext);
|
|
1220
|
+
if (t === null) {
|
|
1221
|
+
throw new Error(
|
|
1222
|
+
"[cbox-ui] useTransport must be used inside <CboxSurfaces>."
|
|
1223
|
+
);
|
|
1224
|
+
}
|
|
1225
|
+
return t;
|
|
1226
|
+
}
|
|
1227
|
+
function NotificationsBell({ className }) {
|
|
1228
|
+
const ctx = useCboxIdOptional();
|
|
1229
|
+
const surfaces = useCboxSurfacesOptional();
|
|
1230
|
+
const [open, setOpen] = useState(false);
|
|
1231
|
+
const ref = useRef(null);
|
|
1232
|
+
useEffect(() => {
|
|
1233
|
+
if (!open) return;
|
|
1234
|
+
const onDocClick = (e) => {
|
|
1235
|
+
const target = e.target;
|
|
1236
|
+
if (target instanceof Node && ref.current !== null && !ref.current.contains(target)) {
|
|
1237
|
+
setOpen(false);
|
|
1238
|
+
}
|
|
1239
|
+
};
|
|
1240
|
+
const onEsc = (e) => {
|
|
1241
|
+
if (e.key === "Escape") setOpen(false);
|
|
1242
|
+
};
|
|
1243
|
+
document.addEventListener("mousedown", onDocClick);
|
|
1244
|
+
document.addEventListener("keydown", onEsc);
|
|
1245
|
+
return () => {
|
|
1246
|
+
document.removeEventListener("mousedown", onDocClick);
|
|
1247
|
+
document.removeEventListener("keydown", onEsc);
|
|
1248
|
+
};
|
|
1249
|
+
}, [open]);
|
|
1250
|
+
const notificationsData = ctx === null ? null : ctx.notifications ?? null;
|
|
1251
|
+
if (ctx === null) {
|
|
1252
|
+
return null;
|
|
1253
|
+
}
|
|
1254
|
+
if (notificationsData === null) {
|
|
1255
|
+
return /* @__PURE__ */ jsx("div", { className: cn("relative", className), children: /* @__PURE__ */ jsx(
|
|
1256
|
+
"button",
|
|
1257
|
+
{
|
|
1258
|
+
type: "button",
|
|
1259
|
+
"aria-label": "Notifications",
|
|
1260
|
+
disabled: true,
|
|
1261
|
+
className: "relative flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground opacity-60 cursor-default",
|
|
1262
|
+
children: /* @__PURE__ */ jsx(Bell, {})
|
|
1263
|
+
}
|
|
1264
|
+
) });
|
|
1265
|
+
}
|
|
1266
|
+
const { unreadCount, inboxUrl, previewItems, markReadUrl } = notificationsData;
|
|
1267
|
+
const display = unreadCount > 99 ? "99+" : unreadCount.toString();
|
|
1268
|
+
return /* @__PURE__ */ jsxs("div", { ref, className: cn("relative", className), children: [
|
|
1269
|
+
/* @__PURE__ */ jsxs(
|
|
1270
|
+
"button",
|
|
1271
|
+
{
|
|
1272
|
+
type: "button",
|
|
1273
|
+
onClick: () => setOpen((v) => !v),
|
|
1274
|
+
"aria-label": unreadCount > 0 ? `${unreadCount} unread notifications` : "Notifications",
|
|
1275
|
+
"aria-expanded": open,
|
|
1276
|
+
"aria-haspopup": "menu",
|
|
1277
|
+
className: "relative flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-secondary hover:text-foreground data-[open=true]:bg-secondary data-[open=true]:text-foreground",
|
|
1278
|
+
"data-open": open ? "true" : "false",
|
|
1279
|
+
children: [
|
|
1280
|
+
/* @__PURE__ */ jsx(Bell, {}),
|
|
1281
|
+
unreadCount > 0 ? /* @__PURE__ */ jsx("span", { className: "absolute -right-0.5 -top-0.5 flex min-w-[16px] items-center justify-center rounded-full bg-destructive px-1 text-[9px] font-semibold leading-4 text-destructive-foreground", children: display }) : null
|
|
1282
|
+
]
|
|
1283
|
+
}
|
|
1284
|
+
),
|
|
1285
|
+
open ? /* @__PURE__ */ jsxs(
|
|
1286
|
+
"div",
|
|
1287
|
+
{
|
|
1288
|
+
role: "menu",
|
|
1289
|
+
className: "absolute right-0 z-30 mt-2 w-[360px] origin-top-right rounded-md border border-border bg-popover text-popover-foreground shadow-lg",
|
|
1290
|
+
children: [
|
|
1291
|
+
/* @__PURE__ */ jsxs("header", { className: "flex items-center justify-between border-b border-border/70 px-3 py-2", children: [
|
|
1292
|
+
/* @__PURE__ */ jsx("span", { className: "text-mono text-[10px] font-semibold tracking-[0.18em] uppercase text-muted-foreground", children: "Notifications" }),
|
|
1293
|
+
unreadCount > 0 ? /* @__PURE__ */ jsxs("span", { className: "text-mono text-[10px] tabular-nums text-muted-foreground", children: [
|
|
1294
|
+
unreadCount,
|
|
1295
|
+
" unread"
|
|
1296
|
+
] }) : null
|
|
1297
|
+
] }),
|
|
1298
|
+
/* @__PURE__ */ jsx("div", { className: "max-h-[420px] overflow-y-auto", children: previewItems === void 0 || previewItems.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "px-3 py-8 text-center", children: [
|
|
1299
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-foreground", children: "All caught up" }),
|
|
1300
|
+
/* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: "No recent notifications." })
|
|
1301
|
+
] }) : /* @__PURE__ */ jsx("ul", { className: "divide-y divide-border/70", children: previewItems.map((item) => /* @__PURE__ */ jsx(
|
|
1302
|
+
PreviewRow,
|
|
1303
|
+
{
|
|
1304
|
+
item,
|
|
1305
|
+
markReadUrl,
|
|
1306
|
+
onMarkedRead: () => setOpen(false)
|
|
1307
|
+
},
|
|
1308
|
+
item.id
|
|
1309
|
+
)) }) }),
|
|
1310
|
+
surfaces !== null ? /* @__PURE__ */ jsx(
|
|
1311
|
+
"button",
|
|
1312
|
+
{
|
|
1313
|
+
type: "button",
|
|
1314
|
+
onClick: () => {
|
|
1315
|
+
setOpen(false);
|
|
1316
|
+
surfaces.open("inbox");
|
|
1317
|
+
},
|
|
1318
|
+
className: "flex w-full items-center justify-center border-t border-border/70 px-3 py-2 text-xs font-medium text-foreground transition-colors hover:bg-secondary",
|
|
1319
|
+
children: "See all notifications \u2192"
|
|
1320
|
+
}
|
|
1321
|
+
) : /* @__PURE__ */ jsx(
|
|
1322
|
+
"a",
|
|
1323
|
+
{
|
|
1324
|
+
href: inboxUrl,
|
|
1325
|
+
className: "flex items-center justify-center border-t border-border/70 px-3 py-2 text-xs font-medium text-foreground transition-colors hover:bg-secondary",
|
|
1326
|
+
children: "See all notifications \u2192"
|
|
1327
|
+
}
|
|
1328
|
+
)
|
|
1329
|
+
]
|
|
1330
|
+
}
|
|
1331
|
+
) : null
|
|
1332
|
+
] });
|
|
1333
|
+
}
|
|
1334
|
+
function PreviewRow({
|
|
1335
|
+
item,
|
|
1336
|
+
markReadUrl,
|
|
1337
|
+
onMarkedRead
|
|
1338
|
+
}) {
|
|
1339
|
+
const tone = item.severity === "critical" ? "bg-destructive" : item.severity === "warning" ? "bg-warning" : "bg-info";
|
|
1340
|
+
const ts = (() => {
|
|
1341
|
+
try {
|
|
1342
|
+
return new Date(item.createdAt).toLocaleString();
|
|
1343
|
+
} catch {
|
|
1344
|
+
return item.createdAt;
|
|
1345
|
+
}
|
|
1346
|
+
})();
|
|
1347
|
+
const markRead = async (e) => {
|
|
1348
|
+
e.preventDefault();
|
|
1349
|
+
e.stopPropagation();
|
|
1350
|
+
if (markReadUrl === void 0 || markReadUrl === "") return;
|
|
1351
|
+
const url = markReadUrl.replace("{id}", item.id);
|
|
1352
|
+
const csrf = document.querySelector('meta[name="csrf-token"]')?.content ?? "";
|
|
1353
|
+
try {
|
|
1354
|
+
await fetch(url, {
|
|
1355
|
+
method: "POST",
|
|
1356
|
+
headers: {
|
|
1357
|
+
Accept: "application/json",
|
|
1358
|
+
"X-CSRF-TOKEN": csrf,
|
|
1359
|
+
"X-Requested-With": "XMLHttpRequest"
|
|
1360
|
+
}
|
|
1361
|
+
});
|
|
1362
|
+
} catch {
|
|
1363
|
+
}
|
|
1364
|
+
onMarkedRead();
|
|
1365
|
+
};
|
|
1366
|
+
const Wrapper = ({ children }) => item.ctaUrl !== null && item.ctaUrl !== void 0 && item.ctaUrl !== "" ? /* @__PURE__ */ jsx(
|
|
1367
|
+
"a",
|
|
1368
|
+
{
|
|
1369
|
+
href: item.ctaUrl,
|
|
1370
|
+
className: "block transition-colors hover:bg-secondary/50",
|
|
1371
|
+
children
|
|
1372
|
+
}
|
|
1373
|
+
) : /* @__PURE__ */ jsx("div", { children });
|
|
1374
|
+
return /* @__PURE__ */ jsx("li", { className: item.readAt === null ? "bg-secondary/20" : "", children: /* @__PURE__ */ jsx(Wrapper, { children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2 px-3 py-2.5", children: [
|
|
1375
|
+
/* @__PURE__ */ jsx(
|
|
1376
|
+
"span",
|
|
1377
|
+
{
|
|
1378
|
+
"aria-hidden": true,
|
|
1379
|
+
className: cn(
|
|
1380
|
+
"mt-1.5 h-1.5 w-1.5 shrink-0 rounded-full",
|
|
1381
|
+
tone
|
|
1382
|
+
)
|
|
1383
|
+
}
|
|
1384
|
+
),
|
|
1385
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
1386
|
+
/* @__PURE__ */ jsx("p", { className: "truncate text-[13px] font-medium text-foreground", children: item.title }),
|
|
1387
|
+
item.body !== void 0 && item.body !== null && item.body !== "" ? /* @__PURE__ */ jsx("p", { className: "mt-0.5 line-clamp-2 text-xs text-muted-foreground", children: item.body }) : null,
|
|
1388
|
+
/* @__PURE__ */ jsx("p", { className: "text-mono mt-1 text-[10px] text-muted-foreground", children: ts })
|
|
1389
|
+
] }),
|
|
1390
|
+
item.readAt === null ? /* @__PURE__ */ jsx(
|
|
1391
|
+
"button",
|
|
1392
|
+
{
|
|
1393
|
+
type: "button",
|
|
1394
|
+
onClick: markRead,
|
|
1395
|
+
className: "shrink-0 rounded-md px-2 py-1 text-[10px] font-medium text-muted-foreground transition-colors hover:bg-secondary hover:text-foreground",
|
|
1396
|
+
"aria-label": "Mark as read",
|
|
1397
|
+
title: "Mark as read",
|
|
1398
|
+
children: "\u2713"
|
|
1399
|
+
}
|
|
1400
|
+
) : null
|
|
1401
|
+
] }) }) });
|
|
1402
|
+
}
|
|
1403
|
+
function Bell() {
|
|
1404
|
+
return /* @__PURE__ */ jsxs(
|
|
1405
|
+
"svg",
|
|
1406
|
+
{
|
|
1407
|
+
width: "16",
|
|
1408
|
+
height: "16",
|
|
1409
|
+
viewBox: "0 0 16 16",
|
|
1410
|
+
fill: "none",
|
|
1411
|
+
"aria-hidden": true,
|
|
1412
|
+
children: [
|
|
1413
|
+
/* @__PURE__ */ jsx(
|
|
1414
|
+
"path",
|
|
1415
|
+
{
|
|
1416
|
+
d: "M4 6.5a4 4 0 1 1 8 0v2.4l1.2 2H2.8l1.2-2V6.5z",
|
|
1417
|
+
stroke: "currentColor",
|
|
1418
|
+
strokeWidth: "1.4",
|
|
1419
|
+
strokeLinejoin: "round"
|
|
1420
|
+
}
|
|
1421
|
+
),
|
|
1422
|
+
/* @__PURE__ */ jsx(
|
|
1423
|
+
"path",
|
|
1424
|
+
{
|
|
1425
|
+
d: "M6.5 12.5a1.5 1.5 0 0 0 3 0",
|
|
1426
|
+
stroke: "currentColor",
|
|
1427
|
+
strokeWidth: "1.4",
|
|
1428
|
+
strokeLinecap: "round"
|
|
1429
|
+
}
|
|
1430
|
+
)
|
|
1431
|
+
]
|
|
1432
|
+
}
|
|
1433
|
+
);
|
|
1434
|
+
}
|
|
1435
|
+
function SearchTrigger({ onOpen, className, label = "Search" }) {
|
|
1436
|
+
const [shortcut, setShortcut] = useState("\u2318K");
|
|
1437
|
+
useEffect(() => {
|
|
1438
|
+
if (typeof navigator === "undefined") return;
|
|
1439
|
+
setShortcut(/Mac/.test(navigator.platform) ? "\u2318K" : "Ctrl K");
|
|
1440
|
+
}, []);
|
|
1441
|
+
return /* @__PURE__ */ jsxs(
|
|
1442
|
+
"button",
|
|
1443
|
+
{
|
|
1444
|
+
type: "button",
|
|
1445
|
+
onClick: onOpen,
|
|
1446
|
+
className: cn(
|
|
1447
|
+
"hidden h-8 w-[180px] items-center gap-2 rounded-md border border-border bg-card pr-1.5 pl-2.5 text-xs text-muted-foreground transition-colors hover:border-border-strong hover:text-foreground sm:inline-flex",
|
|
1448
|
+
className
|
|
1449
|
+
),
|
|
1450
|
+
"aria-label": "Open command palette",
|
|
1451
|
+
children: [
|
|
1452
|
+
/* @__PURE__ */ jsx(SearchIcon2, {}),
|
|
1453
|
+
/* @__PURE__ */ jsx("span", { className: "flex-1 text-left", children: label }),
|
|
1454
|
+
/* @__PURE__ */ jsx("kbd", { className: "text-mono ml-1 rounded-sm border border-border bg-secondary px-1 py-0.5 text-[10px] font-medium tracking-tight text-muted-foreground", children: shortcut })
|
|
1455
|
+
]
|
|
1456
|
+
}
|
|
1457
|
+
);
|
|
1458
|
+
}
|
|
1459
|
+
function SearchIcon2() {
|
|
1460
|
+
return /* @__PURE__ */ jsxs(
|
|
1461
|
+
"svg",
|
|
1462
|
+
{
|
|
1463
|
+
width: "14",
|
|
1464
|
+
height: "14",
|
|
1465
|
+
viewBox: "0 0 24 24",
|
|
1466
|
+
fill: "none",
|
|
1467
|
+
stroke: "currentColor",
|
|
1468
|
+
strokeWidth: "1.8",
|
|
1469
|
+
strokeLinecap: "round",
|
|
1470
|
+
strokeLinejoin: "round",
|
|
1471
|
+
"aria-hidden": true,
|
|
1472
|
+
children: [
|
|
1473
|
+
/* @__PURE__ */ jsx("circle", { cx: "11", cy: "11", r: "7" }),
|
|
1474
|
+
/* @__PURE__ */ jsx("path", { d: "m21 21-4.3-4.3" })
|
|
1475
|
+
]
|
|
1476
|
+
}
|
|
1477
|
+
);
|
|
1478
|
+
}
|
|
1479
|
+
function UserMenu({ extras, className }) {
|
|
1480
|
+
const { user, currentOrganization, urls } = useCboxId();
|
|
1481
|
+
const [open, setOpen] = useState(false);
|
|
1482
|
+
const initials = user.name.split(/\s+/).filter(Boolean).slice(0, 2).map((n) => n[0]?.toUpperCase() ?? "").join("");
|
|
1483
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("relative", className), children: [
|
|
1484
|
+
/* @__PURE__ */ jsx(
|
|
1485
|
+
"button",
|
|
1486
|
+
{
|
|
1487
|
+
type: "button",
|
|
1488
|
+
onClick: () => setOpen((v) => !v),
|
|
1489
|
+
className: "flex h-9 items-center gap-2 rounded-md px-1.5 hover:bg-secondary",
|
|
1490
|
+
"aria-label": "Open user menu",
|
|
1491
|
+
children: user.avatar_url ? /* @__PURE__ */ jsx(
|
|
1492
|
+
"img",
|
|
1493
|
+
{
|
|
1494
|
+
src: user.avatar_url,
|
|
1495
|
+
alt: "",
|
|
1496
|
+
className: "h-7 w-7 rounded-full object-cover"
|
|
1497
|
+
}
|
|
1498
|
+
) : /* @__PURE__ */ jsx("span", { className: "flex h-7 w-7 items-center justify-center rounded-full bg-accent-soft text-[11px] font-semibold text-foreground", children: initials || "?" })
|
|
1499
|
+
}
|
|
1500
|
+
),
|
|
1501
|
+
open ? /* @__PURE__ */ jsxs("div", { className: "absolute right-0 top-full z-50 mt-1 w-64 rounded-md border border-border bg-popover p-1 shadow-md", children: [
|
|
1502
|
+
/* @__PURE__ */ jsxs("div", { className: "px-2 py-2", children: [
|
|
1503
|
+
/* @__PURE__ */ jsx("p", { className: "truncate text-sm font-medium text-foreground", children: user.name }),
|
|
1504
|
+
/* @__PURE__ */ jsx("p", { className: "truncate text-xs text-muted-foreground", children: user.email })
|
|
1505
|
+
] }),
|
|
1506
|
+
extras ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1507
|
+
/* @__PURE__ */ jsx("div", { className: "my-1 border-t border-border" }),
|
|
1508
|
+
extras
|
|
1509
|
+
] }) : null,
|
|
1510
|
+
/* @__PURE__ */ jsx("div", { className: "my-1 border-t border-border" }),
|
|
1511
|
+
/* @__PURE__ */ jsxs(
|
|
1512
|
+
"a",
|
|
1513
|
+
{
|
|
1514
|
+
href: urls.accountUrl,
|
|
1515
|
+
className: "flex items-center justify-between rounded-sm px-2 py-1.5 text-sm hover:bg-secondary",
|
|
1516
|
+
children: [
|
|
1517
|
+
/* @__PURE__ */ jsx("span", { children: "Manage account" }),
|
|
1518
|
+
/* @__PURE__ */ jsx(CrossAppArrow, {})
|
|
1519
|
+
]
|
|
1520
|
+
}
|
|
1521
|
+
),
|
|
1522
|
+
urls.orgSettingsUrl && currentOrganization ? /* @__PURE__ */ jsxs(
|
|
1523
|
+
"a",
|
|
1524
|
+
{
|
|
1525
|
+
href: urls.orgSettingsUrl,
|
|
1526
|
+
className: "flex items-center justify-between rounded-sm px-2 py-1.5 text-sm hover:bg-secondary",
|
|
1527
|
+
children: [
|
|
1528
|
+
/* @__PURE__ */ jsxs("span", { className: "truncate", children: [
|
|
1529
|
+
"Team & billing",
|
|
1530
|
+
/* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
|
|
1531
|
+
" \xB7 ",
|
|
1532
|
+
currentOrganization.name
|
|
1533
|
+
] })
|
|
1534
|
+
] }),
|
|
1535
|
+
/* @__PURE__ */ jsx(CrossAppArrow, {})
|
|
1536
|
+
]
|
|
1537
|
+
}
|
|
1538
|
+
) : null,
|
|
1539
|
+
/* @__PURE__ */ jsx("div", { className: "my-1 border-t border-border" }),
|
|
1540
|
+
/* @__PURE__ */ jsxs("form", { action: urls.signOutUrl, method: "POST", children: [
|
|
1541
|
+
/* @__PURE__ */ jsx(SignOutCsrf, {}),
|
|
1542
|
+
/* @__PURE__ */ jsx(
|
|
1543
|
+
"button",
|
|
1544
|
+
{
|
|
1545
|
+
type: "submit",
|
|
1546
|
+
className: "flex w-full items-center rounded-sm px-2 py-1.5 text-left text-sm hover:bg-secondary",
|
|
1547
|
+
children: "Sign out"
|
|
1548
|
+
}
|
|
1549
|
+
)
|
|
1550
|
+
] })
|
|
1551
|
+
] }) : null
|
|
1552
|
+
] });
|
|
1553
|
+
}
|
|
1554
|
+
function CrossAppArrow() {
|
|
1555
|
+
return /* @__PURE__ */ jsx(
|
|
1556
|
+
"svg",
|
|
1557
|
+
{
|
|
1558
|
+
width: "10",
|
|
1559
|
+
height: "10",
|
|
1560
|
+
viewBox: "0 0 10 10",
|
|
1561
|
+
fill: "none",
|
|
1562
|
+
"aria-hidden": true,
|
|
1563
|
+
children: /* @__PURE__ */ jsx(
|
|
1564
|
+
"path",
|
|
1565
|
+
{
|
|
1566
|
+
d: "M3 7l4-4M3 3h4v4",
|
|
1567
|
+
stroke: "currentColor",
|
|
1568
|
+
strokeWidth: "1.4",
|
|
1569
|
+
strokeLinecap: "round",
|
|
1570
|
+
strokeLinejoin: "round"
|
|
1571
|
+
}
|
|
1572
|
+
)
|
|
1573
|
+
}
|
|
1574
|
+
);
|
|
1575
|
+
}
|
|
1576
|
+
function SignOutCsrf() {
|
|
1577
|
+
if (typeof document === "undefined") return null;
|
|
1578
|
+
const csrf = document.querySelector('meta[name="csrf-token"]')?.content;
|
|
1579
|
+
if (!csrf) return null;
|
|
1580
|
+
return /* @__PURE__ */ jsx("input", { type: "hidden", name: "_token", value: csrf });
|
|
1581
|
+
}
|
|
1582
|
+
function CboxAppShell({
|
|
1583
|
+
appKey,
|
|
1584
|
+
brandSubmark,
|
|
1585
|
+
assetBase,
|
|
1586
|
+
brandHref,
|
|
1587
|
+
sections,
|
|
1588
|
+
currentPath,
|
|
1589
|
+
LinkComponent,
|
|
1590
|
+
sidebarCollapsed = false,
|
|
1591
|
+
onToggleSidebar,
|
|
1592
|
+
topbarCentre,
|
|
1593
|
+
onSearchOpen,
|
|
1594
|
+
topbarRightCluster,
|
|
1595
|
+
children
|
|
1596
|
+
}) {
|
|
1597
|
+
const ToggleIcon = sidebarCollapsed ? PanelLeftOpen : PanelLeftClose;
|
|
1598
|
+
const [mobileNavOpen, setMobileNavOpen] = useState(false);
|
|
1599
|
+
useEffect(() => {
|
|
1600
|
+
setMobileNavOpen(false);
|
|
1601
|
+
}, [currentPath]);
|
|
1602
|
+
return /* @__PURE__ */ jsxs(AppShell, { children: [
|
|
1603
|
+
/* @__PURE__ */ jsx(
|
|
1604
|
+
CboxAppSidebar,
|
|
1605
|
+
{
|
|
1606
|
+
appKey,
|
|
1607
|
+
brandSubmark,
|
|
1608
|
+
assetBase,
|
|
1609
|
+
brandHref,
|
|
1610
|
+
sections,
|
|
1611
|
+
currentPath,
|
|
1612
|
+
LinkComponent,
|
|
1613
|
+
collapsed: sidebarCollapsed
|
|
1614
|
+
}
|
|
1615
|
+
),
|
|
1616
|
+
/* @__PURE__ */ jsx(
|
|
1617
|
+
CboxMobileNavDrawer,
|
|
1618
|
+
{
|
|
1619
|
+
open: mobileNavOpen,
|
|
1620
|
+
onClose: () => setMobileNavOpen(false),
|
|
1621
|
+
appKey,
|
|
1622
|
+
brandSubmark,
|
|
1623
|
+
assetBase,
|
|
1624
|
+
brandHref,
|
|
1625
|
+
sections,
|
|
1626
|
+
currentPath,
|
|
1627
|
+
LinkComponent
|
|
1628
|
+
}
|
|
1629
|
+
),
|
|
1630
|
+
/* @__PURE__ */ jsxs(AppContent, { children: [
|
|
1631
|
+
/* @__PURE__ */ jsxs("header", { className: "sticky top-0 z-20 flex h-14 shrink-0 items-center justify-between gap-3 border-b border-border/70 bg-background/80 px-4 backdrop-blur-md", children: [
|
|
1632
|
+
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [
|
|
1633
|
+
/* @__PURE__ */ jsx(
|
|
1634
|
+
"button",
|
|
1635
|
+
{
|
|
1636
|
+
type: "button",
|
|
1637
|
+
onClick: () => setMobileNavOpen(true),
|
|
1638
|
+
className: "inline-flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-secondary hover:text-foreground lg:hidden",
|
|
1639
|
+
"aria-label": "Open navigation",
|
|
1640
|
+
title: "Open navigation",
|
|
1641
|
+
children: /* @__PURE__ */ jsx(Menu, { className: "h-4 w-4", strokeWidth: 1.8 })
|
|
1642
|
+
}
|
|
1643
|
+
),
|
|
1644
|
+
onToggleSidebar !== void 0 ? /* @__PURE__ */ jsx(
|
|
1645
|
+
"button",
|
|
1646
|
+
{
|
|
1647
|
+
type: "button",
|
|
1648
|
+
onClick: onToggleSidebar,
|
|
1649
|
+
className: "hidden h-8 w-8 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-secondary hover:text-foreground lg:inline-flex",
|
|
1650
|
+
"aria-label": sidebarCollapsed ? "Expand sidebar" : "Collapse sidebar",
|
|
1651
|
+
title: sidebarCollapsed ? "Expand sidebar" : "Collapse sidebar",
|
|
1652
|
+
children: /* @__PURE__ */ jsx(
|
|
1653
|
+
ToggleIcon,
|
|
1654
|
+
{
|
|
1655
|
+
className: "h-4 w-4",
|
|
1656
|
+
strokeWidth: 1.8
|
|
1657
|
+
}
|
|
1658
|
+
)
|
|
1659
|
+
}
|
|
1660
|
+
) : null,
|
|
1661
|
+
/* @__PURE__ */ jsx(AppSwitcher, {}),
|
|
1662
|
+
topbarCentre !== void 0 ? topbarCentre : null
|
|
1663
|
+
] }),
|
|
1664
|
+
/* @__PURE__ */ jsx("div", { className: "flex shrink-0 items-center gap-2", children: topbarRightCluster !== void 0 ? topbarRightCluster : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1665
|
+
/* @__PURE__ */ jsx(SearchTrigger, { onOpen: onSearchOpen ?? (() => {
|
|
1666
|
+
}) }),
|
|
1667
|
+
/* @__PURE__ */ jsx(NotificationsBell, {}),
|
|
1668
|
+
/* @__PURE__ */ jsx(UserMenu, {})
|
|
1669
|
+
] }) })
|
|
1670
|
+
] }),
|
|
1671
|
+
children
|
|
1672
|
+
] })
|
|
1673
|
+
] });
|
|
1674
|
+
}
|
|
1675
|
+
var DefaultLink = ({ href, className, children }) => /* @__PURE__ */ jsx("a", { href, className, children });
|
|
1676
|
+
function ContextBar({
|
|
1677
|
+
crumbs,
|
|
1678
|
+
LinkComponent = DefaultLink,
|
|
1679
|
+
className
|
|
1680
|
+
}) {
|
|
1681
|
+
if (crumbs.length === 0) {
|
|
1682
|
+
return null;
|
|
1683
|
+
}
|
|
1684
|
+
return /* @__PURE__ */ jsx(
|
|
1685
|
+
"nav",
|
|
1686
|
+
{
|
|
1687
|
+
"data-component": "context-bar",
|
|
1688
|
+
"aria-label": "Context",
|
|
1689
|
+
className: cn("flex min-w-0 items-center gap-1", className),
|
|
1690
|
+
children: crumbs.map((crumb, index) => /* @__PURE__ */ jsxs(
|
|
1691
|
+
"div",
|
|
1692
|
+
{
|
|
1693
|
+
className: "flex min-w-0 items-center gap-1",
|
|
1694
|
+
children: [
|
|
1695
|
+
index > 0 ? /* @__PURE__ */ jsx(Separator, {}) : null,
|
|
1696
|
+
/* @__PURE__ */ jsx(
|
|
1697
|
+
ContextCrumbChip,
|
|
1698
|
+
{
|
|
1699
|
+
crumb,
|
|
1700
|
+
LinkComponent
|
|
1701
|
+
}
|
|
1702
|
+
)
|
|
1703
|
+
]
|
|
1704
|
+
},
|
|
1705
|
+
crumb.id
|
|
1706
|
+
))
|
|
1707
|
+
}
|
|
1708
|
+
);
|
|
1709
|
+
}
|
|
1710
|
+
function Separator() {
|
|
1711
|
+
return /* @__PURE__ */ jsx(
|
|
1712
|
+
"span",
|
|
1713
|
+
{
|
|
1714
|
+
"aria-hidden": true,
|
|
1715
|
+
className: "text-mono shrink-0 text-[14px] font-light text-muted-foreground/50 select-none",
|
|
1716
|
+
children: "/"
|
|
1717
|
+
}
|
|
1718
|
+
);
|
|
1719
|
+
}
|
|
1720
|
+
function ContextCrumbChip({
|
|
1721
|
+
crumb,
|
|
1722
|
+
LinkComponent
|
|
1723
|
+
}) {
|
|
1724
|
+
const [open, setOpen] = useState(false);
|
|
1725
|
+
const ref = useRef(null);
|
|
1726
|
+
const listId = useId();
|
|
1727
|
+
useEffect(() => {
|
|
1728
|
+
if (!open) return;
|
|
1729
|
+
function onPointerDown(event) {
|
|
1730
|
+
if (ref.current !== null && !ref.current.contains(event.target)) {
|
|
1731
|
+
setOpen(false);
|
|
1732
|
+
}
|
|
1733
|
+
}
|
|
1734
|
+
function onKey(event) {
|
|
1735
|
+
if (event.key === "Escape") setOpen(false);
|
|
1736
|
+
}
|
|
1737
|
+
window.addEventListener("pointerdown", onPointerDown);
|
|
1738
|
+
window.addEventListener("keydown", onKey);
|
|
1739
|
+
return () => {
|
|
1740
|
+
window.removeEventListener("pointerdown", onPointerDown);
|
|
1741
|
+
window.removeEventListener("keydown", onKey);
|
|
1742
|
+
};
|
|
1743
|
+
}, [open]);
|
|
1744
|
+
const hasMenu = crumb.items.length > 0 || crumb.footerLink !== void 0;
|
|
1745
|
+
return /* @__PURE__ */ jsxs("div", { ref, className: "relative", children: [
|
|
1746
|
+
/* @__PURE__ */ jsxs(
|
|
1747
|
+
"button",
|
|
1748
|
+
{
|
|
1749
|
+
type: "button",
|
|
1750
|
+
onClick: () => setOpen((v) => !v),
|
|
1751
|
+
"aria-expanded": hasMenu ? open : void 0,
|
|
1752
|
+
"aria-haspopup": hasMenu ? "listbox" : void 0,
|
|
1753
|
+
"aria-controls": hasMenu ? listId : void 0,
|
|
1754
|
+
"aria-label": crumb.ariaLabel ?? `Switch ${crumb.label}`,
|
|
1755
|
+
disabled: !hasMenu,
|
|
1756
|
+
className: cn(
|
|
1757
|
+
"flex h-8 min-w-0 items-center gap-2 rounded-md border border-transparent px-2 text-[13px] text-foreground transition-colors",
|
|
1758
|
+
hasMenu && "hover:border-border hover:bg-secondary/60 focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none",
|
|
1759
|
+
open && "border-border bg-secondary/60"
|
|
1760
|
+
),
|
|
1761
|
+
children: [
|
|
1762
|
+
crumb.avatar !== void 0 ? /* @__PURE__ */ jsx(
|
|
1763
|
+
ContextAvatarChip,
|
|
1764
|
+
{
|
|
1765
|
+
avatar: crumb.avatar,
|
|
1766
|
+
fallbackLabel: crumb.label,
|
|
1767
|
+
size: "sm"
|
|
1768
|
+
}
|
|
1769
|
+
) : null,
|
|
1770
|
+
/* @__PURE__ */ jsx("span", { className: "truncate font-medium", children: crumb.label }),
|
|
1771
|
+
hasMenu ? /* @__PURE__ */ jsx(Chevron2, { open }) : null
|
|
1772
|
+
]
|
|
1773
|
+
}
|
|
1774
|
+
),
|
|
1775
|
+
open && hasMenu ? /* @__PURE__ */ jsxs(
|
|
1776
|
+
"div",
|
|
1777
|
+
{
|
|
1778
|
+
id: listId,
|
|
1779
|
+
role: "listbox",
|
|
1780
|
+
"aria-label": crumb.ariaLabel ?? crumb.label,
|
|
1781
|
+
className: "absolute left-0 top-full z-30 mt-1 w-64 overflow-hidden rounded-md border border-border bg-popover shadow-lg",
|
|
1782
|
+
children: [
|
|
1783
|
+
crumb.items.length > 0 ? /* @__PURE__ */ jsx("ul", { className: "max-h-72 overflow-y-auto py-1", children: crumb.items.map((item) => /* @__PURE__ */ jsx(
|
|
1784
|
+
ContextItemRow,
|
|
1785
|
+
{
|
|
1786
|
+
item,
|
|
1787
|
+
onSelect: crumb.onSelect,
|
|
1788
|
+
LinkComponent,
|
|
1789
|
+
onAfterClick: () => setOpen(false)
|
|
1790
|
+
},
|
|
1791
|
+
item.id
|
|
1792
|
+
)) }) : null,
|
|
1793
|
+
crumb.footerLink !== void 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1794
|
+
crumb.items.length > 0 ? /* @__PURE__ */ jsx("div", { className: "border-t border-border/60" }) : null,
|
|
1795
|
+
/* @__PURE__ */ jsx(
|
|
1796
|
+
"div",
|
|
1797
|
+
{
|
|
1798
|
+
onClickCapture: () => setOpen(false),
|
|
1799
|
+
children: /* @__PURE__ */ jsxs(
|
|
1800
|
+
LinkComponent,
|
|
1801
|
+
{
|
|
1802
|
+
href: crumb.footerLink.href,
|
|
1803
|
+
prefetch: true,
|
|
1804
|
+
className: "flex items-center gap-2 px-3 py-2.5 text-[13px] font-medium text-info transition-colors hover:bg-info-soft",
|
|
1805
|
+
children: [
|
|
1806
|
+
/* @__PURE__ */ jsx(Plus, {}),
|
|
1807
|
+
/* @__PURE__ */ jsx("span", { children: crumb.footerLink.label })
|
|
1808
|
+
]
|
|
1809
|
+
}
|
|
1810
|
+
)
|
|
1811
|
+
}
|
|
1812
|
+
)
|
|
1813
|
+
] }) : null
|
|
1814
|
+
]
|
|
1815
|
+
}
|
|
1816
|
+
) : null
|
|
1817
|
+
] });
|
|
1818
|
+
}
|
|
1819
|
+
function ContextItemRow({
|
|
1820
|
+
item,
|
|
1821
|
+
onSelect,
|
|
1822
|
+
LinkComponent,
|
|
1823
|
+
onAfterClick
|
|
1824
|
+
}) {
|
|
1825
|
+
const body = /* @__PURE__ */ jsxs("div", { className: "flex w-full min-w-0 items-center gap-2.5 px-2.5 py-2", children: [
|
|
1826
|
+
item.avatar !== void 0 ? /* @__PURE__ */ jsx(
|
|
1827
|
+
ContextAvatarChip,
|
|
1828
|
+
{
|
|
1829
|
+
avatar: item.avatar,
|
|
1830
|
+
fallbackLabel: item.label,
|
|
1831
|
+
size: "md"
|
|
1832
|
+
}
|
|
1833
|
+
) : null,
|
|
1834
|
+
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col leading-tight", children: [
|
|
1835
|
+
/* @__PURE__ */ jsx("span", { className: "truncate text-[13px] font-medium text-foreground", children: item.label }),
|
|
1836
|
+
item.secondary !== void 0 ? /* @__PURE__ */ jsx("span", { className: "text-mono truncate text-[10px] text-muted-foreground", children: item.secondary }) : null
|
|
1837
|
+
] }),
|
|
1838
|
+
item.current === true && item.settingsHref === void 0 ? /* @__PURE__ */ jsx(Check, {}) : null
|
|
1839
|
+
] });
|
|
1840
|
+
const baseClassName = cn(
|
|
1841
|
+
"group/item block w-full rounded-sm transition-colors",
|
|
1842
|
+
item.current === true ? "bg-accent-soft hover:bg-accent-soft/80" : "hover:bg-secondary"
|
|
1843
|
+
);
|
|
1844
|
+
const settingsCog = item.settingsHref !== void 0 ? /* @__PURE__ */ jsx(
|
|
1845
|
+
LinkComponent,
|
|
1846
|
+
{
|
|
1847
|
+
href: item.settingsHref,
|
|
1848
|
+
prefetch: true,
|
|
1849
|
+
className: "invisible absolute right-1 top-1/2 inline-flex h-7 w-7 -translate-y-1/2 items-center justify-center rounded-sm text-muted-foreground transition-colors group-hover/item:visible hover:bg-card hover:text-foreground focus-visible:visible focus-visible:bg-card focus-visible:text-foreground focus-visible:outline-none",
|
|
1850
|
+
children: /* @__PURE__ */ jsx(
|
|
1851
|
+
"span",
|
|
1852
|
+
{
|
|
1853
|
+
"aria-label": item.settingsAriaLabel ?? `Settings for ${item.label}`,
|
|
1854
|
+
onClickCapture: (event) => {
|
|
1855
|
+
event.stopPropagation();
|
|
1856
|
+
onAfterClick();
|
|
1857
|
+
},
|
|
1858
|
+
className: "contents",
|
|
1859
|
+
children: /* @__PURE__ */ jsx(SettingsIcon, {})
|
|
1860
|
+
}
|
|
1861
|
+
)
|
|
1862
|
+
}
|
|
1863
|
+
) : null;
|
|
1864
|
+
if (onSelect !== void 0) {
|
|
1865
|
+
return /* @__PURE__ */ jsxs("li", { className: "relative", children: [
|
|
1866
|
+
/* @__PURE__ */ jsx(
|
|
1867
|
+
"button",
|
|
1868
|
+
{
|
|
1869
|
+
type: "button",
|
|
1870
|
+
onClick: () => {
|
|
1871
|
+
onSelect(item.id);
|
|
1872
|
+
onAfterClick();
|
|
1873
|
+
},
|
|
1874
|
+
className: cn(baseClassName, "text-left"),
|
|
1875
|
+
children: body
|
|
1876
|
+
}
|
|
1877
|
+
),
|
|
1878
|
+
settingsCog
|
|
1879
|
+
] });
|
|
1880
|
+
}
|
|
1881
|
+
if (item.href !== void 0) {
|
|
1882
|
+
return /* @__PURE__ */ jsxs("li", { className: "relative", children: [
|
|
1883
|
+
/* @__PURE__ */ jsx(
|
|
1884
|
+
LinkComponent,
|
|
1885
|
+
{
|
|
1886
|
+
href: item.href,
|
|
1887
|
+
prefetch: true,
|
|
1888
|
+
className: baseClassName,
|
|
1889
|
+
children: body
|
|
1890
|
+
}
|
|
1891
|
+
),
|
|
1892
|
+
settingsCog
|
|
1893
|
+
] });
|
|
1894
|
+
}
|
|
1895
|
+
return /* @__PURE__ */ jsxs("li", { className: "relative", children: [
|
|
1896
|
+
/* @__PURE__ */ jsx("div", { className: baseClassName, children: body }),
|
|
1897
|
+
settingsCog
|
|
1898
|
+
] });
|
|
1899
|
+
}
|
|
1900
|
+
function SettingsIcon() {
|
|
1901
|
+
return /* @__PURE__ */ jsxs(
|
|
1902
|
+
"svg",
|
|
1903
|
+
{
|
|
1904
|
+
width: "14",
|
|
1905
|
+
height: "14",
|
|
1906
|
+
viewBox: "0 0 14 14",
|
|
1907
|
+
fill: "none",
|
|
1908
|
+
stroke: "currentColor",
|
|
1909
|
+
strokeWidth: "1.5",
|
|
1910
|
+
strokeLinecap: "round",
|
|
1911
|
+
strokeLinejoin: "round",
|
|
1912
|
+
"aria-hidden": true,
|
|
1913
|
+
children: [
|
|
1914
|
+
/* @__PURE__ */ jsx("circle", { cx: "7", cy: "7", r: "2" }),
|
|
1915
|
+
/* @__PURE__ */ jsx("path", { d: "M11.66 8.42a.79.79 0 0 0 .16.86l.03.03a.96.96 0 1 1-1.36 1.36l-.03-.03a.79.79 0 0 0-.86-.16.79.79 0 0 0-.48.72v.08a.96.96 0 1 1-1.92 0v-.04a.79.79 0 0 0-.52-.72.79.79 0 0 0-.86.16l-.03.03a.96.96 0 1 1-1.36-1.36l.03-.03a.79.79 0 0 0 .16-.86.79.79 0 0 0-.72-.48h-.08a.96.96 0 1 1 0-1.92h.04a.79.79 0 0 0 .72-.52.79.79 0 0 0-.16-.86l-.03-.03a.96.96 0 1 1 1.36-1.36l.03.03a.79.79 0 0 0 .86.16h.04a.79.79 0 0 0 .48-.72v-.08a.96.96 0 1 1 1.92 0v.04a.79.79 0 0 0 .48.72.79.79 0 0 0 .86-.16l.03-.03a.96.96 0 1 1 1.36 1.36l-.03.03a.79.79 0 0 0-.16.86v.04a.79.79 0 0 0 .72.48h.08a.96.96 0 1 1 0 1.92h-.04a.79.79 0 0 0-.72.48Z" })
|
|
1916
|
+
]
|
|
1917
|
+
}
|
|
1918
|
+
);
|
|
1919
|
+
}
|
|
1920
|
+
function ContextAvatarChip({
|
|
1921
|
+
avatar,
|
|
1922
|
+
fallbackLabel,
|
|
1923
|
+
size = "md"
|
|
1924
|
+
}) {
|
|
1925
|
+
const initials = avatar.initials ?? deriveInitials(fallbackLabel);
|
|
1926
|
+
const sizeClass = size === "sm" ? "h-5 w-5 text-[9px]" : "h-7 w-7 text-[11px]";
|
|
1927
|
+
const style = avatar.src === void 0 && avatar.accent !== void 0 ? {
|
|
1928
|
+
backgroundColor: avatar.accent,
|
|
1929
|
+
color: "#fff"
|
|
1930
|
+
} : void 0;
|
|
1931
|
+
if (avatar.src !== void 0) {
|
|
1932
|
+
return /* @__PURE__ */ jsx(
|
|
1933
|
+
"img",
|
|
1934
|
+
{
|
|
1935
|
+
src: avatar.src,
|
|
1936
|
+
alt: "",
|
|
1937
|
+
className: cn(
|
|
1938
|
+
"shrink-0 rounded-sm bg-secondary object-cover",
|
|
1939
|
+
sizeClass
|
|
1940
|
+
),
|
|
1941
|
+
loading: "lazy"
|
|
1942
|
+
}
|
|
1943
|
+
);
|
|
1944
|
+
}
|
|
1945
|
+
return /* @__PURE__ */ jsx(
|
|
1946
|
+
"span",
|
|
1947
|
+
{
|
|
1948
|
+
style,
|
|
1949
|
+
className: cn(
|
|
1950
|
+
"flex shrink-0 items-center justify-center rounded-sm font-semibold tracking-tight",
|
|
1951
|
+
avatar.accent === void 0 && "bg-info-soft text-info",
|
|
1952
|
+
sizeClass
|
|
1953
|
+
),
|
|
1954
|
+
children: initials
|
|
1955
|
+
}
|
|
1956
|
+
);
|
|
1957
|
+
}
|
|
1958
|
+
function deriveInitials(label) {
|
|
1959
|
+
const parts = label.trim().split(/\s+/);
|
|
1960
|
+
if (parts.length >= 2) {
|
|
1961
|
+
return ((parts[0]?.[0] ?? "") + (parts[1]?.[0] ?? "")).toUpperCase();
|
|
1962
|
+
}
|
|
1963
|
+
return (label.slice(0, 2) || "?").toUpperCase();
|
|
1964
|
+
}
|
|
1965
|
+
function Chevron2({ open }) {
|
|
1966
|
+
return /* @__PURE__ */ jsx(
|
|
1967
|
+
"svg",
|
|
1968
|
+
{
|
|
1969
|
+
width: "10",
|
|
1970
|
+
height: "10",
|
|
1971
|
+
viewBox: "0 0 10 10",
|
|
1972
|
+
fill: "none",
|
|
1973
|
+
stroke: "currentColor",
|
|
1974
|
+
strokeWidth: "1.5",
|
|
1975
|
+
strokeLinecap: "round",
|
|
1976
|
+
strokeLinejoin: "round",
|
|
1977
|
+
className: cn(
|
|
1978
|
+
"shrink-0 text-muted-foreground transition-transform duration-150",
|
|
1979
|
+
open && "rotate-180"
|
|
1980
|
+
),
|
|
1981
|
+
"aria-hidden": true,
|
|
1982
|
+
children: /* @__PURE__ */ jsx("path", { d: "m2.5 4 2.5 2.5L7.5 4" })
|
|
1983
|
+
}
|
|
1984
|
+
);
|
|
1985
|
+
}
|
|
1986
|
+
function Check() {
|
|
1987
|
+
return /* @__PURE__ */ jsx(
|
|
1988
|
+
"svg",
|
|
1989
|
+
{
|
|
1990
|
+
width: "14",
|
|
1991
|
+
height: "14",
|
|
1992
|
+
viewBox: "0 0 14 14",
|
|
1993
|
+
fill: "none",
|
|
1994
|
+
stroke: "currentColor",
|
|
1995
|
+
strokeWidth: "1.6",
|
|
1996
|
+
strokeLinecap: "round",
|
|
1997
|
+
strokeLinejoin: "round",
|
|
1998
|
+
className: "shrink-0 text-accent",
|
|
1999
|
+
"aria-hidden": true,
|
|
2000
|
+
children: /* @__PURE__ */ jsx("path", { d: "m3 7.5 2.5 2.5L11 4.5" })
|
|
2001
|
+
}
|
|
2002
|
+
);
|
|
2003
|
+
}
|
|
2004
|
+
function Plus() {
|
|
2005
|
+
return /* @__PURE__ */ jsx(
|
|
2006
|
+
"svg",
|
|
2007
|
+
{
|
|
2008
|
+
width: "12",
|
|
2009
|
+
height: "12",
|
|
2010
|
+
viewBox: "0 0 12 12",
|
|
2011
|
+
fill: "none",
|
|
2012
|
+
stroke: "currentColor",
|
|
2013
|
+
strokeWidth: "1.8",
|
|
2014
|
+
strokeLinecap: "round",
|
|
2015
|
+
strokeLinejoin: "round",
|
|
2016
|
+
className: "shrink-0",
|
|
2017
|
+
"aria-hidden": true,
|
|
2018
|
+
children: /* @__PURE__ */ jsx("path", { d: "M6 2v8M2 6h8" })
|
|
2019
|
+
}
|
|
2020
|
+
);
|
|
2021
|
+
}
|
|
2022
|
+
function CboxOrgContextBar({
|
|
2023
|
+
LinkComponent,
|
|
2024
|
+
onNavigate
|
|
2025
|
+
}) {
|
|
2026
|
+
const cboxId = useCboxIdOptional();
|
|
2027
|
+
if (cboxId === null) {
|
|
2028
|
+
return null;
|
|
2029
|
+
}
|
|
2030
|
+
const { currentOrganization, organizations, urls } = cboxId;
|
|
2031
|
+
const activeOrg = currentOrganization ?? organizations[0] ?? null;
|
|
2032
|
+
if (activeOrg === null) {
|
|
2033
|
+
return null;
|
|
2034
|
+
}
|
|
2035
|
+
const idBase = urls.idBaseUrl.replace(/\/$/, "");
|
|
2036
|
+
const items = organizations.map((org) => ({
|
|
2037
|
+
id: org.slug,
|
|
2038
|
+
label: org.name,
|
|
2039
|
+
avatar: { initials: deriveInitials2(org.name) },
|
|
2040
|
+
current: org.slug === activeOrg.slug,
|
|
2041
|
+
// Cogwheel shortcut → id's org-settings page. Cross-app
|
|
2042
|
+
// deep link; id handles the auth and rendering.
|
|
2043
|
+
settingsHref: `${idBase}/org/${org.slug}/settings`,
|
|
2044
|
+
settingsAriaLabel: `Settings for ${org.name}`
|
|
2045
|
+
}));
|
|
2046
|
+
const onSelect = (slug) => {
|
|
2047
|
+
const org = organizations.find((o) => o.slug === slug);
|
|
2048
|
+
if (org === void 0) return;
|
|
2049
|
+
const path = typeof window !== "undefined" ? window.location.pathname : "/";
|
|
2050
|
+
const next = path.startsWith("/org/") ? path.replace(/^\/org\/[^/]+/, `/org/${org.slug}`) : `/org/${org.slug}`;
|
|
2051
|
+
const search = typeof window !== "undefined" ? window.location.search : "";
|
|
2052
|
+
onNavigate(next + search);
|
|
2053
|
+
};
|
|
2054
|
+
return /* @__PURE__ */ jsx(
|
|
2055
|
+
ContextBar,
|
|
2056
|
+
{
|
|
2057
|
+
LinkComponent,
|
|
2058
|
+
crumbs: [
|
|
2059
|
+
{
|
|
2060
|
+
id: "org",
|
|
2061
|
+
label: activeOrg.name,
|
|
2062
|
+
avatar: { initials: deriveInitials2(activeOrg.name) },
|
|
2063
|
+
items,
|
|
2064
|
+
onSelect,
|
|
2065
|
+
footerLink: {
|
|
2066
|
+
label: "Manage organizations",
|
|
2067
|
+
href: `${idBase}/settings/organizations`
|
|
2068
|
+
},
|
|
2069
|
+
ariaLabel: "Switch organization"
|
|
2070
|
+
}
|
|
2071
|
+
]
|
|
2072
|
+
}
|
|
2073
|
+
);
|
|
2074
|
+
}
|
|
2075
|
+
function deriveInitials2(label) {
|
|
2076
|
+
const parts = label.trim().split(/\s+/);
|
|
2077
|
+
if (parts.length >= 2) {
|
|
2078
|
+
return ((parts[0]?.[0] ?? "") + (parts[1]?.[0] ?? "")).toUpperCase();
|
|
2079
|
+
}
|
|
2080
|
+
return (label.slice(0, 2) || "?").toUpperCase();
|
|
2081
|
+
}
|
|
2082
|
+
function OrgSwitcher({ Trigger = DefaultTrigger, className }) {
|
|
2083
|
+
const { currentOrganization, organizations, onSwitchOrganization } = useCboxId();
|
|
2084
|
+
const [open, setOpen] = useState(false);
|
|
2085
|
+
const [query, setQuery] = useState("");
|
|
2086
|
+
const [activeIndex, setActiveIndex] = useState(0);
|
|
2087
|
+
const containerRef = useRef(null);
|
|
2088
|
+
const searchRef = useRef(null);
|
|
2089
|
+
useEffect(() => {
|
|
2090
|
+
if (open) {
|
|
2091
|
+
setQuery("");
|
|
2092
|
+
setActiveIndex(0);
|
|
2093
|
+
const id = requestAnimationFrame(() => searchRef.current?.focus());
|
|
2094
|
+
return () => cancelAnimationFrame(id);
|
|
2095
|
+
}
|
|
2096
|
+
}, [open]);
|
|
2097
|
+
useEffect(() => {
|
|
2098
|
+
if (!open) {
|
|
2099
|
+
return;
|
|
2100
|
+
}
|
|
2101
|
+
function onPointerDown(event) {
|
|
2102
|
+
if (containerRef.current !== null && !containerRef.current.contains(event.target)) {
|
|
2103
|
+
setOpen(false);
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
2106
|
+
function onKey(event) {
|
|
2107
|
+
if (event.key === "Escape") {
|
|
2108
|
+
setOpen(false);
|
|
2109
|
+
}
|
|
2110
|
+
}
|
|
2111
|
+
window.addEventListener("pointerdown", onPointerDown);
|
|
2112
|
+
window.addEventListener("keydown", onKey);
|
|
2113
|
+
return () => {
|
|
2114
|
+
window.removeEventListener("pointerdown", onPointerDown);
|
|
2115
|
+
window.removeEventListener("keydown", onKey);
|
|
2116
|
+
};
|
|
2117
|
+
}, [open]);
|
|
2118
|
+
const filtered = useMemo(() => {
|
|
2119
|
+
const q = query.trim().toLowerCase();
|
|
2120
|
+
if (q === "") {
|
|
2121
|
+
return organizations;
|
|
2122
|
+
}
|
|
2123
|
+
return organizations.filter(
|
|
2124
|
+
(org) => org.name.toLowerCase().includes(q) || org.slug.toLowerCase().includes(q)
|
|
2125
|
+
);
|
|
2126
|
+
}, [organizations, query]);
|
|
2127
|
+
useEffect(() => {
|
|
2128
|
+
if (activeIndex >= filtered.length) {
|
|
2129
|
+
setActiveIndex(0);
|
|
2130
|
+
}
|
|
2131
|
+
}, [filtered.length, activeIndex]);
|
|
2132
|
+
const onPick = (org) => {
|
|
2133
|
+
setOpen(false);
|
|
2134
|
+
if (onSwitchOrganization) {
|
|
2135
|
+
onSwitchOrganization(org);
|
|
2136
|
+
return;
|
|
2137
|
+
}
|
|
2138
|
+
if (typeof console !== "undefined") {
|
|
2139
|
+
console.warn(
|
|
2140
|
+
"[cbox-ui] OrgSwitcher: no onSwitchOrganization handler provided. Apps must wire one (router.visit) \u2014 there is no server endpoint to fall back to."
|
|
2141
|
+
);
|
|
2142
|
+
}
|
|
2143
|
+
};
|
|
2144
|
+
function onSearchKeyDown(event) {
|
|
2145
|
+
if (event.key === "ArrowDown") {
|
|
2146
|
+
event.preventDefault();
|
|
2147
|
+
if (filtered.length === 0) {
|
|
2148
|
+
return;
|
|
2149
|
+
}
|
|
2150
|
+
setActiveIndex((i) => (i + 1) % filtered.length);
|
|
2151
|
+
return;
|
|
2152
|
+
}
|
|
2153
|
+
if (event.key === "ArrowUp") {
|
|
2154
|
+
event.preventDefault();
|
|
2155
|
+
if (filtered.length === 0) {
|
|
2156
|
+
return;
|
|
2157
|
+
}
|
|
2158
|
+
setActiveIndex((i) => (i - 1 + filtered.length) % filtered.length);
|
|
2159
|
+
return;
|
|
2160
|
+
}
|
|
2161
|
+
if (event.key === "Enter") {
|
|
2162
|
+
event.preventDefault();
|
|
2163
|
+
const target = filtered[activeIndex];
|
|
2164
|
+
if (target !== void 0) {
|
|
2165
|
+
onPick(target);
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
}
|
|
2169
|
+
return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: cn("relative", className), children: [
|
|
2170
|
+
/* @__PURE__ */ jsx(
|
|
2171
|
+
Trigger,
|
|
2172
|
+
{
|
|
2173
|
+
organization: currentOrganization,
|
|
2174
|
+
onClick: () => setOpen((v) => !v),
|
|
2175
|
+
open
|
|
2176
|
+
}
|
|
2177
|
+
),
|
|
2178
|
+
open ? /* @__PURE__ */ jsxs(
|
|
2179
|
+
"div",
|
|
2180
|
+
{
|
|
2181
|
+
role: "dialog",
|
|
2182
|
+
"aria-label": "Switch organization",
|
|
2183
|
+
className: "absolute left-0 top-full z-50 mt-1 w-64 overflow-hidden rounded-md border border-border bg-popover shadow-md",
|
|
2184
|
+
children: [
|
|
2185
|
+
/* @__PURE__ */ jsx("div", { className: "border-b border-border/70 px-3 pt-2 pb-1", children: /* @__PURE__ */ jsx("p", { className: "text-mono text-[10px] font-semibold tracking-[0.18em] text-muted-foreground uppercase", children: "Switch organization" }) }),
|
|
2186
|
+
/* @__PURE__ */ jsx("div", { className: "border-b border-border/70 px-2 py-1.5", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 rounded-md px-2 py-1 focus-within:bg-secondary", children: [
|
|
2187
|
+
/* @__PURE__ */ jsx(SearchIcon3, {}),
|
|
2188
|
+
/* @__PURE__ */ jsx(
|
|
2189
|
+
"input",
|
|
2190
|
+
{
|
|
2191
|
+
ref: searchRef,
|
|
2192
|
+
type: "text",
|
|
2193
|
+
value: query,
|
|
2194
|
+
onChange: (e) => setQuery(e.target.value),
|
|
2195
|
+
onKeyDown: onSearchKeyDown,
|
|
2196
|
+
placeholder: "Filter\u2026",
|
|
2197
|
+
"aria-label": "Filter organizations",
|
|
2198
|
+
autoComplete: "off",
|
|
2199
|
+
spellCheck: false,
|
|
2200
|
+
className: "w-full bg-transparent text-sm text-foreground placeholder:text-muted-foreground/60 focus:outline-none"
|
|
2201
|
+
}
|
|
2202
|
+
)
|
|
2203
|
+
] }) }),
|
|
2204
|
+
/* @__PURE__ */ jsx("div", { className: "max-h-[50vh] overflow-y-auto p-1", children: filtered.length === 0 ? /* @__PURE__ */ jsxs("p", { className: "px-3 py-6 text-center text-[12px] text-muted-foreground", children: [
|
|
2205
|
+
"No organizations match",
|
|
2206
|
+
" ",
|
|
2207
|
+
/* @__PURE__ */ jsxs("span", { className: "text-mono text-foreground", children: [
|
|
2208
|
+
"\u201C",
|
|
2209
|
+
query,
|
|
2210
|
+
"\u201D"
|
|
2211
|
+
] })
|
|
2212
|
+
] }) : /* @__PURE__ */ jsx("ul", { className: "space-y-0.5", children: filtered.map((org, index) => {
|
|
2213
|
+
const active = currentOrganization?.id === org.id;
|
|
2214
|
+
const highlighted = index === activeIndex;
|
|
2215
|
+
return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
|
|
2216
|
+
"button",
|
|
2217
|
+
{
|
|
2218
|
+
type: "button",
|
|
2219
|
+
onMouseEnter: () => setActiveIndex(index),
|
|
2220
|
+
onClick: () => onPick(org),
|
|
2221
|
+
className: cn(
|
|
2222
|
+
"flex w-full items-center justify-between rounded-sm px-2 py-1.5 text-left text-sm transition-colors",
|
|
2223
|
+
active ? "bg-accent-soft text-foreground" : highlighted ? "bg-secondary" : "hover:bg-secondary"
|
|
2224
|
+
),
|
|
2225
|
+
children: [
|
|
2226
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: org.name }),
|
|
2227
|
+
active ? /* @__PURE__ */ jsx("span", { className: "text-mono text-[10px] text-muted-foreground", children: "current" }) : null
|
|
2228
|
+
]
|
|
2229
|
+
}
|
|
2230
|
+
) }, org.id);
|
|
2231
|
+
}) }) })
|
|
2232
|
+
]
|
|
2233
|
+
}
|
|
2234
|
+
) : null
|
|
2235
|
+
] });
|
|
2236
|
+
}
|
|
2237
|
+
var DefaultTrigger = ({ organization, onClick }) => /* @__PURE__ */ jsxs(
|
|
2238
|
+
"button",
|
|
2239
|
+
{
|
|
2240
|
+
type: "button",
|
|
2241
|
+
onClick,
|
|
2242
|
+
className: "flex items-center gap-2 rounded-md border border-border bg-card px-3 py-1.5 text-sm font-medium text-foreground hover:bg-secondary",
|
|
2243
|
+
children: [
|
|
2244
|
+
/* @__PURE__ */ jsx("span", { className: "truncate max-w-[180px]", children: organization?.name ?? "Pick an organization" }),
|
|
2245
|
+
/* @__PURE__ */ jsx(Chevron3, {})
|
|
2246
|
+
]
|
|
2247
|
+
}
|
|
2248
|
+
);
|
|
2249
|
+
function Chevron3() {
|
|
2250
|
+
return /* @__PURE__ */ jsx(
|
|
2251
|
+
"svg",
|
|
2252
|
+
{
|
|
2253
|
+
width: "10",
|
|
2254
|
+
height: "10",
|
|
2255
|
+
viewBox: "0 0 10 10",
|
|
2256
|
+
fill: "none",
|
|
2257
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
2258
|
+
"aria-hidden": true,
|
|
2259
|
+
children: /* @__PURE__ */ jsx(
|
|
2260
|
+
"path",
|
|
2261
|
+
{
|
|
2262
|
+
d: "M2 4l3 3 3-3",
|
|
2263
|
+
stroke: "currentColor",
|
|
2264
|
+
strokeWidth: "1.4",
|
|
2265
|
+
strokeLinecap: "round",
|
|
2266
|
+
strokeLinejoin: "round"
|
|
2267
|
+
}
|
|
2268
|
+
)
|
|
2269
|
+
}
|
|
2270
|
+
);
|
|
2271
|
+
}
|
|
2272
|
+
function SearchIcon3() {
|
|
2273
|
+
return /* @__PURE__ */ jsxs(
|
|
2274
|
+
"svg",
|
|
2275
|
+
{
|
|
2276
|
+
width: "12",
|
|
2277
|
+
height: "12",
|
|
2278
|
+
viewBox: "0 0 12 12",
|
|
2279
|
+
fill: "none",
|
|
2280
|
+
stroke: "currentColor",
|
|
2281
|
+
strokeWidth: "1.4",
|
|
2282
|
+
strokeLinecap: "round",
|
|
2283
|
+
strokeLinejoin: "round",
|
|
2284
|
+
"aria-hidden": true,
|
|
2285
|
+
className: "shrink-0 text-muted-foreground",
|
|
2286
|
+
children: [
|
|
2287
|
+
/* @__PURE__ */ jsx("circle", { cx: "5.25", cy: "5.25", r: "3.25" }),
|
|
2288
|
+
/* @__PURE__ */ jsx("path", { d: "M8 8l2 2" })
|
|
2289
|
+
]
|
|
2290
|
+
}
|
|
2291
|
+
);
|
|
2292
|
+
}
|
|
2293
|
+
function TopBar({
|
|
2294
|
+
brand,
|
|
2295
|
+
centre,
|
|
2296
|
+
rightCluster,
|
|
2297
|
+
showOrgSwitcher = true,
|
|
2298
|
+
className
|
|
2299
|
+
}) {
|
|
2300
|
+
return /* @__PURE__ */ jsxs(
|
|
2301
|
+
"header",
|
|
2302
|
+
{
|
|
2303
|
+
className: cn(
|
|
2304
|
+
"flex h-14 shrink-0 items-center gap-3 border-b border-border bg-background px-4",
|
|
2305
|
+
className
|
|
2306
|
+
),
|
|
2307
|
+
children: [
|
|
2308
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
2309
|
+
brand,
|
|
2310
|
+
/* @__PURE__ */ jsx(AppSwitcher, {}),
|
|
2311
|
+
showOrgSwitcher ? /* @__PURE__ */ jsx(OrgSwitcher, {}) : null
|
|
2312
|
+
] }),
|
|
2313
|
+
/* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1", children: centre }),
|
|
2314
|
+
/* @__PURE__ */ jsx("div", { className: "flex shrink-0 items-center gap-1", children: rightCluster ?? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2315
|
+
/* @__PURE__ */ jsx(NotificationsBell, {}),
|
|
2316
|
+
/* @__PURE__ */ jsx(UserMenu, {})
|
|
2317
|
+
] }) })
|
|
2318
|
+
]
|
|
2319
|
+
}
|
|
2320
|
+
);
|
|
2321
|
+
}
|
|
2322
|
+
function AppLauncher({ className, onLockedAppClick }) {
|
|
2323
|
+
const cboxId = useCboxIdOptional();
|
|
2324
|
+
const [open, setOpen] = useState(false);
|
|
2325
|
+
const containerRef = useRef(null);
|
|
2326
|
+
useEffect(() => {
|
|
2327
|
+
if (!open) {
|
|
2328
|
+
return;
|
|
2329
|
+
}
|
|
2330
|
+
function onPointerDown(event) {
|
|
2331
|
+
if (containerRef.current !== null && !containerRef.current.contains(event.target)) {
|
|
2332
|
+
setOpen(false);
|
|
2333
|
+
}
|
|
2334
|
+
}
|
|
2335
|
+
function onKey(event) {
|
|
2336
|
+
if (event.key === "Escape") {
|
|
2337
|
+
setOpen(false);
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2340
|
+
window.addEventListener("pointerdown", onPointerDown);
|
|
2341
|
+
window.addEventListener("keydown", onKey);
|
|
2342
|
+
return () => {
|
|
2343
|
+
window.removeEventListener("pointerdown", onPointerDown);
|
|
2344
|
+
window.removeEventListener("keydown", onKey);
|
|
2345
|
+
};
|
|
2346
|
+
}, [open]);
|
|
2347
|
+
if (cboxId === null || cboxId.apps.length === 0) {
|
|
2348
|
+
return null;
|
|
2349
|
+
}
|
|
2350
|
+
const apps = cboxId.apps;
|
|
2351
|
+
const orgSlug = cboxId.currentOrganization?.slug;
|
|
2352
|
+
const entitled = [];
|
|
2353
|
+
const locked = [];
|
|
2354
|
+
for (const app of apps) {
|
|
2355
|
+
const isEntitled = app.entitled !== false && app.comingSoon !== true;
|
|
2356
|
+
(isEntitled ? entitled : locked).push(app);
|
|
2357
|
+
}
|
|
2358
|
+
return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: cn("relative", className), children: [
|
|
2359
|
+
/* @__PURE__ */ jsx(
|
|
2360
|
+
"button",
|
|
2361
|
+
{
|
|
2362
|
+
type: "button",
|
|
2363
|
+
"aria-label": "Open app launcher",
|
|
2364
|
+
"aria-expanded": open,
|
|
2365
|
+
onClick: () => setOpen((v) => !v),
|
|
2366
|
+
className: cn(
|
|
2367
|
+
"flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-secondary hover:text-foreground",
|
|
2368
|
+
open && "bg-secondary text-foreground"
|
|
2369
|
+
),
|
|
2370
|
+
children: /* @__PURE__ */ jsx(Grid, {})
|
|
2371
|
+
}
|
|
2372
|
+
),
|
|
2373
|
+
open ? /* @__PURE__ */ jsxs(
|
|
2374
|
+
"div",
|
|
2375
|
+
{
|
|
2376
|
+
role: "dialog",
|
|
2377
|
+
"aria-label": "Cbox app launcher",
|
|
2378
|
+
className: "absolute right-0 top-full z-50 mt-1.5 w-[min(20rem,calc(100vw-1.5rem))] overflow-hidden rounded-lg border border-border bg-popover shadow-lg",
|
|
2379
|
+
children: [
|
|
2380
|
+
/* @__PURE__ */ jsx("div", { className: "border-b border-border/70 px-3 py-2.5", children: /* @__PURE__ */ jsx("p", { className: "text-mono text-[10px] font-semibold tracking-[0.18em] text-muted-foreground uppercase", children: "Cbox suite" }) }),
|
|
2381
|
+
/* @__PURE__ */ jsxs("div", { className: "max-h-[60vh] overflow-y-auto py-1", children: [
|
|
2382
|
+
entitled.length > 0 ? /* @__PURE__ */ jsx("ul", { className: "space-y-0.5 p-1", children: entitled.map((app) => /* @__PURE__ */ jsx(
|
|
2383
|
+
AppRow2,
|
|
2384
|
+
{
|
|
2385
|
+
app,
|
|
2386
|
+
orgSlug,
|
|
2387
|
+
onLockedAppClick
|
|
2388
|
+
},
|
|
2389
|
+
app.key
|
|
2390
|
+
)) }) : null,
|
|
2391
|
+
locked.length > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2392
|
+
entitled.length > 0 ? /* @__PURE__ */ jsx("div", { className: "my-1 border-t border-border/50" }) : null,
|
|
2393
|
+
/* @__PURE__ */ jsx("p", { className: "text-mono px-3 py-1.5 text-[10px] font-semibold tracking-[0.18em] text-muted-foreground/70 uppercase", children: "Available" }),
|
|
2394
|
+
/* @__PURE__ */ jsx("ul", { className: "space-y-0.5 p-1 pt-0", children: locked.map((app) => /* @__PURE__ */ jsx(
|
|
2395
|
+
AppRow2,
|
|
2396
|
+
{
|
|
2397
|
+
app,
|
|
2398
|
+
orgSlug,
|
|
2399
|
+
onLockedAppClick
|
|
2400
|
+
},
|
|
2401
|
+
app.key
|
|
2402
|
+
)) })
|
|
2403
|
+
] }) : null
|
|
2404
|
+
] })
|
|
2405
|
+
]
|
|
2406
|
+
}
|
|
2407
|
+
) : null
|
|
2408
|
+
] });
|
|
2409
|
+
}
|
|
2410
|
+
function AppRow2({ app, orgSlug, onLockedAppClick }) {
|
|
2411
|
+
const isComingSoon = app.comingSoon === true;
|
|
2412
|
+
const isLocked = app.entitled === false || isComingSoon;
|
|
2413
|
+
const monogram = app.monogram ?? app.name.slice(0, 1).toUpperCase();
|
|
2414
|
+
const accent = app.accentColor;
|
|
2415
|
+
const tileStyle = accent !== void 0 && app.iconUrl === void 0 ? { backgroundColor: accent, color: "#fff", borderColor: accent } : void 0;
|
|
2416
|
+
const href = orgSlug !== void 0 && app.current !== true ? buildOrgUrl2(app.url, orgSlug) : app.url;
|
|
2417
|
+
const tile = /* @__PURE__ */ jsx(
|
|
2418
|
+
"div",
|
|
2419
|
+
{
|
|
2420
|
+
style: tileStyle,
|
|
2421
|
+
className: cn(
|
|
2422
|
+
"flex h-9 w-9 shrink-0 items-center justify-center rounded-md border border-border bg-card text-[13px] font-semibold text-foreground",
|
|
2423
|
+
app.current && "ring-2 ring-accent-edge ring-offset-1 ring-offset-popover",
|
|
2424
|
+
isLocked && "opacity-60 grayscale"
|
|
2425
|
+
),
|
|
2426
|
+
children: app.iconUrl ? /* @__PURE__ */ jsx("img", { src: app.iconUrl, alt: "", className: "h-5 w-5", loading: "lazy" }) : /* @__PURE__ */ jsx("span", { children: monogram })
|
|
2427
|
+
}
|
|
2428
|
+
);
|
|
2429
|
+
const body = /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col leading-tight", children: [
|
|
2430
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
2431
|
+
/* @__PURE__ */ jsx(
|
|
2432
|
+
"span",
|
|
2433
|
+
{
|
|
2434
|
+
className: cn(
|
|
2435
|
+
"truncate text-sm font-medium text-foreground",
|
|
2436
|
+
isLocked && "text-muted-foreground"
|
|
2437
|
+
),
|
|
2438
|
+
children: app.name
|
|
2439
|
+
}
|
|
2440
|
+
),
|
|
2441
|
+
app.current ? /* @__PURE__ */ jsx("span", { className: "text-mono shrink-0 rounded-sm border border-accent-edge/40 bg-accent-soft px-1 text-[9px] font-semibold tracking-[0.14em] text-accent uppercase", children: "current" }) : null
|
|
2442
|
+
] }),
|
|
2443
|
+
app.description ? /* @__PURE__ */ jsx(
|
|
2444
|
+
"span",
|
|
2445
|
+
{
|
|
2446
|
+
className: cn(
|
|
2447
|
+
"truncate text-[11px]",
|
|
2448
|
+
isLocked ? "text-muted-foreground/70" : "text-muted-foreground"
|
|
2449
|
+
),
|
|
2450
|
+
children: app.description
|
|
2451
|
+
}
|
|
2452
|
+
) : null
|
|
2453
|
+
] });
|
|
2454
|
+
const trailing = isComingSoon ? /* @__PURE__ */ jsx("span", { className: "text-mono shrink-0 rounded-sm border border-border bg-secondary px-1.5 py-0.5 text-[9px] font-semibold tracking-[0.14em] text-muted-foreground uppercase", children: "soon" }) : isLocked ? /* @__PURE__ */ jsxs("span", { className: "flex shrink-0 items-center gap-1 text-[10px] font-medium text-muted-foreground", children: [
|
|
2455
|
+
/* @__PURE__ */ jsx(Lock2, {}),
|
|
2456
|
+
"Add"
|
|
2457
|
+
] }) : null;
|
|
2458
|
+
if (isComingSoon) {
|
|
2459
|
+
return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
|
|
2460
|
+
"div",
|
|
2461
|
+
{
|
|
2462
|
+
"aria-disabled": true,
|
|
2463
|
+
className: "flex w-full cursor-not-allowed items-center gap-3 rounded-md px-2.5 py-2",
|
|
2464
|
+
title: `${app.name} \u2014 coming soon`,
|
|
2465
|
+
children: [
|
|
2466
|
+
tile,
|
|
2467
|
+
body,
|
|
2468
|
+
trailing
|
|
2469
|
+
]
|
|
2470
|
+
}
|
|
2471
|
+
) });
|
|
2472
|
+
}
|
|
2473
|
+
if (isLocked) {
|
|
2474
|
+
return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
|
|
2475
|
+
"button",
|
|
2476
|
+
{
|
|
2477
|
+
type: "button",
|
|
2478
|
+
onClick: () => onLockedAppClick?.(app),
|
|
2479
|
+
className: cn(
|
|
2480
|
+
"group flex w-full items-center gap-3 rounded-md px-2.5 py-2 text-left transition-colors",
|
|
2481
|
+
onLockedAppClick !== void 0 ? "hover:bg-secondary" : "cursor-default"
|
|
2482
|
+
),
|
|
2483
|
+
title: `Add ${app.name} to your plan`,
|
|
2484
|
+
children: [
|
|
2485
|
+
tile,
|
|
2486
|
+
body,
|
|
2487
|
+
trailing
|
|
2488
|
+
]
|
|
2489
|
+
}
|
|
2490
|
+
) });
|
|
2491
|
+
}
|
|
2492
|
+
return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
|
|
2493
|
+
"a",
|
|
2494
|
+
{
|
|
2495
|
+
href,
|
|
2496
|
+
className: cn(
|
|
2497
|
+
"group flex w-full items-center gap-3 rounded-md px-2.5 py-2 transition-colors hover:bg-secondary",
|
|
2498
|
+
app.current && "bg-accent-soft hover:bg-accent-soft"
|
|
2499
|
+
),
|
|
2500
|
+
children: [
|
|
2501
|
+
tile,
|
|
2502
|
+
body,
|
|
2503
|
+
trailing
|
|
2504
|
+
]
|
|
2505
|
+
}
|
|
2506
|
+
) });
|
|
2507
|
+
}
|
|
2508
|
+
function buildOrgUrl2(url, orgSlug) {
|
|
2509
|
+
if (url === "" || orgSlug === "") {
|
|
2510
|
+
return url;
|
|
2511
|
+
}
|
|
2512
|
+
try {
|
|
2513
|
+
const u = new URL(url);
|
|
2514
|
+
u.pathname = `/org/${encodeURIComponent(orgSlug)}`;
|
|
2515
|
+
return u.toString();
|
|
2516
|
+
} catch {
|
|
2517
|
+
return `${url.replace(/\/$/, "")}/org/${encodeURIComponent(orgSlug)}`;
|
|
2518
|
+
}
|
|
2519
|
+
}
|
|
2520
|
+
function Grid() {
|
|
2521
|
+
return /* @__PURE__ */ jsx("svg", { width: "18", height: "18", viewBox: "0 0 18 18", fill: "none", "aria-hidden": true, children: [1, 8, 15].flatMap(
|
|
2522
|
+
(y) => [1, 8, 15].map((x) => /* @__PURE__ */ jsx(
|
|
2523
|
+
"rect",
|
|
2524
|
+
{
|
|
2525
|
+
x,
|
|
2526
|
+
y,
|
|
2527
|
+
width: "2.5",
|
|
2528
|
+
height: "2.5",
|
|
2529
|
+
rx: "0.6",
|
|
2530
|
+
fill: "currentColor"
|
|
2531
|
+
},
|
|
2532
|
+
`${x}-${y}`
|
|
2533
|
+
))
|
|
2534
|
+
) });
|
|
2535
|
+
}
|
|
2536
|
+
function Lock2() {
|
|
2537
|
+
return /* @__PURE__ */ jsxs(
|
|
2538
|
+
"svg",
|
|
2539
|
+
{
|
|
2540
|
+
width: "10",
|
|
2541
|
+
height: "10",
|
|
2542
|
+
viewBox: "0 0 12 12",
|
|
2543
|
+
fill: "none",
|
|
2544
|
+
stroke: "currentColor",
|
|
2545
|
+
strokeWidth: "1.6",
|
|
2546
|
+
strokeLinecap: "round",
|
|
2547
|
+
strokeLinejoin: "round",
|
|
2548
|
+
"aria-hidden": true,
|
|
2549
|
+
children: [
|
|
2550
|
+
/* @__PURE__ */ jsx("rect", { x: "2.5", y: "5", width: "7", height: "5", rx: "1" }),
|
|
2551
|
+
/* @__PURE__ */ jsx("path", { d: "M4 5V3.5a2 2 0 1 1 4 0V5" })
|
|
2552
|
+
]
|
|
2553
|
+
}
|
|
2554
|
+
);
|
|
2555
|
+
}
|
|
2556
|
+
function Combobox(props) {
|
|
2557
|
+
const {
|
|
2558
|
+
items,
|
|
2559
|
+
placeholder = "Select\u2026",
|
|
2560
|
+
searchPlaceholder = "Search\u2026",
|
|
2561
|
+
emptyText = "Nothing matches.",
|
|
2562
|
+
footerLink,
|
|
2563
|
+
disabled = false,
|
|
2564
|
+
required = false,
|
|
2565
|
+
ariaLabel,
|
|
2566
|
+
triggerClassName,
|
|
2567
|
+
panelClassName,
|
|
2568
|
+
triggerWidth = "auto",
|
|
2569
|
+
LinkComponent,
|
|
2570
|
+
id: idProp,
|
|
2571
|
+
className,
|
|
2572
|
+
value
|
|
2573
|
+
} = props;
|
|
2574
|
+
const groups = useMemo(() => normaliseGroups(items), [items]);
|
|
2575
|
+
const flatOptions = useMemo(
|
|
2576
|
+
() => groups.flatMap((g) => g.options),
|
|
2577
|
+
[groups]
|
|
2578
|
+
);
|
|
2579
|
+
const activeOption = useMemo(
|
|
2580
|
+
() => value !== null && value !== void 0 ? flatOptions.find((opt) => opt.value === value) : null,
|
|
2581
|
+
[flatOptions, value]
|
|
2582
|
+
);
|
|
2583
|
+
const [open, setOpen] = useState(false);
|
|
2584
|
+
const [query, setQuery] = useState("");
|
|
2585
|
+
const [highlightIndex, setHighlightIndex] = useState(-1);
|
|
2586
|
+
const containerRef = useRef(null);
|
|
2587
|
+
const inputRef = useRef(null);
|
|
2588
|
+
const listRef = useRef(null);
|
|
2589
|
+
const reactId = useId();
|
|
2590
|
+
const id = idProp ?? reactId;
|
|
2591
|
+
const listId = `${id}-listbox`;
|
|
2592
|
+
const filteredGroups = useMemo(
|
|
2593
|
+
() => filterGroups(groups, query),
|
|
2594
|
+
[groups, query]
|
|
2595
|
+
);
|
|
2596
|
+
const filteredFlat = useMemo(
|
|
2597
|
+
() => filteredGroups.flatMap((g) => g.options),
|
|
2598
|
+
[filteredGroups]
|
|
2599
|
+
);
|
|
2600
|
+
useEffect(() => {
|
|
2601
|
+
if (!open) return;
|
|
2602
|
+
const firstEnabled = filteredFlat.findIndex(
|
|
2603
|
+
(opt) => opt.disabled !== true
|
|
2604
|
+
);
|
|
2605
|
+
setHighlightIndex(firstEnabled);
|
|
2606
|
+
}, [filteredFlat, open]);
|
|
2607
|
+
useEffect(() => {
|
|
2608
|
+
if (open) {
|
|
2609
|
+
const id2 = window.setTimeout(() => inputRef.current?.focus(), 0);
|
|
2610
|
+
return () => window.clearTimeout(id2);
|
|
2611
|
+
}
|
|
2612
|
+
setQuery("");
|
|
2613
|
+
return void 0;
|
|
2614
|
+
}, [open]);
|
|
2615
|
+
useEffect(() => {
|
|
2616
|
+
if (!open) return;
|
|
2617
|
+
function onPointerDown(event) {
|
|
2618
|
+
if (containerRef.current !== null && !containerRef.current.contains(event.target)) {
|
|
2619
|
+
setOpen(false);
|
|
2620
|
+
}
|
|
2621
|
+
}
|
|
2622
|
+
window.addEventListener("pointerdown", onPointerDown);
|
|
2623
|
+
return () => window.removeEventListener("pointerdown", onPointerDown);
|
|
2624
|
+
}, [open]);
|
|
2625
|
+
const onSelectOption = useCallback(
|
|
2626
|
+
(opt) => {
|
|
2627
|
+
if (opt.disabled === true) return;
|
|
2628
|
+
if (opt.href !== void 0 && LinkComponent !== void 0 && props.onChange === void 0) {
|
|
2629
|
+
setOpen(false);
|
|
2630
|
+
return;
|
|
2631
|
+
}
|
|
2632
|
+
if (props.onChange !== void 0) {
|
|
2633
|
+
props.onChange(opt.value);
|
|
2634
|
+
}
|
|
2635
|
+
setOpen(false);
|
|
2636
|
+
},
|
|
2637
|
+
[LinkComponent, props]
|
|
2638
|
+
);
|
|
2639
|
+
const onKeyDown = useCallback(
|
|
2640
|
+
(event) => {
|
|
2641
|
+
if (event.key === "ArrowDown") {
|
|
2642
|
+
event.preventDefault();
|
|
2643
|
+
setHighlightIndex(
|
|
2644
|
+
(current) => nextEnabledIndex(filteredFlat, current, 1)
|
|
2645
|
+
);
|
|
2646
|
+
return;
|
|
2647
|
+
}
|
|
2648
|
+
if (event.key === "ArrowUp") {
|
|
2649
|
+
event.preventDefault();
|
|
2650
|
+
setHighlightIndex(
|
|
2651
|
+
(current) => nextEnabledIndex(filteredFlat, current, -1)
|
|
2652
|
+
);
|
|
2653
|
+
return;
|
|
2654
|
+
}
|
|
2655
|
+
if (event.key === "Home") {
|
|
2656
|
+
event.preventDefault();
|
|
2657
|
+
setHighlightIndex(
|
|
2658
|
+
filteredFlat.findIndex((o) => o.disabled !== true)
|
|
2659
|
+
);
|
|
2660
|
+
return;
|
|
2661
|
+
}
|
|
2662
|
+
if (event.key === "End") {
|
|
2663
|
+
event.preventDefault();
|
|
2664
|
+
let last = -1;
|
|
2665
|
+
for (let i = 0; i < filteredFlat.length; i++) {
|
|
2666
|
+
const opt = filteredFlat[i];
|
|
2667
|
+
if (opt !== void 0 && opt.disabled !== true) {
|
|
2668
|
+
last = i;
|
|
2669
|
+
}
|
|
2670
|
+
}
|
|
2671
|
+
setHighlightIndex(last);
|
|
2672
|
+
return;
|
|
2673
|
+
}
|
|
2674
|
+
if (event.key === "Enter") {
|
|
2675
|
+
if (highlightIndex >= 0) {
|
|
2676
|
+
event.preventDefault();
|
|
2677
|
+
const opt = filteredFlat[highlightIndex];
|
|
2678
|
+
if (opt !== void 0) {
|
|
2679
|
+
onSelectOption(opt);
|
|
2680
|
+
}
|
|
2681
|
+
}
|
|
2682
|
+
return;
|
|
2683
|
+
}
|
|
2684
|
+
if (event.key === "Escape") {
|
|
2685
|
+
event.preventDefault();
|
|
2686
|
+
setOpen(false);
|
|
2687
|
+
return;
|
|
2688
|
+
}
|
|
2689
|
+
if (event.key === "Tab") {
|
|
2690
|
+
setOpen(false);
|
|
2691
|
+
return;
|
|
2692
|
+
}
|
|
2693
|
+
},
|
|
2694
|
+
[filteredFlat, highlightIndex, onSelectOption]
|
|
2695
|
+
);
|
|
2696
|
+
return /* @__PURE__ */ jsxs(
|
|
2697
|
+
"div",
|
|
2698
|
+
{
|
|
2699
|
+
ref: containerRef,
|
|
2700
|
+
className: cn("relative", triggerWidth === "full" && "w-full", className),
|
|
2701
|
+
children: [
|
|
2702
|
+
/* @__PURE__ */ jsxs(
|
|
2703
|
+
"button",
|
|
2704
|
+
{
|
|
2705
|
+
id,
|
|
2706
|
+
type: "button",
|
|
2707
|
+
role: "combobox",
|
|
2708
|
+
disabled,
|
|
2709
|
+
"aria-expanded": open,
|
|
2710
|
+
"aria-haspopup": "listbox",
|
|
2711
|
+
"aria-controls": listId,
|
|
2712
|
+
"aria-required": required,
|
|
2713
|
+
"aria-label": ariaLabel,
|
|
2714
|
+
onClick: () => setOpen((v) => !v),
|
|
2715
|
+
className: cn(
|
|
2716
|
+
"flex h-9 items-center gap-2 rounded-md border border-input bg-card px-3 text-left text-[13px] text-foreground transition-colors",
|
|
2717
|
+
triggerWidth === "full" && "w-full",
|
|
2718
|
+
"hover:border-border-strong focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none",
|
|
2719
|
+
open && "border-border-strong ring-2 ring-ring/30",
|
|
2720
|
+
disabled && "cursor-not-allowed opacity-60",
|
|
2721
|
+
triggerClassName
|
|
2722
|
+
),
|
|
2723
|
+
children: [
|
|
2724
|
+
activeOption?.avatar !== void 0 ? /* @__PURE__ */ jsx(
|
|
2725
|
+
ComboboxAvatarChip,
|
|
2726
|
+
{
|
|
2727
|
+
avatar: activeOption.avatar,
|
|
2728
|
+
fallbackLabel: activeOption.label
|
|
2729
|
+
}
|
|
2730
|
+
) : activeOption?.icon !== void 0 ? /* @__PURE__ */ jsx(
|
|
2731
|
+
activeOption.icon,
|
|
2732
|
+
{
|
|
2733
|
+
className: "h-3.5 w-3.5 shrink-0 text-muted-foreground",
|
|
2734
|
+
strokeWidth: 1.7
|
|
2735
|
+
}
|
|
2736
|
+
) : null,
|
|
2737
|
+
/* @__PURE__ */ jsx(
|
|
2738
|
+
"span",
|
|
2739
|
+
{
|
|
2740
|
+
className: cn(
|
|
2741
|
+
"flex-1 truncate",
|
|
2742
|
+
activeOption === null || activeOption === void 0 ? "text-muted-foreground" : ""
|
|
2743
|
+
),
|
|
2744
|
+
children: activeOption !== null && activeOption !== void 0 ? activeOption.label : placeholder
|
|
2745
|
+
}
|
|
2746
|
+
),
|
|
2747
|
+
/* @__PURE__ */ jsx(Chevron4, { open })
|
|
2748
|
+
]
|
|
2749
|
+
}
|
|
2750
|
+
),
|
|
2751
|
+
open ? /* @__PURE__ */ jsxs(
|
|
2752
|
+
"div",
|
|
2753
|
+
{
|
|
2754
|
+
className: cn(
|
|
2755
|
+
"absolute left-0 right-0 top-full z-30 mt-1 overflow-hidden rounded-md border border-border bg-popover shadow-lg",
|
|
2756
|
+
panelClassName
|
|
2757
|
+
),
|
|
2758
|
+
children: [
|
|
2759
|
+
/* @__PURE__ */ jsx("div", { className: "border-b border-border/60 px-2 py-2", children: /* @__PURE__ */ jsxs("div", { className: "relative", children: [
|
|
2760
|
+
/* @__PURE__ */ jsx(SearchIcon4, {}),
|
|
2761
|
+
/* @__PURE__ */ jsx(
|
|
2762
|
+
"input",
|
|
2763
|
+
{
|
|
2764
|
+
ref: inputRef,
|
|
2765
|
+
type: "text",
|
|
2766
|
+
value: query,
|
|
2767
|
+
onChange: (e) => setQuery(e.target.value),
|
|
2768
|
+
onKeyDown,
|
|
2769
|
+
placeholder: searchPlaceholder,
|
|
2770
|
+
className: "h-8 w-full rounded-sm border-0 bg-transparent pl-7 pr-2 text-[13px] outline-none placeholder:text-muted-foreground/70",
|
|
2771
|
+
"aria-controls": listId,
|
|
2772
|
+
"aria-autocomplete": "list",
|
|
2773
|
+
"aria-activedescendant": highlightIndex >= 0 ? `${listId}-${highlightIndex}` : void 0
|
|
2774
|
+
}
|
|
2775
|
+
)
|
|
2776
|
+
] }) }),
|
|
2777
|
+
/* @__PURE__ */ jsx(
|
|
2778
|
+
"ul",
|
|
2779
|
+
{
|
|
2780
|
+
id: listId,
|
|
2781
|
+
ref: listRef,
|
|
2782
|
+
role: "listbox",
|
|
2783
|
+
className: "max-h-72 overflow-y-auto py-1",
|
|
2784
|
+
children: filteredFlat.length === 0 ? /* @__PURE__ */ jsx("li", { className: "px-3 py-6 text-center text-[13px] text-muted-foreground", children: emptyText }) : filteredGroups.map((group, groupIndex) => /* @__PURE__ */ jsx(
|
|
2785
|
+
ComboboxGroupRows,
|
|
2786
|
+
{
|
|
2787
|
+
group,
|
|
2788
|
+
flatOptions: filteredFlat,
|
|
2789
|
+
listId,
|
|
2790
|
+
highlightIndex,
|
|
2791
|
+
activeValue: value ?? null,
|
|
2792
|
+
LinkComponent,
|
|
2793
|
+
onSelect: onSelectOption,
|
|
2794
|
+
onHover: (idx) => setHighlightIndex(idx)
|
|
2795
|
+
},
|
|
2796
|
+
group.label || `group-${groupIndex}`
|
|
2797
|
+
))
|
|
2798
|
+
}
|
|
2799
|
+
),
|
|
2800
|
+
footerLink !== void 0 ? /* @__PURE__ */ jsx(
|
|
2801
|
+
FooterLink,
|
|
2802
|
+
{
|
|
2803
|
+
label: footerLink.label,
|
|
2804
|
+
href: footerLink.href,
|
|
2805
|
+
LinkComponent,
|
|
2806
|
+
onAfterClick: () => setOpen(false)
|
|
2807
|
+
}
|
|
2808
|
+
) : null
|
|
2809
|
+
]
|
|
2810
|
+
}
|
|
2811
|
+
) : null
|
|
2812
|
+
]
|
|
2813
|
+
}
|
|
2814
|
+
);
|
|
2815
|
+
}
|
|
2816
|
+
function ComboboxGroupRows({
|
|
2817
|
+
group,
|
|
2818
|
+
flatOptions,
|
|
2819
|
+
listId,
|
|
2820
|
+
highlightIndex,
|
|
2821
|
+
activeValue,
|
|
2822
|
+
LinkComponent,
|
|
2823
|
+
onSelect,
|
|
2824
|
+
onHover
|
|
2825
|
+
}) {
|
|
2826
|
+
if (group.options.length === 0) return null;
|
|
2827
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2828
|
+
group.label !== "" ? /* @__PURE__ */ jsx(
|
|
2829
|
+
"li",
|
|
2830
|
+
{
|
|
2831
|
+
role: "presentation",
|
|
2832
|
+
className: "text-mono px-3 pt-2 pb-1 text-[10px] font-semibold tracking-[0.18em] text-muted-foreground/70 uppercase",
|
|
2833
|
+
children: group.label
|
|
2834
|
+
}
|
|
2835
|
+
) : null,
|
|
2836
|
+
group.options.map((opt) => {
|
|
2837
|
+
const flatIndex = flatOptions.indexOf(opt);
|
|
2838
|
+
const isHighlighted = flatIndex === highlightIndex;
|
|
2839
|
+
const isActive = opt.value === activeValue;
|
|
2840
|
+
const inner = /* @__PURE__ */ jsxs("div", { className: "flex w-full min-w-0 items-center gap-2.5 px-2.5 py-2", children: [
|
|
2841
|
+
opt.avatar !== void 0 ? /* @__PURE__ */ jsx(
|
|
2842
|
+
ComboboxAvatarChip,
|
|
2843
|
+
{
|
|
2844
|
+
avatar: opt.avatar,
|
|
2845
|
+
fallbackLabel: opt.label
|
|
2846
|
+
}
|
|
2847
|
+
) : opt.icon !== void 0 ? /* @__PURE__ */ jsx(
|
|
2848
|
+
opt.icon,
|
|
2849
|
+
{
|
|
2850
|
+
className: "h-3.5 w-3.5 shrink-0 text-muted-foreground",
|
|
2851
|
+
strokeWidth: 1.7
|
|
2852
|
+
}
|
|
2853
|
+
) : null,
|
|
2854
|
+
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col leading-tight", children: [
|
|
2855
|
+
/* @__PURE__ */ jsx("span", { className: "truncate text-[13px] font-medium text-foreground", children: opt.label }),
|
|
2856
|
+
opt.secondary !== void 0 ? /* @__PURE__ */ jsx("span", { className: "text-mono truncate text-[10px] text-muted-foreground", children: opt.secondary }) : null
|
|
2857
|
+
] }),
|
|
2858
|
+
isActive ? /* @__PURE__ */ jsx(Check2, {}) : null
|
|
2859
|
+
] });
|
|
2860
|
+
const className = cn(
|
|
2861
|
+
"group block w-full rounded-sm transition-colors text-left",
|
|
2862
|
+
opt.disabled === true && "cursor-not-allowed opacity-50",
|
|
2863
|
+
isHighlighted && opt.disabled !== true && "bg-secondary",
|
|
2864
|
+
isActive && "bg-accent-soft hover:bg-accent-soft/80"
|
|
2865
|
+
);
|
|
2866
|
+
if (opt.href !== void 0 && LinkComponent !== void 0) {
|
|
2867
|
+
return /* @__PURE__ */ jsx(
|
|
2868
|
+
"li",
|
|
2869
|
+
{
|
|
2870
|
+
id: `${listId}-${flatIndex}`,
|
|
2871
|
+
role: "option",
|
|
2872
|
+
"aria-selected": isActive,
|
|
2873
|
+
"aria-disabled": opt.disabled === true,
|
|
2874
|
+
onMouseEnter: () => onHover(flatIndex),
|
|
2875
|
+
children: /* @__PURE__ */ jsx(
|
|
2876
|
+
LinkComponent,
|
|
2877
|
+
{
|
|
2878
|
+
href: opt.href,
|
|
2879
|
+
prefetch: true,
|
|
2880
|
+
className,
|
|
2881
|
+
children: inner
|
|
2882
|
+
}
|
|
2883
|
+
)
|
|
2884
|
+
},
|
|
2885
|
+
opt.value
|
|
2886
|
+
);
|
|
2887
|
+
}
|
|
2888
|
+
return /* @__PURE__ */ jsx(
|
|
2889
|
+
"li",
|
|
2890
|
+
{
|
|
2891
|
+
id: `${listId}-${flatIndex}`,
|
|
2892
|
+
role: "option",
|
|
2893
|
+
"aria-selected": isActive,
|
|
2894
|
+
"aria-disabled": opt.disabled === true,
|
|
2895
|
+
onMouseEnter: () => onHover(flatIndex),
|
|
2896
|
+
children: /* @__PURE__ */ jsx(
|
|
2897
|
+
"button",
|
|
2898
|
+
{
|
|
2899
|
+
type: "button",
|
|
2900
|
+
disabled: opt.disabled === true,
|
|
2901
|
+
onClick: () => onSelect(opt),
|
|
2902
|
+
className,
|
|
2903
|
+
children: inner
|
|
2904
|
+
}
|
|
2905
|
+
)
|
|
2906
|
+
},
|
|
2907
|
+
opt.value
|
|
2908
|
+
);
|
|
2909
|
+
})
|
|
2910
|
+
] });
|
|
2911
|
+
}
|
|
2912
|
+
function FooterLink({
|
|
2913
|
+
label,
|
|
2914
|
+
href,
|
|
2915
|
+
LinkComponent,
|
|
2916
|
+
onAfterClick
|
|
2917
|
+
}) {
|
|
2918
|
+
const className = "flex items-center gap-2 border-t border-border/60 px-3 py-2.5 text-[13px] font-medium text-info transition-colors hover:bg-info-soft";
|
|
2919
|
+
if (LinkComponent !== void 0) {
|
|
2920
|
+
return /* @__PURE__ */ jsxs(
|
|
2921
|
+
LinkComponent,
|
|
2922
|
+
{
|
|
2923
|
+
href,
|
|
2924
|
+
prefetch: true,
|
|
2925
|
+
className,
|
|
2926
|
+
children: [
|
|
2927
|
+
/* @__PURE__ */ jsx(Plus2, {}),
|
|
2928
|
+
/* @__PURE__ */ jsx("span", { children: label })
|
|
2929
|
+
]
|
|
2930
|
+
}
|
|
2931
|
+
);
|
|
2932
|
+
}
|
|
2933
|
+
return /* @__PURE__ */ jsxs(
|
|
2934
|
+
"a",
|
|
2935
|
+
{
|
|
2936
|
+
href,
|
|
2937
|
+
className,
|
|
2938
|
+
onClick: onAfterClick,
|
|
2939
|
+
children: [
|
|
2940
|
+
/* @__PURE__ */ jsx(Plus2, {}),
|
|
2941
|
+
/* @__PURE__ */ jsx("span", { children: label })
|
|
2942
|
+
]
|
|
2943
|
+
}
|
|
2944
|
+
);
|
|
2945
|
+
}
|
|
2946
|
+
function ComboboxAvatarChip({
|
|
2947
|
+
avatar,
|
|
2948
|
+
fallbackLabel
|
|
2949
|
+
}) {
|
|
2950
|
+
const initials = avatar.initials ?? deriveInitials3(fallbackLabel);
|
|
2951
|
+
const style = avatar.src === void 0 && avatar.accent !== void 0 ? { backgroundColor: avatar.accent, color: "#fff" } : void 0;
|
|
2952
|
+
if (avatar.src !== void 0) {
|
|
2953
|
+
return /* @__PURE__ */ jsx(
|
|
2954
|
+
"img",
|
|
2955
|
+
{
|
|
2956
|
+
src: avatar.src,
|
|
2957
|
+
alt: "",
|
|
2958
|
+
className: "h-5 w-5 shrink-0 rounded-sm bg-secondary object-cover",
|
|
2959
|
+
loading: "lazy"
|
|
2960
|
+
}
|
|
2961
|
+
);
|
|
2962
|
+
}
|
|
2963
|
+
return /* @__PURE__ */ jsx(
|
|
2964
|
+
"span",
|
|
2965
|
+
{
|
|
2966
|
+
style,
|
|
2967
|
+
className: cn(
|
|
2968
|
+
"flex h-5 w-5 shrink-0 items-center justify-center rounded-sm text-[9px] font-semibold tracking-tight",
|
|
2969
|
+
avatar.accent === void 0 && "bg-info-soft text-info"
|
|
2970
|
+
),
|
|
2971
|
+
children: initials
|
|
2972
|
+
}
|
|
2973
|
+
);
|
|
2974
|
+
}
|
|
2975
|
+
function deriveInitials3(label) {
|
|
2976
|
+
const parts = label.trim().split(/\s+/);
|
|
2977
|
+
if (parts.length >= 2) {
|
|
2978
|
+
return ((parts[0]?.[0] ?? "") + (parts[1]?.[0] ?? "")).toUpperCase();
|
|
2979
|
+
}
|
|
2980
|
+
return (label.slice(0, 2) || "?").toUpperCase();
|
|
2981
|
+
}
|
|
2982
|
+
function normaliseGroups(items) {
|
|
2983
|
+
if (items.length === 0) return [];
|
|
2984
|
+
const first = items[0];
|
|
2985
|
+
if (first !== void 0 && "options" in first) {
|
|
2986
|
+
return items;
|
|
2987
|
+
}
|
|
2988
|
+
return [{ label: "", options: items }];
|
|
2989
|
+
}
|
|
2990
|
+
function filterGroups(groups, query) {
|
|
2991
|
+
const q = query.trim().toLowerCase();
|
|
2992
|
+
if (q === "") return groups;
|
|
2993
|
+
return groups.map((group) => ({
|
|
2994
|
+
label: group.label,
|
|
2995
|
+
options: group.options.filter((opt) => matchesQuery(opt, q))
|
|
2996
|
+
})).filter((group) => group.options.length > 0);
|
|
2997
|
+
}
|
|
2998
|
+
function matchesQuery(option, q) {
|
|
2999
|
+
const haystack = [
|
|
3000
|
+
option.label,
|
|
3001
|
+
option.secondary ?? "",
|
|
3002
|
+
...option.keywords ?? []
|
|
3003
|
+
].join(" ").toLowerCase();
|
|
3004
|
+
return haystack.includes(q);
|
|
3005
|
+
}
|
|
3006
|
+
function nextEnabledIndex(options, current, direction) {
|
|
3007
|
+
const total = options.length;
|
|
3008
|
+
if (total === 0) return -1;
|
|
3009
|
+
let i = current;
|
|
3010
|
+
for (let step = 0; step < total; step++) {
|
|
3011
|
+
i = (i + direction + total) % total;
|
|
3012
|
+
const opt = options[i];
|
|
3013
|
+
if (opt !== void 0 && opt.disabled !== true) {
|
|
3014
|
+
return i;
|
|
3015
|
+
}
|
|
3016
|
+
}
|
|
3017
|
+
return -1;
|
|
3018
|
+
}
|
|
3019
|
+
function Chevron4({ open }) {
|
|
3020
|
+
return /* @__PURE__ */ jsx(
|
|
3021
|
+
"svg",
|
|
3022
|
+
{
|
|
3023
|
+
width: "14",
|
|
3024
|
+
height: "14",
|
|
3025
|
+
viewBox: "0 0 14 14",
|
|
3026
|
+
fill: "none",
|
|
3027
|
+
stroke: "currentColor",
|
|
3028
|
+
strokeWidth: "1.6",
|
|
3029
|
+
strokeLinecap: "round",
|
|
3030
|
+
strokeLinejoin: "round",
|
|
3031
|
+
"aria-hidden": true,
|
|
3032
|
+
className: cn(
|
|
3033
|
+
"shrink-0 text-muted-foreground transition-transform duration-150",
|
|
3034
|
+
open && "rotate-180"
|
|
3035
|
+
),
|
|
3036
|
+
children: /* @__PURE__ */ jsx("path", { d: "m3.5 5.5 3.5 3.5 3.5-3.5" })
|
|
3037
|
+
}
|
|
3038
|
+
);
|
|
3039
|
+
}
|
|
3040
|
+
function Check2() {
|
|
3041
|
+
return /* @__PURE__ */ jsx(
|
|
3042
|
+
"svg",
|
|
3043
|
+
{
|
|
3044
|
+
width: "14",
|
|
3045
|
+
height: "14",
|
|
3046
|
+
viewBox: "0 0 14 14",
|
|
3047
|
+
fill: "none",
|
|
3048
|
+
stroke: "currentColor",
|
|
3049
|
+
strokeWidth: "1.6",
|
|
3050
|
+
strokeLinecap: "round",
|
|
3051
|
+
strokeLinejoin: "round",
|
|
3052
|
+
className: "shrink-0 text-accent",
|
|
3053
|
+
"aria-hidden": true,
|
|
3054
|
+
children: /* @__PURE__ */ jsx("path", { d: "m3 7.5 2.5 2.5L11 4.5" })
|
|
3055
|
+
}
|
|
3056
|
+
);
|
|
3057
|
+
}
|
|
3058
|
+
function Plus2() {
|
|
3059
|
+
return /* @__PURE__ */ jsx(
|
|
3060
|
+
"svg",
|
|
3061
|
+
{
|
|
3062
|
+
width: "12",
|
|
3063
|
+
height: "12",
|
|
3064
|
+
viewBox: "0 0 12 12",
|
|
3065
|
+
fill: "none",
|
|
3066
|
+
stroke: "currentColor",
|
|
3067
|
+
strokeWidth: "1.8",
|
|
3068
|
+
strokeLinecap: "round",
|
|
3069
|
+
strokeLinejoin: "round",
|
|
3070
|
+
className: "shrink-0",
|
|
3071
|
+
"aria-hidden": true,
|
|
3072
|
+
children: /* @__PURE__ */ jsx("path", { d: "M6 2v8M2 6h8" })
|
|
3073
|
+
}
|
|
3074
|
+
);
|
|
3075
|
+
}
|
|
3076
|
+
function SearchIcon4() {
|
|
3077
|
+
return /* @__PURE__ */ jsxs(
|
|
3078
|
+
"svg",
|
|
3079
|
+
{
|
|
3080
|
+
width: "14",
|
|
3081
|
+
height: "14",
|
|
3082
|
+
viewBox: "0 0 14 14",
|
|
3083
|
+
fill: "none",
|
|
3084
|
+
stroke: "currentColor",
|
|
3085
|
+
strokeWidth: "1.6",
|
|
3086
|
+
strokeLinecap: "round",
|
|
3087
|
+
strokeLinejoin: "round",
|
|
3088
|
+
className: "pointer-events-none absolute left-1.5 top-1/2 -translate-y-1/2 text-muted-foreground",
|
|
3089
|
+
"aria-hidden": true,
|
|
3090
|
+
children: [
|
|
3091
|
+
/* @__PURE__ */ jsx("circle", { cx: "6", cy: "6", r: "4.25" }),
|
|
3092
|
+
/* @__PURE__ */ jsx("path", { d: "m11.5 11.5-2.5-2.5" })
|
|
3093
|
+
]
|
|
3094
|
+
}
|
|
3095
|
+
);
|
|
3096
|
+
}
|
|
3097
|
+
function DataPanel({
|
|
3098
|
+
className,
|
|
3099
|
+
children,
|
|
3100
|
+
...rest
|
|
3101
|
+
}) {
|
|
3102
|
+
return /* @__PURE__ */ jsx(
|
|
3103
|
+
"section",
|
|
3104
|
+
{
|
|
3105
|
+
className: cn(
|
|
3106
|
+
"rounded-lg border border-border bg-card text-card-foreground shadow-[0_1px_2px_rgba(0,0,0,0.02)]",
|
|
3107
|
+
className
|
|
3108
|
+
),
|
|
3109
|
+
...rest,
|
|
3110
|
+
children
|
|
3111
|
+
}
|
|
3112
|
+
);
|
|
3113
|
+
}
|
|
3114
|
+
function SectionHeader({
|
|
3115
|
+
title,
|
|
3116
|
+
description,
|
|
3117
|
+
actions,
|
|
3118
|
+
className
|
|
3119
|
+
}) {
|
|
3120
|
+
return /* @__PURE__ */ jsxs(
|
|
3121
|
+
"header",
|
|
3122
|
+
{
|
|
3123
|
+
className: cn(
|
|
3124
|
+
"flex flex-col gap-3 border-b border-border px-5 py-4 sm:flex-row sm:items-center sm:justify-between",
|
|
3125
|
+
className
|
|
3126
|
+
),
|
|
3127
|
+
children: [
|
|
3128
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
|
|
3129
|
+
/* @__PURE__ */ jsx("h2", { className: "text-[15px] leading-none font-semibold text-foreground", children: title }),
|
|
3130
|
+
description ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: description }) : null
|
|
3131
|
+
] }),
|
|
3132
|
+
actions ? /* @__PURE__ */ jsx("div", { className: "flex shrink-0 items-center gap-2", children: actions }) : null
|
|
3133
|
+
]
|
|
3134
|
+
}
|
|
3135
|
+
);
|
|
3136
|
+
}
|
|
3137
|
+
function PanelBody({
|
|
3138
|
+
className,
|
|
3139
|
+
children,
|
|
3140
|
+
...rest
|
|
3141
|
+
}) {
|
|
3142
|
+
return /* @__PURE__ */ jsx("div", { className: cn("px-5 py-4", className), ...rest, children });
|
|
3143
|
+
}
|
|
3144
|
+
function Heading({
|
|
3145
|
+
title,
|
|
3146
|
+
description,
|
|
3147
|
+
variant = "default"
|
|
3148
|
+
}) {
|
|
3149
|
+
return /* @__PURE__ */ jsxs("header", { className: variant === "small" ? "" : "mb-8 space-y-0.5", children: [
|
|
3150
|
+
/* @__PURE__ */ jsx(
|
|
3151
|
+
"h2",
|
|
3152
|
+
{
|
|
3153
|
+
className: variant === "small" ? "mb-0.5 text-base font-medium" : "text-xl font-semibold tracking-tight",
|
|
3154
|
+
children: title
|
|
3155
|
+
}
|
|
3156
|
+
),
|
|
3157
|
+
description && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: description })
|
|
3158
|
+
] });
|
|
3159
|
+
}
|
|
3160
|
+
function KeyValue({
|
|
3161
|
+
label,
|
|
3162
|
+
children,
|
|
3163
|
+
mono = true,
|
|
3164
|
+
className
|
|
3165
|
+
}) {
|
|
3166
|
+
return /* @__PURE__ */ jsxs(
|
|
3167
|
+
"div",
|
|
3168
|
+
{
|
|
3169
|
+
className: cn(
|
|
3170
|
+
"grid grid-cols-[140px_1fr] items-baseline gap-x-6 border-b border-border py-3 last:border-b-0",
|
|
3171
|
+
className
|
|
3172
|
+
),
|
|
3173
|
+
children: [
|
|
3174
|
+
/* @__PURE__ */ jsx("dt", { className: "text-eyebrow text-muted-foreground", children: label }),
|
|
3175
|
+
/* @__PURE__ */ jsx(
|
|
3176
|
+
"dd",
|
|
3177
|
+
{
|
|
3178
|
+
className: cn(
|
|
3179
|
+
"text-sm break-all text-foreground",
|
|
3180
|
+
mono && "text-mono text-[13px]"
|
|
3181
|
+
),
|
|
3182
|
+
children
|
|
3183
|
+
}
|
|
3184
|
+
)
|
|
3185
|
+
]
|
|
3186
|
+
}
|
|
3187
|
+
);
|
|
3188
|
+
}
|
|
3189
|
+
function KeyValueList({
|
|
3190
|
+
children,
|
|
3191
|
+
className
|
|
3192
|
+
}) {
|
|
3193
|
+
return /* @__PURE__ */ jsx("dl", { className: cn("px-6", className), children });
|
|
3194
|
+
}
|
|
3195
|
+
function PageHeader({
|
|
3196
|
+
title,
|
|
3197
|
+
eyebrow,
|
|
3198
|
+
description,
|
|
3199
|
+
actions,
|
|
3200
|
+
className
|
|
3201
|
+
}) {
|
|
3202
|
+
return /* @__PURE__ */ jsxs(
|
|
3203
|
+
"header",
|
|
3204
|
+
{
|
|
3205
|
+
className: cn(
|
|
3206
|
+
"flex flex-col gap-3 sm:flex-row sm:items-end sm:justify-between",
|
|
3207
|
+
className
|
|
3208
|
+
),
|
|
3209
|
+
children: [
|
|
3210
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
3211
|
+
eyebrow !== void 0 ? /* @__PURE__ */ jsx("p", { className: "text-mono text-[10px] font-semibold tracking-[0.18em] text-muted-foreground uppercase", children: eyebrow }) : null,
|
|
3212
|
+
/* @__PURE__ */ jsx("h1", { className: "text-2xl leading-tight font-semibold tracking-tight text-foreground", children: title }),
|
|
3213
|
+
description ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: description }) : null
|
|
3214
|
+
] }),
|
|
3215
|
+
actions ? /* @__PURE__ */ jsx("div", { className: "flex shrink-0 flex-wrap items-center gap-2", children: actions }) : null
|
|
3216
|
+
]
|
|
3217
|
+
}
|
|
3218
|
+
);
|
|
3219
|
+
}
|
|
3220
|
+
var widthClass = {
|
|
3221
|
+
comfortable: "max-w-6xl px-6 py-8 lg:px-10 lg:py-10",
|
|
3222
|
+
wide: "max-w-7xl px-6 py-8 lg:px-10 lg:py-10",
|
|
3223
|
+
full: "max-w-none px-6 py-8 lg:px-10 lg:py-10",
|
|
3224
|
+
bleed: "max-w-none p-0"
|
|
3225
|
+
};
|
|
3226
|
+
function PageShell({
|
|
3227
|
+
children,
|
|
3228
|
+
width = "comfortable",
|
|
3229
|
+
className
|
|
3230
|
+
}) {
|
|
3231
|
+
return /* @__PURE__ */ jsx(
|
|
3232
|
+
"div",
|
|
3233
|
+
{
|
|
3234
|
+
className: cn(
|
|
3235
|
+
"mx-auto w-full space-y-6",
|
|
3236
|
+
widthClass[width],
|
|
3237
|
+
className
|
|
3238
|
+
),
|
|
3239
|
+
children
|
|
3240
|
+
}
|
|
3241
|
+
);
|
|
3242
|
+
}
|
|
3243
|
+
var DefaultLink2 = ({ href, className, children, "aria-current": ariaCurrent }) => /* @__PURE__ */ jsx("a", { href, className, "aria-current": ariaCurrent, children });
|
|
3244
|
+
function PageTabs({
|
|
3245
|
+
items,
|
|
3246
|
+
currentPath,
|
|
3247
|
+
LinkComponent = DefaultLink2,
|
|
3248
|
+
actions,
|
|
3249
|
+
className
|
|
3250
|
+
}) {
|
|
3251
|
+
const activeHref = resolveActiveHref(items, currentPath);
|
|
3252
|
+
return /* @__PURE__ */ jsxs(
|
|
3253
|
+
"div",
|
|
3254
|
+
{
|
|
3255
|
+
"data-component": "page-tabs",
|
|
3256
|
+
className: cn(
|
|
3257
|
+
// Card-style chrome — same border, radius, bg as DataPanel
|
|
3258
|
+
// so the tab strip reads as a peer of the content panels
|
|
3259
|
+
// beneath it instead of floating on the page background.
|
|
3260
|
+
// Width matches PageShell content; no sticky positioning.
|
|
3261
|
+
"rounded-lg border border-border bg-card",
|
|
3262
|
+
className
|
|
3263
|
+
),
|
|
3264
|
+
children: [
|
|
3265
|
+
/* @__PURE__ */ jsxs("div", { className: "hidden items-end gap-3 px-3 sm:flex", children: [
|
|
3266
|
+
/* @__PURE__ */ jsx(
|
|
3267
|
+
DesktopStrip,
|
|
3268
|
+
{
|
|
3269
|
+
items,
|
|
3270
|
+
activeHref,
|
|
3271
|
+
LinkComponent
|
|
3272
|
+
}
|
|
3273
|
+
),
|
|
3274
|
+
/* @__PURE__ */ jsx("div", { className: "flex min-h-12 shrink-0 items-center gap-2", children: actions })
|
|
3275
|
+
] }),
|
|
3276
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 p-3 sm:hidden", children: [
|
|
3277
|
+
/* @__PURE__ */ jsx(
|
|
3278
|
+
MobileDropdown,
|
|
3279
|
+
{
|
|
3280
|
+
items,
|
|
3281
|
+
activeHref,
|
|
3282
|
+
LinkComponent
|
|
3283
|
+
}
|
|
3284
|
+
),
|
|
3285
|
+
actions !== void 0 ? /* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center gap-2", children: actions }) : null
|
|
3286
|
+
] })
|
|
3287
|
+
]
|
|
3288
|
+
}
|
|
3289
|
+
);
|
|
3290
|
+
}
|
|
3291
|
+
function DesktopStrip({ items, activeHref, LinkComponent }) {
|
|
3292
|
+
return /* @__PURE__ */ jsx(
|
|
3293
|
+
"nav",
|
|
3294
|
+
{
|
|
3295
|
+
className: "flex min-h-12 flex-1 items-center gap-1 overflow-x-auto overflow-y-hidden py-1.5",
|
|
3296
|
+
role: "tablist",
|
|
3297
|
+
"aria-label": "Section navigation",
|
|
3298
|
+
children: items.map((item) => {
|
|
3299
|
+
const Icon = item.icon;
|
|
3300
|
+
const active = item.href === activeHref;
|
|
3301
|
+
return /* @__PURE__ */ jsxs(
|
|
3302
|
+
LinkComponent,
|
|
3303
|
+
{
|
|
3304
|
+
href: item.href,
|
|
3305
|
+
prefetch: true,
|
|
3306
|
+
"aria-current": active ? "page" : void 0,
|
|
3307
|
+
className: cn(
|
|
3308
|
+
"inline-flex items-center gap-2 rounded-md px-2.5 py-1.5 text-[13px] whitespace-nowrap transition-colors",
|
|
3309
|
+
active ? "bg-secondary font-semibold text-foreground shadow-sm ring-1 ring-border-strong/60" : "text-muted-foreground hover:bg-secondary/60 hover:text-foreground"
|
|
3310
|
+
),
|
|
3311
|
+
children: [
|
|
3312
|
+
Icon !== void 0 ? /* @__PURE__ */ jsx(
|
|
3313
|
+
Icon,
|
|
3314
|
+
{
|
|
3315
|
+
className: cn(
|
|
3316
|
+
"h-3.5 w-3.5 shrink-0",
|
|
3317
|
+
active ? "text-foreground" : "text-muted-foreground"
|
|
3318
|
+
),
|
|
3319
|
+
strokeWidth: 1.7
|
|
3320
|
+
}
|
|
3321
|
+
) : null,
|
|
3322
|
+
/* @__PURE__ */ jsx("span", { children: item.label }),
|
|
3323
|
+
/* @__PURE__ */ jsx(ItemTrailing, { item, active })
|
|
3324
|
+
]
|
|
3325
|
+
},
|
|
3326
|
+
item.href
|
|
3327
|
+
);
|
|
3328
|
+
})
|
|
3329
|
+
}
|
|
3330
|
+
);
|
|
3331
|
+
}
|
|
3332
|
+
function MobileDropdown({ items, activeHref, LinkComponent }) {
|
|
3333
|
+
const [open, setOpen] = useState(false);
|
|
3334
|
+
const ref = useRef(null);
|
|
3335
|
+
const listId = useId();
|
|
3336
|
+
const active = items.find((item) => item.href === activeHref) ?? items[0];
|
|
3337
|
+
useEffect(() => {
|
|
3338
|
+
if (!open) return;
|
|
3339
|
+
function onPointerDown(event) {
|
|
3340
|
+
if (ref.current !== null && !ref.current.contains(event.target)) {
|
|
3341
|
+
setOpen(false);
|
|
3342
|
+
}
|
|
3343
|
+
}
|
|
3344
|
+
function onKey(event) {
|
|
3345
|
+
if (event.key === "Escape") setOpen(false);
|
|
3346
|
+
}
|
|
3347
|
+
window.addEventListener("pointerdown", onPointerDown);
|
|
3348
|
+
window.addEventListener("keydown", onKey);
|
|
3349
|
+
return () => {
|
|
3350
|
+
window.removeEventListener("pointerdown", onPointerDown);
|
|
3351
|
+
window.removeEventListener("keydown", onKey);
|
|
3352
|
+
};
|
|
3353
|
+
}, [open]);
|
|
3354
|
+
if (active === void 0) {
|
|
3355
|
+
return null;
|
|
3356
|
+
}
|
|
3357
|
+
const ActiveIcon = active.icon;
|
|
3358
|
+
return /* @__PURE__ */ jsxs("div", { ref, className: "relative", children: [
|
|
3359
|
+
/* @__PURE__ */ jsxs(
|
|
3360
|
+
"button",
|
|
3361
|
+
{
|
|
3362
|
+
type: "button",
|
|
3363
|
+
onClick: () => setOpen((v) => !v),
|
|
3364
|
+
"aria-expanded": open,
|
|
3365
|
+
"aria-haspopup": "listbox",
|
|
3366
|
+
"aria-controls": listId,
|
|
3367
|
+
className: cn(
|
|
3368
|
+
"flex w-full min-h-11 items-center justify-between gap-2 rounded-md border border-border bg-card px-3 text-left text-[13px] font-medium text-foreground transition-colors hover:border-border-strong",
|
|
3369
|
+
open && "border-border-strong ring-2 ring-ring/30"
|
|
3370
|
+
),
|
|
3371
|
+
children: [
|
|
3372
|
+
/* @__PURE__ */ jsxs("span", { className: "flex min-w-0 items-center gap-2", children: [
|
|
3373
|
+
ActiveIcon !== void 0 ? /* @__PURE__ */ jsx(
|
|
3374
|
+
ActiveIcon,
|
|
3375
|
+
{
|
|
3376
|
+
className: "h-3.5 w-3.5 shrink-0 text-muted-foreground",
|
|
3377
|
+
strokeWidth: 1.7
|
|
3378
|
+
}
|
|
3379
|
+
) : null,
|
|
3380
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: active.label }),
|
|
3381
|
+
/* @__PURE__ */ jsx(ItemTrailing, { item: active, active: true })
|
|
3382
|
+
] }),
|
|
3383
|
+
/* @__PURE__ */ jsx(Chevron5, { open })
|
|
3384
|
+
]
|
|
3385
|
+
}
|
|
3386
|
+
),
|
|
3387
|
+
open ? /* @__PURE__ */ jsx(
|
|
3388
|
+
"ul",
|
|
3389
|
+
{
|
|
3390
|
+
id: listId,
|
|
3391
|
+
role: "listbox",
|
|
3392
|
+
"aria-label": "Section navigation",
|
|
3393
|
+
className: "absolute left-0 right-0 top-full z-30 mt-1 overflow-hidden rounded-md border border-border bg-popover shadow-lg",
|
|
3394
|
+
children: items.map((item) => {
|
|
3395
|
+
const Icon = item.icon;
|
|
3396
|
+
const isActive = item.href === activeHref;
|
|
3397
|
+
return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
|
|
3398
|
+
LinkComponent,
|
|
3399
|
+
{
|
|
3400
|
+
href: item.href,
|
|
3401
|
+
prefetch: true,
|
|
3402
|
+
"aria-current": isActive ? "page" : void 0,
|
|
3403
|
+
className: cn(
|
|
3404
|
+
"flex w-full items-center gap-2 px-3 py-2.5 text-[13px] transition-colors",
|
|
3405
|
+
isActive ? "bg-accent-soft font-semibold text-foreground" : "text-muted-foreground hover:bg-secondary hover:text-foreground"
|
|
3406
|
+
),
|
|
3407
|
+
children: [
|
|
3408
|
+
Icon !== void 0 ? /* @__PURE__ */ jsx(
|
|
3409
|
+
Icon,
|
|
3410
|
+
{
|
|
3411
|
+
className: cn(
|
|
3412
|
+
"h-3.5 w-3.5 shrink-0",
|
|
3413
|
+
isActive ? "text-foreground" : "text-muted-foreground"
|
|
3414
|
+
),
|
|
3415
|
+
strokeWidth: 1.7
|
|
3416
|
+
}
|
|
3417
|
+
) : null,
|
|
3418
|
+
/* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: item.label }),
|
|
3419
|
+
/* @__PURE__ */ jsx(
|
|
3420
|
+
ItemTrailing,
|
|
3421
|
+
{
|
|
3422
|
+
item,
|
|
3423
|
+
active: isActive
|
|
3424
|
+
}
|
|
3425
|
+
)
|
|
3426
|
+
]
|
|
3427
|
+
}
|
|
3428
|
+
) }, item.href);
|
|
3429
|
+
})
|
|
3430
|
+
}
|
|
3431
|
+
) : null
|
|
3432
|
+
] });
|
|
3433
|
+
}
|
|
3434
|
+
function ItemTrailing({
|
|
3435
|
+
item,
|
|
3436
|
+
active
|
|
3437
|
+
}) {
|
|
3438
|
+
if (item.badge !== void 0) {
|
|
3439
|
+
return /* @__PURE__ */ jsx("span", { className: "ml-1 shrink-0", children: item.badge });
|
|
3440
|
+
}
|
|
3441
|
+
if (item.count !== void 0) {
|
|
3442
|
+
return /* @__PURE__ */ jsx(
|
|
3443
|
+
"span",
|
|
3444
|
+
{
|
|
3445
|
+
className: cn(
|
|
3446
|
+
"ml-1 shrink-0 rounded-sm border px-1 text-[10px] font-semibold tracking-tight tabular-nums",
|
|
3447
|
+
active ? "border-border-strong bg-secondary text-foreground" : "border-border bg-secondary/60 text-muted-foreground"
|
|
3448
|
+
),
|
|
3449
|
+
children: item.count
|
|
3450
|
+
}
|
|
3451
|
+
);
|
|
3452
|
+
}
|
|
3453
|
+
return null;
|
|
3454
|
+
}
|
|
3455
|
+
function Chevron5({ open }) {
|
|
3456
|
+
return /* @__PURE__ */ jsx(
|
|
3457
|
+
"svg",
|
|
3458
|
+
{
|
|
3459
|
+
width: "14",
|
|
3460
|
+
height: "14",
|
|
3461
|
+
viewBox: "0 0 14 14",
|
|
3462
|
+
fill: "none",
|
|
3463
|
+
stroke: "currentColor",
|
|
3464
|
+
strokeWidth: "1.6",
|
|
3465
|
+
strokeLinecap: "round",
|
|
3466
|
+
strokeLinejoin: "round",
|
|
3467
|
+
className: cn(
|
|
3468
|
+
"shrink-0 text-muted-foreground transition-transform duration-150",
|
|
3469
|
+
open && "rotate-180"
|
|
3470
|
+
),
|
|
3471
|
+
"aria-hidden": true,
|
|
3472
|
+
children: /* @__PURE__ */ jsx("path", { d: "m3.5 5.5 3.5 3.5 3.5-3.5" })
|
|
3473
|
+
}
|
|
3474
|
+
);
|
|
3475
|
+
}
|
|
3476
|
+
function pathOnly(href) {
|
|
3477
|
+
const queryIndex = href.indexOf("?");
|
|
3478
|
+
const hashIndex = href.indexOf("#");
|
|
3479
|
+
let end = href.length;
|
|
3480
|
+
if (queryIndex !== -1) end = Math.min(end, queryIndex);
|
|
3481
|
+
if (hashIndex !== -1) end = Math.min(end, hashIndex);
|
|
3482
|
+
return href.slice(0, end);
|
|
3483
|
+
}
|
|
3484
|
+
function resolveActiveHref(items, currentPath) {
|
|
3485
|
+
const current = pathOnly(currentPath);
|
|
3486
|
+
const candidates = items.map((item) => {
|
|
3487
|
+
const itemPath = pathOnly(item.href);
|
|
3488
|
+
const matched = item.matchPrefix === true ? current === itemPath || current.startsWith(itemPath + "/") : current === itemPath;
|
|
3489
|
+
return matched ? item.href : null;
|
|
3490
|
+
}).filter((href) => href !== null);
|
|
3491
|
+
if (candidates.length === 0) return null;
|
|
3492
|
+
return candidates.reduce(
|
|
3493
|
+
(longest, href) => href.length > longest.length ? href : longest
|
|
3494
|
+
);
|
|
3495
|
+
}
|
|
3496
|
+
var DefaultLink3 = ({ href, className, children }) => /* @__PURE__ */ jsx("a", { href, className, children });
|
|
3497
|
+
function RowLink({
|
|
3498
|
+
href,
|
|
3499
|
+
children,
|
|
3500
|
+
trailing,
|
|
3501
|
+
LinkComponent = DefaultLink3,
|
|
3502
|
+
active = false,
|
|
3503
|
+
className
|
|
3504
|
+
}) {
|
|
3505
|
+
return /* @__PURE__ */ jsx(
|
|
3506
|
+
LinkComponent,
|
|
3507
|
+
{
|
|
3508
|
+
href,
|
|
3509
|
+
prefetch: true,
|
|
3510
|
+
className: cn(
|
|
3511
|
+
"group flex w-full items-center gap-4 px-5 py-3.5 transition-colors focus-visible:bg-secondary/60 focus-visible:outline-none",
|
|
3512
|
+
active ? "bg-accent-soft hover:bg-accent-soft/80" : "hover:bg-secondary/60",
|
|
3513
|
+
className
|
|
3514
|
+
),
|
|
3515
|
+
children: /* @__PURE__ */ jsxs(
|
|
3516
|
+
"span",
|
|
3517
|
+
{
|
|
3518
|
+
"data-component": "row-link",
|
|
3519
|
+
"data-active": active ? "true" : void 0,
|
|
3520
|
+
onClickCapture: (event) => {
|
|
3521
|
+
const target = event.target;
|
|
3522
|
+
if (target.closest(
|
|
3523
|
+
'button, a, input, select, textarea, [role="button"], [data-stop-row-click]'
|
|
3524
|
+
) !== null && target.closest("[data-row-body]") === null) {
|
|
3525
|
+
event.stopPropagation();
|
|
3526
|
+
}
|
|
3527
|
+
},
|
|
3528
|
+
className: "flex w-full items-center gap-4",
|
|
3529
|
+
children: [
|
|
3530
|
+
/* @__PURE__ */ jsx(
|
|
3531
|
+
"span",
|
|
3532
|
+
{
|
|
3533
|
+
"data-row-body": true,
|
|
3534
|
+
className: "flex min-w-0 flex-1 items-center gap-4",
|
|
3535
|
+
children
|
|
3536
|
+
}
|
|
3537
|
+
),
|
|
3538
|
+
trailing !== void 0 ? /* @__PURE__ */ jsx("span", { className: "flex shrink-0 items-center gap-2", children: trailing }) : null
|
|
3539
|
+
]
|
|
3540
|
+
}
|
|
3541
|
+
)
|
|
3542
|
+
}
|
|
3543
|
+
);
|
|
3544
|
+
}
|
|
3545
|
+
var toneStyles2 = {
|
|
3546
|
+
info: { bg: "bg-info-soft", fg: "text-info" },
|
|
3547
|
+
success: { bg: "bg-success-soft", fg: "text-success" },
|
|
3548
|
+
warning: { bg: "bg-warning-soft", fg: "text-warning" },
|
|
3549
|
+
destructive: { bg: "bg-destructive-soft", fg: "text-destructive" },
|
|
3550
|
+
neutral: { bg: "bg-secondary", fg: "text-foreground" }
|
|
3551
|
+
};
|
|
3552
|
+
function StatTile({
|
|
3553
|
+
label,
|
|
3554
|
+
value,
|
|
3555
|
+
suffix,
|
|
3556
|
+
icon: Icon,
|
|
3557
|
+
tone = "info",
|
|
3558
|
+
className
|
|
3559
|
+
}) {
|
|
3560
|
+
const styles = toneStyles2[tone];
|
|
3561
|
+
return /* @__PURE__ */ jsxs(
|
|
3562
|
+
"div",
|
|
3563
|
+
{
|
|
3564
|
+
className: cn(
|
|
3565
|
+
"flex items-center gap-3 rounded-lg border border-border bg-card p-4 shadow-[0_1px_2px_rgba(0,0,0,0.02)] transition-colors hover:border-border-strong",
|
|
3566
|
+
className
|
|
3567
|
+
),
|
|
3568
|
+
children: [
|
|
3569
|
+
/* @__PURE__ */ jsx(
|
|
3570
|
+
"div",
|
|
3571
|
+
{
|
|
3572
|
+
className: cn(
|
|
3573
|
+
"flex h-10 w-10 shrink-0 items-center justify-center rounded-md",
|
|
3574
|
+
styles.bg
|
|
3575
|
+
),
|
|
3576
|
+
children: /* @__PURE__ */ jsx(Icon, { className: cn("h-5 w-5", styles.fg), strokeWidth: 1.8 })
|
|
3577
|
+
}
|
|
3578
|
+
),
|
|
3579
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1 space-y-0.5", children: [
|
|
3580
|
+
/* @__PURE__ */ jsx("p", { className: "tabular text-2xl leading-none font-semibold tracking-tight text-foreground", children: value }),
|
|
3581
|
+
/* @__PURE__ */ jsx("p", { className: "truncate text-xs text-muted-foreground", children: label }),
|
|
3582
|
+
suffix ? /* @__PURE__ */ jsx("p", { className: "text-mono truncate text-[10px] text-muted-foreground/80", children: suffix }) : null
|
|
3583
|
+
] })
|
|
3584
|
+
]
|
|
3585
|
+
}
|
|
3586
|
+
);
|
|
3587
|
+
}
|
|
3588
|
+
|
|
3589
|
+
// src/utils/url-state.ts
|
|
3590
|
+
function urlString(defaultValue = "") {
|
|
3591
|
+
return {
|
|
3592
|
+
parse: (raw) => raw ?? defaultValue,
|
|
3593
|
+
serialize: (value) => value === "" ? null : value,
|
|
3594
|
+
isDefault: (value) => value === defaultValue
|
|
3595
|
+
};
|
|
3596
|
+
}
|
|
3597
|
+
function urlNumber(defaultValue, options = {}) {
|
|
3598
|
+
const clamp = (n) => {
|
|
3599
|
+
if (options.min !== void 0 && n < options.min) return options.min;
|
|
3600
|
+
if (options.max !== void 0 && n > options.max) return options.max;
|
|
3601
|
+
return n;
|
|
3602
|
+
};
|
|
3603
|
+
return {
|
|
3604
|
+
parse: (raw) => {
|
|
3605
|
+
if (raw === null) return defaultValue;
|
|
3606
|
+
const parsed = Number.parseInt(raw, 10);
|
|
3607
|
+
if (Number.isNaN(parsed)) return defaultValue;
|
|
3608
|
+
return clamp(parsed);
|
|
3609
|
+
},
|
|
3610
|
+
serialize: (value) => String(value),
|
|
3611
|
+
isDefault: (value) => value === defaultValue
|
|
3612
|
+
};
|
|
3613
|
+
}
|
|
3614
|
+
function urlEnum(values, defaultValue) {
|
|
3615
|
+
const allowed = new Set(values);
|
|
3616
|
+
return {
|
|
3617
|
+
parse: (raw) => raw !== null && allowed.has(raw) ? raw : defaultValue,
|
|
3618
|
+
serialize: (value) => value,
|
|
3619
|
+
isDefault: (value) => value === defaultValue
|
|
3620
|
+
};
|
|
3621
|
+
}
|
|
3622
|
+
function urlBoolean(defaultValue = false) {
|
|
3623
|
+
return {
|
|
3624
|
+
parse: (raw) => raw === null ? defaultValue : raw === "true",
|
|
3625
|
+
serialize: (value) => value ? "true" : "false",
|
|
3626
|
+
isDefault: (value) => value === defaultValue
|
|
3627
|
+
};
|
|
3628
|
+
}
|
|
3629
|
+
function urlStringArray(defaultValue = []) {
|
|
3630
|
+
const defaultKey = [...defaultValue].sort().join(",");
|
|
3631
|
+
return {
|
|
3632
|
+
parse: (raw) => {
|
|
3633
|
+
if (raw === null || raw === "") return [...defaultValue];
|
|
3634
|
+
return raw.split(",").filter((item) => item !== "");
|
|
3635
|
+
},
|
|
3636
|
+
serialize: (value) => value.length === 0 ? null : value.join(","),
|
|
3637
|
+
isDefault: (value) => [...value].sort().join(",") === defaultKey
|
|
3638
|
+
};
|
|
3639
|
+
}
|
|
3640
|
+
function defineUrlSchema(schema) {
|
|
3641
|
+
return schema;
|
|
3642
|
+
}
|
|
3643
|
+
function parseUrlSearch(search, schema) {
|
|
3644
|
+
const normalised = search.startsWith("?") ? search.slice(1) : search;
|
|
3645
|
+
const params = new URLSearchParams(normalised);
|
|
3646
|
+
const state = {};
|
|
3647
|
+
for (const key of Object.keys(schema)) {
|
|
3648
|
+
const field = schema[key];
|
|
3649
|
+
if (field === void 0) continue;
|
|
3650
|
+
state[key] = field.parse(params.get(key));
|
|
3651
|
+
}
|
|
3652
|
+
return state;
|
|
3653
|
+
}
|
|
3654
|
+
function buildUrlSearch(state, schema) {
|
|
3655
|
+
const params = new URLSearchParams();
|
|
3656
|
+
for (const key of Object.keys(schema)) {
|
|
3657
|
+
const field = schema[key];
|
|
3658
|
+
if (field === void 0) continue;
|
|
3659
|
+
const value = state[key];
|
|
3660
|
+
if (field.isDefault(value)) continue;
|
|
3661
|
+
const serialized = field.serialize(value);
|
|
3662
|
+
if (serialized !== null) {
|
|
3663
|
+
params.set(key, serialized);
|
|
3664
|
+
}
|
|
3665
|
+
}
|
|
3666
|
+
return params.toString();
|
|
3667
|
+
}
|
|
3668
|
+
|
|
3669
|
+
export { AppContent, AppLauncher, AppShell, AppSwitcher, CboxAppShell, CboxAppSidebar, CboxBrand, CboxIdProvider, CboxMobileNavDrawer, CboxOrgContextBar, CboxSurfaces, Combobox, ContextAvatarChip, ContextBar, DataPanel, EmptyState, Heading, KeyValue, KeyValueList, NotificationsBell, OrgSwitcher, PageHeader, PageShell, PageTabs, PanelBody, RowLink, SearchTrigger, SectionHeader, StatTile, StatusPill, TopBar, UserMenu, buildUrlSearch, cn, defineUrlSchema, parseUrlSearch, urlBoolean, urlEnum, urlNumber, urlString, urlStringArray, useCboxId, useCboxIdOptional, useCboxSurfaces, useCboxSurfacesOptional };
|
|
3670
|
+
//# sourceMappingURL=index.js.map
|
|
3671
|
+
//# sourceMappingURL=index.js.map
|