1
0
mirror of https://gitlab.nic.cz/turris/reforis/foris-js.git synced 2024-12-26 00:21:36 +01:00

Compare commits

...

2 Commits

Author SHA1 Message Date
Aleksandr Gumroian
29a7c491a4 Merge branch 'modal-focus-trap' into 'dev'
Refactor Modal component to use useFocusTrap hook

See merge request turris/reforis/foris-js!237
2024-09-16 18:00:48 +02:00
Aleksandr Gumroian
446ec1bbe5
Refactor Modal component to use useFocusTrap hook 2024-09-16 18:00:37 +02:00
2 changed files with 47 additions and 6 deletions

View File

@ -9,7 +9,7 @@ import React, { useRef, useEffect } from "react";
import PropTypes from "prop-types";
import { useClickOutside } from "../utils/hooks";
import { useClickOutside, useFocusTrap } from "../utils/hooks";
import Portal from "../utils/Portal";
import "./Modal.css";
@ -29,10 +29,11 @@ Modal.propTypes = {
};
export function Modal({ shown, setShown, scrollable, size, children }) {
const dialogRef = useRef();
const modalRef = useRef();
let modalSize = "modal-";
useClickOutside(dialogRef, () => setShown(false));
useClickOutside(modalRef, () => setShown(false));
useFocusTrap(modalRef);
useEffect(() => {
const handleEsc = (event) => {
@ -45,7 +46,7 @@ export function Modal({ shown, setShown, scrollable, size, children }) {
return () => {
window.removeEventListener("keydown", handleEsc);
};
}, [setShown]);
}, [shown]);
switch (size) {
case "sm":
@ -65,11 +66,13 @@ export function Modal({ shown, setShown, scrollable, size, children }) {
return (
<Portal containerId="modal-container">
<div
ref={modalRef}
className={`modal fade ${shown ? "show" : ""}`.trim()}
role="dialog"
aria-modal="true"
aria-labelledby="modal-title"
>
<div
ref={dialogRef}
className={`${modalSize.trim()} modal-dialog modal-dialog-centered ${
scrollable ? "modal-dialog-scrollable" : ""
}`.trim()}
@ -90,7 +93,7 @@ ModalHeader.propTypes = {
export function ModalHeader({ setShown, title }) {
return (
<div className="modal-header">
<h5 className="modal-title">{title}</h5>
<h1 className="modal-title fs-5">{title}</h1>
<button
type="button"
className="btn-close"

View File

@ -40,3 +40,41 @@ export function useClickOutside(element, callback) {
};
});
}
/* Trap focus inside modal. */
export function useFocusTrap(modalRef) {
useEffect(() => {
if (shown) {
const modal = modalRef.current;
const focusableElements = modal.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
const firstElement = focusableElements[0];
const lastElement = focusableElements[focusableElements.length - 1];
const handleTab = (event) => {
if (event.key === "Tab") {
if (event.shiftKey) {
if (document.activeElement === firstElement) {
lastElement.focus();
event.preventDefault();
}
} else {
if (document.activeElement === lastElement) {
firstElement.focus();
event.preventDefault();
}
}
}
};
modal.addEventListener("keydown", handleTab);
firstElement.focus();
return () => {
modal.removeEventListener("keydown", handleTab);
};
}
}, [shown]);
}