@the-portland-company/shell 0.25.0 → 0.27.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,3 +1,46 @@
1
+ ## 0.27.0 — 2026-07-23
2
+
3
+ VRM native-chrome parity (shell-nav-handoff §1–§3). All three were verified in
4
+ this package's source before changing anything, and all three are additive —
5
+ forms / surveys / polls / contacts behavior is unchanged.
6
+
7
+ ### Added — `NavAppShell contentWrapper`
8
+
9
+ `'padded'` (default) keeps the existing `<main class="px-6 py-7">` + `mx-auto
10
+ max-w-7xl`. `'bare'` renders children directly. VRM wraps every page in the
11
+ shell's own `PageContainer` — which emits its OWN `<main>` and max-width — so
12
+ the default gave it a **nested `<main>`** (invalid HTML) and a doubled gutter on
13
+ Org Settings, Email Broadcast and SMS/MMS Blast.
14
+
15
+ ### Added — nav group headers can navigate as well as expand
16
+
17
+ A group header always toggles; when the item also carries an `onClick`, that now
18
+ runs too, so a parent row can open its section dashboard AND expand in one
19
+ click. Existing apps pass no parent `onClick`, so nothing changes for them.
20
+
21
+ ### Added — `NavItem.disabled` + `NavItem.disabledTitle`
22
+
23
+ Mirrors `NavTab`'s existing contract: a gated row renders as a non-interactive,
24
+ dimmed `aria-disabled` span with its reason as a tooltip. VRM gates Email / SMS
25
+ until setup is complete and previously had no way to express that, so gated rows
26
+ rendered fully clickable.
27
+
28
+ ## 0.26.0 — 2026-07-23
29
+
30
+ ### Added — `PolitogyAppFrame` data plumbing (so Contacts can adopt it)
31
+
32
+ `PolitogyFrameData` (the optional `data` prop) carries the things that differ by
33
+ **where an app's data comes from**, never by how its chrome looks:
34
+
35
+ - `fetchJson` / `devNotes` — for apps that authenticate client-side instead of
36
+ through a same-origin `/api/chrome/*` proxy (Contacts).
37
+ - `org` — controlled active-org state for apps that own switching.
38
+ - `contactsItem` — app-owned children for a standard nav entry the app owns.
39
+ - `fallbackAvatarUrl` — avatar fallback (stored memoji / default) when the
40
+ profile has no uploaded photo.
41
+
42
+ Adding a *visual* option here would re-open the drift this component closes.
43
+
1
44
  ## 0.25.0 — 2026-07-23
2
45
 
3
46
  ### Added — `PolitogyAppFrame`: one component owns the whole app frame
@@ -1313,6 +1313,17 @@ function NavRow({
1313
1313
  item.label
1314
1314
  ] })
1315
1315
  ] });
1316
+ if (item.disabled) {
1317
+ return /* @__PURE__ */ jsxRuntime.jsx(
1318
+ "span",
1319
+ {
1320
+ "aria-disabled": "true",
1321
+ title: item.disabledTitle,
1322
+ className: `${wrapperClasses} cursor-not-allowed opacity-50`,
1323
+ children: inner
1324
+ }
1325
+ );
1326
+ }
1316
1327
  if (item.onClick && !item.href) {
1317
1328
  return /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: item.onClick, className: `w-full text-left ${wrapperClasses}`, children: inner });
1318
1329
  }
