@the-portland-company/shell 0.11.3 → 0.12.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.
@@ -0,0 +1,49 @@
1
+ import { FiUser, FiSettings, FiMessageSquare, FiMail, FiInfo, FiSun, FiMoon, FiHelpCircle, FiLifeBuoy, FiAlertCircle, FiAlertTriangle, FiBell, FiGrid, FiZap } from 'react-icons/fi';
2
+ import { useState, useEffect } from 'react';
3
+
4
+ // src/lib/brandGlyphs.tsx
5
+ var GLYPHS = {
6
+ bolt: FiZap,
7
+ zap: FiZap,
8
+ grid: FiGrid,
9
+ apps: FiGrid,
10
+ bell: FiBell,
11
+ notification: FiBell,
12
+ "alert-triangle": FiAlertTriangle,
13
+ alert: FiAlertTriangle,
14
+ "alert-circle": FiAlertCircle,
15
+ "life-buoy": FiLifeBuoy,
16
+ "life-ring": FiLifeBuoy,
17
+ support: FiLifeBuoy,
18
+ "help-circle": FiHelpCircle,
19
+ moon: FiMoon,
20
+ sun: FiSun,
21
+ info: FiInfo,
22
+ mail: FiMail,
23
+ "message-square": FiMessageSquare,
24
+ settings: FiSettings,
25
+ user: FiUser
26
+ };
27
+ function resolveBrandGlyph(name) {
28
+ if (!name) return null;
29
+ return GLYPHS[name.trim().toLowerCase()] ?? null;
30
+ }
31
+ function useTopBarIconSlotGlyph(slot) {
32
+ const [name, setName] = useState(null);
33
+ useEffect(() => {
34
+ if (typeof window === "undefined") return;
35
+ const read = () => {
36
+ const raw = window.getComputedStyle(document.documentElement).getPropertyValue(`--topbar-icon-${slot}`).trim().replace(/^['"]|['"]$/g, "");
37
+ setName(raw.length > 0 ? raw : null);
38
+ };
39
+ read();
40
+ const observer = new MutationObserver(read);
41
+ observer.observe(document.head, { childList: true, subtree: true });
42
+ return () => observer.disconnect();
43
+ }, [slot]);
44
+ return name;
45
+ }
46
+
47
+ export { resolveBrandGlyph, useTopBarIconSlotGlyph };
48
+ //# sourceMappingURL=chunk-2GFWARYP.js.map
49
+ //# sourceMappingURL=chunk-2GFWARYP.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/brandGlyphs.tsx","../src/lib/topbarIconSlot.ts"],"names":[],"mappings":";;;;AAgCA,IAAM,MAAA,GAAyC;AAAA,EAC7C,IAAA,EAAM,KAAA;AAAA,EACN,GAAA,EAAK,KAAA;AAAA,EACL,IAAA,EAAM,MAAA;AAAA,EACN,IAAA,EAAM,MAAA;AAAA,EACN,IAAA,EAAM,MAAA;AAAA,EACN,YAAA,EAAc,MAAA;AAAA,EACd,gBAAA,EAAkB,eAAA;AAAA,EAClB,KAAA,EAAO,eAAA;AAAA,EACP,cAAA,EAAgB,aAAA;AAAA,EAChB,WAAA,EAAa,UAAA;AAAA,EACb,WAAA,EAAa,UAAA;AAAA,EACb,OAAA,EAAS,UAAA;AAAA,EACT,aAAA,EAAe,YAAA;AAAA,EACf,IAAA,EAAM,MAAA;AAAA,EACN,GAAA,EAAK,KAAA;AAAA,EACL,IAAA,EAAM,MAAA;AAAA,EACN,IAAA,EAAM,MAAA;AAAA,EACN,gBAAA,EAAkB,eAAA;AAAA,EAClB,QAAA,EAAU,UAAA;AAAA,EACV,IAAA,EAAM;AACR,CAAA;AAOO,SAAS,kBAAkB,IAAA,EAAwD;AACxF,EAAA,IAAI,CAAC,MAAM,OAAO,IAAA;AAClB,EAAA,OAAO,OAAO,IAAA,CAAK,IAAA,EAAK,CAAE,WAAA,EAAa,CAAA,IAAK,IAAA;AAC9C;AClCO,SAAS,uBAAuB,IAAA,EAAqC;AAC1E,EAAA,MAAM,CAAC,IAAA,EAAM,OAAO,CAAA,GAAI,SAAwB,IAAI,CAAA;AAEpD,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,OAAO,WAAW,WAAA,EAAa;AAEnC,IAAA,MAAM,OAAO,MAAM;AACjB,MAAA,MAAM,GAAA,GAAM,MAAA,CACT,gBAAA,CAAiB,QAAA,CAAS,eAAe,CAAA,CACzC,gBAAA,CAAiB,CAAA,cAAA,EAAiB,IAAI,EAAE,CAAA,CACxC,IAAA,EAAK,CAEL,OAAA,CAAQ,gBAAgB,EAAE,CAAA;AAC7B,MAAA,OAAA,CAAQ,GAAA,CAAI,MAAA,GAAS,CAAA,GAAI,GAAA,GAAM,IAAI,CAAA;AAAA,IACrC,CAAA;AAEA,IAAA,IAAA,EAAK;AAEL,IAAA,MAAM,QAAA,GAAW,IAAI,gBAAA,CAAiB,IAAI,CAAA;AAC1C,IAAA,QAAA,CAAS,OAAA,CAAQ,SAAS,IAAA,EAAM,EAAE,WAAW,IAAA,EAAM,OAAA,EAAS,MAAM,CAAA;AAClE,IAAA,OAAO,MAAM,SAAS,UAAA,EAAW;AAAA,EACnC,CAAA,EAAG,CAAC,IAAI,CAAC,CAAA;AAET,EAAA,OAAO,IAAA;AACT","file":"chunk-2GFWARYP.js","sourcesContent":["import type { ComponentType } from 'react'\nimport {\n FiAlertCircle,\n FiAlertTriangle,\n FiBell,\n FiGrid,\n FiHelpCircle,\n FiInfo,\n FiLifeBuoy,\n FiMail,\n FiMessageSquare,\n FiMoon,\n FiSettings,\n FiSun,\n FiUser,\n FiZap,\n} from 'react-icons/fi'\n\nexport type GlyphComponent = ComponentType<{ size?: number | string; className?: string }>\n\n/**\n * Brand-glyph registry: maps the icon NAME the Brand API emits (e.g.\n * `--topbar-icon-comms-fuel: bolt`) to a component.\n *\n * These names are Feather/Lucide icon names, NOT asset filenames — there is no\n * `/brand/icons/<name>.svg` for them in any app, and treating them as URLs\n * renders a broken image. Resolving them against the icon library the shell\n * already depends on keeps every app consistent with zero vendored assets.\n *\n * Aliases are included where Brand and Feather disagree on a name (`bolt` vs\n * `zap`, `life-buoy` vs `life-ring`).\n */\nconst GLYPHS: Record<string, GlyphComponent> = {\n bolt: FiZap,\n zap: FiZap,\n grid: FiGrid,\n apps: FiGrid,\n bell: FiBell,\n notification: FiBell,\n 'alert-triangle': FiAlertTriangle,\n alert: FiAlertTriangle,\n 'alert-circle': FiAlertCircle,\n 'life-buoy': FiLifeBuoy,\n 'life-ring': FiLifeBuoy,\n support: FiLifeBuoy,\n 'help-circle': FiHelpCircle,\n moon: FiMoon,\n sun: FiSun,\n info: FiInfo,\n mail: FiMail,\n 'message-square': FiMessageSquare,\n settings: FiSettings,\n user: FiUser,\n}\n\n/**\n * Resolve a Brand glyph name to a component. Returns null for an unknown name\n * so the caller renders its own fallback — an unrecognized token must never\n * produce a broken or empty icon.\n */\nexport function resolveBrandGlyph(name: string | null | undefined): GlyphComponent | null {\n if (!name) return null\n return GLYPHS[name.trim().toLowerCase()] ?? null\n}\n\n/** Names the registry knows — exported for tests and tooling. */\nexport const KNOWN_BRAND_GLYPHS = Object.keys(GLYPHS)\n","import { useEffect, useState } from 'react'\n\n/**\n * The six canonical top-bar action slots (Reference/Global-Rules v9 → top-bar\n * icons). Each slot's GLYPH is unique and comes from the Brand API\n * `topbarIcons.items.*` tokens, exposed as `--topbar-icon-<slot>` CSS vars\n * whose value is the brand-icon NAME. Every OTHER property — size, gap, color,\n * hover, selected — is universal across all six slots.\n */\nexport type TopBarIconSlot =\n | 'comms-fuel'\n | 'apps'\n | 'notifications'\n | 'alerts'\n | 'support'\n | 'dark-mode'\n\n/**\n * Reads the glyph NAME for a slot from its `--topbar-icon-<slot>` CSS var.\n *\n * Runs in an effect because the Brand stylesheet is appended to <head> at\n * runtime (Global Rules forbids baking it into the build), so the var is not\n * resolvable during SSR or the first synchronous render. The MutationObserver\n * re-reads once the stylesheet lands so the glyph upgrades from the caller's\n * fallback without a reload.\n *\n * Returns null when the slot has no Brand glyph — callers render their own\n * fallback icon in that case.\n */\nexport function useTopBarIconSlotGlyph(slot: TopBarIconSlot): string | null {\n const [name, setName] = useState<string | null>(null)\n\n useEffect(() => {\n if (typeof window === 'undefined') return\n\n const read = () => {\n const raw = window\n .getComputedStyle(document.documentElement)\n .getPropertyValue(`--topbar-icon-${slot}`)\n .trim()\n // Brand may emit the name quoted (e.g. \"bell\"); strip the quotes.\n .replace(/^['\"]|['\"]$/g, '')\n setName(raw.length > 0 ? raw : null)\n }\n\n read()\n\n const observer = new MutationObserver(read)\n observer.observe(document.head, { childList: true, subtree: true })\n return () => observer.disconnect()\n }, [slot])\n\n return name\n}\n\n/** Path to a brand-icon SVG in the consuming app's public dir. */\nexport function brandIconSrc(name: string, basePath = '/brand/icons/') {\n return `${basePath}${name}.svg`\n}\n"]}
package/dist/index.cjs CHANGED
@@ -1653,9 +1653,10 @@ function NavItem({
1653
1653
  const hasActiveChild = Array.isArray(subItems) && subItems.some((item) => matchesSubItem(item.href || ""));
1654
1654
  const isParentActive = isActive || hasActiveChild;
1655
1655
  const isSubMenuOpen = isExpanded !== void 0 ? isExpanded : hasSubItems && subItems.some((item) => matchesSubItem(item.href || ""));
1656
- const expandedRowBg = isParentActive ? modeConfig?.primaryColor || "brand.500" : isSubMenuOpen ? "gray.100" : "transparent";
1657
- const expandedRowTextColor = isParentActive ? "white" : isSubMenuOpen ? "gray.900" : "gray.600";
1658
- const disabledRowBg = isSubItem ? "gray.50" : "transparent";
1656
+ const submenuBg = "var(--sidebar-submenu-bg, #F1F5F9)";
1657
+ const expandedRowBg = isParentActive ? modeConfig?.primaryColor || "brand.500" : isSubMenuOpen ? submenuBg : "transparent";
1658
+ const expandedRowTextColor = isParentActive ? "white" : isSubMenuOpen ? "var(--sidebar-text, #1A202C)" : "var(--sidebar-text-muted, #4A5568)";
1659
+ const disabledRowBg = isSubItem ? submenuBg : "transparent";
1659
1660
  const disabledRowTextColor = "gray.400";
1660
1661
  const renderIcon = (activeForHover) => {
1661
1662
  if (brandIcon) {
@@ -1708,8 +1709,8 @@ function NavItem({
1708
1709
  bg: isDisabled ? disabledRowBg : expandedRowBg,
1709
1710
  color: isDisabled ? disabledRowTextColor : expandedRowTextColor,
1710
1711
  _hover: {
1711
- bg: isDisabled ? disabledRowBg : isParentActive ? modeConfig?.accentColor || "brand.600" : "gray.200",
1712
- color: isDisabled ? disabledRowTextColor : isParentActive ? "white" : "gray.900"
1712
+ bg: isDisabled ? disabledRowBg : isParentActive ? modeConfig?.accentColor || "brand.600" : "var(--sidebar-hover-bg, #E2E8F0)",
1713
+ color: isDisabled ? disabledRowTextColor : isParentActive ? "white" : "var(--sidebar-text, #1A202C)"
1713
1714
  },
1714
1715
  "aria-disabled": isDisabled ? "true" : "false",
1715
1716
  onClick: (e) => {
@@ -1750,8 +1751,8 @@ function NavItem({
1750
1751
  bg: isDisabled ? disabledRowBg : expandedRowBg,
1751
1752
  color: isDisabled ? disabledRowTextColor : expandedRowTextColor,
1752
1753
  _hover: {
1753
- bg: isDisabled ? disabledRowBg : hasActiveChild ? modeConfig?.accentColor || "brand.600" : "gray.200",
1754
- color: isDisabled ? disabledRowTextColor : hasActiveChild ? "white" : "gray.900"
1754
+ bg: isDisabled ? disabledRowBg : hasActiveChild ? modeConfig?.accentColor || "brand.600" : "var(--sidebar-hover-bg, #E2E8F0)",
1755
+ color: isDisabled ? disabledRowTextColor : hasActiveChild ? "white" : "var(--sidebar-text, #1A202C)"
1755
1756
  },
1756
1757
  "aria-disabled": isDisabled ? "true" : "false",
1757
1758
  onClick: (e) => {
@@ -1784,7 +1785,7 @@ function NavItem({
1784
1785
  py: "1",
1785
1786
  borderTopRadius: 0,
1786
1787
  borderBottomRadius: "md",
1787
- bg: isDisabled ? "gray.100" : "gray.200",
1788
+ bg: submenuBg,
1788
1789
  children: subItems.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
1789
1790
  NavItem,
1790
1791
  {
@@ -1839,10 +1840,10 @@ function NavItem({
1839
1840
  opacity: 1,
1840
1841
  pointerEvents: isDisabled ? "none" : "auto",
1841
1842
  "data-nav-item-active": String(rowActive),
1842
- bg: isDisabled ? disabledRowBg : rowActive ? isSubItem ? "gray.100" : modeConfig?.primaryColor || "brand.500" : "transparent",
1843
+ bg: isDisabled ? disabledRowBg : rowActive ? isSubItem ? "var(--sidebar-hover-bg, #EDF2F7)" : modeConfig?.primaryColor || "brand.500" : "transparent",
1843
1844
  color: isDisabled ? disabledRowTextColor : rowActive ? isSubItem ? modeConfig?.primaryColor || "brand.500" : "white" : "gray.600",
1844
1845
  _hover: {
1845
- bg: isDisabled ? disabledRowBg : rowActive ? isSubItem ? "gray.200" : modeConfig?.accentColor || "brand.600" : "gray.100",
1846
+ bg: isDisabled ? disabledRowBg : rowActive ? isSubItem ? "var(--sidebar-hover-bg, #E2E8F0)" : modeConfig?.accentColor || "brand.600" : "var(--sidebar-hover-bg, #EDF2F7)",
1846
1847
  color: isDisabled ? disabledRowTextColor : rowActive ? isSubItem ? modeConfig?.primaryColor || "brand.500" : "white" : "gray.900"
1847
1848
  },
1848
1849
  "aria-disabled": isDisabled ? "true" : "false",
@@ -2655,6 +2656,32 @@ function AppLayout({
2655
2656
  overlaySlot
2656
2657
  ] });
2657
2658
  }
2659
+ var GLYPHS = {
2660
+ bolt: fi.FiZap,
2661
+ zap: fi.FiZap,
2662
+ grid: fi.FiGrid,
2663
+ apps: fi.FiGrid,
2664
+ bell: fi.FiBell,
2665
+ notification: fi.FiBell,
2666
+ "alert-triangle": fi.FiAlertTriangle,
2667
+ alert: fi.FiAlertTriangle,
2668
+ "alert-circle": fi.FiAlertCircle,
2669
+ "life-buoy": fi.FiLifeBuoy,
2670
+ "life-ring": fi.FiLifeBuoy,
2671
+ support: fi.FiLifeBuoy,
2672
+ "help-circle": fi.FiHelpCircle,
2673
+ moon: fi.FiMoon,
2674
+ sun: fi.FiSun,
2675
+ info: fi.FiInfo,
2676
+ mail: fi.FiMail,
2677
+ "message-square": fi.FiMessageSquare,
2678
+ settings: fi.FiSettings,
2679
+ user: fi.FiUser
2680
+ };
2681
+ function resolveBrandGlyph(name) {
2682
+ if (!name) return null;
2683
+ return GLYPHS[name.trim().toLowerCase()] ?? null;
2684
+ }
2658
2685
  function useTopBarIconSlotGlyph(slot) {
2659
2686
  const [name, setName] = react$1.useState(null);
2660
2687
  react$1.useEffect(() => {
@@ -2673,16 +2700,8 @@ function useTopBarIconSlotGlyph(slot) {
2673
2700
  var TopBarIcon = react$1.forwardRef(
2674
2701
  function TopBarIcon2({ slot, fallbackIcon, isSelected, ...rest }, ref) {
2675
2702
  const glyphName = useTopBarIconSlotGlyph(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;
2703
+ const Glyph = resolveBrandGlyph(glyphName);
2704
+ const icon = Glyph ? /* @__PURE__ */ jsxRuntime.jsx(Glyph, { size: "var(--topbar-icon-size, 20px)" }) : fallbackIcon;
2686
2705
  return /* @__PURE__ */ jsxRuntime.jsx(
2687
2706
  react.IconButton,
2688
2707
  {