@the-portland-company/shell 0.40.0 → 0.41.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.
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { broadcastLogout, watchLogout } from './chunk-LL6LSJAH.js';
3
- import { standardPolitogyNav, NavAppShell, NavChromeProvider, FuelAction, NavDisplayAction, TopBarWidgetSlot, SupportAction, NotificationsAction, useChromeMe, useThemePreference, useNavChromeFetcher, useChromeOrgs } from './chunk-HJOFMJDM.js';
3
+ import { standardPolitogyNav, NavAppShell, NavChromeProvider, FuelAction, NavDisplayAction, TopBarWidgetSlot, SupportAction, NotificationsAction, useChromeMe, useThemePreference, useNavChromeFetcher, useChromeOrgs } from './chunk-IX664IMD.js';
4
4
  import './chunk-U4RZPZET.js';
5
5
  import { NAV_DISPLAY_DEFAULT, resolveNavDisplay, isNavDisplayPreference, writeNavDisplayCookie, writeNavDisplayLocal, NAV_DISPLAY_STORAGE_KEY, readNavDisplayCookie } from './chunk-6UHJV2FH.js';
6
6
  import { resolveActiveOrg, writeActiveOrgCookie, isOrgId, ACTIVE_ORG_QUERY_PARAM } from './chunk-W2HITMV5.js';
package/dist/chrome.css CHANGED
@@ -283,13 +283,17 @@
283
283
  --skeleton-shine: var(--color-layer-1);
284
284
 
285
285
  /* Beta banner — a semantic NOTICE, not a chrome surface, so it keeps one
286
- amber treatment in both themes (same reasoning as the warning alerts it now
287
- matches, e.g. "10DLC Registration Required"). It previously fell through to
288
- --color-accent, which is the brand RED, so the notice read as an error.
289
- A Brand edit can still override these; they are only the defaults. */
286
+ amber treatment in both themes. It previously fell through to
287
+ --color-accent, which is the brand RED, so the notice read as an error. */
290
288
  --beta-banner-bg: #feebc8;
291
289
  --beta-banner-text: #1a202c;
292
290
  --beta-banner-accent: #dd6b20;
291
+
292
+ /* The expanded sub-menu panel and the row hover inside it — one step off
293
+ the left bar so nesting reads. Dark values are pinned by
294
+ Reference/UI-Platform → Styling (#212121 / #2B2B2B). */
295
+ --nav-submenu-bg: #F4F4F4;
296
+ --nav-row-hover: #E8E8E8;
293
297
  }
294
298
  .dark {
295
299
  --background: 222.2 84% 4.9%;
@@ -330,22 +334,39 @@
330
334
  --color-muted: #A3A3A3;
331
335
  --skeleton-base: #212121;
332
336
  --skeleton-shine: #2B2B2B;
337
+ --nav-submenu-bg: #212121;
338
+ --nav-row-hover: #2B2B2B;
339
+ --form-control-border: #333333;
340
+ --form-control-border-hover: #404040;
341
+ --form-control-focus: #A3A3A3;
342
+ --form-placeholder-color: #8A8A8A;
343
+ --form-value-color: #E8E8E8;
344
+ --form-label-bg: #161616;
345
+ --form-select-icon: #A3A3A3;
346
+ --app-field-bg: #1F1F1F;
347
+ --app-field-border: #333333;
348
+ --app-field-border-hover: #404040;
349
+ --app-floating-label-bg: #161616;
350
+ --app-color-gray-50: #1F1F1F;
351
+ --app-color-gray-100: #212121;
352
+ --app-color-gray-200: #333333;
353
+ --app-color-gray-300: #404040;
354
+ --app-color-gray-400: #6B6B6B;
355
+ --app-color-gray-500: #8A8A8A;
356
+ --app-color-gray-600: #A3A3A3;
357
+ --app-color-gray-700: #C6C6C6;
358
+ --app-color-gray-800: #E8E8E8;
359
+ --app-color-gray-900: #F5F5F5;
333
360
  }
334
361
 
