@cboxdk/cbox-ui 0.6.0 → 0.7.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/README.md +93 -2
- package/dist/charts.d.ts +82 -0
- package/dist/charts.js +189 -0
- package/dist/charts.js.map +1 -0
- package/dist/chunk-OFTNLF7H.js +55 -0
- package/dist/chunk-OFTNLF7H.js.map +1 -0
- package/dist/engine-4DRZ3POZ.js +10 -0
- package/dist/engine-4DRZ3POZ.js.map +1 -0
- package/dist/index.d.ts +148 -4
- package/dist/index.js +475 -130
- package/dist/index.js.map +1 -1
- package/dist/scale-BaaY5qJs.d.ts +24 -0
- package/package.json +10 -1
- package/tokens/cbox.css +92 -88
package/dist/index.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { cn } from './chunk-OFTNLF7H.js';
|
|
2
|
+
export { cn, niceTicks, timeTicks } from './chunk-OFTNLF7H.js';
|
|
3
|
+
import { createContext, useContext, useMemo, useEffect, useState, useRef, useCallback, useId } from 'react';
|
|
2
4
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
3
|
-
import
|
|
4
|
-
import { twMerge } from 'tailwind-merge';
|
|
5
|
-
import { X, Menu, PanelLeftOpen, PanelLeftClose } from 'lucide-react';
|
|
5
|
+
import { X, Menu, PanelLeftOpen, PanelLeftClose, PinOff, Pin } from 'lucide-react';
|
|
6
6
|
|
|
7
|
-
// src/context/cbox-id.tsx
|
|
8
7
|
var CboxIdContext = createContext(null);
|
|
9
8
|
function CboxIdProvider({
|
|
10
9
|
value,
|
|
@@ -24,9 +23,6 @@ function useCboxId() {
|
|
|
24
23
|
function useCboxIdOptional() {
|
|
25
24
|
return useContext(CboxIdContext);
|
|
26
25
|
}
|
|
27
|
-
function cn(...inputs) {
|
|
28
|
-
return twMerge(clsx(inputs));
|
|
29
|
-
}
|
|
30
26
|
function AppContent({ children, className, ...props }) {
|
|
31
27
|
return /* @__PURE__ */ jsx(
|
|
32
28
|
"main",
|
|
@@ -43,6 +39,27 @@ function AppContent({ children, className, ...props }) {
|
|
|
43
39
|
function AppShell({ children }) {
|
|
44
40
|
return /* @__PURE__ */ jsx("div", { className: "flex h-svh w-full bg-canvas text-foreground", children });
|
|
45
41
|
}
|
|
42
|
+
var Localization = createContext({
|
|
43
|
+
locale: "en",
|
|
44
|
+
t: interpolate,
|
|
45
|
+
formatDate: (value) => new Date(value).toLocaleString()
|
|
46
|
+
});
|
|
47
|
+
function interpolate(message, values = {}) {
|
|
48
|
+
return message.replace(
|
|
49
|
+
/\{([a-zA-Z][a-zA-Z0-9_]*)\}/g,
|
|
50
|
+
(match, key) => Object.prototype.hasOwnProperty.call(values, key) ? String(values[key]) : match
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
function CboxLocalizationProvider({ locale, translate, formatDate = defaultDate, children }) {
|
|
54
|
+
const value = useMemo(() => ({ locale, t: translate, formatDate }), [locale, translate, formatDate]);
|
|
55
|
+
return /* @__PURE__ */ jsx(Localization.Provider, { value, children });
|
|
56
|
+
}
|
|
57
|
+
function useCboxLocalization() {
|
|
58
|
+
return useContext(Localization);
|
|
59
|
+
}
|
|
60
|
+
function defaultDate(value) {
|
|
61
|
+
return new Date(value).toLocaleString();
|
|
62
|
+
}
|
|
46
63
|
function CboxBrand({
|
|
47
64
|
appKey,
|
|
48
65
|
submark,
|
|
@@ -84,14 +101,17 @@ function CboxBrand({
|
|
|
84
101
|
draggable: false
|
|
85
102
|
}
|
|
86
103
|
),
|
|
87
|
-
submarkText !== null && submarkText !== "" ? /* @__PURE__ */
|
|
104
|
+
submarkText !== null && submarkText !== "" ? /* @__PURE__ */ jsxs(
|
|
88
105
|
"span",
|
|
89
106
|
{
|
|
90
107
|
className: cn(
|
|
91
|
-
"text-
|
|
108
|
+
"text-[13px] font-semibold whitespace-nowrap",
|
|
92
109
|
tone === "light" ? "text-white/60" : "text-muted-foreground/80"
|
|
93
110
|
),
|
|
94
|
-
children:
|
|
111
|
+
children: [
|
|
112
|
+
"\xB7 ",
|
|
113
|
+
submarkText
|
|
114
|
+
]
|
|
95
115
|
}
|
|
96
116
|
) : null
|
|
97
117
|
]
|
|
@@ -109,6 +129,7 @@ function CboxAppSidebar({
|
|
|
109
129
|
collapsed = false,
|
|
110
130
|
className
|
|
111
131
|
}) {
|
|
132
|
+
const { t } = useCboxLocalization();
|
|
112
133
|
return /* @__PURE__ */ jsxs(
|
|
113
134
|
"aside",
|
|
114
135
|
{
|
|
@@ -124,7 +145,7 @@ function CboxAppSidebar({
|
|
|
124
145
|
LinkComponent,
|
|
125
146
|
{
|
|
126
147
|
href: brandHref,
|
|
127
|
-
"aria-label": "Home",
|
|
148
|
+
"aria-label": t("Home"),
|
|
128
149
|
className: cn(
|
|
129
150
|
"flex h-14 shrink-0 items-center border-b border-sidebar-border/70",
|
|
130
151
|
collapsed ? "justify-center px-0" : "px-4"
|
|
@@ -197,6 +218,7 @@ function CboxMobileNavDrawer({
|
|
|
197
218
|
currentPath,
|
|
198
219
|
LinkComponent
|
|
199
220
|
}) {
|
|
221
|
+
const { t } = useCboxLocalization();
|
|
200
222
|
useEffect(() => {
|
|
201
223
|
if (!open) {
|
|
202
224
|
return;
|
|
@@ -238,7 +260,7 @@ function CboxMobileNavDrawer({
|
|
|
238
260
|
{
|
|
239
261
|
role: "dialog",
|
|
240
262
|
"aria-modal": "true",
|
|
241
|
-
"aria-label": "Navigation",
|
|
263
|
+
"aria-label": t("Navigation"),
|
|
242
264
|
className: cn(
|
|
243
265
|
"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
266
|
open ? "translate-x-0" : "-translate-x-full"
|
|
@@ -249,7 +271,7 @@ function CboxMobileNavDrawer({
|
|
|
249
271
|
LinkComponent,
|
|
250
272
|
{
|
|
251
273
|
href: brandHref,
|
|
252
|
-
"aria-label": "Home",
|
|
274
|
+
"aria-label": t("Home"),
|
|
253
275
|
className: "flex items-center",
|
|
254
276
|
children: /* @__PURE__ */ jsx(
|
|
255
277
|
CboxBrand,
|
|
@@ -267,7 +289,7 @@ function CboxMobileNavDrawer({
|
|
|
267
289
|
{
|
|
268
290
|
type: "button",
|
|
269
291
|
onClick: onClose,
|
|
270
|
-
"aria-label": "Close navigation",
|
|
292
|
+
"aria-label": t("Close navigation"),
|
|
271
293
|
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
294
|
children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4", strokeWidth: 1.8 })
|
|
273
295
|
}
|
|
@@ -314,6 +336,7 @@ function CboxMobileNavDrawer({
|
|
|
314
336
|
);
|
|
315
337
|
}
|
|
316
338
|
function AppSwitcher({ className, onLockedAppClick }) {
|
|
339
|
+
const { t } = useCboxLocalization();
|
|
317
340
|
const cboxId = useCboxIdOptional();
|
|
318
341
|
const [open, setOpen] = useState(false);
|
|
319
342
|
const [query, setQuery] = useState("");
|
|
@@ -396,7 +419,7 @@ function AppSwitcher({ className, onLockedAppClick }) {
|
|
|
396
419
|
}
|
|
397
420
|
}, [navigableHrefs.length, activeIndex]);
|
|
398
421
|
const triggerMonogram = currentApp?.monogram ?? currentApp?.name.slice(0, 1).toUpperCase() ?? "\u2B1B";
|
|
399
|
-
const triggerName = currentApp?.name ?? "Apps";
|
|
422
|
+
const triggerName = currentApp?.name ?? t("Apps");
|
|
400
423
|
const triggerAccent = currentApp?.accentColor;
|
|
401
424
|
function onSearchKeyDown(event) {
|
|
402
425
|
if (event.key === "ArrowDown") {
|
|
@@ -470,10 +493,10 @@ function AppSwitcher({ className, onLockedAppClick }) {
|
|
|
470
493
|
"div",
|
|
471
494
|
{
|
|
472
495
|
role: "dialog",
|
|
473
|
-
"aria-label": "Switch app",
|
|
496
|
+
"aria-label": t("Switch app"),
|
|
474
497
|
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
498
|
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" }) }),
|
|
499
|
+
/* @__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: t("Switch app") }) }),
|
|
477
500
|
/* @__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
501
|
/* @__PURE__ */ jsx(SearchIcon, {}),
|
|
479
502
|
/* @__PURE__ */ jsx(
|
|
@@ -484,8 +507,8 @@ function AppSwitcher({ className, onLockedAppClick }) {
|
|
|
484
507
|
value: query,
|
|
485
508
|
onChange: (e) => setQuery(e.target.value),
|
|
486
509
|
onKeyDown: onSearchKeyDown,
|
|
487
|
-
placeholder: "Filter apps\u2026",
|
|
488
|
-
"aria-label": "Filter apps",
|
|
510
|
+
placeholder: t("Filter apps\u2026"),
|
|
511
|
+
"aria-label": t("Filter apps"),
|
|
489
512
|
autoComplete: "off",
|
|
490
513
|
spellCheck: false,
|
|
491
514
|
className: "w-full bg-transparent text-sm text-foreground placeholder:text-muted-foreground/60 focus:outline-none"
|
|
@@ -493,15 +516,7 @@ function AppSwitcher({ className, onLockedAppClick }) {
|
|
|
493
516
|
)
|
|
494
517
|
] }) }),
|
|
495
518
|
/* @__PURE__ */ jsxs("div", { className: "max-h-[60vh] overflow-y-auto py-1", children: [
|
|
496
|
-
totalMatches === 0 ? /* @__PURE__ */
|
|
497
|
-
"No apps match",
|
|
498
|
-
" ",
|
|
499
|
-
/* @__PURE__ */ jsxs("span", { className: "text-mono text-foreground", children: [
|
|
500
|
-
"\u201C",
|
|
501
|
-
query,
|
|
502
|
-
"\u201D"
|
|
503
|
-
] })
|
|
504
|
-
] }) : null,
|
|
519
|
+
totalMatches === 0 ? /* @__PURE__ */ jsx("p", { className: "px-3 py-6 text-center text-[12px] text-muted-foreground", children: t("No apps match \u201C{query}\u201D", { query }) }) : null,
|
|
505
520
|
entitled.length > 0 ? /* @__PURE__ */ jsx("ul", { className: "space-y-0.5 p-1", children: entitled.map((app) => {
|
|
506
521
|
const navIndex = findNavIndex(navigableHrefs, app);
|
|
507
522
|
return /* @__PURE__ */ jsx(
|
|
@@ -523,7 +538,7 @@ function AppSwitcher({ className, onLockedAppClick }) {
|
|
|
523
538
|
}) }) : null,
|
|
524
539
|
locked.length > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
525
540
|
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" }),
|
|
541
|
+
/* @__PURE__ */ jsx("p", { className: "text-mono px-3 py-1.5 text-[10px] font-semibold tracking-[0.18em] text-muted-foreground/70 uppercase", children: t("Available") }),
|
|
527
542
|
/* @__PURE__ */ jsx("ul", { className: "space-y-0.5 p-1 pt-0", children: locked.map((app) => {
|
|
528
543
|
const navIndex = findNavIndex(navigableHrefs, app);
|
|
529
544
|
return /* @__PURE__ */ jsx(
|
|
@@ -561,6 +576,7 @@ function AppRow({
|
|
|
561
576
|
onLockedAppClick,
|
|
562
577
|
onNavigate
|
|
563
578
|
}) {
|
|
579
|
+
const { t } = useCboxLocalization();
|
|
564
580
|
const isComingSoon = app.comingSoon === true;
|
|
565
581
|
const isLocked = app.entitled === false || isComingSoon;
|
|
566
582
|
const monogram = app.monogram ?? app.name.slice(0, 1).toUpperCase();
|
|
@@ -591,7 +607,7 @@ function AppRow({
|
|
|
591
607
|
children: app.name
|
|
592
608
|
}
|
|
593
609
|
),
|
|
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
|
|
610
|
+
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: t("current") }) : null
|
|
595
611
|
] }),
|
|
596
612
|
app.description ? /* @__PURE__ */ jsx(
|
|
597
613
|
"span",
|
|
@@ -604,9 +620,9 @@ function AppRow({
|
|
|
604
620
|
}
|
|
605
621
|
) : null
|
|
606
622
|
] });
|
|
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: [
|
|
623
|
+
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: t("soon") }) : isLocked ? /* @__PURE__ */ jsxs("span", { className: "flex shrink-0 items-center gap-1 text-[10px] font-medium text-muted-foreground", children: [
|
|
608
624
|
/* @__PURE__ */ jsx(Lock, {}),
|
|
609
|
-
"Add"
|
|
625
|
+
t("Add")
|
|
610
626
|
] }) : null;
|
|
611
627
|
if (isComingSoon) {
|
|
612
628
|
return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
|
|
@@ -614,7 +630,7 @@ function AppRow({
|
|
|
614
630
|
{
|
|
615
631
|
"aria-disabled": true,
|
|
616
632
|
className: "flex w-full cursor-not-allowed items-center gap-3 rounded-md px-2.5 py-2",
|
|
617
|
-
title:
|
|
633
|
+
title: t("{name} \u2014 coming soon", { name: app.name }),
|
|
618
634
|
children: [
|
|
619
635
|
tile,
|
|
620
636
|
body,
|
|
@@ -638,7 +654,7 @@ function AppRow({
|
|
|
638
654
|
highlighted && "bg-secondary",
|
|
639
655
|
onLockedAppClick !== void 0 ? "hover:bg-secondary" : "cursor-default"
|
|
640
656
|
),
|
|
641
|
-
title:
|
|
657
|
+
title: t("Add {name} to your plan", { name: app.name }),
|
|
642
658
|
children: [
|
|
643
659
|
tile,
|
|
644
660
|
body,
|
|
@@ -832,6 +848,7 @@ function SurfaceModal({
|
|
|
832
848
|
size = "wide",
|
|
833
849
|
children
|
|
834
850
|
}) {
|
|
851
|
+
const { t } = useCboxLocalization();
|
|
835
852
|
const dialogRef = useRef(null);
|
|
836
853
|
const previousFocusRef = useRef(null);
|
|
837
854
|
useEffect(() => {
|
|
@@ -928,7 +945,7 @@ function SurfaceModal({
|
|
|
928
945
|
type: "button",
|
|
929
946
|
onClick: onClose,
|
|
930
947
|
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",
|
|
948
|
+
"aria-label": t("Close"),
|
|
932
949
|
children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4", strokeWidth: 1.8 })
|
|
933
950
|
}
|
|
934
951
|
)
|
|
@@ -947,6 +964,7 @@ var TONES = {
|
|
|
947
964
|
critical: "destructive"
|
|
948
965
|
};
|
|
949
966
|
function InboxSurface({ open, onClose }) {
|
|
967
|
+
const { t, formatDate } = useCboxLocalization();
|
|
950
968
|
const transport = useTransport();
|
|
951
969
|
const cboxId = useCboxIdOptional();
|
|
952
970
|
const [inbox, setInbox] = useState(null);
|
|
@@ -974,7 +992,7 @@ function InboxSurface({ open, onClose }) {
|
|
|
974
992
|
if (!cancelled) setInbox(body);
|
|
975
993
|
} catch (e) {
|
|
976
994
|
if (!cancelled) {
|
|
977
|
-
setError(e instanceof Error ? e.message : "Load failed");
|
|
995
|
+
setError(e instanceof Error ? e.message : t("Load failed"));
|
|
978
996
|
}
|
|
979
997
|
} finally {
|
|
980
998
|
if (!cancelled) setLoading(false);
|
|
@@ -1025,15 +1043,15 @@ function InboxSurface({ open, onClose }) {
|
|
|
1025
1043
|
{
|
|
1026
1044
|
open,
|
|
1027
1045
|
onClose,
|
|
1028
|
-
eyebrow: "Notifications",
|
|
1029
|
-
title: unread > 0 ?
|
|
1046
|
+
eyebrow: t("Notifications"),
|
|
1047
|
+
title: unread > 0 ? t("Inbox \xB7 {count} unread", { count: unread }) : t("Inbox \xB7 all caught up"),
|
|
1030
1048
|
headerActions: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
1031
1049
|
/* @__PURE__ */ jsx(
|
|
1032
1050
|
FilterTab,
|
|
1033
1051
|
{
|
|
1034
1052
|
active: filter === "all",
|
|
1035
1053
|
onClick: () => setFilter("all"),
|
|
1036
|
-
children: "All"
|
|
1054
|
+
children: t("All")
|
|
1037
1055
|
}
|
|
1038
1056
|
),
|
|
1039
1057
|
/* @__PURE__ */ jsx(
|
|
@@ -1041,18 +1059,12 @@ function InboxSurface({ open, onClose }) {
|
|
|
1041
1059
|
{
|
|
1042
1060
|
active: filter === "unread",
|
|
1043
1061
|
onClick: () => setFilter("unread"),
|
|
1044
|
-
children: "Unread"
|
|
1062
|
+
children: t("Unread")
|
|
1045
1063
|
}
|
|
1046
1064
|
)
|
|
1047
1065
|
] }),
|
|
1048
1066
|
footer: inbox !== null && baseUrl !== null ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1049
|
-
/* @__PURE__ */
|
|
1050
|
-
"Showing ",
|
|
1051
|
-
filtered.length,
|
|
1052
|
-
" of ",
|
|
1053
|
-
items.length,
|
|
1054
|
-
"."
|
|
1055
|
-
] }),
|
|
1067
|
+
/* @__PURE__ */ jsx("span", { children: t("Showing {shown} of {total}.", { shown: filtered.length, total: items.length }) }),
|
|
1056
1068
|
/* @__PURE__ */ jsx(
|
|
1057
1069
|
"a",
|
|
1058
1070
|
{
|
|
@@ -1060,18 +1072,18 @@ function InboxSurface({ open, onClose }) {
|
|
|
1060
1072
|
target: "_blank",
|
|
1061
1073
|
rel: "noreferrer",
|
|
1062
1074
|
className: "font-medium text-foreground hover:underline",
|
|
1063
|
-
children: "Open in new tab \u2197"
|
|
1075
|
+
children: t("Open in new tab \u2197")
|
|
1064
1076
|
}
|
|
1065
1077
|
)
|
|
1066
1078
|
] }) : /* @__PURE__ */ jsx("span", { children: "\u2014" }),
|
|
1067
1079
|
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" }),
|
|
1080
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-destructive", children: t("Couldn't load inbox") }),
|
|
1069
1081
|
/* @__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(
|
|
1082
|
+
] }) : loading && inbox === null ? /* @__PURE__ */ jsx("div", { className: "px-5 py-12 text-center text-sm text-muted-foreground", children: t("Loading\u2026") }) : filtered.length === 0 ? /* @__PURE__ */ jsx("div", { className: "px-5 py-12", children: /* @__PURE__ */ jsx(
|
|
1071
1083
|
EmptyState,
|
|
1072
1084
|
{
|
|
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."
|
|
1085
|
+
title: filter === "unread" ? t("No unread notifications") : t("No notifications yet"),
|
|
1086
|
+
description: t("When a Cbox app sends you something \u2014 invitation accepted, billing event, security alert \u2014 it lands here.")
|
|
1075
1087
|
}
|
|
1076
1088
|
) }) : /* @__PURE__ */ jsx("ul", { className: "divide-y divide-border/70", children: filtered.map((item) => {
|
|
1077
1089
|
const data = item.data ?? {};
|
|
@@ -1105,9 +1117,7 @@ function InboxSurface({ open, onClose }) {
|
|
|
1105
1117
|
/* @__PURE__ */ jsxs("p", { className: "text-mono mt-1 text-[10px] text-muted-foreground", children: [
|
|
1106
1118
|
item.event_type,
|
|
1107
1119
|
" \xB7 ",
|
|
1108
|
-
|
|
1109
|
-
item.created_at
|
|
1110
|
-
).toLocaleString()
|
|
1120
|
+
formatDate(item.created_at)
|
|
1111
1121
|
] })
|
|
1112
1122
|
] }),
|
|
1113
1123
|
item.read_at === null ? /* @__PURE__ */ jsx(
|
|
@@ -1120,8 +1130,8 @@ function InboxSurface({ open, onClose }) {
|
|
|
1120
1130
|
void markRead(item.id);
|
|
1121
1131
|
},
|
|
1122
1132
|
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"
|
|
1133
|
+
"aria-label": t("Mark as read"),
|
|
1134
|
+
children: t("Mark read")
|
|
1125
1135
|
}
|
|
1126
1136
|
) : null
|
|
1127
1137
|
] }) })
|
|
@@ -1225,6 +1235,7 @@ function useTransport() {
|
|
|
1225
1235
|
return t;
|
|
1226
1236
|
}
|
|
1227
1237
|
function NotificationsBell({ className }) {
|
|
1238
|
+
const { t, formatDate } = useCboxLocalization();
|
|
1228
1239
|
const ctx = useCboxIdOptional();
|
|
1229
1240
|
const surfaces = useCboxSurfacesOptional();
|
|
1230
1241
|
const [open, setOpen] = useState(false);
|
|
@@ -1256,7 +1267,7 @@ function NotificationsBell({ className }) {
|
|
|
1256
1267
|
"button",
|
|
1257
1268
|
{
|
|
1258
1269
|
type: "button",
|
|
1259
|
-
"aria-label": "Notifications",
|
|
1270
|
+
"aria-label": t("Notifications"),
|
|
1260
1271
|
disabled: true,
|
|
1261
1272
|
className: "relative flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground opacity-60 cursor-default",
|
|
1262
1273
|
children: /* @__PURE__ */ jsx(Bell, {})
|
|
@@ -1271,7 +1282,7 @@ function NotificationsBell({ className }) {
|
|
|
1271
1282
|
{
|
|
1272
1283
|
type: "button",
|
|
1273
1284
|
onClick: () => setOpen((v) => !v),
|
|
1274
|
-
"aria-label": unreadCount > 0 ?
|
|
1285
|
+
"aria-label": unreadCount > 0 ? t("{count} unread notifications", { count: unreadCount }) : t("Notifications"),
|
|
1275
1286
|
"aria-expanded": open,
|
|
1276
1287
|
"aria-haspopup": "menu",
|
|
1277
1288
|
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",
|
|
@@ -1289,15 +1300,12 @@ function NotificationsBell({ className }) {
|
|
|
1289
1300
|
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
1301
|
children: [
|
|
1291
1302
|
/* @__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__ */
|
|
1294
|
-
unreadCount,
|
|
1295
|
-
" unread"
|
|
1296
|
-
] }) : null
|
|
1303
|
+
/* @__PURE__ */ jsx("span", { className: "text-mono text-[10px] font-semibold tracking-[0.18em] uppercase text-muted-foreground", children: t("Notifications") }),
|
|
1304
|
+
unreadCount > 0 ? /* @__PURE__ */ jsx("span", { className: "text-mono text-[10px] tabular-nums text-muted-foreground", children: t("{count} unread", { count: unreadCount }) }) : null
|
|
1297
1305
|
] }),
|
|
1298
1306
|
/* @__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." })
|
|
1307
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-foreground", children: t("All caught up") }),
|
|
1308
|
+
/* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: t("No recent notifications.") })
|
|
1301
1309
|
] }) : /* @__PURE__ */ jsx("ul", { className: "divide-y divide-border/70", children: previewItems.map((item) => /* @__PURE__ */ jsx(
|
|
1302
1310
|
PreviewRow,
|
|
1303
1311
|
{
|
|
@@ -1316,14 +1324,14 @@ function NotificationsBell({ className }) {
|
|
|
1316
1324
|
surfaces.open("inbox");
|
|
1317
1325
|
},
|
|
1318
1326
|
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"
|
|
1327
|
+
children: t("See all notifications \u2192")
|
|
1320
1328
|
}
|
|
1321
1329
|
) : /* @__PURE__ */ jsx(
|
|
1322
1330
|
"a",
|
|
1323
1331
|
{
|
|
1324
1332
|
href: inboxUrl,
|
|
1325
1333
|
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"
|
|
1334
|
+
children: t("See all notifications \u2192")
|
|
1327
1335
|
}
|
|
1328
1336
|
)
|
|
1329
1337
|
]
|
|
@@ -1336,10 +1344,11 @@ function PreviewRow({
|
|
|
1336
1344
|
markReadUrl,
|
|
1337
1345
|
onMarkedRead
|
|
1338
1346
|
}) {
|
|
1347
|
+
const { t, formatDate } = useCboxLocalization();
|
|
1339
1348
|
const tone = item.severity === "critical" ? "bg-destructive" : item.severity === "warning" ? "bg-warning" : "bg-info";
|
|
1340
1349
|
const ts = (() => {
|
|
1341
1350
|
try {
|
|
1342
|
-
return
|
|
1351
|
+
return formatDate(item.createdAt);
|
|
1343
1352
|
} catch {
|
|
1344
1353
|
return item.createdAt;
|
|
1345
1354
|
}
|
|
@@ -1393,8 +1402,8 @@ function PreviewRow({
|
|
|
1393
1402
|
type: "button",
|
|
1394
1403
|
onClick: markRead,
|
|
1395
1404
|
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",
|
|
1405
|
+
"aria-label": t("Mark as read"),
|
|
1406
|
+
title: t("Mark as read"),
|
|
1398
1407
|
children: "\u2713"
|
|
1399
1408
|
}
|
|
1400
1409
|
) : null
|
|
@@ -1432,7 +1441,8 @@ function Bell() {
|
|
|
1432
1441
|
}
|
|
1433
1442
|
);
|
|
1434
1443
|
}
|
|
1435
|
-
function SearchTrigger({ onOpen, className, label
|
|
1444
|
+
function SearchTrigger({ onOpen, className, label }) {
|
|
1445
|
+
const { t } = useCboxLocalization();
|
|
1436
1446
|
const [shortcut, setShortcut] = useState("\u2318K");
|
|
1437
1447
|
useEffect(() => {
|
|
1438
1448
|
if (typeof navigator === "undefined") return;
|
|
@@ -1447,10 +1457,10 @@ function SearchTrigger({ onOpen, className, label = "Search" }) {
|
|
|
1447
1457
|
"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
1458
|
className
|
|
1449
1459
|
),
|
|
1450
|
-
"aria-label": "Open command palette",
|
|
1460
|
+
"aria-label": t("Open command palette"),
|
|
1451
1461
|
children: [
|
|
1452
1462
|
/* @__PURE__ */ jsx(SearchIcon2, {}),
|
|
1453
|
-
/* @__PURE__ */ jsx("span", { className: "flex-1 text-left", children: label }),
|
|
1463
|
+
/* @__PURE__ */ jsx("span", { className: "flex-1 text-left", children: label ?? t("Search") }),
|
|
1454
1464
|
/* @__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
1465
|
]
|
|
1456
1466
|
}
|
|
@@ -1476,18 +1486,28 @@ function SearchIcon2() {
|
|
|
1476
1486
|
}
|
|
1477
1487
|
);
|
|
1478
1488
|
}
|
|
1479
|
-
|
|
1489
|
+
var Anchor = ({ href, className, children }) => /* @__PURE__ */ jsx("a", { href, className, children });
|
|
1490
|
+
function UserMenu({ extras, className, placement = "bottom-end", LinkComponent: Link = Anchor }) {
|
|
1491
|
+
const { t } = useCboxLocalization();
|
|
1480
1492
|
const { user, currentOrganization, urls } = useCboxId();
|
|
1481
1493
|
const [open, setOpen] = useState(false);
|
|
1482
1494
|
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),
|
|
1495
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("relative", className), onKeyDown: (event) => {
|
|
1496
|
+
if (event.key === "Escape") setOpen(false);
|
|
1497
|
+
}, onBlur: (event) => {
|
|
1498
|
+
if (!event.currentTarget.contains(event.relatedTarget)) setOpen(false);
|
|
1499
|
+
}, children: [
|
|
1484
1500
|
/* @__PURE__ */ jsx(
|
|
1485
1501
|
"button",
|
|
1486
1502
|
{
|
|
1487
1503
|
type: "button",
|
|
1488
1504
|
onClick: () => setOpen((v) => !v),
|
|
1489
1505
|
className: "flex h-9 items-center gap-2 rounded-md px-1.5 hover:bg-secondary",
|
|
1490
|
-
"aria-label": "Open user menu",
|
|
1506
|
+
"aria-label": t("Open user menu"),
|
|
1507
|
+
"aria-expanded": open,
|
|
1508
|
+
onKeyDown: (event) => {
|
|
1509
|
+
if (event.key === "Escape") setOpen(false);
|
|
1510
|
+
},
|
|
1491
1511
|
children: user.avatar_url ? /* @__PURE__ */ jsx(
|
|
1492
1512
|
"img",
|
|
1493
1513
|
{
|
|
@@ -1498,7 +1518,7 @@ function UserMenu({ extras, className }) {
|
|
|
1498
1518
|
) : /* @__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
1519
|
}
|
|
1500
1520
|
),
|
|
1501
|
-
open ? /* @__PURE__ */ jsxs("div", { className: "absolute
|
|
1521
|
+
open ? /* @__PURE__ */ jsxs("div", { className: cn("absolute z-50 w-64 rounded-lg border border-border bg-popover p-1 shadow-md", placement === "top-start" ? "bottom-full left-0 mb-2" : "right-0 top-full mt-1"), children: [
|
|
1502
1522
|
/* @__PURE__ */ jsxs("div", { className: "px-2 py-2", children: [
|
|
1503
1523
|
/* @__PURE__ */ jsx("p", { className: "truncate text-sm font-medium text-foreground", children: user.name }),
|
|
1504
1524
|
/* @__PURE__ */ jsx("p", { className: "truncate text-xs text-muted-foreground", children: user.email })
|
|
@@ -1509,24 +1529,24 @@ function UserMenu({ extras, className }) {
|
|
|
1509
1529
|
] }) : null,
|
|
1510
1530
|
/* @__PURE__ */ jsx("div", { className: "my-1 border-t border-border" }),
|
|
1511
1531
|
/* @__PURE__ */ jsxs(
|
|
1512
|
-
|
|
1532
|
+
Link,
|
|
1513
1533
|
{
|
|
1514
1534
|
href: urls.accountUrl,
|
|
1515
1535
|
className: "flex items-center justify-between rounded-sm px-2 py-1.5 text-sm hover:bg-secondary",
|
|
1516
1536
|
children: [
|
|
1517
|
-
/* @__PURE__ */ jsx("span", { children: "Manage account" }),
|
|
1537
|
+
/* @__PURE__ */ jsx("span", { children: t("Manage account") }),
|
|
1518
1538
|
/* @__PURE__ */ jsx(CrossAppArrow, {})
|
|
1519
1539
|
]
|
|
1520
1540
|
}
|
|
1521
1541
|
),
|
|
1522
1542
|
urls.orgSettingsUrl && currentOrganization ? /* @__PURE__ */ jsxs(
|
|
1523
|
-
|
|
1543
|
+
Link,
|
|
1524
1544
|
{
|
|
1525
1545
|
href: urls.orgSettingsUrl,
|
|
1526
1546
|
className: "flex items-center justify-between rounded-sm px-2 py-1.5 text-sm hover:bg-secondary",
|
|
1527
1547
|
children: [
|
|
1528
1548
|
/* @__PURE__ */ jsxs("span", { className: "truncate", children: [
|
|
1529
|
-
"Team & billing",
|
|
1549
|
+
t("Team & billing"),
|
|
1530
1550
|
/* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
|
|
1531
1551
|
" \xB7 ",
|
|
1532
1552
|
currentOrganization.name
|
|
@@ -1544,7 +1564,7 @@ function UserMenu({ extras, className }) {
|
|
|
1544
1564
|
{
|
|
1545
1565
|
type: "submit",
|
|
1546
1566
|
className: "flex w-full items-center rounded-sm px-2 py-1.5 text-left text-sm hover:bg-secondary",
|
|
1547
|
-
children: "Sign out"
|
|
1567
|
+
children: t("Sign out")
|
|
1548
1568
|
}
|
|
1549
1569
|
)
|
|
1550
1570
|
] })
|
|
@@ -1594,6 +1614,7 @@ function CboxAppShell({
|
|
|
1594
1614
|
topbarRightCluster,
|
|
1595
1615
|
children
|
|
1596
1616
|
}) {
|
|
1617
|
+
const { t } = useCboxLocalization();
|
|
1597
1618
|
const ToggleIcon = sidebarCollapsed ? PanelLeftOpen : PanelLeftClose;
|
|
1598
1619
|
const [mobileNavOpen, setMobileNavOpen] = useState(false);
|
|
1599
1620
|
useEffect(() => {
|
|
@@ -1636,8 +1657,8 @@ function CboxAppShell({
|
|
|
1636
1657
|
type: "button",
|
|
1637
1658
|
onClick: () => setMobileNavOpen(true),
|
|
1638
1659
|
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",
|
|
1660
|
+
"aria-label": t("Open navigation"),
|
|
1661
|
+
title: t("Open navigation"),
|
|
1641
1662
|
children: /* @__PURE__ */ jsx(Menu, { className: "h-4 w-4", strokeWidth: 1.8 })
|
|
1642
1663
|
}
|
|
1643
1664
|
),
|
|
@@ -1647,8 +1668,8 @@ function CboxAppShell({
|
|
|
1647
1668
|
type: "button",
|
|
1648
1669
|
onClick: onToggleSidebar,
|
|
1649
1670
|
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",
|
|
1671
|
+
"aria-label": sidebarCollapsed ? t("Expand sidebar") : t("Collapse sidebar"),
|
|
1672
|
+
title: sidebarCollapsed ? t("Expand sidebar") : t("Collapse sidebar"),
|
|
1652
1673
|
children: /* @__PURE__ */ jsx(
|
|
1653
1674
|
ToggleIcon,
|
|
1654
1675
|
{
|
|
@@ -1678,6 +1699,7 @@ function ContextBar({
|
|
|
1678
1699
|
LinkComponent = DefaultLink,
|
|
1679
1700
|
className
|
|
1680
1701
|
}) {
|
|
1702
|
+
const { t } = useCboxLocalization();
|
|
1681
1703
|
if (crumbs.length === 0) {
|
|
1682
1704
|
return null;
|
|
1683
1705
|
}
|
|
@@ -1685,7 +1707,7 @@ function ContextBar({
|
|
|
1685
1707
|
"nav",
|
|
1686
1708
|
{
|
|
1687
1709
|
"data-component": "context-bar",
|
|
1688
|
-
"aria-label": "Context",
|
|
1710
|
+
"aria-label": t("Context"),
|
|
1689
1711
|
className: cn("flex min-w-0 items-center gap-1", className),
|
|
1690
1712
|
children: crumbs.map((crumb, index) => /* @__PURE__ */ jsxs(
|
|
1691
1713
|
"div",
|
|
@@ -1721,6 +1743,7 @@ function ContextCrumbChip({
|
|
|
1721
1743
|
crumb,
|
|
1722
1744
|
LinkComponent
|
|
1723
1745
|
}) {
|
|
1746
|
+
const { t } = useCboxLocalization();
|
|
1724
1747
|
const [open, setOpen] = useState(false);
|
|
1725
1748
|
const ref = useRef(null);
|
|
1726
1749
|
const listId = useId();
|
|
@@ -1742,7 +1765,7 @@ function ContextCrumbChip({
|
|
|
1742
1765
|
};
|
|
1743
1766
|
}, [open]);
|
|
1744
1767
|
const hasMenu = crumb.items.length > 0 || crumb.footerLink !== void 0;
|
|
1745
|
-
return /* @__PURE__ */ jsxs("div", { ref, className: "relative", children: [
|
|
1768
|
+
return /* @__PURE__ */ jsxs("div", { ref, className: "relative min-w-0", children: [
|
|
1746
1769
|
/* @__PURE__ */ jsxs(
|
|
1747
1770
|
"button",
|
|
1748
1771
|
{
|
|
@@ -1751,7 +1774,7 @@ function ContextCrumbChip({
|
|
|
1751
1774
|
"aria-expanded": hasMenu ? open : void 0,
|
|
1752
1775
|
"aria-haspopup": hasMenu ? "listbox" : void 0,
|
|
1753
1776
|
"aria-controls": hasMenu ? listId : void 0,
|
|
1754
|
-
"aria-label": crumb.ariaLabel ??
|
|
1777
|
+
"aria-label": crumb.ariaLabel ?? t("Switch {name}", { name: crumb.label }),
|
|
1755
1778
|
disabled: !hasMenu,
|
|
1756
1779
|
className: cn(
|
|
1757
1780
|
"flex h-8 min-w-0 items-center gap-2 rounded-md border border-transparent px-2 text-[13px] text-foreground transition-colors",
|
|
@@ -1767,7 +1790,7 @@ function ContextCrumbChip({
|
|
|
1767
1790
|
size: "sm"
|
|
1768
1791
|
}
|
|
1769
1792
|
) : null,
|
|
1770
|
-
/* @__PURE__ */ jsx("span", { className: "truncate font-
|
|
1793
|
+
/* @__PURE__ */ jsx("span", { className: "truncate font-semibold", children: crumb.label }),
|
|
1771
1794
|
hasMenu ? /* @__PURE__ */ jsx(Chevron2, { open }) : null
|
|
1772
1795
|
]
|
|
1773
1796
|
}
|
|
@@ -1822,6 +1845,7 @@ function ContextItemRow({
|
|
|
1822
1845
|
LinkComponent,
|
|
1823
1846
|
onAfterClick
|
|
1824
1847
|
}) {
|
|
1848
|
+
const { t } = useCboxLocalization();
|
|
1825
1849
|
const body = /* @__PURE__ */ jsxs("div", { className: "flex w-full min-w-0 items-center gap-2.5 px-2.5 py-2", children: [
|
|
1826
1850
|
item.avatar !== void 0 ? /* @__PURE__ */ jsx(
|
|
1827
1851
|
ContextAvatarChip,
|
|
@@ -1850,7 +1874,7 @@ function ContextItemRow({
|
|
|
1850
1874
|
children: /* @__PURE__ */ jsx(
|
|
1851
1875
|
"span",
|
|
1852
1876
|
{
|
|
1853
|
-
"aria-label": item.settingsAriaLabel ??
|
|
1877
|
+
"aria-label": item.settingsAriaLabel ?? t("Settings for {name}", { name: item.label }),
|
|
1854
1878
|
onClickCapture: (event) => {
|
|
1855
1879
|
event.stopPropagation();
|
|
1856
1880
|
onAfterClick();
|
|
@@ -1923,7 +1947,7 @@ function ContextAvatarChip({
|
|
|
1923
1947
|
size = "md"
|
|
1924
1948
|
}) {
|
|
1925
1949
|
const initials = avatar.initials ?? deriveInitials(fallbackLabel);
|
|
1926
|
-
const sizeClass = size === "sm" ? "h-
|
|
1950
|
+
const sizeClass = size === "sm" ? "h-[26px] w-[26px] text-[10px]" : "h-7 w-7 text-[11px]";
|
|
1927
1951
|
const style = avatar.src === void 0 && avatar.accent !== void 0 ? {
|
|
1928
1952
|
backgroundColor: avatar.accent,
|
|
1929
1953
|
color: "#fff"
|
|
@@ -2023,6 +2047,7 @@ function CboxOrgContextBar({
|
|
|
2023
2047
|
LinkComponent,
|
|
2024
2048
|
onNavigate
|
|
2025
2049
|
}) {
|
|
2050
|
+
const { t } = useCboxLocalization();
|
|
2026
2051
|
const cboxId = useCboxIdOptional();
|
|
2027
2052
|
if (cboxId === null) {
|
|
2028
2053
|
return null;
|
|
@@ -2041,7 +2066,7 @@ function CboxOrgContextBar({
|
|
|
2041
2066
|
// Cogwheel shortcut → id's org-settings page. Cross-app
|
|
2042
2067
|
// deep link; id handles the auth and rendering.
|
|
2043
2068
|
settingsHref: `${idBase}/org/${org.slug}/settings`,
|
|
2044
|
-
settingsAriaLabel:
|
|
2069
|
+
settingsAriaLabel: t("Settings for {name}", { name: org.name })
|
|
2045
2070
|
}));
|
|
2046
2071
|
const onSelect = (slug) => {
|
|
2047
2072
|
const org = organizations.find((o) => o.slug === slug);
|
|
@@ -2063,10 +2088,10 @@ function CboxOrgContextBar({
|
|
|
2063
2088
|
items,
|
|
2064
2089
|
onSelect,
|
|
2065
2090
|
footerLink: {
|
|
2066
|
-
label: "Manage organizations",
|
|
2091
|
+
label: t("Manage organizations"),
|
|
2067
2092
|
href: `${idBase}/settings/organizations`
|
|
2068
2093
|
},
|
|
2069
|
-
ariaLabel: "Switch organization"
|
|
2094
|
+
ariaLabel: t("Switch organization")
|
|
2070
2095
|
}
|
|
2071
2096
|
]
|
|
2072
2097
|
}
|
|
@@ -2080,6 +2105,7 @@ function deriveInitials2(label) {
|
|
|
2080
2105
|
return (label.slice(0, 2) || "?").toUpperCase();
|
|
2081
2106
|
}
|
|
2082
2107
|
function OrgSwitcher({ Trigger = DefaultTrigger, className }) {
|
|
2108
|
+
const { t } = useCboxLocalization();
|
|
2083
2109
|
const { currentOrganization, organizations, onSwitchOrganization } = useCboxId();
|
|
2084
2110
|
const [open, setOpen] = useState(false);
|
|
2085
2111
|
const [query, setQuery] = useState("");
|
|
@@ -2179,10 +2205,10 @@ function OrgSwitcher({ Trigger = DefaultTrigger, className }) {
|
|
|
2179
2205
|
"div",
|
|
2180
2206
|
{
|
|
2181
2207
|
role: "dialog",
|
|
2182
|
-
"aria-label": "Switch organization",
|
|
2208
|
+
"aria-label": t("Switch organization"),
|
|
2183
2209
|
className: "absolute left-0 top-full z-50 mt-1 w-64 overflow-hidden rounded-md border border-border bg-popover shadow-md",
|
|
2184
2210
|
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" }) }),
|
|
2211
|
+
/* @__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: t("Switch organization") }) }),
|
|
2186
2212
|
/* @__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
2213
|
/* @__PURE__ */ jsx(SearchIcon3, {}),
|
|
2188
2214
|
/* @__PURE__ */ jsx(
|
|
@@ -2193,23 +2219,15 @@ function OrgSwitcher({ Trigger = DefaultTrigger, className }) {
|
|
|
2193
2219
|
value: query,
|
|
2194
2220
|
onChange: (e) => setQuery(e.target.value),
|
|
2195
2221
|
onKeyDown: onSearchKeyDown,
|
|
2196
|
-
placeholder: "Filter\u2026",
|
|
2197
|
-
"aria-label": "Filter organizations",
|
|
2222
|
+
placeholder: t("Filter\u2026"),
|
|
2223
|
+
"aria-label": t("Filter organizations"),
|
|
2198
2224
|
autoComplete: "off",
|
|
2199
2225
|
spellCheck: false,
|
|
2200
2226
|
className: "w-full bg-transparent text-sm text-foreground placeholder:text-muted-foreground/60 focus:outline-none"
|
|
2201
2227
|
}
|
|
2202
2228
|
)
|
|
2203
2229
|
] }) }),
|
|
2204
|
-
/* @__PURE__ */ jsx("div", { className: "max-h-[50vh] overflow-y-auto p-1", children: filtered.length === 0 ? /* @__PURE__ */
|
|
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) => {
|
|
2230
|
+
/* @__PURE__ */ jsx("div", { className: "max-h-[50vh] overflow-y-auto p-1", children: filtered.length === 0 ? /* @__PURE__ */ jsx("p", { className: "px-3 py-6 text-center text-[12px] text-muted-foreground", children: t("No organizations match \u201C{query}\u201D", { query }) }) : /* @__PURE__ */ jsx("ul", { className: "space-y-0.5", children: filtered.map((org, index) => {
|
|
2213
2231
|
const active = currentOrganization?.id === org.id;
|
|
2214
2232
|
const highlighted = index === activeIndex;
|
|
2215
2233
|
return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
|
|
@@ -2224,7 +2242,7 @@ function OrgSwitcher({ Trigger = DefaultTrigger, className }) {
|
|
|
2224
2242
|
),
|
|
2225
2243
|
children: [
|
|
2226
2244
|
/* @__PURE__ */ jsx("span", { className: "truncate", children: org.name }),
|
|
2227
|
-
active ? /* @__PURE__ */ jsx("span", { className: "text-mono text-[10px] text-muted-foreground", children: "current" }) : null
|
|
2245
|
+
active ? /* @__PURE__ */ jsx("span", { className: "text-mono text-[10px] text-muted-foreground", children: t("current") }) : null
|
|
2228
2246
|
]
|
|
2229
2247
|
}
|
|
2230
2248
|
) }, org.id);
|
|
@@ -2320,6 +2338,7 @@ function TopBar({
|
|
|
2320
2338
|
);
|
|
2321
2339
|
}
|
|
2322
2340
|
function AppLauncher({ className, onLockedAppClick }) {
|
|
2341
|
+
const { t } = useCboxLocalization();
|
|
2323
2342
|
const cboxId = useCboxIdOptional();
|
|
2324
2343
|
const [open, setOpen] = useState(false);
|
|
2325
2344
|
const containerRef = useRef(null);
|
|
@@ -2360,7 +2379,7 @@ function AppLauncher({ className, onLockedAppClick }) {
|
|
|
2360
2379
|
"button",
|
|
2361
2380
|
{
|
|
2362
2381
|
type: "button",
|
|
2363
|
-
"aria-label": "Open app launcher",
|
|
2382
|
+
"aria-label": t("Open app launcher"),
|
|
2364
2383
|
"aria-expanded": open,
|
|
2365
2384
|
onClick: () => setOpen((v) => !v),
|
|
2366
2385
|
className: cn(
|
|
@@ -2374,10 +2393,10 @@ function AppLauncher({ className, onLockedAppClick }) {
|
|
|
2374
2393
|
"div",
|
|
2375
2394
|
{
|
|
2376
2395
|
role: "dialog",
|
|
2377
|
-
"aria-label": "Cbox app launcher",
|
|
2396
|
+
"aria-label": t("Cbox app launcher"),
|
|
2378
2397
|
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
2398
|
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" }) }),
|
|
2399
|
+
/* @__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: t("Cbox suite") }) }),
|
|
2381
2400
|
/* @__PURE__ */ jsxs("div", { className: "max-h-[60vh] overflow-y-auto py-1", children: [
|
|
2382
2401
|
entitled.length > 0 ? /* @__PURE__ */ jsx("ul", { className: "space-y-0.5 p-1", children: entitled.map((app) => /* @__PURE__ */ jsx(
|
|
2383
2402
|
AppRow2,
|
|
@@ -2390,7 +2409,7 @@ function AppLauncher({ className, onLockedAppClick }) {
|
|
|
2390
2409
|
)) }) : null,
|
|
2391
2410
|
locked.length > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2392
2411
|
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" }),
|
|
2412
|
+
/* @__PURE__ */ jsx("p", { className: "text-mono px-3 py-1.5 text-[10px] font-semibold tracking-[0.18em] text-muted-foreground/70 uppercase", children: t("Available") }),
|
|
2394
2413
|
/* @__PURE__ */ jsx("ul", { className: "space-y-0.5 p-1 pt-0", children: locked.map((app) => /* @__PURE__ */ jsx(
|
|
2395
2414
|
AppRow2,
|
|
2396
2415
|
{
|
|
@@ -2408,6 +2427,7 @@ function AppLauncher({ className, onLockedAppClick }) {
|
|
|
2408
2427
|
] });
|
|
2409
2428
|
}
|
|
2410
2429
|
function AppRow2({ app, orgSlug, onLockedAppClick }) {
|
|
2430
|
+
const { t } = useCboxLocalization();
|
|
2411
2431
|
const isComingSoon = app.comingSoon === true;
|
|
2412
2432
|
const isLocked = app.entitled === false || isComingSoon;
|
|
2413
2433
|
const monogram = app.monogram ?? app.name.slice(0, 1).toUpperCase();
|
|
@@ -2438,7 +2458,7 @@ function AppRow2({ app, orgSlug, onLockedAppClick }) {
|
|
|
2438
2458
|
children: app.name
|
|
2439
2459
|
}
|
|
2440
2460
|
),
|
|
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
|
|
2461
|
+
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: t("current") }) : null
|
|
2442
2462
|
] }),
|
|
2443
2463
|
app.description ? /* @__PURE__ */ jsx(
|
|
2444
2464
|
"span",
|
|
@@ -2451,9 +2471,9 @@ function AppRow2({ app, orgSlug, onLockedAppClick }) {
|
|
|
2451
2471
|
}
|
|
2452
2472
|
) : null
|
|
2453
2473
|
] });
|
|
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: [
|
|
2474
|
+
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: t("soon") }) : isLocked ? /* @__PURE__ */ jsxs("span", { className: "flex shrink-0 items-center gap-1 text-[10px] font-medium text-muted-foreground", children: [
|
|
2455
2475
|
/* @__PURE__ */ jsx(Lock2, {}),
|
|
2456
|
-
"Add"
|
|
2476
|
+
t("Add")
|
|
2457
2477
|
] }) : null;
|
|
2458
2478
|
if (isComingSoon) {
|
|
2459
2479
|
return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
|
|
@@ -2461,7 +2481,7 @@ function AppRow2({ app, orgSlug, onLockedAppClick }) {
|
|
|
2461
2481
|
{
|
|
2462
2482
|
"aria-disabled": true,
|
|
2463
2483
|
className: "flex w-full cursor-not-allowed items-center gap-3 rounded-md px-2.5 py-2",
|
|
2464
|
-
title:
|
|
2484
|
+
title: t("{name} \u2014 coming soon", { name: app.name }),
|
|
2465
2485
|
children: [
|
|
2466
2486
|
tile,
|
|
2467
2487
|
body,
|
|
@@ -2480,7 +2500,7 @@ function AppRow2({ app, orgSlug, onLockedAppClick }) {
|
|
|
2480
2500
|
"group flex w-full items-center gap-3 rounded-md px-2.5 py-2 text-left transition-colors",
|
|
2481
2501
|
onLockedAppClick !== void 0 ? "hover:bg-secondary" : "cursor-default"
|
|
2482
2502
|
),
|
|
2483
|
-
title:
|
|
2503
|
+
title: t("Add {name} to your plan", { name: app.name }),
|
|
2484
2504
|
children: [
|
|
2485
2505
|
tile,
|
|
2486
2506
|
body,
|
|
@@ -2553,12 +2573,190 @@ function Lock2() {
|
|
|
2553
2573
|
}
|
|
2554
2574
|
);
|
|
2555
2575
|
}
|
|
2576
|
+
var DEFAULT_REFRESH_SECONDS = 60;
|
|
2577
|
+
function parseAppCatalog(ok, body) {
|
|
2578
|
+
const data = typeof body === "object" && body !== null ? body : {};
|
|
2579
|
+
if (!ok) {
|
|
2580
|
+
const authorize = typeof data.authorize_url === "string" ? data.authorize_url : null;
|
|
2581
|
+
if (authorize !== null || data.state === "authorization_required") {
|
|
2582
|
+
return { status: "authorization_required", authorizeUrl: authorize };
|
|
2583
|
+
}
|
|
2584
|
+
return { status: "unavailable" };
|
|
2585
|
+
}
|
|
2586
|
+
if (!Array.isArray(data.apps)) {
|
|
2587
|
+
return { status: "unavailable" };
|
|
2588
|
+
}
|
|
2589
|
+
const apps = [];
|
|
2590
|
+
for (const entry of data.apps) {
|
|
2591
|
+
if (typeof entry !== "object" || entry === null) continue;
|
|
2592
|
+
const app = entry;
|
|
2593
|
+
if (typeof app.key !== "string" || typeof app.name !== "string" || typeof app.launch_url !== "string") continue;
|
|
2594
|
+
if (!/^https?:\/\//.test(app.launch_url)) continue;
|
|
2595
|
+
apps.push({
|
|
2596
|
+
key: app.key,
|
|
2597
|
+
name: app.name,
|
|
2598
|
+
launch_url: app.launch_url,
|
|
2599
|
+
icon_url: typeof app.icon_url === "string" ? app.icon_url : null,
|
|
2600
|
+
description: typeof app.description === "string" ? app.description : null
|
|
2601
|
+
});
|
|
2602
|
+
}
|
|
2603
|
+
const refresh = typeof data.refresh_after === "number" && Number.isFinite(data.refresh_after) ? data.refresh_after : DEFAULT_REFRESH_SECONDS;
|
|
2604
|
+
return { status: "ready", apps, refreshAfter: Math.max(1, Math.min(DEFAULT_REFRESH_SECONDS, refresh)) };
|
|
2605
|
+
}
|
|
2606
|
+
function authorizeHref(authorizeUrl, location) {
|
|
2607
|
+
if (authorizeUrl === null) return null;
|
|
2608
|
+
try {
|
|
2609
|
+
const url = new URL(authorizeUrl, location.origin);
|
|
2610
|
+
if (url.origin !== location.origin) return null;
|
|
2611
|
+
url.searchParams.set("return_to", location.pathname + location.search);
|
|
2612
|
+
return url.pathname + url.search;
|
|
2613
|
+
} catch {
|
|
2614
|
+
return null;
|
|
2615
|
+
}
|
|
2616
|
+
}
|
|
2617
|
+
function CboxAppCatalog({ endpoint, currentAppKey, icons, className }) {
|
|
2618
|
+
const { t } = useCboxLocalization();
|
|
2619
|
+
const [open, setOpen] = useState(false);
|
|
2620
|
+
const [state, setState] = useState({ status: "idle" });
|
|
2621
|
+
const container = useRef(null);
|
|
2622
|
+
const flight = useRef(null);
|
|
2623
|
+
const expires = useRef(0);
|
|
2624
|
+
const load = useCallback(async () => {
|
|
2625
|
+
if (endpoint === null || flight.current !== null) return;
|
|
2626
|
+
const request = new AbortController();
|
|
2627
|
+
flight.current = request;
|
|
2628
|
+
setState((current) => current.status === "ready" ? current : { status: "loading" });
|
|
2629
|
+
try {
|
|
2630
|
+
const response = await fetch(endpoint, {
|
|
2631
|
+
credentials: "same-origin",
|
|
2632
|
+
cache: "no-store",
|
|
2633
|
+
headers: { Accept: "application/json", "X-Requested-With": "XMLHttpRequest" },
|
|
2634
|
+
signal: request.signal
|
|
2635
|
+
});
|
|
2636
|
+
const body = await response.json().catch(() => null);
|
|
2637
|
+
if (request.signal.aborted) return;
|
|
2638
|
+
const next = parseAppCatalog(response.ok, body);
|
|
2639
|
+
expires.current = next.status === "ready" ? Date.now() + next.refreshAfter * 1e3 : 0;
|
|
2640
|
+
setState(next);
|
|
2641
|
+
} catch {
|
|
2642
|
+
if (!request.signal.aborted) {
|
|
2643
|
+
expires.current = 0;
|
|
2644
|
+
setState({ status: "unavailable" });
|
|
2645
|
+
}
|
|
2646
|
+
} finally {
|
|
2647
|
+
if (flight.current === request) flight.current = null;
|
|
2648
|
+
}
|
|
2649
|
+
}, [endpoint]);
|
|
2650
|
+
const refresh = useCallback(() => {
|
|
2651
|
+
if (document.visibilityState === "visible" && Date.now() >= expires.current) void load();
|
|
2652
|
+
}, [load]);
|
|
2653
|
+
useEffect(() => {
|
|
2654
|
+
if (!open) return;
|
|
2655
|
+
refresh();
|
|
2656
|
+
const outside = (event) => {
|
|
2657
|
+
if (container.current !== null && !container.current.contains(event.target)) setOpen(false);
|
|
2658
|
+
};
|
|
2659
|
+
const key = (event) => {
|
|
2660
|
+
if (event.key === "Escape") setOpen(false);
|
|
2661
|
+
};
|
|
2662
|
+
window.addEventListener("pointerdown", outside);
|
|
2663
|
+
window.addEventListener("keydown", key);
|
|
2664
|
+
window.addEventListener("focus", refresh);
|
|
2665
|
+
document.addEventListener("visibilitychange", refresh);
|
|
2666
|
+
return () => {
|
|
2667
|
+
window.removeEventListener("pointerdown", outside);
|
|
2668
|
+
window.removeEventListener("keydown", key);
|
|
2669
|
+
window.removeEventListener("focus", refresh);
|
|
2670
|
+
document.removeEventListener("visibilitychange", refresh);
|
|
2671
|
+
};
|
|
2672
|
+
}, [open, refresh]);
|
|
2673
|
+
useEffect(() => {
|
|
2674
|
+
if (!open || state.status !== "ready") return;
|
|
2675
|
+
const timer = window.setTimeout(refresh, Math.max(50, expires.current - Date.now()));
|
|
2676
|
+
return () => window.clearTimeout(timer);
|
|
2677
|
+
}, [open, state, refresh]);
|
|
2678
|
+
useEffect(() => () => flight.current?.abort(), []);
|
|
2679
|
+
useEffect(() => {
|
|
2680
|
+
expires.current = 0;
|
|
2681
|
+
setState({ status: "idle" });
|
|
2682
|
+
}, [endpoint]);
|
|
2683
|
+
const reconnect = state.status === "authorization_required" && typeof window !== "undefined" ? authorizeHref(state.authorizeUrl, window.location) : null;
|
|
2684
|
+
return /* @__PURE__ */ jsxs("div", { ref: container, className: cn("relative", className), children: [
|
|
2685
|
+
/* @__PURE__ */ jsx(
|
|
2686
|
+
"button",
|
|
2687
|
+
{
|
|
2688
|
+
type: "button",
|
|
2689
|
+
"aria-label": t("Switch app"),
|
|
2690
|
+
title: t("Switch app"),
|
|
2691
|
+
"aria-haspopup": "dialog",
|
|
2692
|
+
"aria-expanded": open,
|
|
2693
|
+
disabled: endpoint === null,
|
|
2694
|
+
onClick: () => setOpen((value) => !value),
|
|
2695
|
+
className: cn(
|
|
2696
|
+
"flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-secondary hover:text-foreground disabled:cursor-default disabled:opacity-60 disabled:hover:bg-transparent",
|
|
2697
|
+
open && "bg-secondary text-foreground"
|
|
2698
|
+
),
|
|
2699
|
+
children: /* @__PURE__ */ jsx(Grid2, {})
|
|
2700
|
+
}
|
|
2701
|
+
),
|
|
2702
|
+
open ? /* @__PURE__ */ jsxs("div", { role: "dialog", "aria-label": t("Switch app"), className: "absolute right-0 top-full z-50 mt-1.5 w-[min(18.25rem,calc(100vw-1rem))] overflow-hidden rounded-lg border border-border bg-popover p-3 shadow-lg", children: [
|
|
2703
|
+
/* @__PURE__ */ jsx("h2", { className: "mb-2 text-sm font-semibold text-foreground", children: t("Switch app") }),
|
|
2704
|
+
/* @__PURE__ */ jsxs("div", { "aria-live": "polite", children: [
|
|
2705
|
+
(state.status === "idle" || state.status === "loading") && /* @__PURE__ */ jsx("p", { className: "px-1 py-2 text-sm text-muted-foreground", children: t("Loading apps\u2026") }),
|
|
2706
|
+
state.status === "unavailable" && /* @__PURE__ */ jsxs("div", { className: "space-y-2 px-1 py-2 text-sm", children: [
|
|
2707
|
+
/* @__PURE__ */ jsx("p", { role: "alert", className: "text-muted-foreground", children: t("Apps could not be loaded.") }),
|
|
2708
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => void load(), className: "font-medium text-foreground underline", children: t("Try again") })
|
|
2709
|
+
] }),
|
|
2710
|
+
state.status === "authorization_required" && /* @__PURE__ */ jsxs("div", { className: "space-y-2 px-1 py-2 text-sm", children: [
|
|
2711
|
+
/* @__PURE__ */ jsx("p", { role: "alert", className: "text-muted-foreground", children: t("Apps could not be loaded.") }),
|
|
2712
|
+
reconnect !== null ? /* @__PURE__ */ jsx("a", { href: reconnect, className: "font-medium text-foreground underline", children: t("Reconnect to Cbox ID") }) : /* @__PURE__ */ jsx("button", { type: "button", onClick: () => void load(), className: "font-medium text-foreground underline", children: t("Try again") })
|
|
2713
|
+
] }),
|
|
2714
|
+
state.status === "ready" && (state.apps.length === 0 ? /* @__PURE__ */ jsx("p", { className: "px-1 py-2 text-sm text-muted-foreground", children: t("No apps available") }) : /* @__PURE__ */ jsx("nav", { "aria-label": t("Switch app"), className: "grid grid-cols-3 gap-1", children: state.apps.map((app) => {
|
|
2715
|
+
const current = app.key === currentAppKey;
|
|
2716
|
+
return /* @__PURE__ */ jsxs(
|
|
2717
|
+
"a",
|
|
2718
|
+
{
|
|
2719
|
+
href: app.launch_url,
|
|
2720
|
+
"aria-current": current ? "true" : void 0,
|
|
2721
|
+
title: app.description ?? app.name,
|
|
2722
|
+
onClick: () => setOpen(false),
|
|
2723
|
+
className: cn("relative flex min-h-[60px] min-w-0 flex-col items-center justify-center gap-1.5 rounded-md p-2 text-center text-[13px] font-medium leading-tight text-foreground hover:bg-secondary", current && "bg-accent-soft"),
|
|
2724
|
+
children: [
|
|
2725
|
+
/* @__PURE__ */ jsx(AppMark, { app, icon: icons?.[app.key] }),
|
|
2726
|
+
/* @__PURE__ */ jsx("span", { className: "max-w-full break-words", children: app.name.replace(/^Cbox\s*·\s*/i, "") }),
|
|
2727
|
+
current && /* @__PURE__ */ jsx(Check2, {})
|
|
2728
|
+
]
|
|
2729
|
+
},
|
|
2730
|
+
app.key
|
|
2731
|
+
);
|
|
2732
|
+
}) }))
|
|
2733
|
+
] })
|
|
2734
|
+
] }) : null
|
|
2735
|
+
] });
|
|
2736
|
+
}
|
|
2737
|
+
function AppMark({ app, icon }) {
|
|
2738
|
+
const [failed, setFailed] = useState(false);
|
|
2739
|
+
if (icon !== void 0) {
|
|
2740
|
+
return /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "flex h-6 w-6 items-center justify-center", children: icon });
|
|
2741
|
+
}
|
|
2742
|
+
if (typeof app.icon_url === "string" && app.icon_url !== "" && !failed) {
|
|
2743
|
+
return /* @__PURE__ */ jsx("img", { src: app.icon_url, alt: "", className: "h-6 w-6 rounded object-contain", loading: "lazy", onError: () => setFailed(true) });
|
|
2744
|
+
}
|
|
2745
|
+
return /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "grid h-6 w-6 place-items-center rounded bg-secondary text-[11px] font-semibold text-foreground", children: app.name.slice(0, 2) });
|
|
2746
|
+
}
|
|
2747
|
+
function Grid2() {
|
|
2748
|
+
return /* @__PURE__ */ jsx("svg", { width: "18", height: "18", viewBox: "0 0 18 18", fill: "currentColor", "aria-hidden": true, children: [3, 9, 15].flatMap((y) => [3, 9, 15].map((x) => /* @__PURE__ */ jsx("circle", { cx: x, cy: y, r: "1.65" }, `${x}-${y}`))) });
|
|
2749
|
+
}
|
|
2750
|
+
function Check2() {
|
|
2751
|
+
return /* @__PURE__ */ jsx("svg", { "aria-hidden": true, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", className: "absolute right-2 top-1.5 h-3 w-3 text-accent", children: /* @__PURE__ */ jsx("path", { d: "m5 12 4 4L19 6" }) });
|
|
2752
|
+
}
|
|
2556
2753
|
function Combobox(props) {
|
|
2754
|
+
const { t } = useCboxLocalization();
|
|
2557
2755
|
const {
|
|
2558
2756
|
items,
|
|
2559
|
-
placeholder = "Select\u2026",
|
|
2560
|
-
searchPlaceholder = "Search\u2026",
|
|
2561
|
-
emptyText = "Nothing matches.",
|
|
2757
|
+
placeholder = t("Select\u2026"),
|
|
2758
|
+
searchPlaceholder = t("Search\u2026"),
|
|
2759
|
+
emptyText = t("Nothing matches."),
|
|
2562
2760
|
footerLink,
|
|
2563
2761
|
disabled = false,
|
|
2564
2762
|
required = false,
|
|
@@ -2855,7 +3053,7 @@ function ComboboxGroupRows({
|
|
|
2855
3053
|
/* @__PURE__ */ jsx("span", { className: "truncate text-[13px] font-medium text-foreground", children: opt.label }),
|
|
2856
3054
|
opt.secondary !== void 0 ? /* @__PURE__ */ jsx("span", { className: "text-mono truncate text-[10px] text-muted-foreground", children: opt.secondary }) : null
|
|
2857
3055
|
] }),
|
|
2858
|
-
isActive ? /* @__PURE__ */ jsx(
|
|
3056
|
+
isActive ? /* @__PURE__ */ jsx(Check3, {}) : null
|
|
2859
3057
|
] });
|
|
2860
3058
|
const className = cn(
|
|
2861
3059
|
"group block w-full rounded-sm transition-colors text-left",
|
|
@@ -3037,7 +3235,7 @@ function Chevron4({ open }) {
|
|
|
3037
3235
|
}
|
|
3038
3236
|
);
|
|
3039
3237
|
}
|
|
3040
|
-
function
|
|
3238
|
+
function Check3() {
|
|
3041
3239
|
return /* @__PURE__ */ jsx(
|
|
3042
3240
|
"svg",
|
|
3043
3241
|
{
|
|
@@ -3103,7 +3301,7 @@ function DataPanel({
|
|
|
3103
3301
|
"section",
|
|
3104
3302
|
{
|
|
3105
3303
|
className: cn(
|
|
3106
|
-
"rounded-lg border border-border bg-card text-card-foreground shadow-[
|
|
3304
|
+
"rounded-lg border border-border bg-card text-card-foreground shadow-[0_1px_2px_rgb(0_0_0/0.04),0_4px_12px_rgb(0_0_0/0.03)]",
|
|
3107
3305
|
className
|
|
3108
3306
|
),
|
|
3109
3307
|
...rest,
|
|
@@ -3289,12 +3487,13 @@ function PageTabs({
|
|
|
3289
3487
|
);
|
|
3290
3488
|
}
|
|
3291
3489
|
function DesktopStrip({ items, activeHref, LinkComponent }) {
|
|
3490
|
+
const { t } = useCboxLocalization();
|
|
3292
3491
|
return /* @__PURE__ */ jsx(
|
|
3293
3492
|
"nav",
|
|
3294
3493
|
{
|
|
3295
3494
|
className: "flex min-h-12 flex-1 items-center gap-1 overflow-x-auto overflow-y-hidden py-1.5",
|
|
3296
3495
|
role: "tablist",
|
|
3297
|
-
"aria-label": "Section navigation",
|
|
3496
|
+
"aria-label": t("Section navigation"),
|
|
3298
3497
|
children: items.map((item) => {
|
|
3299
3498
|
const Icon = item.icon;
|
|
3300
3499
|
const active = item.href === activeHref;
|
|
@@ -3330,6 +3529,7 @@ function DesktopStrip({ items, activeHref, LinkComponent }) {
|
|
|
3330
3529
|
);
|
|
3331
3530
|
}
|
|
3332
3531
|
function MobileDropdown({ items, activeHref, LinkComponent }) {
|
|
3532
|
+
const { t } = useCboxLocalization();
|
|
3333
3533
|
const [open, setOpen] = useState(false);
|
|
3334
3534
|
const ref = useRef(null);
|
|
3335
3535
|
const listId = useId();
|
|
@@ -3389,7 +3589,7 @@ function MobileDropdown({ items, activeHref, LinkComponent }) {
|
|
|
3389
3589
|
{
|
|
3390
3590
|
id: listId,
|
|
3391
3591
|
role: "listbox",
|
|
3392
|
-
"aria-label": "Section navigation",
|
|
3592
|
+
"aria-label": t("Section navigation"),
|
|
3393
3593
|
className: "absolute left-0 right-0 top-full z-30 mt-1 overflow-hidden rounded-md border border-border bg-popover shadow-lg",
|
|
3394
3594
|
children: items.map((item) => {
|
|
3395
3595
|
const Icon = item.icon;
|
|
@@ -3590,7 +3790,7 @@ function StatTile({
|
|
|
3590
3790
|
function urlString(defaultValue = "") {
|
|
3591
3791
|
return {
|
|
3592
3792
|
parse: (raw) => raw ?? defaultValue,
|
|
3593
|
-
serialize: (value) => value
|
|
3793
|
+
serialize: (value) => value,
|
|
3594
3794
|
isDefault: (value) => value === defaultValue
|
|
3595
3795
|
};
|
|
3596
3796
|
}
|
|
@@ -3665,7 +3865,152 @@ function buildUrlSearch(state, schema) {
|
|
|
3665
3865
|
}
|
|
3666
3866
|
return params.toString();
|
|
3667
3867
|
}
|
|
3868
|
+
function navMatchLength(path, href) {
|
|
3869
|
+
if (href === "/") {
|
|
3870
|
+
return path === "/" ? 1 : -1;
|
|
3871
|
+
}
|
|
3872
|
+
return path === href || path.startsWith(href + "/") ? href.length : -1;
|
|
3873
|
+
}
|
|
3874
|
+
function activeNavItem(path, items) {
|
|
3875
|
+
let best;
|
|
3876
|
+
let length = -1;
|
|
3877
|
+
for (const item of items) {
|
|
3878
|
+
const match = navMatchLength(path, item.href);
|
|
3879
|
+
if (match > length) {
|
|
3880
|
+
best = item;
|
|
3881
|
+
length = match;
|
|
3882
|
+
}
|
|
3883
|
+
}
|
|
3884
|
+
return best;
|
|
3885
|
+
}
|
|
3886
|
+
function activeNavArea(path, areas) {
|
|
3887
|
+
let best;
|
|
3888
|
+
let length = -1;
|
|
3889
|
+
for (const area of areas) {
|
|
3890
|
+
const candidates = [area.href, ...(area.items ?? []).map((item) => item.href)];
|
|
3891
|
+
const match = Math.max(...candidates.map((href) => navMatchLength(path, href)));
|
|
3892
|
+
if (match > length) {
|
|
3893
|
+
best = area;
|
|
3894
|
+
length = match;
|
|
3895
|
+
}
|
|
3896
|
+
}
|
|
3897
|
+
return best;
|
|
3898
|
+
}
|
|
3899
|
+
function useControllable(value, fallback, onChange) {
|
|
3900
|
+
const [own, setOwn] = useState(fallback);
|
|
3901
|
+
const current = value ?? own;
|
|
3902
|
+
const ref = useRef(current);
|
|
3903
|
+
ref.current = current;
|
|
3904
|
+
const set = useCallback((next) => {
|
|
3905
|
+
const resolved = typeof next === "function" ? next(ref.current) : next;
|
|
3906
|
+
if (value === void 0) setOwn(resolved);
|
|
3907
|
+
onChange?.(resolved);
|
|
3908
|
+
}, [value, onChange]);
|
|
3909
|
+
return [current, set];
|
|
3910
|
+
}
|
|
3911
|
+
function CboxWorkspaceShell({ appKey, brandSubmark, areas, currentPath, LinkComponent: Link, context, tools, account, children, brandHref = "/", mainId = "main-content", pinned: pinnedProp, defaultPinned = false, onPinnedChange, collapsed: collapsedProp, defaultCollapsed = false, onCollapsedChange }) {
|
|
3912
|
+
const { t } = useCboxLocalization();
|
|
3913
|
+
const [pinned, setPinned] = useControllable(pinnedProp, defaultPinned, onPinnedChange);
|
|
3914
|
+
const [collapsed, setCollapsed] = useControllable(collapsedProp, defaultCollapsed, onCollapsedChange);
|
|
3915
|
+
const [expanded, setExpanded] = useState(false);
|
|
3916
|
+
const [mobile, setMobile] = useState(false);
|
|
3917
|
+
const dialog = useRef(null);
|
|
3918
|
+
const path = currentPath.split(/[?#]/)[0] ?? "/";
|
|
3919
|
+
const area = activeNavArea(path, areas);
|
|
3920
|
+
const subnav = area?.items && area.items.length > 1 ? area : void 0;
|
|
3921
|
+
useEffect(() => {
|
|
3922
|
+
setMobile(false);
|
|
3923
|
+
}, [currentPath]);
|
|
3924
|
+
useEffect(() => {
|
|
3925
|
+
const el = dialog.current;
|
|
3926
|
+
if (mobile && el && !el.open) el.showModal();
|
|
3927
|
+
if (!mobile && el?.open) el.close();
|
|
3928
|
+
}, [mobile]);
|
|
3929
|
+
useEffect(() => {
|
|
3930
|
+
const key = (event) => {
|
|
3931
|
+
if ((event.metaKey || event.ctrlKey) && event.key === ".") {
|
|
3932
|
+
event.preventDefault();
|
|
3933
|
+
setCollapsed((value) => !value);
|
|
3934
|
+
}
|
|
3935
|
+
};
|
|
3936
|
+
addEventListener("keydown", key);
|
|
3937
|
+
return () => removeEventListener("keydown", key);
|
|
3938
|
+
}, [setCollapsed]);
|
|
3939
|
+
const open = pinned || expanded;
|
|
3940
|
+
const links = (items) => {
|
|
3941
|
+
const active = activeNavItem(path, items);
|
|
3942
|
+
return items.map((item) => /* @__PURE__ */ jsxs(Link, { href: item.href, className: `cbx-workspace-link ${item === active ? "is-active" : ""}`, "aria-label": item.label, "aria-current": item === active ? "page" : void 0, children: [
|
|
3943
|
+
/* @__PURE__ */ jsx(item.icon, { className: "h-4 w-4 shrink-0", strokeWidth: 1.7 }),
|
|
3944
|
+
/* @__PURE__ */ jsx("span", { children: item.label })
|
|
3945
|
+
] }, item.href));
|
|
3946
|
+
};
|
|
3947
|
+
return /* @__PURE__ */ jsxs("div", { className: "cbx-workspace", children: [
|
|
3948
|
+
/* @__PURE__ */ jsx("div", { className: "cbx-rail-slot", "data-pinned": pinned, children: /* @__PURE__ */ jsxs("aside", { className: "cbx-rail", "data-open": open, onMouseEnter: () => setExpanded(true), onMouseLeave: () => setExpanded(false), onFocusCapture: () => setExpanded(true), onBlurCapture: (event) => {
|
|
3949
|
+
if (!event.currentTarget.contains(event.relatedTarget)) setExpanded(false);
|
|
3950
|
+
}, children: [
|
|
3951
|
+
/* @__PURE__ */ jsxs("div", { className: "cbx-rail-brand", children: [
|
|
3952
|
+
/* @__PURE__ */ jsxs(Link, { href: brandHref, "aria-label": t("Home"), children: [
|
|
3953
|
+
/* @__PURE__ */ jsx("span", { className: "dark:hidden", children: /* @__PURE__ */ jsx(CboxBrand, { appKey, submark: brandSubmark, collapsed: !open }) }),
|
|
3954
|
+
/* @__PURE__ */ jsx("span", { className: "hidden dark:block", children: /* @__PURE__ */ jsx(CboxBrand, { appKey, submark: brandSubmark, collapsed: !open, tone: "light" }) })
|
|
3955
|
+
] }),
|
|
3956
|
+
open && /* @__PURE__ */ jsx("button", { type: "button", onClick: () => setPinned((value) => !value), "aria-label": t(pinned ? "Unpin navigation" : "Pin navigation"), "aria-pressed": pinned, children: pinned ? /* @__PURE__ */ jsx(PinOff, { size: 16 }) : /* @__PURE__ */ jsx(Pin, { size: 16 }) })
|
|
3957
|
+
] }),
|
|
3958
|
+
/* @__PURE__ */ jsx("nav", { "aria-label": brandSubmark, children: areas.map((item) => /* @__PURE__ */ jsxs(Link, { href: item.href, "aria-label": item.label, "aria-current": area === item ? "page" : void 0, title: open ? void 0 : item.label, className: `cbx-workspace-link ${area === item ? "is-active" : ""}`, children: [
|
|
3959
|
+
/* @__PURE__ */ jsx(item.icon, { className: "h-4 w-4 shrink-0", strokeWidth: 1.7 }),
|
|
3960
|
+
open && /* @__PURE__ */ jsx("span", { children: item.label })
|
|
3961
|
+
] }, item.href)) }),
|
|
3962
|
+
/* @__PURE__ */ jsx("div", { className: "cbx-rail-account", children: account })
|
|
3963
|
+
] }) }),
|
|
3964
|
+
/* @__PURE__ */ jsxs("div", { className: "cbx-workspace-column", children: [
|
|
3965
|
+
/* @__PURE__ */ jsxs("header", { className: "cbx-workspace-header", children: [
|
|
3966
|
+
/* @__PURE__ */ jsx("button", { className: "cbx-mobile-trigger", type: "button", onClick: () => setMobile(true), "aria-label": t("Open navigation"), children: /* @__PURE__ */ jsx(Menu, { size: 18 }) }),
|
|
3967
|
+
/* @__PURE__ */ jsx("div", { className: "cbx-workspace-context", children: context }),
|
|
3968
|
+
/* @__PURE__ */ jsx("div", { className: "cbx-workspace-tools", children: tools })
|
|
3969
|
+
] }),
|
|
3970
|
+
/* @__PURE__ */ jsxs("div", { className: "cbx-workspace-body", children: [
|
|
3971
|
+
subnav && /* @__PURE__ */ jsx("aside", { className: "cbx-subnav", "data-collapsed": collapsed, children: collapsed ? /* @__PURE__ */ jsxs("button", { className: "cbx-subnav-strip", type: "button", onClick: () => setCollapsed(false), "aria-label": t("Expand sidebar"), children: [
|
|
3972
|
+
/* @__PURE__ */ jsx(PanelLeftOpen, { size: 16 }),
|
|
3973
|
+
/* @__PURE__ */ jsx("span", { children: subnav.label })
|
|
3974
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3975
|
+
/* @__PURE__ */ jsxs("div", { className: "cbx-subnav-header", children: [
|
|
3976
|
+
/* @__PURE__ */ jsx("strong", { children: subnav.label }),
|
|
3977
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => setCollapsed(true), "aria-label": t("Collapse sidebar"), title: "\u2318.", children: /* @__PURE__ */ jsx(PanelLeftClose, { size: 16 }) })
|
|
3978
|
+
] }),
|
|
3979
|
+
/* @__PURE__ */ jsx("nav", { "aria-label": subnav.label, children: links(subnav.items ?? []) })
|
|
3980
|
+
] }) }),
|
|
3981
|
+
/* @__PURE__ */ jsx("main", { id: mainId, className: "canvas-gradient cbx-workspace-main", children })
|
|
3982
|
+
] })
|
|
3983
|
+
] }),
|
|
3984
|
+
/* @__PURE__ */ jsxs("dialog", { ref: dialog, className: "cbx-workspace-mobile", onClose: () => setMobile(false), onClick: (event) => {
|
|
3985
|
+
if (event.target === event.currentTarget) setMobile(false);
|
|
3986
|
+
}, children: [
|
|
3987
|
+
/* @__PURE__ */ jsxs("div", { className: "cbx-subnav-header", children: [
|
|
3988
|
+
/* @__PURE__ */ jsx("strong", { children: brandSubmark }),
|
|
3989
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => setMobile(false), "aria-label": t("Close navigation"), children: /* @__PURE__ */ jsx(X, { size: 18 }) })
|
|
3990
|
+
] }),
|
|
3991
|
+
/* @__PURE__ */ jsx("nav", { "aria-label": t("Navigation"), children: areas.map((item) => /* @__PURE__ */ jsxs("section", { children: [
|
|
3992
|
+
links([item]),
|
|
3993
|
+
item.items && /* @__PURE__ */ jsx("div", { className: "pl-4", children: links(item.items) })
|
|
3994
|
+
] }, item.href)) }),
|
|
3995
|
+
/* @__PURE__ */ jsx("div", { className: "cbx-rail-account", children: account })
|
|
3996
|
+
] })
|
|
3997
|
+
] });
|
|
3998
|
+
}
|
|
3999
|
+
var CHART_PALETTE = ["var(--chart-1)", "var(--chart-2)", "var(--chart-3)", "var(--chart-4)", "var(--chart-5)"];
|
|
4000
|
+
function Sparkline({ values, color = "var(--chart-1)", width = 90, height = 20 }) {
|
|
4001
|
+
if (values.length === 0) {
|
|
4002
|
+
return null;
|
|
4003
|
+
}
|
|
4004
|
+
const clean = values.map((value) => Number.isFinite(value) ? value : 0);
|
|
4005
|
+
const max = Math.max(1e-9, ...clean);
|
|
4006
|
+
const points = clean.map((value, index) => [index / Math.max(1, clean.length - 1) * (width - 2) + 1, height - 1 - value / max * (height - 3)]);
|
|
4007
|
+
const d = points.map(([px, py], index) => `${index === 0 ? "M" : "L"}${px.toFixed(1)} ${py.toFixed(1)}`).join("");
|
|
4008
|
+
return /* @__PURE__ */ jsxs("svg", { width, height, viewBox: `0 0 ${width} ${height}`, "aria-hidden": "true", children: [
|
|
4009
|
+
/* @__PURE__ */ jsx("path", { d: `${d}L${width - 1} ${height}L1 ${height}Z`, fill: color, opacity: 0.14 }),
|
|
4010
|
+
/* @__PURE__ */ jsx("path", { d, fill: "none", stroke: color, strokeWidth: 1.5, strokeLinejoin: "round", strokeLinecap: "round" })
|
|
4011
|
+
] });
|
|
4012
|
+
}
|
|
3668
4013
|
|
|
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,
|
|
4014
|
+
export { AppContent, AppLauncher, AppShell, AppSwitcher, CHART_PALETTE, CboxAppCatalog, CboxAppShell, CboxAppSidebar, CboxBrand, CboxIdProvider, CboxLocalizationProvider, CboxMobileNavDrawer, CboxOrgContextBar, CboxSurfaces, CboxWorkspaceShell, Combobox, ContextAvatarChip, ContextBar, DataPanel, EmptyState, Heading, KeyValue, KeyValueList, NotificationsBell, OrgSwitcher, PageHeader, PageShell, PageTabs, PanelBody, RowLink, SearchTrigger, SectionHeader, Sparkline, StatTile, StatusPill, TopBar, UserMenu, activeNavArea, activeNavItem, authorizeHref, buildUrlSearch, defineUrlSchema, interpolate, navMatchLength, parseAppCatalog, parseUrlSearch, urlBoolean, urlEnum, urlNumber, urlString, urlStringArray, useCboxId, useCboxIdOptional, useCboxLocalization, useCboxSurfaces, useCboxSurfacesOptional };
|
|
3670
4015
|
//# sourceMappingURL=index.js.map
|
|
3671
4016
|
//# sourceMappingURL=index.js.map
|