Feat/ab/menu-and-header (#17)

* chore(deps): add lucide-react for layout icon migration

* feat(auth): add getPrimaryUserRole helper for header display

* style(theme): add sidebar active tokens and nav group typography

* refactor(menu): migrate nav icons to lucide and trim menu groups

* feat(layout): redesign sidebar, topbar, and admin shell viewport

* chore(menu): hide Reports and Documents from sidebar

---------

Co-authored-by: Arthur Bassi <arthur.winiarski.ranger@outlook.com>
This commit is contained in:
Arthur Bassi 2026-06-18 11:30:09 -03:00 • committed by GitHub
parent b6ac72147e
commit f1a2527a49
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 348 additions and 260 deletions

16
package-lock.json generated
View file

@ -29,6 +29,7 @@
"@testing-library/user-event": "^13.5.0",
"clsx": "^2.1.1",
"ky": "^1.7.5",
"lucide-react": "^1.17.0",
"react": "^19.1.1",
"react-dom": "^19.1.1",
"react-hook-form": "^7.78.0",
@ -5752,6 +5753,15 @@
"yallist": "^3.0.2"
}
},
"node_modules/lucide-react": {
"version": "1.17.0",
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.17.0.tgz",
"integrity": "sha512-9FA9evdox/JQL5PT57fdA1x/yg8T7knJ98+zjTL3UfKza6pflQUUh3XtaQIHKvnsJw1lmsEyHVlt5jchYxOQ5w==",
"license": "ISC",
"peerDependencies": {
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/lz-string": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
@ -11255,6 +11265,12 @@
"yallist": "^3.0.2"
}
},
"lucide-react": {
"version": "1.17.0",
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.17.0.tgz",
"integrity": "sha512-9FA9evdox/JQL5PT57fdA1x/yg8T7knJ98+zjTL3UfKza6pflQUUh3XtaQIHKvnsJw1lmsEyHVlt5jchYxOQ5w==",
"requires": {}
},
"lz-string": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",

View file

@ -60,6 +60,7 @@
"@testing-library/user-event": "^13.5.0",
"clsx": "^2.1.1",
"ky": "^1.7.5",
"lucide-react": "^1.17.0",
"react": "^19.1.1",
"react-dom": "^19.1.1",
"react-hook-form": "^7.78.0",

View file

