@the-portland-company/shell 0.3.4 → 0.4.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,12 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.5 — 2026-05-14
4
+
5
+ ### Fixed
6
+
7
+ - `ShellModeValue.getModeConfig` is now optional. If omitted, shell auto-resolves the active mode config via `modes.find(m => m.id === currentMode)`. Defangs the common `this`-binding mistake where consumers wrote `getModeConfig() { return this.modes[0] }` and shell internals destructured the function (losing `this`) and crashed the chrome.
8
+ - All internal call sites (`<ModePill>`, `<UserMenu>`, `<NavItem>`) now use a defensive `resolveModeConfig(mode)` helper that try/catches the consumer-supplied function and falls back to derivation. A broken `getModeConfig` no longer blanks the page.
9
+
3
10
  ## 0.3.4 — 2026-05-14
4
11
 
5
12
  ### Fixed
package/dist/chrome.css CHANGED
@@ -1,102 +1,264 @@
1
1
  /*
2
- * Politogy chrome global CSS variables. Ported verbatim from
3
- * react/app/src/index.css :root block (2026-05-13). Apps that adopt the
4
- * shell import this once (typically alongside the view-transitions CSS)
5
- * so form controls / buttons / tabs / mode colors stay consistent across
6
- * SPAs.
2
+ * GENERATED by scripts/gen-tokens.mjs from Specs (/api/tokens). DO NOT EDIT.
3
+ * Source of truth: Admin Brand API → Specs. Edit tokens in Admin, sync to
4
+ * Specs, then re-run `npm run gen:tokens`.
7
5
  */