335
362
  /* ---------------------------------------------------------------------------
336
363
  Third-party top-bar widget slots (<TopBarWidgetSlot>, e.g. DevNotes).
337
364
 
338
365
  Such a widget ships its OWN launcher button, so it does not carry
339
- topBarIconButtonClass and rendered as a 6px rounded square beside four 32px
340
- circles — measured on forms, contacts, polls, surveys AND app (2026-07-24).
341
- VRM core used to hide it with an app-side `.vrm-topbar-actions` override,
342
- which is precisely the app-side patching Reference/UI-Platform forbids: the
343
- moment VRM moved onto the shared frame the override went away and the square
344
- button reappeared. It belongs here, once, for every app.
345
-
346
- These values are topBarIconButtonClass expressed as CSS. `!important` is
347
- required because the widget's own utility classes (h-8 w-8 rounded-md …) are
348
- authored by the third-party package and would otherwise win.
366
+ topBarIconButtonClass. These values are topBarIconButtonClass expressed as
367
+ CSS; `!important` is required because the widget's own utility classes and
368
+ inline styles are authored by the third-party package and would otherwise
369
+ win.
349
370
  --------------------------------------------------------------------------- */
350
371
  .topbar-widget-slot button {
351
372
  height: var(--topbar-icon-button-size, 32px) !important;
@@ -365,15 +386,15 @@
365
386
  color: var(--topbar-icon-hover-color, var(--foreground)) !important;
366
387
  background: var(--topbar-icon-hover-bg, transparent) !important;
367
388
  }
389
+
368
390
  /* The launcher's own count bubble nests in an inner span around just the glyph.
369
391
  Neutralize that so the badge anchors to the 32px trigger like every other
370
392
  slot's (UI-Platform "Badge Counters": one universal placement).
371
393
 
372
394
  The selector must NOT depend on a class: DevNotes 0.6.x writes that wrapper
373
- with an INLINE `position: relative` and no class at all, so the old
395
+ with an INLINE `position: relative` and no class at all, so an earlier
374
396
  `span.relative` selector matched nothing and the badge silently kept
375
- anchoring to the glyph. Match every direct span child of the launcher
376
- instead, and use !important so it beats the inline style. */
397
+ anchoring to the glyph. */
377
398
  .topbar-widget-slot button > span {
378
399
  position: static !important;
379
400
  }
@@ -1,5 +1,5 @@
1
1
  import { readThemeCookie, THEME_STORAGE_KEY, isThemePreference, applyThemePreference, readPendingThemeChoice, writeThemeCookie, clearPendingThemeChoice, markThemeChoicePending } from './chunk-U4RZPZET.js';
2
- import { createContext, useState, useEffect, useContext, useRef, useCallback, useMemo } from 'react';
2
+ import { createContext, useState, useEffect, useContext, useRef, useCallback, useMemo, useSyncExternalStore } from 'react';
3
3
  import { FiUser, FiSettings, FiMessageSquare, FiMail, FiInfo, FiSun, FiMoon, FiHelpCircle, FiLifeBuoy, FiAlertCircle, FiAlertTriangle, FiBell, FiGrid, FiZap, FiBatteryCharging, FiBattery } from 'react-icons/fi';
4
4
  import { createPortal } from 'react-dom';
5
5
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
@@ -58,6 +58,10 @@ function withOrgParam(url, orgId) {
58
58
  if (!orgId) return url;
59
59
  return `${url}${url.includes("?") ? "&" : "?"}org=${orgId}`;
60
60
  }
61
+ function withPhoneParam(url, phone) {
62
+ if (!phone) return url;
63
+ return `${url}${url.includes("?") ? "&" : "?"}phone=${encodeURIComponent(phone)}`;
64
+ }
61
65
  function resolveOrgLogoUrl(url) {
62
66
  const raw = url?.trim();
63
67
  if (!raw) return void 0;
@@ -73,6 +77,23 @@ function topBarIconButtonClass(tone = "default") {
73
77
  toneClass
74
78
  ].join(" ");
75
79
  }
