@the-portland-company/shell 0.71.0 → 0.72.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,19 @@
1
+ ## 0.72.0
2
+
3
+ Left-Navigation §5, §10 and §12.
4
+ - The rail takes its mode colour from the current mode label when no registry modeId is passed, as in VRM core. Campaign Mode is now Deep Race Red, not ink.
5
+ - The Campaign rail tokens are #A12430 for raised wells, #F0CFD2 for inactive row text and #F6DADD for the eyebrow.
6
+ - The footer user row shows the name as "First L.", with two-letter initials.
7
+ - Flat bar per §8.2: a row with children is drawn as one link with no chevron and no accordion. It points to its own page or its first child's, and it is active on any route beneath it. The children stay in the tree for breadcrumbs and tabs.
8
+ - Icons per §8.1: standardPolitogyNav falls back to the shared Lucide map when no `icons` are passed, so VRM core rows get icons. The map moved to native/standardNavIcons and app-chrome re-exports it.
9
+ - Removed the tests for the retired accordion and Navigation Display sub-menu, and added §8.2 tests.
10
+
11
+ ## 0.71.1
12
+
13
+ Build line in the rail footer follows Specs/Left-Navigation §9 and Reference/UI-Platform Meta.
14
+ - Shown in production too, as `prod | <commit> | v<version>` over `date | author`.
15
+ - Two centered rows of plain mono text at 11px, with no chip and no leading or trailing pipes.
16
+
1
17
  ## 0.71.0
2
18
 
3
19
  Rail and top bar match the Specs/Left-Navigation Design.
@@ -6,8 +6,8 @@ var clsx = require('clsx');
6
6
  var fi = require('react-icons/fi');
7
7
  var reactDom = require('react-dom');
8
8
  var jsxRuntime = require('react/jsx-runtime');
9
- var devnotes = require('@the-portland-company/devnotes');
10
9
  var lu = require('react-icons/lu');
10
+ var devnotes = require('@the-portland-company/devnotes');
11
11
 
12
12
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
13
13
 
@@ -1931,6 +1931,45 @@ function useThemePreference() {
1931
1931
  );
1932
1932
  return { value, setValue };
1933
1933
  }
1934
+ var standardNavIcons = {
1935
+ home: lu.LuHouse,
1936
+ users: lu.LuUsers,
1937
+ mail: lu.LuMail,
1938
+ messageSquare: lu.LuMessageSquare,
1939
+ layout: lu.LuLayoutDashboard,
1940
+ activity: lu.LuActivity,
1941
+ lifeBuoy: lu.LuLifeBuoy,
1942
+ grid: lu.LuLayoutGrid,
1943
+ list: lu.LuList,
1944
+ upload: lu.LuUpload,
1945
+ edit: lu.LuPencil,
1946
+ fileText: lu.LuFileText,
1947
+ send: lu.LuSend,
1948
+ clock: lu.LuClock,
1949
+ user: lu.LuUser,
1950
+ barChart: lu.LuChartColumn,
1951
+ briefcase: lu.LuBriefcase,
1952
+ settings: lu.LuSettings,
1953
+ creditCard: lu.LuCreditCard,
1954
+ dollarSign: lu.LuDollarSign,
1955
+ share: lu.LuShare2,
1956
+ // Campaign / Petition mode slots.
1957
+ phone: lu.LuPhone,
1958
+ map: lu.LuMap,
1959
+ mapPin: lu.LuMapPin,
1960
+ target: lu.LuTarget,
1961
+ flag: lu.LuFlag,
1962
+ layoutDashboard: lu.LuLayoutDashboard,
1963
+ usersRound: lu.LuUsersRound,
1964
+ mousePointerClick: lu.LuMousePointerClick,
1965
+ clipboardList: lu.LuClipboardList,
1966
+ circleUser: lu.LuCircleUser,
1967
+ crosshair: lu.LuCrosshair,
1968
+ landmark: lu.LuLandmark,
1969
+ chartNoAxes: lu.LuChartNoAxesColumn,
1970
+ doorOpen: lu.LuDoorOpen,
1971
+ signpost: lu.LuSignpost
1972
+ };
1934
1973
 
1935
1974
  // src/native/standardNav.ts
