From 8d89d76fbee8930cf4970c185692fa7d70a47ed6 Mon Sep 17 00:00:00 2001 From: Mayank Mehta Date: Tue, 19 Nov 2024 22:38:17 +0530 Subject: [PATCH 1/2] Added focus trap to mobile menu --- src/components/Header/Header.tsx | 57 +++++++++++++++++++++++++++++--- 1 file changed, 52 insertions(+), 5 deletions(-) diff --git a/src/components/Header/Header.tsx b/src/components/Header/Header.tsx index 14d46bb..7dddc17 100644 --- a/src/components/Header/Header.tsx +++ b/src/components/Header/Header.tsx @@ -1,23 +1,66 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect, useRef } from 'react'; import Link from 'next/link'; import styles from './Header.module.scss'; export const Header = () => { const [isMenuOpen, setIsMenuOpen] = useState(false); + const menuRef = useRef(null); // Reference for the menu + const closeButtonRef = useRef(null); // Reference for the close button + const firstFocusableElementRef = useRef(null); // First focusable element in the menu const toggleMenu = () => { - setIsMenuOpen(!isMenuOpen); + setIsMenuOpen((prev) => !prev); }; + // Handle trapping focus + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if (isMenuOpen) { + const focusableElements = menuRef.current?.querySelectorAll('a, button') || []; + const firstElement = focusableElements[0]; + const lastElement = focusableElements[focusableElements.length - 1]; + + if (e.key === 'Tab') { + // Shift + Tab (backward) + if (e.shiftKey && document.activeElement === firstElement) { + e.preventDefault(); + lastElement?.focus(); // Loop focus to last element + } + // Tab (forward) + else if (!e.shiftKey && document.activeElement === lastElement) { + e.preventDefault(); + firstElement?.focus(); // Loop focus to first element + } + } else if (e.key === 'Escape') { + // Close menu on Escape key + setIsMenuOpen(false); + } + } + }; + + if (isMenuOpen) { + document.addEventListener('keydown', handleKeyDown); + // Focus on the close button when menu opens + closeButtonRef.current?.focus(); + } else { + document.removeEventListener('keydown', handleKeyDown); + } + + return () => document.removeEventListener('keydown', handleKeyDown); + }, [isMenuOpen]); return (
); -}; +};; From 34fe16438a7abd7ae65223b80efa0895151ba02f Mon Sep 17 00:00:00 2001 From: Mayank Mehta Date: Sun, 1 Dec 2024 02:51:02 +0530 Subject: [PATCH 2/2] fix focus trap issue & linting issue --- src/components/Header/Header.tsx | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/src/components/Header/Header.tsx b/src/components/Header/Header.tsx index 7dddc17..9f59d2c 100644 --- a/src/components/Header/Header.tsx +++ b/src/components/Header/Header.tsx @@ -5,9 +5,9 @@ import styles from './Header.module.scss'; export const Header = () => { const [isMenuOpen, setIsMenuOpen] = useState(false); - const menuRef = useRef(null); // Reference for the menu - const closeButtonRef = useRef(null); // Reference for the close button - const firstFocusableElementRef = useRef(null); // First focusable element in the menu + const menuRef = useRef(null); + const closeButtonRef = useRef(null); + const firstFocusableElementRef = useRef(null); const toggleMenu = () => { setIsMenuOpen((prev) => !prev); @@ -17,9 +17,13 @@ export const Header = () => { useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (isMenuOpen) { - const focusableElements = menuRef.current?.querySelectorAll('a, button') || []; + const focusableElements = + menuRef.current?.querySelectorAll( + 'a, button', + ) || []; const firstElement = focusableElements[0]; - const lastElement = focusableElements[focusableElements.length - 1]; + const lastElement = + focusableElements[focusableElements.length - 1]; if (e.key === 'Tab') { // Shift + Tab (backward) @@ -28,7 +32,10 @@ export const Header = () => { lastElement?.focus(); // Loop focus to last element } // Tab (forward) - else if (!e.shiftKey && document.activeElement === lastElement) { + else if ( + !e.shiftKey && + document.activeElement === lastElement + ) { e.preventDefault(); firstElement?.focus(); // Loop focus to first element } @@ -54,10 +61,10 @@ export const Header = () => {