@the-portland-company/shell 0.7.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
@@ -78,6 +78,18 @@ interface ShellUser {
78
78
  }
79
79
  /** Status of the auth session lifecycle. */
80
80
  type ShellAuthStatus = 'loading' | 'authenticated' | 'unauthenticated';
81
+ /**
82
+ * Host-app-driven auth for apps that do not authenticate through Supabase
83
+ * (e.g., OIDC apps like Contacts). When passed to ShellProvider it takes
84
+ * precedence over `supabaseClient` session tracking. The host app is
85
+ * responsible for building a Supabase-shaped `user.raw` (id/email/
86
+ * user_metadata.full_name) so chrome components render identically.
87
+ */
88
+ interface ShellInjectedAuth {
89
+ status: ShellAuthStatus;
90
+ user: ShellUser | null;
91
+ signOut: () => void | Promise<void>;
92
+ }
81
93
  /**
82
94
  * One entry in the shell's app-switcher sidebar. The shell uses this list to
83
95
  * render navigation links between sub-apps and to drive Speculation Rules
@@ -261,8 +273,11 @@ interface ShellProviderProps {
261
273
  children: ReactNode;
262
274
  /** Stable ID of the current app, e.g., 'crm'. */
263
275
  currentApp: string;
264
- /** Authenticated Supabase client owned by the host app. */
265
- supabaseClient: SupabaseClient;
276
+ /** Authenticated Supabase client owned by the host app. Optional since
277
+ * 0.8.0 — apps that authenticate elsewhere pass `auth` instead. */
278
+ supabaseClient?: SupabaseClient;
279
+ /** Host-driven auth (takes precedence over supabaseClient). Since 0.8.0. */
280
+ auth?: ShellInjectedAuth;
266
281
  /** App registry for the sidebar. If omitted, uses the default registry shipped with the shell. */
267
282
  appRegistry?: AppRegistryEntry[];
268
283
  /** Optional override for the base path if the app is mounted under a sub-path (e.g., '/contacts'). */
@@ -378,7 +393,7 @@ interface ShellChromeValue {
378
393
  userAvatarUrl?: string;
379
394
  }
380
395
  declare const ShellChromeContext: react.Context<ShellChromeValue | null>;
381
- declare function ShellProvider({ children, supabaseClient, currentApp, appRegistry, basename, organization, linkedAccounts, support, devNotesMenu, myProfileHref, userAvatarUrl, }: ShellProviderProps): react_jsx_runtime.JSX.Element;
396
+ declare function ShellProvider({ children, supabaseClient, auth, currentApp, appRegistry, basename, organization, linkedAccounts, support, devNotesMenu, myProfileHref, userAvatarUrl, }: ShellProviderProps): react_jsx_runtime.JSX.Element;
382
397
 
383
398
  declare function useShellAuth(): ShellAuthValue;
384
399
 
@@ -574,15 +589,13 @@ interface NavItemProps {
574
589
  [key: string]: unknown;
575
590
  }
576
591
  /**
577
- * Single sidebar nav row. Ported verbatim from
578
- * react/app/src/layouts/AuthenticatedLayout.tsx lines 1292–1730 (2026-05-13)
579
- * 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).
580
594
  *
581
- * Renders:
582
- * - Top-level rows with optional children (accordion).
583
- * - Sub-items inside an expanded parent (children list).
584
- * - Active state via `data-nav-item-active` and primary-color fill.
585
- * - 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.
586
599
  */
587
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;
588
601
 
@@ -805,4 +818,4 @@ declare function registerShellPrecache(opts?: RegisterShellPrecacheOptions): Pro
805
818
  */
806
819
  declare function buildSpeculationRules(currentApp: string, registry: AppRegistryEntry[]): string | null;
807
820
 
