98 lines
2.9 KiB
TypeScript
98 lines
2.9 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
import Link from "next/link";
|
|
import { usePathname } from "next/navigation";
|
|
import { Menu, X } from "lucide-react";
|
|
|
|
import { cn } from "@/lib/utils";
|
|
import { navItems } from "./nav-config";
|
|
|
|
export function MobileNav() {
|
|
const [isOpen, setIsOpen] = useState(false);
|
|
const pathname = usePathname();
|
|
|
|
useEffect(() => {
|
|
setIsOpen(false);
|
|
}, [pathname]);
|
|
|
|
useEffect(() => {
|
|
if (!isOpen) {
|
|
document.body.style.overflow = "";
|
|
return;
|
|
}
|
|
|
|
document.body.style.overflow = "hidden";
|
|
|
|
function handleKeyDown(e: KeyboardEvent) {
|
|
if (e.key === "Escape") setIsOpen(false);
|
|
}
|
|
|
|
document.addEventListener("keydown", handleKeyDown);
|
|
return () => {
|
|
document.removeEventListener("keydown", handleKeyDown);
|
|
};
|
|
}, [isOpen]);
|
|
|
|
return (
|
|
<>
|
|
<button
|
|
aria-label="메뉴 열기"
|
|
className="flex size-9 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground md:hidden"
|
|
onClick={() => setIsOpen(true)}
|
|
type="button"
|
|
>
|
|
<Menu aria-hidden="true" className="size-5" />
|
|
</button>
|
|
|
|
{/* 배경 오버레이 — 탭하면 닫힘 */}
|
|
<div
|
|
aria-hidden="true"
|
|
className={cn(
|
|
"fixed inset-0 z-20 bg-black/50 transition-opacity duration-200 md:hidden",
|
|
isOpen ? "opacity-100" : "pointer-events-none opacity-0"
|
|
)}
|
|
onClick={() => setIsOpen(false)}
|
|
/>
|
|
|
|
{/* 슬라이드인 패널 */}
|
|
<aside
|
|
aria-hidden={!isOpen}
|
|
className={cn(
|
|
"fixed inset-y-0 left-0 z-30 w-64 border-r bg-background transition-transform duration-200 md:hidden",
|
|
isOpen ? "translate-x-0" : "-translate-x-full"
|
|
)}
|
|
>
|
|
<div className="flex h-16 items-center justify-between border-b px-5">
|
|
<Link className="text-lg font-semibold" href="/">
|
|
chocoadmin
|
|
</Link>
|
|
<button
|
|
aria-label="메뉴 닫기"
|
|
className="flex size-8 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
|
|
onClick={() => setIsOpen(false)}
|
|
type="button"
|
|
>
|
|
<X aria-hidden="true" className="size-4" />
|
|
</button>
|
|
</div>
|
|
<nav className="space-y-1 p-3">
|
|
{navItems.map((item) => {
|
|
const Icon = item.icon;
|
|
return (
|
|
<Link
|
|
className="flex items-center gap-3 rounded-md px-3 py-2 text-sm font-medium text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
|
|
href={item.href}
|
|
key={item.href}
|
|
>
|
|
<Icon aria-hidden="true" className="size-4" />
|
|
{item.label}
|
|
</Link>
|
|
);
|
|
})}
|
|
</nav>
|
|
</aside>
|
|
</>
|
|
);
|
|
}
|