Files
ERP_Frontend/components/Layouts/AppSidebar.tsx
T
2026-07-08 11:08:24 +05:30

190 lines
6.4 KiB
TypeScript

"use client"
import { useEffect, useState } from "react"
import Link from "next/link"
import { usePathname } from "next/navigation"
import {
ChevronRight,
HelpCircle,
LayoutGrid,
Menu,
Package,
Settings,
ShoppingCart,
X,
type LucideIcon,
} from "lucide-react"
import { cn } from "@/lib/utils"
const navItems: { title: string; href: string; icon: LucideIcon; chevron?: boolean }[] = [
{ title: "Dashboard", href: "/dashboard", icon: LayoutGrid },
{ title: "Products", href: "/dashboard/products", icon: Package, chevron: true },
{ title: "Orders", href: "/dashboard/orders", icon: ShoppingCart, chevron: true },
{ title: "Settings", href: "/dashboard/settings", icon: Settings, chevron: true },
{ title: "Help", href: "/dashboard/help", icon: HelpCircle },
]
function SidebarContent({
collapsed,
onCollapse,
onClose,
pathname,
isMobile,
}: {
collapsed: boolean
onCollapse: () => void
onClose?: () => void
pathname: string
isMobile: boolean
}) {
return (
<nav
className={cn(
"flex h-full flex-col rounded-3xl bg-white p-3 shadow-sm ring-1 ring-black/5 transition-[width] duration-200",
!isMobile && (collapsed ? "w-20" : "w-64")
)}
>
{/* Header */}
<div
className={cn(
"mb-10 flex items-center gap-2.5 px-4 py-3",
!isMobile && collapsed ? "flex-col-reverse justify-center gap-3 px-0" : "justify-between"
)}
>
<Link href="/dashboard" className="flex items-center gap-2.5" onClick={onClose}>
<div className="flex size-8 shrink-0 items-center justify-center rounded-xl bg-indigo-50">
<svg viewBox="0 0 48 32" className="h-3.5 w-5 fill-indigo-600">
<path d="M24 16c-3-8-11-12-16-8s-3 14 6 14c5 0 8.5-2.5 10-6 1.5 3.5 5 6 10 6 9 0 11-10 6-14s-13 0-16 8Z" />
</svg>
</div>
{(!collapsed || isMobile) && (
<span className="text-lg font-bold tracking-tight text-slate-900">Hexa ERP</span>
)}
</Link>
<button
type="button"
onClick={isMobile ? onClose : onCollapse}
aria-label={isMobile ? "Close menu" : collapsed ? "Expand sidebar" : "Minimize sidebar"}
className="flex size-8 shrink-0 items-center justify-center rounded-xl text-slate-400 hover:bg-slate-50 hover:text-slate-600"
>
{isMobile ? <X className="size-4" /> : <Menu className="size-4" />}
</button>
</div>
{/* Nav items */}
<ul className="flex flex-col gap-1">
{navItems.map((item) => {
const isActive =
item.href === "/dashboard" ? pathname === item.href : pathname.startsWith(item.href)
return (
<li key={item.href}>
<Link
href={item.href}
title={!isMobile && collapsed ? item.title : undefined}
onClick={onClose}
className={cn(
"flex items-center gap-3 rounded-2xl px-4 py-3 text-base font-semibold transition-colors",
!isMobile && collapsed && "justify-center px-0",
isActive
? "bg-indigo-50 text-indigo-600"
: "text-slate-700 hover:bg-slate-50"
)}
>
<item.icon
className={cn("size-5 shrink-0", isActive ? "text-indigo-600" : "text-slate-400")}
/>
{(!collapsed || isMobile) && (
<>
<span className="flex-1">{item.title}</span>
{item.chevron && !isActive && (
<ChevronRight className="size-4 shrink-0 text-slate-300" />
)}
</>
)}
</Link>
</li>
)
})}
</ul>
<div className="mt-auto flex items-center justify-center pt-6">
<div className="flex size-12 items-center justify-center rounded-2xl bg-slate-50 ring-1 ring-black/5">
<svg viewBox="0 0 48 32" className="h-4 w-6 fill-slate-400">
<path d="M24 16c-3-8-11-12-16-8s-3 14 6 14c5 0 8.5-2.5 10-6 1.5 3.5 5 6 10 6 9 0 11-10 6-14s-13 0-16 8Z" />
</svg>
</div>
</div>
</nav>
)
}
export function AppSidebar() {
const pathname = usePathname()
const [collapsed, setCollapsed] = useState(false)
const [mobileOpen, setMobileOpen] = useState(false)
// Close mobile menu on route change
useEffect(() => {
const timeout = setTimeout(() => setMobileOpen(false), 0)
return () => clearTimeout(timeout)
}, [pathname])
// Prevent body scroll when mobile menu is open
useEffect(() => {
document.body.style.overflow = mobileOpen ? "hidden" : ""
return () => { document.body.style.overflow = "" }
}, [mobileOpen])
return (
<>
{/* ── Desktop sidebar ─────────────────────────────── */}
<div className="hidden lg:flex">
<SidebarContent
collapsed={collapsed}
onCollapse={() => setCollapsed((v) => !v)}
pathname={pathname}
isMobile={false}
/>
</div>
{/* ── Mobile hamburger trigger ─────────────────────── */}
<button
type="button"
onClick={() => setMobileOpen(true)}
aria-label="Open menu"
className="fixed top-5 left-5 z-40 flex size-10 items-center justify-center rounded-2xl bg-white shadow-sm ring-1 ring-black/5 text-slate-600 hover:bg-slate-50 lg:hidden"
>
<Menu className="size-5" />
</button>
{/* ── Mobile overlay backdrop ──────────────────────── */}
{mobileOpen && (
<div
className="fixed inset-0 z-40 bg-black/30 backdrop-blur-xs lg:hidden"
onClick={() => setMobileOpen(false)}
aria-hidden="true"
/>
)}
{/* ── Mobile drawer ────────────────────────────────── */}
<div
className={cn(
"fixed inset-y-0 left-0 z-50 w-72 p-3 transition-transform duration-200 lg:hidden",
mobileOpen ? "translate-x-0" : "-translate-x-full"
)}
>
<SidebarContent
collapsed={false}
onCollapse={() => {}}
onClose={() => setMobileOpen(false)}
pathname={pathname}
isMobile={true}
/>
</div>
</>
)
}