808
- export { AppLayout, type AppRegistryEntry, BrandIcon, BrandLogo, BreadcrumbBar, Card, CommitCopyButton, type CommitCopyButtonProps, Footer, GlobalUiIdProvider, Header, HeaderTabBar, type HeaderTabBarItem, ModePill, NavItem, OrgPill, OrgPill as OrgSelector, PageContainer, PageHeader, type ShellActiveRoleConfig, type ShellAuthStatus, type ShellAuthValue, ShellChrome, ShellChromeContext, type ShellChromeProps, type ShellChromeValue, ShellFooterContext, type ShellFooterLink, type ShellFooterValue, type ShellLinkedAccount, ShellLinkedAccountsContext, type ShellLinkedAccountsValue, type ShellModeConfig, ShellModeContext, type ShellModeValue, type ShellNavItem, ShellNavItemsContext, type ShellNavItemsValue, ShellNavigationPreferenceContext, type ShellNavigationPreferenceValue, ShellOrgContext, type ShellOrganization, type ShellOrganizationValue, ShellProvider, type ShellProviderProps, ShellRoleContext, type ShellRoleValue, type ShellSlots, type ShellSupportContext, ShellSupportProviderContext, type ShellSupportValue, type ShellUser, Sidebar, SupportRequestButton, UserMenu, buildSpeculationRules, getOrganizationBadgeLetters, getOrganizationBrandColor, getUserDisplayName, hexToRgba, lightenHexColor, normalizeHexColor, politogyTheme, registerShellPrecache, useAppRegistry, useCurrentApp, useShellAuth, useShellChrome, useShellFooter, useShellLinkedAccounts, useShellMode, useShellNavItems, useShellNavigationPreference, useShellOrganization, useShellRole, useShellSupport };
821
+ export { AppLayout, type AppRegistryEntry, BrandIcon, BrandLogo, BreadcrumbBar, Card, CommitCopyButton, type CommitCopyButtonProps, Footer, GlobalUiIdProvider, Header, HeaderTabBar, type HeaderTabBarItem, ModePill, NavItem, OrgPill, OrgPill as OrgSelector, PageContainer, PageHeader, type ShellActiveRoleConfig, type ShellAuthStatus, type ShellAuthValue, ShellChrome, ShellChromeContext, type ShellChromeProps, type ShellChromeValue, ShellFooterContext, type ShellFooterLink, type ShellFooterValue, type ShellInjectedAuth, type ShellLinkedAccount, ShellLinkedAccountsContext, type ShellLinkedAccountsValue, type ShellModeConfig, ShellModeContext, type ShellModeValue, type ShellNavItem, ShellNavItemsContext, type ShellNavItemsValue, ShellNavigationPreferenceContext, type ShellNavigationPreferenceValue, ShellOrgContext, type ShellOrganization, type ShellOrganizationValue, ShellProvider, type ShellProviderProps, ShellRoleContext, type ShellRoleValue, type ShellSlots, type ShellSupportContext, ShellSupportProviderContext, type ShellSupportValue, type ShellUser, Sidebar, SupportRequestButton, UserMenu, buildSpeculationRules, getOrganizationBadgeLetters, getOrganizationBrandColor, getUserDisplayName, hexToRgba, lightenHexColor, normalizeHexColor, politogyTheme, registerShellPrecache, useAppRegistry, useCurrentApp, useShellAuth, useShellChrome, useShellFooter, useShellLinkedAccounts, useShellMode, useShellNavItems, useShellNavigationPreference, useShellOrganization, useShellRole, useShellSupport };
package/dist/index.d.ts CHANGED
@@ -78,6 +78,18 @@ interface ShellUser {
78
78
  }
79
79
  /** Status of the auth session lifecycle. */
80
80
  type ShellAuthStatus = 'loading' | 'authenticated' | 'unauthenticated';
