From 6f22cbb4cbd4b6569c8caa771e3b0c9ce957db77 Mon Sep 17 00:00:00 2001 From: RachelElysia Date: Thu, 26 Dec 2024 13:02:51 -0500 Subject: [PATCH 1/2] Fleet UI: Open a modal moves updates active element to modal elements --- frontend/components/Modal/Modal.tsx | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/frontend/components/Modal/Modal.tsx b/frontend/components/Modal/Modal.tsx index 8049b474419b..2197bffae89f 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 } from "react"; import classnames from "classnames"; import Button from "components/buttons/Button/Button"; import Icon from "components/Icon/Icon"; @@ -51,6 +51,23 @@ const Modal = ({ disableClosingModal = false, className, }: IModalProps): JSX.Element => { + const contentRef = useRef(null); + + /** 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 within Modal.tsx */ + useEffect(() => { + 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(); + } + } + }, [isHidden]); + useEffect(() => { const closeWithEscapeKey = (e: KeyboardEvent) => { if (e.key === "Escape") { @@ -107,7 +124,7 @@ const Modal = ({ }); return ( -
+
{title} From e6d173d68c5049a8be7193d8cc999d55bbae55f4 Mon Sep 17 00:00:00 2001 From: RachelElysia Date: Thu, 26 Dec 2024 14:31:43 -0500 Subject: [PATCH 2/2] Allow returning back to previous focused element when closing a modal --- frontend/components/Modal/Modal.tsx | 48 ++++++++++++++++++++++++++--- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/frontend/components/Modal/Modal.tsx b/frontend/components/Modal/Modal.tsx index 2197bffae89f..14cd424db16a 100644 --- a/frontend/components/Modal/Modal.tsx +++ b/frontend/components/Modal/Modal.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef } 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"; @@ -52,11 +52,45 @@ const Modal = ({ className, }: IModalProps): JSX.Element => { const contentRef = useRef(null); + const previousActiveElement = useRef(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 within Modal.tsx */ + * 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"])' @@ -66,7 +100,13 @@ const Modal = ({ (focusableElements[0] as HTMLElement).focus(); } } - }, [isHidden]); + + return () => { + if (previousActiveElement.current) { + previousActiveElement.current.focus(); + } + }; + }, []); useEffect(() => { const closeWithEscapeKey = (e: KeyboardEvent) => {