@the-portland-company/shell 0.4.0 → 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/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.6.0 — 2026-07-01
4
+
5
+ ### Added (native chrome — all backward-compatible)
6
+
7
+ Chrome-parity upgrades to the `./native` module (`NavSidebar`/`NavTopBar`/`NavAppShell`) so the dependency-light chrome is a faithful twin of the main VRM app's Chakra chrome. No Chakra components touched.
8
+
9
+ - **`NavFooter`** gains optional `commitDate?: string` and `commitAuthor?: string`. The footer now renders the main app's two-row build block: row 1 is `[env chip] | {commit last-4} | v{version}` (env chip is a color-coded capsule via the extended `envChipClasses`), row 2 (only when date/author present) is `| {Mon D} | {firstName} |` in mono.
10
+ - **`NavOrg`** gains optional `id?: string | null`. When present, the "Organization" label row shows a right-aligned `...{last-8}` copy button that writes the full id to the clipboard (`navigator.clipboard.writeText`) with a brief "Copied" affordance.
11
+ - **`NavItem`** gains optional `onClick?: () => void` (`href` is now optional). onClick items render as a `<button>` styled identically to the link rows — used for a sidebar "Support" action.
12
+ - **`NavTopBarProps`** gains optional `actionsList?: NavTopBarAction[]` (new exported `NavTopBarAction` type). Each renders as a 32×32 circular icon button (20px icon) with an optional red count badge (`badge > 0`), `warning`/`danger` tones coloring the icon red, and `href` support (external links open in a new tab). Rendered before the existing `actions` ReactNode slot (kept for back-compat).
13
+ - **Nav hover gradient sweep**: inactive sidebar nav rows (links and buttons) animate a left→right `linear-gradient(90deg,#ea6a3a,#E63946)` width 0→100% over 300ms with white text on hover; active rows keep solid race-red. No layout shift.
14
+ - **Org pill default color** changed from dark slate `#2d2f36` to race red (`var(--color-race-red)`, `#E63946`), matching the main app's fallback.
15
+ - `envChipClasses` extended: production red, staging orange, local/dev green, unknown gray.
16
+
17
+ ### Tests
18
+
19
+ - Added `__tests__/native.test.tsx` covering `actionsList` badge/href rendering, org-id copy, onClick nav items, footer rows, and `envChipClasses` tones (6 tests; suite now 49).
20
+
3
21
  ## 0.3.5 — 2026-05-14
4
22
 
5
23
  ### Fixed
package/dist/native.cjs CHANGED
@@ -1,5 +1,6 @@
1
1
  'use strict';
2
2
 
3
+ var react = require('react');
3
4
  var jsxRuntime = require('react/jsx-runtime');
4
5
 
5
6
  // src/native/index.tsx
@@ -10,11 +11,15 @@ function isNavItemActive(pathname, href) {
10
11
  }
11
12
  function envChipClasses(label) {
12
13
  const l = label.toLowerCase();
13
- if (l === "production")
14
- return "bg-[rgba(230,57,70,0.12)] text-[color:var(--color-race-red)]";
15
- if (l === "staging") return "bg-[rgba(242,201,76,0.18)] text-[#a6791f]";
16
- return "bg-[rgba(0,166,81,0.14)] text-[color:var(--color-green-flag)]";
14
+ if (l === "production" || l === "prod")
15
+ return "bg-[#fee2e2] text-[#ef4444]";
16
+ if (l === "staging" || l === "stage")
17
+ return "bg-[#ffedd5] text-[#f97316]";
18
+ if (l === "local" || l === "development" || l === "dev")
19
+ return "bg-[#dcfce7] text-[#22c55e]";
20
+ return "bg-[rgba(107,114,128,0.16)] text-[#6b7280]";
17
21
  }
22
+ var ORG_PILL_DEFAULT = "var(--color-race-red)";
18
23
  function NavSidebar({
19
24
  pathname,
20
25
  brand,
@@ -27,102 +32,202 @@ function NavSidebar({
27
32
  const Link = linkComponent ?? DefaultLink;
28
33
  const activeFn = nav.isActive ?? isNavItemActive;
29
34
  const ModeIcon = mode?.icon;
30
- return /* @__PURE__ */ jsxRuntime.jsxs("aside", { className: "fixed inset-y-0 left-0 z-30 hidden w-[264px] flex-col border-r border-[color:var(--line)] bg-[color:var(--panel-elevated)] px-4 py-5 lg:flex", children: [
31
- /* @__PURE__ */ jsxRuntime.jsxs(Link, { href: brand.homeHref ?? "/", className: "flex items-center gap-3 px-1 py-1", children: [
32
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex h-9 w-9 items-center justify-center rounded-xl bg-[color:var(--color-race-red)] text-base font-black text-white shadow-sm", children: brand.badgeLabel }),
33
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex flex-col leading-tight", children: [
34
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-bold tracking-[-0.01em] text-[color:var(--foreground)]", children: brand.title }),
35
- brand.subtitle ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-[color:var(--muted)]", children: brand.subtitle }) : null
36
- ] })
37
- ] }),
38
- org ? /* @__PURE__ */ jsxRuntime.jsxs(
39
- "button",
35
+ const orgBg = org?.brandColor || ORG_PILL_DEFAULT;
36
+ return /* @__PURE__ */ jsxRuntime.jsxs("aside", { className: "fixed inset-y-0 left-0 z-30 hidden w-[240px] flex-col border-r border-[color:var(--line)] bg-[color:var(--panel-elevated)] lg:flex", children: [
37
+ /* @__PURE__ */ jsxRuntime.jsx(
38
+ Link,
40
39
  {
41
- type: "button",
42
- onClick: org.onClick,
43
- className: "mt-5 flex w-full items-center justify-between rounded-xl border border-[color:var(--line)] bg-[color:var(--panel)] px-3 py-2.5 text-left transition hover:border-[color:var(--color-race-red)]/40",
44
- children: [
45
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-2.5 overflow-hidden", children: [
46
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-lg bg-[color:var(--accent-muted)] text-xs font-bold text-[color:var(--color-race-red)]", children: (org.name ?? "Org").slice(0, 2).toUpperCase() }),
47
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-sm font-semibold text-[color:var(--foreground)]", children: org.name ?? "Account" })
48
- ] }),
49
- /* @__PURE__ */ jsxRuntime.jsx(ChevronDownUp, {})
50
- ]
40
+ href: brand.homeHref ?? "/",
41
+ className: "flex h-[64px] flex-shrink-0 items-center gap-2.5 px-6",
42
+ children: brand.logoSrc ? /* @__PURE__ */ jsxRuntime.jsx(
43
+ "img",
44
+ {
45
+ src: brand.logoSrc,
46
+ alt: brand.logoAlt ?? brand.title,
47
+ draggable: false,
48
+ className: "h-10 w-auto object-contain"
49
+ }
50
+ ) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
51
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex h-9 w-9 items-center justify-center rounded-xl bg-[color:var(--color-race-red)] text-base font-black text-white shadow-sm", children: brand.badgeLabel }),
52
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex flex-col leading-tight", children: [
53
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-base font-extrabold tracking-[-0.01em] text-[color:var(--color-race-red)]", children: brand.title }),
54
+ brand.subtitle ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-[color:var(--muted)]", children: brand.subtitle }) : null
55
+ ] })
56
+ ] })
51
57
  }
