add focus traps to overlays

This commit is contained in:
mrjvs 2023-11-22 17:50:24 +01:00
parent 2def74cb32
commit ce6b6ef88b
3 changed files with 59 additions and 42 deletions

View File

@ -16,6 +16,7 @@
"core-js": "^3.29.1",
"dompurify": "^3.0.1",
"flag-icons": "^6.11.1",
"focus-trap-react": "^10.2.3",
"fscreen": "^1.2.0",
"fuse.js": "^6.4.6",
"hls.js": "^1.0.7",

27
pnpm-lock.yaml generated
View File

@ -47,6 +47,9 @@ dependencies:
flag-icons:
specifier: ^6.11.1
version: 6.11.1
focus-trap-react:
specifier: ^10.2.3
version: 10.2.3(prop-types@15.8.1)(react-dom@17.0.2)(react@17.0.2)
fscreen:
specifier: ^1.2.0
version: 1.2.0
@ -3735,6 +3738,26 @@ packages:
resolution: {integrity: sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==}
dev: true
/focus-trap-react@10.2.3(prop-types@15.8.1)(react-dom@17.0.2)(react@17.0.2):
resolution: {integrity: sha512-YXBpFu/hIeSu6NnmV2xlXzOYxuWkoOtar9jzgp3lOmjWLWY59C/b8DtDHEAV4SPU07Nd/t+nS/SBNGkhUBFmEw==}
peerDependencies:
prop-types: ^15.8.1
react: '>=16.3.0'
react-dom: '>=16.3.0'
dependencies:
focus-trap: 7.5.4
prop-types: 15.8.1
react: 17.0.2
react-dom: 17.0.2(react@17.0.2)
tabbable: 6.2.0
dev: false
/focus-trap@7.5.4:
resolution: {integrity: sha512-N7kHdlgsO/v+iD/dMoJKtsSqs5Dz/dXZVebRgJw23LDk+jMi/974zyiOYDziY2JPp8xivq9BmUGwIJMiuSBi7w==}
dependencies:
tabbable: 6.2.0
dev: false
/for-each@0.3.3:
resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==}
dependencies:
@ -5807,6 +5830,10 @@ packages:
resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
dev: true
/tabbable@6.2.0:
resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==}
dev: false
/tailwind-scrollbar@2.1.0(tailwindcss@3.3.3):
resolution: {integrity: sha512-zpvY5mDs0130YzYjZKBiDaw32rygxk5RyJ4KmeHjGnwkvbjm/PszON1m4Bbt2DkMRIXlXsfNevykAESgURN4KA==}
engines: {node: '>=12.13.0'}

View File

@ -1,4 +1,5 @@
import classNames from "classnames";
import FocusTrap from "focus-trap-react";
import { ReactNode, useCallback, useEffect, useRef, useState } from "react";
import { createPortal } from "react-dom";
@ -37,31 +38,8 @@ export function OverlayPortal(props: {
}) {
const [portalElement, setPortalElement] = useState<Element | null>(null);
const ref = useRef<HTMLDivElement>(null);
const target = useRef<Element | null>(null);
const close = props.close;
useEffect(() => {
function listen(e: MouseEvent) {
target.current = e.target as Element;
}
document.addEventListener("mousedown", listen);
return () => {
document.removeEventListener("mousedown", listen);
};
});
const click = useCallback(
(e: React.MouseEvent) => {
const startedTarget = target.current;
target.current = null;
if (e.currentTarget !== e.target) return;
if (!startedTarget) return;
if (!startedTarget.isEqualNode(e.currentTarget as Element)) return;
close?.();
},
[close]
);
useEffect(() => {
const element = ref.current?.closest(".popout-location");
setPortalElement(element ?? document.body);
@ -72,24 +50,30 @@ export function OverlayPortal(props: {
{portalElement
? createPortal(
<Transition show={props.show} animation="none">
<div className="popout-wrapper fixed overflow-hidden pointer-events-auto inset-0 z-[999] select-none">
<Transition animation="fade" isChild>
<div
onClick={click}
className={classNames({
"absolute inset-0": true,
"bg-black opacity-90": props.darken,
})}
/>
</Transition>
<Transition
animation="slide-up"
className="absolute inset-0 pointer-events-none"
isChild
>
{props.children}
</Transition>
</div>
<FocusTrap
focusTrapOptions={{
onDeactivate: close,
}}
>
<div className="popout-wrapper absolute overflow-hidden pointer-events-auto inset-0 z-[999] select-none">
<Transition animation="fade" isChild>
<div
onClick={close}
className={classNames({
"absolute inset-0": true,
"bg-black opacity-90": props.darken,
})}
/>
</Transition>
<Transition
animation="slide-up"
className="absolute inset-0 pointer-events-none"
isChild
>
{props.children}
</Transition>
</div>
</FocusTrap>
</Transition>,
portalElement
)
@ -100,13 +84,18 @@ export function OverlayPortal(props: {
export function Overlay(props: OverlayProps) {
const router = useInternalOverlayRouter(props.id);
const realClose = router.close;
// listen for anchor updates
useRouterAnchorUpdate(props.id);
const close = useCallback(() => {
realClose();
}, [realClose]);
return (
<OverlayPortal
close={router.close}
close={close}
show={router.isOverlayActive()}
darken={props.darken}
>