@the-portland-company/shell 0.3.2 → 0.3.4
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 +17 -0
- package/README.md +198 -27
- package/dist/css-modules.d.ts +6 -0
- package/package.json +10 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.4 — 2026-05-14
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- `package.json` `exports` for `./css` and `./chrome.css` now ship a `types` entry pointing at a tiny ambient module declaration (`dist/css-modules.d.ts`). Fixes TS2882 errors in fresh consumers importing the side-effect CSS subpaths.
|
|
8
|
+
|
|
9
|
+
### Documentation
|
|
10
|
+
|
|
11
|
+
- README install command now pins peer-dep versions explicitly (`react@^18`, `@chakra-ui/react@^2.8`, etc.) so Vite-scaffolded apps on React 19 / Chakra v3 don't silently install incompatible versions.
|
|
12
|
+
- Clarified `@the-portland-company/devnotes` is optional.
|
|
13
|
+
|
|
14
|
+
## 0.3.3 — 2026-05-14
|
|
15
|
+
|
|
16
|
+
### Documentation
|
|
17
|
+
|
|
18
|
+
- Rewrote `README.md` to cover the v0.3.x chrome contract: every component / hook / helper / type, the `<ShellChrome>` binding pattern with a copy-pasteable bridge example, the `chrome.css` + `css` import paths, multi-SPA routing notes for the politogy edge router, and a pointer to `politogy-vrm/react/app/src/providers/ShellChromeBridge.tsx` as the reference implementation. No code changes.
|
|
19
|
+
|
|
3
20
|
## 0.3.2 — 2026-05-13
|
|
4
21
|
|
|
5
22
|
### Added
|
package/README.md
CHANGED
|
@@ -2,47 +2,203 @@
|
|
|
2
2
|
|
|
3
3
|
Shared chrome for politogy apps. One install, one wrapper, identical look.
|
|
4
4
|
|
|
5
|
+
Every politogy product (Email Blast, Contacts, Forms, future apps…) installs this package and gets the full branded experience — sidebar with VRM logo, organization switcher, mode selector, full nav list, header with notifications and support, branded footer with commit info — without rebuilding any of it. Path-based routing lets multiple separately-deployed Vite SPAs feel like a single continuous app on `app.politogy.com`.
|
|
6
|
+
|
|
7
|
+
> **v0.3.x is a major rewrite.** See the [CHANGELOG](./CHANGELOG.md) for the chrome component, hook, and prop additions over v0.2.x. The minimum viable boot below works for new apps; existing v0.2.x consumers can keep using `OrgSelector`, `useShellAuth`, etc. without changes.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
5
11
|
## Install
|
|
6
12
|
|
|
13
|
+
Pin peer dep versions explicitly — Vite scaffolds React 19 + Chakra v3 by default, neither of which the shell supports:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install @the-portland-company/shell \
|
|
17
|
+
react@^18 react-dom@^18 \
|
|
18
|
+
@chakra-ui/react@^2.8 @emotion/react@^11 @emotion/styled@^11 \
|
|
19
|
+
framer-motion@^11 \
|
|
20
|
+
@supabase/supabase-js@~2.95 \
|
|
21
|
+
react-router-dom@^6 react-icons@^5
|
|
7
22
|
```
|
|
8
|
-
npm install @the-portland-company/shell @chakra-ui/react @emotion/react @emotion/styled @supabase/supabase-js react react-dom
|
|
9
|
-
```
|
|
10
23
|
|
|
11
|
-
|
|
24
|
+
`@the-portland-company/devnotes` is **optional** — only needed if you want the dev-notes menu in the header. Skip it if your app doesn't use it.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Minimum viable boot
|
|
29
|
+
|
|
30
|
+
The simplest possible startup renders the branded chrome with empty defaults — the sidebar appears, the header renders, but the org switcher / mode selector / nav list show "no data" states until you wire them up via `<ShellChrome>` (next section).
|
|
12
31
|
|
|
13
32
|
```tsx
|
|
14
|
-
|
|
15
|
-
import '
|
|
33
|
+
// src/main.tsx
|
|
34
|
+
import React from 'react'
|
|
35
|
+
import ReactDOM from 'react-dom/client'
|
|
36
|
+
import { BrowserRouter } from 'react-router-dom'
|
|
37
|
+
import { ShellProvider, AppLayout } from '@the-portland-company/shell'
|
|
38
|
+
import '@the-portland-company/shell/chrome.css'
|
|
39
|
+
import { createClient } from '@supabase/supabase-js'
|
|
40
|
+
import App from './App'
|
|
41
|
+
|
|
42
|
+
const supabase = createClient(
|
|
43
|
+
import.meta.env.VITE_SUPABASE_URL,
|
|
44
|
+
import.meta.env.VITE_SUPABASE_ANON_KEY,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
48
|
+
<ShellProvider currentApp="email" supabaseClient={supabase}>
|
|
49
|
+
<BrowserRouter>
|
|
50
|
+
<AppLayout>
|
|
51
|
+
<App />
|
|
52
|
+
</AppLayout>
|
|
53
|
+
</BrowserRouter>
|
|
54
|
+
</ShellProvider>,
|
|
55
|
+
)
|
|
56
|
+
```
|
|
16
57
|
|
|
17
|
-
|
|
58
|
+
`<AppLayout>` composes the shell `<Header>`, `<Sidebar>`, your routed content, and `<Footer>`. `<ShellProvider>` owns the Supabase auth lifecycle and exposes `useShellAuth()` for status / user / sign-out.
|
|
18
59
|
|
|
19
|
-
|
|
20
|
-
{ id: 'crm', label: 'CRM', path: '/' },
|
|
21
|
-
{ id: 'messaging', label: 'Messaging', path: '/messaging' },
|
|
22
|
-
]
|
|
60
|
+
---
|
|
23
61
|
|
|
24
|
-
|
|
62
|
+
## Real-world boot: feeding the chrome real data
|
|
63
|
+
|
|
64
|
+
Most apps have an `<AuthProvider>`, `<OrganizationProvider>`, `<ModeProvider>`, etc. that live *below* `<ShellProvider>` in the tree. To push their data up into the shell's chrome contexts, drop a `<ShellChrome>` binding component as deep in the tree as the data is available:
|
|
65
|
+
|
|
66
|
+
```tsx
|
|
67
|
+
import {
|
|
68
|
+
ShellProvider,
|
|
69
|
+
ShellChrome,
|
|
70
|
+
AppLayout,
|
|
71
|
+
} from '@the-portland-company/shell'
|
|
72
|
+
|
|
73
|
+
function ChromeBridge({ children }) {
|
|
74
|
+
const { currentMode, switchMode, getModeConfig, getAvailableModes } = useMode()
|
|
75
|
+
const { currentOrg, organizations, switchOrganization } = useOrganization()
|
|
76
|
+
const { linkedAccounts, ... } = useLinkedAccounts()
|
|
77
|
+
// …compute the nav-items list for the active mode here…
|
|
25
78
|
return (
|
|
26
|
-
<
|
|
27
|
-
|
|
28
|
-
|
|
79
|
+
<ShellChrome
|
|
80
|
+
mode={{ currentMode, modes: getAvailableModes(), switchMode, getModeConfig }}
|
|
81
|
+
organization={{ currentOrg, organizations, switchOrganization }}
|
|
82
|
+
linkedAccounts={{ linkedAccounts, switchToAccount, onAddAccount, onRemoveAccount }}
|
|
83
|
+
navItems={navItems}
|
|
84
|
+
role={{ activeRoleConfig, isSuperAdmin }}
|
|
85
|
+
footer={{ links, commit, version, releaseName, environmentLabel }}
|
|
86
|
+
support={{ submitSupportRequest }}
|
|
87
|
+
brandLogoSrc="/brand/logos/email-mode-logo.png"
|
|
88
|
+
headerIconsSlot={<MyAppSpecificIcons />}
|
|
89
|
+
userAvatarUrl={profile.avatar_url ?? storedMemojiUrl ?? defaultAvatarUrl}
|
|
90
|
+
myProfileHref="/my-profile"
|
|
91
|
+
organizationSettingsHref={`/organization-settings?org=${currentOrg.id}`}
|
|
92
|
+
>
|
|
93
|
+
{children}
|
|
94
|
+
</ShellChrome>
|
|
29
95
|
)
|
|
30
96
|
}
|
|
97
|
+
|
|
98
|
+
// Then in main.tsx:
|
|
99
|
+
<ShellProvider currentApp="email" supabaseClient={supabase}>
|
|
100
|
+
<BrowserRouter>
|
|
101
|
+
<AuthProvider>
|
|
102
|
+
<OrganizationProvider>
|
|
103
|
+
<ModeProvider>
|
|
104
|
+
<ChromeBridge>
|
|
105
|
+
<AppLayout>
|
|
106
|
+
<Routes>…</Routes>
|
|
107
|
+
</AppLayout>
|
|
108
|
+
</ChromeBridge>
|
|
109
|
+
</ModeProvider>
|
|
110
|
+
</OrganizationProvider>
|
|
111
|
+
</AuthProvider>
|
|
112
|
+
</BrowserRouter>
|
|
113
|
+
</ShellProvider>
|
|
31
114
|
```
|
|
32
115
|
|
|
33
|
-
|
|
116
|
+
**Reference implementation:** `politogy-vrm`'s `react/app/src/providers/ShellChromeBridge.tsx` (~600 lines, every chrome wiring pattern). Copy as a starting point for new apps.
|
|
117
|
+
|
|
118
|
+
All `<ShellChrome>` props are optional — only supply what your app has. The chrome gracefully omits the bits it doesn't get data for (e.g., no `support` prop → no support icon rendered).
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## What the shell exports
|
|
123
|
+
|
|
124
|
+
### Components
|
|
125
|
+
|
|
126
|
+
| Export | Use |
|
|
127
|
+
|---|---|
|
|
128
|
+
| `<ShellProvider>` | Top-level provider. Wraps Chakra + auth + meta contexts. |
|
|
129
|
+
| `<ShellChrome>` | Mid-tree binding component. Feeds chrome contexts from app data. |
|
|
130
|
+
| `<AppLayout>` | Composes Header + Sidebar + main content + Footer. |
|
|
131
|
+
| `<Header>` | Header bar (logo, page title, breadcrumbs, icon stack, user menu). |
|
|
132
|
+
| `<Sidebar>` | Sidebar (branded logo, org pill, mode pill, nav list, footer). |
|
|
133
|
+
| `<Footer>` | Below main content. Usually not rendered separately. |
|
|
134
|
+
| `<UserMenu>` | Avatar dropdown (profile, theme, sign out, switch account). |
|
|
135
|
+
| `<OrgPill>` (alias `<OrgSelector>`) | Organization switcher pill rendered in the sidebar. |
|
|
136
|
+
| `<ModePill>` | Mode (Relationship / Campaign / Petition) switcher. |
|
|
137
|
+
| `<NavItem>` | Single sidebar nav row with active state + hover gradient. |
|
|
138
|
+
| `<SupportRequestButton>` | Header life-buoy icon that opens a support modal. |
|
|
139
|
+
| `<BrandLogo>` | `<img>` wrapper for the branded mode logo. |
|
|
140
|
+
| `<BrandIcon name="...">` | Renders `/brand/icons/<name>.svg` from your `public/`. |
|
|
141
|
+
| `<CommitCopyButton>` | Footer commit/version display with copy-to-clipboard. |
|
|
142
|
+
| `<Card>`, `<PageContainer>`, `<PageHeader>`, `<BreadcrumbBar>`, `<HeaderTabBar>` | Page-level layout primitives. |
|
|
143
|
+
| `<GlobalUiIdProvider>` | Assigns stable UI IDs (for testing/analytics). |
|
|
144
|
+
|
|
145
|
+
### Hooks
|
|
146
|
+
|
|
147
|
+
| Hook | Returns |
|
|
148
|
+
|---|---|
|
|
149
|
+
| `useShellAuth()` | `{ status, user, session, signOut }` |
|
|
150
|
+
| `useShellOrganization()` | `{ currentOrg, organizations, switchOrganization, createOrgHref } \| null` |
|
|
151
|
+
| `useShellMode()` | `{ currentMode, modes, switchMode, getModeConfig } \| null` |
|
|
152
|
+
| `useShellNavItems()` | `{ items: ShellNavItem[] } \| null` |
|
|
153
|
+
| `useShellLinkedAccounts()` | `{ linkedAccounts, switchToAccount, onAddAccount, onRemoveAccount } \| null` |
|
|
154
|
+
| `useShellSupport()` | `{ submitSupportRequest } \| null` |
|
|
155
|
+
| `useShellRole()` | `{ activeRoleConfig, isSuperAdmin } \| null` |
|
|
156
|
+
| `useShellFooter()` | `{ links, version, commit, releaseName, … } \| null` |
|
|
157
|
+
| `useShellNavigationPreference()` | `{ showSubMenu } \| null` |
|
|
158
|
+
| `useShellChrome()` | Chrome configuration (`devNotesMenu`, `myProfileHref`, `userAvatarUrl`, `pageTitle`, `breadcrumbs`, `headerActions`, etc.) |
|
|
159
|
+
| `useAppRegistry()`, `useCurrentApp()` | App switcher metadata for cross-app speculation rules. |
|
|
160
|
+
|
|
161
|
+
### Helpers
|
|
34
162
|
|
|
35
|
-
|
|
36
|
-
|---|---|---|
|
|
37
|
-
| `headerExtrasSlot` | Right of header, before user menu | App-specific header widgets |
|
|
38
|
-
| `modalsSlot` | Above main content, position absolute | Modal dialogs |
|
|
39
|
-
| `notificationsSlot` | Inside main, top-right | Toasts, badges |
|
|
40
|
-
| `sidebarFooterSlot` | Bottom of sidebar | Org switcher, status |
|
|
41
|
-
| `overlaySlot` | Top of everything | Always-on-top indicators |
|
|
163
|
+
`normalizeHexColor`, `lightenHexColor`, `hexToRgba`, `getOrganizationBrandColor`, `getOrganizationBadgeLetters`, `getUserDisplayName`.
|
|
42
164
|
|
|
43
|
-
|
|
165
|
+
### Theme
|
|
44
166
|
|
|
45
|
-
|
|
167
|
+
`politogyTheme` — Chakra theme with brand palette, mode-aware semantic tokens, and component overrides.
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## CSS imports
|
|
172
|
+
|
|
173
|
+
```tsx
|
|
174
|
+
import '@the-portland-company/shell/chrome.css' // :root design tokens (--app-color-brand-*, --mode-*, --app-tab-*, --form-control-*)
|
|
175
|
+
import '@the-portland-company/shell/css' // view-transitions CSS for cross-app polish
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
`chrome.css` is required for theme variables. `css` (view-transitions) is optional polish.
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## Multi-SPA routing on `app.politogy.com`
|
|
183
|
+
|
|
184
|
+
Multiple politogy apps live behind one domain via a Cloudflare Worker edge router (in `packages/edge-router` of the monorepo).
|
|
185
|
+
|
|
186
|
+
When you deploy a new app to Cloudflare Pages, add its path prefix to the edge router's `routes.ts`:
|
|
187
|
+
|
|
188
|
+
```ts
|
|
189
|
+
{
|
|
190
|
+
pathPrefix: '/email',
|
|
191
|
+
origin: 'https://politogy-email.pages.dev',
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
After deploy, users hitting `app.politogy.com/email/*` get transparently proxied to your app. Other paths keep going to whichever app owns them. The shared shell + same Supabase session means it feels like one app.
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## Service worker (optional polish)
|
|
200
|
+
|
|
201
|
+
The package ships a precache service worker that caches the shell chrome on first visit so cross-app navigation feels instant.
|
|
46
202
|
|
|
47
203
|
```js
|
|
48
204
|
// scripts/copy-shell-sw.mjs
|
|
@@ -53,11 +209,13 @@ await copyFile(
|
|
|
53
209
|
)
|
|
54
210
|
```
|
|
55
211
|
|
|
56
|
-
|
|
212
|
+
Wire it into your build (`"postinstall": "node scripts/copy-shell-sw.mjs"`) and call `registerShellPrecache()` once on app boot.
|
|
213
|
+
|
|
214
|
+
---
|
|
57
215
|
|
|
58
|
-
## Speculation Rules
|
|
216
|
+
## Speculation Rules (optional polish)
|
|
59
217
|
|
|
60
|
-
|
|
218
|
+
Cross-app prerendering hints for the browser:
|
|
61
219
|
|
|
62
220
|
```tsx
|
|
63
221
|
import { useAppRegistry, useCurrentApp, buildSpeculationRules } from '@the-portland-company/shell'
|
|
@@ -70,3 +228,16 @@ function SpeculationRules() {
|
|
|
70
228
|
return <script type="speculationrules" dangerouslySetInnerHTML={{ __html: json }} />
|
|
71
229
|
}
|
|
72
230
|
```
|
|
231
|
+
|
|
232
|
+
Render once near the app root.
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## Reference + support
|
|
237
|
+
|
|
238
|
+
- **Reference bridge:** `politogy-vrm/react/app/src/providers/ShellChromeBridge.tsx` — full chrome wiring for the main politogy app.
|
|
239
|
+
- **Architectural decisions, gotchas, session log:** `politogy-vrm/docs/superpowers/`.
|
|
240
|
+
- **Type definitions:** `node_modules/@the-portland-company/shell/dist/index.d.ts` — every prop and hook is fully typed.
|
|
241
|
+
- **CHANGELOG:** [CHANGELOG.md](./CHANGELOG.md) — every export added per version.
|
|
242
|
+
|
|
243
|
+
Questions or missing features: file an issue or ping the politogy team.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Side-effect type declarations for the CSS subpath exports so TS consumers
|
|
2
|
+
// can `import '@the-portland-company/shell/css'` and
|
|
3
|
+
// `import '@the-portland-company/shell/chrome.css'` without ambient-shim
|
|
4
|
+
// boilerplate.
|
|
5
|
+
declare module '@the-portland-company/shell/css'
|
|
6
|
+
declare module '@the-portland-company/shell/chrome.css'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@the-portland-company/shell",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.4",
|
|
4
4
|
"description": "Shared chrome (header, sidebar, footer, theme, auth) for politogy apps. Drop into any Vite SPA to inherit the politogy look and signed-in user.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"private": false,
|
|
@@ -17,8 +17,14 @@
|
|
|
17
17
|
"import": "./dist/index.js",
|
|
18
18
|
"require": "./dist/index.cjs"
|
|
19
19
|
},
|
|
20
|
-
"./css":
|
|
21
|
-
|
|
20
|
+
"./css": {
|
|
21
|
+
"types": "./dist/css-modules.d.ts",
|
|
22
|
+
"default": "./dist/view-transitions.css"
|
|
23
|
+
},
|
|
24
|
+
"./chrome.css": {
|
|
25
|
+
"types": "./dist/css-modules.d.ts",
|
|
26
|
+
"default": "./dist/chrome.css"
|
|
27
|
+
},
|
|
22
28
|
"./sw": "./dist/shell-precache.worker.js"
|
|
23
29
|
},
|
|
24
30
|
"files": [
|
|
@@ -30,7 +36,7 @@
|
|
|
30
36
|
"*.css"
|
|
31
37
|
],
|
|
32
38
|
"scripts": {
|
|
33
|
-
"build": "tsup",
|
|
39
|
+
"build": "tsup && cp src/css-modules.d.ts dist/css-modules.d.ts",
|
|
34
40
|
"dev": "tsup --watch",
|
|
35
41
|
"test": "vitest run",
|
|
36
42
|
"test:watch": "vitest",
|