Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(alert): rewrite with hook and support strict mode #24236

Merged
merged 3 commits into from May 19, 2020
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
217 changes: 95 additions & 122 deletions components/alert/index.tsx
@@ -1,5 +1,4 @@
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import CloseOutlined from '@ant-design/icons/CloseOutlined';
import CheckCircleOutlined from '@ant-design/icons/CheckCircleOutlined';
import ExclamationCircleOutlined from '@ant-design/icons/ExclamationCircleOutlined';
Expand All @@ -12,7 +11,7 @@ import CloseCircleFilled from '@ant-design/icons/CloseCircleFilled';
import Animate from 'rc-animate';
import classNames from 'classnames';

import { ConfigConsumer, ConfigConsumerProps } from '../config-provider';
import { ConfigContext } from '../config-provider';
import getDataOrAriaProps from '../_util/getDataOrAriaProps';
import ErrorBoundary from './ErrorBoundary';
import { replaceElement } from '../_util/reactNode';
Expand Down Expand Up @@ -48,11 +47,6 @@ export interface AlertProps {
onClick?: React.MouseEventHandler<HTMLDivElement>;
}

export interface AlertState {
closing: boolean;
closed: boolean;
}

const iconMapFilled = {
success: CheckCircleFilled,
info: InfoCircleFilled,
Expand All @@ -67,66 +61,67 @@ const iconMapOutlined = {
warning: ExclamationCircleOutlined,
};

export default class Alert extends React.Component<AlertProps, AlertState> {
static ErrorBoundary = ErrorBoundary;

state = {
closing: false,
closed: false,
};
interface AlertInterface extends React.FC<AlertProps> {
ErrorBoundary: typeof ErrorBoundary;
}

handleClose = (e: React.MouseEvent<HTMLButtonElement>) => {
const Alert: AlertInterface = ({
description,
prefixCls: customizePrefixCls,
message,
banner,
className = '',
style,
onMouseEnter,
onMouseLeave,
onClick,
showIcon,
closable,
closeText,
...props
}) => {
const [closing, setClosing] = React.useState(false);
const [closed, setClosed] = React.useState(false);

const ref = React.useRef<HTMLElement>();
const { getPrefixCls, direction } = React.useContext(ConfigContext);
const prefixCls = getPrefixCls('alert', customizePrefixCls);

const handleClose = (e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
const dom = ReactDOM.findDOMNode(this) as HTMLElement;
const dom = ref.current as HTMLElement;
dom.style.height = `${dom.offsetHeight}px`;
// Magic code
// 重复一次后才能正确设置 height
dom.style.height = `${dom.offsetHeight}px`;

this.setState({
closing: true,
});
this.props.onClose?.(e);
setClosing(true);
props.onClose?.(e);
};

animationEnd = () => {
this.setState({
closing: false,
closed: true,
});
this.props.afterClose?.();
const animationEnd = () => {
setClosing(false);
setClosed(true);
props.afterClose?.();
};

getShowIcon() {
const { banner, showIcon } = this.props;
// banner 模式默认有 Icon
return banner && showIcon === undefined ? true : showIcon;
}

getType() {
const { banner, type } = this.props;
const getType = () => {
const { type } = props;
if (type !== undefined) {
return type;
}
// banner 模式默认为警告
return banner ? 'warning' : 'info';
}
};

getClosable() {
const { closable, closeText } = this.props;
// closeable when closeText is assigned
return closeText ? true : closable;
}
// closeable when closeText is assigned
const isClosable = closeText ? true : closable;
const type = getType();

getIconType() {
const { description } = this.props;
const renderIconNode = () => {
const { icon } = props;
// use outline icon in alert with description
return (description ? iconMapOutlined : iconMapFilled)[this.getType()] || null;
}

renderIconNode({ prefixCls }: { prefixCls: string }) {
const { icon } = this.props;
const iconType = this.getIconType();
const iconType = (description ? iconMapOutlined : iconMapFilled)[type] || null;
if (icon) {
return replaceElement(icon, <span className={`${prefixCls}-icon`}>{icon}</span>, () => ({
className: classNames(`${prefixCls}-icon`, {
Expand All @@ -135,14 +130,13 @@ export default class Alert extends React.Component<AlertProps, AlertState> {
}));
}
return React.createElement(iconType, { className: `${prefixCls}-icon` });
}
};

renderCloseIcon({ prefixCls }: { prefixCls: string }) {
const { closeText } = this.props;
return this.getClosable() ? (
const renderCloseIcon = () => {
return isClosable ? (
<button
type="button"
onClick={this.handleClose}
onClick={handleClose}
className={`${prefixCls}-close-icon`}
tabIndex={0}
>
Expand All @@ -153,74 +147,53 @@ export default class Alert extends React.Component<AlertProps, AlertState> {
)}
</button>
) : null;
}

renderAlert = ({ getPrefixCls, direction }: ConfigConsumerProps) => {
const {
description,
prefixCls: customizePrefixCls,
message,
banner,
className = '',
style,
onMouseEnter,
onMouseLeave,
onClick,
} = this.props;
const { closing, closed } = this.state;

const prefixCls = getPrefixCls('alert', customizePrefixCls);

const isShowIcon = this.getShowIcon();
const type = this.getType();
const closable = this.getClosable();

const alertCls = classNames(
prefixCls,
`${prefixCls}-${type}`,
{
[`${prefixCls}-closing`]: closing,
[`${prefixCls}-with-description`]: !!description,
[`${prefixCls}-no-icon`]: !isShowIcon,
[`${prefixCls}-banner`]: !!banner,
[`${prefixCls}-closable`]: closable,
[`${prefixCls}-rtl`]: direction === 'rtl',
},
className,
);

const closeIcon = this.renderCloseIcon({ prefixCls });

const dataOrAriaProps = getDataOrAriaProps(this.props);

const iconNode = this.renderIconNode({ prefixCls });

return closed ? null : (
<Animate
component=""
showProp="data-show"
transitionName={`${prefixCls}-slide-up`}
onEnd={this.animationEnd}
>
<div
data-show={!closing}
className={alertCls}
style={style}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
onClick={onClick}
{...dataOrAriaProps}
>
{isShowIcon ? iconNode : null}
<span className={`${prefixCls}-message`}>{message}</span>
<span className={`${prefixCls}-description`}>{description}</span>
{closeIcon}
</div>
</Animate>
);
};

render() {
return <ConfigConsumer>{this.renderAlert}</ConfigConsumer>;
}
}
// banner 模式默认有 Icon
const isShowIcon = banner && showIcon === undefined ? true : showIcon;

const alertCls = classNames(
prefixCls,
`${prefixCls}-${type}`,
{
[`${prefixCls}-closing`]: closing,
[`${prefixCls}-with-description`]: !!description,
[`${prefixCls}-no-icon`]: !isShowIcon,
[`${prefixCls}-banner`]: !!banner,
[`${prefixCls}-closable`]: isClosable,
[`${prefixCls}-rtl`]: direction === 'rtl',
},
className,
);

const dataOrAriaProps = getDataOrAriaProps(props);

return closed ? null : (
<Animate
component=""
showProp="data-show"
transitionName={`${prefixCls}-slide-up`}
onEnd={animationEnd}
>
<div
ref={ref}
data-show={!closing}
className={alertCls}
style={style}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
onClick={onClick}
{...dataOrAriaProps}
>
{isShowIcon ? renderIconNode() : null}
<span className={`${prefixCls}-message`}>{message}</span>
<span className={`${prefixCls}-description`}>{description}</span>
{renderCloseIcon()}
</div>
</Animate>
);
};

Alert.ErrorBoundary = ErrorBoundary;

export default Alert;