@the-portland-company/shell 0.10.2 → 0.11.1

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,3 +1,50 @@
1
+ ## 0.11.1 — 2026-07-20
2
+
3
+ ### Fixed (packaging — native install)
4
+
5
+ - **Chakra-only peers are now `optional`** via `peerDependenciesMeta`
6
+ (`@chakra-ui/react`, `@emotion/react`, `@emotion/styled`,
7
+ `@supabase/supabase-js`, `react-icons`, `react-router-dom`). The `./native`
8
+ export imports none of them — only `react`/`react-dom` (still required) — so
9
+ the Next.js worker apps (Forms/Surveys/Polls) can `npm i
10
+ @the-portland-company/shell` without `ERESOLVE` and without
11
+ `--legacy-peer-deps`. The Chakra `.` export still expects these at runtime;
12
+ apps that use it already install them.
13
+ - **Relaxed the `@supabase/supabase-js` peer range** from `~2.95.3`
14
+ (`>=2.95.3 <2.96`) to `>=2.95.3 <3` so it no longer conflicts with apps on a
15
+ newer 2.x (e.g. surveys on `^2.106.2`).
16
+
17
+ ## 0.11.0 — 2026-07-20
18
+
19
+ ### Added (native — icon-only tab strip)
20
+
21
+ - **`NavTabStrip`** in the `./native` export: the standard icon-only secondary
22
+ navigation the Next.js worker apps (Forms / Surveys / Polls) render below the
23
+ top bar, promoted from each app's hand-rolled `.tabstrip`/`.tab-strip` markup
24
+ into the shell so they stop diverging. Icon-only tabs with `role="tablist"` /
25
+ `role="tab"`, `title` tooltips + `aria-label`; the **selected tab merges into
26
+ the content panel** (top/left/right border, no bottom border, shared
27
+ `--tab-content-bg`). All geometry is driven by the Brand `--tab-*` CSS vars
28
+ (`--tab-height`, `--tab-icon-size`, `--tab-radius`, `--tab-gap`,
29
+ `--tab-content-bg`, `--tab-unselected-opacity` [default `0.6`]) with literal
30
+ fallbacks. Items are `{ key, label, icon, href?, onSelect?, selected,
31
+ disabled?, disabledTitle? }`; `href` tabs render through the injectable
32
+ `linkComponent` (Next `<Link>`), no-`href` tabs render as `<button>`, and
33
+ `disabled` tabs render a dimmed non-interactive span (e.g. Surveys' Canvas /
34
+ Results before a survey is chosen). Pass `children` to render the merged
35
+ content panel, or omit to compose your own. No Chakra, no react-router.
36
+ - `NavLinkProps` widened with optional passthrough attributes
37
+ (`role`, `aria-selected`, `aria-label`, `title`, `style`) so one injected link
38
+ can back both a nav row and a routed tab.
39
+ - Tokens regenerated from Specs (`gen:tokens`, tokens v38).
40
+
41
+ ## 0.10.3 — 2026-07-13
42
+
43
+ ### Fixed (sidebar)
44
+
45
+ - **Sidebar width is driven by the `--sidebar-width` Brand token** (default
46
+ `240px`) so the Chakra `Sidebar` tracks the Brand API value.
47
+
1
48
  ## 0.10.2 — 2026-07-13
2
49
 
3
50
  ### Security (XSS) + UI
package/dist/chrome.css CHANGED
@@ -135,7 +135,7 @@
135
135
  --form-label-padding-x: 6px;
136
136
 
137
137
  /* Tabs */
138
- --tab-header-bg: #888888;
138
+ --tab-header-bg: transparent;
139
139
  --tab-row-bg: #F0F4F8;
140
140
  --tab-content-bg: #F8FAFC;
141
141
  --tab-height: 44px;
@@ -145,7 +145,7 @@
145
145
  --tab-radius: 12px;
146
146
 
147
147
  /* Sidebar (nav chrome) */
148
- --sidebar-width: 140px;
148
+ --sidebar-width: 240px;
149
149
  --sidebar-bg: #FFFFFF;
150
150
  --sidebar-border: #E2E8F0;
151
151
  --sidebar-brand-logo-height: 40px;
