2021-02-04 19:15:23 +01:00
|
|
|
import React, { Component } from 'react';
|
|
|
|
import PropTypes from 'prop-types';
|
2019-12-06 16:40:07 +01:00
|
|
|
|
2021-02-04 19:15:23 +01:00
|
|
|
let index = 0;
|
|
|
|
let extraSheet;
|
2019-12-06 16:40:07 +01:00
|
|
|
|
2020-02-15 21:34:12 +01:00
|
|
|
const insertRule = (css) => {
|
2019-12-06 16:40:07 +01:00
|
|
|
if (!extraSheet) {
|
|
|
|
// First time, create an extra stylesheet for adding rules
|
2021-02-04 19:15:23 +01:00
|
|
|
extraSheet = document.createElement('style');
|
|
|
|
document.getElementsByTagName('head')[0].appendChild(extraSheet);
|
2019-12-06 16:40:07 +01:00
|
|
|
// Keep reference to actual StyleSheet object (`styleSheet` for IE < 9)
|
2021-02-04 19:15:23 +01:00
|
|
|
extraSheet = extraSheet.sheet || extraSheet.styleSheet;
|
2019-12-06 16:40:07 +01:00
|
|
|
}
|
|
|
|
|
2021-02-04 19:15:23 +01:00
|
|
|
extraSheet.insertRule(css, (extraSheet.cssRules || extraSheet.rules).length);
|
2019-12-06 16:40:07 +01:00
|
|
|
|
2021-02-04 19:15:23 +01:00
|
|
|
return extraSheet;
|
|
|
|
};
|
2019-12-06 16:40:07 +01:00
|
|
|
|
2020-02-15 21:34:12 +01:00
|
|
|
const insertKeyframesRule = (keyframes) => {
|
2019-12-06 16:40:07 +01:00
|
|
|
// random name
|
2020-08-13 01:06:44 +02:00
|
|
|
// eslint-disable-next-line no-plusplus
|
2021-02-04 19:15:23 +01:00
|
|
|
const name = `anim_${++index}${Number(new Date())}`;
|
|
|
|
let css = `@keyframes ${name} {`;
|
2019-12-06 16:40:07 +01:00
|
|
|
|
2020-07-21 23:10:45 +02:00
|
|
|
Object.keys(keyframes).forEach((key) => {
|
2021-02-04 19:15:23 +01:00
|
|
|
css += `${key} {`;
|
2019-12-06 16:40:07 +01:00
|
|
|
|
2020-07-21 23:10:45 +02:00
|
|
|
Object.keys(keyframes[key]).forEach((property) => {
|
2021-02-04 19:15:23 +01:00
|
|
|
const part = `:${keyframes[key][property]};`;
|
|
|
|
css += property + part;
|
|
|
|
});
|
2019-12-06 16:40:07 +01:00
|
|
|
|
2021-02-04 19:15:23 +01:00
|
|
|
css += '}';
|
|
|
|
});
|
2019-12-06 16:40:07 +01:00
|
|
|
|
2021-02-04 19:15:23 +01:00
|
|
|
css += '}';
|
2019-12-06 16:40:07 +01:00
|
|
|
|
2021-02-04 19:15:23 +01:00
|
|
|
insertRule(css);
|
2019-12-06 16:40:07 +01:00
|
|
|
|
2021-02-04 19:15:23 +01:00
|
|
|
return name;
|
|
|
|
};
|
2019-12-06 16:40:07 +01:00
|
|
|
|
|
|
|
const animation = {
|
|
|
|
show: {
|
|
|
|
animationDuration: '0.3s',
|
|
|
|
animationTimingFunction: 'ease-out',
|
|
|
|
},
|
|
|
|
hide: {
|
|
|
|
animationDuration: '0.3s',
|
|
|
|
animationTimingFunction: 'ease-out',
|
|
|
|
},
|
|
|
|
showContentAnimation: insertKeyframesRule({
|
|
|
|
'0%': {
|
|
|
|
opacity: 0,
|
|
|
|
},
|
|
|
|
'100%': {
|
|
|
|
opacity: 1,
|
|
|
|
},
|
|
|
|
}),
|
|
|
|
hideContentAnimation: insertKeyframesRule({
|
|
|
|
'0%': {
|
|
|
|
opacity: 1,
|
|
|
|
},
|
|
|
|
'100%': {
|
|
|
|
opacity: 0,
|
|
|
|
},
|
|
|
|
}),
|
|
|
|
showBackdropAnimation: insertKeyframesRule({
|
|
|
|
'0%': {
|
|
|
|
opacity: 0,
|
|
|
|
},
|
|
|
|
'100%': {
|
|
|
|
opacity: 0.9,
|
|
|
|
},
|
|
|
|
}),
|
|
|
|
hideBackdropAnimation: insertKeyframesRule({
|
|
|
|
'0%': {
|
|
|
|
opacity: 0.9,
|
|
|
|
},
|
|
|
|
'100%': {
|
|
|
|
opacity: 0,
|
|
|
|
},
|
|
|
|
}),
|
2021-02-04 19:15:23 +01:00
|
|
|
};
|
2019-12-06 16:40:07 +01:00
|
|
|
|
2021-02-04 19:15:23 +01:00
|
|
|
const endEvents = ['transitionend', 'animationend'];
|
2019-12-06 16:40:07 +01:00
|
|
|
|
2020-11-03 00:41:28 +01:00
|
|
|
function addEventListener(node, eventName, eventListener) {
|
2021-02-04 19:15:23 +01:00
|
|
|
node.addEventListener(eventName, eventListener, false);
|
2019-12-06 16:40:07 +01:00
|
|
|
}
|
|
|
|
|
2020-11-03 00:41:28 +01:00
|
|
|
function removeEventListener(node, eventName, eventListener) {
|
2021-02-04 19:15:23 +01:00
|
|
|
node.removeEventListener(eventName, eventListener, false);
|
2019-12-06 16:40:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
const removeEndEventListener = (node, eventListener) => {
|
|
|
|
if (endEvents.length === 0) {
|
2021-02-04 19:15:23 +01:00
|
|
|
return;
|
2019-12-06 16:40:07 +01:00
|
|
|
}
|
|
|
|
endEvents.forEach(function (endEvent) {
|
2021-02-04 19:15:23 +01:00
|
|
|
removeEventListener(node, endEvent, eventListener);
|
|
|
|
});
|
|
|
|
};
|
2019-12-06 16:40:07 +01:00
|
|
|
|
|
|
|
const addEndEventListener = (node, eventListener) => {
|
|
|
|
if (endEvents.length === 0) {
|
|
|
|
// If CSS transitions are not supported, trigger an "end animation"
|
|
|
|
// event immediately.
|
2021-02-04 19:15:23 +01:00
|
|
|
window.setTimeout(eventListener, 0);
|
|
|
|
return;
|
2019-12-06 16:40:07 +01:00
|
|
|
}
|
|
|
|
endEvents.forEach(function (endEvent) {
|
2021-02-04 19:15:23 +01:00
|
|
|
addEventListener(node, endEvent, eventListener);
|
|
|
|
});
|
|
|
|
};
|
2019-12-06 16:40:07 +01:00
|
|
|
|
|
|
|
class FadeModal extends Component {
|
2021-02-04 19:15:23 +01:00
|
|
|
content = null;
|
2019-12-06 16:40:07 +01:00
|
|
|
|
|
|
|
static propTypes = {
|
|
|
|
backdrop: PropTypes.bool,
|
|
|
|
backdropStyle: PropTypes.object,
|
|
|
|
closeOnClick: PropTypes.bool,
|
|
|
|
contentStyle: PropTypes.object,
|
|
|
|
keyboard: PropTypes.bool,
|
|
|
|
modalStyle: PropTypes.object,
|
|
|
|
onShow: PropTypes.func,
|
|
|
|
onHide: PropTypes.func,
|
2020-02-27 15:31:59 +01:00
|
|
|
children: PropTypes.node,
|
2021-02-04 19:15:23 +01:00
|
|
|
};
|
2019-12-06 16:40:07 +01:00
|
|
|
|
|
|
|
static defaultProps = {
|
2020-08-14 13:47:02 +02:00
|
|
|
onShow: () => undefined,
|
|
|
|
onHide: () => undefined,
|
2019-12-06 16:40:07 +01:00
|
|
|
keyboard: true,
|
|
|
|
backdrop: true,
|
|
|
|
closeOnClick: true,
|
|
|
|
modalStyle: {},
|
|
|
|
backdropStyle: {},
|
|
|
|
contentStyle: {},
|
|
|
|
children: [],
|
2021-02-04 19:15:23 +01:00
|
|
|
};
|
2019-12-06 16:40:07 +01:00
|
|
|
|
|
|
|
state = {
|
|
|
|
willHide: true,
|
|
|
|
hidden: true,
|
2021-02-04 19:15:23 +01:00
|
|
|
};
|
2019-12-06 16:40:07 +01:00
|
|
|
|
|
|
|
addTransitionListener = (node, handle) => {
|
|
|
|
if (node) {
|
|
|
|
const endListener = function (e) {
|
|
|
|
if (e && e.target !== node) {
|
2021-02-04 19:15:23 +01:00
|
|
|
return;
|
2019-12-06 16:40:07 +01:00
|
|
|
}
|
2021-02-04 19:15:23 +01:00
|
|
|
removeEndEventListener(node, endListener);
|
|
|
|
handle();
|
|
|
|
};
|
|
|
|
addEndEventListener(node, endListener);
|
2019-12-06 16:40:07 +01:00
|
|
|
}
|
2021-02-04 19:15:23 +01:00
|
|
|
};
|
2019-12-06 16:40:07 +01:00
|
|
|
|
|
|
|
handleBackdropClick = () => {
|
|
|
|
if (this.props.closeOnClick) {
|
2021-02-04 19:15:23 +01:00
|
|
|
this.hide();
|
2019-12-06 16:40:07 +01:00
|
|
|
}
|
2021-02-04 19:15:23 +01:00
|
|
|
};
|
2019-12-06 16:40:07 +01:00
|
|
|
|
|
|
|
hasHidden = () => {
|
2021-02-04 19:15:23 +01:00
|
|
|
return this.state.hidden;
|
|
|
|
};
|
2019-12-06 16:40:07 +01:00
|
|
|
|
2020-11-03 00:41:28 +01:00
|
|
|
render() {
|
2019-12-06 16:40:07 +01:00
|
|
|
if (this.state.hidden) {
|
2021-02-04 19:15:23 +01:00
|
|
|
return null;
|
2019-12-06 16:40:07 +01:00
|
|
|
}
|
|
|
|
|
2021-02-04 19:15:23 +01:00
|
|
|
const { willHide } = this.state;
|
|
|
|
const { modalStyle } = this.props;
|
2020-08-19 18:27:05 +02:00
|
|
|
const backdropStyle = {
|
2020-11-03 00:41:28 +01:00
|
|
|
animationName: willHide
|
|
|
|
? animation.hideBackdropAnimation
|
|
|
|
: animation.showBackdropAnimation,
|
|
|
|
animationTimingFunction: (willHide ? animation.hide : animation.show)
|
|
|
|
.animationTimingFunction,
|
|
|
|
...this.props.backdropStyle,
|
2021-02-04 19:15:23 +01:00
|
|
|
};
|
2020-08-19 18:27:05 +02:00
|
|
|
const contentStyle = {
|
2020-11-03 00:41:28 +01:00
|
|
|
animationDuration: (willHide ? animation.hide : animation.show)
|
|
|
|
.animationDuration,
|
|
|
|
animationName: willHide
|
|
|
|
? animation.hideContentAnimation
|
|
|
|
: animation.showContentAnimation,
|
|
|
|
animationTimingFunction: (willHide ? animation.hide : animation.show)
|
|
|
|
.animationTimingFunction,
|
|
|
|
...this.props.contentStyle,
|
2021-02-04 19:15:23 +01:00
|
|
|
};
|
2019-12-06 16:40:07 +01:00
|
|
|
|
2020-11-03 00:41:28 +01:00
|
|
|
const backdrop = this.props.backdrop ? (
|
|
|
|
<div
|
|
|
|
className="modal__backdrop"
|
|
|
|
style={backdropStyle}
|
|
|
|
onClick={this.props.closeOnClick ? this.handleBackdropClick : null}
|
|
|
|
/>
|
2021-02-04 19:15:23 +01:00
|
|
|
) : undefined;
|
2019-12-06 16:40:07 +01:00
|
|
|
|
|
|
|
if (willHide) {
|
2021-02-04 19:15:23 +01:00
|
|
|
this.addTransitionListener(this.content, this.leave);
|
2019-12-06 16:40:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<span>
|
|
|
|
<div className="modal" style={modalStyle}>
|
|
|
|
<div
|
2020-08-12 17:49:45 +02:00
|
|
|
className="modal__content"
|
2020-02-15 21:34:12 +01:00
|
|
|
ref={(el) => (this.content = el)}
|
2019-12-06 16:40:07 +01:00
|
|
|
tabIndex="-1"
|
|
|
|
style={contentStyle}
|
|
|
|
>
|
|
|
|
{this.props.children}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
{backdrop}
|
|
|
|
</span>
|
2021-02-04 19:15:23 +01:00
|
|
|
);
|
2019-12-06 16:40:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
leave = () => {
|
|
|
|
this.setState({
|
|
|
|
hidden: true,
|
2021-02-04 19:15:23 +01:00
|
|
|
});
|
|
|
|
this.props.onHide(this.state.hideSource);
|
|
|
|
};
|
2019-12-06 16:40:07 +01:00
|
|
|
|
|
|
|
enter = () => {
|
2021-02-04 19:15:23 +01:00
|
|
|
this.props.onShow();
|
|
|
|
};
|
2019-12-06 16:40:07 +01:00
|
|
|
|
|
|
|
show = () => {
|
|
|
|
if (!this.state.hidden) {
|
2021-02-04 19:15:23 +01:00
|
|
|
return;
|
2019-12-06 16:40:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
this.setState({
|
|
|
|
willHide: false,
|
|
|
|
hidden: false,
|
2021-02-04 19:15:23 +01:00
|
|
|
});
|
2019-12-06 16:40:07 +01:00
|
|
|
|
2020-11-03 00:41:28 +01:00
|
|
|
setTimeout(
|
|
|
|
function () {
|
2021-02-04 19:15:23 +01:00
|
|
|
this.addTransitionListener(this.content, this.enter);
|
2020-11-03 00:41:28 +01:00
|
|
|
}.bind(this),
|
|
|
|
0,
|
2021-02-04 19:15:23 +01:00
|
|
|
);
|
|
|
|
};
|
2019-12-06 16:40:07 +01:00
|
|
|
|
|
|
|
hide = () => {
|
|
|
|
if (this.hasHidden()) {
|
2021-02-04 19:15:23 +01:00
|
|
|
return;
|
2019-12-06 16:40:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
this.setState({
|
|
|
|
willHide: true,
|
2021-02-04 19:15:23 +01:00
|
|
|
});
|
|
|
|
};
|
2019-12-06 16:40:07 +01:00
|
|
|
|
|
|
|
listenKeyboard = (event) => {
|
|
|
|
if (typeof this.props.keyboard === 'function') {
|
2021-02-04 19:15:23 +01:00
|
|
|
this.props.keyboard(event);
|
2019-12-06 16:40:07 +01:00
|
|
|
} else {
|
2021-02-04 19:15:23 +01:00
|
|
|
this.closeOnEsc(event);
|
2019-12-06 16:40:07 +01:00
|
|
|
}
|
2021-02-04 19:15:23 +01:00
|
|
|
};
|
2019-12-06 16:40:07 +01:00
|
|
|
|
|
|
|
closeOnEsc = (event) => {
|
2020-11-03 00:41:28 +01:00
|
|
|
if (
|
|
|
|
this.props.keyboard &&
|
|
|
|
(event.key === 'Escape' || event.keyCode === 27)
|
|
|
|
) {
|
2021-02-04 19:15:23 +01:00
|
|
|
this.hide();
|
2019-12-06 16:40:07 +01:00
|
|
|
}
|
2021-02-04 19:15:23 +01:00
|
|
|
};
|
2019-12-06 16:40:07 +01:00
|
|
|
|
|
|
|
UNSAFE_componentDidMount = () => {
|
2021-02-04 19:15:23 +01:00
|
|
|
window.addEventListener('keydown', this.listenKeyboard, true);
|
|
|
|
};
|
2019-12-06 16:40:07 +01:00
|
|
|
|
|
|
|
UNSAFE_componentWillUnmount = () => {
|
2021-02-04 19:15:23 +01:00
|
|
|
window.removeEventListener('keydown', this.listenKeyboard, true);
|
|
|
|
};
|
2019-12-06 16:40:07 +01:00
|
|
|
}
|
|
|
|
|
2021-02-04 19:15:23 +01:00
|
|
|
export default FadeModal;
|