81
+ /**
82
+ * Host-app-driven auth for apps that do not authenticate through Supabase
83
+ * (e.g., OIDC apps like Contacts). When passed to ShellProvider it takes
84
+ * precedence over `supabaseClient` session tracking. The host app is
85
+ * responsible for building a Supabase-shaped `user.raw` (id/email/
86
+ * user_metadata.full_name) so chrome components render identically.
87
+ */
88
+ interface ShellInjectedAuth {
89
+ status: ShellAuthStatus;
90
+ user: ShellUser | null;
91
+ signOut: () => void | Promise<void>;
92
+ }
81
93
  /**
82
94
  * One entry in the shell's app-switcher sidebar. The shell uses this list to
83
95
  * render navigation links between sub-apps and to drive Speculation Rules
@@ -261,8 +273,11 @@ interface ShellProviderProps {
261
273
  children: ReactNode;
262
274
  /** Stable ID of the current app, e.g., 'crm'. */
263
275
  currentApp: string;
264
- /** Authenticated Supabase client owned by the host app. */
265
- supabaseClient: SupabaseClient;
276
+ /** Authenticated Supabase client owned by the host app. Optional since
277
+ * 0.8.0 — apps that authenticate elsewhere pass `auth` instead. */
278
+ supabaseClient?: SupabaseClient;
279
+ /** Host-driven auth (takes precedence over supabaseClient). Since 0.8.0. */
280
+ auth?: ShellInjectedAuth;
266
281
  /** App registry for the sidebar. If omitted, uses the default registry shipped with the shell. */
267
282
  appRegistry?: AppRegistryEntry[];
268
283
  /** Optional override for the base path if the app is mounted under a sub-path (e.g., '/contacts'). */
@@ -378,7 +393,7 @@ interface ShellChromeValue {
378
393
  userAvatarUrl?: string;
379
394
  }
380
395
  declare const ShellChromeContext: react.Context<ShellChromeValue | null>;
381
- declare function ShellProvider({ children, supabaseClient, currentApp, appRegistry, basename, organization, linkedAccounts, support, devNotesMenu, myProfileHref, userAvatarUrl, }: ShellProviderProps): react_jsx_runtime.JSX.Element;
396
+ declare function ShellProvider({ children, supabaseClient, auth, currentApp, appRegistry, basename, organization, linkedAccounts, support, devNotesMenu, myProfileHref, userAvatarUrl, }: ShellProviderProps): react_jsx_runtime.JSX.Element;
382
397
 
383
398
  declare function useShellAuth(): ShellAuthValue;
384
399
 
@@ -574,15 +589,13 @@ interface NavItemProps {
574
589
  [key: string]: unknown;
575
590
  }
576
591
  /**
577
- * Single sidebar nav row. Ported verbatim from
578
- * react/app/src/layouts/AuthenticatedLayout.tsx lines 1292–1730 (2026-05-13)
579
- * 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).
580
594
  *
581
- * Renders:
582
- * - Top-level rows with optional children (accordion).
583
- * - Sub-items inside an expanded parent (children list).
584
- * - Active state via `data-nav-item-active` and primary-color fill.
585
- * - 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.
586
599
  */
587
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;
588
601
 
@@ -805,4 +818,4 @@ declare function registerShellPrecache(opts?: RegisterShellPrecacheOptions): Pro
805
818
  */
806
819
  declare function buildSpeculationRules(currentApp: string, registry: AppRegistryEntry[]): string | null;
807
820
 
