refactor(layout): share popup menu dismiss handling

The language and theme menus carried two identical outside-click/Escape effects; fold them into one useMenuDismiss hook.
This commit is contained in:
LTbinglingfeng
2026-06-13 02:28:40 +08:00
Unverified
parent bbd69ffa81
commit a984ce6ab7
+38 -51
View File
@@ -1,5 +1,6 @@
import {
ReactNode,
RefObject,
SVGProps,
useCallback,
useEffect,
@@ -87,6 +88,39 @@ interface SidebarNavGroup {
const flattenNavItems = (items: SidebarNavItem[]): SidebarNavLinkItem[] =>
items.flatMap((item) => item.kind === 'drawer' ? item.children : [item]);
/** 点击菜单外或按下 Escape 时关闭弹出菜单 */
function useMenuDismiss(
open: boolean,
menuRef: RefObject<HTMLDivElement | null>,
onClose: () => void
) {
useEffect(() => {
if (!open) {
return;
}
const handlePointerDown = (event: MouseEvent) => {
if (!menuRef.current?.contains(event.target as Node)) {
onClose();
}
};
const handleEscape = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
onClose();
}
};
document.addEventListener('mousedown', handlePointerDown);
document.addEventListener('keydown', handleEscape);
return () => {
document.removeEventListener('mousedown', handlePointerDown);
document.removeEventListener('keydown', handleEscape);
};
}, [open, menuRef, onClose]);
}
function PluginSidebarIcon({ src }: { src: string }) {
const [failed, setFailed] = useState(false);
const showImage = Boolean(src) && !failed;
@@ -355,57 +389,10 @@ export function MainLayout() {
};
}, []);
useEffect(() => {
if (!languageMenuOpen) {
return;
}
const handlePointerDown = (event: MouseEvent) => {
if (!languageMenuRef.current?.contains(event.target as Node)) {
setLanguageMenuOpen(false);
}
};
const handleEscape = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
setLanguageMenuOpen(false);
}
};
document.addEventListener('mousedown', handlePointerDown);
document.addEventListener('keydown', handleEscape);
return () => {
document.removeEventListener('mousedown', handlePointerDown);
document.removeEventListener('keydown', handleEscape);
};
}, [languageMenuOpen]);
useEffect(() => {
if (!themeMenuOpen) {
return;
}
const handlePointerDown = (event: MouseEvent) => {
if (!themeMenuRef.current?.contains(event.target as Node)) {
setThemeMenuOpen(false);
}
};
const handleEscape = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
setThemeMenuOpen(false);
}
};
document.addEventListener('mousedown', handlePointerDown);
document.addEventListener('keydown', handleEscape);
return () => {
document.removeEventListener('mousedown', handlePointerDown);
document.removeEventListener('keydown', handleEscape);
};
}, [themeMenuOpen]);
const closeLanguageMenu = useCallback(() => setLanguageMenuOpen(false), []);
const closeThemeMenu = useCallback(() => setThemeMenuOpen(false), []);
useMenuDismiss(languageMenuOpen, languageMenuRef, closeLanguageMenu);
useMenuDismiss(themeMenuOpen, themeMenuRef, closeThemeMenu);
const toggleLanguageMenu = useCallback(() => {
setLanguageMenuOpen((prev) => !prev);