52
- ) : null,
53
- mode ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-2.5 flex items-center gap-2.5 rounded-xl bg-[color:var(--color-race-red)] px-3 py-2.5 text-white shadow-sm", children: [
54
- ModeIcon ? /* @__PURE__ */ jsxRuntime.jsx(ModeIcon, { size: 16, className: "shrink-0" }) : null,
55
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-semibold tracking-[-0.01em]", children: mode.label })
56
- ] }) : null,
57
- /* @__PURE__ */ jsxRuntime.jsxs("nav", { className: "mt-5 flex flex-1 flex-col gap-1 overflow-y-auto", children: [
58
- nav.groupLabel ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "px-3 pb-1 text-[11px] font-semibold uppercase tracking-[0.16em] text-[color:var(--muted)]", children: nav.groupLabel }) : null,
59
- nav.items.map((item) => {
60
- const active = activeFn(pathname, item.href);
61
- const Icon = item.icon;
62
- return /* @__PURE__ */ jsxRuntime.jsxs(
63
- Link,
58
+ ),
59
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-1 flex-col overflow-y-auto px-4 pb-2", children: [
60
+ org ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-3", children: [
61
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-2 flex items-center justify-between px-1", children: [
62
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[11px] font-semibold uppercase tracking-[0.08em] text-[color:var(--muted)]", children: "Organization" }),
63
+ org.id ? /* @__PURE__ */ jsxRuntime.jsx(OrgIdCopyButton, { id: org.id }) : null
64
+ ] }),
65
+ /* @__PURE__ */ jsxRuntime.jsxs(
66
+ "button",
64
67
  {
65
- href: item.href,
66
- "aria-current": active ? "page" : void 0,
67
- className: [
68
- "group flex items-center gap-3 rounded-xl px-3 py-2.5 text-sm font-semibold transition",
69
- active ? "bg-[color:var(--accent-muted)] text-[color:var(--color-race-red)]" : "text-[color:var(--muted)] hover:bg-[color:var(--panel)] hover:text-[color:var(--foreground)]"
70
- ].join(" "),
68
+ type: "button",
69
+ onClick: org.onClick,
70
+ style: { backgroundColor: orgBg },
71
+ className: "flex w-full items-center justify-between gap-2 rounded-md px-2.5 py-2 text-left text-white shadow-sm transition hover:opacity-95",
71
72
  children: [
72
- Icon ? /* @__PURE__ */ jsxRuntime.jsx(
73
- Icon,
74
- {
75
- size: 18,
76
- className: active ? "text-[color:var(--color-race-red)]" : "text-[color:var(--muted)] group-hover:text-[color:var(--foreground)]"
77
- }
78
- ) : null,
79
- item.label
73
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex min-w-0 items-center gap-2.5", children: [
74
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex h-7 w-7 shrink-0 items-center justify-center overflow-hidden rounded-full bg-white/20 text-[11px] font-bold text-white", children: org.logoUrl ? /* @__PURE__ */ jsxRuntime.jsx("img", { src: org.logoUrl, alt: "", className: "h-full w-full object-cover" }) : (org.name ?? "Org").slice(0, 2).toUpperCase() }),
75
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-sm font-semibold", children: org.name ?? "No organizations" })
76
+ ] }),
77
+ /* @__PURE__ */ jsxRuntime.jsx(ChevronDown, {})
80
78
  ]
81
- },
82
- item.href
83
- );
84
- })
79
+ }
80
+ )
81
+ ] }) : null,
82
+ mode ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-4", children: /* @__PURE__ */ jsxRuntime.jsxs(
83
+ "button",
84
+ {
85
+ type: "button",
86
+ className: "flex w-full items-center justify-between gap-2 rounded-md bg-[color:var(--color-race-red)] px-2.5 py-2 text-white shadow-sm",
87
+ children: [
88
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-2.5", children: [
89
+ ModeIcon ? /* @__PURE__ */ jsxRuntime.jsx(ModeIcon, { size: 16, className: "shrink-0" }) : null,
90
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-semibold tracking-[-0.01em]", children: mode.label })
91
+ ] }),
92
+ /* @__PURE__ */ jsxRuntime.jsx(ChevronDown, {})
93
+ ]
94
+ }
95
+ ) }) : null,
96
+ /* @__PURE__ */ jsxRuntime.jsxs("nav", { className: "flex flex-col gap-1", children: [
97
+ nav.groupLabel ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "px-2 pb-1 text-[11px] font-semibold uppercase tracking-[0.08em] text-[color:var(--muted)]", children: nav.groupLabel }) : null,
98
+ nav.items.map((item, idx) => {
99
+ const active = item.href ? activeFn(pathname, item.href) : false;
100
+ return /* @__PURE__ */ jsxRuntime.jsx(
101
+ NavRow,
102
+ {
103
+ item,
104
+ active,
105
+ Link
106
+ },
107
+ item.href ?? `nav-${idx}-${item.label}`
108
+ );
109
+ })
110
+ ] })
85
111
  ] }),
86
- footer ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-4 border-t border-[color:var(--line)] pt-3", children: [
87
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
112
+ footer ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-shrink-0 border-t border-[color:var(--line)] px-4 py-3", children: [
113
+ footer.links && footer.links.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap items-center gap-1.5 text-xs", children: footer.links.map((l, idx) => /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex items-center gap-1.5", children: [
114
+ idx > 0 ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[color:var(--muted)]", children: "|" }) : null,
88
115
  /* @__PURE__ */ jsxRuntime.jsx(
89
- "span",
116
+ "a",
90
117
  {
91
- className: `rounded-full px-2 py-0.5 text-[10px] font-bold uppercase tracking-[0.12em] ${envChipClasses(
92
- footer.envLabel
93
- )}`,
94
- children: footer.envLabel
118
+ href: l.href,
119
+ className: "font-medium text-[color:var(--color-race-red)] hover:underline",
120
+ children: l.label
95
121
  }
96
- ),
97
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "font-[family-name:var(--font-plex-mono)] text-[10px] text-[color:var(--muted)]", children: [
98
- "v",
99
- footer.version,
100
- " \xB7 ",
101
- footer.commit.slice(0, 7)
102
- ] })
103
- ] }),
104
- footer.links && footer.links.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-2 flex gap-3 text-[11px] font-medium text-[color:var(--muted)]", children: footer.links.map((l) => /* @__PURE__ */ jsxRuntime.jsx("a", { href: l.href, className: "hover:text-[color:var(--foreground)]", children: l.label }, l.label)) }) : null
122
+ )
123
+ ] }, l.label)) }) : null,
124
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-1.5 flex flex-col items-center gap-0.5 font-[family-name:var(--font-plex-mono)] text-[10px] text-[color:var(--muted)]", children: [
125
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [
126
+ /* @__PURE__ */ jsxRuntime.jsx(
127
+ "span",
128
+ {
129
+ className: [
130
+ "rounded-md px-1.5 py-0.5 text-[10px] font-semibold",
131
+ envChipClasses(footer.envLabel)
132
+ ].join(" "),
133
+ children: footer.envLabel
134
+ }
135
+ ),
136
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[color:var(--muted)]", children: "|" }),
137
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: footer.commit.slice(-4) }),
138
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[color:var(--muted)]", children: "|" }),
139
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
140
+ "v",
141
+ footer.version
142
+ ] })
143
+ ] }),
144
+ footer.commitDate || footer.commitAuthor ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [
145
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[color:var(--muted)]", children: "|" }),
146
+ footer.commitDate ? /* @__PURE__ */ jsxRuntime.jsx("span", { children: new Date(footer.commitDate).toLocaleDateString(void 0, {
147
+ month: "short",
148
+ day: "numeric"
149
+ }) }) : null,
150
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[color:var(--muted)]", children: "|" }),
151
+ footer.commitAuthor ? /* @__PURE__ */ jsxRuntime.jsx("span", { children: footer.commitAuthor.split(/\s+/)[0] }) : null,
152
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[color:var(--muted)]", children: "|" })
153
+ ] }) : null
154
+ ] })
105
155
  ] }) : null
