@xzibit/ui 0.1.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.cjs ADDED
@@ -0,0 +1,555 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ AppsDropdown: () => AppsDropdown,
24
+ BackToLauncher: () => BackToLauncher,
25
+ TopBar: () => TopBar,
26
+ XzibitMark: () => XzibitMark,
27
+ useApps: () => useApps
28
+ });
29
+ module.exports = __toCommonJS(index_exports);
30
+
31
+ // src/BackToLauncher.tsx
32
+ var import_react = require("react");
33
+
34
+ // src/XzibitMark.tsx
35
+ var import_jsx_runtime = require("react/jsx-runtime");
36
+ function XzibitMark({ size = 28, className, ariaLabel }) {
37
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
38
+ "svg",
39
+ {
40
+ width: size,
41
+ height: size,
42
+ viewBox: "0 0 100 100",
43
+ xmlns: "http://www.w3.org/2000/svg",
44
+ role: ariaLabel ? "img" : "presentation",
45
+ "aria-label": ariaLabel,
46
+ "aria-hidden": ariaLabel ? void 0 : true,
47
+ className,
48
+ children: [
49
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: 0, y: 0, width: 100, height: 100, fill: "#000000" }),
50
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
51
+ "g",
52
+ {
53
+ stroke: "#FFFFFF",
54
+ strokeWidth: 9,
55
+ fill: "none",
56
+ strokeLinecap: "square",
57
+ strokeLinejoin: "miter",
58
+ children: [
59
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M 22 22 L 30 22 L 30 32 L 50 50" }),
60
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M 78 22 L 70 22 L 70 32 L 50 50" }),
61
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M 22 78 L 30 78 L 30 68 L 50 50" }),
62
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M 78 78 L 70 78 L 70 68 L 50 50" })
63
+ ]
64
+ }
65
+ )
66
+ ]
67
+ }
68
+ );
69
+ }
70
+
71
+ // src/BackToLauncher.tsx
72
+ var import_jsx_runtime2 = require("react/jsx-runtime");
73
+ var DEFAULT_LAUNCHER_URL = "https://xzibit-apps.vercel.app";
74
+ function BackToLauncher({
75
+ launcherUrl = DEFAULT_LAUNCHER_URL
76
+ }) {
77
+ const [hover, setHover] = (0, import_react.useState)(false);
78
+ const chevronColor = hover ? "#ffffff" : "rgba(255, 255, 255, 0.7)";
79
+ const textColor = hover ? "#ffffff" : "rgba(255, 255, 255, 0.85)";
80
+ const bgColor = hover ? "rgba(255, 255, 255, 0.07)" : "transparent";
81
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
82
+ "a",
83
+ {
84
+ href: launcherUrl,
85
+ "aria-label": "Back to Xzibit Apps launcher",
86
+ onMouseEnter: () => setHover(true),
87
+ onMouseLeave: () => setHover(false),
88
+ onFocus: () => setHover(true),
89
+ onBlur: () => setHover(false),
90
+ style: {
91
+ display: "flex",
92
+ alignItems: "center",
93
+ gap: "8px",
94
+ padding: "0 1rem",
95
+ height: "44px",
96
+ fontSize: "15px",
97
+ fontWeight: 500,
98
+ color: textColor,
99
+ background: bgColor,
100
+ textDecoration: "none",
101
+ transition: "background 120ms, color 120ms"
102
+ },
103
+ children: [
104
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
105
+ "svg",
106
+ {
107
+ width: 12,
108
+ height: 12,
109
+ viewBox: "0 0 12 12",
110
+ fill: "none",
111
+ "aria-hidden": "true",
112
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
113
+ "path",
114
+ {
115
+ d: "M 8 2 L 3 6 L 8 10",
116
+ stroke: chevronColor,
117
+ strokeWidth: 1.8,
118
+ strokeLinecap: "round",
119
+ strokeLinejoin: "round"
120
+ }
121
+ )
122
+ }
123
+ ),
124
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(XzibitMark, { size: 28 }),
125
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: "Xzibit Apps" })
126
+ ]
127
+ }
128
+ );
129
+ }
130
+
131
+ // src/AppsDropdown.tsx
132
+ var import_react3 = require("react");
133
+
134
+ // src/useApps.ts
135
+ var import_react2 = require("react");
136
+ function useApps(options = {}) {
137
+ const { endpoint = "/api/me/apps", lazy = false } = options;
138
+ const [apps, setApps] = (0, import_react2.useState)([]);
139
+ const [loading, setLoading] = (0, import_react2.useState)(false);
140
+ const [error, setError] = (0, import_react2.useState)(null);
141
+ const fetchApps = (0, import_react2.useCallback)(async () => {
142
+ setLoading(true);
143
+ setError(null);
144
+ try {
145
+ const res = await fetch(endpoint, {
146
+ credentials: "include",
147
+ headers: { Accept: "application/json" }
148
+ });
149
+ if (!res.ok) {
150
+ throw new Error(`Failed to load apps (HTTP ${res.status})`);
151
+ }
152
+ const data = await res.json();
153
+ if (data.error) {
154
+ throw new Error(data.error);
155
+ }
156
+ setApps(data.apps ?? []);
157
+ } catch (err) {
158
+ console.error("[@xzibit/ui useApps] fetch failed:", err);
159
+ setError(err instanceof Error ? err.message : "Unknown error");
160
+ setApps([]);
161
+ } finally {
162
+ setLoading(false);
163
+ }
164
+ }, [endpoint]);
165
+ (0, import_react2.useEffect)(() => {
166
+ if (!lazy) {
167
+ fetchApps();
168
+ }
169
+ }, [fetchApps, lazy]);
170
+ return { apps, loading, error, refetch: fetchApps };
171
+ }
172
+
173
+ // src/AppsDropdown.tsx
174
+ var import_jsx_runtime3 = require("react/jsx-runtime");
175
+ function AppsDropdown({ endpoint } = {}) {
176
+ const [open, setOpen] = (0, import_react3.useState)(false);
177
+ const { apps, loading, error } = useApps({ endpoint });
178
+ const buttonRef = (0, import_react3.useRef)(null);
179
+ const panelRef = (0, import_react3.useRef)(null);
180
+ (0, import_react3.useEffect)(() => {
181
+ if (!open) return;
182
+ const handler = (e) => {
183
+ if (e.key === "Escape") {
184
+ setOpen(false);
185
+ buttonRef.current?.focus();
186
+ }
187
+ };
188
+ document.addEventListener("keydown", handler);
189
+ return () => document.removeEventListener("keydown", handler);
190
+ }, [open]);
191
+ (0, import_react3.useEffect)(() => {
192
+ if (!open) return;
193
+ const handler = (e) => {
194
+ const target = e.target;
195
+ if (panelRef.current?.contains(target)) return;
196
+ if (buttonRef.current?.contains(target)) return;
197
+ setOpen(false);
198
+ };
199
+ document.addEventListener("mousedown", handler);
200
+ return () => document.removeEventListener("mousedown", handler);
201
+ }, [open]);
202
+ const grouped = groupAndSort(apps);
203
+ const hasApps = grouped.length > 0;
204
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { position: "relative" }, children: [
205
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
206
+ "button",
207
+ {
208
+ ref: buttonRef,
209
+ type: "button",
210
+ onClick: () => setOpen((o) => !o),
211
+ "aria-haspopup": "menu",
212
+ "aria-expanded": open,
213
+ style: {
214
+ display: "flex",
215
+ alignItems: "center",
216
+ gap: "6px",
217
+ padding: "0 1rem",
218
+ height: "44px",
219
+ background: open ? "rgba(255, 255, 255, 0.05)" : "transparent",
220
+ border: "none",
221
+ color: open ? "#ffffff" : "rgba(255, 255, 255, 0.7)",
222
+ fontSize: "13px",
223
+ fontWeight: 400,
224
+ cursor: "pointer",
225
+ fontFamily: "inherit",
226
+ transition: "background 120ms, color 120ms"
227
+ },
228
+ children: [
229
+ "Apps",
230
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { width: 10, height: 10, viewBox: "0 0 10 10", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
231
+ "path",
232
+ {
233
+ d: open ? "M 1 7 L 5 3 L 9 7" : "M 1 3 L 5 7 L 9 3",
234
+ stroke: "currentColor",
235
+ strokeWidth: 1.5,
236
+ strokeLinecap: "round",
237
+ strokeLinejoin: "round"
238
+ }
239
+ ) })
240
+ ]
241
+ }
242
+ ),
243
+ open && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
244
+ "div",
245
+ {
246
+ ref: panelRef,
247
+ role: "menu",
248
+ style: {
249
+ position: "absolute",
250
+ top: "44px",
251
+ left: 0,
252
+ minWidth: "280px",
253
+ maxWidth: "360px",
254
+ maxHeight: "60vh",
255
+ overflowY: "auto",
256
+ background: "var(--xz-white, #ffffff)",
257
+ border: "1px solid var(--border, #E2E4E5)",
258
+ borderRadius: "8px",
259
+ boxShadow: "var(--shadow-card-hover, 0 4px 12px rgba(29,37,45,0.10), 0 12px 32px rgba(29,37,45,0.10))",
260
+ padding: "0.5rem",
261
+ zIndex: 110
262
+ },
263
+ children: [
264
+ loading && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(DropdownSkeleton, {}),
265
+ !loading && error && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(DropdownError, { message: error }),
266
+ !loading && !error && !hasApps && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(DropdownEmpty, {}),
267
+ !loading && !error && hasApps && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_jsx_runtime3.Fragment, { children: grouped.map((group, i) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react3.Fragment, { children: [
268
+ i > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
269
+ "hr",
270
+ {
271
+ "aria-hidden": "true",
272
+ style: {
273
+ border: "none",
274
+ borderTop: "1px solid var(--border, #E2E4E5)",
275
+ margin: "8px 0"
276
+ }
277
+ }
278
+ ),
279
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(DropdownSection, { section: group.section, apps: group.apps })
280
+ ] }, group.section ?? "__no_section__")) })
281
+ ]
282
+ }
283
+ )
284
+ ] });
285
+ }
286
+ function groupAndSort(apps) {
287
+ const sectionMap = /* @__PURE__ */ new Map();
288
+ const sectionOrder = /* @__PURE__ */ new Map();
289
+ apps.forEach((app, i) => {
290
+ const key = app.section ?? null;
291
+ if (!sectionMap.has(key)) {
292
+ sectionMap.set(key, []);
293
+ sectionOrder.set(key, app.section_order ?? i);
294
+ }
295
+ sectionMap.get(key).push(app);
296
+ });
297
+ const groups = Array.from(sectionMap.entries()).map(
298
+ ([section, sectionApps]) => ({
299
+ section,
300
+ apps: [...sectionApps].sort(
301
+ (a, b) => a.name.localeCompare(b.name, void 0, { sensitivity: "base" })
302
+ )
303
+ })
304
+ );
305
+ groups.sort(
306
+ (a, b) => (sectionOrder.get(a.section) ?? Number.MAX_SAFE_INTEGER) - (sectionOrder.get(b.section) ?? Number.MAX_SAFE_INTEGER)
307
+ );
308
+ return groups;
309
+ }
310
+ function DropdownSection({
311
+ section,
312
+ apps
313
+ }) {
314
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
315
+ section && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
316
+ "div",
317
+ {
318
+ style: {
319
+ fontSize: "11px",
320
+ fontWeight: 500,
321
+ color: "var(--muted-foreground, #888A8B)",
322
+ letterSpacing: "0.06em",
323
+ textTransform: "uppercase",
324
+ padding: "0 0.75rem",
325
+ marginBottom: "4px"
326
+ },
327
+ children: section
328
+ }
329
+ ),
330
+ apps.map((app) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(DropdownItem, { app }, app.name + app.url))
331
+ ] });
332
+ }
333
+ function DropdownItem({ app }) {
334
+ const [hover, setHover] = (0, import_react3.useState)(false);
335
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
336
+ "a",
337
+ {
338
+ href: app.url,
339
+ role: "menuitem",
340
+ onMouseEnter: () => setHover(true),
341
+ onMouseLeave: () => setHover(false),
342
+ style: {
343
+ display: "block",
344
+ padding: "0.5rem 0.75rem",
345
+ borderRadius: "4px",
346
+ fontSize: "14px",
347
+ fontWeight: 400,
348
+ color: "var(--foreground, #1D252D)",
349
+ textDecoration: "none",
350
+ background: hover ? "var(--xz-off-white, #F4F4F2)" : "transparent",
351
+ transition: "background 80ms"
352
+ },
353
+ children: app.name
354
+ }
355
+ );
356
+ }
357
+ function DropdownSkeleton() {
358
+ const widths = ["78%", "64%", "82%", "70%"];
359
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { padding: "0.5rem 0.75rem" }, children: [
360
+ widths.map((w, i) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
361
+ "div",
362
+ {
363
+ "aria-hidden": "true",
364
+ style: {
365
+ height: "14px",
366
+ background: "rgba(216, 218, 219, 0.4)",
367
+ borderRadius: "4px",
368
+ marginBottom: "8px",
369
+ width: w,
370
+ animation: "xzibit-pulse 1.4s ease-in-out infinite"
371
+ }
372
+ },
373
+ i
374
+ )),
375
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("style", { children: `@keyframes xzibit-pulse { 0%, 100% { opacity: 1 } 50% { opacity: 0.5 } }` })
376
+ ] });
377
+ }
378
+ function DropdownError({ message }) {
379
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
380
+ "div",
381
+ {
382
+ role: "alert",
383
+ style: {
384
+ padding: "0.75rem",
385
+ fontSize: "13px",
386
+ fontWeight: 400,
387
+ color: "var(--destructive, #C0392B)"
388
+ },
389
+ children: [
390
+ "Failed to load apps: ",
391
+ message
392
+ ]
393
+ }
394
+ );
395
+ }
396
+ function DropdownEmpty() {
397
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
398
+ "div",
399
+ {
400
+ style: {
401
+ padding: "0.75rem",
402
+ fontSize: "13px",
403
+ fontWeight: 400,
404
+ color: "var(--muted-foreground, #888A8B)"
405
+ },
406
+ children: "No other apps available"
407
+ }
408
+ );
409
+ }
410
+
411
+ // src/TopBar.tsx
412
+ var import_jsx_runtime4 = require("react/jsx-runtime");
413
+ function TopBar({
414
+ appName,
415
+ appHomeUrl = "/",
416
+ buildSha,
417
+ buildTimestamp,
418
+ launcherUrl,
419
+ appsEndpoint
420
+ }) {
421
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
422
+ "header",
423
+ {
424
+ style: {
425
+ position: "fixed",
426
+ top: 0,
427
+ left: 0,
428
+ right: 0,
429
+ height: "44px",
430
+ zIndex: 100,
431
+ background: "var(--xz-charcoal, #252E38)",
432
+ borderBottom: "1px solid rgba(255, 255, 255, 0.08)",
433
+ padding: "0 1rem",
434
+ display: "flex",
435
+ alignItems: "center"
436
+ },
437
+ children: [
438
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
439
+ "a",
440
+ {
441
+ href: "#main",
442
+ style: {
443
+ position: "absolute",
444
+ left: "-9999px",
445
+ top: "auto",
446
+ width: "1px",
447
+ height: "1px",
448
+ overflow: "hidden"
449
+ },
450
+ children: "Skip to main content"
451
+ }
452
+ ),
453
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(BackToLauncher, { launcherUrl }),
454
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(VerticalSeparator, {}),
455
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(AppWordmark, { appName, appHomeUrl }),
456
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(VerticalSeparator, {}),
457
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(AppsDropdown, { endpoint: appsEndpoint }),
458
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
459
+ "div",
460
+ {
461
+ style: {
462
+ marginLeft: "auto",
463
+ display: "flex",
464
+ alignItems: "center"
465
+ },
466
+ children: buildSha && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(BuildBadge, { sha: buildSha, timestamp: buildTimestamp })
467
+ }
468
+ )
469
+ ]
470
+ }
471
+ );
472
+ }
473
+ function VerticalSeparator() {
474
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
475
+ "div",
476
+ {
477
+ "aria-hidden": "true",
478
+ style: {
479
+ width: "1px",
480
+ height: "26px",
481
+ background: "rgba(255, 255, 255, 0.08)",
482
+ flexShrink: 0
483
+ }
484
+ }
485
+ );
486
+ }
487
+ function AppWordmark({
488
+ appName,
489
+ appHomeUrl
490
+ }) {
491
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
492
+ "a",
493
+ {
494
+ href: appHomeUrl,
495
+ style: {
496
+ display: "flex",
497
+ alignItems: "center",
498
+ gap: "8px",
499
+ padding: "0 1rem",
500
+ height: "44px",
501
+ textDecoration: "none",
502
+ color: "#ffffff",
503
+ fontSize: "18px",
504
+ fontWeight: 500
505
+ },
506
+ children: [
507
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
508
+ "span",
509
+ {
510
+ "aria-hidden": "true",
511
+ style: {
512
+ width: "4px",
513
+ height: "4px",
514
+ background: "var(--xz-teal, #19B1A1)",
515
+ borderRadius: "50%",
516
+ display: "inline-block",
517
+ flexShrink: 0
518
+ }
519
+ }
520
+ ),
521
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: appName })
522
+ ]
523
+ }
524
+ );
525
+ }
526
+ function BuildBadge({
527
+ sha,
528
+ timestamp
529
+ }) {
530
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
531
+ "div",
532
+ {
533
+ "aria-hidden": "true",
534
+ style: {
535
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
536
+ fontSize: "11px",
537
+ fontWeight: 400,
538
+ color: "rgba(255, 255, 255, 0.5)",
539
+ padding: "0 1rem"
540
+ },
541
+ children: [
542
+ sha,
543
+ timestamp && ` \xB7 Last updated ${timestamp}`
544
+ ]
545
+ }
546
+ );
547
+ }
548
+ // Annotate the CommonJS export names for ESM import in node:
549
+ 0 && (module.exports = {
550
+ AppsDropdown,
551
+ BackToLauncher,
552
+ TopBar,
553
+ XzibitMark,
554
+ useApps
555
+ });
@@ -0,0 +1,153 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ interface TopBarProps {
4
+ /** App display name shown in the wordmark slot (e.g. "ERP Overview"). */
5
+ appName: string;
6
+ /** Optional click target for the app wordmark. Defaults to "/" (app home). */
7
+ appHomeUrl?: string;
8
+ /** Build SHA — typically `process.env.VERCEL_GIT_COMMIT_SHA?.slice(0, 7)`. */
9
+ buildSha?: string;
10
+ /**
11
+ * Last-updated timestamp string. Pre-format on the consumer side
12
+ * (e.g. "22 May 2026, 5:03 pm AEST"). Brisbane time recommended per
13
+ * portfolio convention.
14
+ */
15
+ buildTimestamp?: string;
16
+ /** Launcher URL override — defaults to https://xzibit-apps.vercel.app. */
17
+ launcherUrl?: string;
18
+ /** Override the /api/me/apps endpoint for the dropdown. */
19
+ appsEndpoint?: string;
20
+ }
21
+ /**
22
+ * Universal 44px fixed top bar across every Xzibit App.
23
+ *
24
+ * Per DESIGN-STANDARD v2.3 + v2.3.1 §Top Bar:
25
+ * - Absorbs back-to-launcher anchor + app wordmark + build badge
26
+ * - Adds Apps dropdown (dynamic data, sectioned + within-section alphabetical)
27
+ * - Sits above the left rail (Pattern A apps) at z-index 100
28
+ * - Page content offsets `margin-top: 44px`
29
+ *
30
+ * Reference impl: xzibit-apps/erp-overview SHA 6f1a5b5+
31
+ */
32
+ declare function TopBar({ appName, appHomeUrl, buildSha, buildTimestamp, launcherUrl, appsEndpoint, }: TopBarProps): react_jsx_runtime.JSX.Element;
33
+
34
+ interface BackToLauncherProps {
35
+ /** Launcher URL — defaults to the production Xzibit Apps launcher. */
36
+ launcherUrl?: string;
37
+ }
38
+ /**
39
+ * Back-to-launcher anchor for the TopBar's left cluster.
40
+ *
41
+ * Renders `[chevron-left] [Xzibit X logo] [Xzibit Apps text]` inside a single
42
+ * `<a>` element so the whole cluster is one click target with one navigation
43
+ * destination. Cross-deployment navigation — uses native anchor, NOT Next.js
44
+ * Link (cross-domain).
45
+ *
46
+ * Per DESIGN-STANDARD v2.3.1:
47
+ * - Text "Xzibit Apps" at 15px / 500 / 85% white (full white on hover)
48
+ * - Chevron 12×12 at 70% white (full white on hover)
49
+ * - X logo 28×28 unchanged on hover (stays native white-on-black stamp)
50
+ */
51
+ declare function BackToLauncher({ launcherUrl, }: BackToLauncherProps): react_jsx_runtime.JSX.Element;
52
+
53
+ interface AppsDropdownProps {
54
+ /** Override the endpoint URL. Default: '/api/me/apps' via useApps(). */
55
+ endpoint?: string;
56
+ }
57
+ /**
58
+ * Apps dropdown for the TopBar — sectioned + within-section alphabetical.
59
+ *
60
+ * Per DESIGN-STANDARD v2.3.1 §Top Bar §Apps dropdown panel:
61
+ * - Groups apps by `section` field
62
+ * - Section order from `section_order` (matches launcher curation)
63
+ * - Within each section: alphabetical by `name` ASC, case-insensitive
64
+ * - Section heading: 11px / 500 / muted / uppercase / +0.06em letter-spacing
65
+ * - Section divider: 1px var(--border), 8px margin top/bottom — NOT above
66
+ * the first section, NOT below the last
67
+ * - Skeleton during fetch; empty-state copy if user has no other apps
68
+ *
69
+ * Accessibility: opens on click; closes on Esc; closes on outside-click;
70
+ * focus returns to trigger on close. Items are role="menuitem".
71
+ */
72
+ declare function AppsDropdown({ endpoint }?: AppsDropdownProps): react_jsx_runtime.JSX.Element;
73
+
74
+ interface XzibitMarkProps {
75
+ /** Size in pixels (square). Default 28 to fit inside the 44px TopBar. */
76
+ size?: number;
77
+ /** Optional className for additional styling. */
78
+ className?: string;
79
+ /** If provided, sets the accessible name. Omit for decorative usage (defaults to aria-hidden). */
80
+ ariaLabel?: string;
81
+ }
82
+ /**
83
+ * Xzibit X brand mark, rendered as inline SVG for crisp display at any pixel density.
84
+ *
85
+ * Native black background — the "stamp" treatment that gives the mark visual weight
86
+ * against the var(--xz-charcoal) TopBar background. The mark is geometric / angular,
87
+ * approximating the architectural character of the brand asset.
88
+ *
89
+ * Default usage is decorative (aria-hidden). Provide `ariaLabel` for cases where
90
+ * the mark is the sole semantic content of an interactive element.
91
+ */
92
+ declare function XzibitMark({ size, className, ariaLabel }: XzibitMarkProps): react_jsx_runtime.JSX.Element;
93
+
94
+ /**
95
+ * Shared types for @xzibit/ui.
96
+ */
97
+ /**
98
+ * An app in the Xzibit Apps portfolio.
99
+ *
100
+ * Shape matches the response from each app's `/api/me/apps` endpoint,
101
+ * which queries `public.apps` JOIN `public.role_app_permissions` and returns
102
+ * the apps the authenticated user has access to per their role.
103
+ */
104
+ interface App {
105
+ /** Display name, e.g. "Capacity Planner". */
106
+ name: string;
107
+ /** Full URL including protocol, e.g. "https://xzibit-capacity-planner.vercel.app". */
108
+ url: string;
109
+ /** Optional one-line description rendered as secondary text in the dropdown. */
110
+ description?: string;
111
+ /** Section grouping label, e.g. "Strategic", "Calculators". Null = no section. */
112
+ section?: string | null;
113
+ /** Sort order for the section itself (matches launcher curation). */
114
+ section_order?: number;
115
+ }
116
+ /**
117
+ * Response shape from `/api/me/apps`.
118
+ *
119
+ * Per CODING-STANDARDS §6.4 — successful responses are wrapped in a `data`
120
+ * or domain-specific key (in this case `apps`).
121
+ */
122
+ interface AppsResponse {
123
+ apps?: App[];
124
+ error?: string;
125
+ }
126
+
127
+ interface UseAppsResult {
128
+ apps: App[];
129
+ loading: boolean;
130
+ error: string | null;
131
+ refetch: () => Promise<void>;
132
+ }
133
+ interface UseAppsOptions {
134
+ /** Override the endpoint URL. Default: '/api/me/apps' (same-origin per-app endpoint). */
135
+ endpoint?: string;
136
+ /** If true, defer the initial fetch until refetch() is called explicitly. Default false (fetch on mount). */
137
+ lazy?: boolean;
138
+ }
139
+ /**
140
+ * Fetch the list of Xzibit Apps the current user has access to.
141
+ *
142
+ * Each consuming app exposes its own `/api/me/apps` endpoint (same-origin)
143
+ * that queries shared `public.apps` + `public.role_app_permissions` and
144
+ * returns the filtered list. This hook wraps the fetch with loading + error
145
+ * state and an exposed `refetch()`.
146
+ *
147
+ * Per DESIGN-STANDARD v2.3 §Top Bar — the dropdown MUST be dynamic for
148
+ * production (no hardcoded lists). Adding a new portfolio app then becomes
149
+ * one INSERT in `public.apps` + role grants — zero per-app code changes.
150
+ */
151
+ declare function useApps(options?: UseAppsOptions): UseAppsResult;
152
+
153
+ export { type App, AppsDropdown, type AppsDropdownProps, type AppsResponse, BackToLauncher, type BackToLauncherProps, TopBar, type TopBarProps, type UseAppsOptions, type UseAppsResult, XzibitMark, type XzibitMarkProps, useApps };