diff --git a/frontend/components/Modal/Modal.tsx b/frontend/components/Modal/Modal.tsx index 8049b474419b..14cd424db16a 100644 --- a/frontend/components/Modal/Modal.tsx +++ b/frontend/components/Modal/Modal.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from "react"; +import React, { useEffect, useRef, useLayoutEffect } from "react"; import classnames from "classnames"; import Button from "components/buttons/Button/Button"; import Icon from "components/Icon/Icon"; @@ -51,6 +51,63 @@ const Modal = ({ disableClosingModal = false, className, }: IModalProps): JSX.Element => { + const contentRef = useRef<HTMLDivElement>(null); + const previousActiveElement = useRef<HTMLElement | null>(null); + const isClosingRef = useRef(false); + + // This returns focus to the previous active element before opening the modal + useLayoutEffect(() => { + previousActiveElement.current = document.activeElement as HTMLElement; + }, []); + + useEffect(() => { + const observer = new MutationObserver(() => { + if ( + !isClosingRef.current && + !document.body.contains(contentRef.current) + ) { + isClosingRef.current = true; + if (previousActiveElement.current) { + previousActiveElement.current.focus(); + } + } + }); + + observer.observe(document.body, { childList: true, subtree: true }); + + return () => { + observer.disconnect(); + if (previousActiveElement.current) { + previousActiveElement.current.focus(); + } + }; + }, []); + + /** Allows keyboard accessibility to modals -- Because of loading, + * we cannot have a global fix to access focusable elements *within* + * children, but we can access the close button on the Modal */ + useEffect(() => { + previousActiveElement.current = document.activeElement as HTMLElement; + + // This just grabs the x button, left it robust incase we use it to grab + // a different focusable element in the future + if (contentRef.current) { + const focusableElements = contentRef.current.querySelectorAll( + 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' + ); + + if (focusableElements.length > 0) { + (focusableElements[0] as HTMLElement).focus(); + } + } + + return () => { + if (previousActiveElement.current) { + previousActiveElement.current.focus(); + } + }; + }, []); + useEffect(() => { const closeWithEscapeKey = (e: KeyboardEvent) => { if (e.key === "Escape") { @@ -107,7 +164,7 @@ const Modal = ({ }); return ( - <div className={backgroundClasses}> + <div ref={contentRef} className={backgroundClasses}> <div className={modalContainerClasses}> <div className={`${baseClass}__header`}> <span>{title}</span>