Retour au catalogue
Sidebar Dashboard
Sidebar dashboard avec groupes collapsibles, badges et avatar.
sidebarsimple Both Responsive a11y
minimaluniversalstacked
Theme
"use client";
import { useState } from "react";
import {
LayoutDashboard, BarChart3, Users, Settings, FileText,
ChevronDown, Bell, LogOut, Inbox, CreditCard, Shield,
} from "lucide-react";
interface NavItem {
label: string;
icon: React.ElementType;
badge?: string;
href?: string;
children?: { label: string; href: string }[];
}
const navItems: NavItem[] = [
{ label: "Dashboard", icon: LayoutDashboard, href: "#" },
{ label: "Analytics", icon: BarChart3, badge: "New", href: "#" },
{
label: "Team",
icon: Users,
children: [
{ label: "Members", href: "#" },
{ label: "Roles", href: "#" },
{ label: "Invitations", href: "#" },
],
},
{ label: "Inbox", icon: Inbox, badge: "12", href: "#" },
{ label: "Documents", icon: FileText, href: "#" },
{
label: "Settings",
icon: Settings,
children: [
{ label: "General", href: "#" },
{ label: "Billing", href: "#" },
{ label: "Security", href: "#" },
{ label: "Notifications", href: "#" },
],
},
];
const bottomItems = [
{ label: "Billing", icon: CreditCard },
{ label: "Security", icon: Shield },
];
export default function SidebarDashboard() {
const [activeItem, setActiveItem] = useState("Dashboard");
const [expanded, setExpanded] = useState<Set<string>>(new Set(["Team"]));
const toggleExpand = (label: string) => {
setExpanded((prev) => {
const next = new Set(prev);
if (next.has(label)) next.delete(label);
else next.add(label);
return next;
});
};
return (
<aside
className="w-64 min-h-[600px] flex flex-col"
style={{ background: "var(--color-background)", borderRight: "1px solid var(--color-border)" }}
>
{/* Brand */}
<div className="px-5 py-5 flex items-center gap-3" style={{ borderBottom: "1px solid var(--color-border)" }}>
<div
className="w-8 h-8 rounded-lg flex items-center justify-center text-sm font-bold"
style={{ background: "var(--color-accent)", color: "var(--color-background)" }}
>
A
</div>
<div>
<p className="text-sm font-semibold" style={{ color: "var(--color-foreground)" }}>Acme Corp</p>
<p className="text-[10px]" style={{ color: "var(--color-foreground-light)" }}>Pro Plan</p>
</div>
<button className="ml-auto relative">
<Bell size={16} style={{ color: "var(--color-foreground-muted)" }} />
<span
className="absolute -top-1 -right-1 w-2 h-2 rounded-full"
style={{ background: "var(--color-accent)" }}
/>
</button>
</div>
{/* Navigation */}
<nav className="flex-1 px-3 py-4 flex flex-col gap-0.5 overflow-y-auto">
{navItems.map((item) => {
const Icon = item.icon;
const isActive = activeItem === item.label;
const isExpanded = expanded.has(item.label);
const hasChildren = item.children && item.children.length > 0;
return (
<div key={item.label}>
<button
onClick={() => {
if (hasChildren) toggleExpand(item.label);
else setActiveItem(item.label);
}}
className="flex items-center gap-2.5 w-full px-3 py-2 text-sm rounded-lg transition-all duration-200"
style={{
background: isActive ? "var(--color-background-alt)" : "transparent",
color: isActive ? "var(--color-foreground)" : "var(--color-foreground-muted)",
}}
>
<Icon size={16} style={{ color: isActive ? "var(--color-accent)" : "var(--color-foreground-muted)" }} />
<span className="flex-1 text-left font-medium">{item.label}</span>
{item.badge && (
<span
className="px-1.5 py-0.5 text-[10px] font-bold rounded-full"
style={{ background: "var(--color-accent)", color: "var(--color-background)" }}
>
{item.badge}
</span>
)}
{hasChildren && (
<ChevronDown
size={14}
style={{
color: "var(--color-foreground-light)",
transform: isExpanded ? "rotate(180deg)" : "rotate(0)",
transition: "transform 0.2s",
}}
/>
)}
</button>
{hasChildren && isExpanded && (
<div className="ml-5 pl-4 py-1 flex flex-col gap-0.5" style={{ borderLeft: "1px solid var(--color-border)" }}>
{item.children?.map((child) => (
<a
key={child.label}
href={child.href}
onClick={(e) => { e.preventDefault(); setActiveItem(child.label); }}
className="block px-3 py-1.5 text-[13px] rounded-md transition-colors duration-200"
style={{
color: activeItem === child.label ? "var(--color-accent)" : "var(--color-foreground-muted)",
background: activeItem === child.label ? "var(--color-background-alt)" : "transparent",
}}
>
{child.label}
</a>
))}
</div>
)}
</div>
);
})}
{/* Separator */}
<div className="my-3 h-px" style={{ background: "var(--color-border)" }} />
{bottomItems.map((item) => {
const Icon = item.icon;
return (
<button
key={item.label}
className="flex items-center gap-2.5 w-full px-3 py-2 text-sm rounded-lg transition-colors duration-200"
style={{ color: "var(--color-foreground-muted)" }}
>
<Icon size={16} />
<span className="font-medium">{item.label}</span>
</button>
);
})}
</nav>
{/* User section */}
<div className="px-4 py-4" style={{ borderTop: "1px solid var(--color-border)" }}>
<div className="flex items-center gap-3">
<div
className="w-8 h-8 rounded-full flex items-center justify-center text-xs font-bold"
style={{ background: "var(--color-accent)", color: "var(--color-background)" }}
>
JD
</div>
<div className="flex-1 min-w-0">
<p className="text-sm font-medium truncate" style={{ color: "var(--color-foreground)" }}>Jane Doe</p>
<p className="text-[10px] truncate" style={{ color: "var(--color-foreground-light)" }}>jane@acme.com</p>
</div>
<button className="p-1.5 rounded-md transition-colors hover:bg-[var(--color-background-alt)]">
<LogOut size={14} style={{ color: "var(--color-foreground-light)" }} />
</button>
</div>
</div>
</aside>
);
}