808
- export { AppLayout, type AppRegistryEntry, BrandIcon, BrandLogo, BreadcrumbBar, Card, CommitCopyButton, type CommitCopyButtonProps, Footer, GlobalUiIdProvider, Header, HeaderTabBar, type HeaderTabBarItem, ModePill, NavItem, OrgPill, OrgPill as OrgSelector, PageContainer, PageHeader, type ShellActiveRoleConfig, type ShellAuthStatus, type ShellAuthValue, ShellChrome, ShellChromeContext, type ShellChromeProps, type ShellChromeValue, ShellFooterContext, type ShellFooterLink, type ShellFooterValue, type ShellLinkedAccount, ShellLinkedAccountsContext, type ShellLinkedAccountsValue, type ShellModeConfig, ShellModeContext, type ShellModeValue, type ShellNavItem, ShellNavItemsContext, type ShellNavItemsValue, ShellNavigationPreferenceContext, type ShellNavigationPreferenceValue, ShellOrgContext, type ShellOrganization, type ShellOrganizationValue, ShellProvider, type ShellProviderProps, ShellRoleContext, type ShellRoleValue, type ShellSlots, type ShellSupportContext, ShellSupportProviderContext, type ShellSupportValue, type ShellUser, Sidebar, SupportRequestButton, UserMenu, buildSpeculationRules, getOrganizationBadgeLetters, getOrganizationBrandColor, getUserDisplayName, hexToRgba, lightenHexColor, normalizeHexColor, politogyTheme, registerShellPrecache, useAppRegistry, useCurrentApp, useShellAuth, useShellChrome, useShellFooter, useShellLinkedAccounts, useShellMode, useShellNavItems, useShellNavigationPreference, useShellOrganization, useShellRole, useShellSupport };
821
+ export { AppLayout, type AppRegistryEntry, BrandIcon, BrandLogo, BreadcrumbBar, Card, CommitCopyButton, type CommitCopyButtonProps, Footer, GlobalUiIdProvider, Header, HeaderTabBar, type HeaderTabBarItem, ModePill, NavItem, OrgPill, OrgPill as OrgSelector, PageContainer, PageHeader, type ShellActiveRoleConfig, type ShellAuthStatus, type ShellAuthValue, ShellChrome, ShellChromeContext, type ShellChromeProps, type ShellChromeValue, ShellFooterContext, type ShellFooterLink, type ShellFooterValue, type ShellInjectedAuth, type ShellLinkedAccount, ShellLinkedAccountsContext, type ShellLinkedAccountsValue, type ShellModeConfig, ShellModeContext, type ShellModeValue, type ShellNavItem, ShellNavItemsContext, type ShellNavItemsValue, ShellNavigationPreferenceContext, type ShellNavigationPreferenceValue, ShellOrgContext, type ShellOrganization, type ShellOrganizationValue, ShellProvider, type ShellProviderProps, ShellRoleContext, type ShellRoleValue, type ShellSlots, type ShellSupportContext, ShellSupportProviderContext, type ShellSupportValue, type ShellUser, Sidebar, SupportRequestButton, UserMenu, buildSpeculationRules, getOrganizationBadgeLetters, getOrganizationBrandColor, getUserDisplayName, hexToRgba, lightenHexColor, normalizeHexColor, politogyTheme, registerShellPrecache, useAppRegistry, useCurrentApp, useShellAuth, useShellChrome, useShellFooter, useShellLinkedAccounts, useShellMode, useShellNavItems, useShellNavigationPreference, useShellOrganization, useShellRole, useShellSupport };
package/dist/index.js CHANGED
@@ -1120,6 +1120,7 @@ function toShellUser(session) {
1120
1120
  function ShellProvider({
1121
1121
  children,
1122
1122
  supabaseClient,
1123
+ auth,
1123
1124
  currentApp,
1124
1125
  appRegistry,
1125
1126
  basename,
@@ -1133,6 +1134,11 @@ function ShellProvider({
1133
1134
  const [status, setStatus] = useState("loading");
1134
1135
  const [session, setSession] = useState(null);
1135
1136
  useEffect(() => {
1137
+ if (auth || !supabaseClient) {
1138
+ setSession(null);
1139
+ if (!auth) setStatus("unauthenticated");
1140
+ return;
1141
+ }
1136
1142
  let cancelled = false;
1137
1143
  supabaseClient.auth.getSession().then(({ data }) => {
1138
1144
  if (cancelled) return;
@@ -1147,18 +1153,27 @@ function ShellProvider({
1147
1153
  cancelled = true;
1148
1154
  sub.data.subscription.unsubscribe();
1149
1155
  };
1150
- }, [supabaseClient]);
1151
- const value = useMemo(
1152
- () => ({
1156
+ }, [supabaseClient, auth]);
1157
+ const value = useMemo(() => {
1158
+ if (auth) {
1159
+ return {
1160
+ status: auth.status,
1161
+ session: null,
1162
+ user: auth.user,
1163
+ signOut: async () => {
1164
+ await auth.signOut();
1165
+ }
1166
+ };
1167
+ }
1168
+ return {
1153
1169
  status,
1154
1170
  session,
1155
1171
  user: toShellUser(session),
1156
1172
  signOut: async () => {
1157
- await supabaseClient.auth.signOut();
1173
+ await supabaseClient?.auth.signOut();
1158
1174
  }
1159
- }),
1160
- [status, session, supabaseClient]
1161
- );
1175
+ };
1176
+ }, [auth, status, session, supabaseClient]);
1162
1177
  const meta = useMemo(
1163
1178
  () => ({
1164
1179
  currentApp,
@@ -1552,8 +1567,7 @@ function BrandIcon({
1552
1567
  );
1553
1568
  }
1554
1569
  var BrandIcon_default = BrandIcon;
1555
- var MODE_CAMPAIGN = "campaign";
1556
- var MODE_PETITION = "petition";
1570
+ var safeHref = (h) => h && /^(https?:|mailto:|tel:|\/|#|\.)/i.test(h.trim()) ? h : "#";
1557
1571
  function NavItem({
1558
1572
  icon,
1559
1573
  brandIcon,
@@ -1574,23 +1588,9 @@ function NavItem({
1574
1588
  const location = useLocation();
1575
1589
  const navigate = useNavigate();
1576
1590
  const mode = useShellMode();
1577
- const currentMode = mode?.currentMode ?? null;
1578
1591
  const modeConfig = resolveModeConfig(mode);
1579
1592
  const navPref = useShellNavigationPreference();
1580
1593
  const showSubMenu = navPref?.showSubMenu ?? true;
1581
- 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%)";
1582
- const gradientSweepBefore = {
1583
- content: '""',
1584
- position: "absolute",
1585
- top: 0,
1586
- left: 0,
1587
- bottom: 0,
1588
- width: "0%",
1589
- background: navHoverGradient,
1590
- transition: "width 300ms ease",
1591
- pointerEvents: "none",
1592
- zIndex: 0
1593
- };
1594
1594
  const isDisabled = Boolean(navIsDisabled || disabled);
1595
1595
  const effectiveHref = href || (Array.isArray(subItems) && subItems.length > 0 ? subItems[0].href : void 0);
1596
1596
  const effectivePathname = effectiveHref?.split("?")[0];
@@ -1600,6 +1600,7 @@ function NavItem({
1600
1600
  [location.search]
1601
1601
  );
1602
1602
  const matchesSubItem = (itemHref) => {
1603
+ if (!itemHref) return false;
1603
1604
  if (itemHref === currentFullPath) return true;
1604
1605
  const itemPath = itemHref.split("?")[0];
1605
1606
  const itemSearch = itemHref.includes("?") ? itemHref.split("?")[1] : "";
@@ -1643,83 +1644,130 @@ function NavItem({
1643
1644
  return effectivePathname !== `/${location.pathname.split("/")[1]}/dashboard` && location.pathname.startsWith(effectivePathname + "/");
1644
1645
  })();
1645
1646
  const hasSubItems = showSubMenu && Array.isArray(subItems) && subItems.length > 0;
1646
- const hasActiveChild = hasSubItems && subItems.some((item) => matchesSubItem(item.href || ""));
1647
+ const hasActiveChild = Array.isArray(subItems) && subItems.some((item) => matchesSubItem(item.href || ""));
1647
1648
  const isParentActive = isActive || hasActiveChild;
1648
1649
  const isSubMenuOpen = isExpanded !== void 0 ? isExpanded : hasSubItems && subItems.some((item) => matchesSubItem(item.href || ""));
1649
1650
  const expandedRowBg = isParentActive ? modeConfig?.primaryColor || "brand.500" : isSubMenuOpen ? "gray.100" : "transparent";
1650
1651
  const expandedRowTextColor = isParentActive ? "white" : isSubMenuOpen ? "gray.900" : "gray.600";
1651
1652
  const disabledRowBg = isSubItem ? "gray.50" : "transparent";
1652
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
+ };
1653
1685
  if (hasSubItems) {
1654
- const headerProps = {
1655
- align: "center",
1656
- px: "4",
1657
- py: "2.5",
1658
- mx: "4",
1659
- w: "auto",
1660
- borderTopRadius: "lg",
1661
- borderBottomRadius: isSubMenuOpen ? 0 : "lg",
1662
- role: "group",
1663
- cursor: isDisabled ? "not-allowed" : "pointer",
1664
- opacity: 1,
1665
- pointerEvents: isDisabled ? "none" : "auto",
1666
- "data-nav-item-active": String(isParentActive),
1667
- position: "relative",
1668
- overflow: "hidden",
1669
- bg: isDisabled ? disabledRowBg : expandedRowBg,
1670
- color: isDisabled ? disabledRowTextColor : expandedRowTextColor,
1671
- _before: isDisabled ? void 0 : gradientSweepBefore,
1672
- _hover: {
1673
- color: isDisabled ? disabledRowTextColor : "white",
1674
- _before: isDisabled ? void 0 : { width: "100%" }
1675
- },
1676
- "aria-disabled": isDisabled ? "true" : "false",
1677
- onClick: (e) => {
1678
- if (isDisabled) return;
1679
- if (effectiveHref) e.preventDefault();
1680
- e.stopPropagation();
1681
- onToggle?.(menuKey || null);
1682
- }
1683
- };
1684
1686
  return /* @__PURE__ */ jsxs(Box, { children: [
1685
- /* @__PURE__ */ jsxs(Flex, { ...headerProps, ...rest, children: [
1686
- brandIcon ? /* @__PURE__ */ jsx(
1687
- Box,
1688
- {
1689
- mr: "4",
1690
- display: "inline-flex",
1691
- alignItems: "center",
1692
- position: "relative",
1693
- zIndex: 1,
1694
- filter: isParentActive ? "brightness(0) invert(1)" : void 0,
1695
- _groupHover: isDisabled ? void 0 : { filter: "brightness(0) invert(1)" },
1696
- children: /* @__PURE__ */ jsx(BrandIcon_default, { name: brandIcon, size: 18 })
1697
- }
1698
- ) : icon ? /* @__PURE__ */ jsx(
1699
- Box,
1700
- {
1701
- mr: "4",
1702
- fontSize: "16",
1703
- position: "relative",
1704
- zIndex: 1,
1705
- _groupHover: {
1706
- color: isDisabled ? disabledRowTextColor : "white"
1707
- },
1708
- as: icon
1709
- }
1710
- ) : null,
1711
- /* @__PURE__ */ jsx(Box, { flex: "1", position: "relative", zIndex: 1, children }),
1712
- /* @__PURE__ */ jsx(
1713
- Box,
1714
- {
1715
- as: FiChevronDown,
1716
- position: "relative",
1717
- zIndex: 1,
1718
- transform: isSubMenuOpen ? "rotate(180deg)" : void 0,
1719
- transition: "transform 0.2s"
1720
- }
1721
- )
1722
- ] }),
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
+ ),
1723
1771
  /* @__PURE__ */ jsx(Collapse, { in: isSubMenuOpen, animateOpacity: true, children: /* @__PURE__ */ jsx(
1724
1772
  Box,
1725
1773
  {
@@ -1763,74 +1811,68 @@ function NavItem({
1763
1811
  navigate(effectiveHref);
1764
1812
  }
1765
1813
  };
1766
- return /* @__PURE__ */ jsx(Box, { as: "a", href: effectiveHref || "#", onClick: handleClick, style: { textDecoration: "none" }, children: /* @__PURE__ */ jsxs(
1767
- Flex,
1814
+ const rowActive = isSubItem ? isActive : isParentActive;
1815
+ return /* @__PURE__ */ jsx(
1816
+ Box,
1768
1817
  {
1769
- align: "center",
1770
- px: "4",
1771
- py: "2.5",
1772
- mx: isSubItem ? "0" : "4",
1773
- w: isSubItem ? "full" : "auto",
1774
- borderRadius: isSubItem ? "0" : "lg",
1775
- role: "group",
1776
- cursor: isDisabled ? "not-allowed" : "pointer",
1777
- opacity: 1,
1778
- pointerEvents: isDisabled ? "none" : "auto",
1779
- "data-nav-item-active": String(isActive),
1780
- position: "relative",
1781
- overflow: "hidden",
1782
- bg: isDisabled ? disabledRowBg : isActive ? isSubItem ? "gray.100" : modeConfig?.primaryColor || "brand.500" : "transparent",
1783
- color: isDisabled ? disabledRowTextColor : isActive ? isSubItem ? modeConfig?.primaryColor || "brand.500" : "white" : "gray.600",
1784
- _before: isDisabled ? void 0 : gradientSweepBefore,
1785
- _hover: {
1786
- color: isDisabled ? disabledRowTextColor : "white",
1787
- _before: isDisabled ? void 0 : { width: "100%" }
1788
- },
1789
- "aria-disabled": isDisabled ? "true" : "false",
1818
+ as: "a",
1819
+ href: safeHref(effectiveHref),
1790
1820
  onClick: handleClick,
1791
- children: [
1792
- brandIcon ? /* @__PURE__ */ jsx(
1793
- Box,
1794
- {
1795
- mr: "4",
1796
- display: "inline-flex",
1797
- alignItems: "center",
1798
- position: "relative",
1799
- zIndex: 1,
1800
- filter: isActive && !isSubItem ? "brightness(0) invert(1)" : void 0,
1801
- _groupHover: isDisabled ? void 0 : { filter: "brightness(0) invert(1)" },
1802
- children: /* @__PURE__ */ jsx(BrandIcon_default, { name: brandIcon, size: 18 })
1803
- }
1804
- ) : icon ? /* @__PURE__ */ jsx(
1805
- Box,
1806
- {
1807
- mr: "4",
1808
- fontSize: "16",
1809
- position: "relative",
1810
- zIndex: 1,
1811
- _groupHover: {
1812
- color: isDisabled ? disabledRowTextColor : "white"
1813
- },
1814
- as: icon
1815
- }
1816
- ) : null,
1817
- /* @__PURE__ */ jsxs(Box, { flex: "1", position: "relative", zIndex: 1, children: [
1818
- /* @__PURE__ */ jsx(Box, { children }),
1819
- subLabel ? /* @__PURE__ */ jsx(
1820
- Text,
1821
- {
1822
- mt: 0.5,
1823
- fontSize: "10px",
1824
- lineHeight: "1.3",
1825
- color: isDisabled ? "gray.400" : "gray.400",
1826
- children: subLabel
1827
- }
1828
- ) : null,
1829
- badge && /* @__PURE__ */ jsx(Badge, { colorScheme: "orange", size: "xs", mt: 1, fontSize: "xs", display: "block", children: badge })
1830
- ] })
1831
- ]
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
+ )
1832
1874
  }
1833
- ) });
1875
+ );
1834
1876
  }
1835
1877
  var NavItem_default = NavItem;
1836
1878