@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/dist/index.js CHANGED
@@ -1,10 +1,9 @@
1
- import { createContext, useContext, useEffect, useState, useRef, useMemo, useCallback, useId } from 'react';
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 clsx from 'clsx';
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__ */ jsx(
104
+ submarkText !== null && submarkText !== "" ? /* @__PURE__ */ jsxs(
88
105
  "span",
89
106
  {
90
107
  className: cn(
91
- "text-mono text-[10px] font-semibold tracking-[0.22em] uppercase",
108
+ "text-[13px] font-semibold whitespace-nowrap",
92
109
  tone === "light" ? "text-white/60" : "text-muted-foreground/80"
93
110
  ),
94
- children: submarkText
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__ */ 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,
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: `${app.name} \u2014 coming soon`,
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: `Add ${app.name} to your plan`,
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 ? `Inbox \xB7 ${unread} unread` : "Inbox \xB7 all caught up",
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__ */ jsxs("span", { children: [
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
- new Date(
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 ? `${unreadCount} unread notifications` : "Notifications",
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__ */ jsxs("span", { className: "text-mono text-[10px] tabular-nums text-muted-foreground", children: [
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 new Date(item.createdAt).toLocaleString();
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 = "Search" }) {
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
- function UserMenu({ extras, className }) {
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), children: [
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 right-0 top-full z-50 mt-1 w-64 rounded-md border border-border bg-popover p-1 shadow-md", children: [
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
- "a",
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
- "a",
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 ?? `Switch ${crumb.label}`,
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-medium", children: crumb.label }),
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 ?? `Settings for ${item.label}`,
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-5 w-5 text-[9px]" : "h-7 w-7 text-[11px]";
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: `Settings for ${org.name}`
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__ */ 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) => {
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: `${app.name} \u2014 coming soon`,
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: `Add ${app.name} to your plan`,
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(Check2, {}) : null
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 Check2() {
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-[0_1px_2px_rgba(0,0,0,0.02)]",
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 === "" ? null : 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, buildUrlSearch, cn, defineUrlSchema, parseUrlSearch, urlBoolean, urlEnum, urlNumber, urlString, urlStringArray, useCboxId, useCboxIdOptional, useCboxSurfaces, useCboxSurfacesOptional };
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