From a08573f12098c1bac8ad1fc18e0265a8c2222a97 Mon Sep 17 00:00:00 2001 From: Pragadesh-45 <54320162+Pragadesh-45@users.noreply.github.com> Date: Fri, 13 Sep 2024 11:42:55 +0530 Subject: [PATCH] Feature/use focus trap (#3075) * addded tab order for modal elements * fixed Tab Order * feat: Add useFocusTrap hook for managing focus within modals * chore: improvements * chore: removed console log --------- Co-authored-by: Srikar Co-authored-by: srikary12 <121927567+srikary12@users.noreply.github.com> Co-authored-by: Anoop M D --- .../bruno-app/src/components/Modal/index.js | 15 +++++-- .../bruno-app/src/hooks/useFocusTrap/index.js | 43 +++++++++++++++++++ 2 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 packages/bruno-app/src/hooks/useFocusTrap/index.js diff --git a/packages/bruno-app/src/components/Modal/index.js b/packages/bruno-app/src/components/Modal/index.js index b83549aac..0ec1aca08 100644 --- a/packages/bruno-app/src/components/Modal/index.js +++ b/packages/bruno-app/src/components/Modal/index.js @@ -1,5 +1,6 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useState, useRef } from 'react'; import StyledWrapper from './StyledWrapper'; +import useFocusTrap from 'hooks/useFocusTrap'; const ModalHeader = ({ title, handleCancel, customHeader, hideClose }) => (
@@ -69,6 +70,7 @@ const Modal = ({ onClick, closeModalFadeTimeout = 500 }) => { + const modalRef = useRef(null); const [isClosing, setIsClosing] = useState(false); const escFunction = (event) => { const escKeyCode = 27; @@ -77,6 +79,8 @@ const Modal = ({ } }; + useFocusTrap(modalRef); + const closeModal = (args) => { setIsClosing(true); setTimeout(() => handleCancel(args), closeModalFadeTimeout); @@ -85,7 +89,6 @@ const Modal = ({ useEffect(() => { if (disableEscapeKey) return; document.addEventListener('keydown', escFunction, false); - return () => { document.removeEventListener('keydown', escFunction, false); }; @@ -100,7 +103,13 @@ const Modal = ({ } return ( onClick(e) : null}> -
+
{ + const firstFocusableElementRef = useRef(null); + const lastFocusableElementRef = useRef(null); + + useEffect(() => { + const modalElement = modalRef.current; + if (!modalElement) return; + + const focusableElements = modalElement.querySelectorAll( + 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' + ); + + if (focusableElements.length === 0) return; + + const firstElement = focusableElements[0]; + const lastElement = focusableElements[focusableElements.length - 1]; + + firstFocusableElementRef.current = firstElement; + lastFocusableElementRef.current = lastElement; + + const handleKeyDown = (event) => { + if (event.key === 'Tab') { + if (event.shiftKey && document.activeElement === firstElement) { + event.preventDefault(); + lastElement.focus(); + } else if (!event.shiftKey && document.activeElement === lastElement) { + event.preventDefault(); + firstElement.focus(); + } + } + }; + + modalElement.addEventListener('keydown', handleKeyDown); + + return () => { + modalElement.removeEventListener('keydown', handleKeyDown); + }; + }, [modalRef]); +}; + +export default useFocusTrap;