@the-portland-company/shell 0.76.15 → 0.76.18
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/CHANGELOG.md +31 -0
- package/dist/app-chrome.cjs +133 -16
- package/dist/app-chrome.cjs.map +1 -1
- package/dist/app-chrome.d.cts +7 -2
- package/dist/app-chrome.d.ts +7 -2
- package/dist/app-chrome.js +5 -4
- package/dist/app-chrome.js.map +1 -1
- package/dist/{chunk-HSC6EYRR.js → chunk-GGM4AD43.js} +6 -1769
- package/dist/chunk-GGM4AD43.js.map +1 -0
- package/dist/chunk-TUK2IEL5.js +2002 -0
- package/dist/chunk-TUK2IEL5.js.map +1 -0
- package/dist/index-Cu5frXRQ.d.cts +990 -0
- package/dist/index-DJ3qBzfQ.d.ts +990 -0
- package/dist/index.cjs +170 -85
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +37 -36
- package/dist/index.js.map +1 -1
- package/dist/native.cjs +131 -15
- package/dist/native.cjs.map +1 -1
- package/dist/native.d.cts +4 -929
- package/dist/native.d.ts +4 -929
- package/dist/native.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-HSC6EYRR.js.map +0 -1
- package/dist/chunk-MY6G3LMN.js +0 -125
- package/dist/chunk-MY6G3LMN.js.map +0 -1
|
@@ -0,0 +1,990 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { ReactNode, ComponentType, CSSProperties } from 'react';
|
|
3
|
+
import { ThemePreference as ThemePreference$1 } from './theme-preference.cjs';
|
|
4
|
+
|
|
5
|
+
type FormColumnProps = {
|
|
6
|
+
children: ReactNode;
|
|
7
|
+
className?: string;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* The ONE constrained layout inside the frame's fluid `<main>` (Brand guideline
|
|
11
|
+
* "Content width is fluid"): a single left-aligned column, 960px max, for form
|
|
12
|
+
* and settings pages — single-record edit forms, Organization Settings tabs,
|
|
13
|
+
* My Profile, activation and setup flows. Data surfaces (tables, dashboards,
|
|
14
|
+
* maps) never use it; they take the full fluid width.
|
|
15
|
+
*
|
|
16
|
+
* It adds no gutter of its own — `<main>` already owns the 24px / 16px gutter —
|
|
17
|
+
* and it is never centered. There is no width prop on purpose: a per-page
|
|
18
|
+
* max-width is exactly what the guideline forbids.
|
|
19
|
+
*/
|
|
20
|
+
declare function FormColumn({ children, className }: FormColumnProps): react_jsx_runtime.JSX.Element;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The six canonical top-bar action slots (Reference/Global-Rules v9 → top-bar
|
|
24
|
+
* icons). Each slot's GLYPH is unique and comes from the Brand API
|
|
25
|
+
* `topbarIcons.items.*` tokens, exposed as `--topbar-icon-<slot>` CSS vars
|
|
26
|
+
* whose value is the brand-icon NAME. Every OTHER property — size, gap, color,
|
|
27
|
+
* hover, selected — is universal across all six slots.
|
|
28
|
+
*/
|
|
29
|
+
type TopBarIconSlot = 'comms-fuel' | 'apps' | 'notifications' | 'alerts' | 'support' | 'dark-mode';
|
|
30
|
+
|
|
31
|
+
type CommsNotificationKind = 'notification' | 'alert';
|
|
32
|
+
/** One row of the bell feed, as the shell reads it (camelCased). */
|
|
33
|
+
type CommsNotification = {
|
|
34
|
+
id: string;
|
|
35
|
+
kind: CommsNotificationKind;
|
|
36
|
+
title: string;
|
|
37
|
+
body: string;
|
|
38
|
+
icon: string | null;
|
|
39
|
+
link: string | null;
|
|
40
|
+
readAt: string | null;
|
|
41
|
+
orgId: string | null;
|
|
42
|
+
createdAt: string | null;
|
|
43
|
+
};
|
|
44
|
+
type NotificationsFeed = {
|
|
45
|
+
/** Alerts first, then notifications, newest first (Comms sorts them). */
|
|
46
|
+
notifications: CommsNotification[];
|
|
47
|
+
/** One badge number: unread notifications plus unresolved alerts (Spec §6.3). */
|
|
48
|
+
badge: number;
|
|
49
|
+
};
|
|
50
|
+
type NotificationsAdapter = {
|
|
51
|
+
/** Read the signed-in viewer's feed, scoped to an org when one is active. */
|
|
52
|
+
list(orgId: string | null): Promise<NotificationsFeed>;
|
|
53
|
+
/** Mark one row read. Resolves false on any failure, never throws. */
|
|
54
|
+
markRead(id: string): Promise<boolean>;
|
|
55
|
+
/** Archive one row (hides it from the feed). Resolves false on failure. */
|
|
56
|
+
archive(id: string): Promise<boolean>;
|
|
57
|
+
/** Resolve one alert. Comms rejects this for non-alert rows. */
|
|
58
|
+
resolve(id: string): Promise<boolean>;
|
|
59
|
+
};
|
|
60
|
+
/** The empty bell: what every degraded path renders. Always a fresh object. */
|
|
61
|
+
declare function emptyNotificationsFeed(): NotificationsFeed;
|
|
62
|
+
/**
|
|
63
|
+
* Shape a raw Comms body into the feed. Tolerant on purpose: unknown keys are
|
|
64
|
+
* ignored, rows without an id are dropped, and archived rows are never shown.
|
|
65
|
+
*/
|
|
66
|
+
declare function normalizeNotificationsFeed(raw: unknown): NotificationsFeed;
|
|
67
|
+
/**
|
|
68
|
+
* Build the comms-backed adapter over the app's chrome fetcher. A null fetcher
|
|
69
|
+
* (no chrome proxy wired) yields the empty bell with no network activity.
|
|
70
|
+
*/
|
|
71
|
+
declare function createCommsNotificationsAdapter(fetchJson: NavChromeFetcher | null): NotificationsAdapter;
|
|
72
|
+
|
|
73
|
+
type NavIconLike = ComponentType<{
|
|
74
|
+
size?: number;
|
|
75
|
+
className?: string;
|
|
76
|
+
}>;
|
|
77
|
+
declare const CORE_APP_ORIGIN = "https://app.politogyvrm.com";
|
|
78
|
+
/** Text/Email fuel top-up page (SMS Signal + Email Broadcast). */
|
|
79
|
+
declare const CORE_BILLING_PATH = "/organization-settings/subscription-billing/text-email";
|
|
80
|
+
/** Specs origin — same env-aware-constant pattern as CORE_APP_ORIGIN. */
|
|
81
|
+
declare const SPECS_ORIGIN = "https://specs.politogyvrm.com";
|
|
82
|
+
/**
|
|
83
|
+
* Resolve an organization logo URL for rendering in ANY app.
|
|
84
|
+
*
|
|
85
|
+
* `organizations.logo_url` is authored by VRM core and stored as a ROOT-RELATIVE
|
|
86
|
+
* core route — `/api/storage/public?bucket=organization-logos&path=<org>/logo.png`
|
|
87
|
+
* (the signed-storage proxy). VRM core renders it correctly because that path
|
|
88
|
+
* resolves against its own origin; every other app resolves it against its own
|
|
89
|
+
* origin (forms.politogyvrm.com/api/storage/public), gets a 404, and shows a
|
|
90
|
+
* broken image. The chrome API passes the stored value through verbatim, so the
|
|
91
|
+
* resolution belongs here — one definition for every app, core included.
|
|
92
|
+
*
|
|
93
|
+
* Absolute URLs (http/https/data/blob) pass through untouched.
|
|
94
|
+
*/
|
|
95
|
+
declare function resolveOrgLogoUrl(url: string | null | undefined): string | undefined;
|
|
96
|
+
declare function topBarIconButtonClass(tone?: 'default' | 'warning' | 'danger'): string;
|
|
97
|
+
declare function TopBarCountBadge({ count }: {
|
|
98
|
+
count: number;
|
|
99
|
+
}): react_jsx_runtime.JSX.Element | null;
|
|
100
|
+
/**
|
|
101
|
+
* The shared hover tooltip every top-bar control renders BELOW itself: an
|
|
102
|
+
* up-pointing arrow at the icon, a bold title line and a muted description.
|
|
103
|
+
* Pure CSS (group-hover) so it works on <a>, <button> and <summary> triggers.
|
|
104
|
+
* The trigger must carry `group/tbtip relative` (topBarIconButtonClass does).
|
|
105
|
+
*/
|
|
106
|
+
declare function TopBarTooltip({ title, description, }: {
|
|
107
|
+
title: string;
|
|
108
|
+
description?: ReactNode;
|
|
109
|
+
}): react_jsx_runtime.JSX.Element;
|
|
110
|
+
/**
|
|
111
|
+
* The up-pointing arrow every top-bar panel renders, anchored under its
|
|
112
|
+
* trigger icon (panels are right-aligned, so the arrow sits at the trigger's
|
|
113
|
+
* center: 16px in from the panel's right edge). One definition — every menu
|
|
114
|
+
* points at its own icon identically.
|
|
115
|
+
*/
|
|
116
|
+
declare function TopBarPanelArrow(): react_jsx_runtime.JSX.Element;
|
|
117
|
+
/**
|
|
118
|
+
* Wrapper for third-party top-bar widgets that ship their own launcher button
|
|
119
|
+
* (e.g. DevNotes). Normalizes the trigger to the universal 32px circular slot
|
|
120
|
+
* (chrome.css `.topbar-widget-slot`) and adds the standard tooltip.
|
|
121
|
+
*/
|
|
122
|
+
declare function TopBarWidgetSlot({ title, description, children, }: {
|
|
123
|
+
title: string;
|
|
124
|
+
description?: string;
|
|
125
|
+
children: ReactNode;
|
|
126
|
+
}): react_jsx_runtime.JSX.Element;
|
|
127
|
+
type ThemePreference = ThemePreference$1;
|
|
128
|
+
declare function NavThemeControl({ value, onChange, }: {
|
|
129
|
+
value: ThemePreference;
|
|
130
|
+
onChange: (next: ThemePreference) => void;
|
|
131
|
+
}): react_jsx_runtime.JSX.Element;
|
|
132
|
+
type NavChromeFetcher = (path: string, init?: {
|
|
133
|
+
method?: string;
|
|
134
|
+
headers?: Record<string, string>;
|
|
135
|
+
body?: string;
|
|
136
|
+
}) => Promise<unknown>;
|
|
137
|
+
/** GET me — the authenticated user's chrome identity from the shared API.
|
|
138
|
+
* userName prefers the VRM-core profile full name over token claims. */
|
|
139
|
+
type ChromeMe = {
|
|
140
|
+
user: string | null;
|
|
141
|
+
userName: string | null;
|
|
142
|
+
avatarUrl: string | null;
|
|
143
|
+
themePreference: string | null;
|
|
144
|
+
/** Universal navigation-display choice ('menu'|'tabs'|'both'); null when
|
|
145
|
+
* unset or when the profile column is absent in this environment. */
|
|
146
|
+
navDisplay: string | null;
|
|
147
|
+
/** The user's durable active-org preference (full org uuid); null when unset
|
|
148
|
+
* or when the profile column is absent. Adopted only when the request
|
|
149
|
+
* carries no `?org=`/cookie selector — URL → cookie → profile → default. */
|
|
150
|
+
activeOrg?: string | null;
|
|
151
|
+
account?: string | null;
|
|
152
|
+
accountName?: string | null;
|
|
153
|
+
role?: string | null;
|
|
154
|
+
/** Contact reply-to, when the profile carries them (optional/tolerant). */
|
|
155
|
+
email?: string | null;
|
|
156
|
+
phone?: string | null;
|
|
157
|
+
};
|
|
158
|
+
/**
|
|
159
|
+
* A shared read cache scoped to one `<NavChromeProvider>` (one fetcher, one
|
|
160
|
+
* page boot). Every consumer of a given (path, orgId) pair within the TTL
|
|
161
|
+
* window awaits the SAME promise instead of firing its own request — this is
|
|
162
|
+
* what collapsed `/orgs` (2-4x), `/me` (2-3x) and `/notifications` (2-3x) per
|
|
163
|
+
* page load, 1-3.5s each, down to one call apiece (nav-latency-report.md
|
|
164
|
+
* Table 2). Keying by orgId means switching the active org is itself the
|
|
165
|
+
* invalidation: the new org gets a fresh key, never the previous org's cached
|
|
166
|
+
* value. A failed request is evicted immediately so the next consumer retries
|
|
167
|
+
* rather than replaying the same rejection for the rest of the TTL.
|
|
168
|
+
*/
|
|
169
|
+
declare function createChromeFetchCache(getFetchJson: () => NavChromeFetcher): {
|
|
170
|
+
dedupedFetch: (path: string, init: Parameters<NavChromeFetcher>[1] | undefined, opts?: {
|
|
171
|
+
orgId?: string | null;
|
|
172
|
+
ttlMs?: number;
|
|
173
|
+
}) => Promise<unknown>;
|
|
174
|
+
invalidate: (path: string) => void;
|
|
175
|
+
};
|
|
176
|
+
type NavChromeContextValue = {
|
|
177
|
+
fetchJson: NavChromeFetcher;
|
|
178
|
+
dedupedFetch: ReturnType<typeof createChromeFetchCache>['dedupedFetch'];
|
|
179
|
+
invalidateChromeCache: ReturnType<typeof createChromeFetchCache>['invalidate'];
|
|
180
|
+
me: ChromeMe | null;
|
|
181
|
+
meLoaded: boolean;
|
|
182
|
+
/** The bell's feed adapter; null = the legacy contacts-shaped feed. */
|
|
183
|
+
notificationsAdapter: NotificationsAdapter | null;
|
|
184
|
+
};
|
|
185
|
+
declare function NavChromeProvider({ fetchJson, notificationsAdapter, children, }: {
|
|
186
|
+
fetchJson: NavChromeFetcher;
|
|
187
|
+
/**
|
|
188
|
+
* Omitted: the bell reads the comms feed through the same proxy (default).
|
|
189
|
+
* An app may inject its own adapter; `null` keeps the legacy feed.
|
|
190
|
+
*/
|
|
191
|
+
notificationsAdapter?: NotificationsAdapter | null;
|
|
192
|
+
children: ReactNode;
|
|
193
|
+
}): react_jsx_runtime.JSX.Element;
|
|
194
|
+
/** The authenticated user's chrome identity (profile name / avatar / theme). */
|
|
195
|
+
declare function useChromeMe(): {
|
|
196
|
+
me: ChromeMe | null;
|
|
197
|
+
loaded: boolean;
|
|
198
|
+
};
|
|
199
|
+
/** The provider's de-duped chrome read (null outside <NavChromeProvider>). */
|
|
200
|
+
declare function useNavChromeDedupedFetch(): NavChromeContextValue['dedupedFetch'] | null;
|
|
201
|
+
/** The injected chrome fetcher (null outside <NavChromeProvider>). */
|
|
202
|
+
declare function useNavChromeFetcher(): NavChromeFetcher | null;
|
|
203
|
+
type FuelChannelStatus = {
|
|
204
|
+
remainingUnits: number;
|
|
205
|
+
rawBalanceUnits: number;
|
|
206
|
+
usedUnits: number;
|
|
207
|
+
lowFuel: boolean;
|
|
208
|
+
lowFuelThreshold: number;
|
|
209
|
+
};
|
|
210
|
+
type CommunicationsFuelStatus = {
|
|
211
|
+
organizationId: string;
|
|
212
|
+
signal: FuelChannelStatus;
|
|
213
|
+
broadcast: FuelChannelStatus;
|
|
214
|
+
combinedAvailableCredits: number;
|
|
215
|
+
};
|
|
216
|
+
type ChromeNotificationAlert = {
|
|
217
|
+
id: string;
|
|
218
|
+
title: string;
|
|
219
|
+
description: string;
|
|
220
|
+
href: string | null;
|
|
221
|
+
kind: string;
|
|
222
|
+
/** Server read state (adapter feeds only); the local watermark still applies. */
|
|
223
|
+
read?: boolean;
|
|
224
|
+
};
|
|
225
|
+
type ChromeNotificationReply = {
|
|
226
|
+
id: string;
|
|
227
|
+
contactId: string | null;
|
|
228
|
+
name: string;
|
|
229
|
+
preview: string;
|
|
230
|
+
/** Optional deep-link to the conversation; the new-reply toast opens it. */
|
|
231
|
+
href?: string;
|
|
232
|
+
/**
|
|
233
|
+
* Optional arrival timestamp. Present, it lets the trigger badge count a
|
|
234
|
+
* reply as "unseen" only when it is newer than the last time the panel was
|
|
235
|
+
* opened; absent (older payloads), replies simply don't survive the first
|
|
236
|
+
* open as unseen. Tolerated either way.
|
|
237
|
+
*/
|
|
238
|
+
createdAt?: string;
|
|
239
|
+
};
|
|
240
|
+
/** A running or finished contact import surfaced in the notifications panel. */
|
|
241
|
+
type ChromeNotificationImport = {
|
|
242
|
+
id: string;
|
|
243
|
+
label: string;
|
|
244
|
+
/** 0–100 progress, or null when indeterminate (no bar rendered). */
|
|
245
|
+
percent: number | null;
|
|
246
|
+
status: 'active' | 'completed';
|
|
247
|
+
href: string | null;
|
|
248
|
+
};
|
|
249
|
+
/** A cluster of quarantined contacts sharing one phone number. */
|
|
250
|
+
type ChromeNotificationQuarantine = {
|
|
251
|
+
phone: string;
|
|
252
|
+
count: number;
|
|
253
|
+
/** Upstream may omit it; the row already renders conditionally on it. */
|
|
254
|
+
latestAt: string | null;
|
|
255
|
+
};
|
|
256
|
+
type ChromeNotificationsPayload = {
|
|
257
|
+
organizationId: string;
|
|
258
|
+
alerts: ChromeNotificationAlert[];
|
|
259
|
+
replies: ChromeNotificationReply[];
|
|
260
|
+
/** Optional — running/finished imports (tolerant when absent). */
|
|
261
|
+
imports?: ChromeNotificationImport[];
|
|
262
|
+
/** Optional — clustered quarantined contacts (tolerant when absent). */
|
|
263
|
+
quarantined?: ChromeNotificationQuarantine[];
|
|
264
|
+
/** Optional — the server's one badge number (adapter feeds only). */
|
|
265
|
+
badge?: number;
|
|
266
|
+
};
|
|
267
|
+
type ChromeOrgSummary = {
|
|
268
|
+
id: string;
|
|
269
|
+
name: string | null;
|
|
270
|
+
logoUrl: string | null;
|
|
271
|
+
brandColor: string | null;
|
|
272
|
+
role: string | null;
|
|
273
|
+
/** Un-mapped VRM-core membership role (e.g. "super_admin"). */
|
|
274
|
+
rawRole?: string | null;
|
|
275
|
+
};
|
|
276
|
+
/**
|
|
277
|
+
* Fetch the signed-in user's VRM-core organizations through the injected
|
|
278
|
+
* fetcher. Exposed as a hook so the org switcher in the sidebar and the
|
|
279
|
+
* org-scoped widgets share one request.
|
|
280
|
+
*/
|
|
281
|
+
declare function useChromeOrgs({ enabled }?: {
|
|
282
|
+
enabled?: boolean;
|
|
283
|
+
}): {
|
|
284
|
+
orgs: ChromeOrgSummary[] | null;
|
|
285
|
+
loading: boolean;
|
|
286
|
+
error: boolean;
|
|
287
|
+
};
|
|
288
|
+
/**
|
|
289
|
+
* A top-bar popover: the shared circular trigger + a right-aligned panel.
|
|
290
|
+
* Closes on outside click / Escape. Purely presentational.
|
|
291
|
+
*
|
|
292
|
+
* EXPORTED so an app with a richer panel than the stock actions can render it
|
|
293
|
+
* inside the SHARED chrome — same 32px trigger, same tooltip, same badge, same
|
|
294
|
+
* panel surface and arrow — instead of hand-rolling a trigger and drifting.
|
|
295
|
+
* This is the enabling step for folding VRM's Fuel breakdown, import-progress,
|
|
296
|
+
* alerts and reply-feed panels into the shared cluster one at a time, without
|
|
297
|
+
* dropping capability (shell-nav-handoff §4; UI-Platform forbids migrating an
|
|
298
|
+
* app to a rendering that loses an element it already ships).
|
|
299
|
+
*/
|
|
300
|
+
declare function TopBarPopover({ label, description, slot, fallbackIcon, badge, tone, onOpen, openToken, onOpenChange, hideTooltip, children, panelClassName, }: {
|
|
301
|
+
label: string;
|
|
302
|
+
description?: string;
|
|
303
|
+
slot?: TopBarIconSlot;
|
|
304
|
+
fallbackIcon: NavIconLike;
|
|
305
|
+
badge?: number;
|
|
306
|
+
tone?: 'default' | 'warning' | 'danger';
|
|
307
|
+
onOpen?: () => void;
|
|
308
|
+
/**
|
|
309
|
+
* Imperative "open" signal: whenever this number CHANGES the panel opens
|
|
310
|
+
* (and `onOpen` fires). Lets a sibling — e.g. the notifications toast — open
|
|
311
|
+
* the panel without owning the popover's state. Undefined disables it.
|
|
312
|
+
*/
|
|
313
|
+
openToken?: number;
|
|
314
|
+
/**
|
|
315
|
+
* Notified on every open/close. A sibling surface anchored to the same
|
|
316
|
+
* trigger (e.g. the notifications toast) must know when the panel is open so
|
|
317
|
+
* the two never render on top of each other — Reference/UI-Platform gives
|
|
318
|
+
* every top-bar panel ONE surface anchored to its trigger, so a second
|
|
319
|
+
* surface at the same anchor always occludes it.
|
|
320
|
+
*/
|
|
321
|
+
onOpenChange?: (open: boolean) => void;
|
|
322
|
+
/**
|
|
323
|
+
* Hide the trigger's hover tooltip while a transient nudge (the new-reply
|
|
324
|
+
* toast, the low-fuel hint) is up under the same trigger. Both are z-50 and
|
|
325
|
+
* anchored to the trigger, so without this the nudge paints over the
|
|
326
|
+
* tooltip (measured on a2p: the reply toast covered "Alerts &
|
|
327
|
+
* Notifications"). Reference/UI-Platform: one surface per trigger.
|
|
328
|
+
*/
|
|
329
|
+
hideTooltip?: boolean;
|
|
330
|
+
children: ReactNode;
|
|
331
|
+
panelClassName?: string;
|
|
332
|
+
}): react_jsx_runtime.JSX.Element;
|
|
333
|
+
declare function FuelAction({ orgId }: {
|
|
334
|
+
orgId: string | null;
|
|
335
|
+
}): react_jsx_runtime.JSX.Element;
|
|
336
|
+
declare function NotificationsAction({ orgId }: {
|
|
337
|
+
orgId: string | null;
|
|
338
|
+
}): react_jsx_runtime.JSX.Element;
|
|
339
|
+
declare function SupportAction({ orgId, orgs: knownOrgs, }?: {
|
|
340
|
+
orgId?: string | null;
|
|
341
|
+
orgs?: {
|
|
342
|
+
id: string;
|
|
343
|
+
name: string | null;
|
|
344
|
+
}[];
|
|
345
|
+
}): react_jsx_runtime.JSX.Element;
|
|
346
|
+
declare function SpecOverlayAction({ orgId, orgName }?: {
|
|
347
|
+
orgId?: string | null;
|
|
348
|
+
orgName?: string | null;
|
|
349
|
+
}): react_jsx_runtime.JSX.Element | null;
|
|
350
|
+
type NavDisplayMode = 'menu' | 'tabs' | 'both';
|
|
351
|
+
declare function NavDisplayAction({ value, onChange, }: {
|
|
352
|
+
value: NavDisplayMode;
|
|
353
|
+
onChange: (next: NavDisplayMode) => void;
|
|
354
|
+
}): react_jsx_runtime.JSX.Element;
|
|
355
|
+
declare function useThemePreference(): {
|
|
356
|
+
value: ThemePreference;
|
|
357
|
+
setValue: (next: ThemePreference) => void;
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Icon slots used by the standard menus. The shell stays icon-library-agnostic:
|
|
362
|
+
* callers map each key to their own icon components (lucide, react-icons, …).
|
|
363
|
+
* A missing key simply renders that row without an icon.
|
|
364
|
+
*/
|
|
365
|
+
type StandardNavIconKey = 'home' | 'users' | 'mail' | 'messageSquare' | 'layout' | 'activity' | 'lifeBuoy' | 'grid' | 'list' | 'upload' | 'edit' | 'fileText' | 'send' | 'clock' | 'user' | 'barChart' | 'briefcase' | 'settings' | 'creditCard' | 'dollarSign' | 'share' | 'phone' | 'map' | 'mapPin' | 'target' | 'flag' | 'layoutDashboard' | 'usersRound' | 'mousePointerClick' | 'clipboardList' | 'circleUser' | 'crosshair' | 'landmark' | 'chartNoAxes' | 'doorOpen' | 'signpost';
|
|
366
|
+
/** The operating Mode whose nav tree to build. Defaults to `relationship`. */
|
|
367
|
+
type StandardNavMode = 'relationship' | 'campaign' | 'petition';
|
|
368
|
+
/**
|
|
369
|
+
* Per-feature availability for the Campaign tree. Campaign Mode ships its apps
|
|
370
|
+
* incrementally: a disabled feature is omitted from the nav entirely (rather
|
|
371
|
+
* than rendered as a dead/disabled row), and its flag flips to `true` in a
|
|
372
|
+
* later patch release as the app goes live. Dashboard and Voter Contact are
|
|
373
|
+
* core-hosted and always present, so they carry no flag.
|
|
374
|
+
*/
|
|
375
|
+
interface CampaignFeatureAvailability {
|
|
376
|
+
a2p: boolean;
|
|
377
|
+
phonebank: boolean;
|
|
378
|
+
canvass: boolean;
|
|
379
|
+
signs: boolean;
|
|
380
|
+
tribes: boolean;
|
|
381
|
+
polling: boolean;
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Launch state at this shell version: every Campaign feature is deployed and
|
|
385
|
+
* live (a2p / phonebank / canvass / signs .politogyvrm.com all serve the app
|
|
386
|
+
* behind SSO as of 2026-07-24), so no row is hidden. The flags remain so a
|
|
387
|
+
* future feature can ship dark, and so a caller can hide one per-deployment.
|
|
388
|
+
*/
|
|
389
|
+
declare const CAMPAIGN_FEATURE_AVAILABILITY: CampaignFeatureAvailability;
|
|
390
|
+
interface StandardNavOptions {
|
|
391
|
+
/**
|
|
392
|
+
* @deprecated app.politogyvrm.com (VRM core) is retired (Reference/UI-Platform
|
|
393
|
+
* → "Retirement of app.politogyvrm.com"). Every row this tree used to resolve
|
|
394
|
+
* against VRM core now points at its V2 host directly, so this is unused.
|
|
395
|
+
* Kept only so existing callers don't need to drop the field immediately.
|
|
396
|
+
*/
|
|
397
|
+
coreBaseUrl?: string;
|
|
398
|
+
/** Icon component per slot; omitted slots render icon-less rows. */
|
|
399
|
+
icons?: Partial<Record<StandardNavIconKey, NavIcon>>;
|
|
400
|
+
/**
|
|
401
|
+
* App-local override for the "Contacts" item. `children` are used verbatim
|
|
402
|
+
* (NOT absolutized) so the owning app nests its own routes; `href` (also
|
|
403
|
+
* verbatim) feeds active-detection for the group header. When omitted, the
|
|
404
|
+
* item carries VRM core's own contacts sub-pages as absolute links.
|
|
405
|
+
* (Relationship tree only.)
|
|
406
|
+
*/
|
|
407
|
+
contactsItem?: {
|
|
408
|
+
href?: string;
|
|
409
|
+
children?: NavItem[];
|
|
410
|
+
/**
|
|
411
|
+
* Live contact count shown right-aligned on the Contacts row, already
|
|
412
|
+
* formatted for display ("31.9K"). Only the owning app knows it.
|
|
413
|
+
* (Specs/Contacts design: `Contacts · 31.9K`.)
|
|
414
|
+
*/
|
|
415
|
+
count?: string | number | null;
|
|
416
|
+
};
|
|
417
|
+
/**
|
|
418
|
+
* Top-level rows to OMIT, by label (case-insensitive), e.g. `['Polls','CTAs']`.
|
|
419
|
+
* The Specs/Contacts design rail lists Relationship → Collect → Account with
|
|
420
|
+
* exactly Dashboard · Contacts · Email broadcast · SMS/MMS blast / Calls to
|
|
421
|
+
* action · Forms · Surveys / My profile · Organization settings; apps that
|
|
422
|
+
* still surface Polls / CTAs keep them by default. A section row with no
|
|
423
|
+
* remaining items is dropped too.
|
|
424
|
+
*/
|
|
425
|
+
hiddenItems?: string[];
|
|
426
|
+
/**
|
|
427
|
+
* App-local override for the Petition-mode "Dashboard" entry, same contract
|
|
428
|
+
* as `contactsItem`: `href`/`children` used verbatim so the Petitions app
|
|
429
|
+
* nests its own routes under the entry. (Petition tree only.)
|
|
430
|
+
*/
|
|
431
|
+
petitionsItem?: {
|
|
432
|
+
href?: string;
|
|
433
|
+
children?: NavItem[];
|
|
434
|
+
};
|
|
435
|
+
/**
|
|
436
|
+
* The trailing "Support" row. VRM core opens its SupportRequestModal here;
|
|
437
|
+
* other apps supply their own handler (`onClick`) or a link (`href`).
|
|
438
|
+
* Omitted or `false` → no Support row. Appended to every mode's tree.
|
|
439
|
+
*/
|
|
440
|
+
supportItem?: {
|
|
441
|
+
onClick?: () => void;
|
|
442
|
+
href?: string;
|
|
443
|
+
} | false;
|
|
444
|
+
/**
|
|
445
|
+
* Which mode's tree to build. Default `relationship`, whose output is
|
|
446
|
+
* byte-for-byte what this function produced before modes existed.
|
|
447
|
+
*/
|
|
448
|
+
mode?: StandardNavMode;
|
|
449
|
+
/**
|
|
450
|
+
* Active organization id (full UUID). EVERY link in EVERY tree carries it as
|
|
451
|
+
* `?org=<uuid>` — the same convention the Mode switcher and My Account menu
|
|
452
|
+
* use — so the destination app opens on the same org instead of the user's
|
|
453
|
+
* default one. Omit it only when the caller genuinely has no active org yet.
|
|
454
|
+
*/
|
|
455
|
+
activeOrgId?: string | null;
|
|
456
|
+
/**
|
|
457
|
+
* Per-feature Campaign availability overrides, merged over
|
|
458
|
+
* `CAMPAIGN_FEATURE_AVAILABILITY`. Only meaningful for the Campaign tree.
|
|
459
|
+
*/
|
|
460
|
+
campaignAvailability?: Partial<CampaignFeatureAvailability>;
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Build the standard Politogy VRM menu for the given `mode` (default
|
|
464
|
+
* `relationship`), in the exact order and wording the main app renders it. See
|
|
465
|
+
* `StandardNavOptions` for the app-specific hooks (`contactsItem`,
|
|
466
|
+
* `supportItem`) and the Campaign inputs (`activeOrgId`, `campaignAvailability`).
|
|
467
|
+
*/
|
|
468
|
+
declare function standardPolitogyNav(opts: StandardNavOptions): NavItem[];
|
|
469
|
+
|
|
470
|
+
type NavIcon = ComponentType<{
|
|
471
|
+
size?: number;
|
|
472
|
+
className?: string;
|
|
473
|
+
strokeWidth?: number;
|
|
474
|
+
}>;
|
|
475
|
+
type NavItem = {
|
|
476
|
+
/**
|
|
477
|
+
* What this row IS. Defaults to `'link'` — every existing `NavItem[]` keeps
|
|
478
|
+
* its exact type and rendering.
|
|
479
|
+
*
|
|
480
|
+
* `'section'` is a non-interactive heading that groups the rows beneath it:
|
|
481
|
+
* uppercase, muted, letterspaced, not focusable, no hover. Only `label` is
|
|
482
|
+
* read; `href`, `icon`, `onClick` and `children` are ignored.
|
|
483
|
+
*
|
|
484
|
+
* The legacy chrome supported this and 0.43 dropped it, so apps faked it by
|
|
485
|
+
* passing `disabled` rows with no href — which looks approximately right and
|
|
486
|
+
* is wrong where it counts: a disabled row announces as an unavailable
|
|
487
|
+
* CONTROL to a screen reader, when it is not a control at all. A section is
|
|
488
|
+
* its own kind of row (Reference/UI-Platform → Left bar).
|
|
489
|
+
*
|
|
490
|
+
* Deliberately a widened field rather than a discriminated union: a union
|
|
491
|
+
* would make `item.href` a type error for every existing consumer that reads
|
|
492
|
+
* it, turning an additive feature into a breaking change.
|
|
493
|
+
*/
|
|
494
|
+
kind?: 'link' | 'section';
|
|
495
|
+
/** Route to link to. Optional when `onClick` is supplied (renders a button). */
|
|
496
|
+
href?: string;
|
|
497
|
+
label: string;
|
|
498
|
+
icon?: NavIcon;
|
|
499
|
+
/**
|
|
500
|
+
* When supplied the row renders as a `<button>` (styled identically to the
|
|
501
|
+
* link rows) instead of a routed link — used for actions like "Support".
|
|
502
|
+
*/
|
|
503
|
+
onClick?: () => void;
|
|
504
|
+
/**
|
|
505
|
+
* Disables the row: non-interactive and dimmed, with `disabledTitle` shown as
|
|
506
|
+
* a hover tooltip explaining why. Mirrors `NavTab`'s existing contract so a
|
|
507
|
+
* gated row looks the same whether it renders in the sidebar or a tab strip.
|
|
508
|
+
* Without this, an app that gates a section (VRM dims Email / SMS until setup
|
|
509
|
+
* is complete) had no way to express it and the row rendered fully clickable
|
|
510
|
+
* (shell-nav-handoff §3).
|
|
511
|
+
*/
|
|
512
|
+
disabled?: boolean;
|
|
513
|
+
/** Tooltip for a disabled row — say why, and how to resolve it. */
|
|
514
|
+
disabledTitle?: string;
|
|
515
|
+
/**
|
|
516
|
+
* Right-aligned count/annotation on the row — e.g. the live contact count
|
|
517
|
+
* on "Contacts" (`31.9K`). Pre-formatted by the app; the shell only draws
|
|
518
|
+
* it (Specs/Contacts design rail). Omitted/null draws nothing.
|
|
519
|
+
*/
|
|
520
|
+
badge?: string | number | null;
|
|
521
|
+
/**
|
|
522
|
+
* Nested sub-items. When present, the row becomes an expandable GROUP header.
|
|
523
|
+
* Clicking it always toggles the children open/closed and rotates the
|
|
524
|
+
* right-aligned chevron; if the item ALSO carries an `onClick`, that runs too,
|
|
525
|
+
* so a parent can navigate to its section dashboard while expanding
|
|
526
|
+
* (shell-nav-handoff §2). An `href` alone never navigates from a group header. The sidebar runs a single-open accordion (opening one group closes
|
|
527
|
+
* the others) and auto-expands whichever group contains the active route —
|
|
528
|
+
* mirroring the main VRM app's Chakra sidebar. Children render as smaller nav
|
|
529
|
+
* rows on a lighter panel and support both `href` links and `onClick` actions.
|
|
530
|
+
*/
|
|
531
|
+
children?: NavItem[];
|
|
532
|
+
};
|
|
533
|
+
type NavLinkProps = {
|
|
534
|
+
href: string;
|
|
535
|
+
className?: string;
|
|
536
|
+
'aria-current'?: 'page' | undefined;
|
|
537
|
+
children: ReactNode;
|
|
538
|
+
role?: string;
|
|
539
|
+
'aria-selected'?: boolean;
|
|
540
|
+
'aria-label'?: string;
|
|
541
|
+
title?: string;
|
|
542
|
+
style?: CSSProperties;
|
|
543
|
+
};
|
|
544
|
+
type NavLinkComponent = ComponentType<NavLinkProps>;
|
|
545
|
+
declare function isNavItemActive(pathname: string, href: string): boolean;
|
|
546
|
+
/** A multi-mode app (VRM core) renders NavAppShell without a registry modeId;
|
|
547
|
+
* its current mode label still decides the rail colour (Left-Navigation §5). */
|
|
548
|
+
declare function modeIdFromLabel(label: string | undefined): string | undefined;
|
|
549
|
+
/** "Ben Edtl" -> "Ben E." (Left-Navigation §10). One word stays as is. */
|
|
550
|
+
declare function shortUserName(name: string): string;
|
|
551
|
+
/** Short environment name for the rail's build line ("prod", "staging", "local"). */
|
|
552
|
+
declare function shortEnvLabel(label: string): string;
|
|
553
|
+
declare function envChipClasses(label: string): string;
|
|
554
|
+
type NavBrand = {
|
|
555
|
+
/** Where the logo links to. Defaults to "/". */
|
|
556
|
+
homeHref?: string;
|
|
557
|
+
/**
|
|
558
|
+
* Brand logo image src. When supplied this renders as the wordmark logo
|
|
559
|
+
* (matching the main app's BrandLogo), and `badgeLabel`/`title`/`subtitle`
|
|
560
|
+
* are used only as the no-image fallback.
|
|
561
|
+
*/
|
|
562
|
+
logoSrc?: string;
|
|
563
|
+
logoAlt?: string;
|
|
564
|
+
/**
|
|
565
|
+
* The square mark shown at the top of the COLLAPSED rail (Left-Navigation
|
|
566
|
+
* §11.6) — Brand `assets.logoSquare`, the all-white vrm tile. Falls back to
|
|
567
|
+
* the badge when omitted.
|
|
568
|
+
*/
|
|
569
|
+
logoSquareSrc?: string;
|
|
570
|
+
/**
|
|
571
|
+
* The logo URLs are still resolving from the Brand token sheet. The rail
|
|
572
|
+
* reserves the logo's box and draws nothing — never the badge fallback,
|
|
573
|
+
* which would flash a stand-in on every load.
|
|
574
|
+
*/
|
|
575
|
+
logoPending?: boolean;
|
|
576
|
+
/** Short label shown inside the brand badge fallback (e.g. "P"). */
|
|
577
|
+
badgeLabel: string;
|
|
578
|
+
title: string;
|
|
579
|
+
subtitle?: string;
|
|
580
|
+
};
|
|
581
|
+
type NavOrgOption = {
|
|
582
|
+
id: string;
|
|
583
|
+
name: string | null;
|
|
584
|
+
logoUrl?: string | null;
|
|
585
|
+
/** The org's Brand Kit icon mark; falls back to `logoUrl`, then initials. */
|
|
586
|
+
iconUrl?: string | null;
|
|
587
|
+
brandColor?: string | null;
|
|
588
|
+
role?: string | null;
|
|
589
|
+
};
|
|
590
|
+
type NavOrg = {
|
|
591
|
+
name: string | null;
|
|
592
|
+
/** Full org id. When present, a copy affordance is shown by the label. */
|
|
593
|
+
id?: string | null;
|
|
594
|
+
/** Optional org logo for the pill badge; falls back to initials. */
|
|
595
|
+
logoUrl?: string | null;
|
|
596
|
+
/**
|
|
597
|
+
* The org's Brand Kit icon mark, shown on the collapsed rail (32px, on its
|
|
598
|
+
* white tile). Falls back to `logoUrl` scaled to fit, then initials.
|
|
599
|
+
*/
|
|
600
|
+
iconUrl?: string | null;
|
|
601
|
+
/** Solid pill background (the org's brand color). Defaults to the accent. */
|
|
602
|
+
brandColor?: string | null;
|
|
603
|
+
/** Org's secondary Brand Kit color; the card's bottom-edge gradient runs
|
|
604
|
+
* primary → secondary. Falls back to the Politogy gradient when unset. */
|
|
605
|
+
brandColorSecondary?: string | null;
|
|
606
|
+
/**
|
|
607
|
+
* Organization Settings §7.1–§7.2: when the org has no logo yet, an Account
|
|
608
|
+
* Admin sees an "Upload logo" link in the band instead of the org name.
|
|
609
|
+
*/
|
|
610
|
+
canUploadLogo?: boolean;
|
|
611
|
+
uploadLogoHref?: string;
|
|
612
|
+
onClick?: () => void;
|
|
613
|
+
/**
|
|
614
|
+
* The user's organizations. When present (even one), the pill renders as a
|
|
615
|
+
* dropdown SWITCHER (Reference/UI-Platform "Organization selector": never a
|
|
616
|
+
* static label in a multi-org app); selecting one calls `onSwitch(id)`.
|
|
617
|
+
*/
|
|
618
|
+
organizations?: NavOrgOption[];
|
|
619
|
+
onSwitch?: (id: string) => void;
|
|
620
|
+
/**
|
|
621
|
+
* The active org is still resolving from the chrome API. Renders a skeleton
|
|
622
|
+
* pill instead of a name. Required because the app's own workspace label is
|
|
623
|
+
* NOT the organization: showing it while the real org loads flashes a wrong
|
|
624
|
+
* value (Global Rules — async values shimmer, they never render a stand-in).
|
|
625
|
+
*/
|
|
626
|
+
loading?: boolean;
|
|
627
|
+
};
|
|
628
|
+
type NavModeOption = {
|
|
629
|
+
id: string;
|
|
630
|
+
label: string;
|
|
631
|
+
icon?: NavIcon;
|
|
632
|
+
/**
|
|
633
|
+
* Left-Navigation §7: a mode the organization does not hold is always
|
|
634
|
+
* shown (never hidden), dimmed, and not selectable. Defaults to true.
|
|
635
|
+
*/
|
|
636
|
+
available?: boolean;
|
|
637
|
+
/** Tooltip for an unavailable mode, e.g. "Campaign Mode isn't active for
|
|
638
|
+
* this organization." Appended with "See upgrade options" for an Account
|
|
639
|
+
* Admin on a paid account (never during a trial). */
|
|
640
|
+
unavailableTooltip?: string;
|
|
641
|
+
};
|
|
642
|
+
type NavMode = {
|
|
643
|
+
label: string;
|
|
644
|
+
icon?: NavIcon;
|
|
645
|
+
/**
|
|
646
|
+
* Entitled modes for the active organization. With more than one option the
|
|
647
|
+
* pill renders as a SELECTOR (dropdown); with zero/one it stays the classic
|
|
648
|
+
* disabled single-mode pill. Selecting calls onSelect(id).
|
|
649
|
+
*/
|
|
650
|
+
options?: NavModeOption[];
|
|
651
|
+
onSelect?: (id: string) => void;
|
|
652
|
+
};
|
|
653
|
+
type NavFooterLink = {
|
|
654
|
+
label: string;
|
|
655
|
+
href: string;
|
|
656
|
+
external?: boolean;
|
|
657
|
+
};
|
|
658
|
+
type NavFooter = {
|
|
659
|
+
/**
|
|
660
|
+
* Build metadata (env chip / commit / version / date / author). OPTIONAL:
|
|
661
|
+
* omit envLabel/version/commit to render a links-only footer — apps show
|
|
662
|
+
* the meta block to Super Administrators only (Reference/UI-Platform).
|
|
663
|
+
*/
|
|
664
|
+
envLabel?: string;
|
|
665
|
+
version?: string;
|
|
666
|
+
commit?: string;
|
|
667
|
+
links?: NavFooterLink[];
|
|
668
|
+
/** ISO date (or any Date-parseable string) of the deployed commit. */
|
|
669
|
+
commitDate?: string;
|
|
670
|
+
/** Full name of the commit author (only the first name is shown). */
|
|
671
|
+
commitAuthor?: string;
|
|
672
|
+
/**
|
|
673
|
+
* The signed-in user, drawn as a card (avatar · name · email · chevron)
|
|
674
|
+
* ABOVE the links row. Reference/UI-Platform v30 → Left bar: "The user —
|
|
675
|
+
* avatar, name, email and the user menu — lives in the left bar footer."
|
|
676
|
+
* Clicking it opens the account menu UPWARD. `NavAppShell` fills these from
|
|
677
|
+
* `topBar.user` / `topBar.userMenu` / `topBar.onSignOut` automatically, so
|
|
678
|
+
* standalone `NavSidebar` consumers pass them only when they want the card.
|
|
679
|
+
*/
|
|
680
|
+
user?: NavUser | null;
|
|
681
|
+
userMenu?: NavUserMenu | null;
|
|
682
|
+
onSignOut?: () => void;
|
|
683
|
+
};
|
|
684
|
+
type NavSidebarProps = {
|
|
685
|
+
pathname: string;
|
|
686
|
+
brand: NavBrand;
|
|
687
|
+
nav: {
|
|
688
|
+
groupLabel?: string;
|
|
689
|
+
items: NavItem[];
|
|
690
|
+
isActive?: (pathname: string, href: string) => boolean;
|
|
691
|
+
/**
|
|
692
|
+
* Render each top-level group's CHILDREN directly at the root of the rail
|
|
693
|
+
* — no parent header row, no accordion. For an app whose whole tree nests
|
|
694
|
+
* under one entry (Petitions), that parent row is pure anatomy: V1 never
|
|
695
|
+
* had it, it duplicates the first child's route, and it costs a level of
|
|
696
|
+
* indentation. Sections render as sections; rows render with the 'flat'
|
|
697
|
+
* variant (top-level geometry, wash active treatment). Top-level items
|
|
698
|
+
* WITHOUT children render unchanged, so a mixed tree degrades sanely.
|
|
699
|
+
*/
|
|
700
|
+
flatten?: boolean;
|
|
701
|
+
};
|
|
702
|
+
org?: NavOrg | null;
|
|
703
|
+
mode?: NavMode | null;
|
|
704
|
+
footer?: NavFooter | null;
|
|
705
|
+
linkComponent?: NavLinkComponent;
|
|
706
|
+
/**
|
|
707
|
+
* Small-screen expand state (owned by `NavAppShell`). The rail is never
|
|
708
|
+
* hidden (Left-Navigation §11.6): at `md` and up it is the full rail, below
|
|
709
|
+
* `md` it collapses to the 64px icon rail. `open` expands the collapsed
|
|
710
|
+
* rail to the full rail as a modal drawer with a backdrop, and `onClose`
|
|
711
|
+
* wires the backdrop, the close button, and Escape. `md` and up ignores
|
|
712
|
+
* both.
|
|
713
|
+
*/
|
|
714
|
+
open?: boolean;
|
|
715
|
+
onClose?: () => void;
|
|
716
|
+
/**
|
|
717
|
+
* Expands the collapsed (below `md`) rail. When set, the square logo tile at
|
|
718
|
+
* the top of the collapsed rail becomes the "Expand navigation" control —
|
|
719
|
+
* the Left-Navigation Design draws no other toggle and no top-bar hamburger.
|
|
720
|
+
*/
|
|
721
|
+
onOpen?: () => void;
|
|
722
|
+
/**
|
|
723
|
+
* Closes the expanded rail after a nav-link click without counting as the
|
|
724
|
+
* user's Collapse choice. Defaults to `onClose`.
|
|
725
|
+
*/
|
|
726
|
+
onNavigate?: () => void;
|
|
727
|
+
};
|
|
728
|
+
declare function NavSidebar({ pathname, brand, nav, org, mode, footer, linkComponent, open, onClose, onOpen, onNavigate, }: NavSidebarProps): react_jsx_runtime.JSX.Element;
|
|
729
|
+
type NavUser = {
|
|
730
|
+
name: string | null;
|
|
731
|
+
email?: string | null;
|
|
732
|
+
/** Profile PHOTO url — highest-precedence avatar source. */
|
|
733
|
+
avatarUrl?: string | null;
|
|
734
|
+
/** Memoji url — used when no photo is set; falls back to initials. */
|
|
735
|
+
memojiUrl?: string | null;
|
|
736
|
+
tier?: string | null;
|
|
737
|
+
role?: string | null;
|
|
738
|
+
};
|
|
739
|
+
type NavTopBarAction = {
|
|
740
|
+
key: string;
|
|
741
|
+
/**
|
|
742
|
+
* Canonical top-bar slot. When set, the glyph is sourced by NAME from the
|
|
743
|
+
* Brand API via `--topbar-icon-<slot>`; `icon` becomes the fallback used
|
|
744
|
+
* until the runtime Brand stylesheet lands (or when the token is unset).
|
|
745
|
+
*/
|
|
746
|
+
slot?: TopBarIconSlot;
|
|
747
|
+
icon: NavIcon;
|
|
748
|
+
label: string;
|
|
749
|
+
onClick?: () => void;
|
|
750
|
+
href?: string;
|
|
751
|
+
badge?: number | null;
|
|
752
|
+
tone?: 'default' | 'warning' | 'danger';
|
|
753
|
+
/** Tooltip body shown under the icon (label is the tooltip title). */
|
|
754
|
+
description?: string;
|
|
755
|
+
};
|
|
756
|
+
type NavLinkedAccount = {
|
|
757
|
+
id: string;
|
|
758
|
+
name: string | null;
|
|
759
|
+
email?: string | null;
|
|
760
|
+
avatarUrl?: string | null;
|
|
761
|
+
active?: boolean;
|
|
762
|
+
};
|
|
763
|
+
type NavRoleBadge = {
|
|
764
|
+
role: string;
|
|
765
|
+
orgName?: string | null;
|
|
766
|
+
};
|
|
767
|
+
/**
|
|
768
|
+
* The My Account menu (avatar dropdown) — mirrors the main VRM app's user
|
|
769
|
+
* menu: My Profile / Organization Settings links, a ROLES & ORGANIZATIONS
|
|
770
|
+
* section, an ACCOUNTS switcher, an optional theme control, then Sign out.
|
|
771
|
+
* Every section renders only when its data is supplied.
|
|
772
|
+
*/
|
|
773
|
+
type NavUserMenu = {
|
|
774
|
+
myProfileHref?: string;
|
|
775
|
+
organizationSettingsHref?: string;
|
|
776
|
+
roles?: NavRoleBadge[];
|
|
777
|
+
accounts?: NavLinkedAccount[];
|
|
778
|
+
onSwitchAccount?: (id: string) => void;
|
|
779
|
+
onRemoveAccount?: (id: string) => void;
|
|
780
|
+
addAccountHref?: string;
|
|
781
|
+
onAddAccount?: () => void;
|
|
782
|
+
/**
|
|
783
|
+
* Navigation display (Sub Menu / Tabs Only / Both) — lives in the account
|
|
784
|
+
* menu since 0.68.0; the top-bar `NavDisplayAction` popover is retired from
|
|
785
|
+
* the standard cluster (design: 4 icon buttons + Help).
|
|
786
|
+
*/
|
|
787
|
+
navDisplay?: {
|
|
788
|
+
value: NavDisplayMode;
|
|
789
|
+
onChange: (next: NavDisplayMode) => void;
|
|
790
|
+
};
|
|
791
|
+
/** Theme preference segmented control (Light / System / Dark). */
|
|
792
|
+
theme?: {
|
|
793
|
+
value: ThemePreference;
|
|
794
|
+
onChange: (next: ThemePreference) => void;
|
|
795
|
+
};
|
|
796
|
+
};
|
|
797
|
+
type NavBreadcrumb = {
|
|
798
|
+
label: string;
|
|
799
|
+
href?: string;
|
|
800
|
+
};
|
|
801
|
+
type NavTopBarProps = {
|
|
802
|
+
/**
|
|
803
|
+
* Chrome-owned page title line (`<h1>` above main). Omit when the page body
|
|
804
|
+
* owns its own title (registry `pageTitle: false`, e.g. Contacts).
|
|
805
|
+
*/
|
|
806
|
+
title?: string;
|
|
807
|
+
subtitle?: string;
|
|
808
|
+
/**
|
|
809
|
+
* Breadcrumb trail rendered UNDER the page title (Reference/UI-Platform:
|
|
810
|
+
* every page and sub-page/tab shows its location). Last crumb = current
|
|
811
|
+
* location (non-link); earlier crumbs may carry hrefs.
|
|
812
|
+
*/
|
|
813
|
+
breadcrumbs?: NavBreadcrumb[];
|
|
814
|
+
user?: NavUser | null;
|
|
815
|
+
userMenu?: NavUserMenu;
|
|
816
|
+
onSignOut?: () => void;
|
|
817
|
+
/**
|
|
818
|
+
* Structured icon-button action cluster (32×32 circular buttons with optional
|
|
819
|
+
* count badges), rendered before the `actions` slot. Matches the main app's
|
|
820
|
+
* topbar action icons.
|
|
821
|
+
*/
|
|
822
|
+
actionsList?: NavTopBarAction[];
|
|
823
|
+
/** App-specific action cluster rendered left of the theme toggle. */
|
|
824
|
+
actions?: ReactNode;
|
|
825
|
+
/** Theme toggle slot (injected so this package needs no theme dependency). */
|
|
826
|
+
themeToggle?: ReactNode;
|
|
827
|
+
/**
|
|
828
|
+
* Mobile drawer trigger (owned by `NavAppShell`). Supplying it renders a
|
|
829
|
+
* hamburger button, hidden at `lg` and up, as the first thing in the header
|
|
830
|
+
* — below `lg` the sidebar is otherwise unreachable. Omit to render no
|
|
831
|
+
* hamburger at all (e.g. `NavTopBar` used standalone).
|
|
832
|
+
*/
|
|
833
|
+
onOpenNav?: () => void;
|
|
834
|
+
/** Whether the drawer `onOpenNav` opens is currently open (drives `aria-expanded`). */
|
|
835
|
+
navOpen?: boolean;
|
|
836
|
+
/**
|
|
837
|
+
* Universal search — Reference/UI-Platform "Top bar → required elements":
|
|
838
|
+
* the search field, 320px wide, at the left end of the bar; nothing else
|
|
839
|
+
* sits on the left. Omit `onChange`/`onSubmit` for a decorative field (an
|
|
840
|
+
* app that has not wired search yet still gets the correct chrome).
|
|
841
|
+
*/
|
|
842
|
+
search?: {
|
|
843
|
+
placeholder?: string;
|
|
844
|
+
value?: string;
|
|
845
|
+
onChange?: (value: string) => void;
|
|
846
|
+
onSubmit?: (value: string) => void;
|
|
847
|
+
};
|
|
848
|
+
/**
|
|
849
|
+
* Build notes — a quiet text button that starts the right-hand group
|
|
850
|
+
* (Reference/UI-Platform "Top bar → required elements").
|
|
851
|
+
*/
|
|
852
|
+
/**
|
|
853
|
+
* Where the My Account trigger lives. `'topbar'` (default for a standalone
|
|
854
|
+
* `NavTopBar`) keeps the avatar + name + chevron at the right end of the bar;
|
|
855
|
+
* `'sidebar'` / `'none'` hide it here — `NavAppShell` passes `'sidebar'` and
|
|
856
|
+
* draws the user card in the left-bar footer instead (UI-Platform v30:
|
|
857
|
+
* "There is no My Account badge in the top bar").
|
|
858
|
+
*/
|
|
859
|
+
accountMenu?: 'topbar' | 'sidebar' | 'none';
|
|
860
|
+
buildNotes?: {
|
|
861
|
+
label?: string;
|
|
862
|
+
onOpen?: () => void;
|
|
863
|
+
};
|
|
864
|
+
};
|
|
865
|
+
declare function NavTopBar({ title, subtitle, breadcrumbs, user, userMenu, onSignOut, actionsList, actions, themeToggle, onOpenNav, navOpen, search, buildNotes, accountMenu, }: NavTopBarProps): react_jsx_runtime.JSX.Element;
|
|
866
|
+
type NavTab = {
|
|
867
|
+
/** Stable identity (used as the React key). */
|
|
868
|
+
key: string;
|
|
869
|
+
/** The tab's visible text. */
|
|
870
|
+
label: string;
|
|
871
|
+
/**
|
|
872
|
+
* A Lucide icon rendered 6px left of the label (Reference/UI-Platform →
|
|
873
|
+
* Tabs, decided 2026-10-06: "Icon + label. Each tab is a Lucide icon."),
|
|
874
|
+
* 15px / 1.75 stroke (13px on a small strip), following the label's
|
|
875
|
+
* selected / unselected / hover color via `currentColor`. Icon-ONLY tabs
|
|
876
|
+
* (no label) are retired — this always pairs with `label`.
|
|
877
|
+
*/
|
|
878
|
+
icon?: NavIcon;
|
|
879
|
+
/** Optional count, drawn as a mono pill after the label. */
|
|
880
|
+
count?: string | number | null;
|
|
881
|
+
/** Route to link to (rendered through `linkComponent`). */
|
|
882
|
+
href?: string;
|
|
883
|
+
/** Click handler — rendered as a `<button>` when there's no `href`. */
|
|
884
|
+
onSelect?: () => void;
|
|
885
|
+
/** Whether this tab is the active one (drives the mode-color underline). */
|
|
886
|
+
selected?: boolean;
|
|
887
|
+
/**
|
|
888
|
+
* Non-interactive, dimmed tab (e.g. Surveys' Canvas/Results before a survey
|
|
889
|
+
* is chosen). Renders a non-navigating span; `disabledTitle` is the tooltip
|
|
890
|
+
* explaining why.
|
|
891
|
+
*/
|
|
892
|
+
disabled?: boolean;
|
|
893
|
+
disabledTitle?: string;
|
|
894
|
+
};
|
|
895
|
+
type NavTabStripProps = {
|
|
896
|
+
tabs: NavTab[];
|
|
897
|
+
/** Accessible name for the tablist. */
|
|
898
|
+
'aria-label'?: string;
|
|
899
|
+
/** Routing primitive for `href` tabs (Next `<Link>`, etc). Defaults to <a>. */
|
|
900
|
+
linkComponent?: NavLinkComponent;
|
|
901
|
+
/** `'sm'`: 12px text with 8px vertical padding. Default `'md'` (13px / 12px). */
|
|
902
|
+
size?: 'md' | 'sm';
|
|
903
|
+
/** Extra classes on the tablist container. */
|
|
904
|
+
className?: string;
|
|
905
|
+
/**
|
|
906
|
+
* Optional tab panel rendered directly below the strip, on the page
|
|
907
|
+
* surface with no card chrome. Omit to render only the strip and compose
|
|
908
|
+
* the page body yourself.
|
|
909
|
+
*/
|
|
910
|
+
children?: ReactNode;
|
|
911
|
+
};
|
|
912
|
+
/**
|
|
913
|
+
* The canonical page tab strip: underlined text tabs on a 1px rule. Render it
|
|
914
|
+
* in the page body below the page header; pass the app's `Link` as
|
|
915
|
+
* `linkComponent` for `href` tabs.
|
|
916
|
+
*/
|
|
917
|
+
declare function NavTabStrip({ tabs, 'aria-label': ariaLabel, linkComponent, size, className, children, }: NavTabStripProps): react_jsx_runtime.JSX.Element;
|
|
918
|
+
/**
|
|
919
|
+
* sessionStorage key holding the Beta-banner dismissal. sessionStorage — not
|
|
920
|
+
* localStorage — so the dismissal lasts only for the browser session; and it is
|
|
921
|
+
* additionally cleared on sign-out (see `clearBetaBannerDismissal`, called from
|
|
922
|
+
* the chrome frame) so a fresh LOGIN always re-shows the notice.
|
|
923
|
+
*/
|
|
924
|
+
declare const BETA_BANNER_DISMISS_KEY = "politogy_beta_banner_dismissed";
|
|
925
|
+
/**
|
|
926
|
+
* Re-arm the Beta banner. The chrome frame calls this on sign-out so the notice
|
|
927
|
+
* reappears at the next login rather than staying hidden across the boundary
|
|
928
|
+
* (sessionStorage survives an in-tab logout→login round trip on its own).
|
|
929
|
+
*/
|
|
930
|
+
declare function clearBetaBannerDismissal(): void;
|
|
931
|
+
type NavBetaBannerProps = {
|
|
932
|
+
/** Leading sentence(s) before the link. Defaults to the standard Beta copy. */
|
|
933
|
+
text?: string;
|
|
934
|
+
/** Bold + underlined link label. Defaults to the standard Terms copy. */
|
|
935
|
+
linkLabel?: string;
|
|
936
|
+
/** Terms & Conditions URL (opens in a new tab). Defaults to the app T&C page. */
|
|
937
|
+
href?: string;
|
|
938
|
+
/**
|
|
939
|
+
* Show a dismiss (×) button that hides the banner for the rest of the browser
|
|
940
|
+
* session. Cleared on sign-out, so it returns at the next login. Default true.
|
|
941
|
+
*/
|
|
942
|
+
dismissible?: boolean;
|
|
943
|
+
};
|
|
944
|
+
declare function NavBetaBanner({ text, linkLabel, href, dismissible }: NavBetaBannerProps): react_jsx_runtime.JSX.Element | null;
|
|
945
|
+
type NavAppShellProps = {
|
|
946
|
+
sidebar: NavSidebarProps;
|
|
947
|
+
topBar: NavTopBarProps;
|
|
948
|
+
/**
|
|
949
|
+
* When supplied, a full-width Beta notice strip renders directly below the top
|
|
950
|
+
* bar. Presence of the prop enables it; all fields fall back to the standard
|
|
951
|
+
* Beta copy + Terms & Conditions link.
|
|
952
|
+
*/
|
|
953
|
+
betaBanner?: NavBetaBannerProps;
|
|
954
|
+
/**
|
|
955
|
+
* @deprecated The frame's content area is one fluid `<main>` (Brand
|
|
956
|
+
* guideline "Content width is fluid"): the 24px gutter (16px below `md`),
|
|
957
|
+
* no max-width and no centered container. Apps never emit their own page
|
|
958
|
+
* container, so there is nothing left to choose here.
|
|
959
|
+
*
|
|
960
|
+
* `'bare'` is still honored — it renders `children` with no `<main>` and no
|
|
961
|
+
* gutter — only so VRM core, which renders `NavAppShell` directly around
|
|
962
|
+
* pages that still carry their own `PageContainer`, does not get a nested
|
|
963
|
+
* `<main>` before it migrates. It warns in development and will be removed.
|
|
964
|
+
* `'padded'` is now the same as omitting the prop.
|
|
965
|
+
*/
|
|
966
|
+
contentWrapper?: 'padded' | 'bare';
|
|
967
|
+
/**
|
|
968
|
+
* The operating Mode this frame is scoped to, stamped on the root as
|
|
969
|
+
* `data-politogy-mode` so the Brand API's per-Mode chrome tokens can target
|
|
970
|
+
* it (Reference/UI-Platform → "Per-mode branding — one accent per Mode").
|
|
971
|
+
*
|
|
972
|
+
* This is app IDENTITY, not app configuration: `PolitogyAppFrame` passes it
|
|
973
|
+
* from the `POLITOGY_APPS` registry, never from a prop an app supplies, so an
|
|
974
|
+
* app still cannot choose its own chrome colour.
|
|
975
|
+
*
|
|
976
|
+
* Omitted (or a Mode that declares no chrome group in the Brand API) => every
|
|
977
|
+
* accent falls back to the global one, exactly as before 0.47.
|
|
978
|
+
*/
|
|
979
|
+
modeId?: string;
|
|
980
|
+
/**
|
|
981
|
+
* Where the user card / account menu renders. `'sidebar'` (default since
|
|
982
|
+
* 0.68.0) draws it in the left-bar footer and hides the top-bar trigger
|
|
983
|
+
* (Reference/UI-Platform v30); `'topbar'` restores the 0.67 top-right badge.
|
|
984
|
+
*/
|
|
985
|
+
accountMenuPlacement?: 'sidebar' | 'topbar';
|
|
986
|
+
children: ReactNode;
|
|
987
|
+
};
|
|
988
|
+
declare function NavAppShell({ sidebar, topBar, betaBanner, contentWrapper, accountMenuPlacement, modeId, children, }: NavAppShellProps): react_jsx_runtime.JSX.Element;
|
|
989
|
+
|
|
990
|
+
export { NavThemeControl as $, type ChromeOrgSummary as A, BETA_BANNER_DISMISS_KEY as B, type CommsNotification as C, type CommunicationsFuelStatus as D, FuelAction as E, FormColumn as F, type FuelChannelStatus as G, NavAppShell as H, type NavAppShellProps as I, NavBetaBanner as J, type NavBetaBannerProps as K, type NavBrand as L, NavChromeProvider as M, type NavItem as N, NavDisplayAction as O, type NavFooterLink as P, type NavLinkProps as Q, type NavLinkedAccount as R, type StandardNavIconKey as S, type NavModeOption as T, type NavOrg as U, type NavRoleBadge as V, NavSidebar as W, type NavSidebarProps as X, type NavTab as Y, NavTabStrip as Z, type NavTabStripProps as _, type CommsNotificationKind as a, NavTopBar as a0, type NavTopBarAction as a1, type NavTopBarProps as a2, type NavUser as a3, NotificationsAction as a4, SPECS_ORIGIN as a5, SpecOverlayAction as a6, type StandardNavOptions as a7, SupportAction as a8, type ThemePreference as a9, TopBarCountBadge as aa, TopBarPanelArrow as ab, TopBarPopover as ac, TopBarTooltip as ad, TopBarWidgetSlot as ae, clearBetaBannerDismissal as af, envChipClasses as ag, isNavItemActive as ah, modeIdFromLabel as ai, resolveOrgLogoUrl as aj, shortEnvLabel as ak, shortUserName as al, standardPolitogyNav as am, topBarIconButtonClass as an, useChromeMe as ao, useChromeOrgs as ap, useNavChromeDedupedFetch as aq, useNavChromeFetcher as ar, useThemePreference as as, type NotificationsAdapter as b, type NotificationsFeed as c, createCommsNotificationsAdapter as d, emptyNotificationsFeed as e, type NavIcon as f, type NavBreadcrumb as g, type StandardNavMode as h, type NavLinkComponent as i, type NavChromeFetcher as j, type NavMode as k, type NavUserMenu as l, type NavOrgOption as m, normalizeNotificationsFeed as n, type NavFooter as o, type NavDisplayMode as p, CAMPAIGN_FEATURE_AVAILABILITY as q, CORE_APP_ORIGIN as r, CORE_BILLING_PATH as s, type CampaignFeatureAvailability as t, type ChromeMe as u, type ChromeNotificationAlert as v, type ChromeNotificationImport as w, type ChromeNotificationQuarantine as x, type ChromeNotificationReply as y, type ChromeNotificationsPayload as z };
|