8
-
9
6
  :root {
10
- /* Politogy brand palette — design spec 2026-05-02 */
7
+ /* Politogy brand palette */
8
+ --color-race-red-50: #FDECEE;
9
+ --color-race-blue-50: #E6F2FF;
10
+ --color-race-red-100: #FCE0E3;
11
+ --color-race-blue-100: #DCEBFB;
12
+ --color-race-red-200: #F8B6BD;
13
+ --color-race-blue-200: #9EC8F5;
14
+ --color-race-red-300: #EF6671;
15
+ --color-race-blue-300: #4DA3FF;
16
+ --color-race-red-400: #EA4751;
17
+ --color-race-blue-400: #1A89FF;
18
+ --color-race-red-500: #E63946;
19
+ --color-race-blue-500: #007BFF;
20
+ --color-race-red-600: #C42E3A;
21
+ --color-race-blue-600: #0061CC;
22
+ --color-race-red-700: #9F252E;
23
+ --color-race-blue-700: #004999;
24
+ --color-race-red-800: #660F17;
25
+ --color-race-blue-800: #002A52;
26
+ --color-race-red-900: #39080D;
27
+ --color-race-blue-900: #001124;
11
28
  --color-race-red: #E63946;
12
29
  --color-race-blue: #007BFF;
13
30
  --color-tread-yellow: #F2C94C;
14
31
  --color-green-flag: #00A651;
15
- --color-layer-1: #F0F4F7; /* G1 — page background */
16
- --color-layer-2: #ECEEEF; /* G2 — recessed surfaces */
17
- --color-layer-3: #FFFFFF; /* White — elevated cards */
18
- --color-graphite: #2F3136; /* Font 2 — primary body text */
19
- --color-g4: #929599; /* Font — secondary / muted text */
20
- --color-g3: #D7DADF; /* Small print + subtle accent borders */
32
+ --color-graphite: #2F3136;
33
+ --color-g3: #D7DADF;
34
+ --color-g4: #929599;
35
+ --color-layer-1: #F0F4F7;
36
+ --color-layer-2: #ECEEEF;
37
+ --color-white: #FFFFFF;
38
+ --color-layer-3: #FFFFFF;
39
+
40
+ /* Mode palettes */
41
+ --mode-relationship-primary: #E63946;
42
+ --mode-relationship-accent: #007BFF;
43
+ --mode-campaign-primary: #E63946;
44
+ --mode-campaign-accent: #F2C94C;
45
+ --mode-petition-primary: #007BFF;
46
+ --mode-petition-accent: #F2C94C;
47
+
48
+ /* Typography */
49
+ --font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif;
50
+ --type-h1-size: 2.25rem;
51
+ --type-h1-weight: 700;
52
+ --type-h1-line-height: 1.25;
53
+ --type-h1-color: #2F3136;
54
+ --type-h2-size: 1.875rem;
55
+ --type-h2-weight: 700;
56
+ --type-h2-line-height: 1.25;
57
+ --type-h2-color: #2F3136;
58
+ --type-h3-size: 1.5rem;
59
+ --type-h3-weight: 600;
60
+ --type-h3-line-height: 1.375;
61
+ --type-h3-color: #2F3136;
62
+ --type-h4-size: 1.25rem;
63
+ --type-h4-weight: 600;
64
+ --type-h4-line-height: 1.375;
65
+ --type-h4-color: #2F3136;
66
+ --type-h5-size: 1.125rem;
67
+ --type-h5-weight: 600;
68
+ --type-h5-line-height: 1.375;
69
+ --type-h5-color: #2F3136;
70
+ --type-h6-size: 1rem;
71
+ --type-h6-weight: 600;
72
+ --type-h6-line-height: 1.5;
73
+ --type-h6-color: #2F3136;
74
+ --type-body-size: 0.875rem;
75
+ --type-body-weight: 400;
76
+ --type-body-line-height: 1.5;
77
+ --type-body-color: #2F3136;
78
+ --type-bodySm-size: 0.75rem;
79
+ --type-bodySm-weight: 400;
80
+ --type-bodySm-line-height: 1.5;
81
+ --type-bodySm-color: #2F3136;
82
+ --type-caption-size: 0.75rem;
83
+ --type-caption-weight: 400;
84
+ --type-caption-line-height: 1.5;
85
+ --type-caption-color: #929599;
86
+ --type-eyebrow-size: 0.75rem;
87
+ --type-eyebrow-weight: 600;
88
+ --type-eyebrow-line-height: 1.5;
89
+ --type-eyebrow-color: #929599;
90
+ --type-mono-size: 0.75rem;
91
+ --type-mono-weight: 400;
92
+ --type-mono-line-height: 1.5;
93
+ --type-mono-color: #929599;
94
+
95
+ /* Radii */
96
+ --radius-sm: 0.125rem;
97
+ --radius-base: 0.25rem;
98
+ --radius-md: 0.375rem;
99
+ --radius-lg: 0.5rem;
100
+ --radius-xl: 0.75rem;
101
+ --radius-2xl: 1rem;
102
+ --radius-full: 9999px;
103
+
104
+ /* Shadows */
105
+ --shadow-sm: 0 1px 2px 0 rgba(15, 23, 42, 0.05);
106
+ --shadow-md: 0 4px 6px -1px rgba(15, 23, 42, 0.1), 0 2px 4px -2px rgba(15, 23, 42, 0.1);
107
+ --shadow-lg: 0 10px 15px -3px rgba(15, 23, 42, 0.1), 0 4px 6px -4px rgba(15, 23, 42, 0.1);
108
+ --shadow-outline: 0 0 0 3px rgba(232, 106, 58, 1);
109
+
110
+ /* Surfaces */
111
+ --surface-1: #F0F4F7;
112
+ --surface-2: #ECEEEF;
113
+ --surface-3: #FFFFFF;
114
+
115
+ /* Focus ring */
116
+ --focus-ring-color: #E86A3A;
117
+ --focus-ring-width: 3px;
118
+ --focus-ring-offset: 0px;
21
119
 
22
120
  /* Form-control sizing + colors */
23
- --app-field-bg: #ffffff;
24
- --app-field-border: #e2e8f0;
25
- --app-field-border-hover: #a0aec0;
26
- --app-field-focus: #e86a3a;
27
- --app-floating-label-bg: #ffffff;
28
- --app-floating-label-top: -9px;
29
- --app-floating-label-left: 10px;
30
- --app-floating-label-font-size: 12px;
31
- --app-floating-label-line-height: 16px;
32
- --app-floating-label-padding-x: 6px;
33
- --app-field-radius: 10px;
34
121
  --form-control-height: 44px;
35
122
  --form-control-radius: 5px;
36
123
  --form-control-padding: 12px;
37
124
  --form-control-font-size: 14px;
38
- --form-placeholder-font-size: 14px;
39
- --form-placeholder-color: #9ca3af;
125
+ --form-control-border: #CBD5E1;
126
+ --form-control-border-hover: #94A3B8;
127
+ --form-control-focus: #64748B;
128
+ --form-placeholder-color: #9CA3AF;
40
129
  --form-value-color: #000000;
41
- --form-control-border: #cbd5e1;
42
- --form-control-border-hover: #94a3b8;
43
- --form-control-focus: #64748b;
44
- --form-select-icon: #64748b;
45
- --form-label-left: 10px;
130
+ --form-label-bg: #FFFFFF;
46
131
  --form-label-top: 0px;
132
+ --form-label-left: 10px;
47
133
  --form-label-font-size: 12px;
134
+ --form-label-line-height: 16px;
135
+ --form-label-padding-x: 6px;
136
+
137
+ /* Tabs */
138
+ --tab-header-bg: #888888;
139
+ --tab-row-bg: #F0F4F8;
140
+ --tab-content-bg: #F8FAFC;
141
+ --tab-height: 44px;
142
+ --tab-icon-size: 18px;
143
+ --tab-strip-padding-y: 14px;
144
+ --tab-gap: 8px;
145
+ --tab-radius: 12px;
146
+
147
+ /* Sidebar (nav chrome) */
148
+ --sidebar-width: 140px;
149
+ --sidebar-bg: #FFFFFF;
150
+ --sidebar-border: #E2E8F0;
151
+ --sidebar-brand-logo-height: 40px;
152
+ --sidebar-org-logo-max-height: 70px;
153
+ --sidebar-item-radius: 8px;
154
+ --sidebar-item-padding-x: 16px;
155
+ --sidebar-item-padding-y: 10px;
156
+ --sidebar-item-gap: 2px;
157
+ --sidebar-text: #2F3136;
158
+ --sidebar-text-muted: #929599;
159
+ --sidebar-hover-bg: #ECEEEF;
160
+ --sidebar-active-text: #FFFFFF;
161
+ --sidebar-scrollbar-thumb: #CBD5E0;
162
+
163
+ /* Topbar */
164
+ --topbar-height: 80px;
165
+ --topbar-bg: #FFFFFF;
166
+ --topbar-border: #E2E8F0;
167
+ --topbar-padding-x: 16px;
168
+ --topbar-title-size: 18px;
169
+ --topbar-title-weight: 700;
170
+ --topbar-title-color: #2F3136;
171
+ --topbar-breadcrumb-color: #718096;
172
+ --topbar-icon-button-size: 32px;
173
+ --topbar-icon-size: 20px;
48
174
 
49
- /* Buttons */
50
- --app-btn-size: var(--form-control-height);
175
+ /* Shell chrome: brand color ramps */
176
+ --app-color-brand-50: #FFF5F0;
177
+ --app-color-brand-100: #FFE8DC;
178
+ --app-color-brand-200: #FFD0B8;
179
+ --app-color-brand-300: #FFB088;
180
+ --app-color-brand-400: #FF8C5A;
181
+ --app-color-brand-500: #E86A3A;
182
+ --app-color-brand-600: #CC5429;
183
+ --app-color-brand-700: #A6421F;
184
+ --app-color-brand-800: #803317;
185
+ --app-color-brand-900: #5C2410;
186
+
187
+ /* Shell chrome: gray ramps */
188
+ --app-color-gray-50: #F7FAFC;
189
+ --app-color-gray-100: #EDF2F7;
190
+ --app-color-gray-200: #E2E8F0;
191
+ --app-color-gray-300: #CBD5E0;
192
+ --app-color-gray-400: #A0AEC0;
193
+ --app-color-gray-500: #718096;
194
+ --app-color-gray-600: #4A5568;
195
+ --app-color-gray-700: #2D3748;
196
+ --app-color-gray-800: #1A202C;
197
+ --app-color-gray-900: #171923;
198
+
199
+ /* Shell chrome: fields */
200
+ --app-field-bg: #FFFFFF;
201
+ --app-field-border: #E2E8F0;
202
+ --app-field-border-hover: #A0AEC0;
203
+ --app-field-focus: #E86A3A;
204
+ --app-field-radius: 10px;
205
+ --form-placeholder-font-size: 14px;
206
+ --form-select-icon: #64748B;
207
+ --app-floating-label-bg: #FFFFFF;
208
+ --app-floating-label-top: -9px;
209
+ --app-floating-label-left: 10px;
210
+ --app-floating-label-font-size: 12px;
211
+ --app-floating-label-line-height: 16px;
212
+ --app-floating-label-padding-x: 6px;
213
+
214
+ /* Shell chrome: buttons */
215
+ --app-btn-size: 44px;
51
216
  --app-btn-radius: 10px;
52
217
  --app-btn-font-size: 14px;
53
- --app-btn-primary-bg: #e86a3a;
54
- --app-btn-primary-bg-hover: #d95f30;
55
- --app-btn-primary-fg: #ffffff;
56
- --app-btn-secondary-bg: #ffffff;
57
- --app-btn-secondary-bg-hover: #f8fafc;
218
+ --app-btn-primary-bg: #E86A3A;
219
+ --app-btn-primary-bg-hover: #D95F30;
220
+ --app-btn-primary-fg: #FFFFFF;
221
+ --app-btn-secondary-bg: #FFFFFF;
222
+ --app-btn-secondary-bg-hover: #F8FAFC;
58
223
  --app-btn-secondary-fg: #334155;
59
- --app-btn-secondary-border: #cbd5e1;
60
- --app-btn-tooltip-bg: #0f172a;
61
- --app-btn-tooltip-fg: #ffffff;
224
+ --app-btn-secondary-border: #CBD5E1;
225
+ --app-btn-tooltip-bg: #0F172A;
226
+ --app-btn-tooltip-fg: #FFFFFF;
62
227
 
63
- /* Tabs */
228
+ /* Shell chrome: tabs */
64
229
  --app-tab-header-bg: #888888;
65
- --app-tab-row-bg: #f0f4f8;
66
- --app-tab-content-bg: #f8fafc;
67
- --app-tab-height: var(--form-control-height);
230
+ --app-tab-row-bg: #F0F4F8;
231
+ --app-tab-content-bg: #F8FAFC;
232
+ --app-tab-height: 44px;
68
233
  --app-tab-icon-size: 18px;
69
234
  --app-tab-strip-padding-y: 14px;
70
235
  --app-tab-gap: 8px;
71
236
  --app-tab-radius: 12px;
72
237
 
73
- /* Brand color ramps */
74
- --app-color-brand-50: #fff5f0;
75
- --app-color-brand-100: #ffe8dc;
76
- --app-color-brand-200: #ffd0b8;
77
- --app-color-brand-300: #ffb088;
78
- --app-color-brand-400: #ff8c5a;
79
- --app-color-brand-500: #e86a3a;
80
- --app-color-brand-600: #cc5429;
81
- --app-color-brand-700: #a6421f;
82
- --app-color-brand-800: #803317;
83
- --app-color-brand-900: #5c2410;
84
-
85
- /* Gray ramps (for components referencing --app-color-gray-N) */
86
- --app-color-gray-50: #f7fafc;
87
- --app-color-gray-100: #edf2f7;
88
- --app-color-gray-200: #e2e8f0;
89
- --app-color-gray-300: #cbd5e0;
90
- --app-color-gray-400: #a0aec0;
91
- --app-color-gray-500: #718096;
92
- --app-color-gray-600: #4a5568;
93
- --app-color-gray-700: #2d3748;
94
- --app-color-gray-800: #1a202c;
95
- --app-color-gray-900: #171923;
96
-
97
238
  /* Mode-active colors (overwritten by app at mode-switch time) */
98
- --mode-primary-color: var(--app-color-brand-500);
99
- --mode-input-border-color: var(--app-color-brand-500);
100
- --mode-input-hover-border-color: var(--app-color-brand-600);
101
- --mode-input-focus-shadow-color: var(--app-color-brand-500);
239
+ --mode-primary-color: #E86A3A;
240
+ --mode-input-border-color: #E86A3A;
241
+ --mode-input-hover-border-color: #CC5429;
242
+ --mode-input-focus-shadow-color: #E86A3A;
243
+ }
244
+ .dark {
245
+ --background: 222.2 84% 4.9%;
246
+ --foreground: 210 40% 98%;
247
+ --card: 222.2 84% 4.9%;
248
+ --card-foreground: 210 40% 98%;
249
+ --popover: 222.2 84% 4.9%;
250
+ --popover-foreground: 210 40% 98%;
251
+ --primary: 210 40% 98%;
252
+ --primary-foreground: 222.2 47.4% 11.2%;
253
+ --secondary: 217.2 32.6% 17.5%;
254
+ --secondary-foreground: 210 40% 98%;
255
+ --muted: 217.2 32.6% 17.5%;
256
+ --muted-foreground: 215 20.2% 65.1%;
257
+ --accent: 217.2 32.6% 17.5%;
258
+ --accent-foreground: 210 40% 98%;
259
+ --destructive: 0 62.8% 30.6%;
260
+ --destructive-foreground: 210 40% 98%;
261
+ --border: 217.2 32.6% 17.5%;
262
+ --input: 217.2 32.6% 17.5%;
263
+ --ring: 212.7 26.8% 83.9%;
102
264
  }