80
+ var openPanelCount = 0;
81
+ var panelListeners = /* @__PURE__ */ new Set();
82
+ var emitPanelChange = () => panelListeners.forEach((l) => l());
83
+ function setPanelOpenGlobal(open) {
84
+ openPanelCount = Math.max(0, openPanelCount + (open ? 1 : -1));
85
+ emitPanelChange();
86
+ }
87
+ function useAnyTopBarPanelOpen() {
88
+ return useSyncExternalStore(
89
+ (cb) => {
90
+ panelListeners.add(cb);
91
+ return () => panelListeners.delete(cb);
92
+ },
93
+ () => openPanelCount > 0,
94
+ () => false
95
+ );
96
+ }
76
97
  function TopBarCountBadge({ count }) {
77
98
  if (!(count > 0)) return null;
78
99
  return /* @__PURE__ */ jsx("span", { className: "absolute -top-1 -right-1 flex h-4 min-w-4 items-center justify-center rounded-full bg-[color:var(--color-race-red)] px-1 text-[10px] font-normal leading-none text-white", children: count > 99 ? "99+" : count });
@@ -256,6 +277,9 @@ function TopBarPopover({
256
277
  onOpenChangeRef.current = onOpenChange;
257
278
  useEffect(() => {
258
279
  onOpenChangeRef.current?.(open);
280
+ if (!open) return;
281
+ setPanelOpenGlobal(true);
282
+ return () => setPanelOpenGlobal(false);
259
283
  }, [open]);
260
284
  const rootRef = useRef(null);
261
285
  const lastOpenTokenRef = useRef(openToken);
@@ -434,7 +458,8 @@ function LowFuelHint({ lowFuel, orgId }) {
434
458
  } catch {
435
459
  }
436
460
  }, [lowFuel]);
437
- if (!lowFuel || dismissed) return null;
461
+ const panelOpen = useAnyTopBarPanelOpen();
462
+ if (!lowFuel || dismissed || panelOpen) return null;
438
463
  const dismiss = () => {
439
464
  try {
440
465
  window.sessionStorage.setItem(LOW_FUEL_HINT_DISMISSED_KEY, "1");
@@ -668,6 +693,7 @@ function NotificationsAction({ orgId }) {
668
693
  const [toastReply, setToastReply] = useState(null);
669
694
  const [openToken, setOpenToken] = useState(0);
670
695
  const [panelOpen, setPanelOpen] = useState(false);
696
+ const anyPanelOpen = useAnyTopBarPanelOpen();
671
697
  useEffect(() => {
672
698
  setNotif(readNotifState(orgId));
673
699
  setToastReply(null);
@@ -851,19 +877,27 @@ function NotificationsAction({ orgId }) {
851
877
  }
852
878
  )
853
879
  ] }),
854
- /* @__PURE__ */ jsx("ul", { className: "flex flex-col gap-1", children: quarantined.map((q) => /* @__PURE__ */ jsxs("li", { className: "rounded-lg px-2 py-2 hover:bg-[color:var(--panel)]", children: [
855
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
856
- /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold text-[color:var(--foreground)]", children: formatPhone(q.phone) }),
857
- /* @__PURE__ */ jsx("span", { className: "flex h-4 min-w-4 items-center justify-center rounded-full bg-[color:var(--panel)] px-1 text-[10px] tabular-nums text-[color:var(--muted)]", children: q.count })
858
- ] }),
859
- q.latestAt ? /* @__PURE__ */ jsx("p", { className: "text-xs text-[color:var(--muted)]", children: new Date(q.latestAt).toLocaleString() }) : null
860
- ] }, q.phone)) })
880
+ /* @__PURE__ */ jsx("ul", { className: "flex flex-col gap-1", children: quarantined.map((q) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
881
+ "a",
882
+ {
883
+ href: withPhoneParam(withOrgParam(QUARANTINED_CONTACTS_URL, orgId), q.phone),
884
+ "aria-label": `Review ${q.count} quarantined message${q.count === 1 ? "" : "s"} from ${formatPhone(q.phone)}`,
885
+ className: "block rounded-lg px-2 py-2 transition hover:bg-[color:var(--nav-row-hover)] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[color:var(--color-race-red)]",
886
+ children: [
887
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
888
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold text-[color:var(--foreground)]", children: formatPhone(q.phone) }),
889
+ /* @__PURE__ */ jsx("span", { className: "flex h-4 min-w-4 items-center justify-center rounded-full bg-[color:var(--panel)] px-1 text-[10px] font-normal leading-none tabular-nums text-[color:var(--muted)]", children: q.count > 99 ? "99+" : q.count })
890
+ ] }),
891
+ q.latestAt ? /* @__PURE__ */ jsx("p", { className: "text-xs text-[color:var(--muted)]", children: new Date(q.latestAt).toLocaleString() }) : null
892
+ ]
893
+ }
894
+ ) }, q.phone)) })
861
895
  ] }) : null
