1
0
mirror of https://github.com/chinchang/web-maker.git synced 2025-08-02 11:30:22 +02:00

turn modal into fn component

This commit is contained in:
Kushagra Gour
2021-04-01 14:27:59 +05:30
parent 9643ae6ff0
commit a274ece4c5

View File

@@ -1,95 +1,108 @@
import { h, Component } from 'preact'; import { h } from 'preact';
import Portal from './Portal'; import { createPortal, useEffect, useRef } from 'preact/compat';
export default class Modal extends Component { const Portal = ({ children, into }) => {
componentDidMount() { const container = document.querySelector(into);
this.container = document.createElement('div'); return createPortal(children, container);
this.container.id = `container-${~~(Math.random() * 1000)}`; };
document.body.append(this.container);
window.addEventListener('keydown', this.onKeyDownHandler.bind(this)); const Modal = ({
} show,
componentWillUnmount() { extraClasses,
this.container.remove(); small,
window.removeEventListener('keydown', this.onKeyDownHandler.bind(this)); hideCloseButton,
if (this.focusGrabber) { closeHandler,
this.focusGrabber.remove(); noOverlay,
this.focusGrabber = null; children
} }) => {
} const focusGrabberRef = useRef();
onKeyDownHandler(e) { const overlayRef = useRef();
const onKeyDownHandler = e => {
if (e.keyCode === 27) { if (e.keyCode === 27) {
this.props.closeHandler(); closeHandler();
} }
} };
onOverlayClick(e) { const onOverlayClick = e => {
if (e.target === this.overlayEl) { if (e.target === overlayRef.current) {
this.props.closeHandler(); closeHandler();
} }
} };
componentDidUpdate(prevProps) { useEffect(() => {
if (this.props.show !== prevProps.show) { window.addEventListener('keydown', onKeyDownHandler);
if (!this.props.noOverlay) { return () => {
document.body.classList[this.props.show ? 'add' : 'remove']( window.removeEventListener('keydown', this.onKeyDownHandler.bind(this));
'overlay-visible' if (focusGrabberRef.current) {
); focusGrabberRef.current.remove();
focusGrabberRef.current = null;
} }
if (this.props.show) { };
// HACK: refs will evaluate on next tick due to portals }, []);
setTimeout(() => {
const closeButton = this.overlayEl.querySelector(
'.js-modal__close-btn'
);
if (closeButton) {
closeButton.focus();
}
}, 0);
/* We insert a dummy hidden input which will take focus as soon as focus useEffect(() => {
escapes the modal, instead of focus going outside modal because modal if (!noOverlay) {
is last focusable element. */ document.body.classList[show ? 'add' : 'remove']('overlay-visible');
this.focusGrabber = document.createElement('input'); }
this.focusGrabber.setAttribute( if (show) {
'style', // HACK: refs will evaluate on next tick due to portals
'height:0;opacity:0;overflow:hidden;width:0;' setTimeout(() => {
const closeButton = this.overlayEl.querySelector(
'.js-modal__close-btn'
); );
setTimeout(() => { if (closeButton) {
document.body.appendChild(this.focusGrabber); closeButton.focus();
}, 10); }
} else { }, 0);
this.focusGrabber.remove();
this.focusGrabber = null; /* 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]);
render() {
if (!this.props.show) return null;
return ( if (!show) return null;
<Portal into={`#${this.container.id}`}>
<div return (
role="dialog" <Portal into={`body`}>
class={`${this.props.extraClasses || ''} modal is-modal-visible ${ <div
this.props.small ? 'modal--small' : '' role="dialog"
}`} class={`${extraClasses || ''} modal is-modal-visible ${
ref={el => (this.overlayEl = el)} small ? 'modal--small' : ''
onClick={this.onOverlayClick.bind(this)} }`}
> ref={overlayRef}
<div class="modal__content"> onClick={onOverlayClick}
{this.props.hideCloseButton ? null : ( >
<button <div class="modal__content">
type="button" {hideCloseButton ? null : (
onClick={this.props.closeHandler} <button
aria-label="Close modal" type="button"
title="Close" onClick={closeHandler}
class="js-modal__close-btn modal__close-btn" aria-label="Close modal"
> title="Close"
Close class="js-modal__close-btn modal__close-btn"
</button> >
)} Close
{this.props.children} </button>
</div> )}
{children}
</div> </div>
</Portal> </div>
); </Portal>
} );
} };
export default Modal;