package/dist/index.cjs CHANGED
@@ -1521,19 +1521,19 @@ function Header({ onOpen, id }) {
1521
1521
  /* @__PURE__ */ jsxRuntime.jsxs(
1522
1522
  react.HStack,
1523
1523
  {
1524
- spacing: 2,
1524
+ spacing: "var(--topbar-icon-gap, 8px)",
1525
1525
  mr: "15px",
1526
- color: "gray.600",
1526
+ color: "var(--topbar-icon-color, #4A5568)",
1527
1527
  sx: {
1528
1528
  '& button:not([role="menuitem"]):not([data-menu-item])': {
1529
- width: "32px",
1530
- height: "32px",
1531
- minWidth: "32px",
1529
+ width: "var(--topbar-icon-button-size, 32px)",
1530
+ height: "var(--topbar-icon-button-size, 32px)",
1531
+ minWidth: "var(--topbar-icon-button-size, 32px)",
1532
1532
  padding: 0
1533
1533
  },
1534
- '& button:not([role="menuitem"]):not([data-menu-item]) svg': {
1535
- width: "20px",
1536
- height: "20px"
1534
+ '& button:not([role="menuitem"]):not([data-menu-item]) svg, & button:not([role="menuitem"]):not([data-menu-item]) img': {
1535
+ width: "var(--topbar-icon-size, 20px)",
1536
+ height: "var(--topbar-icon-size, 20px)"
1537
1537
  }
1538
1538
  },
1539
1539
  children: [
@@ -2488,7 +2488,7 @@ function Sidebar({ onClose, id }) {
2488
2488
  bg: sidebarBg,
2489
2489
  borderRight: "1px",
2490
2490
  borderRightColor: sidebarBorderColor,
2491
- w: { base: "full", md: 60 },
2491
+ w: { base: "full", md: "var(--sidebar-width, 240px)" },
2492
2492
  pos: "fixed",
2493
2493
  h: "100vh",
2494
2494
  direction: "column",
@@ -2655,6 +2655,53 @@ function AppLayout({
2655
2655
  overlaySlot
2656
2656
  ] });
2657
2657
  }
2658
+ function useSlotGlyphName(slot) {
2659
+ const [name, setName] = react$1.useState(null);
2660
+ react$1.useEffect(() => {
2661
+ if (typeof window === "undefined") return;
2662
+ const read = () => {
2663
+ const raw = window.getComputedStyle(document.documentElement).getPropertyValue(`--topbar-icon-${slot}`).trim().replace(/^['"]|['"]$/g, "");
2664
+ setName(raw.length > 0 ? raw : null);
2665
+ };
2666
+ read();
2667
+ const observer = new MutationObserver(read);
2668
+ observer.observe(document.head, { childList: true, subtree: true });
2669
+ return () => observer.disconnect();
2670
+ }, [slot]);
2671
+ return name;
2672
+ }
2673
+ var TopBarIcon = react$1.forwardRef(
2674
+ function TopBarIcon2({ slot, fallbackIcon, isSelected, ...rest }, ref) {
2675
+ const glyphName = useSlotGlyphName(slot);
2676
+ const icon = glyphName ? /* @__PURE__ */ jsxRuntime.jsx(
2677
+ BrandIcon,
2678
+ {
2679
+ name: glyphName,
2680
+ style: {
2681
+ width: "var(--topbar-icon-size, 20px)",
2682
+ height: "var(--topbar-icon-size, 20px)"
2683
+ }
2684
+ }
2685
+ ) : fallbackIcon;
2686
+ return /* @__PURE__ */ jsxRuntime.jsx(
2687
+ react.IconButton,
2688
+ {
2689
+ ref,
2690
+ icon,
2691
+ variant: "ghost",
2692
+ minW: "var(--topbar-icon-button-size, 32px)",
2693
+ h: "var(--topbar-icon-button-size, 32px)",
2694
+ bg: isSelected ? "var(--topbar-icon-selected-bg, transparent)" : "transparent",
2695
+ color: isSelected ? "var(--topbar-icon-selected-color, var(--topbar-icon-color, #64748B))" : "var(--topbar-icon-color, #64748B)",
2696
+ _hover: {
2697
+ bg: "var(--topbar-icon-hover-bg, transparent)",
2698
+ color: "var(--topbar-icon-hover-color, var(--topbar-icon-color, #64748B))"
2699
+ },
2700
+ ...rest
2701
+ }
2702
+ );
2703
+ }
2704
+ );
2658
2705
  var SUPPORT_REQUEST_SUCCESS_MESSAGE = "Your support request was sent to Politogy leadership.";
2659
2706
  function splitName(value) {
2660
2707
  const trimmed = String(value ?? "").trim();
@@ -3055,6 +3102,7 @@ exports.ShellRoleContext = ShellRoleContext;
3055
3102
  exports.ShellSupportProviderContext = ShellSupportProviderContext;
3056
3103
  exports.Sidebar = Sidebar;
3057
3104
  exports.SupportRequestButton = SupportRequestButton;
3105
+ exports.TopBarIcon = TopBarIcon;
3058
3106
  exports.UserMenu = UserMenu;
3059
3107
  exports.buildSpeculationRules = buildSpeculationRules;
3060
3108
  exports.getOrganizationBadgeLetters = getOrganizationBadgeLetters;