Sidebar mobile friendly

This commit is contained in:
2025-12-26 21:22:17 +01:00
parent 225eea0861
commit 947b9e0f1b
3 changed files with 114 additions and 73 deletions
+28 -4
View File
@@ -1,5 +1,6 @@
import { Bell, Search, User } from "lucide-react"; import { Bell, Search, User } from "lucide-react";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { useSidebar } from "../ui/sidebar";
interface DashboardHeaderProps { interface DashboardHeaderProps {
title: string; title: string;
@@ -7,8 +8,13 @@ interface DashboardHeaderProps {
} }
export function DashboardHeader({ title, subtitle }: DashboardHeaderProps) { export function DashboardHeader({ title, subtitle }: DashboardHeaderProps) {
const sidebar = useSidebar();
return ( return (
<header className="h-14 border-b border-border flex items-center justify-between px-6 bg-background"> <header
className="h-14 border-b border-border flex items-center justify-between px-6 bg-background"
onClick={() => sidebar.setOpenMobile(true)}
>
<div> <div>
<h1 className="text-sm font-medium text-foreground">{title}</h1> <h1 className="text-sm font-medium text-foreground">{title}</h1>
{subtitle && ( {subtitle && (
@@ -17,14 +23,32 @@ export function DashboardHeader({ title, subtitle }: DashboardHeaderProps) {
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Button variant="ghost" size="icon"> <Button
variant="ghost"
size="icon"
onClick={(e) => {
e.stopPropagation();
}}
>
<Search className="w-4 h-4" /> <Search className="w-4 h-4" />
</Button> </Button>
<Button variant="ghost" size="icon"> <Button
variant="ghost"
size="icon"
onClick={(e) => {
e.stopPropagation();
}}
>
<Bell className="w-4 h-4" /> <Bell className="w-4 h-4" />
</Button> </Button>
<div className="w-px h-6 bg-border mx-2" /> <div className="w-px h-6 bg-border mx-2" />
<Button variant="ghost" size="icon"> <Button
variant="ghost"
size="icon"
onClick={(e) => {
e.stopPropagation();
}}
>
<User className="w-4 h-4" /> <User className="w-4 h-4" />
</Button> </Button>
</div> </div>
+9 -6
View File
@@ -1,6 +1,7 @@
import { ReactNode } from "react"; import { ReactNode } from "react";
import { DashboardSidebar } from "./DashboardSidebar"; import { DashboardSidebar } from "./DashboardSidebar";
import { DashboardHeader } from "./DashboardHeader"; import { DashboardHeader } from "./DashboardHeader";
import { SidebarProvider } from "../ui/sidebar";
interface DashboardLayoutProps { interface DashboardLayoutProps {
children: ReactNode; children: ReactNode;
@@ -8,16 +9,18 @@ interface DashboardLayoutProps {
subtitle?: string; subtitle?: string;
} }
export function DashboardLayout({ children, title, subtitle }: DashboardLayoutProps) { export function DashboardLayout({
children,
title,
subtitle,
}: DashboardLayoutProps) {
return ( return (
<div className="flex h-screen w-full bg-background"> <SidebarProvider>
<DashboardSidebar /> <DashboardSidebar />
<div className="flex-1 flex flex-col overflow-hidden"> <div className="flex-1 flex flex-col overflow-hidden">
<DashboardHeader title={title} subtitle={subtitle} /> <DashboardHeader title={title} subtitle={subtitle} />
<main className="flex-1 overflow-auto p-6"> <main className="flex-1 overflow-auto p-6">{children}</main>
{children}
</main>
</div> </div>
</div> </SidebarProvider>
); );
} }
+77 -63
View File
@@ -1,4 +1,5 @@
import { useState } from "react"; "use client";
import { import {
LayoutGrid, LayoutGrid,
Box, Box,
@@ -7,15 +8,29 @@ import {
FileText, FileText,
Settings, Settings,
Activity, Activity,
ChevronLeft,
ChevronRight,
Container, Container,
Command,
} from "lucide-react"; } from "lucide-react";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import { usePathname } from "next/navigation"; import { usePathname } from "next/navigation";
import Link from "next/link"; import Link from "next/link";
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarGroupContent,
SidebarHeader,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarRail,
useSidebar,
} from "@/components/ui/sidebar";
import Image from "next/image";
import { cn } from "@/lib/utils";
import { version } from "../../../package.json";
const navigation = [ const navigation = [
{ name: "Overview", href: "/overview", icon: LayoutGrid }, { name: "Overview", href: "/overview", icon: LayoutGrid },
{ name: "Applications", href: "/applications", icon: Box }, { name: "Applications", href: "/applications", icon: Box },
@@ -28,70 +43,69 @@ const navigation = [
]; ];
export function DashboardSidebar() { export function DashboardSidebar() {
const [collapsed, setCollapsed] = useState(false); const pathname = usePathname();
const location = usePathname();
return ( return (
<aside <Sidebar collapsible="icon">
className={cn( <SidebarHeader>
"flex flex-col h-screen bg-sidebar border-r border-sidebar-border transition-all duration-300", <SidebarMenu>
collapsed ? "w-14" : "w-56" <SidebarMenuItem>
)} <SidebarMenuButton size="lg" asChild>
> <Link href="/">
{/* Logo */} <div className="flex aspect-square size-8 items-center justify-center rounded-lg text-primary-foreground">
<div className="flex items-center h-14 px-4 border-b border-sidebar-border"> <Image
<div className="flex items-center gap-2"> src="/dockship.svg"
<div className="w-6 h-6 bg-primary flex items-center justify-center"> alt="DockShip"
<span className="text-primary-foreground text-xs font-bold">D</span> width={24}
</div> height={24}
{!collapsed && ( />
<span className="font-mono font-semibold text-foreground tracking-tight"> </div>
DOCKSHIP <div className="flex flex-col gap-0.5 leading-none">
</span> <span className="font-semibold">DOCKSHIP</span>
)} <span className="text-xs text-muted-foreground">
</div> v{version}
</div> </span>
</div>
</Link>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarHeader>
{/* Navigation */} <SidebarContent>
<nav className="flex-1 py-4 overflow-y-auto"> <SidebarGroup>
<ul className="space-y-1 px-2"> <SidebarGroupContent>
{navigation.map((item) => { <SidebarMenu>
const isActive = location === item.href; {navigation.map((item) => (
return ( <SidebarMenuItem
<li key={item.name}> key={item.name}
<Link
href={item.href}
className={cn( className={cn(
"flex items-center gap-3 px-3 py-2 text-sm transition-colors", "border-l-2",
isActive item.href === pathname
? "bg-sidebar-accent text-sidebar-accent-foreground border-l-2 border-primary -ml-0.5" ? "border-primary"
: "text-sidebar-foreground hover:bg-sidebar-accent hover:text-sidebar-accent-foreground" : "border-transparent"
)} )}
> >
<item.icon className="w-4 h-4 shrink-0" /> <SidebarMenuButton
{!collapsed && <span>{item.name}</span>} asChild
</Link> isActive={pathname === item.href}
</li> tooltip={item.name}
); >
})} <Link href={item.href}>
</ul> <item.icon />
</nav> <span>{item.name}</span>
</Link>
</SidebarMenuButton>
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
{/* Collapse Toggle */} <SidebarFooter></SidebarFooter>
<div className="p-2 border-t border-sidebar-border">
<Button <SidebarRail />
variant="ghost" </Sidebar>
size="icon-sm"
onClick={() => setCollapsed(!collapsed)}
className="w-full flex justify-center"
>
{collapsed ? (
<ChevronRight className="w-4 h-4" />
) : (
<ChevronLeft className="w-4 h-4" />
)}
</Button>
</div>
</aside>
); );
} }