|
|
@ -1,5 +1,4 @@ |
|
|
|
import * as React from 'react'; |
|
|
|
import * as ReactDOM from 'react-dom'; |
|
|
|
import Animate from 'rc-animate'; |
|
|
|
import classNames from 'classnames'; |
|
|
|
import omit from 'omit.js'; |
|
|
@ -14,23 +13,26 @@ export interface TagProps extends React.HTMLAttributes<HTMLDivElement> { |
|
|
|
prefixCls?: string; |
|
|
|
className?: string; |
|
|
|
color?: string; |
|
|
|
/** 标签是否可以关闭 */ |
|
|
|
closable?: boolean; |
|
|
|
visible?: boolean; |
|
|
|
/** 关闭时的回调 */ |
|
|
|
onClose?: Function; |
|
|
|
/** 动画关闭后的回调 */ |
|
|
|
afterClose?: Function; |
|
|
|
style?: React.CSSProperties; |
|
|
|
} |
|
|
|
|
|
|
|
export interface TagState { |
|
|
|
closing: boolean; |
|
|
|
closed: boolean; |
|
|
|
interface TagState { |
|
|
|
visible: boolean; |
|
|
|
mounted: boolean; |
|
|
|
} |
|
|
|
|
|
|
|
interface InnterTagProps extends TagProps { |
|
|
|
show: boolean; |
|
|
|
} |
|
|
|
|
|
|
|
const InnerTag = ({ show, ...restProps }: InnterTagProps) => { |
|
|
|
const divProps = omit(restProps, ['onClose', 'afterClose', 'color', 'visible', 'closable']); |
|
|
|
return <div {...divProps} />; |
|
|
|
}; |
|
|
|
|
|
|
|
class Tag extends React.Component<TagProps, TagState> { |
|
|
|
static CheckableTag = CheckableTag; |
|
|
|
static defaultProps = { |
|
|
@ -38,84 +40,40 @@ class Tag extends React.Component<TagProps, TagState> { |
|
|
|
closable: false, |
|
|
|
}; |
|
|
|
|
|
|
|
static getDerivedStateFromProps(nextProps: TagProps, state: TagState) { |
|
|
|
static getDerivedStateFromProps(nextProps: TagProps) { |
|
|
|
if ('visible' in nextProps) { |
|
|
|
let newState: Partial<TagState> = { |
|
|
|
return { |
|
|
|
visible: nextProps.visible, |
|
|
|
mounted: true, |
|
|
|
}; |
|
|
|
|
|
|
|
if (!state.mounted) { |
|
|
|
newState = { |
|
|
|
...newState, |
|
|
|
closed: !nextProps.visible, |
|
|
|
}; |
|
|
|
} |
|
|
|
return newState; |
|
|
|
} |
|
|
|
return null; |
|
|
|
} |
|
|
|
|
|
|
|
state = { |
|
|
|
closing: false, |
|
|
|
closed: false, |
|
|
|
visible: true, |
|
|
|
mounted: false, |
|
|
|
}; |
|
|
|
|
|
|
|
componentDidUpdate(_prevProps: TagProps, prevState: TagState) { |
|
|
|
if (prevState.visible && !this.state.visible) { |
|
|
|
this.close(); |
|
|
|
} else if (!prevState.visible && this.state.visible) { |
|
|
|
this.show(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
handleIconClick = (e: React.MouseEvent<HTMLElement>) => { |
|
|
|
const onClose = this.props.onClose; |
|
|
|
setVisible(visible: boolean, e: React.MouseEvent<HTMLElement>) { |
|
|
|
const { onClose } = this.props; |
|
|
|
if (onClose) { |
|
|
|
onClose(e); |
|
|
|
} |
|
|
|
if (e.defaultPrevented || 'visible' in this.props) { |
|
|
|
if (e.defaultPrevented) { |
|
|
|
return; |
|
|
|
} |
|
|
|
this.setState({ visible: false }); |
|
|
|
}; |
|
|
|
|
|
|
|
close = () => { |
|
|
|
if (this.state.closing || this.state.closed) { |
|
|
|
return; |
|
|
|
if (!('visible' in this.props)) { |
|
|
|
this.setState({ visible }); |
|
|
|
} |
|
|
|
const dom = ReactDOM.findDOMNode(this) as HTMLElement; |
|
|
|
dom.style.width = `${dom.getBoundingClientRect().width}px`; |
|
|
|
// It's Magic Code, don't know why
|
|
|
|
dom.style.width = `${dom.getBoundingClientRect().width}px`; |
|
|
|
this.setState({ |
|
|
|
closing: true, |
|
|
|
}); |
|
|
|
}; |
|
|
|
} |
|
|
|
|
|
|
|
show = () => { |
|
|
|
this.setState({ |
|
|
|
closed: false, |
|
|
|
}); |
|
|
|
handleIconClick = (e: React.MouseEvent<HTMLElement>) => { |
|
|
|
this.setVisible(false, e); |
|
|
|
}; |
|
|
|
|
|
|
|
animationEnd = (_: string, existed: boolean) => { |
|
|
|
if (!existed && !this.state.closed) { |
|
|
|
this.setState({ |
|
|
|
closed: true, |
|
|
|
closing: false, |
|
|
|
}); |
|
|
|
|
|
|
|
const afterClose = this.props.afterClose; |
|
|
|
if (afterClose) { |
|
|
|
afterClose(); |
|
|
|
} |
|
|
|
} else { |
|
|
|
this.setState({ |
|
|
|
closed: false, |
|
|
|
}); |
|
|
|
animationEnd = () => { |
|
|
|
const { afterClose } = this.props; |
|
|
|
if (afterClose) { |
|
|
|
afterClose(); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
@ -128,44 +86,55 @@ class Tag extends React.Component<TagProps, TagState> { |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
render() { |
|
|
|
const { prefixCls, closable, color, className, children, style, ...otherProps } = this.props; |
|
|
|
const closeIcon = closable ? <Icon type="close" onClick={this.handleIconClick} /> : ''; |
|
|
|
getTagStyle() { |
|
|
|
const { color, style } = this.props; |
|
|
|
const isPresetColor = this.isPresetColor(color); |
|
|
|
const classString = classNames( |
|
|
|
return { |
|
|
|
backgroundColor: color && !isPresetColor ? color : undefined, |
|
|
|
...style, |
|
|
|
}; |
|
|
|
} |
|
|
|
|
|
|
|
getTagClassName() { |
|
|
|
const { prefixCls, className, color } = this.props; |
|
|
|
const { visible } = this.state; |
|
|
|
const isPresetColor = this.isPresetColor(color); |
|
|
|
return classNames( |
|
|
|
prefixCls, |
|
|
|
{ |
|
|
|
[`${prefixCls}-${color}`]: isPresetColor, |
|
|
|
[`${prefixCls}-has-color`]: color && !isPresetColor, |
|
|
|
[`${prefixCls}-close`]: this.state.closing, |
|
|
|
[`${prefixCls}-hidden`]: !visible, |
|
|
|
}, |
|
|
|
className, |
|
|
|
); |
|
|
|
// fix https://fb.me/react-unknown-prop
|
|
|
|
const divProps = omit(otherProps, ['onClose', 'afterClose', 'visible']); |
|
|
|
const tagStyle = { |
|
|
|
backgroundColor: color && !isPresetColor ? color : null, |
|
|
|
...style, |
|
|
|
}; |
|
|
|
const tag = this.state.closed ? ( |
|
|
|
<span /> |
|
|
|
) : ( |
|
|
|
<div data-show={!this.state.closing} {...divProps} className={classString} style={tagStyle}> |
|
|
|
{children} |
|
|
|
{closeIcon} |
|
|
|
</div> |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
renderCloseIcon() { |
|
|
|
const { closable } = this.props; |
|
|
|
return closable ? <Icon type="close" onClick={this.handleIconClick} /> : null; |
|
|
|
} |
|
|
|
|
|
|
|
render() { |
|
|
|
const { prefixCls, children, ...otherProps } = this.props; |
|
|
|
const { visible } = this.state; |
|
|
|
return ( |
|
|
|
<Wave> |
|
|
|
<Animate |
|
|
|
component="" |
|
|
|
showProp="data-show" |
|
|
|
showProp="show" |
|
|
|
transitionName={`${prefixCls}-zoom`} |
|
|
|
transitionAppear |
|
|
|
onEnd={this.animationEnd} |
|
|
|
> |
|
|
|
{tag} |
|
|
|
<InnerTag |
|
|
|
show={visible} |
|
|
|
{...otherProps} |
|
|
|
className={this.getTagClassName()} |
|
|
|
style={this.getTagStyle()} |
|
|
|
> |
|
|
|
{children} |
|
|
|
{this.renderCloseIcon()} |
|
|
|
</InnerTag> |
|
|
|
</Animate> |
|
|
|
</Wave> |
|
|
|
); |
|
|
|