1936
1975
  var CAMPAIGN_FEATURE_AVAILABILITY = {
@@ -2044,7 +2083,7 @@ var APP_SIGNS_CHILDREN = [
2044
2083
  function standardPolitogyNav(opts) {
2045
2084
  const base = opts.coreBaseUrl.replace(/\/+$/, "");
2046
2085
  const abs = (path) => `${base}${path.startsWith("/") ? path : `/${path}`}`;
2047
- const icon = (key) => opts.icons?.[key];
2086
+ const icon = (key) => opts.icons?.[key] ?? standardNavIcons[key];
2048
2087
  const orgParam = opts.activeOrgId ? `org=${opts.activeOrgId}` : "";
2049
2088
  const withOrg = (url) => orgParam ? `${url}${url.includes("?") ? "&" : "?"}${orgParam}` : url;
2050
2089
  const leaves = (children) => children.map((c) => ({ label: c.label, icon: icon(c.icon), href: withOrg(abs(c.path)) }));
@@ -2256,15 +2295,29 @@ function hasActiveChild(item, pathname, activeFn) {
2256
2295
  )
2257
2296
  );
2258
2297
  }
2259
- function envChipClasses(label) {
2260
- const l = label.toLowerCase();
2261
- if (l === "production" || l === "prod")
2262
- return "bg-[#fee2e2] text-[#ef4444]";
2263
- if (l === "staging" || l === "stage")
2264
- return "bg-[#ffedd5] text-[#f97316]";
2265
- if (l === "local" || l === "development" || l === "dev")
2266
- return "bg-[#dcfce7] text-[#22c55e]";
2267
- return "bg-[rgba(107,114,128,0.16)] text-[#6b7280]";
2298
+ function firstLeafHref(item) {
2299
+ for (const c of item.children ?? []) {
2300
+ if (c.kind === "section") continue;
2301
+ if (c.href) return c.href;
2302
+ const deep = firstLeafHref(c);
2303
+ if (deep) return deep;
2304
+ }
2305
+ return void 0;
2306
+ }
2307
+ function modeIdFromLabel(label) {
2308
+ const l = label?.trim().toLowerCase();
2309
+ return l ? l.replace(/\s+mode$/, "") : void 0;
2310
+ }
2311
+ function shortUserName(name) {
2312
+ const parts = name.trim().split(/\s+/).filter(Boolean);
2313
+ if (parts.length < 2) return parts[0] ?? "";
2314
+ return `${parts[0]} ${parts[parts.length - 1][0].toUpperCase()}.`;
2315
+ }
2316
+ function shortEnvLabel(label) {
2317
+ const l = label.trim().toLowerCase();
2318
+ if (l === "production") return "prod";
2319
+ if (l === "development") return "dev";
2320
+ return l;
2268
2321
  }
2269
2322
  var ORG_PILL_DEFAULT = "var(--color-accent)";
2270
2323
  var RailCollapsibleContext = react.createContext(false);
@@ -2366,7 +2419,6 @@ function NavSidebar({
2366
2419
  react.useEffect(() => {
2367
2420
  setOpenGroup(activeGroupKey);
2368
2421
  }, [activeGroupKey]);
2369
- const toggleGroup = (key) => setOpenGroup((prev) => prev === key ? null : key);
2370
2422
  return /* @__PURE__ */ jsxRuntime.jsxs(RailCollapsibleContext.Provider, { value: collapsible, children: [
2371
2423
  open ? /* @__PURE__ */ jsxRuntime.jsx(
2372
2424
  "div",
@@ -2706,21 +2758,16 @@ function NavSidebar({
2706
2758
  });
2707
2759
  }
2708
2760
  if (item.children && item.children.length > 0) {
2709
- const key = groupKeyOf(item, idx);
2710
2761
  const selfActive = item.href ? activeFn(pathname, item.href) : false;
2711
2762
  const parentActive = selfActive || hasActiveChild(item, pathname, activeFn);
2712
2763
  return /* @__PURE__ */ jsxRuntime.jsx(
2713
- NavGroup,
2764
+ NavRow,
2714
2765
  {
2715
- item,
2766
+ item: { ...item, href: item.href ?? firstLeafHref(item), children: void 0 },
2716
2767
  active: parentActive,
2717
- open: openGroup === key,
2718
- onToggle: () => toggleGroup(key),
2719
- pathname,
2720
- activeFn,
2721
2768
  Link
2722
2769
  },
2723
- key
2770
+ item.href ?? groupKeyOf(item, idx)
2724
2771
  );
2725
2772
  }
2726
2773
  const active = item.href ? activeFn(pathname, item.href) : false;
@@ -2755,41 +2802,26 @@ function NavSidebar({
2755
2802
  }
2756
2803
  )
2757
2804
  ] }, l.label)) }) : null,
2758
- footer.envLabel && footer.envLabel.trim().toLowerCase() !== "production" ? /* @__PURE__ */ jsxRuntime.jsxs(
2805
+ footer.envLabel || footer.commit || footer.version ? /* @__PURE__ */ jsxRuntime.jsxs(
2759
2806
  "div",
2760
2807
  {
2761
2808
  "data-shell-meta": true,
2762
- className: `mt-1.5 flex flex-col items-start gap-0.5 font-[family-name:var(--font-plex-mono)] text-[10px] text-[color:var(--sidebar-meta-text,var(--sidebar-text))] ${rc("max-md:hidden")}`,
2809
+ className: `mt-1.5 flex flex-col items-center gap-0.5 font-[family-name:var(--font-plex-mono)] text-[11px] text-[color:var(--sidebar-meta-text,rgba(255,255,255,0.4))] ${rc("max-md:hidden")}`,
2763
2810
  children: [
2764
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [
2765
- /* @__PURE__ */ jsxRuntime.jsx(
2766
- "span",
2767
- {
2768
- className: [
2769
- "rounded-md px-1.5 py-0.5 text-[10px] font-semibold",
2770
- envChipClasses(footer.envLabel)
2771
- ].join(" "),
2772
- children: footer.envLabel
2773
- }
2774
- ),
2775
- footer.commit ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2776
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[color:var(--muted)]", children: "|" }),
2777
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: footer.commit.slice(-4) })
2778
- ] }) : null,
2779
- footer.version ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2780
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[color:var(--muted)]", children: "|" }),
2781
- /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
2782
- "v",
2783
- footer.version
2784
- ] })
2785
- ] }) : null
2786
- ] }),
2811
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-1.5", children: [
2812
+ footer.envLabel ? shortEnvLabel(footer.envLabel) : null,
2813
+ footer.commit ? footer.commit.slice(-4) : null,
2814
+ footer.version ? `v${footer.version.replace(/^v/, "")}` : null
2815
+ ].filter((v) => Boolean(v)).map((v, i) => /* @__PURE__ */ jsxRuntime.jsxs(react.Fragment, { children: [
2816
+ i > 0 ? /* @__PURE__ */ jsxRuntime.jsx("span", { children: "|" }) : null,
2817
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: v })
2818
+ ] }, i)) }),
2787
2819
  footer.commitDate || footer.commitAuthor ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [
2788
2820
  footer.commitDate ? /* @__PURE__ */ jsxRuntime.jsx("span", { children: new Date(footer.commitDate).toLocaleDateString(void 0, {
2789
2821
  month: "short",
2790
2822
  day: "numeric"
2791
2823
  }) }) : null,
2792
- footer.commitDate && footer.commitAuthor ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[color:var(--muted)]", children: "|" }) : null,
2824
+ footer.commitDate && footer.commitAuthor ? /* @__PURE__ */ jsxRuntime.jsx("span", { children: "|" }) : null,
2793
2825
  footer.commitAuthor ? /* @__PURE__ */ jsxRuntime.jsx("span", { children: footer.commitAuthor.split(/\s+/)[0] }) : null
2794
2826
  ] }) : null
