@the-portland-company/shell 0.71.1 → 0.72.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,13 @@
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
+
1
11
  ## 0.71.1
2
12
 
3
13
  Build line in the rail footer follows Specs/Left-Navigation §9 and Reference/UI-Platform Meta.
@@ -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,6 +2295,24 @@ function hasActiveChild(item, pathname, activeFn) {
2256
2295
  )
2257
2296
  );
2258
2297
  }
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
+ }
2259
2316
  function shortEnvLabel(label) {
2260
2317
  const l = label.trim().toLowerCase();
2261
2318
  if (l === "production") return "prod";
@@ -2362,7 +2419,6 @@ function NavSidebar({
2362
2419
  react.useEffect(() => {
2363
2420
  setOpenGroup(activeGroupKey);
2364
2421
  }, [activeGroupKey]);
2365
- const toggleGroup = (key) => setOpenGroup((prev) => prev === key ? null : key);
2366
2422
  return /* @__PURE__ */ jsxRuntime.jsxs(RailCollapsibleContext.Provider, { value: collapsible, children: [
2367
2423
  open ? /* @__PURE__ */ jsxRuntime.jsx(
2368
2424
  "div",
@@ -2702,21 +2758,16 @@ function NavSidebar({
2702
2758
  });
2703
2759
  }
2704
2760
  if (item.children && item.children.length > 0) {
2705
- const key = groupKeyOf(item, idx);
2706
2761
  const selfActive = item.href ? activeFn(pathname, item.href) : false;
2707
2762
  const parentActive = selfActive || hasActiveChild(item, pathname, activeFn);
2708
2763
  return /* @__PURE__ */ jsxRuntime.jsx(
2709
- NavGroup,
2764
+ NavRow,
2710
2765
  {
2711
- item,
2766
+ item: { ...item, href: item.href ?? firstLeafHref(item), children: void 0 },
2712
2767
  active: parentActive,
2713
- open: openGroup === key,
2714
- onToggle: () => toggleGroup(key),
2715
- pathname,
2716
- activeFn,
2717
2768
  Link
2718
2769
  },
2719
- key
2770
+ item.href ?? groupKeyOf(item, idx)
2720
2771
  );
2721
2772
  }
2722
2773
  const active = item.href ? activeFn(pathname, item.href) : false;
@@ -3004,142 +3055,6 @@ function NavRow({
3004
3055
  }
3005
3056
  );
3006
3057
  }
3007
- function NavGroup({
3008
- item,
3009
- active,
3010
- open,
3011
- onToggle,
3012
- pathname,
3013
- activeFn,
3014
- Link
3015
- }) {
3016
- const rc = useRailCollapse();
3017
- const Icon = item.icon;
3018
- const headerClasses = [
3019
- "group relative block w-full overflow-hidden text-left transition",
3020
- open ? "rounded-t-[5px]" : "rounded-[5px]",
3021
- 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
3022
- ].join(" ");
3023
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
3024
- /* @__PURE__ */ jsxRuntime.jsxs(
3025
- "button",
3026
- {
3027
- type: "button",
3028
- onClick: () => {
3029
- onToggle();
3030
- item.onClick?.();
3031
- },
3032
- "aria-expanded": open,
3033
- title: item.label,
3034
- className: headerClasses,
3035
- children: [
3036
- active ? /* @__PURE__ */ jsxRuntime.jsx(NavActiveRail, {}) : null,
3037
- /* @__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: [
3038
- Icon ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { size: NAV_TOP_ICON_SIZE, className: "shrink-0 text-current" }) : null,
3039
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: `min-w-0 flex-1 truncate ${rc("max-md:hidden")}`, children: item.label }),
3040
- /* @__PURE__ */ jsxRuntime.jsx(NavRowBadge, { value: item.badge }),
3041
- /* @__PURE__ */ jsxRuntime.jsx(
3042
- ChevronDown,
3043
- {
3044
- className: [
3045
- "shrink-0 opacity-80 transition-transform duration-200",
3046
- open ? "rotate-180" : "",
3047
- rc("max-md:hidden")
3048
- ].join(" ")
3049
- }
3050
- )
3051
- ] })
3052
- ]
3053
- }
3054
- ),
3055
- 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) => {
3056
- if (child.kind === "section") {
3057
- return /* @__PURE__ */ jsxRuntime.jsx(
3058
- NavSectionRow,
3059
- {
3060
- label: child.label,
3061
- first: i === 0,
3062
- variant: "sub"
3063
- },
3064
- `sub-section-${i}-${child.label}`
3065
- );
3066
- }
3067
- const childActive = child.href ? activeFn(pathname, child.href) : false;
3068
- if (child.children && child.children.length > 0) {
3069
- return /* @__PURE__ */ jsxRuntime.jsx(
3070
- NavSubGroup,
3071
- {
3072
- item: child,
3073
- pathname,
3074
- activeFn,
3075
- Link
3076
- },
3077
- child.href ?? `subgrp-${i}-${child.label}`
3078
- );
3079
- }
3080
- return /* @__PURE__ */ jsxRuntime.jsx(
3081
- NavRow,
3082
- {
3083
- item: child,
3084
- active: childActive,
3085
- Link,
3086
- variant: "sub"
3087
- },
3088
- child.href ?? `sub-${i}-${child.label}`
3089
- );
3090
- }) }) }) : null
3091
- ] });
3092
- }
3093
- function NavSubGroup({
3094
- item,
3095
- pathname,
3096
- activeFn,
3097
- Link
3098
- }) {
3099
- const ownsActive = (item.href ? activeFn(pathname, item.href) : false) || (item.children ?? []).some((c) => c.href ? activeFn(pathname, c.href) : false);
3100
- const [open, setOpen] = react.useState(ownsActive);
3101
- react.useEffect(() => {
3102
- if (ownsActive) setOpen(true);
3103
- }, [ownsActive]);
3104
- const Icon = item.icon;
3105
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
3106
- /* @__PURE__ */ jsxRuntime.jsxs(
3107
- "button",
3108
- {
3109
- type: "button",
3110
- onClick: () => setOpen((v) => !v),
3111
- "aria-expanded": open,
3112
- className: [
3113
- "flex w-full items-center gap-2.5 rounded px-3 py-1.5 text-left text-[13px] font-medium transition",
3114
- 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)]"
3115
- ].join(" "),
3116
- children: [
3117
- Icon ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { size: 15, className: "shrink-0 text-current" }) : null,
3118
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1", children: item.label }),
3119
- /* @__PURE__ */ jsxRuntime.jsx(
3120
- ChevronDown,
3121
- {
3122
- className: [
3123
- "shrink-0 opacity-70 transition-transform duration-200",
3124
- open ? "rotate-180" : ""
3125
- ].join(" ")
3126
- }
3127
- )
3128
- ]
3129
- }
3130
- ),
3131
- 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(
3132
- NavRow,
3133
- {
3134
- item: leaf,
3135
- active: leaf.href ? activeFn(pathname, leaf.href) : false,
3136
- Link,
3137
- variant: "sub"
3138
- },
3139
- leaf.href ?? `subsub-${i}-${leaf.label}`
3140
- )) }) : null
3141
- ] });
3142
- }
3143
3058
  function OrgIdCopyButton({ id }) {
3144
3059
  const [copied, setCopied] = react.useState(false);
3145
3060
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -3439,8 +3354,8 @@ function NavSidebarUserCard({
3439
3354
  }) {
3440
3355
  const rawName = user.name?.trim() || "";
3441
3356
  const hasRealName = rawName.length > 0 && !rawName.includes("@");
3442
- const displayName = hasRealName ? rawName : user.email ?? "Signed in";
3443
- 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();
3444
3359
  const secondary = hasRealName ? user.email || [user.tier, user.role].filter(Boolean).join(" \xB7 ") || "member" : [user.tier, user.role].filter(Boolean).join(" \xB7 ") || "member";
3445
3360
  const rc = useRailCollapse();
3446
3361
  return /* @__PURE__ */ jsxRuntime.jsxs("details", { "data-nav-details": true, className: "group/acct relative", children: [
@@ -3756,7 +3671,7 @@ function NavAppShell({
3756
3671
  return /* @__PURE__ */ jsxRuntime.jsxs(
3757
3672
  "div",
3758
3673
  {
3759
- "data-politogy-mode": modeId,
3674
+ "data-politogy-mode": modeId ?? modeIdFromLabel(sidebar.mode?.label),
3760
3675
  className: "min-h-screen bg-[color:var(--background)] text-[color:var(--foreground)]",
3761
3676
  children: [
3762
3677
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -4446,7 +4361,8 @@ var POLITOGY_APPS = {
4446
4361
  },
4447
4362
  forms: { title: "Forms", origin: "https://forms.politogyvrm.com", storagePrefix: "forms" },
4448
4363
  surveys: { title: "Surveys", origin: "https://surveys.politogyvrm.com", storagePrefix: "surveys" },
4449
- polls: { title: "Polls", origin: "https://polls.politogyvrm.com", storagePrefix: "polls" },
4364
+ // Specs/Left-Navigation §9.2: Polling is a Campaign row (Research).
4365
+ polls: { title: "Polls", origin: "https://polls.politogyvrm.com", storagePrefix: "polls", mode: "campaign" },
4450
4366
  ctas: { title: "CTAs", origin: "https://ctas.politogyvrm.com", storagePrefix: "ctas" },
4451
4367
  petitions: {
4452
4368
  title: "Petitions",
@@ -4772,45 +4688,6 @@ function FrameBody({
4772
4688
  )
4773
4689
  ] });
4774
4690
  }
4775
- var standardNavIcons = {
4776
- home: lu.LuHouse,
4777
- users: lu.LuUsers,
4778
- mail: lu.LuMail,
4779
- messageSquare: lu.LuMessageSquare,
4780
- layout: lu.LuLayoutDashboard,
4781
- activity: lu.LuActivity,
4782
- lifeBuoy: lu.LuLifeBuoy,
4783
- grid: lu.LuLayoutGrid,
4784
- list: lu.LuList,
4785
- upload: lu.LuUpload,
4786
- edit: lu.LuPencil,
4787
- fileText: lu.LuFileText,
4788
- send: lu.LuSend,
4789
- clock: lu.LuClock,
4790
- user: lu.LuUser,
4791
- barChart: lu.LuChartColumn,
4792
- briefcase: lu.LuBriefcase,
4793
- settings: lu.LuSettings,
4794
- creditCard: lu.LuCreditCard,
4795
- dollarSign: lu.LuDollarSign,
4796
- share: lu.LuShare2,
4797
- // Campaign / Petition mode slots.
4798
- phone: lu.LuPhone,
4799
- map: lu.LuMap,
4800
- mapPin: lu.LuMapPin,
4801
- target: lu.LuTarget,
4802
- flag: lu.LuFlag,
4803
- layoutDashboard: lu.LuLayoutDashboard,
4804
- usersRound: lu.LuUsersRound,
4805
- mousePointerClick: lu.LuMousePointerClick,
4806
- clipboardList: lu.LuClipboardList,
4807
- circleUser: lu.LuCircleUser,
4808
- crosshair: lu.LuCrosshair,
4809
- landmark: lu.LuLandmark,
4810
- chartNoAxes: lu.LuChartNoAxesColumn,
4811
- doorOpen: lu.LuDoorOpen,
4812
- signpost: lu.LuSignpost
4813
- };
4814
4691
  var ORG_STORAGE_KEY = "vrm.activeOrg";
4815
4692
  var MODE_STORAGE_KEY = "vrm.activeMode";
4816
4693
  var CORE_BASE_URL = "https://app.politogyvrm.com";