diff --git a/next-app/apps/web/src/components/Chrome/Header.tsx b/next-app/apps/web/src/components/Chrome/Header.tsx index 18958e2..db4ded9 100644 --- a/next-app/apps/web/src/components/Chrome/Header.tsx +++ b/next-app/apps/web/src/components/Chrome/Header.tsx @@ -163,66 +163,86 @@ function IconLink({ href, Icon, emoji, label, badge }: { href: string; Icon?: Re } /** Group submenu items: items with children render as section headers with their kids inline below. */ -function MegaPanel({ items }: { items: MenuItem[] }) { - // Two grouping strategies — pick whichever gnuboard's data hints at: - // (a) child-grouped: items with `children` become titled section columns - // (b) separator-grouped: a leaf with href='#' or label like "─ X ─" / "── X ──" - // starts a new section with that label as the column title (used by the - // gnuboard 슬생 data, which flattens 포인트게임 sub-menu) - const sections: { title?: string; href?: string; links: MenuItem[] }[] = []; - let cur: { title?: string; href?: string; links: MenuItem[] } = { links: [] }; - function flush() { if (cur.links.length > 0 || cur.title) sections.push(cur); cur = { links: [] }; } - +// Convert flat menu (with separator-leaves like "─ 스포츠 ─") into a +// hierarchical structure: separators become sub-groups whose following +// leaves become their children. This matches the PHP gnuboard cascade UX. +function buildHierarchy(items: MenuItem[]): MenuItem[] { + const out: MenuItem[] = []; const isSeparator = (it: MenuItem) => { - const lab = (it.label || '').replace(/[─\-=ㅡ]/g, '').trim(); - return (it.href === '#' || !it.href) && lab.length <= 8 && (it.label || '').match(/[─\-=ㅡ]/); + const labelHasDash = /[─\-=ㅡ]/.test(it.label || ''); + return (it.href === '#' || !it.href) && labelHasDash; }; - + let curGroup: MenuItem | null = null; for (const it of items) { if (it.children && it.children.length > 0) { - flush(); - sections.push({ title: it.label, href: it.href, links: it.children }); + curGroup = null; + out.push(it); } else if (isSeparator(it)) { - flush(); const label = (it.label || '').replace(/[─\-=ㅡ\s]/g, '').trim(); - cur = { title: label || it.label, links: [] }; + curGroup = { label: label || it.label, href: '#', children: [] }; + out.push(curGroup); + } else if (curGroup) { + curGroup.children!.push(it); } else { - cur.links.push(it); + out.push(it); } } - flush(); - - // Layout: pick column count by total sections; cap 4 - const colCount = sections.length <= 1 ? 1 : sections.length === 2 ? 2 : sections.length === 3 ? 3 : Math.min(4, sections.length); - const colClass = colCount === 1 ? 'grid-cols-1' : colCount === 2 ? 'grid-cols-2' : colCount === 3 ? 'grid-cols-3' : 'grid-cols-4'; + return out; +} +function MegaPanel({ items }: { items: MenuItem[] }) { + const tree = buildHierarchy(items); + const [openSub, setOpenSub] = useState(null); return ( -
- {sections.map((s, si) => ( -
- {s.title && ( -
- {s.href && s.href !== '#' ? {s.title} : s.title} -
- )} -
    - {s.links.map((c, ci) => ( -
  • - { e.currentTarget.style.color = '#ffffff'; }} - onMouseOut={(e) => { e.currentTarget.style.color = '#1f2937'; }} +
      + {tree.map((it, i) => { + const hasChildren = !!(it.children && it.children.length > 0); + return ( +
    • hasChildren && setOpenSub(i)} + onMouseLeave={() => setOpenSub((v) => (v === i ? null : v))} + > + { e.currentTarget.style.color = '#ffffff'; }} + onMouseOut={(e) => { e.currentTarget.style.color = '#1f2937'; }} + > + {it.label} + {hasChildren && } + + + {hasChildren && openSub === i && ( + - {c.label} - -
    • - ))} -
    -
- ))} -
+ + + )} + + + ); + })} + ); } @@ -253,8 +273,8 @@ function MegaNav({ menus, isDark: _isDark }: { menus: MenuItem[]; isDark: boolea {open === i && hasChildren && (