862
896
  ] })
863
897
  ] })
864
898
  }
865
899
  ),
866
- toastReply && !panelOpen ? /* @__PURE__ */ jsx(
900
+ toastReply && !panelOpen && !anyPanelOpen ? /* @__PURE__ */ jsx(
867
901
  NewReplyToast,
868
902
  {
869
903
  reply: toastReply,
@@ -1905,7 +1939,7 @@ function NavGroup({
1905
1939
  ]
1906
1940
  }
1907
1941
  ),
1908
- open ? /* @__PURE__ */ jsx("div", { className: "overflow-hidden rounded-b-md bg-[color:var(--panel)] p-1", children: /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-0.5", children: item.children.map((child, i) => {
1942
+ open ? /* @__PURE__ */ jsx("div", { className: "overflow-hidden rounded-b-md bg-[color:var(--nav-submenu-bg)] p-1", children: /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-0.5", children: item.children.map((child, i) => {
1909
1943
  const childActive = child.href ? activeFn(pathname, child.href) : false;
1910
1944
  if (child.children && child.children.length > 0) {
1911
1945
  return /* @__PURE__ */ jsx(
@@ -1953,7 +1987,7 @@ function NavSubGroup({
1953
1987
  "aria-expanded": open,
1954
1988
  className: [
1955
1989
  "flex w-full items-center gap-2.5 rounded px-3 py-1.5 text-left text-[13px] font-medium transition",
1956
- ownsActive ? "text-[color:var(--color-race-red)]" : "text-[color:var(--muted)] hover:bg-[color:var(--panel-elevated)] hover:text-[color:var(--foreground)]"
1990
+ ownsActive ? "text-[color:var(--color-race-red)]" : "text-[color:var(--muted)] hover:bg-[color:var(--nav-row-hover)] hover:text-[color:var(--foreground)]"
1957
1991
  ].join(" "),
1958
1992
  children: [
1959
1993
  Icon ? /* @__PURE__ */ jsx(Icon, { size: 15, className: "shrink-0 text-current" }) : null,
@@ -2520,5 +2554,5 @@ function NavAppShell({
2520
2554
  }
2521
2555
 
2522
2556
  export { CAMPAIGN_FEATURE_AVAILABILITY, CORE_APP_ORIGIN, CORE_BILLING_PATH, FuelAction, NavAppShell, NavBetaBanner, NavChromeProvider, NavDisplayAction, NavSidebar, NavTabStrip, NavThemeControl, NavTopBar, NotificationsAction, SupportAction, TopBarCountBadge, TopBarPanelArrow, TopBarPopover, TopBarTooltip, TopBarWidgetSlot, envChipClasses, isNavItemActive, resolveOrgLogoUrl, standardPolitogyNav, topBarIconButtonClass, useChromeMe, useChromeOrgs, useNavChromeFetcher, useThemePreference };
2523
- //# sourceMappingURL=chunk-HJOFMJDM.js.map
2524
- //# sourceMappingURL=chunk-HJOFMJDM.js.map
2557
+ //# sourceMappingURL=chunk-IX664IMD.js.map
2558
+ //# sourceMappingURL=chunk-IX664IMD.js.map