From d14605b57f9007490bef054d5524033319219b67 Mon Sep 17 00:00:00 2001 From: Skylinerffm97 Date: Mon, 28 Sep 2026 00:12:13 +0200 Subject: [PATCH 1/6] web(nav): make the Email, CRM and Resources sections collapsible --- web/src/components/layout/AppNav.tsx | 36 +++++++++++++++++++++++----- web/src/stores/slices/uiSlice.ts | 29 ++++++++++++++++++++++ 2 files changed, 59 insertions(+), 6 deletions(-) diff --git a/web/src/components/layout/AppNav.tsx b/web/src/components/layout/AppNav.tsx index f88467465..c8cd17927 100644 --- a/web/src/components/layout/AppNav.tsx +++ b/web/src/components/layout/AppNav.tsx @@ -13,6 +13,7 @@ import { CableIcon, CalendarClockIcon, CheckSquareIcon, + ChevronDownIcon, CircleDollarSignIcon, FileTextIcon, FlameIcon, @@ -33,7 +34,7 @@ import { XIcon, ZapIcon, } from "lucide-react"; -import { type ReactElement, type ReactNode, useMemo, useState } from "react"; +import { type ReactElement, type ReactNode, useId, useMemo, useState } from "react"; import { useAppStore } from "@/stores"; import useFeatureAccess from "@/hooks/useFeatureAccess"; import { usePermission, type PermissionKey } from "@/hooks/usePermission"; @@ -207,6 +208,10 @@ const ICON_ROW = "group relative mx-auto flex size-8 items-center justify-center const LABEL_ROW = "group relative mx-2 w-[calc(100%-1rem)] flex items-center gap-2.5 px-2.5 h-7 rounded-md text-[12.5px] transition-colors duration-100"; const rowClass = (collapsed: boolean) => (collapsed ? ICON_ROW : LABEL_ROW); +function isNavItemActive(pathname: string, item: NavItem): boolean { + return pathname === item.url || pathname.startsWith(item.url + "/"); +} + function NavRow({ item, collapsed = false }: { item: NavItem; collapsed?: boolean }) { const { pathname } = useLocation(); const unseen = useAppStore((s) => s.unseenCount); @@ -214,8 +219,7 @@ function NavRow({ item, collapsed = false }: { item: NavItem; collapsed?: boolea const hasItemPermission = usePermission(item.permission ?? "VIEW_CAMPAIGNS"); const [deniedOpen, setDeniedOpen] = useState(false); const upgradeDialog = useUpgradeDialog(); - const active = - pathname === item.url || pathname.startsWith(item.url + "/"); + const active = isNavItemActive(pathname, item); const badge = item.badgeStoreKey === "unseenCount" ? unseen : undefined; // Role-gated items disappear from the sidebar for users that @@ -685,18 +689,38 @@ function Section({ first?: boolean; collapsed?: boolean; }) { + const id = useId(); + const { pathname } = useLocation(); + const sectionCollapsed = useAppStore((s) => s.navCollapsedSections[section.label] ?? false); + const toggleNavSection = useAppStore((s) => s.toggleNavSection); + const active = section.items.some((item) => isNavItemActive(pathname, item)); + const hidden = !collapsed && sectionCollapsed; + return (
{/* Collapsed, the hairline above the group carries the grouping on its own — a tracked-uppercase label does not fit in 56px. */} {!collapsed && (
- +
)} -
+