package/dist/index.cjs CHANGED
@@ -14,22 +14,13 @@ var clsx2__default = /*#__PURE__*/_interopDefault(clsx2);
14
14
 
15
15
  // src/theme/index.ts
16
16
 
17
+ // src/theme/tokens.generated.ts
18
+ var brandRamp = { 50: "#FFF5F0", 100: "#FFE8DC", 200: "#FFD0B8", 300: "#FFB088", 400: "#FF8C5A", 500: "#E86A3A", 600: "#CC5429", 700: "#A6421F", 800: "#803317", 900: "#5C2410" };
19
+
17
20
  // src/theme/colors.ts
18
21
  var colors = {
19
- // Primary brand color - Politogy Red/Orange (Energy & Action)
20
- brand: {
21
- 50: "#fff5f0",
22
- 100: "#ffe8dc",
23
- 200: "#ffd0b8",
24
- 300: "#ffb088",
25
- 400: "#ff8c5a",
26
- 500: "#e86a3a",
27
- // Primary brand color - burnt orange/red
28
- 600: "#cc5429",
29
- 700: "#a6421f",
30
- 800: "#803317",
31
- 900: "#5c2410"
32
- },
22
+ // Primary brand color - Politogy burnt-orange (Energy & Action). From Specs.
23
+ brand: { ...brandRamp },
33
24
  // Politogy Brand Colors
34
25
  politogy: {
35
26
  // Legacy Blue - kept for reference
@@ -45,19 +36,8 @@ var colors = {
45
36
  800: "#2a4365",
46
37
  900: "#1a365d"
47
38
  },
48
- // Primary Red/Orange - Energy & Action (main brand color)
49
- red: {
50
- 50: "#fff5f0",
51
- 100: "#ffe8dc",
52
- 200: "#ffd0b8",
53
- 300: "#ffb088",
54
- 400: "#ff8c5a",
55
- 500: "#e86a3a",
56
- 600: "#cc5429",
57
- 700: "#a6421f",
58
- 800: "#803317",
59
- 900: "#5c2410"
60
- },
39
+ // Primary Red/Orange - Energy & Action (main brand color). From Specs.
40
+ red: { ...brandRamp },
61
41
  // Navy - Professional & Authoritative
62
42
  navy: {
63
43
  50: "#e8eef6",
@@ -1275,6 +1255,20 @@ function getUserDisplayName(user) {
1275
1255
  if (!user) return "User";
1276
1256
  return user.profile?.full_name || user.profile?.name || user.user_metadata?.full_name || user.user_metadata?.name || (typeof user.email === "string" ? user.email.split("@")[0] : "") || user.email || "User";
1277
1257
  }
1258
+
1259
+ // src/lib/resolveModeConfig.ts
1260
+ function resolveModeConfig(mode) {
1261
+ if (!mode) return null;
1262
+ const modes = mode.modes ?? [];
1263
+ if (typeof mode.getModeConfig === "function") {
1264
+ try {
1265
+ const result = mode.getModeConfig();
1266
+ if (result) return result;
1267
+ } catch {
1268
+ }
1269
+ }
1270
+ return modes.find((m) => m.id === mode.currentMode) ?? modes[0] ?? null;
1271
+ }
1278
1272
  function avatarUrlFromUser(user) {
1279
1273
  if (!user) return void 0;
1280
1274
  const meta = user.raw.user_metadata;
@@ -1299,7 +1293,7 @@ function UserMenu() {
1299
1293
  if (status !== "authenticated" || !user) return null;
1300
1294
  const displayName = getUserDisplayName(user.raw);
1301
1295
  const avatarUrl = userAvatarUrl ?? avatarUrlFromUser(user);
1302
- const modeConfig = mode?.getModeConfig() ?? null;
1296
+ const modeConfig = resolveModeConfig(mode);
1303
1297
  const currentOrg = org?.currentOrg ?? null;
1304
1298
  const linkedAccounts = linked?.linkedAccounts ?? [];
1305
1299
  const activeRoleConfig = role?.activeRoleConfig ?? null;
@@ -1587,10 +1581,10 @@ function NavItem({
1587
1581
  const navigate = reactRouterDom.useNavigate();
1588
1582
  const mode = useShellMode();
1589
1583
  const currentMode = mode?.currentMode ?? null;
1590
- const modeConfig = mode?.getModeConfig() ?? null;
1584
+ const modeConfig = resolveModeConfig(mode);
1591
1585
  const navPref = useShellNavigationPreference();
1592
1586
  const showSubMenu = navPref?.showSubMenu ?? true;
1593
- const navHoverGradient = currentMode === MODE_CAMPAIGN ? "linear-gradient(90deg, #F2C94C 0%, #E63946 100%)" : currentMode === MODE_PETITION ? "linear-gradient(90deg, #F2C94C 0%, #007BFF 100%)" : "linear-gradient(90deg, #007BFF 0%, #E63946 100%)";
1587
+ const navHoverGradient = currentMode === MODE_CAMPAIGN ? "linear-gradient(90deg, #F2C94C 0%, #E63946 100%)" : currentMode === MODE_PETITION ? "linear-gradient(90deg, #F2C94C 0%, #007BFF 100%)" : "linear-gradient(90deg, #ea6a3a 0%, #E63946 100%)";
1594
1588
  const gradientSweepBefore = {
1595
1589
  content: '""',
1596
1590
  position: "absolute",
@@ -2134,8 +2128,8 @@ var OrgPill_default = OrgPill;
2134
2128
  function ModePill() {
2135
2129
  const mode = useShellMode();
2136
2130
  if (!mode) return null;
2137
- const { currentMode, modes, switchMode, getModeConfig } = mode;
2138
- const modeConfig = getModeConfig();
2131
+ const { currentMode, modes, switchMode } = mode;
2132
+ const modeConfig = resolveModeConfig(mode);
2139
2133
  if (!modeConfig) return null;
2140
2134
  const hasMultipleModes = modes.length > 1;
2141
2135
  const isDisabled = !hasMultipleModes;