From 7a21c85cce582936ab94f37a71649dba6430749c Mon Sep 17 00:00:00 2001 From: BOHA Date: Sat, 6 Jun 2026 20:06:25 +0200 Subject: [PATCH] =?UTF-8?q?feat(mui):=20SidebarNav=20=E2=80=94=20MUI=20Lis?= =?UTF-8?q?t=20nav=20with=20white-pill=20active=20state?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add src/admin/ui/SidebarNav.tsx: drawer content component using MUI List/ListItemButton with NavLink polymorphism. Active item uses the `selected` prop for the white "pill" styling (not NavLink active class). Renders logo header, permission-filtered nav sections, and user/logout footer. Not yet wired — next task mounts it in a Drawer. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/admin/ui/SidebarNav.tsx | 172 ++++++++++++++++++++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100644 src/admin/ui/SidebarNav.tsx diff --git a/src/admin/ui/SidebarNav.tsx b/src/admin/ui/SidebarNav.tsx new file mode 100644 index 0000000..2120ffa --- /dev/null +++ b/src/admin/ui/SidebarNav.tsx @@ -0,0 +1,172 @@ +import { NavLink, useLocation } from "react-router-dom"; +import Box from "@mui/material/Box"; +import List from "@mui/material/List"; +import ListItemButton from "@mui/material/ListItemButton"; +import ListItemIcon from "@mui/material/ListItemIcon"; +import ListItemText from "@mui/material/ListItemText"; +import Typography from "@mui/material/Typography"; +import Avatar from "@mui/material/Avatar"; +import Button from "@mui/material/Button"; +import { useAuth } from "../context/AuthContext"; +import { useTheme as useAppTheme } from "../../context/ThemeContext"; +import { menuSections, isItemActive, hasItemPermission } from "./navData"; + +interface SidebarNavProps { + /** Closes the mobile drawer on item click (optional — desktop omits it). */ + onNavigate?: () => void; + /** Logout handler, owned by the parent. */ + onLogout: () => void; +} + +export default function SidebarNav({ onNavigate, onLogout }: SidebarNavProps) { + const { user, hasPermission } = useAuth(); + const { theme } = useAppTheme(); + const location = useLocation(); + + const visibleSections = menuSections + .map((section) => ({ + ...section, + items: section.items.filter((item) => + hasItemPermission(item, hasPermission), + ), + })) + .filter((section) => section.items.length > 0); + + return ( + + {/* Logo header */} + + { + e.currentTarget.src = + theme === "dark" + ? "/images/logo-dark.png" + : "/images/logo-light.png"; + }} + sx={{ + maxHeight: 40, + maxWidth: "100%", + objectFit: "contain", + display: "block", + }} + /> + + + {/* Nav */} + + {visibleSections.map((section) => ( + + + {section.label} + + + {section.items.map((item) => ( + + + {item.icon} + + + + ))} + + + ))} + + + {/* Footer */} + + + + {user?.fullName?.charAt(0) || user?.username?.charAt(0) || "U"} + + + + {user?.fullName || user?.username} + + + {user?.roleDisplay} + + + + + + + ); +}