Sidebar mobile friendly
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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}
|
||||||
|
height={24}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
{!collapsed && (
|
<div className="flex flex-col gap-0.5 leading-none">
|
||||||
<span className="font-mono font-semibold text-foreground tracking-tight">
|
<span className="font-semibold">DOCKSHIP</span>
|
||||||
DOCKSHIP
|
<span className="text-xs text-muted-foreground">
|
||||||
|
v{version}
|
||||||
</span>
|
</span>
|
||||||
)}
|
|
||||||
</div>
|
</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>
|
</Link>
|
||||||
</li>
|
</SidebarMenuButton>
|
||||||
);
|
</SidebarMenuItem>
|
||||||
})}
|
</SidebarMenu>
|
||||||
</ul>
|
</SidebarHeader>
|
||||||
</nav>
|
|
||||||
|
|
||||||
{/* Collapse Toggle */}
|
<SidebarContent>
|
||||||
<div className="p-2 border-t border-sidebar-border">
|
<SidebarGroup>
|
||||||
<Button
|
<SidebarGroupContent>
|
||||||
variant="ghost"
|
<SidebarMenu>
|
||||||
size="icon-sm"
|
{navigation.map((item) => (
|
||||||
onClick={() => setCollapsed(!collapsed)}
|
<SidebarMenuItem
|
||||||
className="w-full flex justify-center"
|
key={item.name}
|
||||||
>
|
className={cn(
|
||||||
{collapsed ? (
|
"border-l-2",
|
||||||
<ChevronRight className="w-4 h-4" />
|
item.href === pathname
|
||||||
) : (
|
? "border-primary"
|
||||||
<ChevronLeft className="w-4 h-4" />
|
: "border-transparent"
|
||||||
)}
|
)}
|
||||||
</Button>
|
>
|
||||||
</div>
|
<SidebarMenuButton
|
||||||
</aside>
|
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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user