@@ -1346,7 +1357,10 @@ function NavGroup({
1346
1357
  "button",
1347
1358
  {
1348
1359
  type: "button",
1349
- onClick: onToggle,
1360
+ onClick: () => {
1361
+ onToggle();
1362
+ item.onClick?.();
1363
+ },
1350
1364
  "aria-expanded": open,
1351
1365
  className: headerClasses,
1352
1366
  children: [
@@ -1866,14 +1880,20 @@ function useCloseNavDetailsOnOutsideClick() {
1866
1880
  };
1867
1881
  }, []);
1868
1882
  }
1869
- function NavAppShell({ sidebar, topBar, betaBanner, children }) {
1883
+ function NavAppShell({
1884
+ sidebar,
1885
+ topBar,
1886
+ betaBanner,
1887
+ contentWrapper = "padded",
1888
+ children
1889
+ }) {
1870
1890
  useCloseNavDetailsOnOutsideClick();
1871
1891
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-h-screen bg-[color:var(--background)] text-[color:var(--foreground)]", children: [
1872
1892
  /* @__PURE__ */ jsxRuntime.jsx(NavSidebar, { ...sidebar }),
1873
1893
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lg:pl-[240px]", children: [
1874
1894
  /* @__PURE__ */ jsxRuntime.jsx(NavTopBar, { ...topBar }),
1875
1895
  betaBanner ? /* @__PURE__ */ jsxRuntime.jsx(NavBetaBanner, { ...betaBanner }) : null,
1876
- /* @__PURE__ */ jsxRuntime.jsx("main", { className: "px-6 py-7", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-auto max-w-7xl", children }) })
1896
+ contentWrapper === "bare" ? children : /* @__PURE__ */ jsxRuntime.jsx("main", { className: "px-6 py-7", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-auto max-w-7xl", children }) })
1877
1897
  ] })
1878
1898
  ] });
1879
1899
  }
@@ -1963,6 +1983,7 @@ function PolitogyAppFrame({
1963
1983
  session,
1964
1984
  onSignOut,
1965
1985
  buildMeta,
1986
+ data,
1966
1987
  children
1967
1988
  }) {
1968
1989
  const meta = POLITOGY_APPS[app];
@@ -1970,6 +1991,9 @@ function PolitogyAppFrame({
1970
1991
  PolitogyAppChrome,
1971
1992
  {
1972
1993
  storagePrefix: meta.storagePrefix,
1994
+ fetchJson: data?.fetchJson,
1995
+ devNotes: data?.devNotes,
1996
+ org: data?.org,
1973
1997
  identity: {
1974
1998
  userId: session.userId,
1975
1999
  userName: session.userName ?? null,
@@ -1986,6 +2010,7 @@ function PolitogyAppFrame({
1986
2010
  session,
1987
2011
  onSignOut,
1988
2012
  buildMeta,
2013
+ data,
1989
2014
  children
1990
2015
  }
1991
2016
  )
@@ -2000,6 +2025,7 @@ function FrameBody({
2000
2025
  session,
2001
2026
  onSignOut,
2002
2027
  buildMeta,
2028
+ data,
2003
2029
  children
2004
2030
  }) {
2005
2031
  const meta = POLITOGY_APPS[app];
@@ -2016,8 +2042,14 @@ function FrameBody({
2016
2042
  const activeCoreOrg = coreOrgs.find((o) => o.id === chrome?.orgId) ?? null;
2017
2043
  const navDisplay = chrome?.navDisplay ?? "both";
2018
2044
  const navItems = react.useMemo(
2019
- () => standardPolitogyNav({ coreBaseUrl: POLITOGY_APPS.vrm.origin, icons: standardNavIcons }),
2020
- []
2045
+ () => standardPolitogyNav({
2046
+ coreBaseUrl: POLITOGY_APPS.vrm.origin,
2047
+ icons: standardNavIcons,
2048
+ // An app that OWNS a standard nav entry (Contacts) nests its own routes
2049
+ // under it. This is routing data, not chrome configuration.
2050
+ contactsItem: data?.contactsItem
2051
+ }),
2052
+ [data?.contactsItem]
2021
2053
  );
2022
2054
  const breadcrumbs = react.useMemo(
2023
2055
  () => deriveBreadcrumbs(navItems, pathname, { search, origin: meta.origin }),
@@ -2056,7 +2088,9 @@ function FrameBody({
2056
2088
  user: {
2057
2089
  name: identity.userName,
2058
2090
  email: identity.email,
2059
- avatarUrl: identity.avatarUrl,
2091
+ // The chrome API's avatar wins; the app may supply a fallback chain
2092
+ // (e.g. a stored memoji) for users with no uploaded photo.
2093
+ avatarUrl: identity.avatarUrl ?? data?.fallbackAvatarUrl ?? null,
2060
2094
  role: session.role
2061
2095
  },
2062
2096
  userMenu,