2795
2827
  ]
@@ -3023,142 +3055,6 @@ function NavRow({
3023
3055
  }
3024
3056
  );
3025
3057
  }
3026
- function NavGroup({
3027
- item,
3028
- active,
3029
- open,
3030
- onToggle,
3031
- pathname,
3032
- activeFn,
3033
- Link
3034
- }) {
3035
- const rc = useRailCollapse();
3036
- const Icon = item.icon;
3037
- const headerClasses = [
3038
- "group relative block w-full overflow-hidden text-left transition",
3039
- open ? "rounded-t-[5px]" : "rounded-[5px]",
3040
- active ? NAV_TOP_ACTIVE_CLASS : open ? "bg-[color:var(--sidebar-row-hover-bg,var(--panel))] text-[color:var(--sidebar-text,var(--foreground))]" : NAV_TOP_INACTIVE_CLASS
3041
- ].join(" ");
3042
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
3043
- /* @__PURE__ */ jsxRuntime.jsxs(
3044
- "button",
3045
- {
3046
- type: "button",
3047
- onClick: () => {
3048
- onToggle();
3049
- item.onClick?.();
3050
- },
3051
- "aria-expanded": open,
3052
- title: item.label,
3053
- className: headerClasses,
3054
- children: [
3055
- active ? /* @__PURE__ */ jsxRuntime.jsx(NavActiveRail, {}) : null,
3056
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: `relative z-10 flex items-center ${NAV_TOP_ROW_CLASS(active)} ${rc("max-md:justify-center max-md:px-0")}`, children: [
3057
- Icon ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { size: NAV_TOP_ICON_SIZE, className: "shrink-0 text-current" }) : null,
3058
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: `min-w-0 flex-1 truncate ${rc("max-md:hidden")}`, children: item.label }),
3059
- /* @__PURE__ */ jsxRuntime.jsx(NavRowBadge, { value: item.badge }),
3060
- /* @__PURE__ */ jsxRuntime.jsx(
3061
- ChevronDown,
3062
- {
3063
- className: [
3064
- "shrink-0 opacity-80 transition-transform duration-200",
3065
- open ? "rotate-180" : "",
3066
- rc("max-md:hidden")
3067
- ].join(" ")
3068
- }
3069
- )
3070
- ] })
3071
- ]
3072
- }
3073
- ),
3074
- open ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: `overflow-hidden rounded-b-md bg-[color:var(--nav-submenu-bg)] p-1 ${rc("max-md:hidden")}`, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-0.5", children: item.children.map((child, i) => {
3075
- if (child.kind === "section") {
3076
- return /* @__PURE__ */ jsxRuntime.jsx(
3077
- NavSectionRow,
3078
- {
3079
- label: child.label,
3080
- first: i === 0,
3081
- variant: "sub"
3082
- },
3083
- `sub-section-${i}-${child.label}`
3084
- );
3085
- }
3086
- const childActive = child.href ? activeFn(pathname, child.href) : false;
3087
- if (child.children && child.children.length > 0) {
3088
- return /* @__PURE__ */ jsxRuntime.jsx(
3089
- NavSubGroup,
3090
- {
3091
- item: child,
3092
- pathname,
3093
- activeFn,
3094
- Link
3095
- },
3096
- child.href ?? `subgrp-${i}-${child.label}`
3097
- );
3098
- }
3099
- return /* @__PURE__ */ jsxRuntime.jsx(
3100
- NavRow,
3101
- {
3102
- item: child,
3103
- active: childActive,
3104
- Link,
3105
- variant: "sub"
3106
- },
3107
- child.href ?? `sub-${i}-${child.label}`
3108
- );
3109
- }) }) }) : null
3110
- ] });
3111
- }
3112
- function NavSubGroup({
3113
- item,
3114
- pathname,
3115
- activeFn,
3116
- Link
3117
- }) {
3118
- const ownsActive = (item.href ? activeFn(pathname, item.href) : false) || (item.children ?? []).some((c) => c.href ? activeFn(pathname, c.href) : false);
3119
- const [open, setOpen] = react.useState(ownsActive);
3120
- react.useEffect(() => {
3121
- if (ownsActive) setOpen(true);
3122
- }, [ownsActive]);
3123
- const Icon = item.icon;
3124
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
3125
- /* @__PURE__ */ jsxRuntime.jsxs(
3126
- "button",
3127
- {
3128
- type: "button",
3129
- onClick: () => setOpen((v) => !v),
3130
- "aria-expanded": open,
3131
- className: [
3132
- "flex w-full items-center gap-2.5 rounded px-3 py-1.5 text-left text-[13px] font-medium transition",
3133
- ownsActive ? "text-[color:var(--color-accent)]" : "text-[color:var(--sidebar-text,var(--muted))] hover:bg-[color:var(--nav-row-hover)] hover:text-[color:var(--foreground)]"
3134
- ].join(" "),
3135
- children: [
3136
- Icon ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { size: 15, className: "shrink-0 text-current" }) : null,
3137
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1", children: item.label }),
3138
- /* @__PURE__ */ jsxRuntime.jsx(
3139
- ChevronDown,
3140
- {
3141
- className: [
3142
- "shrink-0 opacity-70 transition-transform duration-200",
3143
- open ? "rotate-180" : ""
3144
- ].join(" ")
3145
- }
3146
- )
3147
- ]
3148
- }
3149
- ),
3150
- open ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ml-4 flex flex-col gap-0.5 border-l border-[color:var(--line)] pl-2", children: item.children.map((leaf, i) => /* @__PURE__ */ jsxRuntime.jsx(
3151
- NavRow,
3152
- {
3153
- item: leaf,
3154
- active: leaf.href ? activeFn(pathname, leaf.href) : false,
3155
- Link,
3156
- variant: "sub"
3157
- },
3158
- leaf.href ?? `subsub-${i}-${leaf.label}`
3159
- )) }) : null
3160
- ] });
3161
- }
3162
3058
  function OrgIdCopyButton({ id }) {
3163
3059
  const [copied, setCopied] = react.useState(false);
3164
3060
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -3458,8 +3354,8 @@ function NavSidebarUserCard({
3458
3354
  }) {
3459
3355
  const rawName = user.name?.trim() || "";
3460
3356
  const hasRealName = rawName.length > 0 && !rawName.includes("@");
3461
- const displayName = hasRealName ? rawName : user.email ?? "Signed in";
3462
- const initials = (hasRealName ? rawName : user.email ?? "P").slice(0, 2).toUpperCase();
3357
+ const displayName = hasRealName ? shortUserName(rawName) : user.email ?? "Signed in";
3358
+ const initials = hasRealName ? rawName.split(/\s+/).filter(Boolean).slice(0, 2).map((w) => w[0]).join("").toUpperCase() : (user.email ?? "P").slice(0, 2).toUpperCase();
3463
3359
  const secondary = hasRealName ? user.email || [user.tier, user.role].filter(Boolean).join(" \xB7 ") || "member" : [user.tier, user.role].filter(Boolean).join(" \xB7 ") || "member";
3464
3360
  const rc = useRailCollapse();
3465
3361
  return /* @__PURE__ */ jsxRuntime.jsxs("details", { "data-nav-details": true, className: "group/acct relative", children: [
@@ -3775,7 +3671,7 @@ function NavAppShell({
3775
3671
  return /* @__PURE__ */ jsxRuntime.jsxs(
3776
3672
  "div",
3777
3673
  {
3778
- "data-politogy-mode": modeId,
3674
+ "data-politogy-mode": modeId ?? modeIdFromLabel(sidebar.mode?.label),
3779
3675
  className: "min-h-screen bg-[color:var(--background)] text-[color:var(--foreground)]",
3780
3676
  children: [
3781
3677
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -4791,45 +4687,6 @@ function FrameBody({
4791
4687
  )
4792
4688
  ] });
4793
4689
  }
4794
- var standardNavIcons = {
4795
- home: lu.LuHouse,
4796
- users: lu.LuUsers,
4797
- mail: lu.LuMail,
4798
- messageSquare: lu.LuMessageSquare,
4799
- layout: lu.LuLayoutDashboard,
4800
- activity: lu.LuActivity,
4801
- lifeBuoy: lu.LuLifeBuoy,
4802
- grid: lu.LuLayoutGrid,
4803
- list: lu.LuList,
4804
- upload: lu.LuUpload,
4805
- edit: lu.LuPencil,
4806
- fileText: lu.LuFileText,
4807
- send: lu.LuSend,
4808
- clock: lu.LuClock,
4809
- user: lu.LuUser,
4810
- barChart: lu.LuChartColumn,
4811
- briefcase: lu.LuBriefcase,
4812
- settings: lu.LuSettings,
4813
- creditCard: lu.LuCreditCard,
4814
- dollarSign: lu.LuDollarSign,
4815
- share: lu.LuShare2,
4816
- // Campaign / Petition mode slots.
4817
- phone: lu.LuPhone,
4818
- map: lu.LuMap,
4819
- mapPin: lu.LuMapPin,
4820
- target: lu.LuTarget,
4821
- flag: lu.LuFlag,
4822
- layoutDashboard: lu.LuLayoutDashboard,
4823
- usersRound: lu.LuUsersRound,
4824
- mousePointerClick: lu.LuMousePointerClick,
4825
- clipboardList: lu.LuClipboardList,
4826
- circleUser: lu.LuCircleUser,
4827
- crosshair: lu.LuCrosshair,
4828
- landmark: lu.LuLandmark,
4829
- chartNoAxes: lu.LuChartNoAxesColumn,
4830
- doorOpen: lu.LuDoorOpen,
4831
- signpost: lu.LuSignpost
4832
- };
4833
4690
  var ORG_STORAGE_KEY = "vrm.activeOrg";
4834
4691
  var MODE_STORAGE_KEY = "vrm.activeMode";
4835
4692
  var CORE_BASE_URL = "https://app.politogyvrm.com";