diff --git a/scss/components/_toast.scss b/scss/components/_toast.scss index 76862c07..579304d3 100644 --- a/scss/components/_toast.scss +++ b/scss/components/_toast.scss @@ -90,6 +90,20 @@ animation-name: bounceOut; } + &-close { + position: absolute; + top: $gap-xs; + right: $gap-sm; + color: $white; + line-height: 1; + opacity: 0.8; + + &:hover { + color: $white; + opacity: 1; + } + } + &-title { display: $Toast-display; font-size: $fontSizeMd; diff --git a/src/components/Toast.tsx b/src/components/Toast.tsx index db9f63a7..e50a937d 100644 --- a/src/components/Toast.tsx +++ b/src/components/Toast.tsx @@ -13,8 +13,14 @@ import Transition, { import React from 'react'; import cx from 'classnames'; import Html from './Html'; -import {uuid, autobind} from '../utils/helper'; -import {ClassNamesFn, themeable} from '../theme'; +import {uuid, autobind, noop} from '../utils/helper'; +import {ClassNamesFn, themeable, classnames} from '../theme'; +import {Icon} from './icons'; + +interface Config { + closeButton?: boolean; + timeout?: number; +} const fadeStyles: { [propName: string]: string; @@ -25,12 +31,6 @@ const fadeStyles: { }; let toastRef: any = null; -let config: { - closeButton?: boolean; - timeOut?: number; - extendedTimeOut?: number; -} = {}; - const show = ( content: string, title: string = '', @@ -40,7 +40,7 @@ const show = ( if (!toastRef || !toastRef[method]) { return; } - toastRef[method](content, title || '', {...config, ...conf}); + toastRef[method](content, title || '', {...conf}); }; interface ToastComponentProps { @@ -52,14 +52,13 @@ interface ToastComponentProps { | 'bottom-left' | 'bottom-right'; closeButton: boolean; - timeOut: number; - extendedTimeOut: number; + timeout: number; classPrefix: string; classnames: ClassNamesFn; className?: string; } -interface Item { +interface Item extends Config { title?: string; body: string; level: 'info' | 'success' | 'error' | 'warning'; @@ -76,12 +75,11 @@ export class ToastComponent extends React.Component< > { static defaultProps: Pick< ToastComponentProps, - 'position' | 'closeButton' | 'timeOut' | 'extendedTimeOut' + 'position' | 'closeButton' | 'timeout' > = { position: 'top-right', closeButton: false, - timeOut: 5000, - extendedTimeOut: 3000 + timeout: 5000 }; // 当前ToastComponent是否真正render了 @@ -90,15 +88,6 @@ export class ToastComponent extends React.Component< items: [] }; - componentWillMount() { - const {closeButton, timeOut, extendedTimeOut} = this.props; - config = { - closeButton, - timeOut, - extendedTimeOut - }; - } - componentDidMount() { this.hasRendered = true; toastRef = this; @@ -157,27 +146,27 @@ export class ToastComponent extends React.Component< return null; } - const {classPrefix: ns, className, timeOut, position} = this.props; + const {classnames: cx, className, timeout, position} = this.props; const items = this.state.items; return (
l.toUpperCase() + `Toast-wrap Toast-wrap--${position.replace(/\-(\w)/g, (_, l) => + l.toUpperCase() )}`, className )} > {items.map((item, index) => ( ))} @@ -192,7 +181,8 @@ interface ToastMessageProps { title?: string; body: string; level: 'info' | 'success' | 'error' | 'warning'; - timeOut: number; + timeout: number; + closeButton?: boolean; position: | 'top-right' | 'top-center' @@ -201,7 +191,7 @@ interface ToastMessageProps { | 'bottom-left' | 'bottom-right'; onDismiss?: () => void; - classPrefix: string; + classnames: ClassNamesFn; allowHtml: boolean; } @@ -209,9 +199,12 @@ interface ToastMessageState { visible: boolean; } -export class ToastMessage extends React.Component { +export class ToastMessage extends React.Component< + ToastMessageProps, + ToastMessageState +> { static defaultProps = { - timeOut: 5000, + timeout: 5000, classPrefix: '', position: 'top-right', allowHtml: true, @@ -225,15 +218,6 @@ export class ToastMessage extends React.Component { // content: React.RefObject; timer: NodeJS.Timeout; mounted: boolean = false; - constructor(props: ToastMessageProps) { - super(props); - - // this.content = React.createRef(); - this.handleMouseEnter = this.handleMouseEnter.bind(this); - this.handleMouseLeave = this.handleMouseLeave.bind(this); - this.handleEntered = this.handleEntered.bind(this); - this.close = this.close.bind(this); - } componentDidMount() { this.mounted = true; @@ -247,21 +231,25 @@ export class ToastMessage extends React.Component { this.mounted = false; } + @autobind handleMouseEnter() { clearTimeout(this.timer); } + @autobind handleMouseLeave() { this.handleEntered(); } + @autobind handleEntered() { - const timeOut = this.props.timeOut; - if (this.mounted) { - this.timer = setTimeout(this.close, timeOut); + const timeout = this.props.timeout; + if (this.mounted && timeout) { + this.timer = setTimeout(this.close, timeout); } } + @autobind close() { clearTimeout(this.timer); this.setState({ @@ -272,8 +260,8 @@ export class ToastMessage extends React.Component { render() { const { onDismiss, - classPrefix: ns, - position, + classnames: cx, + closeButton, title, body, allowHtml, @@ -290,26 +278,20 @@ export class ToastMessage extends React.Component { onExited={onDismiss} > {(status: string) => { - // if (status === ENTERING) { - // // force reflow - // // 由于从 mount 进来到加上 in 这个 class 估计是时间太短,上次的样式还没应用进去,所以这里强制reflow一把。 - // // 否则看不到动画。 - // this.content.current && this.content.current.offsetWidth; - // } - return (
- {title ?
{title}
: null} -
+ {closeButton ? ( + + + + ) : null} + {title ?
{title}
: null} +
{allowHtml ? : body}