@the-portland-company/shell 0.8.0 → 0.9.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/dist/index.d.cts CHANGED
@@ -589,15 +589,13 @@ interface NavItemProps {
589
589
  [key: string]: unknown;
590
590
  }
591
591
  /**
592
- * Single sidebar nav row. Ported verbatim from
593
- * react/app/src/layouts/AuthenticatedLayout.tsx lines 1292–1730 (2026-05-13)
594
- * with mode + navigation-preference context reads swapped for shell hooks.
592
+ * Single sidebar nav row. Reconciled with main-app source of truth:
593
+ * `react/app/src/layouts/AuthenticatedLayout.tsx` NavItem (2026-07).
595
594
  *
596
- * Renders:
597
- * - Top-level rows with optional children (accordion).
598
- * - Sub-items inside an expanded parent (children list).
599
- * - Active state via `data-nav-item-active` and primary-color fill.
600
- * - Mode-aware hover gradient that sweeps left-to-right.
595
+ * Shell additions retained: `brandIcon`, `safeHref`, shell context hooks.
596
+ * Main-app behavior restored: gray hover (no gradient sweep), parent active
597
+ * when any child matches even in tabs-only mode, split effectiveHref vs
598
+ * href-less accordion headers.
601
599
  */
602
600
  declare function NavItem({ icon, brandIcon, children, href, action, subItems, badge, subLabel, isSubItem, isExpanded, onToggle, menuKey, isDisabled: navIsDisabled, disabled, ...rest }: NavItemProps): react_jsx_runtime.JSX.Element;
603
601
 
package/dist/index.d.ts CHANGED
@@ -589,15 +589,13 @@ interface NavItemProps {
589
589
  [key: string]: unknown;
590
590
  }
591
591
  /**
592
- * Single sidebar nav row. Ported verbatim from
593
- * react/app/src/layouts/AuthenticatedLayout.tsx lines 1292–1730 (2026-05-13)
594
- * with mode + navigation-preference context reads swapped for shell hooks.
592
+ * Single sidebar nav row. Reconciled with main-app source of truth:
593
+ * `react/app/src/layouts/AuthenticatedLayout.tsx` NavItem (2026-07).
595
594
  *
596
- * Renders:
597
- * - Top-level rows with optional children (accordion).
598
- * - Sub-items inside an expanded parent (children list).
599
- * - Active state via `data-nav-item-active` and primary-color fill.
600
- * - Mode-aware hover gradient that sweeps left-to-right.
595
+ * Shell additions retained: `brandIcon`, `safeHref`, shell context hooks.
596
+ * Main-app behavior restored: gray hover (no gradient sweep), parent active
597
+ * when any child matches even in tabs-only mode, split effectiveHref vs
598
+ * href-less accordion headers.
601
599
  */
602
600
  declare function NavItem({ icon, brandIcon, children, href, action, subItems, badge, subLabel, isSubItem, isExpanded, onToggle, menuKey, isDisabled: navIsDisabled, disabled, ...rest }: NavItemProps): react_jsx_runtime.JSX.Element;
603
601
 
package/dist/index.js CHANGED
@@ -1567,8 +1567,7 @@ function BrandIcon({
1567
1567
  );
1568
1568
  }
1569
1569
  var BrandIcon_default = BrandIcon;
