/** * TopMenuBar — 기능별 그룹 메뉴 + 하단 드롭다운 */ import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import type { MenuPage } from './TopMenuBar'; export type MenuItemDef = { page: MenuPage; label: string; icon: React.ReactNode }; type MenuGroupDef = { id: string; label: string; icon: React.ReactNode; pages: MenuPage[]; }; const IcChevron = () => ( ); interface Props { activePage: MenuPage; items: MenuItemDef[]; groups: MenuGroupDef[]; onPage: (page: MenuPage) => void; tradeNotifyUnread?: number; } export const TopMenuBarNavGroups = memo(function TopMenuBarNavGroups({ activePage, items, groups, onPage, tradeNotifyUnread = 0, }: Props) { const [openGroupId, setOpenGroupId] = useState(null); const navRef = useRef(null); const itemByPage = useMemo(() => { const map = new Map(); for (const item of items) map.set(item.page, item); return map; }, [items]); const visibleGroups = useMemo(() => groups .map(group => ({ ...group, items: group.pages .map(page => itemByPage.get(page)) .filter((item): item is MenuItemDef => Boolean(item)), })) .filter(group => group.items.length > 0), [groups, itemByPage]); const closeDropdown = useCallback(() => setOpenGroupId(null), []); useEffect(() => { if (!openGroupId) return; const onDoc = (e: MouseEvent) => { if (navRef.current && !navRef.current.contains(e.target as Node)) { closeDropdown(); } }; const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') closeDropdown(); }; document.addEventListener('mousedown', onDoc); document.addEventListener('keydown', onKey); return () => { document.removeEventListener('mousedown', onDoc); document.removeEventListener('keydown', onKey); }; }, [openGroupId, closeDropdown]); useEffect(() => { closeDropdown(); }, [activePage, closeDropdown]); const handleSelect = (page: MenuPage) => { if (page === 'chart') { void import('../chart/ChartWorkspaceRoot'); } onPage(page); closeDropdown(); }; return ( {visibleGroups.map((group, index) => { const isOpen = openGroupId === group.id; const isActive = group.items.some(item => item.page === activePage); const groupBadge = group.items.some(item => item.page === 'notifications') && tradeNotifyUnread > 0 ? tradeNotifyUnread : 0; return ( {index > 0 && } setOpenGroupId(prev => (prev === group.id ? null : group.id))} > {group.icon} {group.label} {groupBadge > 0 && ( {groupBadge > 99 ? '99+' : groupBadge} )} {isOpen && ( {group.items.map(item => ( handleSelect(item.page)} > {item.icon} {item.label} {item.page === 'notifications' && tradeNotifyUnread > 0 && ( {tradeNotifyUnread > 99 ? '99+' : tradeNotifyUnread} )} ))} )} ); })} ); }); export default TopMenuBarNavGroups;