diff --git a/lib/components/Modal/index.js b/lib/components/Modal/index.js index 9dab2f23..898d4de5 100644 --- a/lib/components/Modal/index.js +++ b/lib/components/Modal/index.js @@ -1,20 +1,13 @@ -import React, { - useCallback, - useEffect, - useMemo, - useRef, - useState -} from "react"; +import React, { useCallback, useEffect, useMemo, useState } from "react"; import ReactDOM from "react-dom"; import useOnclickOutside from "react-cool-onclickoutside"; import PropTypes from "prop-types"; import styled, { keyframes, ThemeProvider } from "styled-components"; +import FocusTrap from "focus-trap-react"; import { css } from "@styled-system/css"; import { themeGet } from "@styled-system/theme-get"; -import { commonKeys } from "../../hooks/keypress"; - import Icon from "../Icon"; import Button from "../Button"; import Flex from "../Flex"; @@ -151,21 +144,6 @@ const ScrollableContent = styled.div` const isHidden = (el) => window.getComputedStyle(el).display === "none"; -const makeRootNotFocusable = () => { - const root = document.getElementById("root"); - - root?.setAttribute("aria-hidden", true); - root?.setAttribute("inert", true); -}; - -// Make root focusable again -const makeRootFocusable = () => { - const root = document.getElementById("root"); - - root?.removeAttribute("aria-hidden"); - root?.removeAttribute("inert"); -}; - const Modal = ({ children, width, @@ -202,27 +180,6 @@ const Modal = ({ } }, [restProps.ariaLabel, headerContent]); - // Initial focus point for keyboard navigation - const modalContainerRef = useRef(); - - // If bottom becomes focused then re-focus the close button - - // Ref to close button - const button = useRef(); - - const setFocus = useCallback(() => { - const autoFocusable = modalContainerRef.current.querySelector( - "*[autofocus='true']" - ); - const firstInput = modalContainerRef.current.querySelectorAll("input")[0]; - - if (autoFocusable) { - autoFocusable.focus(); - } else { - firstInput?.focus(); - } - }, [modalContainerRef]); - const focusLastActiveElement = useCallback(() => { if (!lastActiveElement) return; @@ -242,93 +199,90 @@ const Modal = ({ // On becoming visible focus the top useEffect(() => { - if (visible) { - if (!lastActiveElement) { - // Keep track of last clicked element to refocus to after dialog closes - setLastActiveElement(document.activeElement); - } - - // Prevents tabbing of elements underneath modal overlay - makeRootNotFocusable(); - - // See function - setFocus(); - } else if (!visible) { - makeRootFocusable(); + if (visible && !lastActiveElement) { + // Keep track of last clicked element to refocus to after dialog closes + setLastActiveElement(document.activeElement); + } else { setLastActiveElement(null); + // Focus the last active element before modal open when modal is closed focusLastActiveElement(); } - }, [visible, setFocus, focusLastActiveElement, lastActiveElement]); + }, [visible, focusLastActiveElement, lastActiveElement]); const component = ( { - e.stopPropagation(); - - if ([commonKeys.ESCAPE, commonKeys.ESC].includes(e.key)) { - onClose(); - } - }} id={overlayID} {...restProps} > -
- - {headerContent ? ( - - - {headerContent} - - - - - - ) : ( - +
+
- - - )} - - {children} - - {footerContent && {footerContent}} - -
+ + {headerContent ? ( + + + {headerContent} + + + + + + ) : ( + + + + )} + + {children} + + {footerContent && ( + {footerContent} + )} + +
+
+ + )}
); diff --git a/package-lock.json b/package-lock.json index e19381eb..4c123bf1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,17 +1,18 @@ { "name": "orcs-design-system", - "version": "3.1.30", + "version": "3.1.31", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "orcs-design-system", - "version": "3.1.30", + "version": "3.1.31", "dependencies": { "@styled-system/css": "^5.1.5", "@styled-system/prop-types": "^5.1.5", "@styled-system/should-forward-prop": "^5.1.5", "@styled-system/theme-get": "^5.1.2", + "focus-trap-react": "^10.2.3", "moment": "^2.29.4", "polished": "^3.7.1", "prop-types": "^15.6.2", @@ -18090,6 +18091,28 @@ "node": ">=0.4.0" } }, + "node_modules/focus-trap": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.5.4.tgz", + "integrity": "sha512-N7kHdlgsO/v+iD/dMoJKtsSqs5Dz/dXZVebRgJw23LDk+jMi/974zyiOYDziY2JPp8xivq9BmUGwIJMiuSBi7w==", + "dependencies": { + "tabbable": "^6.2.0" + } + }, + "node_modules/focus-trap-react": { + "version": "10.2.3", + "resolved": "https://registry.npmjs.org/focus-trap-react/-/focus-trap-react-10.2.3.tgz", + "integrity": "sha512-YXBpFu/hIeSu6NnmV2xlXzOYxuWkoOtar9jzgp3lOmjWLWY59C/b8DtDHEAV4SPU07Nd/t+nS/SBNGkhUBFmEw==", + "dependencies": { + "focus-trap": "^7.5.4", + "tabbable": "^6.2.0" + }, + "peerDependencies": { + "prop-types": "^15.8.1", + "react": ">=16.3.0", + "react-dom": ">=16.3.0" + } + }, "node_modules/follow-redirects": { "version": "1.15.5", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", @@ -37268,6 +37291,11 @@ "integrity": "sha512-AsS729u2RHUfEra9xJrE39peJcc2stq2+poBXX8bcM08Y6g9j/i/PUzwNQqkaJde7Ntg1TO7bSREbR5sdosQ+g==", "dev": true }, + "node_modules/tabbable": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", + "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==" + }, "node_modules/table": { "version": "6.8.1", "resolved": "https://registry.npmjs.org/table/-/table-6.8.1.tgz", diff --git a/package.json b/package.json index 2664551d..813d3e46 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "orcs-design-system", - "version": "3.1.30", + "version": "3.1.31", "engines": { "node": "18.17.1" }, @@ -47,6 +47,7 @@ "@styled-system/prop-types": "^5.1.5", "@styled-system/should-forward-prop": "^5.1.5", "@styled-system/theme-get": "^5.1.2", + "focus-trap-react": "^10.2.3", "moment": "^2.29.4", "polished": "^3.7.1", "prop-types": "^15.6.2",