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:
parent
2def74cb32
commit
ce6b6ef88b
3 changed files with 59 additions and 42 deletions
|
@ -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",
|
||||||
|
|
|
@ -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'}
|
||||||
|
|
|
@ -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,10 +50,15 @@ 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
|
||||||
|
focusTrapOptions={{
|
||||||
|
onDeactivate: close,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="popout-wrapper absolute overflow-hidden pointer-events-auto inset-0 z-[999] select-none">
|
||||||
<Transition animation="fade" isChild>
|
<Transition animation="fade" isChild>
|
||||||
<div
|
<div
|
||||||
onClick={click}
|
onClick={close}
|
||||||
className={classNames({
|
className={classNames({
|
||||||
"absolute inset-0": true,
|
"absolute inset-0": true,
|
||||||
"bg-black opacity-90": props.darken,
|
"bg-black opacity-90": props.darken,
|
||||||
|
@ -90,6 +73,7 @@ export function OverlayPortal(props: {
|
||||||
{props.children}
|
{props.children}
|
||||||
</Transition>
|
</Transition>
|
||||||
</div>
|
</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}
|
||||||
>
|
>
|
||||||
|
|
Loading…
Reference in a new issue