2018-10-09 02:23:32 +00:00
|
|
|
import React from 'react';
|
2018-12-07 04:59:25 +00:00
|
|
|
import { css } from 'glamor';
|
|
|
|
import PropTypes from 'prop-types';
|
|
|
|
import Transition from 'react-transition-group/Transition';
|
|
|
|
import Alert from './alert';
|
2018-10-09 02:23:32 +00:00
|
|
|
|
2018-12-07 04:59:25 +00:00
|
|
|
const animationEasing = {
|
|
|
|
deceleration: 'cubic-bezier(0.0, 0.0, 0.2, 1)',
|
|
|
|
acceleration: 'cubic-bezier(0.4, 0.0, 1, 1)',
|
|
|
|
spring: 'cubic-bezier(0.175, 0.885, 0.320, 1.175)'
|
2018-10-16 10:19:51 +00:00
|
|
|
};
|
2018-10-09 02:23:32 +00:00
|
|
|
|
2018-12-07 04:59:25 +00:00
|
|
|
const ANIMATION_DURATION = 240;
|
|
|
|
|
|
|
|
const openAnimation = css.keyframes('openAnimation', {
|
|
|
|
from: {
|
|
|
|
opacity: 0,
|
|
|
|
transform: 'translateY(-120%)'
|
2018-10-09 02:23:32 +00:00
|
|
|
},
|
2018-12-07 04:59:25 +00:00
|
|
|
to: {
|
|
|
|
transform: 'translateY(0)'
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
const closeAnimation = css.keyframes('closeAnimation', {
|
|
|
|
from: {
|
|
|
|
transform: 'scale(1)',
|
|
|
|
opacity: 1
|
2018-10-09 02:23:32 +00:00
|
|
|
},
|
2018-12-07 04:59:25 +00:00
|
|
|
to: {
|
|
|
|
transform: 'scale(0.9)',
|
|
|
|
opacity: 0
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
const animationStyles = css({
|
|
|
|
display: 'flex',
|
|
|
|
flexDirection: 'column',
|
|
|
|
alignItems: 'center',
|
|
|
|
height: 0,
|
|
|
|
transition: `all ${ANIMATION_DURATION}ms ${animationEasing.deceleration}`,
|
|
|
|
'&[data-state="entering"], &[data-state="entered"]': {
|
2019-01-31 09:37:02 +00:00
|
|
|
animation: `${openAnimation} ${ANIMATION_DURATION}ms ${animationEasing.spring} both`
|
2018-10-09 02:23:32 +00:00
|
|
|
},
|
2018-12-07 04:59:25 +00:00
|
|
|
'&[data-state="exiting"]': {
|
|
|
|
animation: `${closeAnimation} 120ms ${animationEasing.acceleration} both`
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
export default class Toast extends React.PureComponent {
|
|
|
|
static propTypes = {
|
2019-01-31 09:37:02 +00:00
|
|
|
/**
|
|
|
|
* The z-index of the toast.
|
|
|
|
*/
|
2018-12-07 04:59:25 +00:00
|
|
|
zIndex: PropTypes.number,
|
|
|
|
|
2019-01-31 09:37:02 +00:00
|
|
|
/**
|
|
|
|
* Duration of the toast.
|
|
|
|
*/
|
2018-12-07 04:59:25 +00:00
|
|
|
duration: PropTypes.number,
|
|
|
|
|
2019-01-31 09:37:02 +00:00
|
|
|
/**
|
|
|
|
* Function called when the toast is all the way closed.
|
|
|
|
*/
|
2018-12-07 04:59:25 +00:00
|
|
|
onRemove: PropTypes.func,
|
|
|
|
|
2019-01-31 09:37:02 +00:00
|
|
|
/**
|
|
|
|
* The type of the alert.
|
|
|
|
*/
|
|
|
|
intent: PropTypes.oneOf(['none', 'success', 'warning', 'danger']).isRequired,
|
2018-12-07 04:59:25 +00:00
|
|
|
|
2019-01-31 09:37:02 +00:00
|
|
|
/**
|
|
|
|
* The title of the alert.
|
|
|
|
*/
|
2018-12-07 04:59:25 +00:00
|
|
|
title: PropTypes.node,
|
|
|
|
|
2019-01-31 09:37:02 +00:00
|
|
|
/**
|
|
|
|
* Description of the alert.
|
|
|
|
*/
|
2018-12-07 04:59:25 +00:00
|
|
|
children: PropTypes.node,
|
|
|
|
|
2019-01-31 09:37:02 +00:00
|
|
|
/**
|
|
|
|
* When true, show a close icon button inside of the toast.
|
|
|
|
*/
|
2018-12-07 04:59:25 +00:00
|
|
|
hasCloseButton: PropTypes.bool,
|
|
|
|
|
2019-01-31 09:37:02 +00:00
|
|
|
/**
|
|
|
|
* When false, will close the Toast and call onRemove when finished.
|
|
|
|
*/
|
2018-12-07 04:59:25 +00:00
|
|
|
isShown: PropTypes.bool
|
2023-09-13 00:40:50 +00:00
|
|
|
};
|
2018-12-07 04:59:25 +00:00
|
|
|
|
|
|
|
static defaultProps = {
|
|
|
|
intent: 'none'
|
2023-09-13 00:40:50 +00:00
|
|
|
};
|
2018-12-07 04:59:25 +00:00
|
|
|
|
|
|
|
state = {
|
|
|
|
isShown: true,
|
|
|
|
height: 0
|
2023-09-13 00:40:50 +00:00
|
|
|
};
|
2018-12-07 04:59:25 +00:00
|
|
|
|
|
|
|
componentDidUpdate(prevProps) {
|
|
|
|
if (prevProps.isShown !== this.props.isShown) {
|
2019-01-31 09:37:02 +00:00
|
|
|
// eslint-disable-next-line react/no-did-update-set-state
|
2018-12-07 04:59:25 +00:00
|
|
|
this.setState({
|
|
|
|
isShown: this.props.isShown
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
componentDidMount() {
|
|
|
|
this.startCloseTimer();
|
|
|
|
}
|
|
|
|
|
|
|
|
componentWillUnmount() {
|
|
|
|
this.clearCloseTimer();
|
|
|
|
}
|
|
|
|
|
|
|
|
close = () => {
|
|
|
|
this.clearCloseTimer();
|
|
|
|
this.setState({
|
|
|
|
isShown: false
|
|
|
|
});
|
2023-09-13 00:40:50 +00:00
|
|
|
};
|
2018-12-07 04:59:25 +00:00
|
|
|
|
|
|
|
startCloseTimer = () => {
|
|
|
|
if (this.props.duration) {
|
|
|
|
this.closeTimer = setTimeout(() => {
|
|
|
|
this.close();
|
|
|
|
}, this.props.duration * 1000);
|
|
|
|
}
|
2023-09-13 00:40:50 +00:00
|
|
|
};
|
2018-12-07 04:59:25 +00:00
|
|
|
|
|
|
|
clearCloseTimer = () => {
|
|
|
|
if (this.closeTimer) {
|
|
|
|
clearTimeout(this.closeTimer);
|
|
|
|
this.closeTimer = null;
|
|
|
|
}
|
2023-09-13 00:40:50 +00:00
|
|
|
};
|
2018-12-07 04:59:25 +00:00
|
|
|
|
|
|
|
handleMouseEnter = () => {
|
|
|
|
this.clearCloseTimer();
|
2023-09-13 00:40:50 +00:00
|
|
|
};
|
2018-12-07 04:59:25 +00:00
|
|
|
|
|
|
|
handleMouseLeave = () => {
|
|
|
|
this.startCloseTimer();
|
2023-09-13 00:40:50 +00:00
|
|
|
};
|
2018-12-07 04:59:25 +00:00
|
|
|
|
|
|
|
onRef = ref => {
|
|
|
|
if (ref === null) return;
|
|
|
|
|
|
|
|
const { height } = ref.getBoundingClientRect();
|
|
|
|
|
|
|
|
this.setState({
|
|
|
|
height
|
|
|
|
});
|
2023-09-13 00:40:50 +00:00
|
|
|
};
|
2018-12-07 04:59:25 +00:00
|
|
|
|
|
|
|
render() {
|
|
|
|
return (
|
|
|
|
<Transition
|
2019-01-31 09:37:02 +00:00
|
|
|
appear
|
|
|
|
unmountOnExit
|
|
|
|
timeout={ANIMATION_DURATION}
|
|
|
|
in={this.state.isShown}
|
|
|
|
onExited={this.props.onRemove}
|
2018-12-07 04:59:25 +00:00
|
|
|
>
|
|
|
|
{state => (
|
|
|
|
<div
|
2019-01-31 09:37:02 +00:00
|
|
|
data-state={state}
|
|
|
|
className={animationStyles}
|
|
|
|
onMouseEnter={this.handleMouseEnter}
|
|
|
|
onMouseLeave={this.handleMouseLeave}
|
|
|
|
style={{
|
|
|
|
height: this.state.height,
|
|
|
|
zIndex: this.props.zIndex,
|
|
|
|
marginBottom: this.state.isShown ? 0 : -this.state.height
|
|
|
|
}}
|
2018-12-07 04:59:25 +00:00
|
|
|
>
|
|
|
|
<div ref={this.onRef} style={{ padding: 8 }}>
|
|
|
|
<Alert
|
|
|
|
intent={this.props.intent}
|
|
|
|
title={this.props.title}
|
|
|
|
children={this.props.children}
|
|
|
|
isRemoveable={this.props.hasCloseButton}
|
|
|
|
onRemove={() => this.close()}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
</div>
|
2019-01-31 09:37:02 +00:00
|
|
|
)}
|
2018-12-07 04:59:25 +00:00
|
|
|
</Transition>
|
|
|
|
);
|
2018-10-09 02:23:32 +00:00
|
|
|
}
|
2023-09-13 00:40:50 +00:00
|
|
|
}
|