mirror of
https://github.com/chinchang/web-maker.git
synced 2025-04-04 19:02:27 +02:00
turn modal into fn component
This commit is contained in:
parent
9643ae6ff0
commit
a274ece4c5
@ -1,95 +1,108 @@
|
||||
import { h, Component } from 'preact';
|
||||
import Portal from './Portal';
|
||||
import { h } from 'preact';
|
||||
import { createPortal, useEffect, useRef } from 'preact/compat';
|
||||
|
||||
export default class Modal extends Component {
|
||||
componentDidMount() {
|
||||
this.container = document.createElement('div');
|
||||
this.container.id = `container-${~~(Math.random() * 1000)}`;
|
||||
document.body.append(this.container);
|
||||
window.addEventListener('keydown', this.onKeyDownHandler.bind(this));
|
||||
}
|
||||
componentWillUnmount() {
|
||||
this.container.remove();
|
||||
window.removeEventListener('keydown', this.onKeyDownHandler.bind(this));
|
||||
if (this.focusGrabber) {
|
||||
this.focusGrabber.remove();
|
||||
this.focusGrabber = null;
|
||||
}
|
||||
}
|
||||
onKeyDownHandler(e) {
|
||||
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) {
|
||||
this.props.closeHandler();
|
||||
closeHandler();
|
||||
}
|
||||
}
|
||||
onOverlayClick(e) {
|
||||
if (e.target === this.overlayEl) {
|
||||
this.props.closeHandler();
|
||||
};
|
||||
const onOverlayClick = e => {
|
||||
if (e.target === overlayRef.current) {
|
||||
closeHandler();
|
||||
}
|
||||
}
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.props.show !== prevProps.show) {
|
||||
if (!this.props.noOverlay) {
|
||||
document.body.classList[this.props.show ? 'add' : 'remove'](
|
||||
'overlay-visible'
|
||||
);
|
||||
};
|
||||
useEffect(() => {
|
||||
window.addEventListener('keydown', onKeyDownHandler);
|
||||
return () => {
|
||||
window.removeEventListener('keydown', this.onKeyDownHandler.bind(this));
|
||||
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
|
||||
escapes the modal, instead of focus going outside modal because modal
|
||||
is last focusable element. */
|
||||
this.focusGrabber = document.createElement('input');
|
||||
this.focusGrabber.setAttribute(
|
||||
'style',
|
||||
'height:0;opacity:0;overflow:hidden;width:0;'
|
||||
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 = this.overlayEl.querySelector(
|
||||
'.js-modal__close-btn'
|
||||
);
|
||||
setTimeout(() => {
|
||||
document.body.appendChild(this.focusGrabber);
|
||||
}, 10);
|
||||
} else {
|
||||
this.focusGrabber.remove();
|
||||
this.focusGrabber = null;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
render() {
|
||||
if (!this.props.show) return null;
|
||||
}, [show]);
|
||||
|
||||
return (
|
||||
<Portal into={`#${this.container.id}`}>
|
||||
<div
|
||||
role="dialog"
|
||||
class={`${this.props.extraClasses || ''} modal is-modal-visible ${
|
||||
this.props.small ? 'modal--small' : ''
|
||||
}`}
|
||||
ref={el => (this.overlayEl = el)}
|
||||
onClick={this.onOverlayClick.bind(this)}
|
||||
>
|
||||
<div class="modal__content">
|
||||
{this.props.hideCloseButton ? null : (
|
||||
<button
|
||||
type="button"
|
||||
onClick={this.props.closeHandler}
|
||||
aria-label="Close modal"
|
||||
title="Close"
|
||||
class="js-modal__close-btn modal__close-btn"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
)}
|
||||
{this.props.children}
|
||||
</div>
|
||||
if (!show) return null;
|
||||
|
||||
return (
|
||||
<Portal into={`body`}>
|
||||
<div
|
||||
role="dialog"
|
||||
class={`${extraClasses || ''} modal is-modal-visible ${
|
||||
small ? 'modal--small' : ''
|
||||
}`}
|
||||
ref={overlayRef}
|
||||
onClick={onOverlayClick}
|
||||
>
|
||||
<div class="modal__content">
|
||||
{hideCloseButton ? null : (
|
||||
<button
|
||||
type="button"
|
||||
onClick={closeHandler}
|
||||
aria-label="Close modal"
|
||||
title="Close"
|
||||
class="js-modal__close-btn modal__close-btn"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
)}
|
||||
{children}
|
||||
</div>
|
||||
</Portal>
|
||||
);
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</Portal>
|
||||
);
|
||||
};
|
||||
|
||||
export default Modal;
|
||||
|
Loading…
x
Reference in New Issue
Block a user