183 lines
6.7 KiB
TypeScript
183 lines
6.7 KiB
TypeScript
// apps/dm/app/(landing)/reuse/_components/Lnb.tsx
|
|
"use client";
|
|
|
|
import Image from "next/image";
|
|
import Link from "next/link";
|
|
import { usePathname } from "next/navigation";
|
|
import { useMemo, useState } from "react";
|
|
|
|
export type LnbItem = {
|
|
label: string;
|
|
href?: string; // href 없는 부모(2뎁스 컨테이너)도 허용
|
|
children?: LnbItem[];
|
|
};
|
|
|
|
function isExactActive(pathname: string, href?: string) {
|
|
if (!href) return false;
|
|
return pathname === href;
|
|
}
|
|
|
|
export default function Lnb({ title, items }: { title: string; items: LnbItem[] }) {
|
|
const pathname = usePathname();
|
|
|
|
// ✅ 현재 경로가 속한 2depth 그룹을 기본 open
|
|
const defaultOpenLabel = useMemo(() => {
|
|
const found = items.find((it) => it.children?.some((c) => isExactActive(pathname, c.href)));
|
|
return found?.label ?? null;
|
|
}, [items, pathname]);
|
|
|
|
// ✅ 아코디언: 한 번에 하나만 열리도록 openLabel 1개만 관리
|
|
const [openLabel, setOpenLabel] = useState<string | null>(defaultOpenLabel);
|
|
|
|
return (
|
|
<aside
|
|
className={[
|
|
"fixed left-0 top-[83px]", // ✅ 화면 고정 + TopNav 아래로
|
|
"w-[260px] min-w-[260px] shrink-0",
|
|
"h-[calc(100vh-83px)]", // ✅ 남은 높이만큼
|
|
"overflow-y-auto", // ✅ LNB 내부 스크롤
|
|
"bg-white z-50", // ✅ 겹침 방지
|
|
].join(" ")}
|
|
>
|
|
{/* 타이틀 */}
|
|
<div
|
|
className="h-[120px] flex items-center justify-center
|
|
text-white text-[40px] font-bold
|
|
bg-gradient-to-r from-cyan-500 to-green-500"
|
|
>
|
|
{title}
|
|
</div>
|
|
|
|
{/* 메뉴 */}
|
|
<ul>
|
|
{items.map((item) => {
|
|
const hasChildren = !!item.children?.length;
|
|
|
|
// 1뎁스 active
|
|
const active1 = isExactActive(pathname, item.href);
|
|
|
|
// 2뎁스 active(자식 중 exact)
|
|
const active2 = item.children?.some((c) => isExactActive(pathname, c.href)) ?? false;
|
|
|
|
const active = active1 || active2;
|
|
|
|
// ✅ children 있는 경우: 아코디언 헤더 + 패널
|
|
if (hasChildren && !item.href) {
|
|
const isOpen = openLabel === item.label;
|
|
|
|
return (
|
|
<li key={item.label}>
|
|
{/* 아코디언 헤더 */}
|
|
<button
|
|
type="button"
|
|
onClick={() => setOpenLabel((prev) => (prev === item.label ? null : item.label))}
|
|
className={[
|
|
"w-full px-5 py-6",
|
|
"flex items-center justify-between",
|
|
"border-b border-slate-300",
|
|
"text-center text-base font-semibold",
|
|
active ? "bg-slate-100 text-slate-900" : "bg-slate-100 text-slate-500",
|
|
].join(" ")}
|
|
aria-expanded={isOpen}
|
|
>
|
|
<span className="w-full text-center">{item.label}</span>
|
|
|
|
{/* ✅ 우측 화살표(이미지처럼) */}
|
|
<span
|
|
aria-hidden
|
|
className={[
|
|
"ml-3 shrink-0 transition-transform duration-200",
|
|
isOpen ? "rotate-180" : "rotate-0",
|
|
].join(" ")}
|
|
>
|
|
<Image
|
|
src="/images/landing/drop_arrow.svg"
|
|
alt="드롭다운 화살표 아이콘"
|
|
width={28}
|
|
height={28}
|
|
className="w-7 h-7"
|
|
/>
|
|
</span>
|
|
</button>
|
|
|
|
{/* 패널: height auto 애니메이션(그리드 트릭) */}
|
|
<div
|
|
className={[
|
|
"grid transition-[grid-template-rows] duration-200 ease-out",
|
|
isOpen ? "grid-rows-[1fr]" : "grid-rows-[0fr]",
|
|
].join(" ")}
|
|
>
|
|
<div className="overflow-hidden bg-white">
|
|
<ul>
|
|
{item.children!.map((child) => {
|
|
const childActive = isExactActive(pathname, child.href);
|
|
|
|
return (
|
|
<li key={child.href ?? child.label}>
|
|
{child.href ? (
|
|
<Link
|
|
href={child.href}
|
|
className={[
|
|
"block px-5 py-4 pl-[60px] text-[18px] font-medium",
|
|
"border-b border-slate-200 transition-colors",
|
|
childActive
|
|
? "bg-white text-sky-500 cursor-default"
|
|
: "bg-slate-50 text-slate-500 hover:bg-white hover:text-sky-500",
|
|
].join(" ")}
|
|
>
|
|
{/* 이미지처럼 불릿 필요하면 */}
|
|
<span className="mr-2 text-current">
|
|
•
|
|
</span>
|
|
{child.label}
|
|
</Link>
|
|
) : (
|
|
<div className="block px-5 py-4 text-[18px] font-medium border-b border-slate-200 bg-slate-50 text-slate-500">
|
|
{child.label}
|
|
</div>
|
|
)}
|
|
</li>
|
|
);
|
|
})}
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</li>
|
|
);
|
|
}
|
|
|
|
// ✅ 일반 1뎁스 메뉴(기존 유지)
|
|
return (
|
|
<li key={item.href ?? item.label}>
|
|
{item.href ? (
|
|
<Link
|
|
href={item.href}
|
|
className={[
|
|
"block px-5 py-4 text-center text-base font-semibold",
|
|
"border-b border-slate-300 transition-colors",
|
|
active
|
|
? "bg-white text-sky-500 cursor-default"
|
|
: "bg-[#F0F0F0] text-[#767676] hover:bg-white hover:text-sky-500",
|
|
].join(" ")}
|
|
>
|
|
{item.label}
|
|
</Link>
|
|
) : (
|
|
<div
|
|
className={[
|
|
"block px-5 py-4 text-center text-base font-semibold",
|
|
"border-b border-slate-300",
|
|
"bg-slate-100 text-slate-500",
|
|
].join(" ")}
|
|
>
|
|
{item.label}
|
|
</div>
|
|
)}
|
|
</li>
|
|
);
|
|
})}
|
|
</ul>
|
|
</aside>
|
|
);
|
|
}
|