diff --git a/components/Navbar/Navbar.tsx b/components/Navbar/Navbar.tsx index 1486341..22a75aa 100644 --- a/components/Navbar/Navbar.tsx +++ b/components/Navbar/Navbar.tsx @@ -11,6 +11,7 @@ import { useSupportsHover } from "@/hooks/useSupportsHover"; import { useToggleBodyOverflow } from "@/hooks/useToggleBodyOverflow"; import styles from "./Navbar.module.css"; import { NavbarSubMenu } from "./NavbarSubMenu"; +import { useIsDesktop } from "@/hooks/useIsDesktop"; type BasicLink = { label: string; @@ -21,10 +22,13 @@ export default function Navbar() { const pathname = usePathname(); const navRef = useRef(null); const supportsHover = useSupportsHover(); + const isDesktop = useIsDesktop(1024); const [instantSwitch, setInstantSwitch] = useState(false); const raf1Ref = useRef(null); const raf2Ref = useRef(null); + const hamburgerRef = useRef(null); + const navLinksRef = useRef(null); const { menuOpen, @@ -92,6 +96,66 @@ export default function Navbar() { ); + useEffect(() => { + if (isDesktop || !menuOpen) return; + + const navEl = navRef.current; + if (!navEl) return; + + const getFocusableElements = () => + Array.from( + navEl.querySelectorAll( + 'a[href], button:not([disabled]), [tabindex]:not([tabindex="-1"])', + ), + ).filter((el) => { + if (el.hasAttribute("inert")) return false; + if (el.closest("[inert]")) return false; + return true; + }); + + const firstMenuFocusable = navLinksRef.current?.querySelector( + 'a[href], button:not([disabled]), [tabindex]:not([tabindex="-1"])', + ); + + firstMenuFocusable?.focus(); + + const onKeyDown = (e: KeyboardEvent) => { + if (e.key === "Escape") { + e.preventDefault(); + toggleMenu(); + requestAnimationFrame(() => { + hamburgerRef.current?.focus(); + }); + return; + } + + if (e.key !== "Tab") return; + + const currentFocusable = getFocusableElements(); + const currentFirst = currentFocusable[0]; + const currentLast = currentFocusable[currentFocusable.length - 1]; + + if (!currentFirst || !currentLast) return; + + if (e.shiftKey) { + if (document.activeElement === currentFirst) { + e.preventDefault(); + currentLast.focus(); + } + } else { + if (document.activeElement === currentLast) { + e.preventDefault(); + currentFirst.focus(); + } + } + }; + + document.addEventListener("keydown", onKeyDown); + return () => { + document.removeEventListener("keydown", onKeyDown); + }; + }, [isDesktop, menuOpen, toggleMenu]); + return (