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

@@ -14,13 +14,53 @@ import { cn } from "@/lib/utils";
export default function GlassHeader({ lang: propLang }: GlassHeaderProps) {
const [lang, setLang] = useState<Lang>(propLang || defaultLang);
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(() => {
setCurrentPath(window.location.pathname);
const htmlLang = document.documentElement.lang as Lang;
if (htmlLang && (!propLang || htmlLang !== lang)) {
setLang(htmlLang);
}
const updatePath = () => setCurrentPath(normalizePath(window.location.pathname));
updatePath();
const syncLang = () => {
const htmlLang = document.documentElement.lang as Lang;
if (htmlLang && (!propLang || htmlLang !== lang)) {
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]);
const t = useTranslations(lang);
@@ -48,10 +88,13 @@ export default function GlassHeader({ lang: propLang }: GlassHeaderProps) {
];
const isActive = (path: string) => {
if (path === '/' || path === '/zh' || path === '/zh/') {
return currentPath === path;
const normalizedPath = normalizePath(path);
if (normalizedPath === '/' || normalizedPath === '/zh') {
return currentPath === normalizedPath;
}
return currentPath.startsWith(path);
return currentPath === normalizedPath || currentPath.startsWith(`${normalizedPath}/`);
};
return (