From 94694541cda3911a72c5670cfa906e64038fbc32 Mon Sep 17 00:00:00 2001 From: Tom Chen Date: Sat, 30 Sep 2017 14:01:05 +0800 Subject: [PATCH] feat(ToastMessageAnimated): rewrite --- .../ToastMessage/ToastMessageAnimated.jsx | 182 ++++++++++++++++++ 1 file changed, 182 insertions(+) create mode 100644 src/components/ToastMessage/ToastMessageAnimated.jsx diff --git a/src/components/ToastMessage/ToastMessageAnimated.jsx b/src/components/ToastMessage/ToastMessageAnimated.jsx new file mode 100644 index 0000000..c013ec2 --- /dev/null +++ b/src/components/ToastMessage/ToastMessageAnimated.jsx @@ -0,0 +1,182 @@ +// @flow +import _ from "lodash" +import cx from "classnames" +import React from "react" +import on from "dom-helpers/events/on" +import { animationEnd } from "dom-helpers/transition/properties" + +import { ToastMessage as renderToastMessage } from "./ToastMessage" +import type { Props as ToastMessageProps } from "./ToastMessage" + +type Props = ToastMessageProps & { + className?: string, + /** or other animations from animate.css */ + showAnimation?: string, + /** or other animations from animate.css */ + hideAnimation?: string, + + /** + * Set the time (in ms) after which the toast message should automatically close. + */ + timeOut?: number, + /** + * Set the time (in ms) after which the toast message should automatically close after being hovered on. Applied on hover exit. + */ + extendedTimeOut?: number, + /** + * Enable dismissing toasts on click. + */ + tapToDismiss?: boolean, + onRemove: func, + onClick?: func, + onCloseClick?: func, +} + +/** + * A decorated component that leverages animate.css for toast showing/hiding. + * + * @see https://daneden.github.io/animate.css/ + */ +export class ToastMessageAnimated extends React.PureComponent { + static defaultProps = { + className: "toast", + showAnimation: "animated bounceIn", + hideAnimation: "animated bounceOut", + // + timeOut: 5000, + extendedTimeOut: 1000, + // + tapToDismiss: true, + // + onRemove: undefined, + onClick: _.noop, + onCloseClick: _.noop, + } + + state = { + hidingTimoutId: undefined, + className: undefined, + } + + componentDidMount() { + if (!_.isUndefined(this.props.showAnimation)) { + this.setState(() => { + on(this.message, animationEnd, this.handleTransitionEnd) + return { + className: this.props.showAnimation, + } + }) + } + if (this.props.timeOut > 0) { + this.setState(() => ({ + hidingTimoutId: setTimeout(this.handleHide, this.props.timeOut), + })) + } + } + + componentWillUnmount() { + if (!_.isUndefined(this.state.hidingTimoutId)) { + clearTimeout(this.state.hidingTimoutId) + } + this.setState = _.noop + } + + handleMount = ref => { + this.message = ref + } + + handleMouseEnter = () => { + this.setState(state => { + if (!_.isUndefined(state.hidingTimoutId)) { + clearTimeout(state.hidingTimoutId) + } + return { + hidingTimoutId: undefined, + className: undefined, + } + }) + } + + handleMouseLeave = () => { + this.setState(state => { + const timeOut = + this.props.extendedTimeOut !== 0 + ? this.props.extendedTimeOut + : this.props.timeOut + if (_.isUndefined(state.hidingTimoutId) && timeOut !== 0) { + return { + hidingTimoutId: setTimeout(this.handleHide, timeOut), + } + } + return {} + }) + } + + handleTransitionEnd = () => { + this.setState(() => ({ + className: undefined, + })) + } + + handleHideHookOnEnd = () => { + on( + this.message, + animationEnd, + _.flowRight(this.handleRemove, this.handleTransitionEnd) + ) + } + + handleHide = (override = false) => { + this.setState(state => { + if (override || !_.isUndefined(state.hidingTimoutId)) { + if (!_.isNil(this.props.hideAnimation)) { + return { + hidingTimoutId: setTimeout(this.handleHideHookOnEnd, 50), + className: this.props.hideAnimation, + } + } else { + this.handleRemove() + return { + hidingTimoutId: undefined, + className: undefined, + } + } + } + }) + } + + handleRemove = () => { + this.props.onRemove() + } + + handleOnClick = event => { + this.props.onClick(event) + if (this.props.tapToDismiss) { + this.handleHide(true) + } + } + + handleOnCloseClick = event => { + event.stopPropagation() + this.props.onCloseClick(event) + this.handleHide(true) + } + + render() { + const restProps = _.omit( + this.props, + _.keys(ToastMessageAnimated.defaultProps) + ) + return renderToastMessage({ + ...restProps, + ref: this.handleMount, + className: cx(this.props.className, this.state.className), + onClick: this.handleOnClick, + onMouseEnter: this.handleMouseEnter, + onMouseLeave: this.handleMouseLeave, + onCloseClick: this.handleOnCloseClick, + }) + } +} + +export default ToastMessageAnimated