/** * AppMenu — 상단 Overflow Menu 드롭다운 (수정사항-032) * TopAppBar 우측 ... 아이콘 클릭 시 TopAppBar 아래로 펼쳐지는 드롭다운 * - 폭: 화면의 50% 이하 (max-width: 240px) * - 긴 타일(pill) 형태 * - 상단 타이틀바/하단메뉴는 그대로 유지 * - 바깥 클릭 또는 메뉴 선택 시 닫힘 */ import { useEffect, useRef } from 'react'; import { useNavigate } from 'react-router-dom'; import styled, { keyframes } from 'styled-components'; import MatIcon from './MatIcon'; interface MenuItem { label: string; path?: string; dividerBefore?: boolean; iconOnly?: boolean; // 아이콘 없는 메뉴 (Policies 섹션) } interface MenuGroup { title?: string; items: MenuItem[]; } const fadeIn = keyframes` from { opacity: 0; transform: translateY(-8px); } to { opacity: 1; transform: translateY(0); } `; // 화면 전체 클릭 가드 (드롭다운 바깥 클릭 시 닫기) const Backdrop = styled.div` position: fixed; inset: 0; z-index: 150; `; // 드롭다운 패널 — TopAppBar(56px) 바로 아래, 우측 정렬, 폭 50% 이하 const Panel = styled.div` position: fixed; top: 56px; right: 12px; width: calc(100vw - 24px); max-width: 200px; /* 화면폭 50% 이하 (480px 기준) */ max-height: calc(100vh - 56px - 80px); overflow-y: auto; background: var(--md3-surface); border: 1px solid var(--md3-outline-variant); border-radius: 16px; box-shadow: 0 4px 16px rgba(15, 23, 42, 0.12); z-index: 160; animation: ${fadeIn} 0.18s ease-out; padding: 6px 0; `; const GroupTitle = styled.h3` font-family: 'Inter', 'Pretendard', system-ui, sans-serif; font-size: 12px; font-weight: 600; color: var(--md3-on-surface-variant); margin: 0; padding: 10px 14px 6px; letter-spacing: 0.02em; `; const MenuList = styled.ul` list-style: none; margin: 0; padding: 0; `; const MenuRow = styled.li<{ $clickable?: boolean }>` display: flex; align-items: center; gap: 10px; margin: 2px 6px; padding: 12px 14px; border-radius: 999px; /* pill 형태 */ cursor: ${({ $clickable }) => ($clickable ? 'pointer' : 'default')}; transition: background-color 0.15s; background: transparent; &:hover { background: ${({ $clickable }) => $clickable ? 'var(--md3-surface-variant)' : 'transparent'}; } &:active { background: var(--md3-outline-variant); } `; const Divider = styled.div` height: 1px; background: var(--md3-outline-variant); margin: 6px 12px; `; const IconWrapper = styled.span` display: flex; align-items: center; justify-content: center; width: 18px; height: 18px; flex-shrink: 0; `; const MenuLabel = styled.span` font-family: 'Inter', 'Pretendard', system-ui, sans-serif; font-size: 14px; font-weight: 500; color: var(--md3-on-surface); flex: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; `; const menuGroups: MenuGroup[] = [ { items: [ { label: 'Home', path: '/home' }, { label: 'about pCNT', path: '/info/about' }, { label: '테스트 가이드', path: '/info/test-guide' }, { label: '설문', path: '/survey' }, { label: '인지검사', path: '/test' }, { label: '대시보드', path: '/dashboard' }, { label: '프로필', path: '/mypage' }, ], }, { title: 'Policies and Terms', items: [ { label: 'Privacy Policy', path: '/info/privacy-policy', dividerBefore: true, iconOnly: true }, { label: 'Terms of service', path: '/info/terms-of-service', iconOnly: true }, { label: 'Research', path: '/info/research', iconOnly: true }, { label: 'Support', path: '/info/support', iconOnly: true }, ], }, ]; // 아이콘 매핑 (하단 메뉴와 동일 아이콘 사용) const iconMap: Record = { Home: { icon: 'home', size: 14 }, 'about pCNT': { icon: 'info', size: 14 }, '테스트 가이드': { icon: 'menu_book', size: 14 }, 설문: { icon: 'description', size: 14 }, 인지검사: { icon: 'psychology', size: 14 }, 대시보드: { icon: 'dashboard', size: 14 }, 프로필: { icon: 'person', size: 14 }, // Privacy Policy ~ Support: 아이콘 없이 빈공간 'Privacy Policy': null, 'Terms of service': null, Research: null, Support: null, }; interface AppMenuProps { onClose: () => void; } export default function AppMenu({ onClose }: AppMenuProps) { const navigate = useNavigate(); const backdropRef = useRef(null); useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose(); }; document.addEventListener('keydown', handleKeyDown); return () => { document.removeEventListener('keydown', handleKeyDown); }; }, [onClose]); const handleNavigate = (path?: string) => { if (path) { navigate(path); } onClose(); }; const handleBackdropClick = (e: React.MouseEvent) => { // Backdrop 자체 클릭 시에만 닫기 (버블링 막기) if (e.target === backdropRef.current) { onClose(); } }; return ( e.stopPropagation()}> {menuGroups.map((group, gi) => (
{group.title && {group.title}} {group.items.map((item, ii) => (
{item.dividerBefore && } handleNavigate(item.path)} role="menuitem" tabIndex={0} onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') handleNavigate(item.path); }} > {item.iconOnly ? ( ) : ( iconMap[item.label] && ( ) )} {item.label}
))}
))}
); }