1570
- var MODE_CAMPAIGN = "campaign";
1571
- var MODE_PETITION = "petition";
1570
+ var safeHref = (h) => h && /^(https?:|mailto:|tel:|\/|#|\.)/i.test(h.trim()) ? h : "#";
1572
1571
  function NavItem({
1573
1572
  icon,
1574
1573
  brandIcon,
@@ -1589,23 +1588,9 @@ function NavItem({
1589
1588
  const location = useLocation();
1590
1589
  const navigate = useNavigate();
1591
1590
  const mode = useShellMode();
1592
- const currentMode = mode?.currentMode ?? null;
1593
1591
  const modeConfig = resolveModeConfig(mode);
1594
1592
  const navPref = useShellNavigationPreference();
1595
1593
  const showSubMenu = navPref?.showSubMenu ?? true;
1596
- const navHoverGradient = currentMode === MODE_CAMPAIGN ? "linear-gradient(90deg, #F2C94C 0%, #E63946 100%)" : currentMode === MODE_PETITION ? "linear-gradient(90deg, #F2C94C 0%, #007BFF 100%)" : "linear-gradient(90deg, #ea6a3a 0%, #E63946 100%)";
1597
- const gradientSweepBefore = {
1598
- content: '""',
1599
- position: "absolute",
1600
- top: 0,
1601
- left: 0,
1602
- bottom: 0,
1603
- width: "0%",
1604
- background: navHoverGradient,
1605
- transition: "width 300ms ease",
1606
- pointerEvents: "none",
1607
- zIndex: 0
1608
- };
1609
1594
  const isDisabled = Boolean(navIsDisabled || disabled);
1610
1595
  const effectiveHref = href || (Array.isArray(subItems) && subItems.length > 0 ? subItems[0].href : void 0);
1611
1596
  const effectivePathname = effectiveHref?.split("?")[0];
@@ -1615,6 +1600,7 @@ function NavItem({
1615
1600
  [location.search]
1616
1601
  );
1617
1602
  const matchesSubItem = (itemHref) => {
1603
+ if (!itemHref) return false;
1618
1604
  if (itemHref === currentFullPath) return true;
1619
1605
  const itemPath = itemHref.split("?")[0];
1620
1606
  const itemSearch = itemHref.includes("?") ? itemHref.split("?")[1] : "";
@@ -1658,83 +1644,130 @@ function NavItem({
1658
1644
  return effectivePathname !== `/${location.pathname.split("/")[1]}/dashboard` && location.pathname.startsWith(effectivePathname + "/");
1659
1645
  })();
1660
1646
  const hasSubItems = showSubMenu && Array.isArray(subItems) && subItems.length > 0;
1661
- const hasActiveChild = hasSubItems && subItems.some((item) => matchesSubItem(item.href || ""));
1647
+ const hasActiveChild = Array.isArray(subItems) && subItems.some((item) => matchesSubItem(item.href || ""));
1662
1648
  const isParentActive = isActive || hasActiveChild;
1663
1649
  const isSubMenuOpen = isExpanded !== void 0 ? isExpanded : hasSubItems && subItems.some((item) => matchesSubItem(item.href || ""));
1664
1650
  const expandedRowBg = isParentActive ? modeConfig?.primaryColor || "brand.500" : isSubMenuOpen ? "gray.100" : "transparent";
1665
1651
  const expandedRowTextColor = isParentActive ? "white" : isSubMenuOpen ? "gray.900" : "gray.600";
1666
1652
  const disabledRowBg = isSubItem ? "gray.50" : "transparent";
1667
1653
  const disabledRowTextColor = "gray.400";
1654
+ const renderIcon = (activeForHover) => {
1655
+ if (brandIcon) {
1656
+ return /* @__PURE__ */ jsx(
1657
+ Box,
1658
+ {
1659
+ mr: "4",
1660
+ display: "inline-flex",
1661
+ alignItems: "center",
1662
+ filter: isParentActive || isActive ? "brightness(0) invert(1)" : void 0,
1663
+ _groupHover: isDisabled ? void 0 : {
1664
+ filter: activeForHover ? "brightness(0) invert(1)" : void 0
1665
+ },
1666
+ children: /* @__PURE__ */ jsx(BrandIcon_default, { name: brandIcon, size: 18 })
1667
+ }
1668
+ );
1669
+ }
1670
+ if (icon) {
1671
+ return /* @__PURE__ */ jsx(
1672
+ Box,
1673
+ {
1674
+ mr: "4",
1675
+ fontSize: "16",
1676
+ _groupHover: {
1677
+ color: isDisabled ? disabledRowTextColor : activeForHover ? "white" : "gray.900"
1678
+ },
1679
+ as: icon
1680
+ }
1681
+ );
1682
+ }
1683
+ return null;
1684
+ };
1668
1685
  if (hasSubItems) {
1669
- const headerProps = {
1670
- align: "center",
1671
- px: "4",
1672
- py: "2.5",
1673
- mx: "4",
1674
- w: "auto",
1675
- borderTopRadius: "lg",
1676
- borderBottomRadius: isSubMenuOpen ? 0 : "lg",
1677
- role: "group",
1678
- cursor: isDisabled ? "not-allowed" : "pointer",
1679
- opacity: 1,
1680
- pointerEvents: isDisabled ? "none" : "auto",
1681
- "data-nav-item-active": String(isParentActive),
1682
- position: "relative",
1683
- overflow: "hidden",
1684
- bg: isDisabled ? disabledRowBg : expandedRowBg,
1685
- color: isDisabled ? disabledRowTextColor : expandedRowTextColor,
1686
- _before: isDisabled ? void 0 : gradientSweepBefore,
1687
- _hover: {
1688
- color: isDisabled ? disabledRowTextColor : "white",
1689
- _before: isDisabled ? void 0 : { width: "100%" }
1690
- },
1691
- "aria-disabled": isDisabled ? "true" : "false",
1692
- onClick: (e) => {
1693
- if (isDisabled) return;
1694
- if (effectiveHref) e.preventDefault();
1695
- e.stopPropagation();
1696
- onToggle?.(menuKey || null);
1697
- }
1698
- };
1699
1686
  return /* @__PURE__ */ jsxs(Box, { children: [
1700
- /* @__PURE__ */ jsxs(Flex, { ...headerProps, ...rest, children: [
1701
- brandIcon ? /* @__PURE__ */ jsx(
1702
- Box,
1703
- {
1704
- mr: "4",
1705
- display: "inline-flex",
1706
- alignItems: "center",
1707
- position: "relative",
1708
- zIndex: 1,
1709
- filter: isParentActive ? "brightness(0) invert(1)" : void 0,
1710
- _groupHover: isDisabled ? void 0 : { filter: "brightness(0) invert(1)" },
1711
- children: /* @__PURE__ */ jsx(BrandIcon_default, { name: brandIcon, size: 18 })
1712
- }
1713
- ) : icon ? /* @__PURE__ */ jsx(
1714
- Box,
1715
- {
1716
- mr: "4",
1717
- fontSize: "16",
1718
- position: "relative",
1719
- zIndex: 1,
1720
- _groupHover: {
1721
- color: isDisabled ? disabledRowTextColor : "white"
1722
- },
1723
- as: icon
1724
- }
1725
- ) : null,
1726
- /* @__PURE__ */ jsx(Box, { flex: "1", position: "relative", zIndex: 1, children }),
1727
- /* @__PURE__ */ jsx(
1728
- Box,
1729
- {
1730
- as: FiChevronDown,
1731
- position: "relative",
1732
- zIndex: 1,
1733
- transform: isSubMenuOpen ? "rotate(180deg)" : void 0,
1734
- transition: "transform 0.2s"
1735
- }
1736
- )
1737
- ] }),
1687
+ effectiveHref ? /* @__PURE__ */ jsxs(
1688
+ Flex,
1689
+ {
1690
+ align: "center",
1691
+ px: "4",
1692
+ py: "2.5",
1693
+ mx: "4",
1694
+ w: "auto",
1695
+ borderTopRadius: "lg",
1696
+ borderBottomRadius: isSubMenuOpen ? 0 : "lg",
1697
+ role: "group",
1698
+ cursor: isDisabled ? "not-allowed" : "pointer",
1699
+ opacity: 1,
1700
+ pointerEvents: isDisabled ? "none" : "auto",
1701
+ "data-nav-item-active": String(isParentActive),
1702
+ bg: isDisabled ? disabledRowBg : expandedRowBg,
1703
+ color: isDisabled ? disabledRowTextColor : expandedRowTextColor,
1704
+ _hover: {
1705
+ bg: isDisabled ? disabledRowBg : isParentActive ? modeConfig?.accentColor || "brand.600" : "gray.200",
1706
+ color: isDisabled ? disabledRowTextColor : isParentActive ? "white" : "gray.900"
1707
+ },
1708
+ "aria-disabled": isDisabled ? "true" : "false",
1709
+ onClick: (e) => {
1710
+ if (isDisabled) return;
1711
+ e.preventDefault();
1712
+ e.stopPropagation();
1713
+ onToggle?.(menuKey || null);
1714
+ },
1715
+ ...rest,
1716
+ children: [
1717
+ renderIcon(isParentActive),
1718
+ /* @__PURE__ */ jsx(Box, { flex: "1", children }),
1719
+ /* @__PURE__ */ jsx(
1720
+ Box,
1721
+ {
1722
+ as: FiChevronDown,
1723
+ transform: isSubMenuOpen ? "rotate(180deg)" : void 0,
1724
+ transition: "transform 0.2s"
1725
+ }
1726
+ )
1727
+ ]
1728
+ }
1729
+ ) : /* @__PURE__ */ jsxs(
1730
+ Flex,
1731
+ {
1732
+ align: "center",
1733
+ px: "4",
1734
+ py: "2.5",
1735
+ mx: "4",
1736
+ w: "auto",
1737
+ borderTopRadius: "lg",
1738
+ borderBottomRadius: isSubMenuOpen ? 0 : "lg",
1739
+ role: "group",
1740
+ cursor: isDisabled ? "not-allowed" : "pointer",
1741
+ opacity: 1,
1742
+ pointerEvents: isDisabled ? "none" : "auto",
1743
+ "data-nav-item-active": String(isParentActive),
1744
+ bg: isDisabled ? disabledRowBg : expandedRowBg,
1745
+ color: isDisabled ? disabledRowTextColor : expandedRowTextColor,
1746
+ _hover: {
1747
+ bg: isDisabled ? disabledRowBg : hasActiveChild ? modeConfig?.accentColor || "brand.600" : "gray.200",
1748
+ color: isDisabled ? disabledRowTextColor : hasActiveChild ? "white" : "gray.900"
1749
+ },
1750
+ "aria-disabled": isDisabled ? "true" : "false",
1751
+ onClick: (e) => {
1752
+ if (isDisabled) return;
1753
+ e.stopPropagation();
1754
+ onToggle?.(menuKey || null);
1755
+ },
1756
+ ...rest,
1757
+ children: [
1758
+ renderIcon(hasActiveChild),
1759
+ /* @__PURE__ */ jsx(Box, { flex: "1", children }),
1760
+ /* @__PURE__ */ jsx(
1761
+ Box,
1762
+ {
1763
+ as: FiChevronDown,
1764
+ transform: isSubMenuOpen ? "rotate(180deg)" : void 0,
1765
+ transition: "transform 0.2s"
1766
+ }
1767
+ )
1768
+ ]
1769
+ }
1770
+ ),
1738
1771
  /* @__PURE__ */ jsx(Collapse, { in: isSubMenuOpen, animateOpacity: true, children: /* @__PURE__ */ jsx(
1739
1772
  Box,
1740
1773
  {
@@ -1778,74 +1811,68 @@ function NavItem({
1778
1811
  navigate(effectiveHref);
1779
1812
  }
1780
1813
  };
1781
- return /* @__PURE__ */ jsx(Box, { as: "a", href: effectiveHref || "#", onClick: handleClick, style: { textDecoration: "none" }, children: /* @__PURE__ */ jsxs(
1782
- Flex,
1814
+ const rowActive = isSubItem ? isActive : isParentActive;
1815
+ return /* @__PURE__ */ jsx(
1816
+ Box,
1783
1817
  {
1784
- align: "center",
1785
- px: "4",
1786
- py: "2.5",
1787
- mx: isSubItem ? "0" : "4",
1788
- w: isSubItem ? "full" : "auto",
1789
- borderRadius: isSubItem ? "0" : "lg",
1790
- role: "group",
1791
- cursor: isDisabled ? "not-allowed" : "pointer",
1792
- opacity: 1,
1793
- pointerEvents: isDisabled ? "none" : "auto",
1794
- "data-nav-item-active": String(isActive),
1795
- position: "relative",
1796
- overflow: "hidden",
1797
- bg: isDisabled ? disabledRowBg : isActive ? isSubItem ? "gray.100" : modeConfig?.primaryColor || "brand.500" : "transparent",
1798
- color: isDisabled ? disabledRowTextColor : isActive ? isSubItem ? modeConfig?.primaryColor || "brand.500" : "white" : "gray.600",
1799
- _before: isDisabled ? void 0 : gradientSweepBefore,
1800
- _hover: {
1801
- color: isDisabled ? disabledRowTextColor : "white",
1802
- _before: isDisabled ? void 0 : { width: "100%" }
1803
- },
1804
- "aria-disabled": isDisabled ? "true" : "false",
1818
+ as: "a",
1819
+ href: safeHref(effectiveHref),
1805
1820
  onClick: handleClick,
1806
- children: [
1807
- brandIcon ? /* @__PURE__ */ jsx(
1808
- Box,
1809
- {
1810
- mr: "4",
1811
- display: "inline-flex",
1812
- alignItems: "center",
1813
- position: "relative",
1814
- zIndex: 1,
1815
- filter: isActive && !isSubItem ? "brightness(0) invert(1)" : void 0,
1816
- _groupHover: isDisabled ? void 0 : { filter: "brightness(0) invert(1)" },
1817
- children: /* @__PURE__ */ jsx(BrandIcon_default, { name: brandIcon, size: 18 })
1818
- }
1819
- ) : icon ? /* @__PURE__ */ jsx(
1820
- Box,
1821
- {
1822
- mr: "4",
1823
- fontSize: "16",
1824
- position: "relative",
1825
- zIndex: 1,
1826
- _groupHover: {
1827
- color: isDisabled ? disabledRowTextColor : "white"
1828
- },
1829
- as: icon
1830
- }
1831
- ) : null,
1832
- /* @__PURE__ */ jsxs(Box, { flex: "1", position: "relative", zIndex: 1, children: [
1833
- /* @__PURE__ */ jsx(Box, { children }),
1834
- subLabel ? /* @__PURE__ */ jsx(
1835
- Text,
1836
- {
1837
- mt: 0.5,
1838
- fontSize: "10px",
1839
- lineHeight: "1.3",
1840
- color: isDisabled ? "gray.400" : "gray.400",
1841
- children: subLabel
1842
- }
1843
- ) : null,
1844
- badge && /* @__PURE__ */ jsx(Badge, { colorScheme: "orange", size: "xs", mt: 1, fontSize: "xs", display: "block", children: badge })
1845
- ] })
1846
- ]
1821
+ style: { textDecoration: "none" },
1822
+ children: /* @__PURE__ */ jsxs(
1823
+ Flex,
1824
+ {
1825
+ align: "center",
1826
+ px: "4",
1827
+ py: "2.5",
1828
+ mx: isSubItem ? "0" : "4",
1829
+ w: isSubItem ? "full" : "auto",
1830
+ borderRadius: isSubItem ? "0" : "lg",
1831
+ role: "group",
1832
+ cursor: isDisabled ? "not-allowed" : "pointer",
1833
+ opacity: 1,
1834
+ pointerEvents: isDisabled ? "none" : "auto",
1835
+ "data-nav-item-active": String(rowActive),
1836
+ bg: isDisabled ? disabledRowBg : rowActive ? isSubItem ? "gray.100" : modeConfig?.primaryColor || "brand.500" : "transparent",
1837
+ color: isDisabled ? disabledRowTextColor : rowActive ? isSubItem ? modeConfig?.primaryColor || "brand.500" : "white" : "gray.600",
1838
+ _hover: {
1839
+ bg: isDisabled ? disabledRowBg : rowActive ? isSubItem ? "gray.200" : modeConfig?.accentColor || "brand.600" : "gray.100",
1840
+ color: isDisabled ? disabledRowTextColor : rowActive ? isSubItem ? modeConfig?.primaryColor || "brand.500" : "white" : "gray.900"
1841
+ },
1842
+ "aria-disabled": isDisabled ? "true" : "false",
1843
+ onClick: handleClick,
1844
+ ...rest,
1845
+ children: [
1846
+ brandIcon ? /* @__PURE__ */ jsx(
1847
+ Box,
1848
+ {
1849
+ mr: "4",
1850
+ display: "inline-flex",
1851
+ alignItems: "center",
1852
+ filter: rowActive && !isSubItem ? "brightness(0) invert(1)" : void 0,
1853
+ children: /* @__PURE__ */ jsx(BrandIcon_default, { name: brandIcon, size: 18 })
1854
+ }
1855
+ ) : icon ? /* @__PURE__ */ jsx(
1856
+ Box,
1857
+ {
1858
+ mr: "4",
1859
+ fontSize: "16",
1860
+ _groupHover: {
1861
+ color: isDisabled ? disabledRowTextColor : rowActive ? isSubItem ? modeConfig?.primaryColor || "brand.500" : "white" : "gray.900"
1862
+ },
1863
+ as: icon
1864
+ }
1865
+ ) : null,
1866
+ /* @__PURE__ */ jsxs(Box, { flex: "1", children: [
1867
+ /* @__PURE__ */ jsx(Box, { children }),
1868
+ subLabel ? /* @__PURE__ */ jsx(Text, { mt: 0.5, fontSize: "10px", lineHeight: "1.3", color: "gray.400", children: subLabel }) : null,
1869
+ badge && /* @__PURE__ */ jsx(Badge, { colorScheme: "orange", size: "xs", mt: 1, fontSize: "xs", display: "block", children: badge })
1870
+ ] })
1871
+ ]
1872
+ }
1873
+ )
1847
1874
  }
1848
- ) });
1875
+ );
1849
1876
  }
1850
1877
  var NavItem_default = NavItem;
1851
1878