@the-portland-company/shell 0.14.3 → 0.15.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/native.cjs CHANGED
@@ -52,6 +52,444 @@ function resolveBrandGlyph(name) {
52
52
  if (!name) return null;
53
53
  return GLYPHS[name.trim().toLowerCase()] ?? null;
54
54
  }
55
+ function topBarIconButtonClass(tone = "default") {
56
+ const toneClass = tone === "danger" || tone === "warning" ? "text-[color:var(--color-race-red)] hover:text-[color:var(--color-race-red)]" : "text-[color:var(--topbar-icon-color,var(--muted))] hover:text-[color:var(--topbar-icon-hover-color,var(--foreground))] hover:bg-[color:var(--topbar-icon-hover-bg,transparent)]";
57
+ return [
58
+ "relative flex items-center justify-center rounded-full border border-[color:var(--line)] transition",
59
+ "h-[var(--topbar-icon-button-size,32px)] w-[var(--topbar-icon-button-size,32px)]",
60
+ toneClass
61
+ ].join(" ");
62
+ }
63
+ function TopBarCountBadge({ count }) {
64
+ if (!(count > 0)) return null;
65
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute -top-1 -right-1 flex h-4 min-w-4 items-center justify-center rounded-full bg-[color:var(--color-race-red)] px-1 text-[10px] text-white", children: count > 99 ? "99+" : count });
66
+ }
67
+ var NavChromeContext = react.createContext(null);
68
+ function NavChromeProvider({
69
+ fetchJson,
70
+ children
71
+ }) {
72
+ return /* @__PURE__ */ jsxRuntime.jsx(NavChromeContext.Provider, { value: { fetchJson }, children });
73
+ }
74
+ function useNavChrome(component) {
75
+ const ctx = react.useContext(NavChromeContext);
76
+ if (!ctx) {
77
+ throw new Error(
78
+ `${component} must be rendered inside <NavChromeProvider> (inject the chrome API fetcher there).`
79
+ );
80
+ }
81
+ return ctx;
82
+ }
83
+ function useChromeOrgs() {
84
+ const { fetchJson } = useNavChrome("useChromeOrgs");
85
+ const [orgs, setOrgs] = react.useState(null);
86
+ const [loading, setLoading] = react.useState(true);
87
+ const [error, setError] = react.useState(false);
88
+ react.useEffect(() => {
89
+ let cancelled = false;
90
+ setLoading(true);
91
+ setError(false);
92
+ fetchJson("orgs").then((data) => {
93
+ if (cancelled) return;
94
+ const list = data?.orgs ?? [];
95
+ setOrgs(list);
96
+ }).catch(() => {
97
+ if (!cancelled) setError(true);
98
+ }).finally(() => {
99
+ if (!cancelled) setLoading(false);
100
+ });
101
+ return () => {
102
+ cancelled = true;
103
+ };
104
+ }, [fetchJson]);
105
+ return { orgs, loading, error };
106
+ }
107
+ function ValueSkeleton({ className }) {
108
+ return /* @__PURE__ */ jsxRuntime.jsx(
109
+ "span",
110
+ {
111
+ "aria-hidden": true,
112
+ className: [
113
+ "inline-block animate-pulse rounded bg-[color:var(--panel-elevated)]",
114
+ className ?? "h-4 w-16"
115
+ ].join(" ")
116
+ }
117
+ );
118
+ }
119
+ function SlotGlyph({
120
+ slot,
121
+ fallback: Fallback,
122
+ size = 20
123
+ }) {
124
+ const glyphName = useTopBarIconSlotGlyph(slot ?? "apps");
125
+ const BrandGlyph = slot ? resolveBrandGlyph(glyphName) : null;
126
+ const Glyph = BrandGlyph ?? Fallback;
127
+ return /* @__PURE__ */ jsxRuntime.jsx(Glyph, { size, className: "text-current" });
128
+ }
129
+ function TopBarPopover({
130
+ label,
131
+ slot,
132
+ fallbackIcon,
133
+ badge,
134
+ tone,
135
+ onOpen,
136
+ children,
137
+ panelClassName
138
+ }) {
139
+ const [open, setOpen] = react.useState(false);
140
+ const rootRef = react.useRef(null);
141
+ react.useEffect(() => {
142
+ if (!open) return;
143
+ const onDocClick = (e) => {
144
+ if (rootRef.current && !rootRef.current.contains(e.target)) setOpen(false);
145
+ };
146
+ const onKey = (e) => {
147
+ if (e.key === "Escape") setOpen(false);
148
+ };
149
+ document.addEventListener("mousedown", onDocClick);
150
+ document.addEventListener("keydown", onKey);
151
+ return () => {
152
+ document.removeEventListener("mousedown", onDocClick);
153
+ document.removeEventListener("keydown", onKey);
154
+ };
155
+ }, [open]);
156
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: rootRef, className: "relative", children: [
157
+ /* @__PURE__ */ jsxRuntime.jsxs(
158
+ "button",
159
+ {
160
+ type: "button",
161
+ "aria-label": label,
162
+ title: label,
163
+ "aria-expanded": open,
164
+ className: topBarIconButtonClass(tone),
165
+ onClick: () => {
166
+ setOpen((v) => {
167
+ const next = !v;
168
+ if (next) onOpen?.();
169
+ return next;
170
+ });
171
+ },
172
+ children: [
173
+ /* @__PURE__ */ jsxRuntime.jsx(SlotGlyph, { slot, fallback: fallbackIcon }),
174
+ typeof badge === "number" ? /* @__PURE__ */ jsxRuntime.jsx(TopBarCountBadge, { count: badge }) : null
175
+ ]
176
+ }
177
+ ),
178
+ open ? /* @__PURE__ */ jsxRuntime.jsx(
179
+ "div",
180
+ {
181
+ className: [
182
+ "absolute right-0 z-40 mt-2 overflow-hidden rounded-xl border border-[color:var(--line)] bg-[color:var(--panel-elevated)] shadow-[var(--shadow-soft)]",
183
+ panelClassName ?? "w-80"
184
+ ].join(" "),
185
+ children
186
+ }
187
+ ) : null
188
+ ] });
189
+ }
190
+ function BatteryGlyph({ size = 20, className }) {
191
+ return /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", className, "aria-hidden": true, children: [
192
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "1", y: "6", width: "18", height: "12", rx: "2", ry: "2" }),
193
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "23", y1: "10", x2: "23", y2: "14" }),
194
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "5", y1: "10", x2: "5", y2: "14" }),
195
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "9", y1: "10", x2: "9", y2: "14" })
196
+ ] });
197
+ }
198
+ function BellGlyph({ size = 20, className }) {
199
+ return /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", className, "aria-hidden": true, children: [
200
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9" }),
201
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M13.73 21a2 2 0 0 1-3.46 0" })
202
+ ] });
203
+ }
204
+ function LifeBuoyGlyph({ size = 20, className }) {
205
+ return /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", className, "aria-hidden": true, children: [
206
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "10" }),
207
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "4" }),
208
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "4.93", y1: "4.93", x2: "9.17", y2: "9.17" }),
209
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "14.83", y1: "14.83", x2: "19.07", y2: "19.07" }),
210
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "14.83", y1: "9.17", x2: "19.07", y2: "4.93" }),
211
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "4.93", y1: "19.07", x2: "9.17", y2: "14.83" })
212
+ ] });
213
+ }
214
+ function LayoutGlyph({ size = 20, className }) {
215
+ return /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", className, "aria-hidden": true, children: [
216
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2", ry: "2" }),
217
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "3", y1: "9", x2: "21", y2: "9" }),
218
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "9", y1: "21", x2: "9", y2: "9" })
219
+ ] });
220
+ }
221
+ function FuelRow({
222
+ label,
223
+ channel,
224
+ loading
225
+ }) {
226
+ const remaining = channel?.remainingUnits ?? 0;
227
+ const total = channel ? Math.max(channel.rawBalanceUnits, channel.remainingUnits) : 0;
228
+ const ratio = total > 0 ? Math.min(100, Math.max(0, remaining / total * 100)) : 0;
229
+ const low = channel?.lowFuel ?? false;
230
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-full", children: [
231
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-1 flex items-center justify-between", children: [
232
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-semibold text-[color:var(--foreground)]", children: label }),
233
+ loading ? /* @__PURE__ */ jsxRuntime.jsx(ValueSkeleton, { className: "h-3.5 w-14" }) : /* @__PURE__ */ jsxRuntime.jsxs(
234
+ "span",
235
+ {
236
+ className: [
237
+ "text-xs font-semibold tabular-nums",
238
+ low ? "text-[color:var(--color-race-red)]" : "text-[color:var(--muted)]"
239
+ ].join(" "),
240
+ children: [
241
+ remaining.toLocaleString(),
242
+ " left"
243
+ ]
244
+ }
245
+ )
246
+ ] }),
247
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-1.5 w-full overflow-hidden rounded-full bg-[color:var(--panel)]", children: loading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full w-full animate-pulse bg-[color:var(--line)]" }) : /* @__PURE__ */ jsxRuntime.jsx(
248
+ "div",
249
+ {
250
+ className: [
251
+ "h-full rounded-full transition-[width]",
252
+ low ? "bg-[color:var(--color-race-red)]" : "bg-[color:var(--color-ok,#00A651)]"
253
+ ].join(" "),
254
+ style: { width: `${ratio}%` }
255
+ }
256
+ ) })
257
+ ] });
258
+ }
259
+ function FuelAction({ orgId }) {
260
+ const { fetchJson } = useNavChrome("FuelAction");
261
+ const [fuel, setFuel] = react.useState(null);
262
+ const [loading, setLoading] = react.useState(true);
263
+ const [error, setError] = react.useState(false);
264
+ react.useEffect(() => {
265
+ let cancelled = false;
266
+ setLoading(true);
267
+ setError(false);
268
+ setFuel(null);
269
+ fetchJson("communications/fuel-status", {
270
+ headers: orgId ? { "x-contacts-org": orgId } : void 0
271
+ }).then((data) => {
272
+ if (cancelled) return;
273
+ setFuel(data?.fuel ?? null);
274
+ }).catch(() => {
275
+ if (!cancelled) setError(true);
276
+ }).finally(() => {
277
+ if (!cancelled) setLoading(false);
278
+ });
279
+ return () => {
280
+ cancelled = true;
281
+ };
282
+ }, [fetchJson, orgId]);
283
+ const low = !!fuel && (fuel.signal.lowFuel || fuel.broadcast.lowFuel);
284
+ return /* @__PURE__ */ jsxRuntime.jsx(
285
+ TopBarPopover,
286
+ {
287
+ label: "Communications Fuel",
288
+ slot: "comms-fuel",
289
+ fallbackIcon: BatteryGlyph,
290
+ tone: low ? "warning" : "default",
291
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-4 py-3", children: [
292
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-3 text-[11px] font-semibold uppercase tracking-[0.08em] text-[color:var(--muted)]", children: "Communications Fuel" }),
293
+ error || !loading && !fuel ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-[color:var(--muted)]", children: "Fuel balance is unavailable right now." }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-3", children: [
294
+ /* @__PURE__ */ jsxRuntime.jsx(FuelRow, { label: "SMS Signal", channel: fuel?.signal ?? null, loading }),
295
+ /* @__PURE__ */ jsxRuntime.jsx(FuelRow, { label: "Email Broadcast", channel: fuel?.broadcast ?? null, loading })
296
+ ] })
297
+ ] })
298
+ }
299
+ );
300
+ }
301
+ function NotificationsAction({ orgId }) {
302
+ const { fetchJson } = useNavChrome("NotificationsAction");
303
+ const [payload, setPayload] = react.useState(null);
304
+ const [loading, setLoading] = react.useState(true);
305
+ const [error, setError] = react.useState(false);
306
+ react.useEffect(() => {
307
+ let cancelled = false;
308
+ setLoading(true);
309
+ setError(false);
310
+ setPayload(null);
311
+ fetchJson("notifications", {
312
+ headers: orgId ? { "x-contacts-org": orgId } : void 0
313
+ }).then((data) => {
314
+ if (!cancelled) setPayload(data);
315
+ }).catch(() => {
316
+ if (!cancelled) setError(true);
317
+ }).finally(() => {
318
+ if (!cancelled) setLoading(false);
319
+ });
320
+ return () => {
321
+ cancelled = true;
322
+ };
323
+ }, [fetchJson, orgId]);
324
+ const count = (payload?.alerts.length ?? 0) + (payload?.replies.length ?? 0);
325
+ return /* @__PURE__ */ jsxRuntime.jsx(
326
+ TopBarPopover,
327
+ {
328
+ label: "Notifications",
329
+ slot: "notifications",
330
+ fallbackIcon: BellGlyph,
331
+ badge: loading ? 0 : count,
332
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-4 py-3", children: [
333
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-2 text-[11px] font-semibold uppercase tracking-[0.08em] text-[color:var(--muted)]", children: "Notifications" }),
334
+ loading ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2 py-1", children: [
335
+ /* @__PURE__ */ jsxRuntime.jsx(ValueSkeleton, { className: "h-4 w-full" }),
336
+ /* @__PURE__ */ jsxRuntime.jsx(ValueSkeleton, { className: "h-4 w-3/4" })
337
+ ] }) : error ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-[color:var(--muted)]", children: "Notifications are unavailable right now." }) : count === 0 ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "py-1 text-sm text-[color:var(--muted)]", children: "You\u2019re all caught up." }) : /* @__PURE__ */ jsxRuntime.jsxs("ul", { className: "flex max-h-80 flex-col gap-1 overflow-y-auto", children: [
338
+ payload?.alerts.map((a) => /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsxs(
339
+ "a",
340
+ {
341
+ href: a.href ?? "#",
342
+ className: "block rounded-lg px-2 py-2 hover:bg-[color:var(--panel)]",
343
+ children: [
344
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-semibold text-[color:var(--foreground)]", children: a.title }),
345
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-[color:var(--muted)]", children: a.description })
346
+ ]
347
+ }
348
+ ) }, a.id)),
349
+ payload?.replies.map((r) => /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "rounded-lg px-2 py-2 hover:bg-[color:var(--panel)]", children: [
350
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-semibold text-[color:var(--foreground)]", children: r.name }),
351
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate text-xs text-[color:var(--muted)]", children: r.preview })
352
+ ] }, r.id))
353
+ ] })
354
+ ] })
355
+ }
356
+ );
357
+ }
358
+ function SupportAction() {
359
+ const { fetchJson } = useNavChrome("SupportAction");
360
+ const [open, setOpen] = react.useState(false);
361
+ const [message, setMessage] = react.useState("");
362
+ const [state, setState] = react.useState("idle");
363
+ const close = react.useCallback(() => {
364
+ setOpen(false);
365
+ setState("idle");
366
+ setMessage("");
367
+ }, []);
368
+ react.useEffect(() => {
369
+ if (!open) return;
370
+ const onKey = (e) => {
371
+ if (e.key === "Escape") close();
372
+ };
373
+ document.addEventListener("keydown", onKey);
374
+ return () => document.removeEventListener("keydown", onKey);
375
+ }, [open, close]);
376
+ const submit = async () => {
377
+ if (!message.trim() || state === "sending") return;
378
+ setState("sending");
379
+ try {
380
+ await fetchJson("support", {
381
+ method: "POST",
382
+ headers: { "Content-Type": "application/json" },
383
+ body: JSON.stringify({ message: message.trim() })
384
+ });
385
+ setState("sent");
386
+ } catch {
387
+ setState("error");
388
+ }
389
+ };
390
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
391
+ /* @__PURE__ */ jsxRuntime.jsx(
392
+ "button",
393
+ {
394
+ type: "button",
395
+ "aria-label": "Support",
396
+ title: "Support",
397
+ className: topBarIconButtonClass(),
398
+ onClick: () => setOpen(true),
399
+ children: /* @__PURE__ */ jsxRuntime.jsx(SlotGlyph, { slot: "support", fallback: LifeBuoyGlyph })
400
+ }
401
+ ),
402
+ open ? /* @__PURE__ */ jsxRuntime.jsx(
403
+ "div",
404
+ {
405
+ className: "fixed inset-0 z-50 flex items-center justify-center bg-black/40 p-4",
406
+ role: "dialog",
407
+ "aria-modal": "true",
408
+ "aria-label": "Support request",
409
+ onMouseDown: (e) => {
410
+ if (e.target === e.currentTarget) close();
411
+ },
412
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-full max-w-md rounded-2xl border border-[color:var(--line)] bg-[color:var(--panel-elevated)] p-6 shadow-[var(--shadow-soft)]", children: [
413
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-lg font-bold tracking-[-0.01em] text-[color:var(--foreground)]", children: "Contact Support" }),
414
+ state === "sent" ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
415
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-3 text-sm text-[color:var(--muted)]", children: "Your request was sent. The team will reply by email." }),
416
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-5 flex justify-end", children: /* @__PURE__ */ jsxRuntime.jsx(
417
+ "button",
418
+ {
419
+ type: "button",
420
+ onClick: close,
421
+ className: "rounded-full bg-[color:var(--color-race-red)] px-4 py-2 text-sm font-semibold text-white hover:opacity-90",
422
+ children: "Done"
423
+ }
424
+ ) })
425
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
426
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-sm text-[color:var(--muted)]", children: "Describe the problem or question. Your account details ride along automatically." }),
427
+ /* @__PURE__ */ jsxRuntime.jsx(
428
+ "textarea",
429
+ {
430
+ value: message,
431
+ onChange: (e) => setMessage(e.target.value),
432
+ rows: 5,
433
+ placeholder: "How can we help?",
434
+ className: "mt-4 w-full resize-y rounded-lg border border-[color:var(--line)] bg-[color:var(--panel)] px-3 py-2 text-sm text-[color:var(--foreground)] placeholder:text-[color:var(--muted)] focus:outline-none focus:ring-2 focus:ring-[color:var(--color-race-red)]"
435
+ }
436
+ ),
437
+ state === "error" ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-2 text-sm text-[color:var(--color-race-red)]", children: "Sending failed \u2014 try again." }) : null,
438
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-4 flex justify-end gap-2", children: [
439
+ /* @__PURE__ */ jsxRuntime.jsx(
440
+ "button",
441
+ {
442
+ type: "button",
443
+ onClick: close,
444
+ className: "rounded-full border border-[color:var(--line)] px-4 py-2 text-sm font-semibold text-[color:var(--foreground)] hover:bg-[color:var(--panel)]",
445
+ children: "Cancel"
446
+ }
447
+ ),
448
+ /* @__PURE__ */ jsxRuntime.jsx(
449
+ "button",
450
+ {
451
+ type: "button",
452
+ onClick: submit,
453
+ disabled: !message.trim() || state === "sending",
454
+ className: "rounded-full bg-[color:var(--color-race-red)] px-4 py-2 text-sm font-semibold text-white transition hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-50",
455
+ children: state === "sending" ? "Sending\u2026" : "Send request"
456
+ }
457
+ )
458
+ ] })
459
+ ] })
460
+ ] })
461
+ }
462
+ ) : null
463
+ ] });
464
+ }
465
+ var NAV_DISPLAY_OPTIONS = [
466
+ { value: "menu", label: "Sub Menu" },
467
+ { value: "tabs", label: "Tabs Only" },
468
+ { value: "both", label: "Both" }
469
+ ];
470
+ function NavDisplayAction({
471
+ value,
472
+ onChange
473
+ }) {
474
+ return /* @__PURE__ */ jsxRuntime.jsx(TopBarPopover, { label: "Navigation display", fallbackIcon: LayoutGlyph, panelClassName: "w-44", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "py-1", role: "radiogroup", "aria-label": "Navigation display", children: NAV_DISPLAY_OPTIONS.map((opt) => /* @__PURE__ */ jsxRuntime.jsxs(
475
+ "button",
476
+ {
477
+ type: "button",
478
+ role: "radio",
479
+ "aria-checked": value === opt.value,
480
+ onClick: () => onChange(opt.value),
481
+ className: [
482
+ "flex w-full items-center justify-between px-4 py-2 text-left text-sm font-medium",
483
+ value === opt.value ? "text-[color:var(--color-race-red)]" : "text-[color:var(--foreground)] hover:bg-[color:var(--panel)]"
484
+ ].join(" "),
485
+ children: [
486
+ opt.label,
487
+ value === opt.value ? /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": true, children: "\u2713" }) : null
488
+ ]
489
+ },
490
+ opt.value
491
+ )) }) });
492
+ }
55
493
 
