You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
295 lines
6.3 KiB
295 lines
6.3 KiB
4 years ago
|
import React, { Component } from 'react';
|
||
|
import PropTypes from 'prop-types';
|
||
5 years ago
|
|
||
4 years ago
|
let index = 0;
|
||
|
let extraSheet;
|
||
5 years ago
|
|
||
5 years ago
|
const insertRule = (css) => {
|
||
5 years ago
|
if (!extraSheet) {
|
||
|
// First time, create an extra stylesheet for adding rules
|
||
4 years ago
|
extraSheet = document.createElement('style');
|
||
|
document.getElementsByTagName('head')[0].appendChild(extraSheet);
|
||
5 years ago
|
// Keep reference to actual StyleSheet object (`styleSheet` for IE < 9)
|
||
4 years ago
|
extraSheet = extraSheet.sheet || extraSheet.styleSheet;
|
||
5 years ago
|
}
|
||
|
|
||
4 years ago
|
extraSheet.insertRule(css, (extraSheet.cssRules || extraSheet.rules).length);
|
||
5 years ago
|
|
||
4 years ago
|
return extraSheet;
|
||
|
};
|
||
5 years ago
|
|
||
5 years ago
|
const insertKeyframesRule = (keyframes) => {
|
||
5 years ago
|
// random name
|
||
4 years ago
|
// eslint-disable-next-line no-plusplus
|
||
4 years ago
|
const name = `anim_${++index}${Number(new Date())}`;
|
||
|
let css = `@keyframes ${name} {`;
|
||
5 years ago
|
|
||
4 years ago
|
Object.keys(keyframes).forEach((key) => {
|
||
4 years ago
|
css += `${key} {`;
|
||
5 years ago
|
|
||
4 years ago
|
Object.keys(keyframes[key]).forEach((property) => {
|
||
4 years ago
|
const part = `:${keyframes[key][property]};`;
|
||
|
css += property + part;
|
||
|
});
|
||
5 years ago
|
|
||
4 years ago
|
css += '}';
|
||
|
});
|
||
5 years ago
|
|
||
4 years ago
|
css += '}';
|
||
5 years ago
|
|
||
4 years ago
|
insertRule(css);
|
||
5 years ago
|
|
||
4 years ago
|
return name;
|
||
|
};
|
||
5 years ago
|
|
||
|
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,
|
||
|
},
|
||
|
}),
|
||
4 years ago
|
};
|
||
5 years ago
|
|
||
4 years ago
|
const endEvents = ['transitionend', 'animationend'];
|
||
5 years ago
|
|
||
4 years ago
|
function addEventListener(node, eventName, eventListener) {
|
||
4 years ago
|
node.addEventListener(eventName, eventListener, false);
|
||
5 years ago
|
}
|
||
|
|
||
4 years ago
|
function removeEventListener(node, eventName, eventListener) {
|
||
4 years ago
|
node.removeEventListener(eventName, eventListener, false);
|
||
5 years ago
|
}
|
||
|
|
||
|
const removeEndEventListener = (node, eventListener) => {
|
||
|
if (endEvents.length === 0) {
|
||
4 years ago
|
return;
|
||
5 years ago
|
}
|
||
|
endEvents.forEach(function (endEvent) {
|
||
4 years ago
|
removeEventListener(node, endEvent, eventListener);
|
||
|
});
|
||
|
};
|
||
5 years ago
|
|
||
|
const addEndEventListener = (node, eventListener) => {
|
||
|
if (endEvents.length === 0) {
|
||
|
// If CSS transitions are not supported, trigger an "end animation"
|
||
|
// event immediately.
|
||
4 years ago
|
window.setTimeout(eventListener, 0);
|
||
|
return;
|
||
5 years ago
|
}
|
||
|
endEvents.forEach(function (endEvent) {
|
||
4 years ago
|
addEventListener(node, endEvent, eventListener);
|
||
|
});
|
||
|
};
|
||
5 years ago
|
|
||
|
class FadeModal extends Component {
|
||
4 years ago
|
content = null;
|
||
5 years ago
|
|
||
|
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,
|
||
5 years ago
|
children: PropTypes.node,
|
||
4 years ago
|
};
|
||
5 years ago
|
|
||
|
static defaultProps = {
|
||
4 years ago
|
onShow: () => undefined,
|
||
|
onHide: () => undefined,
|
||
5 years ago
|
keyboard: true,
|
||
|
backdrop: true,
|
||
|
closeOnClick: true,
|
||
|
modalStyle: {},
|
||
|
backdropStyle: {},
|
||
|
contentStyle: {},
|
||
|
children: [],
|
||
4 years ago
|
};
|
||
5 years ago
|
|
||
|
state = {
|
||
|
willHide: true,
|
||
|
hidden: true,
|
||
4 years ago
|
};
|
||
5 years ago
|
|
||
|
addTransitionListener = (node, handle) => {
|
||
|
if (node) {
|
||
|
const endListener = function (e) {
|
||
|
if (e && e.target !== node) {
|
||
4 years ago
|
return;
|
||
5 years ago
|
}
|
||
4 years ago
|
removeEndEventListener(node, endListener);
|
||
|
handle();
|
||
|
};
|
||
|
addEndEventListener(node, endListener);
|
||
5 years ago
|
}
|
||
4 years ago
|
};
|
||
5 years ago
|
|
||
|
handleBackdropClick = () => {
|
||
|
if (this.props.closeOnClick) {
|
||
4 years ago
|
this.hide();
|
||
5 years ago
|
}
|
||
4 years ago
|
};
|
||
5 years ago
|
|
||
|
hasHidden = () => {
|
||
4 years ago
|
return this.state.hidden;
|
||
|
};
|
||
5 years ago
|
|
||
4 years ago
|
render() {
|
||
5 years ago
|
if (this.state.hidden) {
|
||
4 years ago
|
return null;
|
||
5 years ago
|
}
|
||
|
|
||
4 years ago
|
const { willHide } = this.state;
|
||
|
const { modalStyle } = this.props;
|
||
4 years ago
|
const backdropStyle = {
|
||
4 years ago
|
animationName: willHide
|
||
|
? animation.hideBackdropAnimation
|
||
|
: animation.showBackdropAnimation,
|
||
|
animationTimingFunction: (willHide ? animation.hide : animation.show)
|
||
|
.animationTimingFunction,
|
||
|
...this.props.backdropStyle,
|
||
4 years ago
|
};
|
||
4 years ago
|
const contentStyle = {
|
||
4 years ago
|
animationDuration: (willHide ? animation.hide : animation.show)
|
||
|
.animationDuration,
|
||
|
animationName: willHide
|
||
|
? animation.hideContentAnimation
|
||
|
: animation.showContentAnimation,
|
||
|
animationTimingFunction: (willHide ? animation.hide : animation.show)
|
||
|
.animationTimingFunction,
|
||
|
...this.props.contentStyle,
|
||
4 years ago
|
};
|
||
5 years ago
|
|
||
4 years ago
|
const backdrop = this.props.backdrop ? (
|
||
|
<div
|
||
|
className="modal__backdrop"
|
||
|
style={backdropStyle}
|
||
|
onClick={this.props.closeOnClick ? this.handleBackdropClick : null}
|
||
|
/>
|
||
4 years ago
|
) : undefined;
|
||
5 years ago
|
|
||
|
if (willHide) {
|
||
4 years ago
|
this.addTransitionListener(this.content, this.leave);
|
||
5 years ago
|
}
|
||
|
|
||
|
return (
|
||
|
<span>
|
||
|
<div className="modal" style={modalStyle}>
|
||
|
<div
|
||
4 years ago
|
className="modal__content"
|
||
5 years ago
|
ref={(el) => (this.content = el)}
|
||
5 years ago
|
tabIndex="-1"
|
||
|
style={contentStyle}
|
||
|
>
|
||
|
{this.props.children}
|
||
|
</div>
|
||
|
</div>
|
||
|
{backdrop}
|
||
|
</span>
|
||
4 years ago
|
);
|
||
5 years ago
|
}
|
||
|
|
||
|
leave = () => {
|
||
|
this.setState({
|
||
|
hidden: true,
|
||
4 years ago
|
});
|
||
|
this.props.onHide(this.state.hideSource);
|
||
|
};
|
||
5 years ago
|
|
||
|
enter = () => {
|
||
4 years ago
|
this.props.onShow();
|
||
|
};
|
||
5 years ago
|
|
||
|
show = () => {
|
||
|
if (!this.state.hidden) {
|
||
4 years ago
|
return;
|
||
5 years ago
|
}
|
||
|
|
||
|
this.setState({
|
||
|
willHide: false,
|
||
|
hidden: false,
|
||
4 years ago
|
});
|
||
5 years ago
|
|
||
4 years ago
|
setTimeout(
|
||
|
function () {
|
||
4 years ago
|
this.addTransitionListener(this.content, this.enter);
|
||
4 years ago
|
}.bind(this),
|
||
|
0,
|
||
4 years ago
|
);
|
||
|
};
|
||
5 years ago
|
|
||
|
hide = () => {
|
||
|
if (this.hasHidden()) {
|
||
4 years ago
|
return;
|
||
5 years ago
|
}
|
||
|
|
||
|
this.setState({
|
||
|
willHide: true,
|
||
4 years ago
|
});
|
||
|
};
|
||
5 years ago
|
|
||
|
listenKeyboard = (event) => {
|
||
|
if (typeof this.props.keyboard === 'function') {
|
||
4 years ago
|
this.props.keyboard(event);
|
||
5 years ago
|
} else {
|
||
4 years ago
|
this.closeOnEsc(event);
|
||
5 years ago
|
}
|
||
4 years ago
|
};
|
||
5 years ago
|
|
||
|
closeOnEsc = (event) => {
|
||
4 years ago
|
if (
|
||
|
this.props.keyboard &&
|
||
|
(event.key === 'Escape' || event.keyCode === 27)
|
||
|
) {
|
||
4 years ago
|
this.hide();
|
||
5 years ago
|
}
|
||
4 years ago
|
};
|
||
5 years ago
|
|
||
|
UNSAFE_componentDidMount = () => {
|
||
4 years ago
|
window.addEventListener('keydown', this.listenKeyboard, true);
|
||
|
};
|
||
5 years ago
|
|
||
|
UNSAFE_componentWillUnmount = () => {
|
||
4 years ago
|
window.removeEventListener('keydown', this.listenKeyboard, true);
|
||
|
};
|
||
5 years ago
|
}
|
||
|
|
||
4 years ago
|
export default FadeModal;
|