fix(nav): keep active menu state synced during client-side navigation

This commit is contained in:
zguiyang
2026-03-16 22:16:16 +08:00
parent bafd029b95
commit d873c3b063

View File

@@ -15,12 +15,52 @@ export default function GlassHeader({ lang: propLang }: GlassHeaderProps) {
const [lang, setLang] = useState<Lang>(propLang || defaultLang); const [lang, setLang] = useState<Lang>(propLang || defaultLang);
const [currentPath, setCurrentPath] = useState(""); const [currentPath, setCurrentPath] = useState("");
const normalizePath = (path: string) => {
const clean = path.split("#")[0].split("?")[0];
if (!clean) return "/";
if (clean === "/") return "/";
return clean.endsWith("/") ? clean.slice(0, -1) : clean;
};
useEffect(() => { useEffect(() => {
setCurrentPath(window.location.pathname); const updatePath = () => setCurrentPath(normalizePath(window.location.pathname));
updatePath();
const syncLang = () => {
const htmlLang = document.documentElement.lang as Lang; const htmlLang = document.documentElement.lang as Lang;
if (htmlLang && (!propLang || htmlLang !== lang)) { if (htmlLang && (!propLang || htmlLang !== lang)) {
setLang(htmlLang); setLang(htmlLang);
} }
};
syncLang();
const originalPushState = history.pushState.bind(history);
const originalReplaceState = history.replaceState.bind(history);
history.pushState = function (...args) {
originalPushState(...args);
window.dispatchEvent(new Event("locationchange"));
};
history.replaceState = function (...args) {
originalReplaceState(...args);
window.dispatchEvent(new Event("locationchange"));
};
window.addEventListener("popstate", updatePath);
window.addEventListener("locationchange", updatePath);
document.addEventListener("astro:after-swap", updatePath as EventListener);
document.addEventListener("astro:page-load", updatePath as EventListener);
document.addEventListener("astro:page-load", syncLang as EventListener);
return () => {
history.pushState = originalPushState;
history.replaceState = originalReplaceState;
window.removeEventListener("popstate", updatePath);
window.removeEventListener("locationchange", updatePath);
document.removeEventListener("astro:after-swap", updatePath as EventListener);
document.removeEventListener("astro:page-load", updatePath as EventListener);
document.removeEventListener("astro:page-load", syncLang as EventListener);
};
}, [propLang, lang]); }, [propLang, lang]);
const t = useTranslations(lang); const t = useTranslations(lang);
@@ -48,10 +88,13 @@ export default function GlassHeader({ lang: propLang }: GlassHeaderProps) {
]; ];
const isActive = (path: string) => { const isActive = (path: string) => {
if (path === '/' || path === '/zh' || path === '/zh/') { const normalizedPath = normalizePath(path);
return currentPath === path;
if (normalizedPath === '/' || normalizedPath === '/zh') {
return currentPath === normalizedPath;
} }
return currentPath.startsWith(path);
return currentPath === normalizedPath || currentPath.startsWith(`${normalizedPath}/`);
}; };
return ( return (