@meetreeve/ui 0.2.0 → 0.5.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/README.md +46 -0
- package/dist/index.d.mts +139 -6
- package/dist/index.d.ts +139 -6
- package/dist/index.js +376 -106
- package/dist/index.mjs +375 -109
- package/dist/tokens/index.d.mts +15 -1
- package/dist/tokens/index.d.ts +15 -1
- package/dist/tokens/index.js +8 -2
- package/dist/tokens/index.mjs +6 -1
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -102,6 +102,18 @@ import { ResponsiveHeader } from "@meetreeve/ui";
|
|
|
102
102
|
|
|
103
103
|
Search is the primary slot — always inline, floored at a minimum width from `@meetreeve/ui/tokens`. Under width pressure everything ELSE collapses first: the wordmark drops to glyph-only, secondary controls fold into a hamburger disclosure below `md`.
|
|
104
104
|
|
|
105
|
+
### CSS container-query collapse (DEV-4260)
|
|
106
|
+
|
|
107
|
+
The wordmark↔glyph and inline-actions↔hamburger split is pure CSS — a Tailwind v4 `@container` on the header plus container-query variants (`@min-[768px]:...`) — not JS/`matchMedia`. Both the desktop and mobile structures are always in the DOM; CSS alone decides which is visible at the header's *current container width* (not the viewport — the header can be narrower than the viewport, e.g. inside a sidebar). This means: no desktop first-paint FOUC, and a no-JS viewer gets the correct layout for their actual width instead of always falling back to the mobile/hamburger markup. JS is used only for the hamburger's open/close state and one `ResizeObserver` that closes an open mobile menu when the header's own container regrows past the split.
|
|
108
|
+
|
|
109
|
+
**Your Tailwind build must scan this package's dist for these classes to work** — same requirement as `AppSidebar`'s `md:flex` (DEV-4351). Tailwind v4 ignores `.gitignore`'d paths (`node_modules`) by default, so add:
|
|
110
|
+
|
|
111
|
+
```css
|
|
112
|
+
@source "../../node_modules/@meetreeve/ui/dist";
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
to your `globals.css` (adjust the relative path to your app). See `reeve-tenant-frontend`'s `src/app/globals.css` for the existing precedent. Without this, `ResponsiveHeader`'s container-variant classes (and `AppSidebar`'s) never make it into your compiled CSS, and the header renders as if `display: none` applied everywhere.
|
|
116
|
+
|
|
105
117
|
## BrandGlyph
|
|
106
118
|
|
|
107
119
|
```tsx
|
|
@@ -112,6 +124,40 @@ import { BrandGlyph } from "@meetreeve/ui";
|
|
|
112
124
|
|
|
113
125
|
Resolution order: favicon → logo → initials monogram.
|
|
114
126
|
|
|
127
|
+
## ContextSwitcher (DEV-4408)
|
|
128
|
+
|
|
129
|
+
One reusable, data-driven, org-grouped dropdown for switching the active
|
|
130
|
+
"context" — a brand, a tenant, or whatever `refId`-addressable entity a host
|
|
131
|
+
app maps onto `ContextSwitcherItem[]`. Fully headless: no data fetching, no
|
|
132
|
+
app-specific types. Host apps write a thin wrapper that pulls their own data
|
|
133
|
+
source (e.g. `useBrand()`) and maps it to items — see reeve-frontend's
|
|
134
|
+
`BrandContextSwitcher` for the pattern.
|
|
135
|
+
|
|
136
|
+
```tsx
|
|
137
|
+
import { ContextSwitcher, type ContextSwitcherItem } from "@meetreeve/ui";
|
|
138
|
+
|
|
139
|
+
const items: ContextSwitcherItem[] = brands.map((b) => ({
|
|
140
|
+
refId: b.id,
|
|
141
|
+
name: b.name,
|
|
142
|
+
favicon: faviconUrl(b.slug), // pre-resolve any host-specific favicon rules yourself
|
|
143
|
+
logo: b.logo_url,
|
|
144
|
+
orgId: b.org_id,
|
|
145
|
+
orgName: b.org_name,
|
|
146
|
+
}));
|
|
147
|
+
|
|
148
|
+
<ContextSwitcher
|
|
149
|
+
items={items}
|
|
150
|
+
activeRefId={activeBrand?.id ?? null}
|
|
151
|
+
onSelect={(item) => switchBrand(item.refId)}
|
|
152
|
+
placeholder="Select brand"
|
|
153
|
+
/>;
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Org headers render only when items span more than one org — the common
|
|
157
|
+
single-org case stays a flat list. `collapsed` swaps the trigger for an
|
|
158
|
+
icon-only button (56px nav rail); `footer` adds a slot below the list (e.g.
|
|
159
|
+
an "Add brand" CTA).
|
|
160
|
+
|
|
115
161
|
## Tokens
|
|
116
162
|
|
|
117
163
|
```ts
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
|
-
import { ReactNode, RefObject } from 'react';
|
|
2
|
+
import { ReactNode, ElementType, RefObject } from 'react';
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -10,9 +10,11 @@ import { z } from 'zod';
|
|
|
10
10
|
* order:
|
|
11
11
|
* 1. `brand.favicon` — the site's own square icon,
|
|
12
12
|
* 2. `brand.logo` rendered `object-contain` (no squish),
|
|
13
|
-
* 3. a
|
|
13
|
+
* 3. a single-letter monogram in the brand colour + an accent dot
|
|
14
|
+
* (DEV-4539 — modeled on Reeve's own "R." canon; no boxed container).
|
|
14
15
|
* Each image load error advances to the next candidate.
|
|
15
16
|
*/
|
|
17
|
+
declare const REEVE_ACCENT = "#FF6B2B";
|
|
16
18
|
interface Brand {
|
|
17
19
|
/** Display name — drives the wordmark and the monogram fallback. */
|
|
18
20
|
name: string;
|
|
@@ -20,8 +22,17 @@ interface Brand {
|
|
|
20
22
|
favicon?: string | null;
|
|
21
23
|
/** Wider brand logo URL (second cascade step, rendered `object-contain`). */
|
|
22
24
|
logo?: string | null;
|
|
23
|
-
/** Override the deterministic monogram
|
|
25
|
+
/** Override the deterministic monogram letter colour. */
|
|
24
26
|
color?: string | null;
|
|
27
|
+
/**
|
|
28
|
+
* Accent-dot colour for the single-letter monogram fallback (DEV-4539) —
|
|
29
|
+
* the "family mark". Pass the brand DNA's own strong accent colour when
|
|
30
|
+
* one is known; falls back to Reeve's own accent dot (REEVE_ACCENT)
|
|
31
|
+
* otherwise, the same "part of the Reeve ecosystem" signal the backend
|
|
32
|
+
* logo generator resolves. `null`/omitted both mean "use the fallback" —
|
|
33
|
+
* this field becomes a brand-manifest-owned value once C2 lands.
|
|
34
|
+
*/
|
|
35
|
+
accentColor?: string | null;
|
|
25
36
|
}
|
|
26
37
|
/**
|
|
27
38
|
* Favicon URL for a brand's canonical domain. Returns null unless `domain`
|
|
@@ -53,6 +64,19 @@ interface ResponsiveHeaderProps {
|
|
|
53
64
|
search: ReactNode;
|
|
54
65
|
/** Secondary nav links — inline on desktop, in the menu on mobile. */
|
|
55
66
|
nav?: HeaderNavItem[];
|
|
67
|
+
/**
|
|
68
|
+
* Optional destination for the brand lockup. When set, the glyph + wordmark
|
|
69
|
+
* become a single clickable link to this href (restores the "click the logo
|
|
70
|
+
* to go home" affordance). When omitted, the lockup is a plain, non-link div.
|
|
71
|
+
*/
|
|
72
|
+
brandHref?: string;
|
|
73
|
+
/**
|
|
74
|
+
* Link renderer for the brand lockup and `nav` href items. Defaults to a bare
|
|
75
|
+
* `"a"` so the package stays framework-agnostic; consumers pass their router's
|
|
76
|
+
* link (e.g. `next/link`) for client-side navigation. Action-only nav items
|
|
77
|
+
* (no href) stay `<button>` regardless.
|
|
78
|
+
*/
|
|
79
|
+
linkComponent?: ElementType;
|
|
56
80
|
/** Auth control (avatar / sign-in) — secondary. */
|
|
57
81
|
auth?: ReactNode;
|
|
58
82
|
/** Theme toggle — secondary. */
|
|
@@ -70,9 +94,53 @@ interface ResponsiveHeaderProps {
|
|
|
70
94
|
* brand wordmark drops to glyph-only and the secondary controls fold into a
|
|
71
95
|
* hamburger disclosure. The search is never touched.
|
|
72
96
|
*
|
|
73
|
-
*
|
|
97
|
+
* CSS container-query collapse (DEV-4260): the wordmark<->glyph and
|
|
98
|
+
* inline-actions<->hamburger split is pure CSS (`@container` on the header +
|
|
99
|
+
* Tailwind v4 container-query variants), NOT JS/matchMedia. Both the desktop
|
|
100
|
+
* and mobile structures render in EVERY environment — SSR, first paint, and
|
|
101
|
+
* with JS fully disabled — so there's no desktop first-paint FOUC and no
|
|
102
|
+
* "no-JS users get hamburger-only" fallback (DEV-4248 follow-up).
|
|
103
|
+
*
|
|
104
|
+
* The split is pinned to `BREAKPOINTS.md` (768px) via an ARBITRARY container
|
|
105
|
+
* variant (`@min-[768px]:...`) rather than Tailwind's *named* `@md` container
|
|
106
|
+
* variant: Tailwind v4's default container scale (`@md` = 448px) is a
|
|
107
|
+
* different scale than the viewport breakpoint scale and does NOT match
|
|
108
|
+
* `BREAKPOINTS.md`. Because Tailwind's build-time scanner needs a literal
|
|
109
|
+
* class string (it greps compiled output, it doesn't evaluate JS), "768" is a
|
|
110
|
+
* hardcoded literal in the classNames below, not interpolated from
|
|
111
|
+
* `BREAKPOINTS.md` — `responsive-header.test.tsx`'s className-contract tests
|
|
112
|
+
* assert against `BREAKPOINTS.md` directly so drift between the token and the
|
|
113
|
+
* literal fails loudly.
|
|
114
|
+
*
|
|
115
|
+
* Consumers must add `@meetreeve/ui/dist` to their Tailwind `@source` scan
|
|
116
|
+
* (see this package's README) or these container-variant classes never make
|
|
117
|
+
* it into the consumer's compiled CSS — same requirement as AppSidebar's
|
|
118
|
+
* `md:flex` (DEV-4351).
|
|
119
|
+
*
|
|
120
|
+
* JS is used ONLY for two things: the hamburger disclosure's open/close
|
|
121
|
+
* state, and ONE ResizeObserver on the header itself (deliberately NOT a
|
|
122
|
+
* viewport matchMedia — the point of DEV-4260/DEV-4248 is that the collapse
|
|
123
|
+
* reacts to the header's own *container* width, e.g. a narrow sidebar on a
|
|
124
|
+
* wide viewport) that tears down an open mobile menu when the header's
|
|
125
|
+
* container regrows past the CSS split.
|
|
126
|
+
*/
|
|
127
|
+
declare function ResponsiveHeader({ brand, search, nav, brandHref, linkComponent, auth, themeToggle, menuLabel, className, }: ResponsiveHeaderProps): react.JSX.Element;
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Pick a viewport-appropriate placeholder string: the `desktop` copy at/above
|
|
131
|
+
* `md`, the shorter `mobile` copy below it (falling back to `desktop` when no
|
|
132
|
+
* mobile string is supplied). Fixes the "long search prompt clips in the
|
|
133
|
+
* 200px-floored mobile slot" class of bug without the consumer wiring up its
|
|
134
|
+
* own media query.
|
|
135
|
+
*
|
|
136
|
+
* Built on the package's guarded `useMediaQuery`, which returns `false` when
|
|
137
|
+
* `matchMedia` is unavailable — so SSR and the first client paint yield the
|
|
138
|
+
* MOBILE string. That mobile-first default is intentional: the short string
|
|
139
|
+
* never clips, and the value reconciles up to the desktop string on mount once
|
|
140
|
+
* the viewport is measured `>= md` (consistent with the DEV-4260 FOUC note; no
|
|
141
|
+
* hydration mismatch, since server and first client render both read `false`).
|
|
74
142
|
*/
|
|
75
|
-
declare function
|
|
143
|
+
declare function useResponsivePlaceholder(desktop: string, mobile?: string): string;
|
|
76
144
|
|
|
77
145
|
/**
|
|
78
146
|
* SidebarLadder — the per-tenant JSON artifact (DEV-4351) that pre-calculates
|
|
@@ -325,4 +393,69 @@ interface FlyoutProps {
|
|
|
325
393
|
*/
|
|
326
394
|
declare function Flyout({ label, icon, active, children, className }: FlyoutProps): react.JSX.Element;
|
|
327
395
|
|
|
328
|
-
|
|
396
|
+
/**
|
|
397
|
+
* ContextSwitcher (DEV-4408) — promoted out of reeve-frontend's
|
|
398
|
+
* src/components/context/context-switcher.tsx (DEV-2513), which was already
|
|
399
|
+
* fully headless (props in, no app-specific data fetching). One reusable,
|
|
400
|
+
* data-driven, org-grouped dropdown for switching the active "context" —
|
|
401
|
+
* whatever refId-addressable entity a host app's own thin wrapper maps onto
|
|
402
|
+
* `ContextSwitcherItem[]` (reeve-frontend's brand, reeve-tenant-frontend's
|
|
403
|
+
* tenant, or — once the spawned-company substrate lands — a company
|
|
404
|
+
* cockpit). Orgs are pure visual groupers — headers render ONLY for
|
|
405
|
+
* multi-org accounts; the common single-org case stays a flat list.
|
|
406
|
+
*
|
|
407
|
+
* The menu renders through Radix DropdownMenu so it portals out of a
|
|
408
|
+
* narrow/collapsible rail and stays on-screen — Radix handles collision
|
|
409
|
+
* flip/shift. `collapsed` swaps the trigger for an icon-only button that
|
|
410
|
+
* fits a 56px collapsed rail.
|
|
411
|
+
*/
|
|
412
|
+
interface ContextSwitcherItem {
|
|
413
|
+
/** Stable identity for this context — a brand id, tenant id, etc. */
|
|
414
|
+
refId: string;
|
|
415
|
+
name: string;
|
|
416
|
+
/**
|
|
417
|
+
* Pre-resolved favicon URL (BrandGlyph's favicon-first cascade). Any
|
|
418
|
+
* host-specific favicon rules (e.g. reeve-frontend's synthetic-domain
|
|
419
|
+
* guard around `faviconUrl`) must be applied by the host BEFORE mapping
|
|
420
|
+
* into this item — this package stays agnostic of any one host's domain
|
|
421
|
+
* conventions.
|
|
422
|
+
*/
|
|
423
|
+
favicon?: string | null;
|
|
424
|
+
/** Wider brand/tenant logo URL (BrandGlyph's second cascade step). */
|
|
425
|
+
logo?: string | null;
|
|
426
|
+
/** Override BrandGlyph's deterministic monogram letter colour. */
|
|
427
|
+
color?: string | null;
|
|
428
|
+
/** Override BrandGlyph's monogram accent-dot colour (DEV-4539). */
|
|
429
|
+
accentColor?: string | null;
|
|
430
|
+
orgId?: string | null;
|
|
431
|
+
orgName?: string | null;
|
|
432
|
+
}
|
|
433
|
+
interface ContextOrgGroup {
|
|
434
|
+
orgId: string | null;
|
|
435
|
+
orgName: string | null;
|
|
436
|
+
items: ContextSwitcherItem[];
|
|
437
|
+
}
|
|
438
|
+
/** Group contexts by owning org. Items sort by name; groups by org name. */
|
|
439
|
+
declare function groupByOrg(items: ContextSwitcherItem[]): ContextOrgGroup[];
|
|
440
|
+
interface ContextSwitcherProps {
|
|
441
|
+
items: ContextSwitcherItem[];
|
|
442
|
+
/** refId of the active context. */
|
|
443
|
+
activeRefId: string | null;
|
|
444
|
+
onSelect: (item: ContextSwitcherItem) => void;
|
|
445
|
+
/** Optional dropdown footer slot, e.g. an "Add brand" CTA. */
|
|
446
|
+
footer?: React.ReactNode;
|
|
447
|
+
/** Trigger label when nothing is active. */
|
|
448
|
+
placeholder?: string;
|
|
449
|
+
/** Icon-only trigger — for a collapsed (56px) nav rail. */
|
|
450
|
+
collapsed?: boolean;
|
|
451
|
+
/** Dropdown placement (Radix). Defaults to bottom/start; Radix auto-flips. */
|
|
452
|
+
side?: "top" | "right" | "bottom" | "left";
|
|
453
|
+
align?: "start" | "center" | "end";
|
|
454
|
+
/** Extra classes merged onto the portaled DropdownMenuContent — e.g. a
|
|
455
|
+
* higher z-index so the dropdown clears a high-z overlay (a mobile
|
|
456
|
+
* drawer above the default z-50 content). */
|
|
457
|
+
contentClassName?: string;
|
|
458
|
+
}
|
|
459
|
+
declare function ContextSwitcher({ items, activeRefId, onSelect, footer, placeholder, collapsed, side, align, contentClassName, }: ContextSwitcherProps): react.JSX.Element;
|
|
460
|
+
|
|
461
|
+
export { type AppRungItem, AppSidebar, type AppSidebarBrand, type AppSidebarModuleDef, type AppSidebarProps, type AppSidebarTopItem, type Brand, BrandGlyph, type BrandGlyphProps, type ContextOrgGroup, ContextSwitcher, type ContextSwitcherItem, type ContextSwitcherProps, type FallbackModuleDef, Flyout, type FlyoutChild, type FlyoutProps, type GroupRungItem, type HeaderNavItem, REEVE_ACCENT, ResponsiveHeader, type ResponsiveHeaderProps, type RungItem, type SidebarLadder, type SidebarLadderParsed, type SidebarRung, type UseLadderRungOptions, deriveFallbackLadder, faviconUrl, groupByOrg, reconcileLadder, resolveLadder, useLadderRung, useResponsivePlaceholder, zSidebarLadder };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
|
-
import { ReactNode, RefObject } from 'react';
|
|
2
|
+
import { ReactNode, ElementType, RefObject } from 'react';
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -10,9 +10,11 @@ import { z } from 'zod';
|
|
|
10
10
|
* order:
|
|
11
11
|
* 1. `brand.favicon` — the site's own square icon,
|
|
12
12
|
* 2. `brand.logo` rendered `object-contain` (no squish),
|
|
13
|
-
* 3. a
|
|
13
|
+
* 3. a single-letter monogram in the brand colour + an accent dot
|
|
14
|
+
* (DEV-4539 — modeled on Reeve's own "R." canon; no boxed container).
|
|
14
15
|
* Each image load error advances to the next candidate.
|
|
15
16
|
*/
|
|
17
|
+
declare const REEVE_ACCENT = "#FF6B2B";
|
|
16
18
|
interface Brand {
|
|
17
19
|
/** Display name — drives the wordmark and the monogram fallback. */
|
|
18
20
|
name: string;
|
|
@@ -20,8 +22,17 @@ interface Brand {
|
|
|
20
22
|
favicon?: string | null;
|
|
21
23
|
/** Wider brand logo URL (second cascade step, rendered `object-contain`). */
|
|
22
24
|
logo?: string | null;
|
|
23
|
-
/** Override the deterministic monogram
|
|
25
|
+
/** Override the deterministic monogram letter colour. */
|
|
24
26
|
color?: string | null;
|
|
27
|
+
/**
|
|
28
|
+
* Accent-dot colour for the single-letter monogram fallback (DEV-4539) —
|
|
29
|
+
* the "family mark". Pass the brand DNA's own strong accent colour when
|
|
30
|
+
* one is known; falls back to Reeve's own accent dot (REEVE_ACCENT)
|
|
31
|
+
* otherwise, the same "part of the Reeve ecosystem" signal the backend
|
|
32
|
+
* logo generator resolves. `null`/omitted both mean "use the fallback" —
|
|
33
|
+
* this field becomes a brand-manifest-owned value once C2 lands.
|
|
34
|
+
*/
|
|
35
|
+
accentColor?: string | null;
|
|
25
36
|
}
|
|
26
37
|
/**
|
|
27
38
|
* Favicon URL for a brand's canonical domain. Returns null unless `domain`
|
|
@@ -53,6 +64,19 @@ interface ResponsiveHeaderProps {
|
|
|
53
64
|
search: ReactNode;
|
|
54
65
|
/** Secondary nav links — inline on desktop, in the menu on mobile. */
|
|
55
66
|
nav?: HeaderNavItem[];
|
|
67
|
+
/**
|
|
68
|
+
* Optional destination for the brand lockup. When set, the glyph + wordmark
|
|
69
|
+
* become a single clickable link to this href (restores the "click the logo
|
|
70
|
+
* to go home" affordance). When omitted, the lockup is a plain, non-link div.
|
|
71
|
+
*/
|
|
72
|
+
brandHref?: string;
|
|
73
|
+
/**
|
|
74
|
+
* Link renderer for the brand lockup and `nav` href items. Defaults to a bare
|
|
75
|
+
* `"a"` so the package stays framework-agnostic; consumers pass their router's
|
|
76
|
+
* link (e.g. `next/link`) for client-side navigation. Action-only nav items
|
|
77
|
+
* (no href) stay `<button>` regardless.
|
|
78
|
+
*/
|
|
79
|
+
linkComponent?: ElementType;
|
|
56
80
|
/** Auth control (avatar / sign-in) — secondary. */
|
|
57
81
|
auth?: ReactNode;
|
|
58
82
|
/** Theme toggle — secondary. */
|
|
@@ -70,9 +94,53 @@ interface ResponsiveHeaderProps {
|
|
|
70
94
|
* brand wordmark drops to glyph-only and the secondary controls fold into a
|
|
71
95
|
* hamburger disclosure. The search is never touched.
|
|
72
96
|
*
|
|
73
|
-
*
|
|
97
|
+
* CSS container-query collapse (DEV-4260): the wordmark<->glyph and
|
|
98
|
+
* inline-actions<->hamburger split is pure CSS (`@container` on the header +
|
|
99
|
+
* Tailwind v4 container-query variants), NOT JS/matchMedia. Both the desktop
|
|
100
|
+
* and mobile structures render in EVERY environment — SSR, first paint, and
|
|
101
|
+
* with JS fully disabled — so there's no desktop first-paint FOUC and no
|
|
102
|
+
* "no-JS users get hamburger-only" fallback (DEV-4248 follow-up).
|
|
103
|
+
*
|
|
104
|
+
* The split is pinned to `BREAKPOINTS.md` (768px) via an ARBITRARY container
|
|
105
|
+
* variant (`@min-[768px]:...`) rather than Tailwind's *named* `@md` container
|
|
106
|
+
* variant: Tailwind v4's default container scale (`@md` = 448px) is a
|
|
107
|
+
* different scale than the viewport breakpoint scale and does NOT match
|
|
108
|
+
* `BREAKPOINTS.md`. Because Tailwind's build-time scanner needs a literal
|
|
109
|
+
* class string (it greps compiled output, it doesn't evaluate JS), "768" is a
|
|
110
|
+
* hardcoded literal in the classNames below, not interpolated from
|
|
111
|
+
* `BREAKPOINTS.md` — `responsive-header.test.tsx`'s className-contract tests
|
|
112
|
+
* assert against `BREAKPOINTS.md` directly so drift between the token and the
|
|
113
|
+
* literal fails loudly.
|
|
114
|
+
*
|
|
115
|
+
* Consumers must add `@meetreeve/ui/dist` to their Tailwind `@source` scan
|
|
116
|
+
* (see this package's README) or these container-variant classes never make
|
|
117
|
+
* it into the consumer's compiled CSS — same requirement as AppSidebar's
|
|
118
|
+
* `md:flex` (DEV-4351).
|
|
119
|
+
*
|
|
120
|
+
* JS is used ONLY for two things: the hamburger disclosure's open/close
|
|
121
|
+
* state, and ONE ResizeObserver on the header itself (deliberately NOT a
|
|
122
|
+
* viewport matchMedia — the point of DEV-4260/DEV-4248 is that the collapse
|
|
123
|
+
* reacts to the header's own *container* width, e.g. a narrow sidebar on a
|
|
124
|
+
* wide viewport) that tears down an open mobile menu when the header's
|
|
125
|
+
* container regrows past the CSS split.
|
|
126
|
+
*/
|
|
127
|
+
declare function ResponsiveHeader({ brand, search, nav, brandHref, linkComponent, auth, themeToggle, menuLabel, className, }: ResponsiveHeaderProps): react.JSX.Element;
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Pick a viewport-appropriate placeholder string: the `desktop` copy at/above
|
|
131
|
+
* `md`, the shorter `mobile` copy below it (falling back to `desktop` when no
|
|
132
|
+
* mobile string is supplied). Fixes the "long search prompt clips in the
|
|
133
|
+
* 200px-floored mobile slot" class of bug without the consumer wiring up its
|
|
134
|
+
* own media query.
|
|
135
|
+
*
|
|
136
|
+
* Built on the package's guarded `useMediaQuery`, which returns `false` when
|
|
137
|
+
* `matchMedia` is unavailable — so SSR and the first client paint yield the
|
|
138
|
+
* MOBILE string. That mobile-first default is intentional: the short string
|
|
139
|
+
* never clips, and the value reconciles up to the desktop string on mount once
|
|
140
|
+
* the viewport is measured `>= md` (consistent with the DEV-4260 FOUC note; no
|
|
141
|
+
* hydration mismatch, since server and first client render both read `false`).
|
|
74
142
|
*/
|
|
75
|
-
declare function
|
|
143
|
+
declare function useResponsivePlaceholder(desktop: string, mobile?: string): string;
|
|
76
144
|
|
|
77
145
|
/**
|
|
78
146
|
* SidebarLadder — the per-tenant JSON artifact (DEV-4351) that pre-calculates
|
|
@@ -325,4 +393,69 @@ interface FlyoutProps {
|
|
|
325
393
|
*/
|
|
326
394
|
declare function Flyout({ label, icon, active, children, className }: FlyoutProps): react.JSX.Element;
|
|
327
395
|
|
|
328
|
-
|
|
396
|
+
/**
|
|
397
|
+
* ContextSwitcher (DEV-4408) — promoted out of reeve-frontend's
|
|
398
|
+
* src/components/context/context-switcher.tsx (DEV-2513), which was already
|
|
399
|
+
* fully headless (props in, no app-specific data fetching). One reusable,
|
|
400
|
+
* data-driven, org-grouped dropdown for switching the active "context" —
|
|
401
|
+
* whatever refId-addressable entity a host app's own thin wrapper maps onto
|
|
402
|
+
* `ContextSwitcherItem[]` (reeve-frontend's brand, reeve-tenant-frontend's
|
|
403
|
+
* tenant, or — once the spawned-company substrate lands — a company
|
|
404
|
+
* cockpit). Orgs are pure visual groupers — headers render ONLY for
|
|
405
|
+
* multi-org accounts; the common single-org case stays a flat list.
|
|
406
|
+
*
|
|
407
|
+
* The menu renders through Radix DropdownMenu so it portals out of a
|
|
408
|
+
* narrow/collapsible rail and stays on-screen — Radix handles collision
|
|
409
|
+
* flip/shift. `collapsed` swaps the trigger for an icon-only button that
|
|
410
|
+
* fits a 56px collapsed rail.
|
|
411
|
+
*/
|
|
412
|
+
interface ContextSwitcherItem {
|
|
413
|
+
/** Stable identity for this context — a brand id, tenant id, etc. */
|
|
414
|
+
refId: string;
|
|
415
|
+
name: string;
|
|
416
|
+
/**
|
|
417
|
+
* Pre-resolved favicon URL (BrandGlyph's favicon-first cascade). Any
|
|
418
|
+
* host-specific favicon rules (e.g. reeve-frontend's synthetic-domain
|
|
419
|
+
* guard around `faviconUrl`) must be applied by the host BEFORE mapping
|
|
420
|
+
* into this item — this package stays agnostic of any one host's domain
|
|
421
|
+
* conventions.
|
|
422
|
+
*/
|
|
423
|
+
favicon?: string | null;
|
|
424
|
+
/** Wider brand/tenant logo URL (BrandGlyph's second cascade step). */
|
|
425
|
+
logo?: string | null;
|
|
426
|
+
/** Override BrandGlyph's deterministic monogram letter colour. */
|
|
427
|
+
color?: string | null;
|
|
428
|
+
/** Override BrandGlyph's monogram accent-dot colour (DEV-4539). */
|
|
429
|
+
accentColor?: string | null;
|
|
430
|
+
orgId?: string | null;
|
|
431
|
+
orgName?: string | null;
|
|
432
|
+
}
|
|
433
|
+
interface ContextOrgGroup {
|
|
434
|
+
orgId: string | null;
|
|
435
|
+
orgName: string | null;
|
|
436
|
+
items: ContextSwitcherItem[];
|
|
437
|
+
}
|
|
438
|
+
/** Group contexts by owning org. Items sort by name; groups by org name. */
|
|
439
|
+
declare function groupByOrg(items: ContextSwitcherItem[]): ContextOrgGroup[];
|
|
440
|
+
interface ContextSwitcherProps {
|
|
441
|
+
items: ContextSwitcherItem[];
|
|
442
|
+
/** refId of the active context. */
|
|
443
|
+
activeRefId: string | null;
|
|
444
|
+
onSelect: (item: ContextSwitcherItem) => void;
|
|
445
|
+
/** Optional dropdown footer slot, e.g. an "Add brand" CTA. */
|
|
446
|
+
footer?: React.ReactNode;
|
|
447
|
+
/** Trigger label when nothing is active. */
|
|
448
|
+
placeholder?: string;
|
|
449
|
+
/** Icon-only trigger — for a collapsed (56px) nav rail. */
|
|
450
|
+
collapsed?: boolean;
|
|
451
|
+
/** Dropdown placement (Radix). Defaults to bottom/start; Radix auto-flips. */
|
|
452
|
+
side?: "top" | "right" | "bottom" | "left";
|
|
453
|
+
align?: "start" | "center" | "end";
|
|
454
|
+
/** Extra classes merged onto the portaled DropdownMenuContent — e.g. a
|
|
455
|
+
* higher z-index so the dropdown clears a high-z overlay (a mobile
|
|
456
|
+
* drawer above the default z-50 content). */
|
|
457
|
+
contentClassName?: string;
|
|
458
|
+
}
|
|
459
|
+
declare function ContextSwitcher({ items, activeRefId, onSelect, footer, placeholder, collapsed, side, align, contentClassName, }: ContextSwitcherProps): react.JSX.Element;
|
|
460
|
+
|
|
461
|
+
export { type AppRungItem, AppSidebar, type AppSidebarBrand, type AppSidebarModuleDef, type AppSidebarProps, type AppSidebarTopItem, type Brand, BrandGlyph, type BrandGlyphProps, type ContextOrgGroup, ContextSwitcher, type ContextSwitcherItem, type ContextSwitcherProps, type FallbackModuleDef, Flyout, type FlyoutChild, type FlyoutProps, type GroupRungItem, type HeaderNavItem, REEVE_ACCENT, ResponsiveHeader, type ResponsiveHeaderProps, type RungItem, type SidebarLadder, type SidebarLadderParsed, type SidebarRung, type UseLadderRungOptions, deriveFallbackLadder, faviconUrl, groupByOrg, reconcileLadder, resolveLadder, useLadderRung, useResponsivePlaceholder, zSidebarLadder };
|