56
494
  // src/native/standardNav.ts
57
495
  var CONTACTS_DEFAULT_CHILDREN = [
@@ -211,7 +649,52 @@ function NavSidebar({
211
649
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[11px] font-semibold uppercase tracking-[0.08em] text-[color:var(--muted)]", children: "Organization" }),
212
650
  org.id ? /* @__PURE__ */ jsxRuntime.jsx(OrgIdCopyButton, { id: org.id }) : null
213
651
  ] }),
214
- /* @__PURE__ */ jsxRuntime.jsxs(
652
+ org.organizations && org.organizations.length > 0 && org.onSwitch ? /* @__PURE__ */ jsxRuntime.jsxs("details", { className: "group relative", children: [
653
+ /* @__PURE__ */ jsxRuntime.jsxs(
654
+ "summary",
655
+ {
656
+ style: { backgroundColor: orgBg },
657
+ className: "flex w-full cursor-pointer list-none items-center justify-between gap-2 rounded-md px-2.5 py-2 text-left text-white shadow-sm transition hover:opacity-95 [&::-webkit-details-marker]:hidden",
658
+ children: [
659
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex min-w-0 items-center gap-2.5", children: [
660
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex h-7 w-7 shrink-0 items-center justify-center overflow-hidden rounded-full bg-white/20 text-[11px] font-bold text-white", children: org.logoUrl ? /* @__PURE__ */ jsxRuntime.jsx("img", { src: org.logoUrl, alt: "", className: "h-full w-full object-cover" }) : (org.name ?? "Org").slice(0, 2).toUpperCase() }),
661
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-sm font-semibold", children: org.name ?? "No organizations" })
662
+ ] }),
663
+ /* @__PURE__ */ jsxRuntime.jsx(ChevronDown, {})
664
+ ]
665
+ }
666
+ ),
667
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute left-0 right-0 z-40 mt-1 max-h-72 overflow-y-auto rounded-xl border border-[color:var(--line)] bg-[color:var(--panel-elevated)] py-1 shadow-[var(--shadow-soft)]", children: org.organizations.map((o) => {
668
+ const selected = o.id === org.id;
669
+ return /* @__PURE__ */ jsxRuntime.jsxs(
670
+ "button",
671
+ {
672
+ type: "button",
673
+ onClick: (e) => {
674
+ e.currentTarget.closest("details")?.removeAttribute("open");
675
+ if (!selected) org.onSwitch?.(o.id);
676
+ },
677
+ "aria-current": selected ? "true" : void 0,
678
+ className: [
679
+ "flex w-full items-center gap-2.5 px-3 py-2 text-left text-sm",
680
+ selected ? "font-semibold text-[color:var(--color-race-red)]" : "font-medium text-[color:var(--foreground)] hover:bg-[color:var(--panel)]"
681
+ ].join(" "),
682
+ children: [
683
+ /* @__PURE__ */ jsxRuntime.jsx(
684
+ "span",
685
+ {
686
+ style: { backgroundColor: o.brandColor || ORG_PILL_DEFAULT },
687
+ className: "flex h-6 w-6 shrink-0 items-center justify-center overflow-hidden rounded-full text-[10px] font-bold text-white",
688
+ children: o.logoUrl ? /* @__PURE__ */ jsxRuntime.jsx("img", { src: o.logoUrl, alt: "", className: "h-full w-full object-cover" }) : (o.name ?? "Org").slice(0, 2).toUpperCase()
689
+ }
690
+ ),
691
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: o.name ?? o.id.slice(0, 8) })
692
+ ]
693
+ },
694
+ o.id
695
+ );
696
+ }) })
697
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(
215
698
  "button",
216
699
  {
217
700
  type: "button",
@@ -542,13 +1025,7 @@ function SignOutIcon() {
542
1025
  }
543
1026
  function NavTopBarActionButton({ action }) {
544
1027
  const Icon = action.icon;
545
- const tone = action.tone ?? "default";
546
- const toneClass = tone === "danger" || tone === "warning" ? "text-[color:var(--color-race-red)] hover:text-[color:var(--color-race-red)]" : "text-[color:var(--topbar-icon-color,var(--muted))] hover:text-[color:var(--topbar-icon-hover-color,var(--foreground))] hover:bg-[color:var(--topbar-icon-hover-bg,transparent)]";
547
- const className = [
548
- "relative flex items-center justify-center rounded-full border border-[color:var(--line)] transition",
549
- "h-[var(--topbar-icon-button-size,32px)] w-[var(--topbar-icon-button-size,32px)]",
550
- toneClass
551
- ].join(" ");
1028
+ const className = topBarIconButtonClass(action.tone ?? "default");
552
1029
  const glyphName = useTopBarIconSlotGlyph(action.slot ?? "apps");
553
1030
  const BrandGlyph = action.slot ? resolveBrandGlyph(glyphName) : null;
554
1031
  const Glyph = BrandGlyph ?? Icon;
@@ -690,12 +1167,20 @@ function NavAppShell({ sidebar, topBar, children }) {
690
1167
  ] });
691
1168
  }
692
1169
 
1170
+ exports.FuelAction = FuelAction;
693
1171
  exports.NavAppShell = NavAppShell;
1172
+ exports.NavChromeProvider = NavChromeProvider;
1173
+ exports.NavDisplayAction = NavDisplayAction;
694
1174
  exports.NavSidebar = NavSidebar;
695
1175
  exports.NavTabStrip = NavTabStrip;
696
1176
  exports.NavTopBar = NavTopBar;
1177
+ exports.NotificationsAction = NotificationsAction;
1178
+ exports.SupportAction = SupportAction;
1179
+ exports.TopBarCountBadge = TopBarCountBadge;
697
1180
  exports.envChipClasses = envChipClasses;
698
1181
  exports.isNavItemActive = isNavItemActive;
699
1182
  exports.standardPolitogyNav = standardPolitogyNav;
1183
+ exports.topBarIconButtonClass = topBarIconButtonClass;
1184
+ exports.useChromeOrgs = useChromeOrgs;
700
1185
  //# sourceMappingURL=native.cjs.map
701
1186
  //# sourceMappingURL=native.cjs.map