Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fleet UI: Open a modal moves updates active element to modal elements #25020

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 59 additions & 2 deletions frontend/components/Modal/Modal.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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") {
Expand Down Expand Up @@ -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>
Expand Down
Loading