@ -3,31 +3,28 @@ import { Outlet } from "react-router-dom";
import { Box } from "@mui/material";
import { ProtectedRoute } from "@/components/auth/protected-route";
import {
AppSidebar,
SIDEBAR_COLLAPSED_WIDTH,
SIDEBAR_WIDTH,
} from "@/components/layout/app-sidebar";
import { AppSidebar } from "@/components/layout/app-sidebar";
import { getSidebarWidth } from "@/config/menu";
import { AppTopbar } from "@/components/layout/app-topbar";
export default function AdminShellLayout() {
const [navCollapsed, setNavCollapsed] = useState(false);
const sidebarWidth = navCollapsed ? SIDEBAR_COLLAPSED_WIDTH : SIDEBAR_WIDTH;
const sidebarWidth = getSidebarWidth(navCollapsed);
return (
<ProtectedRoute>
<Box className="flex min-h-screen w-full bg-background">
<Box className="flex h-screen w-full overflow-hidden bg-background">
<AppSidebar collapsed={navCollapsed} />
<Box
className="flex min-h-screen min-w-0 flex-1 flex-col"
className="flex h-screen min-w-0 flex-1 flex-col overflow-hidden"
sx={{ width: `calc(100% - ${sidebarWidth}px)` }}
>
<AppTopbar onToggleNav={() => setNavCollapsed((value) => !value)} />
<Box
component="main"
className="flex-1 p-2.5"
className="flex-1 overflow-auto p-2.5"
sx={{ backgroundColor: "var(--color-bg-page)" }}
>
<Outlet />

View file

@ -1,76 +1,117 @@
import { useState } from "react";
import { NavLink } from "react-router-dom";
import {
Box,
Drawer,
List,
ListItemButton,
ListItemIcon,
ListItemText,
Typography,
} from "@mui/material";
import { Box, Drawer } from "@mui/material";
import { navGroups, type NavItemDef } from "@/config/menu";
import { cn } from "@/lib/utils";
import { BrandLockup, BrandMark } from "@/components/common/brand-lockup";
import { getSidebarWidth, navGroups, type NavItemDef } from "@/config/menu";
const SIDEBAR_WIDTH = 220;
const SIDEBAR_COLLAPSED_WIDTH = 56;
function getNavItemAppearance(isActive: boolean, isHover: boolean) {
if (isActive) {
return {
itemColor: "var(--primary)",
iconColor: "var(--primary)",
itemBg: "var(--color-sidebar-item-active-bg)",
};
}
if (isHover) {
return {
itemColor: "var(--primary-hover)",
iconColor: "var(--primary-hover)",
itemBg: "var(--sidebar-accent)",
};
}
return {
itemColor: "var(--foreground)",
iconColor: "var(--muted-foreground)",
itemBg: "transparent",
};
}
type NavItemProps = NavItemDef & {
collapsed: boolean;
end?: boolean;
};
function NavItem({ label, icon: Icon, path, collapsed, end }: NavItemProps) {
function NavItem({ label, icon: Icon, path, collapsed, end, badge }: NavItemProps) {
const [hovered, setHovered] = useState(false);
return (
<NavLink to={path} end={end} className="no-underline">
{({ isActive }) => (
<ListItemButton
title={collapsed ? label : undefined}
className={cn("mb-1 rounded-md", isActive && !collapsed && "font-semibold")}
sx={{
justifyContent: collapsed ? "center" : "flex-start",
px: collapsed ? 1 : 1.75,
py: 1.25,
color: "var(--color-sidebar-text)",
transition: "background-color var(--duration-fast, 150ms) var(--ease-default, ease)",
"&:hover": {
backgroundColor: "var(--color-sidebar-hover-bg)",
color: "var(--color-sidebar-text-hover)",
},
...(isActive && collapsed
? { backgroundColor: "var(--color-sidebar-hover-bg)" }
: isActive
? {
backgroundColor: "var(--color-sidebar-active-bg)",
color: "var(--color-sidebar-active-text)",
borderLeft: "3px solid var(--primary)",
pl: "13px",
}
: {}),
}}
>
<ListItemIcon
<NavLink to={path} end={end} className="no-underline" title={collapsed ? label : undefined}>
{({ isActive }) => {
const isHover = hovered && !isActive;
const { itemColor, iconColor, itemBg } = getNavItemAppearance(isActive, isHover);
return (
<Box
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
sx={{
minWidth: collapsed ? 0 : 34,
justifyContent: "center",
color: "inherit",
mr: collapsed ? 0 : 0.5,
position: "relative",
display: "flex",
alignItems: "center",
width: "100%",
height: 40,
gap: "12px",
px: collapsed ? 0 : "12px",
justifyContent: collapsed ? "center" : "flex-start",
borderRadius: "6px",
cursor: "pointer",
fontFamily: "var(--font-sans)",
fontSize: 14,
fontWeight: isActive ? 600 : 500,
color: itemColor,
backgroundColor: itemBg,
boxShadow: isActive ? "var(--color-sidebar-item-active-shadow)" : "none",
transition: "all var(--duration-fast, 150ms) var(--ease-default, ease)",
mb: "4px",
textAlign: "left",
}}
>
<Icon size={18} />
</ListItemIcon>
{!collapsed && (
<ListItemText
primary={label}
primaryTypographyProps={{
fontSize: "0.875rem",
fontWeight: "inherit",
noWrap: true,
}}
/>
)}
</ListItemButton>
)}
{isActive && (
<Box
component="span"
sx={{
position: "absolute",
left: 0,
top: 8,
bottom: 8,
width: 3,
borderRadius: "0 3px 3px 0",
backgroundColor: "var(--primary)",
}}
/>
)}
<Icon size={19} className="shrink-0" style={{ color: iconColor }} />
{!collapsed && (
<>
<Box className="truncate flex-1">{label}</Box>
{badge != null && (
<Box
component="span"
className="inline-flex shrink-0 items-center justify-center"
sx={{
height: 18,
minWidth: 18,
px: "6px",
borderRadius: "9999px",
backgroundColor: "var(--accent)",
color: "#ffffff",
fontFamily: "var(--font-sans)",
fontSize: 10,
fontWeight: 700,
lineHeight: 1,
}}
>
{badge}
</Box>
)}
</>
)}
</Box>
);
}}
</NavLink>
);
}
@ -80,7 +121,7 @@ type AppSidebarProps = {
};
export function AppSidebar({ collapsed }: AppSidebarProps) {
const width = collapsed ? SIDEBAR_COLLAPSED_WIDTH : SIDEBAR_WIDTH;
const width = getSidebarWidth(collapsed);
return (
<Drawer
@ -93,83 +134,76 @@ export function AppSidebar({ collapsed }: AppSidebarProps) {
boxSizing: "border-box",
backgroundColor: "var(--color-sidebar-bg)",
borderRight: "1px solid var(--color-sidebar-border)",
transition: "width var(--duration-normal, 250ms) var(--ease-default, ease)",
borderRadius: 0,
transition: "width 200ms var(--ease-default, ease)",
overflowX: "hidden",
},
}}
>
<Box
className="flex flex-col"
sx={{
height: "100vh",
px: collapsed ? 1 : 2.5,
py: 2.5,
}}
>
<Box className="flex h-full flex-col overflow-hidden">
<Box
className={cn("mb-7 flex items-center", collapsed && "justify-center")}
sx={{ minHeight: 40 }}
className="flex shrink-0 items-center"
sx={{
height: "var(--spacing-topbar)",
gap: "10px",
px: collapsed ? 0 : "16px",
justifyContent: collapsed ? "center" : "flex-start",
backgroundColor: "var(--card)",
borderBottom: "1px solid var(--color-sidebar-border)",
}}
>
<Box
component="img"
src="/logo.png"
alt="SeaHaven Industries"
sx={{
maxWidth: collapsed ? 32 : 140,
height: "auto",
display: "block",
transition: "max-width var(--duration-normal, 250ms) var(--ease-default, ease)",
mx: collapsed ? "auto" : 0,
}}
/>
{collapsed ? <BrandMark size="compact" /> : <BrandLockup size="compact" />}
</Box>
<List disablePadding className="flex-1 overflow-y-auto overflow-x-hidden">
<Box component="nav" className="flex-1 overflow-y-auto overflow-x-hidden" sx={{ py: 1 }}>
{navGroups.map((group, groupIndex) => (
<Box key={group.title} component="li" sx={{ listStyle: "none" }}>
{!collapsed && (
<Typography
className="text-label"
<Box key={group.title || `group-${groupIndex}`}>
{collapsed ? (
groupIndex > 0 && (
<Box
sx={{
height: 1,
backgroundColor: "var(--color-sidebar-border)",
mx: "14px",
my: "10px",
}}
/>
)
) : group.title ? (
<Box
component="p"
className="text-nav-group"
sx={{
px: 1.75,
mt: groupIndex === 0 ? 0 : 2.25,
mb: 0.75,
color: "var(--color-sidebar-label)",
m: 0,
px: "10px",
pt: "12px",
pb: "6px",
}}
>
{group.title}
</Typography>
</Box>
) : (
<Box sx={{ height: 8 }} />
)}
{collapsed && groupIndex > 0 && (
<Box
sx={{
borderTop: "1px solid var(--color-sidebar-border)",
mt: 1,
pt: 1,
mx: 0.5,
}}
/>
)}
<List disablePadding component="ul">
{group.items.map(({ label, icon, path }) => (
<NavItem
key={path}
label={label}
icon={icon}
path={path}
collapsed={collapsed}
end={path === "/"}
/>
<Box component="ul" sx={{ listStyle: "none", m: 0, p: "0 8px" }}>
{group.items.map(({ label, icon, path, badge }) => (
<Box component="li" key={path} sx={{ listStyle: "none" }}>
<NavItem
label={label}
icon={icon}
path={path}
badge={badge}
collapsed={collapsed}
end={path === "/"}
/>
</Box>
))}
</List>
</Box>
</Box>
))}
</List>
</Box>
</Box>
</Drawer>
);
}
export { SIDEBAR_COLLAPSED_WIDTH, SIDEBAR_WIDTH };

View file

@ -1,17 +1,11 @@
import { useState } from "react";
import { useNavigate } from "react-router-dom";
import CheckCircleOutlineIcon from "@mui/icons-material/CheckCircleOutline";
import LogoutOutlinedIcon from "@mui/icons-material/LogoutOutlined";
import MenuIcon from "@mui/icons-material/Menu";
import NotificationsNoneOutlinedIcon from "@mui/icons-material/NotificationsNoneOutlined";
import PersonOutlineOutlinedIcon from "@mui/icons-material/PersonOutlineOutlined";
import SettingsOutlinedIcon from "@mui/icons-material/SettingsOutlined";
import {
AppBar,
Avatar,
Box,
Divider,
IconButton,
ListItemIcon,
ListItemText,
Menu,
@ -19,8 +13,10 @@ import {
Toolbar,
Typography,
} from "@mui/material";
import { ChevronDown, Menu as MenuIcon } from "lucide-react";
import { useAuthContext } from "@/providers/auth-context";
import { getPrimaryUserRole } from "@/lib/auth/user-utils";
type AppTopbarProps = {
onToggleNav: () => void;
@ -30,7 +26,6 @@ export function AppTopbar({ onToggleNav }: AppTopbarProps) {
const navigate = useNavigate();
const { user, logout } = useAuthContext();
const [notifAnchor, setNotifAnchor] = useState<null | HTMLElement>(null);
const [userAnchor, setUserAnchor] = useState<null | HTMLElement>(null);
const handleLogout = () => {
@ -47,100 +42,133 @@ export function AppTopbar({ onToggleNav }: AppTopbarProps) {
.toUpperCase()
: "U";
const userRole = getPrimaryUserRole(user?.userRoles);
return (
<AppBar
position="sticky"
elevation={0}
square
sx={{
height: "var(--spacing-topbar)",
background: "var(--color-header-gradient)",
boxShadow: "var(--color-header-shadow)",
color: "var(--color-header-text)",
borderRadius: 0,
}}
>
<Toolbar
disableGutters
className="flex justify-between px-4"
sx={{ minHeight: "var(--spacing-topbar) !important" }}
>
<Box className="flex items-center">
<IconButton
onClick={onToggleNav}
aria-label="Toggle navigation"
sx={{
color: "var(--color-header-icon)",
mr: 1.5,
"&:hover": { backgroundColor: "var(--color-header-surface-hover)" },
}}
>
<MenuIcon />
</IconButton>
</Box>
<Box className="flex items-center gap-1">
<IconButton
title="Notifications"
onClick={(event) => setNotifAnchor(event.currentTarget)}
sx={{
color: "var(--color-header-text-muted)",
"&:hover": {
color: "var(--color-header-text)",
backgroundColor: "var(--color-header-chip-hover)",
},
}}
>
<NotificationsNoneOutlinedIcon fontSize="small" />
</IconButton>
<Divider
orientation="vertical"
flexItem
sx={{
mx: 0.5,
borderColor: "var(--color-header-surface)",
alignSelf: "center",
height: 24,
}}
/>
<IconButton
title="Account"
onClick={(event) => setUserAnchor(event.currentTarget)}
sx={{
color: "var(--color-header-text-muted)",
"&:hover": {
color: "var(--color-header-text)",
backgroundColor: "var(--color-header-chip-hover)",
},
}}
>
<PersonOutlineOutlinedIcon fontSize="small" />
</IconButton>
</Box>
</Toolbar>
<Menu
anchorEl={notifAnchor}
open={Boolean(notifAnchor)}
onClose={() => setNotifAnchor(null)}
transformOrigin={{ horizontal: "right", vertical: "top" }}
anchorOrigin={{ horizontal: "right", vertical: "bottom" }}
slotProps={{
paper: {
sx: { width: 300, mt: 1, boxShadow: "var(--shadow-lg)" },
},
className="flex items-center"
sx={{
minHeight: "var(--spacing-topbar) !important",
height: "var(--spacing-topbar)",
gap: "18px",
px: "16px",
pr: "22px",
}}
>
<Box sx={{ px: 2, py: 1.5, borderBottom: "1px solid var(--border)" }}>
<Typography variant="subtitle2">Notifications</Typography>
<Box
component="button"
type="button"
onClick={onToggleNav}
aria-label="Toggle navigation"
title="Toggle sidebar"
sx={{
display: "flex",
alignItems: "center",
justifyContent: "center",
flexShrink: 0,
width: 38,
height: 38,
borderRadius: "6px",
border: "none",
cursor: "pointer",
backgroundColor: "var(--color-header-surface)",
color: "var(--color-header-icon)",
transition: "background-color var(--duration-fast, 150ms) var(--ease-default, ease)",
"&:hover": {
backgroundColor: "var(--color-header-surface-hover)",
},
}}
>
<MenuIcon size={18} />
</Box>
<Box className="flex flex-col items-center gap-2 px-4 py-6 text-muted-foreground">
<CheckCircleOutlineIcon sx={{ fontSize: 28, color: "var(--muted)" }} />
<Typography variant="body2" color="text.secondary">
No new notifications
</Typography>
<Box sx={{ flex: 1 }} />
<Box
component="button"
type="button"
aria-haspopup="true"
aria-expanded={Boolean(userAnchor)}
onClick={(event) => setUserAnchor(event.currentTarget)}
sx={{
display: "flex",
alignItems: "center",
flexShrink: 0,
gap: "10px",
py: "5px",
pl: "6px",
pr: "10px",
borderRadius: "9999px",
border: "none",
cursor: "pointer",
backgroundColor: "transparent",
color: "var(--color-header-text)",
transition: "background-color var(--duration-fast, 150ms) var(--ease-default, ease)",
"&:hover": {
backgroundColor: "var(--color-header-chip-hover)",
},
}}
>
<Box
className="flex shrink-0 items-center justify-center"
sx={{
width: 34,
height: 34,
borderRadius: "50%",
background: "linear-gradient(135deg, var(--accent), var(--primary))",
boxShadow: "0 0 0 2px var(--color-header-avatar-ring)",
fontFamily: "var(--font-display)",
fontSize: 12,
fontWeight: 700,
color: "#ffffff",
}}
>
{userInitials}
</Box>
<Box className="text-left leading-tight">
<Typography
component="p"
sx={{
fontFamily: "var(--font-sans)",
fontSize: 13,
fontWeight: 600,
color: "#ffffff",
lineHeight: 1.25,
}}
>
{user?.fullname || "User"}
</Typography>
<Typography
component="p"
sx={{
fontSize: 11,
color: "var(--color-header-text-muted)",
lineHeight: 1.25,
}}
>
{userRole}
</Typography>
</Box>
<ChevronDown
size={15}
className="shrink-0"
style={{ color: "var(--color-header-text-muted)" }}
/>
</Box>
</Menu>
</Toolbar>
<Menu
anchorEl={userAnchor}
@ -155,19 +183,25 @@ export function AppTopbar({ onToggleNav }: AppTopbarProps) {
}}
>
<Box className="flex items-center gap-3 px-4 py-3">
<Avatar
<Box
className="flex shrink-0 items-center justify-center"
sx={{
width: 40,
height: 40,
bgcolor: "var(--accent)",
borderRadius: "50%",
background: "linear-gradient(135deg, var(--accent), var(--primary))",
fontSize: "0.875rem",
fontWeight: 600,
color: "#ffffff",
border: "2px solid var(--color-header-avatar-ring)",
}}
>
{userInitials}
</Avatar>
</Box>
<Box>
<Typography variant="body2" fontWeight={600}>
{user?.fullname || "User"}
</Typography>
<Typography variant="caption" color="text.secondary">
{user?.email || ""}
</Typography>

View file

@ -1,24 +1,18 @@
import type { LucideIcon } from "lucide-react";
import {
FaAddressBook,
FaBell,
FaClipboardList,
FaCog,
FaCubes,
FaDollarSign,
FaFileInvoiceDollar,
FaMapMarkerAlt,
FaMoneyCheckAlt,
FaTachometerAlt,
FaTools,
FaTruck,
FaUserTie,
} from "react-icons/fa";
import type { IconType } from "react-icons";
// BarChart2,
BriefcaseBusiness,
Building2,
ClipboardList,
FileText,
LayoutDashboard,
} from "lucide-react";
export type NavItemDef = {
label: string;
icon: IconType;
icon: LucideIcon;
path: string;
badge?: number;
};
export type NavGroup = {
@ -26,42 +20,36 @@ export type NavGroup = {
items: NavItemDef[];
};
export const SIDEBAR_WIDTH = 244;
export const SIDEBAR_COLLAPSED_WIDTH = 76;
export function getSidebarWidth(collapsed: boolean): number {
return collapsed ? SIDEBAR_COLLAPSED_WIDTH : SIDEBAR_WIDTH;
}
export const navGroups: NavGroup[] = [
{
title: "Overview",
items: [{ label: "Dashboard", icon: FaTachometerAlt, path: "/" }],
},
{
title: "Operations",
title: "",
items: [
{ label: "Work Orders", icon: FaTools, path: "/workorders" },
{ label: "Uplifts", icon: FaDollarSign, path: "/uplifts" },
{ label: "Follow Ups", icon: FaBell, path: "/followups" },
{ label: "PM Schedules", icon: FaClipboardList, path: "/pmschedules" },
{ label: "Work Orders", icon: ClipboardList, path: "/workorders", badge: 11 },
{ label: "Dashboard", icon: LayoutDashboard, path: "/" },
// { label: "Reports", icon: BarChart2, path: "/reports" },
],
},
{
title: "Vendors",
title: "VENDORS",
items: [
{ label: "Vendors", icon: FaTruck, path: "/vendors" },
{ label: "Vendor POs", icon: FaFileInvoiceDollar, path: "/vendor-pos" },
{ label: "Vendors", icon: Building2, path: "/vendors" },
{ label: "Vendor POs", icon: FileText, path: "/vendor-pos" },
],
},
{
title: "Customers & Sites",
items: [
{ label: "Accounts", icon: FaMoneyCheckAlt, path: "/accounts" },
{ label: "Contacts", icon: FaAddressBook, path: "/contacts" },
{ label: "Locations", icon: FaMapMarkerAlt, path: "/locations" },
{ label: "Assets", icon: FaCubes, path: "/assets" },
],
},
{
title: "Internal",
items: [{ label: "Employees", icon: FaUserTie, path: "/employees" }],
},
{
title: "System",
items: [{ label: "Settings", icon: FaCog, path: "/settings/dropdowns" }],
title: "CUSTOMERS & SITES",
items: [{ label: "Accounts", icon: BriefcaseBusiness, path: "/accounts" }],
},
// {
// title: "INTERNAL",
// items: [{ label: "Documents", icon: FileText, path: "/documents" }],
// },
];

View file

@ -0,0 +1,6 @@
export function getPrimaryUserRole(
userRoles: string | null | undefined,
fallback = "User",
): string {
return userRoles?.split(",")[0]?.trim() || fallback;
}

View file

@ -29,9 +29,9 @@
--tracking-wider: 0.08em;
/* ── LAYOUT SPACING ─────────────────────────────────────────────── */
--spacing-sidebar: 220px;
--spacing-sidebar: 244px;
--spacing-topbar: 64px;
--spacing-sidebar-collapsed: 56px;
--spacing-sidebar-collapsed: 76px;
/* ── RADIUS SCALE (scale resolves to 4 / 6 / 8 / 12) ────────────── */
--radius: 8px;
@ -122,6 +122,8 @@
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-badge-bg: #e6f4fb;
--color-sidebar-badge-text: #155a92;
--color-sidebar-item-active-bg: var(--card);
--color-sidebar-item-active-shadow: 0 1px 3px rgba(38, 34, 98, 0.08);
/* ── SEMANTIC (universal meaning) ───────────────────────────────── */
--color-destructive: var(--destructive);

View file

@ -71,3 +71,13 @@ h6 {
text-transform: uppercase;
color: var(--color-sidebar-label);
}
.text-nav-group {
font-family: var(--font-mono);
font-size: 10px;
font-weight: 600;
line-height: normal;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--muted-foreground);
}