1
0
Fork 0
mirror of https://github.com/sussy-code/smov.git synced 2024-12-20 14:37:43 +01:00

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", "core-js": "^3.29.1",
"dompurify": "^3.0.1", "dompurify": "^3.0.1",
"flag-icons": "^6.11.1", "flag-icons": "^6.11.1",
"focus-trap-react": "^10.2.3",
"fscreen": "^1.2.0", "fscreen": "^1.2.0",
"fuse.js": "^6.4.6", "fuse.js": "^6.4.6",
"hls.js": "^1.0.7", "hls.js": "^1.0.7",

View file

@ -47,6 +47,9 @@ dependencies:
flag-icons: flag-icons:
specifier: ^6.11.1 specifier: ^6.11.1
version: 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: fscreen:
specifier: ^1.2.0 specifier: ^1.2.0
version: 1.2.0 version: 1.2.0
@ -3735,6 +3738,26 @@ packages:
resolution: {integrity: sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==} resolution: {integrity: sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==}
dev: true 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: /for-each@0.3.3:
resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==}
dependencies: dependencies:
@ -5807,6 +5830,10 @@ packages:
resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
dev: true dev: true
/tabbable@6.2.0:
resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==}
dev: false
/tailwind-scrollbar@2.1.0(tailwindcss@3.3.3): /tailwind-scrollbar@2.1.0(tailwindcss@3.3.3):
resolution: {integrity: sha512-zpvY5mDs0130YzYjZKBiDaw32rygxk5RyJ4KmeHjGnwkvbjm/PszON1m4Bbt2DkMRIXlXsfNevykAESgURN4KA==} resolution: {integrity: sha512-zpvY5mDs0130YzYjZKBiDaw32rygxk5RyJ4KmeHjGnwkvbjm/PszON1m4Bbt2DkMRIXlXsfNevykAESgURN4KA==}
engines: {node: '>=12.13.0'} engines: {node: '>=12.13.0'}

View file

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