@cboxdk/cbox-ui 0.6.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.
@@ -0,0 +1,1383 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode, ComponentProps, ComponentType } from 'react';
3
+ import { LucideIcon } from 'lucide-react';
4
+ import { ClassValue } from 'clsx';
5
+
6
+ /**
7
+ * Identity contract every Cbox-family app must populate.
8
+ *
9
+ * Shape mirrors what the future `@cboxdk/cbox-id-client` SDK returns
10
+ * from id's `/api/v1/me` endpoint plus a small set of platform-level
11
+ * URLs that each app needs to construct deep-links into id.
12
+ *
13
+ * In the id app itself, populate this from Inertia shared props
14
+ * (id IS the source of truth — no API call needed).
15
+ *
16
+ * In cortex / atlas / track / future apps, populate from the
17
+ * id-client SDK — typically once per session, then refreshed when
18
+ * the user switches org or updates their profile.
19
+ *
20
+ * Components in this package read from the context but never write —
21
+ * mutations always round-trip through id's API via the id-client.
22
+ */
23
+ type CboxAppKey = string;
24
+ type CboxUser = {
25
+ id: number;
26
+ name: string;
27
+ email: string;
28
+ avatar_url?: string | null;
29
+ /** ISO 639-1 (e.g. "en", "da"). Used by formatters that want a locale. */
30
+ locale?: string | null;
31
+ /** IANA tz identifier (e.g. "Europe/Copenhagen"). */
32
+ timezone?: string | null;
33
+ };
34
+ type CboxOrganization = {
35
+ id: number;
36
+ slug: string;
37
+ name: string;
38
+ /**
39
+ * Role of the current user within this org as resolved by id.
40
+ * Apps should honour this for permission gating in the chrome
41
+ * (eg "Manage team" link is hidden for members).
42
+ */
43
+ role?: 'owner' | 'admin' | 'member' | string;
44
+ };
45
+ type CboxAppLink = {
46
+ /** Stable key — e.g. "id", "cortex", "atlas", "track". */
47
+ key: CboxAppKey;
48
+ /** Display name as shown in the launcher. */
49
+ name: string;
50
+ /** Absolute URL the launcher tile links to. */
51
+ url: string;
52
+ /**
53
+ * Short tagline shown under the name. Keep to ~5–8 words —
54
+ * the launcher truncates after one line.
55
+ */
56
+ description?: string;
57
+ /**
58
+ * One-letter monogram fallback when no icon is provided.
59
+ * Defaults to the first letter of `name`.
60
+ */
61
+ monogram?: string;
62
+ /** Optional inline SVG path or URL for the launcher tile icon. */
63
+ iconUrl?: string;
64
+ /**
65
+ * Accent colour for the monogram tile (and current-app highlight).
66
+ * Hex string or any CSS colour the browser accepts. Falls back to
67
+ * the neutral chip when missing.
68
+ */
69
+ accentColor?: string;
70
+ /** Tag the user's currently-active app so the launcher highlights it. */
71
+ current?: boolean;
72
+ /**
73
+ * Whether the current org has access to this app. Defaults to
74
+ * `true` so existing payloads render unchanged. `false` renders
75
+ * the tile as locked: greyed out, no nav, and (if the host app
76
+ * passes `onLockedAppClick`) opens an upgrade affordance.
77
+ */
78
+ entitled?: boolean;
79
+ /**
80
+ * Mark the app as not-yet-released. Renders as a disabled tile
81
+ * with a "soon" badge — distinct from `entitled: false` which
82
+ * implies "available, but not in your plan".
83
+ */
84
+ comingSoon?: boolean;
85
+ };
86
+ type CboxNotificationPreviewItem = {
87
+ id: string;
88
+ title: string;
89
+ body?: string | null;
90
+ severity: 'info' | 'warning' | 'critical';
91
+ /** ISO-8601 timestamp from the notifications service. */
92
+ createdAt: string;
93
+ /** Null when the user hasn't opened the notification yet. */
94
+ readAt?: string | null;
95
+ /** Optional deep-link — typically into the app that originated
96
+ * the notification (id/billing/invoice/123, webhooks/deliveries/x).
97
+ * Null when the notification is informational with no follow-up. */
98
+ ctaUrl?: string | null;
99
+ ctaLabel?: string | null;
100
+ };
101
+ type CboxNotificationsState = {
102
+ unreadCount: number;
103
+ /**
104
+ * "See all" target — typically the central notifications service
105
+ * inbox (notifications.cbox.systems/inbox). The bell's POPOVER
106
+ * shows the preview items inline; only this URL navigates the
107
+ * user out of the current app.
108
+ */
109
+ inboxUrl: string;
110
+ /**
111
+ * Last 5 items (mixed read/unread) for the bell-popover preview.
112
+ * Empty when the host hasn't fetched a preview yet — the bell
113
+ * still renders the badge from `unreadCount`. Falsy on
114
+ * pre-auth pages where the chrome shouldn't render the bell at all.
115
+ */
116
+ previewItems?: CboxNotificationPreviewItem[];
117
+ /**
118
+ * Endpoint pattern for marking a single notification read. The
119
+ * popover POSTs to `markReadUrl.replace('{id}', notification.id)`
120
+ * — a host-local route that forwards to the service via M2M.
121
+ */
122
+ markReadUrl?: string;
123
+ };
124
+ type CboxIdContextValue = {
125
+ user: CboxUser;
126
+ /**
127
+ * Org the current app context is scoped to. Null on user-only
128
+ * surfaces (eg the id account-settings landing page).
129
+ */
130
+ currentOrganization: CboxOrganization | null;
131
+ /**
132
+ * All orgs the user belongs to — feeds the org switcher.
133
+ * Should be small (≤ a few dozen). For users in many orgs,
134
+ * the switcher swaps in a search field.
135
+ */
136
+ organizations: CboxOrganization[];
137
+ /**
138
+ * Sister apps in the Cbox family the user can launch into.
139
+ * Static-ish — doesn't need to refresh per request.
140
+ */
141
+ apps: CboxAppLink[];
142
+ /**
143
+ * Deep-links into id used by chrome controls. Each app
144
+ * constructs these via the id-client SDK; the package
145
+ * doesn't hardcode URLs because id may live on different
146
+ * hosts in dev / staging / prod.
147
+ */
148
+ urls: {
149
+ /** Base of the id app — for the "manage account" link. */
150
+ idBaseUrl: string;
151
+ /** Absolute URL of the user's id profile page. */
152
+ accountUrl: string;
153
+ /** Absolute URL of the current org's settings page in id. */
154
+ orgSettingsUrl: string | null;
155
+ /** POST endpoint that signs the user out. */
156
+ signOutUrl: string;
157
+ /**
158
+ * POST endpoint in id that switches the user's active org.
159
+ * Used by OrgSwitcher as a fallback when `onSwitchOrganization`
160
+ * is not provided — covers apps that can't do a pure URL swap.
161
+ */
162
+ switchOrgUrl?: string;
163
+ };
164
+ /**
165
+ * Notification feed state, or null/undefined when the user has no
166
+ * inbox configured. id's wire shape sets `null` explicitly; the
167
+ * type accepts both shapes so apps can pass the payload straight
168
+ * through without a null→undefined adapter.
169
+ */
170
+ notifications?: CboxNotificationsState | null;
171
+ /**
172
+ * True when the host app's cached payload is known to be out of
173
+ * date — id has emitted a domain event (membership/role/sub
174
+ * change) since the cache was populated. Apps should refetch
175
+ * via the SDK before rendering anything that depends on the
176
+ * cached membership / entitlement state. Soft-TTL covers the
177
+ * case where the event was missed.
178
+ *
179
+ * Default: false (treated as fresh).
180
+ */
181
+ stale?: boolean;
182
+ /**
183
+ * Called when the user picks a different org from the switcher.
184
+ * Apps own this — typically a same-app URL navigation that
185
+ * swaps the slug in the current path (`/org/{slug}/...`). The
186
+ * server validates membership at the destination via
187
+ * EnsureOrganizationMembership, so this is a pure URL change with
188
+ * no mutable state to update.
189
+ */
190
+ onSwitchOrganization?: (org: CboxOrganization) => void;
191
+ };
192
+ declare function CboxIdProvider({ value, children, }: {
193
+ value: CboxIdContextValue;
194
+ children: ReactNode;
195
+ }): react_jsx_runtime.JSX.Element;
196
+ declare function useCboxId(): CboxIdContextValue;
197
+ /**
198
+ * Soft variant — returns null instead of throwing. Useful for
199
+ * components that gracefully degrade on auth-pre pages.
200
+ */
201
+ declare function useCboxIdOptional(): CboxIdContextValue | null;
202
+
203
+ type Props$7 = ComponentProps<'main'> & {
204
+ children: ReactNode;
205
+ };
206
+ /**
207
+ * Scrolling main column inside an <AppShell>. Wears the soft blue-
208
+ * tinted gradient canvas so every Cbox app sits on the same
209
+ * background.
210
+ */
211
+ declare function AppContent({ children, className, ...props }: Props$7): react_jsx_runtime.JSX.Element;
212
+
213
+ /**
214
+ * Top-level chrome. Hosts the desktop sidebar (left) and the
215
+ * scrollable content column (right) side-by-side. Fits the full
216
+ * viewport height; only the content column scrolls.
217
+ *
218
+ * Pair with <AppContent> inside. Per-app sidebars compose alongside
219
+ * — cbox-ui doesn't ship one because the nav items are app-specific.
220
+ */
221
+ declare function AppShell({ children }: {
222
+ children: ReactNode;
223
+ }): react_jsx_runtime.JSX.Element;
224
+
225
+ type CboxNavItem = {
226
+ label: string;
227
+ /**
228
+ * Absolute path the host's router can navigate to. Apps that
229
+ * scope nav by org slug should compose the path themselves
230
+ * (`/org/${slug}/endpoints`) before passing it in — cbox-ui
231
+ * stays unaware of routing conventions.
232
+ */
233
+ href: string;
234
+ icon: ComponentType<{
235
+ className?: string;
236
+ strokeWidth?: number;
237
+ }>;
238
+ /** Highlight the row for child routes too (`/endpoints/123`
239
+ * matches the `Endpoints` row when this is true). */
240
+ matchPrefix?: boolean;
241
+ };
242
+ type CboxNavSection = {
243
+ label: string;
244
+ items: CboxNavItem[];
245
+ };
246
+ /**
247
+ * Generic Link contract — apps inject their own (Inertia's `Link`,
248
+ * Wouter, React Router, plain `<a>`). cbox-ui doesn't depend on
249
+ * any router so the same chrome works everywhere.
250
+ */
251
+ type LinkComponent = ComponentType<{
252
+ href: string;
253
+ prefetch?: boolean;
254
+ children: ReactNode;
255
+ className?: string;
256
+ title?: string;
257
+ 'aria-label'?: string;
258
+ }>;
259
+ type CboxAppSidebarProps = {
260
+ /** App key for the brand submark (and aria labels). */
261
+ appKey: string;
262
+ /** Optional override for the submark — defaults to `appKey`. */
263
+ brandSubmark?: string | null;
264
+ /** Where brand assets live. Default `/brand`. */
265
+ assetBase?: string;
266
+ /** Where the brand row links to (typically the app's home). */
267
+ brandHref?: string;
268
+ /** Nav sections — top to bottom. */
269
+ sections: CboxNavSection[];
270
+ /** Active path — used to highlight the current row. */
271
+ currentPath: string;
272
+ /** Apps inject their router's Link primitive. */
273
+ LinkComponent: LinkComponent;
274
+ /** Persisted "is the desktop sidebar collapsed?" state. */
275
+ collapsed?: boolean;
276
+ className?: string;
277
+ };
278
+ /**
279
+ * Standard Cbox sidebar. Every app wears this — the only variable
280
+ * bits are `sections` (per-app nav) and `appKey` (drives the brand
281
+ * submark).
282
+ *
283
+ * Hidden below `lg` — apps render a separate mobile drawer that
284
+ * shares the same `sections` config.
285
+ *
286
+ * No app should ship its own sidebar component. The chrome is the
287
+ * shape; nav items are the data.
288
+ */
289
+ declare function CboxAppSidebar({ appKey, brandSubmark, assetBase, brandHref, sections, currentPath, LinkComponent, collapsed, className, }: CboxAppSidebarProps): react_jsx_runtime.JSX.Element;
290
+
291
+ type CboxAppShellProps = {
292
+ /** App key — drives the brand submark + aria labels. */
293
+ appKey: string;
294
+ /** Override the submark text (defaults to appKey). */
295
+ brandSubmark?: string | null;
296
+ /** Asset base for brand pngs. Default `/brand`. */
297
+ assetBase?: string;
298
+ /** Brand row href (typically the app's home). */
299
+ brandHref?: string;
300
+ /** Sidebar nav config. */
301
+ sections: CboxNavSection[];
302
+ /** Active path — used by both sidebar (highlight) and topbar
303
+ * (sidebar-collapse toggle aria state). */
304
+ currentPath: string;
305
+ /** Apps inject their router's Link primitive. */
306
+ LinkComponent: LinkComponent;
307
+ /** Persisted "sidebar collapsed?" state — owned by the host
308
+ * (the cookie hook lives there). */
309
+ sidebarCollapsed?: boolean;
310
+ /** Click handler for the topbar collapse toggle. Hide the
311
+ * toggle entirely by omitting this prop. */
312
+ onToggleSidebar?: () => void;
313
+ /**
314
+ * Topbar centre slot — typically the org switcher / context
315
+ * bar. Apps that aren't org-scoped (notifications, account
316
+ * pages) leave this off.
317
+ */
318
+ topbarCentre?: ReactNode;
319
+ /**
320
+ * Handler to open the in-app command palette / search UI.
321
+ * Always rendered — when omitted the trigger is a no-op so the
322
+ * chrome stays visually identical across all apps regardless of
323
+ * whether a palette has been wired up yet.
324
+ */
325
+ onSearchOpen?: () => void;
326
+ /**
327
+ * Override the right cluster on the topbar. Default:
328
+ * `[<SearchTrigger>?] <AppLauncher /> <NotificationsBell /> <UserMenu />`.
329
+ * Override only when an app needs a completely different mix.
330
+ */
331
+ topbarRightCluster?: ReactNode;
332
+ children: ReactNode;
333
+ };
334
+ /**
335
+ * Top-level Cbox application shell. Composes sidebar + topbar +
336
+ * content into the single chrome shape every Cbox app wears.
337
+ *
338
+ * Apps reduce to ~30 lines of layout: define nav, render
339
+ * `<CboxAppShell>{children}</CboxAppShell>`. No app should
340
+ * compose `<AppShell>`/`<AppContent>`/sidebar/topbar manually —
341
+ * drift is the whole reason this component exists.
342
+ */
343
+ declare function CboxAppShell({ appKey, brandSubmark, assetBase, brandHref, sections, currentPath, LinkComponent, sidebarCollapsed, onToggleSidebar, topbarCentre, onSearchOpen, topbarRightCluster, children, }: CboxAppShellProps): react_jsx_runtime.JSX.Element;
344
+
345
+ type CboxMobileNavDrawerProps = {
346
+ open: boolean;
347
+ onClose: () => void;
348
+ appKey: string;
349
+ brandSubmark?: string | null;
350
+ assetBase?: string;
351
+ brandHref?: string;
352
+ sections: CboxNavSection[];
353
+ currentPath: string;
354
+ LinkComponent: LinkComponent;
355
+ };
356
+ /**
357
+ * Mobile-only slide-in sidebar. Hidden on `lg` and up — there the
358
+ * desktop `CboxAppSidebar` is always visible. Shares the same
359
+ * `sections` shape as the desktop sidebar so apps don't have to
360
+ * maintain two nav configs.
361
+ *
362
+ * State (open/close) lives in `CboxAppShell` so it can wire the
363
+ * hamburger button + auto-close on `currentPath` change. The drawer
364
+ * itself just renders; it doesn't track anything.
365
+ *
366
+ * Closes on: Escape, scrim click, navigation (parent unsets `open`
367
+ * when `currentPath` changes). Locks body scroll while open so the
368
+ * page underneath doesn't scroll on touch.
369
+ */
370
+ declare function CboxMobileNavDrawer({ open, onClose, appKey, brandSubmark, assetBase, brandHref, sections, currentPath, LinkComponent, }: CboxMobileNavDrawerProps): react_jsx_runtime.JSX.Element;
371
+
372
+ type CboxBrandProps = {
373
+ /**
374
+ * App key — drives the small mono submark beside the wordmark.
375
+ * Convention: lowercase single word matching the `cbox_apps.key`
376
+ * row in id (`notifications`, `webhooks`, `id`, `cortex`, …).
377
+ */
378
+ appKey: string;
379
+ /**
380
+ * Override the submark text. Defaults to `appKey`. Hide it
381
+ * entirely with `submark={null}`.
382
+ */
383
+ submark?: string | null;
384
+ /** Asset base path. Default `/brand` — apps drop the standard
385
+ * png set (`cbox-icon.png`, `cbox-logo.png`, plus `@2x` retina)
386
+ * into `public/brand/` and they Just Work. */
387
+ assetBase?: string;
388
+ /** Tone of the wordmark. `dark` on light surfaces (default),
389
+ * `light` on dark / coloured surfaces. */
390
+ tone?: 'dark' | 'light';
391
+ /** Collapsed mode renders only the square icon — used in the
392
+ * collapsed-sidebar state. */
393
+ collapsed?: boolean;
394
+ className?: string;
395
+ };
396
+ /**
397
+ * Canonical Cbox brand mark. Every app wears this — the only
398
+ * variable bit is the `appKey` submark beside the wordmark
399
+ * ("notifications", "webhooks", …).
400
+ *
401
+ * No app should ship its own brand component. If a product needs
402
+ * a materially different mark, that's a cbox-ui PR (extend this
403
+ * component), not a per-app fork.
404
+ */
405
+ declare function CboxBrand({ appKey, submark, assetBase, tone, collapsed, className, }: CboxBrandProps): react_jsx_runtime.JSX.Element;
406
+
407
+ type CboxOrgContextBarProps = {
408
+ /**
409
+ * Apps inject their router's Link primitive — used for the
410
+ * crumb's brand-side anchor. The dropdown's onSelect calls
411
+ * `onNavigate(href)` so apps can either `router.visit(href)`
412
+ * (Inertia) or `<Link>`-style navigate.
413
+ */
414
+ LinkComponent: LinkComponent;
415
+ /**
416
+ * Called when the user picks a different org from the dropdown.
417
+ * Receives an absolute path with the slug substituted in. Apps
418
+ * trigger their router.
419
+ *
420
+ * Path-rewrite rule (Cbox suite convention):
421
+ * /org/<old-slug>/foo/bar → /org/<new-slug>/foo/bar
422
+ * /something-else → /org/<new-slug>
423
+ *
424
+ * Apps that aren't org-scoped (notifications) typically don't
425
+ * render this component at all — but if they do, switching
426
+ * lands on the new org's `/org/<slug>` root and lets the host
427
+ * sort the rest out.
428
+ */
429
+ onNavigate: (href: string) => void;
430
+ };
431
+ /**
432
+ * Standard Cbox topbar org switcher. One crumb, one dropdown,
433
+ * URL-driven (no POST, no session pin). Mirrors id's pattern 1:1
434
+ * so the chrome reads identically across the suite — that's the
435
+ * whole reason it's here, not in each app.
436
+ *
437
+ * Apps render this in the `topbarCentre` slot of `CboxAppShell`.
438
+ * Hides itself when there's no current org (eg pre-auth, or
439
+ * non-org-scoped surfaces).
440
+ */
441
+ declare function CboxOrgContextBar({ LinkComponent, onNavigate, }: CboxOrgContextBarProps): react_jsx_runtime.JSX.Element | null;
442
+
443
+ type Props$6 = {
444
+ /**
445
+ * App branding rendered on the left — typically a small logo +
446
+ * the app name. Rendered inside a fixed-height bar so keep it
447
+ * visually compact (24-32px tall content).
448
+ */
449
+ brand: ReactNode;
450
+ /** Anything between brand and the right cluster — search, breadcrumbs, etc. */
451
+ centre?: ReactNode;
452
+ /**
453
+ * Override slots if an app needs a different control mix in the
454
+ * right cluster. Default: AppLauncher → NotificationsBell → UserMenu.
455
+ */
456
+ rightCluster?: ReactNode;
457
+ /**
458
+ * Whether to show the org switcher next to the brand. Hide on
459
+ * non-org-scoped surfaces (eg id account-settings).
460
+ */
461
+ showOrgSwitcher?: boolean;
462
+ className?: string;
463
+ };
464
+ /**
465
+ * Standard Cbox top bar. Same shell across every app — keeps cross-app
466
+ * navigation (launcher, account, notifications) muscle-memory-stable.
467
+ */
468
+ declare function TopBar({ brand, centre, rightCluster, showOrgSwitcher, className, }: Props$6): react_jsx_runtime.JSX.Element;
469
+
470
+ type AppLauncherProps = {
471
+ className?: string;
472
+ /**
473
+ * Called when the user clicks an app they're not entitled to. Use
474
+ * this to open an upgrade flow / Stripe checkout / Intercom message.
475
+ * If unset, locked tiles fall through to a static "Add to plan"
476
+ * affordance — the click is a no-op so we never silently ship the
477
+ * user to a broken paywall.
478
+ */
479
+ onLockedAppClick?: (app: CboxAppLink) => void;
480
+ };
481
+ /**
482
+ * Cross-app launcher — the 9-dot button in the top bar.
483
+ *
484
+ * Renders the Cbox suite as a vertical list with status per app:
485
+ * - entitled apps → full-colour tile, click navigates with ?org=<slug>
486
+ * - locked apps → greyed out + lock badge, opens upgrade flow if the
487
+ * host app provides one (otherwise inert click)
488
+ * - coming-soon → greyed out + "soon" badge, never clickable
489
+ * - the current app → highlighted ring on its monogram tile
490
+ *
491
+ * Row layout (vs. the old 3-col grid) gives us room for descriptions
492
+ * and status badges, which makes the entitlement signal readable at a
493
+ * glance instead of needing a tooltip.
494
+ */
495
+ declare function AppLauncher({ className, onLockedAppClick }: AppLauncherProps): react_jsx_runtime.JSX.Element | null;
496
+
497
+ type AppSwitcherProps = {
498
+ className?: string;
499
+ /**
500
+ * Called when the user clicks an app they're not entitled to. Use
501
+ * this to open an upgrade flow / Stripe checkout / Intercom message.
502
+ * If unset, locked rows fall through to a static "Add to plan"
503
+ * affordance — the click is a no-op so we never silently ship the
504
+ * user to a broken paywall.
505
+ */
506
+ onLockedAppClick?: (app: CboxAppLink) => void;
507
+ };
508
+ /**
509
+ * Left-cluster app picker — the dropdown button that sits beside OrgSwitcher.
510
+ *
511
+ * Shows the current app's monogram + name as the trigger so both context
512
+ * dimensions (which app, which org) are immediately visible in the left
513
+ * cluster without needing to open anything.
514
+ *
515
+ * The dropdown preserves the full AppLauncher row richness: monogram tile
516
+ * with per-app accent, name, description, and entitlement badges. Entitled
517
+ * apps are grouped first; locked / coming-soon land in an "Available" section.
518
+ *
519
+ * Includes typeahead — auto-focus on open, filters name/description/key as
520
+ * the user types, arrow-key navigation, Enter to activate, Escape to close.
521
+ *
522
+ * Soft-reads via useCboxIdOptional — renders null when no provider is
523
+ * mounted above (admin shells, pre-auth pages).
524
+ */
525
+ declare function AppSwitcher({ className, onLockedAppClick }: AppSwitcherProps): react_jsx_runtime.JSX.Element | null;
526
+
527
+ /**
528
+ * Bell with unread-count badge + popover preview. Click opens a
529
+ * dropdown with the last 5 items inline (no navigation), each with
530
+ * an inline mark-read action and an optional deep-link to the page
531
+ * that originated the notification. "See all" at the bottom links
532
+ * to the central notifications service inbox — the only navigation
533
+ * away from the host app.
534
+ *
535
+ * Pattern matches Linear / GitHub / Stripe: glance + mark-read
536
+ * without leaving context; cross-app jump only when the user
537
+ * explicitly wants the full list.
538
+ *
539
+ * Renders nothing if the context is missing or notifications
540
+ * aren't configured (eg pre-auth pages).
541
+ */
542
+ declare function NotificationsBell({ className }: {
543
+ className?: string;
544
+ }): react_jsx_runtime.JSX.Element | null;
545
+
546
+ type Props$5 = {
547
+ /**
548
+ * Optional renderer override for the trigger so apps can match
549
+ * their topbar style (filled button vs ghost vs icon-only).
550
+ */
551
+ Trigger?: ComponentType<{
552
+ organization: CboxOrganization | null;
553
+ onClick: () => void;
554
+ open: boolean;
555
+ }>;
556
+ className?: string;
557
+ };
558
+ /**
559
+ * Dropdown that surfaces the user's orgs and dispatches the picked
560
+ * one through `onSwitchOrganization`. Reads everything else from
561
+ * CboxIdProvider so the same component works in id, cortex, atlas —
562
+ * no per-app data wiring.
563
+ *
564
+ * Includes typeahead — auto-focus on open, filters by name as the
565
+ * user types, arrow-key navigation, Enter to switch, Escape to close.
566
+ */
567
+ declare function OrgSwitcher({ Trigger, className }: Props$5): react_jsx_runtime.JSX.Element;
568
+
569
+ type Props$4 = {
570
+ /** Click handler — typically dispatches the app's command-palette
571
+ * open shortcut. Apps that don't have a palette yet pass a no-op
572
+ * so the chrome stays visually consistent across the suite. */
573
+ onOpen?: () => void;
574
+ className?: string;
575
+ label?: string;
576
+ };
577
+ /**
578
+ * Topbar Search trigger — a visual-only command-palette button.
579
+ *
580
+ * Hosted in cbox-ui because every Cbox app's chrome must show the
581
+ * same search affordance in the same spot, with the same width and
582
+ * the same shortcut hint. Drift across apps means users see layout
583
+ * shifts when tabbing between id / webhooks / cortex; that's a UX
584
+ * tax we don't want to charge them.
585
+ *
586
+ * The actual palette stays per-app (commands differ); this primitive
587
+ * just renders the trigger and dispatches `onOpen`.
588
+ */
589
+ declare function SearchTrigger({ onOpen, className, label }: Props$4): react_jsx_runtime.JSX.Element;
590
+
591
+ type Props$3 = {
592
+ /**
593
+ * App-specific menu items rendered above the standard
594
+ * "Manage account" / "Sign out" set. Use sparingly — the goal
595
+ * is consistency across apps.
596
+ */
597
+ extras?: ReactNode;
598
+ className?: string;
599
+ };
600
+ /**
601
+ * User-menu dropdown. Always exposes:
602
+ * - Manage account ↗ (deep-link into id)
603
+ * - Team & billing ↗ (deep-link into id, current org scope)
604
+ * - Sign out
605
+ *
606
+ * The arrows signal cross-app navigation — id is canonical.
607
+ */
608
+ declare function UserMenu({ extras, className }: Props$3): react_jsx_runtime.JSX.Element;
609
+
610
+ /**
611
+ * Combobox — the canonical select replacement for the Cbox suite.
612
+ *
613
+ * **Native `<select>` is forbidden.** It can't host avatars, can't
614
+ * be searched, can't be styled into the design system, and on mobile
615
+ * it pops the OS picker which breaks the visual contract. Combobox
616
+ * solves all of that with a single primitive:
617
+ *
618
+ * - Type-ahead filter on the search input (case-insensitive,
619
+ * matches label + secondary).
620
+ * - Keyboard contract: ArrowDown / ArrowUp move highlight, Enter
621
+ * selects, Escape closes, Home/End jump to bounds, Tab exits.
622
+ * - Optional avatar per option, optional secondary line.
623
+ * - Optional grouped sections with mono-tracked headers.
624
+ * - Optional footer link (eg "+ New environment").
625
+ * - Empty-state copy when the filter excludes everything.
626
+ *
627
+ * Two modes:
628
+ * - Value mode (default): `value` + `onChange` — Combobox manages
629
+ * selection state, host treats it like a controlled input.
630
+ * - Navigation mode: items carry `href` and `LinkComponent` is
631
+ * supplied — clicking an item is a Link visit, not a state mutation.
632
+ *
633
+ * Both modes are URL/router-friendly. Value mode is for filter
634
+ * controls; navigation mode is for context switchers (the Combobox
635
+ * inside a ContextBar crumb is exactly this).
636
+ */
637
+ type ComboboxOption = {
638
+ /** Stable key. Used as React key and for active-state matching. */
639
+ value: string;
640
+ /** Visible label — primary line. */
641
+ label: string;
642
+ /** Optional secondary line — slug, role, environment, hint. */
643
+ secondary?: string;
644
+ /** Optional leading icon (lucide-style — h-3.5 w-3.5). */
645
+ icon?: ComponentType<{
646
+ className?: string;
647
+ strokeWidth?: number;
648
+ }>;
649
+ /**
650
+ * Optional avatar — image src OR `{initials,accent}` chip. When
651
+ * the option carries an avatar, the trigger renders one too if
652
+ * the option is the active selection.
653
+ */
654
+ avatar?: ComboboxAvatar;
655
+ /** Disable selection of this option. */
656
+ disabled?: boolean;
657
+ /**
658
+ * Hidden text added to the searchable haystack. Useful for
659
+ * synonyms ("United States" should match "USA", "us"). Not
660
+ * displayed.
661
+ */
662
+ keywords?: string[];
663
+ /**
664
+ * Optional href for navigation mode. When set + LinkComponent is
665
+ * supplied, clicking the option navigates instead of mutating
666
+ * value state.
667
+ */
668
+ href?: string;
669
+ };
670
+ type ComboboxGroup = {
671
+ /** Group heading — uppercase mono-tracked. Empty string hides the heading. */
672
+ label: string;
673
+ options: ComboboxOption[];
674
+ };
675
+ type ComboboxAvatar = {
676
+ initials?: string;
677
+ accent?: string;
678
+ src?: string;
679
+ };
680
+ type LinkComp$1 = ComponentType<{
681
+ href: string;
682
+ prefetch?: boolean;
683
+ className?: string;
684
+ children: ReactNode;
685
+ }>;
686
+ type CommonProps = {
687
+ /** Options or grouped options. Pass an array of either shape. */
688
+ items: ComboboxOption[] | ComboboxGroup[];
689
+ /** Placeholder shown in the trigger when no value is selected. */
690
+ placeholder?: string;
691
+ /** Search input placeholder. */
692
+ searchPlaceholder?: string;
693
+ /** Empty-state copy when the filter matches nothing. */
694
+ emptyText?: string;
695
+ /** Optional footer link below the option list ("+ New X"). */
696
+ footerLink?: {
697
+ label: string;
698
+ href: string;
699
+ };
700
+ /** Disable the entire combobox (greyed-out trigger). */
701
+ disabled?: boolean;
702
+ /** Required-field marker on the trigger. */
703
+ required?: boolean;
704
+ /** Aria-label for the trigger. */
705
+ ariaLabel?: string;
706
+ /** Trigger className override. Doesn't change padding/height. */
707
+ triggerClassName?: string;
708
+ /** Dropdown panel className override. */
709
+ panelClassName?: string;
710
+ /** Width policy for the trigger. Defaults to `auto` (intrinsic). */
711
+ triggerWidth?: 'auto' | 'full';
712
+ /**
713
+ * Framework Link adapter for navigation-mode items + footer link.
714
+ * If unset, navigation falls back to plain `<a>`.
715
+ */
716
+ LinkComponent?: LinkComp$1;
717
+ /** ID for the trigger button (associate with a <Label htmlFor>). */
718
+ id?: string;
719
+ /** className for the outer wrapper. */
720
+ className?: string;
721
+ };
722
+ type ValueModeProps = CommonProps & {
723
+ /** Selected value or `null` when nothing is selected. */
724
+ value: string | null;
725
+ /** Called with the option's `value` when the user picks one. */
726
+ onChange: (value: string) => void;
727
+ };
728
+ type NavModeProps = CommonProps & {
729
+ /** Active value for highlight purposes. Optional in nav mode. */
730
+ value?: string | null;
731
+ /**
732
+ * Omitted in nav mode. Items must carry `href`, and a
733
+ * LinkComponent must be supplied for SPA navigation.
734
+ */
735
+ onChange?: never;
736
+ };
737
+ type ComboboxProps = ValueModeProps | NavModeProps;
738
+ declare function Combobox(props: ComboboxProps): react_jsx_runtime.JSX.Element;
739
+
740
+ /**
741
+ * ContextBar — the topbar breadcrumb-style context selector. One
742
+ * crumb per level of context (org / project / environment / branch /
743
+ * deployment). Each crumb renders as a chip (avatar + label +
744
+ * chevron) and opens a dropdown with the items at that level plus
745
+ * an optional "+ New X" footer.
746
+ *
747
+ * The pattern is the cloud-platform default — Vercel, Render, Forge,
748
+ * GitHub, Stripe, Cortex all use it. Topbar carries identity and
749
+ * context; sidebar carries navigation. The two never overlap.
750
+ *
751
+ * URL-driven by default — clicking a dropdown item navigates via the
752
+ * supplied `LinkComponent`. Apps that need a non-navigational select
753
+ * (post a form, mutate session state) pass `onSelect` instead.
754
+ *
755
+ * Separator between crumbs is a muted slash — same as Forge / Cortex.
756
+ */
757
+ type ContextAvatar = {
758
+ /** 1–2 character monogram. Defaults to first letter of the label. */
759
+ initials?: string;
760
+ /**
761
+ * Hex / CSS colour applied as the avatar's bg. Falls back to the
762
+ * neutral chip when missing. Per-item override of the default.
763
+ */
764
+ accent?: string;
765
+ /** Optional avatar image URL — overrides initials when set. */
766
+ src?: string;
767
+ };
768
+ type ContextItem = {
769
+ /** Stable identity for the item (slug, id). */
770
+ id: string;
771
+ /** Primary display label. */
772
+ label: string;
773
+ /** Optional secondary line — slug, role, env name. Mono-tracked. */
774
+ secondary?: string;
775
+ /** Avatar specification. Same shape as the crumb's own avatar. */
776
+ avatar?: ContextAvatar;
777
+ /** Navigation target — clicking the item navigates here. */
778
+ href?: string;
779
+ /** Mark the item as the active selection (gets a check on the right). */
780
+ current?: boolean;
781
+ /**
782
+ * Optional href to a settings page for this item. When set, a
783
+ * cogwheel button appears on the right of the row on hover/focus
784
+ * — clicking it navigates straight to settings without going
785
+ * through the item's normal selection. Cortex-style shortcut.
786
+ */
787
+ settingsHref?: string;
788
+ /**
789
+ * Aria-label for the optional settings cogwheel. Defaults to
790
+ * `Settings for <label>`. Override when the label alone isn't
791
+ * enough context (eg multiple items with the same name).
792
+ */
793
+ settingsAriaLabel?: string;
794
+ };
795
+ type ContextCrumb = {
796
+ /** Stable identity for the crumb (used as React key). */
797
+ id: string;
798
+ /** The chip's visible label — typically the active item's name. */
799
+ label: string;
800
+ /** The chip's avatar — typically the active item's avatar. */
801
+ avatar?: ContextAvatar;
802
+ /** Items shown in the dropdown. Empty array = chip with no menu. */
803
+ items: ContextItem[];
804
+ /**
805
+ * Optional footer link below the items — typically "+ New X" or
806
+ * "Manage Y". Renders with a + icon and an accent text colour.
807
+ */
808
+ footerLink?: {
809
+ label: string;
810
+ href: string;
811
+ };
812
+ /**
813
+ * Override per-item navigation. When set, item clicks call this
814
+ * with the item's id instead of navigating to `item.href`. Useful
815
+ * for posting a form (eg an org switcher that POSTs to a server-side
816
+ * endpoint).
817
+ */
818
+ onSelect?: (itemId: string) => void;
819
+ /** Optional aria-label for the chip's button (defaults to "Switch <label>"). */
820
+ ariaLabel?: string;
821
+ };
822
+ type LinkComp = ComponentType<{
823
+ href: string;
824
+ prefetch?: boolean;
825
+ className?: string;
826
+ children: ReactNode;
827
+ }>;
828
+ type Props$2 = {
829
+ crumbs: ContextCrumb[];
830
+ /**
831
+ * The framework's link component. Inertia consumers pass the
832
+ * Inertia Link; TanStack Router consumers pass their typed Link.
833
+ * Plain `<a>` is the fallback.
834
+ */
835
+ LinkComponent?: LinkComp;
836
+ className?: string;
837
+ };
838
+ declare function ContextBar({ crumbs, LinkComponent, className, }: Props$2): react_jsx_runtime.JSX.Element | null;
839
+ declare function ContextAvatarChip({ avatar, fallbackLabel, size, }: {
840
+ avatar: ContextAvatar;
841
+ fallbackLabel: string;
842
+ size?: 'sm' | 'md';
843
+ }): react_jsx_runtime.JSX.Element;
844
+
845
+ /**
846
+ * Generic panel container — soft border, card surface, subtle shadow.
847
+ * The default building block for every section on a page.
848
+ */
849
+ declare function DataPanel({ className, children, ...rest }: ComponentProps<'section'>): react_jsx_runtime.JSX.Element;
850
+ type SectionHeaderProps = {
851
+ title: ReactNode;
852
+ description?: ReactNode;
853
+ actions?: ReactNode;
854
+ className?: string;
855
+ };
856
+ /**
857
+ * Section header that sits inside a DataPanel. Title in semibold sans,
858
+ * optional muted lead, right-aligned actions slot.
859
+ */
860
+ declare function SectionHeader({ title, description, actions, className, }: SectionHeaderProps): react_jsx_runtime.JSX.Element;
861
+ /**
862
+ * Vertically padded content body for a DataPanel.
863
+ */
864
+ declare function PanelBody({ className, children, ...rest }: ComponentProps<'div'>): react_jsx_runtime.JSX.Element;
865
+
866
+ type EmptyStateProps = {
867
+ icon?: LucideIcon;
868
+ title: ReactNode;
869
+ description?: ReactNode;
870
+ action?: ReactNode;
871
+ className?: string;
872
+ };
873
+ /**
874
+ * Centered empty state with an optional icon block, title, body
875
+ * lede, and a single CTA slot.
876
+ */
877
+ declare function EmptyState({ icon: Icon, title, description, action, className, }: EmptyStateProps): react_jsx_runtime.JSX.Element;
878
+
879
+ declare function Heading({ title, description, variant, }: {
880
+ title: string;
881
+ description?: string;
882
+ variant?: 'default' | 'small';
883
+ }): react_jsx_runtime.JSX.Element;
884
+
885
+ type KeyValueProps = {
886
+ label: string;
887
+ children: ReactNode;
888
+ /** Render the value in monospace. Default true — most values are technical. */
889
+ mono?: boolean;
890
+ className?: string;
891
+ };
892
+ /**
893
+ * Two-column read-only field row. Label in mono caps, value in mono
894
+ * by default (for slugs/IDs/dates) — pass `mono={false}` for prose.
895
+ */
896
+ declare function KeyValue({ label, children, mono, className, }: KeyValueProps): react_jsx_runtime.JSX.Element;
897
+ declare function KeyValueList({ children, className, }: {
898
+ children: ReactNode;
899
+ className?: string;
900
+ }): react_jsx_runtime.JSX.Element;
901
+
902
+ type Props$1 = {
903
+ title: ReactNode;
904
+ /**
905
+ * Tiny uppercase lead-in above the title (sections, breadcrumb-light
906
+ * disambiguation). Shown in mono-tracking caps so it reads as
907
+ * meta, not as a competing heading.
908
+ */
909
+ eyebrow?: ReactNode;
910
+ description?: ReactNode;
911
+ actions?: ReactNode;
912
+ className?: string;
913
+ };
914
+ /**
915
+ * Title block at the top of every page. Big bold title, optional mono
916
+ * eyebrow lead-in, muted lead, optional right-aligned actions slot.
917
+ * Cortex-style — no italics, no brackets — so it carries across many
918
+ * sibling apps cleanly.
919
+ */
920
+ declare function PageHeader({ title, eyebrow, description, actions, className, }: Props$1): react_jsx_runtime.JSX.Element;
921
+
922
+ /**
923
+ * Width policy for `<PageShell>` — the single horizontal-rhythm
924
+ * primitive every Cbox-family app shares. Pick the policy that
925
+ * matches the page's content density:
926
+ *
927
+ * comfortable max-w-6xl (~1152px) — the default. Reading-friendly
928
+ * column for prose-heavy pages: profile, security,
929
+ * billing forms, audit row detail.
930
+ * wide max-w-7xl (~1280px) — for tables / lists / member
931
+ * rosters where horizontal real estate matters more
932
+ * than reading line length. Audit log, member roster,
933
+ * plan comparison.
934
+ * full no max-width, only the standard horizontal padding —
935
+ * for canvas-heavy pages (flow diagrams, dashboards)
936
+ * where every pixel of viewport should be usable. Use
937
+ * sparingly: raw width without column rules feels
938
+ * chaotic on monitors past 1600px.
939
+ * bleed no max-width AND no horizontal padding. Strict edge
940
+ * -to-edge for content that draws its own padding (a
941
+ * flow canvas with its own pan/zoom region, a fullscreen
942
+ * iframe, an embedded preview environment).
943
+ */
944
+ type PageWidth = 'comfortable' | 'wide' | 'full' | 'bleed';
945
+ type Props = {
946
+ children: ReactNode;
947
+ width?: PageWidth;
948
+ className?: string;
949
+ };
950
+ /**
951
+ * The page-level container. Picks one of four width policies (see
952
+ * PageWidth) and applies the canonical Cbox padding / vertical
953
+ * rhythm. Every authenticated page in every Cbox app composes
954
+ * through this — directly via `<PageShell>` or, more usually, via
955
+ * the per-app `<Page>` wrapper that combines PageShell + PageHeader
956
+ * + PageTabs.
957
+ *
958
+ * Width policy is the only knob exposed here. Padding, max-width,
959
+ * gutter, vertical spacing between blocks — those are baked in so
960
+ * the eye never has to track horizontal jitter between sibling
961
+ * pages.
962
+ */
963
+ declare function PageShell({ children, width, className, }: Props): react_jsx_runtime.JSX.Element;
964
+
965
+ /**
966
+ * Section navigation: every "tab" is a real page with its own URL.
967
+ * No internal active-state, no callbacks — the host's router is the
968
+ * source of truth. This is what makes deep links and hard refreshes
969
+ * lossless: paste any URL, the right tab is active and the right
970
+ * panel renders, with no client-side coordination required.
971
+ *
972
+ * Layout:
973
+ * ≥ sm horizontal underline strip + optional `actions` slot to
974
+ * the right; sticky just under the TopBar with a hairline
975
+ * border-bottom and a backdrop-blur for the scroll-into
976
+ * effect.
977
+ * < sm dropdown showing the active tab + chevron; opens a panel
978
+ * with the full list. Actions reflow below the trigger.
979
+ *
980
+ * The same primitive runs the section nav across every Cbox app. It
981
+ * lives here so that the visual contract is single-sourced — there's
982
+ * no way for a feature page to invent its own slightly-different tab
983
+ * strip.
984
+ */
985
+ type PageTabSpec = {
986
+ /** Display label. Truncates inline if it overflows the strip. */
987
+ label: string;
988
+ /** Absolute or app-relative href. The host router renders this. */
989
+ href: string;
990
+ /**
991
+ * Optional leading icon. Constrained to the icon shape we use
992
+ * everywhere — this is how cbox-ui keeps icon weights aligned
993
+ * across primitives without depending on `lucide-react`.
994
+ */
995
+ icon?: ComponentType<{
996
+ className?: string;
997
+ strokeWidth?: number;
998
+ }>;
999
+ /**
1000
+ * Optional badge — a count, status pill, or "new" indicator. Use
1001
+ * sparingly; one badge in a row is signal, three is noise.
1002
+ */
1003
+ badge?: ReactNode;
1004
+ /**
1005
+ * Numeric count rendered inline. Convenience for the common
1006
+ * "Members 12" pattern; mutually exclusive with `badge` (badge
1007
+ * wins if both are set).
1008
+ */
1009
+ count?: number;
1010
+ /**
1011
+ * Match the current path by prefix. Use for hub pages whose
1012
+ * nested routes should still highlight the parent tab — e.g.
1013
+ * `/org/<slug>/members` highlights when on
1014
+ * `/org/<slug>/members/<id>`.
1015
+ */
1016
+ matchPrefix?: boolean;
1017
+ };
1018
+ type PageTabsProps = {
1019
+ items: PageTabSpec[];
1020
+ /** Current pathname, host-supplied. Drives active-state matching. */
1021
+ currentPath: string;
1022
+ /**
1023
+ * The framework's link component. Inertia consumers pass the
1024
+ * Inertia `Link`; TanStack Router consumers pass their typed
1025
+ * `Link`; plain anchors are the fallback. We never invent
1026
+ * navigation here.
1027
+ */
1028
+ LinkComponent?: ComponentType<{
1029
+ href: string;
1030
+ prefetch?: boolean;
1031
+ className?: string;
1032
+ children: ReactNode;
1033
+ 'aria-current'?: 'page' | undefined;
1034
+ }>;
1035
+ /**
1036
+ * Right-aligned slot on desktop, reflowed under the dropdown on
1037
+ * mobile. Use for primary section actions (Invite, Add token,
1038
+ * Export…) that belong to the section as a whole rather than the
1039
+ * specific tab. The slot is always reserved on desktop so pages
1040
+ * with and without actions keep identical row geometry — no
1041
+ * layout shift between sibling tabs.
1042
+ */
1043
+ actions?: ReactNode;
1044
+ className?: string;
1045
+ };
1046
+ declare function PageTabs({ items, currentPath, LinkComponent, actions, className, }: PageTabsProps): react_jsx_runtime.JSX.Element;
1047
+
1048
+ /**
1049
+ * A clickable list row — replaces the "name on left + Manage button on
1050
+ * right" pattern with a row that's clickable as a single Link, with a
1051
+ * visible hover state and a focus ring. Trailing content (badges,
1052
+ * action buttons) sits in the `trailing` slot, which renders alongside
1053
+ * the body but stops click-propagation on any nested interactive
1054
+ * control so action buttons there don't trigger row navigation.
1055
+ *
1056
+ * <RowLink href={editOrg(slug).url} LinkComponent={Link}>
1057
+ * <Avatar />
1058
+ * <div>
1059
+ * <p>Cbox Systems</p>
1060
+ * <p className="text-mono">cbox-systems</p>
1061
+ * </div>
1062
+ * </RowLink>
1063
+ *
1064
+ * With trailing actions:
1065
+ *
1066
+ * <RowLink href={...} trailing={<StatusPill>owner</StatusPill>}>
1067
+ *
1068
+ * Hit target: the entire row. The Link wraps all content directly so
1069
+ * every pixel inside the row is clickable, including the trailing
1070
+ * area's whitespace. Interactive controls inside `trailing` (buttons,
1071
+ * inputs, anchors, role="button") auto-stop the click via the
1072
+ * onClickCapture handler — no per-button stopPropagation boilerplate.
1073
+ */
1074
+ type RowLinkProps = {
1075
+ /** Destination URL — clicking anywhere on the row navigates here. */
1076
+ href: string;
1077
+ /** Row body. Compose freely; common pattern is avatar + text block. */
1078
+ children: ReactNode;
1079
+ /**
1080
+ * Right-aligned slot. Status pills, action buttons, kebab menus.
1081
+ * Interactive controls inside this slot auto-stop the row click
1082
+ * via DOM-traversal (button/a/input/select/textarea/[role=button]
1083
+ * /[data-stop-row-click]). No manual stopPropagation needed.
1084
+ */
1085
+ trailing?: ReactNode;
1086
+ /**
1087
+ * The framework's link component. Inertia consumers pass the
1088
+ * Inertia `Link`; TanStack Router consumers pass their typed
1089
+ * `Link`. Plain `<a>` is the fallback.
1090
+ */
1091
+ LinkComponent?: ComponentType<{
1092
+ href: string;
1093
+ prefetch?: boolean;
1094
+ className?: string;
1095
+ children: ReactNode;
1096
+ }>;
1097
+ /** Highlight as the active row (the user's current selection). */
1098
+ active?: boolean;
1099
+ /** Additional classes for the row container. */
1100
+ className?: string;
1101
+ };
1102
+ declare function RowLink({ href, children, trailing, LinkComponent, active, className, }: RowLinkProps): react_jsx_runtime.JSX.Element;
1103
+
1104
+ type Tone$1 = 'info' | 'success' | 'warning' | 'destructive' | 'neutral';
1105
+ type StatTileProps = {
1106
+ label: string;
1107
+ value: ReactNode;
1108
+ /** Small caption under the value. */
1109
+ suffix?: ReactNode;
1110
+ icon: LucideIcon;
1111
+ tone?: Tone$1;
1112
+ className?: string;
1113
+ };
1114
+ /**
1115
+ * Cortex-style metric tile. Coloured square icon block on the left,
1116
+ * label + value on the right, optional caption below the value.
1117
+ */
1118
+ declare function StatTile({ label, value, suffix, icon: Icon, tone, className, }: StatTileProps): react_jsx_runtime.JSX.Element;
1119
+
1120
+ type Tone = 'neutral' | 'info' | 'success' | 'warning' | 'destructive' | 'muted';
1121
+ type StatusPillProps = {
1122
+ children: React.ReactNode;
1123
+ tone?: Tone;
1124
+ className?: string;
1125
+ /** Show a small dot before the label for "live status" feel. */
1126
+ dot?: boolean;
1127
+ };
1128
+ /**
1129
+ * Compact pill for plan tier, role, status, etc. Rounded-full like
1130
+ * Cortex's "Running"/"Production" tags. Sized for tabular density,
1131
+ * not editorial impact.
1132
+ */
1133
+ declare function StatusPill({ children, tone, className, dot, }: StatusPillProps): react_jsx_runtime.JSX.Element;
1134
+
1135
+ /**
1136
+ * Cross-app surface contract — the API hosts opt into and that
1137
+ * cbox-ui's runtime renders. Each `SurfaceId` corresponds to a
1138
+ * concrete surface implementation shipped by cbox-ui.
1139
+ *
1140
+ * Surfaces are intentionally a closed set. Adding a new surface
1141
+ * is a cbox-ui PR; hosts can't invent their own. That's the
1142
+ * discipline that keeps the chrome from drifting back into per-app
1143
+ * land.
1144
+ *
1145
+ * The criterion for accepting a new surface: "would it be useful
1146
+ * in *every* Cbox app?" Inbox yes. Search yes. Help yes. "Webhook
1147
+ * delivery details" no — that's a route inside webhooks, not a
1148
+ * cross-app overlay.
1149
+ */
1150
+
1151
+ type SurfaceId = 'inbox' | 'search' | 'activity' | 'help';
1152
+ type SurfaceArgs = {
1153
+ inbox: {
1154
+ focusUnread?: boolean;
1155
+ };
1156
+ search: {
1157
+ initialQuery?: string;
1158
+ };
1159
+ activity: {
1160
+ scope?: 'user' | 'org';
1161
+ };
1162
+ help: {
1163
+ topic?: string;
1164
+ };
1165
+ };
1166
+ /**
1167
+ * Auth + transport plumbing the host injects. cbox-ui's surface
1168
+ * implementations call these to reach backend services. The token
1169
+ * function is async + cached so the host can implement it via a
1170
+ * server-side proxy (cookie-authed) or against an in-memory access
1171
+ * token (pure SPA) — both end up callable.
1172
+ */
1173
+ type Transport = {
1174
+ /**
1175
+ * Per-service base URLs. Surfaces look these up by service key.
1176
+ * A host can leave a key unset to disable surfaces that depend on it.
1177
+ */
1178
+ urls: Partial<{
1179
+ notifications: string;
1180
+ search: string;
1181
+ audit: string;
1182
+ realtime: string;
1183
+ }>;
1184
+ /**
1185
+ * Mint or fetch a fresh bearer token for a given audience.
1186
+ * cbox-ui caches the result until `expiresAt` minus a 60-second
1187
+ * guard, so the host implementation can be a real HTTP call.
1188
+ */
1189
+ getToken: (audience: string) => Promise<{
1190
+ token: string;
1191
+ expiresAt: number;
1192
+ }>;
1193
+ /**
1194
+ * Optional CSRF token for host-shaped routes (mark-read
1195
+ * forwarders that go through the host's cookie session). Pure
1196
+ * SPA hosts can leave this off.
1197
+ */
1198
+ csrfToken?: () => string;
1199
+ };
1200
+ type SurfaceConfig = Partial<{
1201
+ search: {
1202
+ placeholder?: string;
1203
+ scopes?: string[];
1204
+ };
1205
+ inbox: {};
1206
+ }>;
1207
+ /**
1208
+ * Imperative API hosts use to open/close surfaces. Returned from
1209
+ * `useCboxSurfaces()`. The runtime guarantees:
1210
+ *
1211
+ * - At most one surface open at a time. `open` replaces the
1212
+ * active surface if any.
1213
+ * - `Escape` closes the active surface (modal/drawer dismiss).
1214
+ * - Navigation (Inertia visit, history change) closes the active
1215
+ * surface so users don't land in another section with a stale
1216
+ * overlay still up.
1217
+ */
1218
+ type SurfaceController = {
1219
+ activeSurface: SurfaceId | null;
1220
+ open: <K extends SurfaceId>(id: K, args?: SurfaceArgs[K]) => void;
1221
+ close: () => void;
1222
+ };
1223
+ /**
1224
+ * Inbox preview state — surfaced to the bell badge (chrome) even
1225
+ * when the inbox surface itself isn't open. The runtime keeps this
1226
+ * live by listening to realtime events; chrome reads it to render
1227
+ * the unread count without round-tripping the server on every
1228
+ * navigation.
1229
+ *
1230
+ * (Realtime is a follow-up session; the v1 implementation hydrates
1231
+ * from the host's CboxIdProvider notifications state and updates
1232
+ * only when the inbox surface explicitly mutates.)
1233
+ */
1234
+ type InboxState = {
1235
+ unreadCount: number;
1236
+ /** ISO timestamp of last successful fetch. */
1237
+ lastFetchedAt: string | null;
1238
+ };
1239
+ type RealtimeStatus = 'idle' | 'connecting' | 'connected' | 'disconnected' | 'unauthorized' | 'unsupported';
1240
+ /**
1241
+ * Shape returned from `useCboxRealtime()` — host code rarely needs
1242
+ * this directly; surfaces consume it internally. Exposed so chrome
1243
+ * components (the bell) can show a connection-state indicator if
1244
+ * desired.
1245
+ */
1246
+ type RealtimeState = {
1247
+ status: RealtimeStatus;
1248
+ };
1249
+ type CboxSurfacesProps = {
1250
+ transport: Transport;
1251
+ surfaces: SurfaceId[];
1252
+ config?: SurfaceConfig;
1253
+ /** Host renders this. cbox-ui mounts the surface runtime + any
1254
+ * active overlay alongside. */
1255
+ children?: ReactNode;
1256
+ };
1257
+
1258
+ /**
1259
+ * Mount the cbox-ui surface runtime once per app, alongside the
1260
+ * page tree. The host's chrome (bell, search trigger) reads
1261
+ * `useCboxSurfaces()` to open surfaces; cbox-ui renders the
1262
+ * active one as a modal/drawer over the page.
1263
+ *
1264
+ * Exactly one CboxSurfaces should be in the tree — typically right
1265
+ * inside CboxAppShell. Multiple instances would race their state
1266
+ * machines.
1267
+ */
1268
+ declare function CboxSurfaces({ transport, surfaces, config, children, }: CboxSurfacesProps): react_jsx_runtime.JSX.Element;
1269
+ /**
1270
+ * Imperative API for triggering surfaces from anywhere in the
1271
+ * host page. The bell-popover's "See all" calls
1272
+ * `useCboxSurfaces().open('inbox')` instead of navigating.
1273
+ */
1274
+ declare function useCboxSurfaces(): SurfaceController;
1275
+ /**
1276
+ * Optional variant — returns `null` when no `<CboxSurfaces>` host is
1277
+ * mounted. Chrome controls (bell, search trigger) use this to
1278
+ * degrade gracefully to cross-app navigation when the host hasn't
1279
+ * opted into the surface runtime yet.
1280
+ */
1281
+ declare function useCboxSurfacesOptional(): SurfaceController | null;
1282
+
1283
+ declare function cn(...inputs: ClassValue[]): string;
1284
+
1285
+ /**
1286
+ * URL-as-state — typed query-string parsing and serialization.
1287
+ *
1288
+ * The contract: anything that should survive a hard refresh, deep
1289
+ * link, or paste-share lives in the URL, not in React state. Tabs,
1290
+ * filters, search terms, pagination, sort order — every one of those
1291
+ * is a navigation, not a UI mutation. This module provides the typed
1292
+ * parsers and serializers; per-app adapters wire them to the host
1293
+ * router (Inertia's `router.get`, TanStack Router's `navigate`, etc.)
1294
+ * and shape the `[state, update]` ergonomics on top.
1295
+ *
1296
+ * Why not URLSearchParams directly? Because raw query strings throw
1297
+ * away types and have no notion of defaults. The schema declares both
1298
+ * once, and `buildUrlSearch` automatically elides values that match
1299
+ * their default so the URL stays human-readable. `?role=all&page=1`
1300
+ * collapses to `''` when `all` and `1` are the defaults. Going from
1301
+ * `state -> URL -> state` always round-trips.
1302
+ */
1303
+ /**
1304
+ * A single field in a URL schema. Encapsulates the three operations
1305
+ * that make typed URL state work:
1306
+ * - `parse(raw)` — string|null from the query string → typed value
1307
+ * - `serialize(v)` — typed value → string|null (null = drop the key)
1308
+ * - `isDefault(v)` — does this value equal the field's default? If
1309
+ * so, `buildUrlSearch` elides it to keep URLs short.
1310
+ */
1311
+ type UrlField<T> = {
1312
+ parse: (raw: string | null) => T;
1313
+ serialize: (value: T) => string | null;
1314
+ isDefault: (value: T) => boolean;
1315
+ };
1316
+ type UrlSchema = Record<string, UrlField<any>>;
1317
+ /**
1318
+ * Map a UrlSchema to its inferred state shape — `urlString('')` →
1319
+ * `string`, `urlEnum(['a', 'b'], 'a')` → `'a' | 'b'`, etc.
1320
+ */
1321
+ type InferUrlState<S extends UrlSchema> = {
1322
+ [K in keyof S]: S[K] extends UrlField<infer T> ? T : never;
1323
+ };
1324
+ /**
1325
+ * String field. The empty string is the conventional "absent" value
1326
+ * — it's what an HTML input gives you when the user clears it — so
1327
+ * we treat `'' === default` as "drop the param" by default. Pass an
1328
+ * explicit non-empty default if you genuinely want to persist `''`.
1329
+ */
1330
+ declare function urlString(defaultValue?: string): UrlField<string>;
1331
+ /**
1332
+ * Integer field with optional clamping. Non-numeric input falls back
1333
+ * to the default rather than NaN; out-of-range input clamps to the
1334
+ * boundary. Both behaviours are belt-and-braces against URL tampering.
1335
+ */
1336
+ declare function urlNumber(defaultValue: number, options?: {
1337
+ min?: number;
1338
+ max?: number;
1339
+ }): UrlField<number>;
1340
+ /**
1341
+ * Enum field — restrict to a fixed set of strings. Unknown values
1342
+ * fall back to the default rather than poisoning typed downstream
1343
+ * code with arbitrary user input. The `as const` on the values array
1344
+ * is what lets TS narrow the field type to the union.
1345
+ */
1346
+ declare function urlEnum<T extends string>(values: readonly T[], defaultValue: T): UrlField<T>;
1347
+ /**
1348
+ * Boolean field. Only the literal string `'true'` parses to true so
1349
+ * we don't accidentally interpret `?flag=0` or `?flag=false` as truthy.
1350
+ */
1351
+ declare function urlBoolean(defaultValue?: boolean): UrlField<boolean>;
1352
+ /**
1353
+ * Array-of-strings field — comma-joined in the URL. Useful for
1354
+ * multi-select filters (`?tags=foo,bar,baz`). Empty arrays drop the
1355
+ * param. Per-item parsing isn't customisable on purpose; if you need
1356
+ * a typed array of enums or numbers, build a thin wrapper.
1357
+ */
1358
+ declare function urlStringArray(defaultValue?: readonly string[]): UrlField<string[]>;
1359
+ /**
1360
+ * Convenience: declare a schema. Pure passthrough — exists to give
1361
+ * call sites a single, named import that reads like a definition
1362
+ * rather than a bare object literal.
1363
+ */
1364
+ declare function defineUrlSchema<S extends UrlSchema>(schema: S): S;
1365
+ /**
1366
+ * Parse a query string into a typed state object. Accepts both
1367
+ * leading-`?` and bare forms. Unknown keys are silently dropped —
1368
+ * the schema is the closed allow-list, which keeps server- and
1369
+ * client-readable surface area bounded.
1370
+ */
1371
+ declare function parseUrlSearch<S extends UrlSchema>(search: string, schema: S): InferUrlState<S>;
1372
+ /**
1373
+ * Serialize a typed state back to a query string. Default-valued
1374
+ * fields are elided so URLs stay short and bookmarks stay portable
1375
+ * across schema changes (a URL with `?page=1` keeps working when
1376
+ * `page` later stops being serialised because it's the default).
1377
+ *
1378
+ * Returns the serialised query without a leading `?`. The host
1379
+ * adapter is responsible for prepending or stitching it onto a path.
1380
+ */
1381
+ declare function buildUrlSearch<S extends UrlSchema>(state: InferUrlState<S>, schema: S): string;
1382
+
1383
+ export { AppContent, AppLauncher, AppShell, AppSwitcher, type CboxAppKey, type CboxAppLink, CboxAppShell, type CboxAppShellProps, CboxAppSidebar, type CboxAppSidebarProps, CboxBrand, type CboxBrandProps, type CboxIdContextValue, CboxIdProvider, CboxMobileNavDrawer, type CboxMobileNavDrawerProps, type CboxNavItem, type CboxNavSection, type CboxNotificationPreviewItem, type CboxNotificationsState, CboxOrgContextBar, type CboxOrgContextBarProps, type CboxOrganization, CboxSurfaces, type CboxSurfacesProps, type CboxUser, Combobox, type ComboboxAvatar, type ComboboxGroup, type ComboboxOption, type ComboboxProps, type ContextAvatar, ContextAvatarChip, ContextBar, type ContextCrumb, type ContextItem, DataPanel, EmptyState, Heading, type InboxState, type InferUrlState, KeyValue, KeyValueList, type LinkComponent, NotificationsBell, OrgSwitcher, PageHeader, PageShell, type PageTabSpec, PageTabs, type PageTabsProps, type PageWidth, PanelBody, type RealtimeState, type RealtimeStatus, RowLink, type RowLinkProps, SearchTrigger, SectionHeader, StatTile, StatusPill, type SurfaceArgs, type SurfaceConfig, type SurfaceController, type SurfaceId, TopBar, type Transport, type UrlField, type UrlSchema, UserMenu, buildUrlSearch, cn, defineUrlSchema, parseUrlSearch, urlBoolean, urlEnum, urlNumber, urlString, urlStringArray, useCboxId, useCboxIdOptional, useCboxSurfaces, useCboxSurfacesOptional };