Add collapsible sidebar - click chevron to collapse/expand
This commit is contained in:
parent
e67020aa63
commit
246ebd8e65
@ -13,7 +13,8 @@ import {
|
|||||||
Wrench,
|
Wrench,
|
||||||
Target,
|
Target,
|
||||||
Menu,
|
Menu,
|
||||||
X,
|
ChevronLeft,
|
||||||
|
ChevronRight,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet";
|
import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet";
|
||||||
@ -32,24 +33,26 @@ const navItems = [
|
|||||||
|
|
||||||
const MISSION_SHORT = "Build an iOS empire → retire on our terms → travel with Heidi → 53 is just the start";
|
const MISSION_SHORT = "Build an iOS empire → retire on our terms → travel with Heidi → 53 is just the start";
|
||||||
|
|
||||||
function SidebarContent({ pathname }: { pathname: string }) {
|
function SidebarContent({ pathname, collapsed = false }: { pathname: string; collapsed?: boolean }) {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-full">
|
<div className="flex flex-col h-full">
|
||||||
<div className="flex items-center gap-2 px-4 py-6">
|
<div className={cn("flex items-center gap-2 py-6", collapsed ? "px-2 justify-center" : "px-4")}>
|
||||||
<div className="w-8 h-8 rounded-lg bg-gradient-to-br from-blue-500 to-purple-600 flex items-center justify-center">
|
<div className="w-8 h-8 rounded-lg bg-gradient-to-br from-blue-500 to-purple-600 flex items-center justify-center shrink-0">
|
||||||
<span className="text-white font-bold text-sm">MC</span>
|
<span className="text-white font-bold text-sm">MC</span>
|
||||||
</div>
|
</div>
|
||||||
<span className="font-bold text-lg">Mission Control</span>
|
{!collapsed && <span className="font-bold text-lg">Mission Control</span>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Mission Statement */}
|
{/* Mission Statement - hide when collapsed */}
|
||||||
<div className="px-4 py-3 mx-3 mb-2 rounded-lg bg-gradient-to-r from-blue-500/10 to-purple-500/10 border border-blue-500/20">
|
{!collapsed && (
|
||||||
<p className="text-[10px] text-muted-foreground leading-tight">
|
<div className="px-4 py-3 mx-3 mb-2 rounded-lg bg-gradient-to-r from-blue-500/10 to-purple-500/10 border border-blue-500/20">
|
||||||
{MISSION_SHORT}
|
<p className="text-[10px] text-muted-foreground leading-tight">
|
||||||
</p>
|
{MISSION_SHORT}
|
||||||
</div>
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<nav className="flex-1 px-3 py-4 space-y-1">
|
<nav className={cn("flex-1 py-4 space-y-1", collapsed ? "px-2" : "px-3")}>
|
||||||
{navItems.map((item) => {
|
{navItems.map((item) => {
|
||||||
const Icon = item.icon;
|
const Icon = item.icon;
|
||||||
const isActive = pathname === item.href;
|
const isActive = pathname === item.href;
|
||||||
@ -57,66 +60,139 @@ function SidebarContent({ pathname }: { pathname: string }) {
|
|||||||
<Link
|
<Link
|
||||||
key={item.href}
|
key={item.href}
|
||||||
href={item.href}
|
href={item.href}
|
||||||
|
title={collapsed ? item.name : undefined}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex items-center gap-3 px-3 py-2 rounded-lg text-sm font-medium transition-colors",
|
"flex items-center rounded-lg text-sm font-medium transition-colors",
|
||||||
|
collapsed
|
||||||
|
? "justify-center px-2 py-3"
|
||||||
|
: "gap-3 px-3 py-2",
|
||||||
isActive
|
isActive
|
||||||
? "bg-primary text-primary-foreground"
|
? "bg-primary text-primary-foreground"
|
||||||
: "text-muted-foreground hover:bg-accent hover:text-accent-foreground"
|
: "text-muted-foreground hover:bg-accent hover:text-accent-foreground"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Icon className="w-4 h-4" />
|
<Icon className="w-4 h-4 shrink-0" />
|
||||||
{item.name}
|
{!collapsed && <span>{item.name}</span>}
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div className="p-4 border-t border-border">
|
<div className={cn("border-t border-border", collapsed ? "p-2" : "p-4")}>
|
||||||
<div className="flex items-center gap-3">
|
<div className={cn("flex items-center", collapsed ? "justify-center" : "gap-3")}>
|
||||||
<div className="w-8 h-8 rounded-full bg-gradient-to-br from-green-400 to-blue-500" />
|
<div className="w-8 h-8 rounded-full bg-gradient-to-br from-green-400 to-blue-500 shrink-0" />
|
||||||
<div className="flex-1 min-w-0">
|
{!collapsed && (
|
||||||
<p className="text-sm font-medium truncate">Matt Bruce</p>
|
<div className="flex-1 min-w-0">
|
||||||
<p className="text-xs text-muted-foreground truncate">TopDogLabs</p>
|
<p className="text-sm font-medium truncate">Matt Bruce</p>
|
||||||
</div>
|
<p className="text-xs text-muted-foreground truncate">TopDogLabs</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Sidebar() {
|
function MobileSidebar({ pathname }: { pathname: string }) {
|
||||||
const pathname = usePathname();
|
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Sheet open={open} onOpenChange={setOpen}>
|
||||||
{/* Desktop Sidebar */}
|
<SheetTrigger asChild className="lg:hidden">
|
||||||
<div className="hidden lg:flex w-64 flex-col fixed inset-y-0 left-0 border-r border-border bg-card">
|
<Button variant="ghost" size="icon" className="absolute top-4 left-4 z-50">
|
||||||
|
<Menu className="w-5 h-5" />
|
||||||
|
</Button>
|
||||||
|
</SheetTrigger>
|
||||||
|
<SheetContent side="left" className="w-64 p-0">
|
||||||
<SidebarContent pathname={pathname} />
|
<SidebarContent pathname={pathname} />
|
||||||
</div>
|
</SheetContent>
|
||||||
|
</Sheet>
|
||||||
{/* Mobile Sidebar */}
|
|
||||||
<Sheet open={open} onOpenChange={setOpen}>
|
|
||||||
<SheetTrigger asChild className="lg:hidden">
|
|
||||||
<Button variant="ghost" size="icon" className="absolute top-4 left-4 z-50">
|
|
||||||
<Menu className="w-5 h-5" />
|
|
||||||
</Button>
|
|
||||||
</SheetTrigger>
|
|
||||||
<SheetContent side="left" className="w-64 p-0">
|
|
||||||
<SidebarContent pathname={pathname} />
|
|
||||||
</SheetContent>
|
|
||||||
</Sheet>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DashboardLayout({ children }: { children: React.ReactNode }) {
|
export function DashboardLayout({ children }: { children: React.ReactNode }) {
|
||||||
|
const [collapsed, setCollapsed] = useState(false);
|
||||||
|
const pathname = usePathname();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-background">
|
<div className="min-h-screen bg-background">
|
||||||
<Sidebar />
|
{/* Desktop Sidebar - Collapsible */}
|
||||||
<main className="lg:pl-64">
|
<div
|
||||||
|
className={cn(
|
||||||
|
"hidden lg:flex flex-col fixed inset-y-0 left-0 border-r border-border bg-card transition-all duration-300 z-40",
|
||||||
|
collapsed ? "w-16" : "w-64"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{/* Collapse Toggle Button */}
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
onClick={() => setCollapsed(!collapsed)}
|
||||||
|
className={cn(
|
||||||
|
"absolute -right-3 top-6 w-6 h-6 rounded-full border border-border bg-card shadow-sm",
|
||||||
|
"hover:bg-accent z-50"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{collapsed ? (
|
||||||
|
<ChevronRight className="w-3 h-3" />
|
||||||
|
) : (
|
||||||
|
<ChevronLeft className="w-3 h-3" />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<SidebarContent pathname={pathname} collapsed={collapsed} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Mobile Sidebar */}
|
||||||
|
<MobileSidebar pathname={pathname} />
|
||||||
|
|
||||||
|
<main
|
||||||
|
className={cn(
|
||||||
|
"transition-all duration-300",
|
||||||
|
collapsed ? "lg:pl-16" : "lg:pl-64"
|
||||||
|
)}
|
||||||
|
>
|
||||||
<div className="p-4 lg:p-8">{children}</div>
|
<div className="p-4 lg:p-8">{children}</div>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Backwards compatibility - export Sidebar for pages that import it directly
|
||||||
|
export function Sidebar() {
|
||||||
|
const pathname = usePathname();
|
||||||
|
const [collapsed, setCollapsed] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/* Desktop Sidebar - Collapsible */}
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"hidden lg:flex flex-col fixed inset-y-0 left-0 border-r border-border bg-card transition-all duration-300 z-40",
|
||||||
|
collapsed ? "w-16" : "w-64"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
onClick={() => setCollapsed(!collapsed)}
|
||||||
|
className={cn(
|
||||||
|
"absolute -right-3 top-6 w-6 h-6 rounded-full border border-border bg-card shadow-sm z-50",
|
||||||
|
"hover:bg-accent"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{collapsed ? (
|
||||||
|
<ChevronRight className="w-3 h-3" />
|
||||||
|
) : (
|
||||||
|
<ChevronLeft className="w-3 h-3" />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<SidebarContent pathname={pathname} collapsed={collapsed} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Mobile Sidebar */}
|
||||||
|
<MobileSidebar pathname={pathname} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user