"use client"; import { useCallback, useRef, useState } from "react"; import { ChevronDown, ChevronRight } from "lucide-react"; type UIMenu = { id: string; name: string; icon?: React.ReactNode; hasChildren?: boolean; children?: UIMenu[]; url?: string; badge?: number; }; type TopNavBarProps = { menus: UIMenu[]; isMenuActive: (m: UIMenu) => boolean; onSelect: (m: UIMenu) => void; }; /** * TopNav — 디자인시스템 `TopNav` 포팅 (shell-components.jsx). * invyone 메뉴 트리(최상위 = 섹션)에 맞게 단순화. * 섹션 hover → flyout (첫 번째 레벨), flyout 아이템에 자식이 있으면 hover 로 2단계 sub-flyout. */ export function TopNavBar({ menus, isMenuActive, onSelect }: TopNavBarProps) { const [openId, setOpenId] = useState(null); const closeTimer = useRef | null>(null); const cancelClose = useCallback(() => { if (closeTimer.current) { clearTimeout(closeTimer.current); closeTimer.current = null; } }, []); const scheduleClose = useCallback(() => { cancelClose(); // 섹션 헤더 → flyout 이동 중 마우스가 경계 근처에서 순간적으로 빠져도 안 닫히도록 여유있게. closeTimer.current = setTimeout(() => setOpenId(null), 260); }, [cancelClose]); const openNow = useCallback( (id: string) => { cancelClose(); setOpenId(id); }, [cancelClose], ); const handleSectionClick = (m: UIMenu) => { // leaf 섹션은 바로 선택. 자식이 있는 섹션은 첫 leaf 로 점프. if (!m.hasChildren) { onSelect(m); return; } const first = m.children?.[0]; if (!first) return; if (first.hasChildren && first.children?.[0]) onSelect(first.children[0]); else onSelect(first); }; return ( ); } function TnRow({ item, isMenuActive, onSelect, delay, }: { item: UIMenu; isMenuActive: (m: UIMenu) => boolean; onSelect: (m: UIMenu) => void; delay: number; }) { const [subOpen, setSubOpen] = useState(false); const hasChildren = !!item.hasChildren && !!item.children?.length; const isOn = isMenuActive(item); return (
hasChildren && setSubOpen(true)} onMouseLeave={() => setSubOpen(false)} onClick={(e) => { if ((e.target as HTMLElement).closest(".v5-tn-sub")) return; if (hasChildren && item.children?.[0]) onSelect(item.children[0]); else onSelect(item); }} > {item.icon && {item.icon}} {item.name} {typeof item.badge === "number" && item.badge > 0 && ( {item.badge} )} {hasChildren && } {hasChildren && subOpen && (
{item.children?.map((c, k) => (
{ e.stopPropagation(); onSelect(c); }} > {c.icon && {c.icon}} {c.name} {typeof c.badge === "number" && c.badge > 0 && ( {c.badge} )}
))}
)}
); }