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