96 lines
2.1 KiB
TypeScript
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,
|
|
};
|
|
}
|