@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.
- package/BRAND.md +77 -0
- package/LICENSE +21 -0
- package/README.md +156 -0
- package/TRADEMARKS.md +5 -0
- package/assets/favicon.svg +6 -0
- package/assets/monograms/atlas.svg +14 -0
- package/assets/monograms/cortex.svg +14 -0
- package/assets/monograms/id.svg +14 -0
- package/assets/monograms/track.svg +14 -0
- package/assets/og-template.svg +50 -0
- package/assets/wordmark-mono.svg +14 -0
- package/assets/wordmark.svg +18 -0
- package/dist/index.d.ts +1383 -0
- package/dist/index.js +3671 -0
- package/dist/index.js.map +1 -0
- package/package.json +69 -0
- package/tailwind.preset.js +98 -0
- package/tokens/cbox.css +274 -0
package/dist/index.d.ts
ADDED
|
@@ -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 };
|