feat: redesign sidebar navigation with grouped sections, improved visual hierarchy, and enhanced active state indicators
Restructure navigation into three logical groups (Operate, Network, Security) with uppercase section labels, move system-related items (Jobs, Audit, Users, Settings) to separate bottom section with border separator, add NF logo badge next to app title in header, extract SidebarLink component with refined styling including left accent bar for active items, update active state to
This commit is contained in:
@@ -18,31 +18,76 @@ import {
|
||||
SquareStack,
|
||||
Sun,
|
||||
Users,
|
||||
type LucideIcon,
|
||||
} from "lucide-react";
|
||||
import { useEffect } from "react";
|
||||
|
||||
import { token } from "../api/client";
|
||||
import { useTheme } from "../stores/theme";
|
||||
|
||||
const nav = [
|
||||
{ to: "/", label: "Dashboard", icon: LayoutDashboard },
|
||||
{ to: "/clusters", label: "Clusters", icon: Server },
|
||||
{ to: "/nodes", label: "Nodes", icon: Activity },
|
||||
{ to: "/workloads", label: "VMs/LXCs", icon: Blocks },
|
||||
{ to: "/networks", label: "Networks", icon: Network },
|
||||
{ to: "/ipam", label: "IPAM", icon: Database },
|
||||
{ to: "/tenants", label: "Tenants", icon: BriefcaseBusiness },
|
||||
{ to: "/security-groups", label: "Security Groups", icon: Shield },
|
||||
{ to: "/policies", label: "Policies", icon: GitBranch },
|
||||
{ to: "/services", label: "Service Catalog", icon: SquareStack },
|
||||
{ to: "/designer", label: "Policy Designer", icon: LockKeyhole },
|
||||
{ to: "/firewall", label: "Firewall Preview", icon: Flame },
|
||||
const navGroups = [
|
||||
{
|
||||
label: "Operate",
|
||||
items: [
|
||||
{ to: "/", label: "Dashboard", icon: LayoutDashboard },
|
||||
{ to: "/clusters", label: "Clusters", icon: Server },
|
||||
{ to: "/nodes", label: "Nodes", icon: Activity },
|
||||
{ to: "/workloads", label: "VMs/LXCs", icon: Blocks },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Network",
|
||||
items: [
|
||||
{ to: "/networks", label: "Networks", icon: Network },
|
||||
{ to: "/ipam", label: "IPAM", icon: Database },
|
||||
{ to: "/services", label: "Service Catalog", icon: SquareStack },
|
||||
{ to: "/tenants", label: "Tenants", icon: BriefcaseBusiness },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Security",
|
||||
items: [
|
||||
{ to: "/security-groups", label: "Security Groups", icon: Shield },
|
||||
{ to: "/policies", label: "Policies", icon: GitBranch },
|
||||
{ to: "/designer", label: "Policy Designer", icon: LockKeyhole },
|
||||
{ to: "/firewall", label: "Firewall Preview", icon: Flame },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const systemNav = [
|
||||
{ to: "/jobs", label: "Jobs", icon: ClipboardList },
|
||||
{ to: "/audit", label: "Audit Logs", icon: BookOpen },
|
||||
{ to: "/users", label: "Users", icon: Users },
|
||||
{ to: "/settings", label: "Settings", icon: Settings },
|
||||
];
|
||||
|
||||
function SidebarLink({ item }: { item: { to: string; label: string; icon: LucideIcon } }) {
|
||||
const Icon = item.icon;
|
||||
return (
|
||||
<NavLink
|
||||
key={item.to}
|
||||
to={item.to}
|
||||
end={item.to === "/"}
|
||||
className={({ isActive }) =>
|
||||
`group relative flex h-9 items-center gap-3 rounded-md px-3 text-sm transition-colors ${
|
||||
isActive
|
||||
? "bg-accent/10 text-accent dark:bg-accent/15"
|
||||
: "text-slate-600 hover:bg-slate-100 hover:text-slate-950 dark:text-slate-300 dark:hover:bg-slate-800/80 dark:hover:text-white"
|
||||
}`
|
||||
}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<>
|
||||
<span className={`absolute left-0 h-5 w-0.5 rounded-r-full ${isActive ? "bg-accent" : "bg-transparent"}`} />
|
||||
<Icon size={17} className={isActive ? "text-accent" : "text-slate-400 group-hover:text-slate-700 dark:group-hover:text-slate-200"} />
|
||||
<span className="truncate">{item.label}</span>
|
||||
</>
|
||||
)}
|
||||
</NavLink>
|
||||
);
|
||||
}
|
||||
|
||||
export function Layout() {
|
||||
const navigate = useNavigate();
|
||||
const { dark, toggle } = useTheme();
|
||||
@@ -60,29 +105,31 @@ export function Layout() {
|
||||
<div className="min-h-screen bg-canvas text-slate-900 dark:text-slate-100">
|
||||
<aside className="fixed inset-y-0 left-0 hidden w-64 border-r border-border bg-panel md:block">
|
||||
<div className="flex h-16 items-center border-b border-border px-5">
|
||||
<div>
|
||||
<div className="text-lg font-semibold">NexaFabric</div>
|
||||
<div className="text-xs text-slate-500 dark:text-slate-400">Control Plane</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="grid h-9 w-9 place-items-center rounded-md bg-accent text-sm font-semibold text-white">NF</div>
|
||||
<div>
|
||||
<div className="text-base font-semibold leading-5">NexaFabric</div>
|
||||
<div className="text-xs text-slate-500 dark:text-slate-400">Control Plane</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<nav className="h-[calc(100vh-4rem)] overflow-y-auto p-3">
|
||||
{nav.map((item) => {
|
||||
const Icon = item.icon;
|
||||
return (
|
||||
<NavLink
|
||||
key={item.to}
|
||||
to={item.to}
|
||||
className={({ isActive }) =>
|
||||
`mb-1 flex h-10 items-center gap-3 rounded-md px-3 text-sm ${
|
||||
isActive ? "bg-accent text-white" : "text-slate-600 hover:bg-slate-100 dark:text-slate-300 dark:hover:bg-slate-800"
|
||||
}`
|
||||
}
|
||||
>
|
||||
<Icon size={18} />
|
||||
<span>{item.label}</span>
|
||||
</NavLink>
|
||||
);
|
||||
})}
|
||||
<nav className="flex h-[calc(100vh-4rem)] flex-col overflow-y-auto p-3">
|
||||
<div className="space-y-5">
|
||||
{navGroups.map((group) => (
|
||||
<section key={group.label}>
|
||||
<div className="mb-2 px-3 text-[11px] font-semibold uppercase tracking-wider text-slate-400 dark:text-slate-500">{group.label}</div>
|
||||
<div className="space-y-1">
|
||||
{group.items.map((item) => <SidebarLink key={item.to} item={item} />)}
|
||||
</div>
|
||||
</section>
|
||||
))}
|
||||
</div>
|
||||
<div className="mt-auto border-t border-border pt-3">
|
||||
<div className="mb-2 px-3 text-[11px] font-semibold uppercase tracking-wider text-slate-400 dark:text-slate-500">System</div>
|
||||
<div className="space-y-1">
|
||||
{systemNav.map((item) => <SidebarLink key={item.to} item={item} />)}
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</aside>
|
||||
<main className="md:pl-64">
|
||||
|
||||
Reference in New Issue
Block a user