import { useEffect, useRef, useState } from 'react'; import { useLocation } from 'react-router-dom'; import { getLocalizedPath, getTranslation, type Locale } from '../i18n/utils'; import { Icon } from '../icons'; type SubnavItem = { title: string; desc: string; icon: string; href: string }; type Subnav = Record; type Translation = ReturnType; type NavKey = keyof Translation['nav']; // 一个 nav 项可以是纯链接(无下拉)或带下拉(subnav 存在)。 // 下拉项的 href 在数据里:以 `#` 开头表示同页锚点(hash 跳转),否则视为绝对路径。 function resolveSubnavHref(pageKey: NavKey, anchor: string, locale: Locale): string { return getLocalizedPath(`/${pageKey === 'home' ? '' : pageKey}`, locale) + (anchor.startsWith('#') ? anchor : `#${anchor}`); } export default function Header({ locale }: { locale: Locale }) { const t = getTranslation(locale); const brandName = t.brand || 'BaskBuddy'; const [menuOpen, setMenuOpen] = useState(false); const [hoveredNav, setHoveredNav] = useState(null); const closeTimer = useRef | null>(null); const location = useLocation(); // 滚动时顶部自动收起:向下滚动隐藏,向上滚动显示(threshold 80px 避免抖动)。 const [hidden, setHidden] = useState(false); useEffect(() => { let lastY = window.scrollY; const onScroll = () => { const y = window.scrollY; if (y > lastY && y > 80) { setHidden(true); } else if (y < lastY) { setHidden(false); } lastY = y; }; window.addEventListener('scroll', onScroll, { passive: true }); return () => window.removeEventListener('scroll', onScroll); }, []); // 路由变化时主动关闭下拉 + 移动端菜单。 useEffect(() => { setHoveredNav(null); setMenuOpen(false); }, [location.pathname]); // Esc 关闭下拉。 useEffect(() => { const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') { setHoveredNav(null); setMenuOpen(false); } }; window.addEventListener('keydown', onKey); return () => window.removeEventListener('keydown', onKey); }, []); const isActive = (key: string) => { const path = location.pathname.replace(/\/$/, '') || '/'; const target = getLocalizedPath(`/${key === 'home' ? '' : key}`, locale); const normalizedTarget = target.replace(/\/$/, '') || '/'; return path === normalizedTarget; }; // 悬停下拉的进出延迟:进入立即,移出 120ms 关闭(防误触,给鼠标留出走向下拉的窗口)。 const openDropdown = (key: NavKey) => { if (closeTimer.current) { clearTimeout(closeTimer.current); closeTimer.current = null; } setHoveredNav(key); }; const scheduleClose = () => { if (closeTimer.current) clearTimeout(closeTimer.current); closeTimer.current = setTimeout(() => setHoveredNav(null), 120); }; // 语言切换:保持当前页面,仅切换语言前缀。 const segments = location.pathname.split('/'); if (segments[1] === 'en' || segments[1] === 'hk') { segments.splice(1, 1); } const basePath = segments.join('/') || '/'; const langHref = (target: Locale) => getLocalizedPath(basePath, target); const locales: { code: Locale; label: string }[] = [ { code: 'zh', label: '简' }, { code: 'en', label: 'EN' }, { code: 'hk', label: '繁' }, ]; // py-1.5:触控目标提升至 ~28px,安全过 WCAG 2.5.8 AA(24px)下限;text-body 走 --fs-body 令牌(16px)。 const navBase = 'inline-flex items-center py-1.5 px-2 md:px-2.5 lg:px-3.5 text-body font-semibold transition-all duration-200'; const navIdle = 'text-[var(--brand-black)] hover:text-[var(--brand-accent-deep)] hover:underline underline-offset-4'; const navActive = 'text-[var(--brand-accent-deep)] font-bold underline underline-offset-4'; const navHasDropdown = 'gap-1'; const mobileNavBase = 'inline-flex items-center text-body py-2.5 px-3 transition-all duration-200 font-semibold'; const mobileNavIdle = 'text-[var(--brand-black)] hover:text-[var(--brand-accent-deep)] hover:underline underline-offset-4'; const mobileNavActive = 'text-[var(--brand-accent-deep)] font-bold underline underline-offset-4'; // 读取 subnav 数据:home/investors/news 没有 subnav 字段,返回 null。 const getSubnav = (key: NavKey): Subnav | null => { const section = t[key] as { subnav?: Subnav } | undefined; return section?.subnav ?? null; }; return (
{/* Mobile Navigation */}
); }