mirror of
https://github.com/chinchang/web-maker.git
synced 2025-07-30 18:20:15 +02:00
126 lines
2.9 KiB
JavaScript
126 lines
2.9 KiB
JavaScript
import { h } from 'preact';
|
|
import { createPortal, useEffect, useRef } from 'preact/compat';
|
|
|
|
const Portal = ({ children, into }) => {
|
|
const container = document.querySelector(into);
|
|
return createPortal(children, container);
|
|
};
|
|
|
|
const Modal = ({
|
|
show,
|
|
extraClasses,
|
|
small,
|
|
hideCloseButton,
|
|
closeHandler,
|
|
noOverlay,
|
|
children
|
|
}) => {
|
|
const focusGrabberRef = useRef();
|
|
const overlayRef = useRef();
|
|
|
|
const onKeyDownHandler = e => {
|
|
if (e.keyCode === 27) {
|
|
closeHandler();
|
|
}
|
|
};
|
|
const onOverlayClick = e => {
|
|
if (e.target === overlayRef.current) {
|
|
closeHandler();
|
|
}
|
|
};
|
|
useEffect(() => {
|
|
window.addEventListener('keydown', onKeyDownHandler);
|
|
return () => {
|
|
window.removeEventListener('keydown', onKeyDownHandler.bind(this));
|
|
if (focusGrabberRef.current) {
|
|
focusGrabberRef.current.remove();
|
|
focusGrabberRef.current = null;
|
|
}
|
|
};
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (!noOverlay) {
|
|
document.body.classList[show ? 'add' : 'remove']('overlay-visible');
|
|
}
|
|
if (show) {
|
|
// HACK: refs will evaluate on next tick due to portals
|
|
setTimeout(() => {
|
|
const closeButton = overlayRef.current.querySelector(
|
|
'.js-modal__close-btn'
|
|
);
|
|
if (closeButton) {
|
|
closeButton.focus();
|
|
}
|
|
}, 0);
|
|
|
|
/* We insert a dummy hidden input which will take focus as soon as focus
|
|
* escapes the modal, instead of focus going outside modal because modal
|
|
* is last focusable element.
|
|
*/
|
|
focusGrabberRef.current = document.createElement('input');
|
|
focusGrabberRef.current.setAttribute(
|
|
'style',
|
|
'height:0;opacity:0;overflow:hidden;width:0;'
|
|
);
|
|
setTimeout(() => {
|
|
document.body.appendChild(focusGrabberRef.current);
|
|
}, 10);
|
|
} else {
|
|
if (focusGrabberRef.current) {
|
|
focusGrabberRef.current.remove();
|
|
focusGrabberRef.current = null;
|
|
}
|
|
}
|
|
}, [show]);
|
|
|
|
if (!show) return null;
|
|
|
|
return (
|
|
<Portal into={`#portal`}>
|
|
<>
|
|
{/* <div class="modal-overlay" /> */}
|
|
<div
|
|
role="dialog"
|
|
class={`${extraClasses || ''} modal is-modal-visible ${
|
|
small ? 'modal--small' : ''
|
|
}
|
|
${noOverlay ? 'modal--no-overlay' : ''}
|
|
`}
|
|
ref={overlayRef}
|
|
onClick={onOverlayClick}
|
|
>
|
|
<div class="modal__content">
|
|
{hideCloseButton ? null : (
|
|
<button
|
|
type="button"
|
|
onClick={closeHandler}
|
|
aria-label="Close modal"
|
|
data-testid="closeModalButton"
|
|
title="Close"
|
|
class="js-modal__close-btn dialog__close-btn modal__close-btn"
|
|
>
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
strokeWidth="3.5"
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
>
|
|
<line x1="18" y1="6" x2="6" y2="18"></line>
|
|
<line x1="6" y1="6" x2="18" y2="18"></line>
|
|
</svg>
|
|
</button>
|
|
)}
|
|
{children}
|
|
</div>
|
|
</div>
|
|
</>
|
|
</Portal>
|
|
);
|
|
};
|
|
|
|
export default Modal;
|