106
156
  ] });
107
157
  }
108
- function ChevronDownUp() {
109
- return /* @__PURE__ */ jsxRuntime.jsxs(
158
+ function ChevronDown() {
159
+ return /* @__PURE__ */ jsxRuntime.jsx(
110
160
  "svg",
111
161
  {
112
- width: 15,
113
- height: 15,
162
+ width: 16,
163
+ height: 16,
114
164
  viewBox: "0 0 24 24",
115
165
  fill: "none",
116
166
  stroke: "currentColor",
117
167
  strokeWidth: 2,
118
168
  strokeLinecap: "round",
119
169
  strokeLinejoin: "round",
120
- className: "shrink-0 text-[color:var(--muted)]",
170
+ className: "shrink-0 opacity-80",
121
171
  "aria-hidden": "true",
122
- children: [
123
- /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m7 15 5 5 5-5" }),
124
- /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m7 9 5-5 5 5" })
125
- ]
172
+ children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m6 9 6 6 6-6" })
173
+ }
174
+ );
175
+ }
176
+ var NAV_HOVER_GRADIENT = "linear-gradient(90deg,#ea6a3a 0%,#E63946 100%)";
177
+ function NavRow({
178
+ item,
179
+ active,
180
+ Link
181
+ }) {
182
+ const Icon = item.icon;
183
+ const wrapperClasses = [
184
+ "group relative block overflow-hidden rounded-md transition",
185
+ active ? "bg-[color:var(--color-race-red)] text-white shadow-sm" : "text-[color:var(--muted)] hover:text-white"
186
+ ].join(" ");
187
+ const inner = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
188
+ !active ? /* @__PURE__ */ jsxRuntime.jsx(
189
+ "span",
190
+ {
191
+ "aria-hidden": "true",
192
+ className: "pointer-events-none absolute inset-y-0 left-0 w-0 transition-[width] duration-300 group-hover:w-full",
193
+ style: { background: NAV_HOVER_GRADIENT }
194
+ }
195
+ ) : null,
196
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "relative z-10 flex items-center gap-3 px-3 py-2 text-sm font-semibold", children: [
197
+ Icon ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { size: 18, className: active ? "text-white" : "text-current" }) : null,
198
+ item.label
199
+ ] })
200
+ ] });
201
+ if (item.onClick && !item.href) {
202
+ return /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: item.onClick, className: `w-full text-left ${wrapperClasses}`, children: inner });
203
+ }
204
+ return /* @__PURE__ */ jsxRuntime.jsx(
205
+ Link,
206
+ {
207
+ href: item.href ?? "#",
208
+ "aria-current": active ? "page" : void 0,
209
+ className: wrapperClasses,
210
+ children: inner
211
+ }
212
+ );
213
+ }
214
+ function OrgIdCopyButton({ id }) {
215
+ const [copied, setCopied] = react.useState(false);
216
+ return /* @__PURE__ */ jsxRuntime.jsx(
217
+ "button",
218
+ {
219
+ type: "button",
220
+ onClick: () => {
221
+ try {
222
+ void navigator.clipboard?.writeText(id);
223
+ } catch {
224
+ }
225
+ setCopied(true);
226
+ setTimeout(() => setCopied(false), 1500);
227
+ },
228
+ title: "Copy organization id",
229
+ className: "rounded px-1 text-[11px] text-[color:var(--muted)] hover:bg-[color:var(--panel)]",
230
+ children: copied ? "Copied" : `...${id.slice(-8)}`
126
231
  }
127
232
  );
128
233
  }
@@ -131,16 +236,18 @@ function NavTopBar({
131
236
  subtitle,
132
237
  user,
133
238
  onSignOut,
239
+ actionsList,
134
240
  actions,
135
241
  themeToggle
136
242
  }) {
137
243
  const initials = (user?.name ?? "Politogy").slice(0, 2).toUpperCase();
138
- return /* @__PURE__ */ jsxRuntime.jsxs("header", { className: "sticky top-0 z-20 flex items-center justify-between gap-4 border-b border-[color:var(--line)] bg-[color:var(--panel)] px-6 py-3 backdrop-blur-xl", children: [
244
+ const secondary = user?.email || [user?.tier, user?.role].filter(Boolean).join(" \xB7 ") || "member";
245
+ return /* @__PURE__ */ jsxRuntime.jsxs("header", { className: "sticky top-0 z-20 flex h-20 items-center justify-between gap-4 border-b border-[color:var(--line)] bg-[color:var(--panel)] px-4 backdrop-blur-xl", children: [
139
246
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0", children: [
140
247
  /* @__PURE__ */ jsxRuntime.jsx(
141
248
  "h1",
142
249
  {
143
- className: "truncate text-xl font-semibold tracking-[-0.02em] text-[color:var(--foreground)]",
250
+ className: "truncate text-lg font-bold tracking-[-0.01em] text-[color:var(--foreground)]",
144
251
  "data-display": "true",
145
252
  children: title
146
253
  }
@@ -148,24 +255,30 @@ function NavTopBar({
148
255
  subtitle ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate text-sm text-[color:var(--muted)]", children: subtitle }) : null
149
256
  ] }),
150
257
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [
258
+ actionsList && actionsList.length > 0 ? actionsList.map((action) => /* @__PURE__ */ jsxRuntime.jsx(NavTopBarActionButton, { action }, action.key)) : null,
151
259
  actions,
152
260
  themeToggle,
153
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2.5 rounded-full border border-[color:var(--line)] bg-[color:var(--panel-elevated)] py-1 pl-1 pr-2.5", children: [
154
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex h-8 w-8 items-center justify-center rounded-full bg-[color:var(--color-race-red)] text-xs font-bold text-white", children: initials }),
155
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "hidden flex-col leading-tight md:flex", children: [
156
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-semibold text-[color:var(--foreground)]", children: user?.name ?? "Signed in" }),
157
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] font-medium uppercase tracking-[0.08em] text-[color:var(--muted)]", children: [user?.tier, user?.role].filter(Boolean).join(" \xB7 ") || "member" })
261
+ /* @__PURE__ */ jsxRuntime.jsxs("details", { className: "group relative", children: [
262
+ /* @__PURE__ */ jsxRuntime.jsxs("summary", { className: "flex cursor-pointer list-none items-center gap-2.5 rounded-full py-1 pl-1 pr-2 transition hover:bg-[color:var(--panel-elevated)] [&::-webkit-details-marker]:hidden", children: [
263
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex h-9 w-9 items-center justify-center overflow-hidden rounded-full bg-[color:var(--color-race-red)] text-xs font-bold text-white", children: user?.avatarUrl ? /* @__PURE__ */ jsxRuntime.jsx("img", { src: user.avatarUrl, alt: "", className: "h-full w-full object-cover" }) : initials }),
264
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "hidden flex-col leading-tight md:flex", children: [
265
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-semibold text-[color:var(--foreground)]", children: user?.name ?? "Signed in" }),
266
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-[color:var(--muted)]", children: secondary })
267
+ ] }),
268
+ /* @__PURE__ */ jsxRuntime.jsx(ChevronDown, {})
158
269
  ] }),
159
- onSignOut ? /* @__PURE__ */ jsxRuntime.jsx(
270
+ onSignOut ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute right-0 z-30 mt-2 w-44 overflow-hidden rounded-xl border border-[color:var(--line)] bg-[color:var(--panel-elevated)] py-1 shadow-[var(--shadow-soft)]", children: /* @__PURE__ */ jsxRuntime.jsxs(
160
271
  "button",
161
272
  {
162
273
  type: "button",
163
274
  onClick: onSignOut,
164
- "aria-label": "Sign out",
165
- className: "ml-1 flex h-7 w-7 items-center justify-center rounded-full text-[color:var(--muted)] hover:text-[color:var(--color-race-red)]",
166
- children: /* @__PURE__ */ jsxRuntime.jsx(SignOutIcon, {})
275
+ className: "flex w-full items-center gap-2.5 px-4 py-2 text-left text-sm font-medium text-[color:var(--foreground)] hover:bg-[color:var(--panel)] hover:text-[color:var(--color-race-red)]",
276
+ children: [
277
+ /* @__PURE__ */ jsxRuntime.jsx(SignOutIcon, {}),
278
+ "Sign out"
279
+ ]
167
280
  }
168
- ) : null
281
+ ) }) : null
169
282
  ] })
170
283
  ] })
