@the-portland-company/shell 0.14.2 → 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/chrome.css +20 -6
- package/dist/native.cjs +494 -9
- package/dist/native.cjs.map +1 -1
- package/dist/native.d.cts +91 -2
- package/dist/native.d.ts +91 -2
- package/dist/native.js +490 -12
- package/dist/native.js.map +1 -1
- package/package.json +1 -1
package/dist/chrome.css
CHANGED
|
@@ -251,6 +251,10 @@
|
|
|
251
251
|
--muted: var(--topbar-breadcrumb-color);
|
|
252
252
|
--panel: var(--topbar-bg);
|
|
253
253
|
--panel-elevated: var(--surface-1);
|
|
254
|
+
/* The left bar's own surface. Separate from --panel-elevated (which also
|
|
255
|
+
backs hover states and dropdowns) so dark can put the bars and the raised
|
|
256
|
+
surfaces on different steps of the neutral ramp. */
|
|
257
|
+
--nav-surface: var(--surface-1);
|
|
254
258
|
--line: var(--sidebar-border);
|
|
255
259
|
--shadow-soft: var(--shadow-lg);
|
|
256
260
|
}
|
|
@@ -274,11 +278,21 @@
|
|
|
274
278
|
--border: 217.2 32.6% 17.5%;
|
|
275
279
|
--input: 217.2 32.6% 17.5%;
|
|
276
280
|
--ring: 212.7 26.8% 83.9%;
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
281
|
+
/* Native chrome semantic layer — dark values.
|
|
282
|
+
Global Rules: dark chrome is NEUTRAL BLACK, never blue/navy/slate. These
|
|
283
|
+
were ShadCN slate (hsl(222.2 84% 4.9%) = #020817, hsl(217.2 32.6% 17.5%)
|
|
284
|
+
= #1E293B) — the exact palette the rule names as forbidden — which made
|
|
285
|
+
the top bar and left bar read as navy. The ramp below is the hue-free one
|
|
286
|
+
from Reference/Global-Rules: page #0A0A0A → bars #161616 → raised #1F1F1F
|
|
287
|
+
→ hover #2B2B2B → borders #333333, with #A3A3A3 muted / #E8E8E8 body text.
|
|
288
|
+
The ShadCN HSL channel tokens above stay as-is — they are the separate
|
|
289
|
+
darkMode group and are Brand-API-controlled. */
|
|
290
|
+
--background: #0A0A0A;
|
|
291
|
+
--foreground: #E8E8E8;
|
|
292
|
+
--muted: #A3A3A3;
|
|
293
|
+
--panel: #161616;
|
|
294
|
+
--nav-surface: #161616;
|
|
295
|
+
--panel-elevated: #1F1F1F;
|
|
296
|
+
--line: #333333;
|
|
283
297
|
--shadow-soft: 0 4px 16px rgba(0, 0, 0, 0.5);
|
|
284
298
|
}
|
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 = [
|
|
@@ -182,7 +620,7 @@ function NavSidebar({
|
|
|
182
620
|
setOpenGroup(activeGroupKey);
|
|
183
621
|
}, [activeGroupKey]);
|
|
184
622
|
const toggleGroup = (key) => setOpenGroup((prev) => prev === key ? null : key);
|
|
185
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("aside", { className: "fixed inset-y-0 left-0 z-30 hidden w-[240px] flex-col border-r border-[color:var(--line)] bg-[color:var(--
|
|
623
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("aside", { className: "fixed inset-y-0 left-0 z-30 hidden w-[240px] flex-col border-r border-[color:var(--line)] bg-[color:var(--nav-surface)] lg:flex", children: [
|
|
186
624
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
187
625
|
Link,
|
|
188
626
|
{
|
|
@@ -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
|
|
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
|