Files
basiclean-webapp/hooks/useNavToggleMenu.ts
T
2026-05-02 10:41:00 +02:00

96 lines
2.1 KiB
TypeScript

"use client";
import { useCallback, useEffect, useRef, useState } from "react";
type Options = {
supportsHover: boolean;
desktopBp?: number;
};
export function useNavToggleMenu({ desktopBp = 1024 }: Options) {
const [menuOpen, setMenuOpen] = useState(false);
const [openSubmenuId, setOpenSubmenuId] = useState<string | null>(null);
const [noTransition, setNoTransition] = useState(false);
const rafRef = useRef<number | null>(null);
const clearRaf = useCallback(() => {
if (rafRef.current != null) {
cancelAnimationFrame(rafRef.current);
rafRef.current = null;
}
}, []);
const closeMenu = useCallback(() => {
setMenuOpen(false);
setOpenSubmenuId(null);
}, []);
const closeInstantNoAnim = useCallback(() => {
clearRaf();
setNoTransition(true);
closeMenu();
rafRef.current = requestAnimationFrame(() => {
setNoTransition(false);
rafRef.current = null;
});
}, [clearRaf, closeMenu]);
const toggleMenu = useCallback(() => {
clearRaf();
setNoTransition(false);
setMenuOpen((prev) => {
const next = !prev;
if (!next) {
setOpenSubmenuId(null);
}
return next;
});
}, [clearRaf]);
const toggleSubmenu = useCallback((id: string) => {
setOpenSubmenuId((cur) => (cur === id ? null : id));
}, []);
useEffect(() => {
const mql = window.matchMedia(`(min-width: ${desktopBp}px)`);
const onChange = () => {
clearRaf();
setNoTransition(true);
closeMenu();
requestAnimationFrame(() => setNoTransition(false));
};
if (mql.addEventListener) {
mql.addEventListener("change", onChange);
} else {
mql.addListener(onChange);
}
return () => {
if (mql.removeEventListener) {
mql.removeEventListener("change", onChange);
} else {
mql.removeListener(onChange);
}
};
}, [desktopBp, clearRaf, closeMenu]);
useEffect(() => clearRaf, [clearRaf]);
return {
menuOpen,
openSubmenuId,
noTransition,
setMenuOpen,
setOpenSubmenuId,
toggleMenu,
toggleSubmenu,
closeMenu,
closeInstantNoAnim,
};
}