171
284
  ] });
@@ -191,10 +304,48 @@ function SignOutIcon() {
191
304
  }
192
305
  );
193
306
  }
307
+ function NavTopBarActionButton({ action }) {
308
+ const Icon = action.icon;
309
+ const tone = action.tone ?? "default";
310
+ const toneClass = tone === "danger" || tone === "warning" ? "text-[color:var(--color-race-red)] hover:text-[color:var(--color-race-red)]" : "text-[color:var(--muted)] hover:text-[color:var(--foreground)]";
311
+ const className = [
312
+ "relative flex h-8 w-8 items-center justify-center rounded-full border border-[color:var(--line)] transition",
313
+ toneClass
314
+ ].join(" ");
315
+ const content = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
316
+ /* @__PURE__ */ jsxRuntime.jsx(Icon, { size: 20, className: "text-current" }),
317
+ typeof action.badge === "number" && action.badge > 0 ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute -top-1 -right-1 flex h-4 min-w-4 items-center justify-center rounded-full bg-[color:var(--color-race-red)] px-1 text-[10px] text-white", children: action.badge }) : null
318
+ ] });
319
+ const isExternal = action.href ? /^https?:\/\//.test(action.href) : false;
320
+ if (action.href) {
321
+ return /* @__PURE__ */ jsxRuntime.jsx(
322
+ "a",
323
+ {
324
+ href: action.href,
325
+ "aria-label": action.label,
326
+ title: action.label,
327
+ className,
328
+ ...isExternal ? { target: "_blank", rel: "noreferrer" } : {},
329
+ children: content
330
+ }
331
+ );
332
+ }
333
+ return /* @__PURE__ */ jsxRuntime.jsx(
334
+ "button",
335
+ {
336
+ type: "button",
337
+ onClick: action.onClick,
338
+ "aria-label": action.label,
339
+ title: action.label,
340
+ className,
341
+ children: content
342
+ }
343
+ );
344
+ }
194
345
  function NavAppShell({ sidebar, topBar, children }) {
195
346
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-h-screen bg-[color:var(--background)] text-[color:var(--foreground)]", children: [
196
347
  /* @__PURE__ */ jsxRuntime.jsx(NavSidebar, { ...sidebar }),
197
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lg:pl-[264px]", children: [
348
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lg:pl-[240px]", children: [
198
349
  /* @__PURE__ */ jsxRuntime.jsx(NavTopBar, { ...topBar }),
199
350
  /* @__PURE__ */ jsxRuntime.jsx("main", { className: "px-6 py-7", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-auto max-w-7xl", children }) })
200
351
  ] })
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/native/index.tsx"],"names":["jsx","jsxs"],"mappings":"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","file":"native.cjs","sourcesContent":["// ---------------------------------------------------------------------------\n// Framework-agnostic chrome (sidebar + top navbar) for @the-portland-company/shell.\n//\n// The other chrome in this package (ShellChrome, Sidebar, Header, …) is built\n// on Chakra UI v2 + react-router and targets Vite SPAs. That stack cannot be\n// imported into a Next.js / React 19 / Tailwind app. This \"native\" module is a\n// dependency-light alternative: pure React (no Chakra, no emotion, no\n// react-router, no icon library) whose styling is driven entirely by the CSS\n// design tokens shipped in `@the-portland-company/shell/chrome.css`.\n//\n// Every app concern (nav items, org, mode, user, links, icons, the routing\n// Link component, the theme toggle) is injected via props, so both the Contacts\n// app (Next.js) and the VRM app could consume the same components.\n// ---------------------------------------------------------------------------\n\nimport type { ComponentType, ReactNode } from 'react'\n\n// An icon is any component that accepts an optional size + className. This\n// matches both `lucide-react` and `react-icons` icon signatures, so consuming\n// apps pass their own icon set without this package depending on one.\nexport type NavIcon = ComponentType<{ size?: number; className?: string }>\n\nexport type NavItem = {\n href: string\n label: string\n icon?: NavIcon\n}\n\n// The routing primitive is injected so each app supplies its own (Next.js\n// `<Link>`, react-router `<Link>`, or a plain `<a>`). Defaults to an anchor.\nexport type NavLinkProps = {\n href: string\n className?: string\n 'aria-current'?: 'page' | undefined\n children: ReactNode\n}\nexport type NavLinkComponent = ComponentType<NavLinkProps>\n\nconst DefaultLink: NavLinkComponent = ({ href, children, ...rest }) => (\n <a href={href} {...rest}>\n {children}\n </a>\n)\n\n/** Default active-route test: exact match for \"/\", prefix match otherwise. */\nexport function isNavItemActive(pathname: string, href: string): boolean {\n if (href === '/') return pathname === '/'\n return pathname === href || pathname.startsWith(`${href}/`)\n}\n\n/** Token-driven tone classes for the sidebar environment chip. */\nexport function envChipClasses(label: string): string {\n const l = label.toLowerCase()\n if (l === 'production')\n return 'bg-[rgba(230,57,70,0.12)] text-[color:var(--color-race-red)]'\n if (l === 'staging') return 'bg-[rgba(242,201,76,0.18)] text-[#a6791f]'\n return 'bg-[rgba(0,166,81,0.14)] text-[color:var(--color-green-flag)]'\n}\n\n// ---------------------------------------------------------------------------\n\nexport type NavBrand = {\n /** Where the logo links to. Defaults to \"/\". */\n homeHref?: string\n /** Short label shown inside the brand badge (e.g. \"P\"). */\n badgeLabel: string\n title: string\n subtitle?: string\n}\n\nexport type NavOrg = {\n name: string | null\n onClick?: () => void\n}\n\nexport type NavMode = {\n label: string\n icon?: NavIcon\n}\n\nexport type NavFooterLink = { label: string; href: string }\n\nexport type NavFooter = {\n envLabel: string\n version: string\n commit: string\n links?: NavFooterLink[]\n}\n\nexport type NavSidebarProps = {\n pathname: string\n brand: NavBrand\n nav: {\n groupLabel?: string\n items: NavItem[]\n isActive?: (pathname: string, href: string) => boolean\n }\n org?: NavOrg | null\n mode?: NavMode | null\n footer?: NavFooter | null\n linkComponent?: NavLinkComponent\n}\n\nexport function NavSidebar({\n pathname,\n brand,\n nav,\n org,\n mode,\n footer,\n linkComponent,\n}: NavSidebarProps) {\n const Link = linkComponent ?? DefaultLink\n const activeFn = nav.isActive ?? isNavItemActive\n const ModeIcon = mode?.icon\n\n return (\n <aside className=\"fixed inset-y-0 left-0 z-30 hidden w-[264px] flex-col border-r border-[color:var(--line)] bg-[color:var(--panel-elevated)] px-4 py-5 lg:flex\">\n {/* Brand */}\n <Link href={brand.homeHref ?? '/'} className=\"flex items-center gap-3 px-1 py-1\">\n <span className=\"flex h-9 w-9 items-center justify-center rounded-xl bg-[color:var(--color-race-red)] text-base font-black text-white shadow-sm\">\n {brand.badgeLabel}\n </span>\n <span className=\"flex flex-col leading-tight\">\n <span className=\"text-sm font-bold tracking-[-0.01em] text-[color:var(--foreground)]\">\n {brand.title}\n </span>\n {brand.subtitle ? (\n <span className=\"text-[11px] font-semibold uppercase tracking-[0.16em] text-[color:var(--muted)]\">\n {brand.subtitle}\n </span>\n ) : null}\n </span>\n </Link>\n\n {/* Organization switcher pill */}\n {org ? (\n <button\n type=\"button\"\n onClick={org.onClick}\n className=\"mt-5 flex w-full items-center justify-between rounded-xl border border-[color:var(--line)] bg-[color:var(--panel)] px-3 py-2.5 text-left transition hover:border-[color:var(--color-race-red)]/40\"\n >\n <span className=\"flex items-center gap-2.5 overflow-hidden\">\n <span className=\"flex h-7 w-7 shrink-0 items-center justify-center rounded-lg bg-[color:var(--accent-muted)] text-xs font-bold text-[color:var(--color-race-red)]\">\n {(org.name ?? 'Org').slice(0, 2).toUpperCase()}\n </span>\n <span className=\"truncate text-sm font-semibold text-[color:var(--foreground)]\">\n {org.name ?? 'Account'}\n </span>\n </span>\n <ChevronDownUp />\n </button>\n ) : null}\n\n {/* Mode pill */}\n {mode ? (\n <div className=\"mt-2.5 flex items-center gap-2.5 rounded-xl bg-[color:var(--color-race-red)] px-3 py-2.5 text-white shadow-sm\">\n {ModeIcon ? <ModeIcon size={16} className=\"shrink-0\" /> : null}\n <span className=\"text-sm font-semibold tracking-[-0.01em]\">{mode.label}</span>\n </div>\n ) : null}\n\n {/* Nav list */}\n <nav className=\"mt-5 flex flex-1 flex-col gap-1 overflow-y-auto\">\n {nav.groupLabel ? (\n <p className=\"px-3 pb-1 text-[11px] font-semibold uppercase tracking-[0.16em] text-[color:var(--muted)]\">\n {nav.groupLabel}\n </p>\n ) : null}\n {nav.items.map((item) => {\n const active = activeFn(pathname, item.href)\n const Icon = item.icon\n return (\n <Link\n key={item.href}\n href={item.href}\n aria-current={active ? 'page' : undefined}\n className={[\n 'group flex items-center gap-3 rounded-xl px-3 py-2.5 text-sm font-semibold transition',\n active\n ? 'bg-[color:var(--accent-muted)] text-[color:var(--color-race-red)]'\n : 'text-[color:var(--muted)] hover:bg-[color:var(--panel)] hover:text-[color:var(--foreground)]',\n ].join(' ')}\n >\n {Icon ? (\n <Icon\n size={18}\n className={\n active\n ? 'text-[color:var(--color-race-red)]'\n : 'text-[color:var(--muted)] group-hover:text-[color:var(--foreground)]'\n }\n />\n ) : null}\n {item.label}\n </Link>\n )\n })}\n </nav>\n\n {/* Footer — env chip + commit/version */}\n {footer ? (\n <div className=\"mt-4 border-t border-[color:var(--line)] pt-3\">\n <div className=\"flex items-center justify-between\">\n <span\n className={`rounded-full px-2 py-0.5 text-[10px] font-bold uppercase tracking-[0.12em] ${envChipClasses(\n footer.envLabel,\n )}`}\n >\n {footer.envLabel}\n </span>\n <span className=\"font-[family-name:var(--font-plex-mono)] text-[10px] text-[color:var(--muted)]\">\n v{footer.version} · {footer.commit.slice(0, 7)}\n </span>\n </div>\n {footer.links && footer.links.length > 0 ? (\n <div className=\"mt-2 flex gap-3 text-[11px] font-medium text-[color:var(--muted)]\">\n {footer.links.map((l) => (\n <a key={l.label} href={l.href} className=\"hover:text-[color:var(--foreground)]\">\n {l.label}\n </a>\n ))}\n </div>\n ) : null}\n </div>\n ) : null}\n </aside>\n )\n}\n\n// Small inline chevron so this module needs no icon dependency. Mirrors the\n// lucide `ChevronsUpDown` glyph used by the org pill.\nfunction ChevronDownUp() {\n return (\n <svg\n width={15}\n height={15}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={2}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className=\"shrink-0 text-[color:var(--muted)]\"\n aria-hidden=\"true\"\n >\n <path d=\"m7 15 5 5 5-5\" />\n <path d=\"m7 9 5-5 5 5\" />\n </svg>\n )\n}\n\n// ---------------------------------------------------------------------------\n\nexport type NavUser = {\n name: string | null\n tier?: string | null\n role?: string | null\n}\n\nexport type NavTopBarProps = {\n title: string\n subtitle?: string\n user?: NavUser | null\n onSignOut?: () => void\n /** App-specific action cluster rendered left of the theme toggle. */\n actions?: ReactNode\n /** Theme toggle slot (injected so this package needs no theme dependency). */\n themeToggle?: ReactNode\n}\n\nexport function NavTopBar({\n title,\n subtitle,\n user,\n onSignOut,\n actions,\n themeToggle,\n}: NavTopBarProps) {\n const initials = (user?.name ?? 'Politogy').slice(0, 2).toUpperCase()\n return (\n <header className=\"sticky top-0 z-20 flex items-center justify-between gap-4 border-b border-[color:var(--line)] bg-[color:var(--panel)] px-6 py-3 backdrop-blur-xl\">\n <div className=\"min-w-0\">\n <h1\n className=\"truncate text-xl font-semibold tracking-[-0.02em] text-[color:var(--foreground)]\"\n data-display=\"true\"\n >\n {title}\n </h1>\n {subtitle ? (\n <p className=\"truncate text-sm text-[color:var(--muted)]\">{subtitle}</p>\n ) : null}\n </div>\n\n <div className=\"flex shrink-0 items-center gap-2\">\n {actions}\n {themeToggle}\n\n {/* User menu */}\n <div className=\"flex items-center gap-2.5 rounded-full border border-[color:var(--line)] bg-[color:var(--panel-elevated)] py-1 pl-1 pr-2.5\">\n <span className=\"flex h-8 w-8 items-center justify-center rounded-full bg-[color:var(--color-race-red)] text-xs font-bold text-white\">\n {initials}\n </span>\n <span className=\"hidden flex-col leading-tight md:flex\">\n <span className=\"text-xs font-semibold text-[color:var(--foreground)]\">\n {user?.name ?? 'Signed in'}\n </span>\n <span className=\"text-[10px] font-medium uppercase tracking-[0.08em] text-[color:var(--muted)]\">\n {[user?.tier, user?.role].filter(Boolean).join(' · ') || 'member'}\n </span>\n </span>\n {onSignOut ? (\n <button\n type=\"button\"\n onClick={onSignOut}\n aria-label=\"Sign out\"\n className=\"ml-1 flex h-7 w-7 items-center justify-center rounded-full text-[color:var(--muted)] hover:text-[color:var(--color-race-red)]\"\n >\n <SignOutIcon />\n </button>\n ) : null}\n </div>\n </div>\n </header>\n )\n}\n\n// Inline log-out glyph (mirrors lucide `LogOut`) to avoid an icon dependency.\nfunction SignOutIcon() {\n return (\n <svg\n width={15}\n height={15}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={2}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4\" />\n <polyline points=\"16 17 21 12 16 7\" />\n <line x1=\"21\" x2=\"9\" y1=\"12\" y2=\"12\" />\n </svg>\n )\n}\n\n// ---------------------------------------------------------------------------\n\nexport type NavAppShellProps = {\n sidebar: NavSidebarProps\n topBar: NavTopBarProps\n children: ReactNode\n}\n\n/**\n * Full chrome: branded left sidebar + sticky top navbar + scrollable content\n * region. Presentational only — wrap it in app-level auth/gating as needed.\n */\nexport function NavAppShell({ sidebar, topBar, children }: NavAppShellProps) {\n return (\n <div className=\"min-h-screen bg-[color:var(--background)] text-[color:var(--foreground)]\">\n <NavSidebar {...sidebar} />\n <div className=\"lg:pl-[264px]\">\n <NavTopBar {...topBar} />\n <main className=\"px-6 py-7\">\n <div className=\"mx-auto max-w-7xl\">{children}</div>\n </main>\n </div>\n </div>\n )\n}\n"]}
1
+ {"version":3,"sources":["../src/native/index.tsx"],"names":["jsx","jsxs","Fragment","useState"],"mappings":"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","file":"native.cjs","sourcesContent":["// ---------------------------------------------------------------------------\n// Framework-agnostic chrome (sidebar + top navbar) for @the-portland-company/shell.\n//\n// The other chrome in this package (ShellChrome, Sidebar, Header, …) is built\n// on Chakra UI v2 + react-router and targets Vite SPAs. That stack cannot be\n// imported into a Next.js / React 19 / Tailwind app. This \"native\" module is a\n// dependency-light alternative: pure React (no Chakra, no emotion, no\n// react-router, no icon library) whose styling is driven entirely by the CSS\n// design tokens shipped in `@the-portland-company/shell/chrome.css`.\n//\n// It is a FAITHFUL VISUAL TWIN of the Chakra chrome (Sidebar.tsx / Header.tsx /\n// OrgPill / ModePill / NavItem / UserMenu): same 240px sidebar, brand-logo\n// slot, \"ORGANIZATION\" org pill, mode pill, solid active nav rows, pipe-\n// separated footer links + commit string, and an avatar+name+email+chevron user\n// menu — so an app rendering this looks identical to the main VRM app.\n//\n// Every app concern (nav items, org, mode, user, links, icons, the routing\n// Link component, the theme toggle) is injected via props, so both the Contacts\n// app (Next.js) and the VRM app could consume the same components.\n// ---------------------------------------------------------------------------\n\nimport type { ComponentType, ReactNode } from 'react'\nimport { useState } from 'react'\n\n// An icon is any component that accepts an optional size + className. This\n// matches both `lucide-react` and `react-icons` icon signatures, so consuming\n// apps pass their own icon set without this package depending on one.\nexport type NavIcon = ComponentType<{ size?: number; className?: string }>\n\nexport type NavItem = {\n /** Route to link to. Optional when `onClick` is supplied (renders a button). */\n href?: string\n label: string\n icon?: NavIcon\n /**\n * When supplied the row renders as a `<button>` (styled identically to the\n * link rows) instead of a routed link — used for actions like \"Support\".\n */\n onClick?: () => void\n}\n\n// The routing primitive is injected so each app supplies its own (Next.js\n// `<Link>`, react-router `<Link>`, or a plain `<a>`). Defaults to an anchor.\nexport type NavLinkProps = {\n href: string\n className?: string\n 'aria-current'?: 'page' | undefined\n children: ReactNode\n}\nexport type NavLinkComponent = ComponentType<NavLinkProps>\n\nconst DefaultLink: NavLinkComponent = ({ href, children, ...rest }) => (\n <a href={href} {...rest}>\n {children}\n </a>\n)\n\n/** Default active-route test: exact match for \"/\", prefix match otherwise. */\nexport function isNavItemActive(pathname: string, href: string): boolean {\n if (href === '/') return pathname === '/'\n return pathname === href || pathname.startsWith(`${href}/`)\n}\n\n/**\n * Color-coded tone classes for the sidebar environment chip, matching the main\n * VRM app: prod red, staging orange, local/dev green, unknown gray.\n */\nexport function envChipClasses(label: string): string {\n const l = label.toLowerCase()\n if (l === 'production' || l === 'prod')\n return 'bg-[#fee2e2] text-[#ef4444]'\n if (l === 'staging' || l === 'stage')\n return 'bg-[#ffedd5] text-[#f97316]'\n if (l === 'local' || l === 'development' || l === 'dev')\n return 'bg-[#dcfce7] text-[#22c55e]'\n return 'bg-[rgba(107,114,128,0.16)] text-[#6b7280]'\n}\n\n// ---------------------------------------------------------------------------\n\nexport type NavBrand = {\n /** Where the logo links to. Defaults to \"/\". */\n homeHref?: string\n /**\n * Brand logo image src. When supplied this renders as the wordmark logo\n * (matching the main app's BrandLogo), and `badgeLabel`/`title`/`subtitle`\n * are used only as the no-image fallback.\n */\n logoSrc?: string\n logoAlt?: string\n /** Short label shown inside the brand badge fallback (e.g. \"P\"). */\n badgeLabel: string\n title: string\n subtitle?: string\n}\n\nexport type NavOrg = {\n name: string | null\n /** Full org id. When present, a copy affordance is shown by the label. */\n id?: string | null\n /** Optional org logo for the pill badge; falls back to initials. */\n logoUrl?: string | null\n /** Solid pill background (the org's brand color). Defaults to race red. */\n brandColor?: string | null\n onClick?: () => void\n}\n\nexport type NavMode = {\n label: string\n icon?: NavIcon\n}\n\nexport type NavFooterLink = { label: string; href: string; external?: boolean }\n\nexport type NavFooter = {\n envLabel: string\n version: string\n commit: string\n links?: NavFooterLink[]\n /** ISO date (or any Date-parseable string) of the deployed commit. */\n commitDate?: string\n /** Full name of the commit author (only the first name is shown). */\n commitAuthor?: string\n}\n\nexport type NavSidebarProps = {\n pathname: string\n brand: NavBrand\n nav: {\n groupLabel?: string\n items: NavItem[]\n isActive?: (pathname: string, href: string) => boolean\n }\n org?: NavOrg | null\n mode?: NavMode | null\n footer?: NavFooter | null\n linkComponent?: NavLinkComponent\n}\n\n// Default org-pill background — race red, matching the main app's fallback\n// (mode primary) when an org has no explicit brand color.\nconst ORG_PILL_DEFAULT = 'var(--color-race-red)'\n\nexport function NavSidebar({\n pathname,\n brand,\n nav,\n org,\n mode,\n footer,\n linkComponent,\n}: NavSidebarProps) {\n const Link = linkComponent ?? DefaultLink\n const activeFn = nav.isActive ?? isNavItemActive\n const ModeIcon = mode?.icon\n const orgBg = org?.brandColor || ORG_PILL_DEFAULT\n\n return (\n <aside className=\"fixed inset-y-0 left-0 z-30 hidden w-[240px] flex-col border-r border-[color:var(--line)] bg-[color:var(--panel-elevated)] lg:flex\">\n {/* Brand — logo image (preferred) or badge fallback */}\n <Link\n href={brand.homeHref ?? '/'}\n className=\"flex h-[64px] flex-shrink-0 items-center gap-2.5 px-6\"\n >\n {brand.logoSrc ? (\n <img\n src={brand.logoSrc}\n alt={brand.logoAlt ?? brand.title}\n draggable={false}\n className=\"h-10 w-auto object-contain\"\n />\n ) : (\n <>\n <span className=\"flex h-9 w-9 items-center justify-center rounded-xl bg-[color:var(--color-race-red)] text-base font-black text-white shadow-sm\">\n {brand.badgeLabel}\n </span>\n <span className=\"flex flex-col leading-tight\">\n <span className=\"text-base font-extrabold tracking-[-0.01em] text-[color:var(--color-race-red)]\">\n {brand.title}\n </span>\n {brand.subtitle ? (\n <span className=\"text-[11px] font-semibold uppercase tracking-[0.16em] text-[color:var(--muted)]\">\n {brand.subtitle}\n </span>\n ) : null}\n </span>\n </>\n )}\n </Link>\n\n {/* Scroll region: org pill, mode pill, nav */}\n <div className=\"flex flex-1 flex-col overflow-y-auto px-4 pb-2\">\n {/* Organization switcher */}\n {org ? (\n <div className=\"mb-3\">\n <div className=\"mb-2 flex items-center justify-between px-1\">\n <p className=\"text-[11px] font-semibold uppercase tracking-[0.08em] text-[color:var(--muted)]\">\n Organization\n </p>\n {org.id ? <OrgIdCopyButton id={org.id} /> : null}\n </div>\n <button\n type=\"button\"\n onClick={org.onClick}\n style={{ backgroundColor: orgBg }}\n className=\"flex w-full items-center justify-between gap-2 rounded-md px-2.5 py-2 text-left text-white shadow-sm transition hover:opacity-95\"\n >\n <span className=\"flex min-w-0 items-center gap-2.5\">\n <span className=\"flex h-7 w-7 shrink-0 items-center justify-center overflow-hidden rounded-full bg-white/20 text-[11px] font-bold text-white\">\n {org.logoUrl ? (\n <img src={org.logoUrl} alt=\"\" className=\"h-full w-full object-cover\" />\n ) : (\n (org.name ?? 'Org').slice(0, 2).toUpperCase()\n )}\n </span>\n <span className=\"truncate text-sm font-semibold\">\n {org.name ?? 'No organizations'}\n </span>\n </span>\n <ChevronDown />\n </button>\n </div>\n ) : null}\n\n {/* Mode pill */}\n {mode ? (\n <div className=\"mb-4\">\n <button\n type=\"button\"\n className=\"flex w-full items-center justify-between gap-2 rounded-md bg-[color:var(--color-race-red)] px-2.5 py-2 text-white shadow-sm\"\n >\n <span className=\"flex items-center gap-2.5\">\n {ModeIcon ? <ModeIcon size={16} className=\"shrink-0\" /> : null}\n <span className=\"text-sm font-semibold tracking-[-0.01em]\">{mode.label}</span>\n </span>\n <ChevronDown />\n </button>\n </div>\n ) : null}\n\n {/* Nav list */}\n <nav className=\"flex flex-col gap-1\">\n {nav.groupLabel ? (\n <p className=\"px-2 pb-1 text-[11px] font-semibold uppercase tracking-[0.08em] text-[color:var(--muted)]\">\n {nav.groupLabel}\n </p>\n ) : null}\n {nav.items.map((item, idx) => {\n const active = item.href ? activeFn(pathname, item.href) : false\n return (\n <NavRow\n key={item.href ?? `nav-${idx}-${item.label}`}\n item={item}\n active={active}\n Link={Link}\n />\n )\n })}\n </nav>\n </div>\n\n {/* Footer — pipe-separated links + commit/version string */}\n {footer ? (\n <div className=\"flex-shrink-0 border-t border-[color:var(--line)] px-4 py-3\">\n {footer.links && footer.links.length > 0 ? (\n <div className=\"flex flex-wrap items-center gap-1.5 text-xs\">\n {footer.links.map((l, idx) => (\n <span key={l.label} className=\"inline-flex items-center gap-1.5\">\n {idx > 0 ? <span className=\"text-[color:var(--muted)]\">|</span> : null}\n <a\n href={l.href}\n className=\"font-medium text-[color:var(--color-race-red)] hover:underline\"\n >\n {l.label}\n </a>\n </span>\n ))}\n </div>\n ) : null}\n <div className=\"mt-1.5 flex flex-col items-center gap-0.5 font-[family-name:var(--font-plex-mono)] text-[10px] text-[color:var(--muted)]\">\n {/* Row 1: env chip | commit | version */}\n <div className=\"flex items-center gap-1.5\">\n <span\n className={[\n 'rounded-md px-1.5 py-0.5 text-[10px] font-semibold',\n envChipClasses(footer.envLabel),\n ].join(' ')}\n >\n {footer.envLabel}\n </span>\n <span className=\"text-[color:var(--muted)]\">|</span>\n <span>{footer.commit.slice(-4)}</span>\n <span className=\"text-[color:var(--muted)]\">|</span>\n <span>v{footer.version}</span>\n </div>\n {/* Row 2: date | author (only when supplied) */}\n {footer.commitDate || footer.commitAuthor ? (\n <div className=\"flex items-center gap-1.5\">\n <span className=\"text-[color:var(--muted)]\">|</span>\n {footer.commitDate ? (\n <span>\n {new Date(footer.commitDate).toLocaleDateString(undefined, {\n month: 'short',\n day: 'numeric',\n })}\n </span>\n ) : null}\n <span className=\"text-[color:var(--muted)]\">|</span>\n {footer.commitAuthor ? (\n <span>{footer.commitAuthor.split(/\\s+/)[0]}</span>\n ) : null}\n <span className=\"text-[color:var(--muted)]\">|</span>\n </div>\n ) : null}\n </div>\n </div>\n ) : null}\n </aside>\n )\n}\n\n// Inline chevron-down (mirrors lucide `ChevronDown`) — no icon dependency.\nfunction ChevronDown() {\n return (\n <svg\n width={16}\n height={16}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={2}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className=\"shrink-0 opacity-80\"\n aria-hidden=\"true\"\n >\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n )\n}\n\n// A single sidebar nav row. Renders as a routed <Link> (href) or a <button>\n// (onClick), styled identically. Inactive rows animate a left→right gradient\n// sweep on hover (matching the main app); active rows keep solid race-red.\nconst NAV_HOVER_GRADIENT = 'linear-gradient(90deg,#ea6a3a 0%,#E63946 100%)'\n\nfunction NavRow({\n item,\n active,\n Link,\n}: {\n item: NavItem\n active: boolean\n Link: NavLinkComponent\n}) {\n const Icon = item.icon\n const wrapperClasses = [\n 'group relative block overflow-hidden rounded-md transition',\n active\n ? 'bg-[color:var(--color-race-red)] text-white shadow-sm'\n : 'text-[color:var(--muted)] hover:text-white',\n ].join(' ')\n\n const inner = (\n <>\n {!active ? (\n <span\n aria-hidden=\"true\"\n className=\"pointer-events-none absolute inset-y-0 left-0 w-0 transition-[width] duration-300 group-hover:w-full\"\n style={{ background: NAV_HOVER_GRADIENT }}\n />\n ) : null}\n <span className=\"relative z-10 flex items-center gap-3 px-3 py-2 text-sm font-semibold\">\n {Icon ? (\n <Icon size={18} className={active ? 'text-white' : 'text-current'} />\n ) : null}\n {item.label}\n </span>\n </>\n )\n\n if (item.onClick && !item.href) {\n return (\n <button type=\"button\" onClick={item.onClick} className={`w-full text-left ${wrapperClasses}`}>\n {inner}\n </button>\n )\n }\n\n return (\n <Link\n href={item.href ?? '#'}\n aria-current={active ? 'page' : undefined}\n className={wrapperClasses}\n >\n {inner}\n </Link>\n )\n}\n\n// Right-aligned copy affordance for the org id, shown beside the \"Organization\"\n// label. Copies the full id and briefly swaps its text to \"Copied\".\nfunction OrgIdCopyButton({ id }: { id: string }) {\n const [copied, setCopied] = useState(false)\n return (\n <button\n type=\"button\"\n onClick={() => {\n try {\n void navigator.clipboard?.writeText(id)\n } catch {\n /* clipboard unavailable — ignore */\n }\n setCopied(true)\n setTimeout(() => setCopied(false), 1500)\n }}\n title=\"Copy organization id\"\n className=\"rounded px-1 text-[11px] text-[color:var(--muted)] hover:bg-[color:var(--panel)]\"\n >\n {copied ? 'Copied' : `...${id.slice(-8)}`}\n </button>\n )\n}\n\n// ---------------------------------------------------------------------------\n\nexport type NavUser = {\n name: string | null\n email?: string | null\n /** Avatar image URL; falls back to initials on the brand color. */\n avatarUrl?: string | null\n tier?: string | null\n role?: string | null\n}\n\nexport type NavTopBarAction = {\n key: string\n icon: NavIcon\n label: string\n onClick?: () => void\n href?: string\n badge?: number | null\n tone?: 'default' | 'warning' | 'danger'\n}\n\nexport type NavTopBarProps = {\n title: string\n subtitle?: string\n user?: NavUser | null\n onSignOut?: () => void\n /**\n * Structured icon-button action cluster (32×32 circular buttons with optional\n * count badges), rendered before the `actions` slot. Matches the main app's\n * topbar action icons.\n */\n actionsList?: NavTopBarAction[]\n /** App-specific action cluster rendered left of the theme toggle. */\n actions?: ReactNode\n /** Theme toggle slot (injected so this package needs no theme dependency). */\n themeToggle?: ReactNode\n}\n\nexport function NavTopBar({\n title,\n subtitle,\n user,\n onSignOut,\n actionsList,\n actions,\n themeToggle,\n}: NavTopBarProps) {\n const initials = (user?.name ?? 'Politogy').slice(0, 2).toUpperCase()\n const secondary = user?.email || [user?.tier, user?.role].filter(Boolean).join(' · ') || 'member'\n return (\n <header className=\"sticky top-0 z-20 flex h-20 items-center justify-between gap-4 border-b border-[color:var(--line)] bg-[color:var(--panel)] px-4 backdrop-blur-xl\">\n <div className=\"min-w-0\">\n <h1\n className=\"truncate text-lg font-bold tracking-[-0.01em] text-[color:var(--foreground)]\"\n data-display=\"true\"\n >\n {title}\n </h1>\n {subtitle ? (\n <p className=\"truncate text-sm text-[color:var(--muted)]\">{subtitle}</p>\n ) : null}\n </div>\n\n <div className=\"flex shrink-0 items-center gap-2\">\n {actionsList && actionsList.length > 0\n ? actionsList.map((action) => (\n <NavTopBarActionButton key={action.key} action={action} />\n ))\n : null}\n {actions}\n {themeToggle}\n\n {/* User menu — avatar + name/email + chevron; sign-out in a native\n <details> popover so the package needs no JS state. */}\n <details className=\"group relative\">\n <summary className=\"flex cursor-pointer list-none items-center gap-2.5 rounded-full py-1 pl-1 pr-2 transition hover:bg-[color:var(--panel-elevated)] [&::-webkit-details-marker]:hidden\">\n <span className=\"flex h-9 w-9 items-center justify-center overflow-hidden rounded-full bg-[color:var(--color-race-red)] text-xs font-bold text-white\">\n {user?.avatarUrl ? (\n <img src={user.avatarUrl} alt=\"\" className=\"h-full w-full object-cover\" />\n ) : (\n initials\n )}\n </span>\n <span className=\"hidden flex-col leading-tight md:flex\">\n <span className=\"text-sm font-semibold text-[color:var(--foreground)]\">\n {user?.name ?? 'Signed in'}\n </span>\n <span className=\"text-xs text-[color:var(--muted)]\">{secondary}</span>\n </span>\n <ChevronDown />\n </summary>\n {onSignOut ? (\n <div className=\"absolute right-0 z-30 mt-2 w-44 overflow-hidden rounded-xl border border-[color:var(--line)] bg-[color:var(--panel-elevated)] py-1 shadow-[var(--shadow-soft)]\">\n <button\n type=\"button\"\n onClick={onSignOut}\n className=\"flex w-full items-center gap-2.5 px-4 py-2 text-left text-sm font-medium text-[color:var(--foreground)] hover:bg-[color:var(--panel)] hover:text-[color:var(--color-race-red)]\"\n >\n <SignOutIcon />\n Sign out\n </button>\n </div>\n ) : null}\n </details>\n </div>\n </header>\n )\n}\n\n// Inline log-out glyph (mirrors lucide `LogOut`) to avoid an icon dependency.\nfunction SignOutIcon() {\n return (\n <svg\n width={15}\n height={15}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={2}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4\" />\n <polyline points=\"16 17 21 12 16 7\" />\n <line x1=\"21\" x2=\"9\" y1=\"12\" y2=\"12\" />\n </svg>\n )\n}\n\n// A single 32×32 circular icon button for the top bar's structured action list.\n// Renders an <a> when `href` is present (external links open in a new tab),\n// otherwise a <button>. Shows a red count badge when `badge > 0`.\nfunction NavTopBarActionButton({ action }: { action: NavTopBarAction }) {\n const Icon = action.icon\n const tone = action.tone ?? 'default'\n const toneClass =\n tone === 'danger' || tone === 'warning'\n ? 'text-[color:var(--color-race-red)] hover:text-[color:var(--color-race-red)]'\n : 'text-[color:var(--muted)] hover:text-[color:var(--foreground)]'\n const className = [\n 'relative flex h-8 w-8 items-center justify-center rounded-full border border-[color:var(--line)] transition',\n toneClass,\n ].join(' ')\n\n const content = (\n <>\n <Icon size={20} className=\"text-current\" />\n {typeof action.badge === 'number' && action.badge > 0 ? (\n <span className=\"absolute -top-1 -right-1 flex h-4 min-w-4 items-center justify-center rounded-full bg-[color:var(--color-race-red)] px-1 text-[10px] text-white\">\n {action.badge}\n </span>\n ) : null}\n </>\n )\n\n const isExternal = action.href ? /^https?:\\/\\//.test(action.href) : false\n\n if (action.href) {\n return (\n <a\n href={action.href}\n aria-label={action.label}\n title={action.label}\n className={className}\n {...(isExternal ? { target: '_blank', rel: 'noreferrer' } : {})}\n >\n {content}\n </a>\n )\n }\n\n return (\n <button\n type=\"button\"\n onClick={action.onClick}\n aria-label={action.label}\n title={action.label}\n className={className}\n >\n {content}\n </button>\n )\n}\n\n// ---------------------------------------------------------------------------\n\nexport type NavAppShellProps = {\n sidebar: NavSidebarProps\n topBar: NavTopBarProps\n children: ReactNode\n}\n\n/**\n * Full chrome: branded left sidebar + sticky top navbar + scrollable content\n * region. Presentational only — wrap it in app-level auth/gating as needed.\n */\nexport function NavAppShell({ sidebar, topBar, children }: NavAppShellProps) {\n return (\n <div className=\"min-h-screen bg-[color:var(--background)] text-[color:var(--foreground)]\">\n <NavSidebar {...sidebar} />\n <div className=\"lg:pl-[240px]\">\n <NavTopBar {...topBar} />\n <main className=\"px-6 py-7\">\n <div className=\"mx-auto max-w-7xl\">{children}</div>\n </main>\n </div>\n </div>\n )\n}\n"]}