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 1 commit
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
221 changes: 106 additions & 115 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,65 @@ const iconMapOutlined = {
warning: ExclamationCircleOutlined,
};

export default class Alert extends React.Component<AlertProps, AlertState> {
static ErrorBoundary = ErrorBoundary;
interface AlertInterface extends React.FC<AlertProps> {
ErrorBoundary: typeof ErrorBoundary;
}

state = {
closing: false,
closed: false,
};
const Alert: AlertInterface = props => {
const [closing, setClosing] = React.useState(false);
const [closed, setClosed] = React.useState(false);

handleClose = (e: React.MouseEvent<HTMLButtonElement>) => {
const ref = React.createRef<HTMLElement>();
hengkx marked this conversation as resolved.
Show resolved Hide resolved
const { getPrefixCls, direction } = React.useContext(ConfigContext);

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;
const getShowIcon = () => {
const { banner, showIcon } = props;
// banner 模式默认有 Icon
return banner && showIcon === undefined ? true : showIcon;
}
};

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

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

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

renderIconNode({ prefixCls }: { prefixCls: string }) {
const { icon } = this.props;
const iconType = this.getIconType();
const renderIconNode = ({ prefixCls }: { prefixCls: string }) => {
const { icon } = props;
const iconType = getIconType();
if (icon) {
return replaceElement(icon, <span className={`${prefixCls}-icon`}>{icon}</span>, () => ({
className: classNames(`${prefixCls}-icon`, {
Expand All @@ -135,14 +128,14 @@ 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 = ({ prefixCls }: { prefixCls: string }) => {
const { closeText } = props;
return getClosable() ? (
<button
type="button"
onClick={this.handleClose}
onClick={handleClose}
className={`${prefixCls}-close-icon`}
tabIndex={0}
>
Expand All @@ -153,74 +146,72 @@ 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>;
}
}
const {
description,
prefixCls: customizePrefixCls,
message,
banner,
className = '',
style,
onMouseEnter,
onMouseLeave,
onClick,
} = props;

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

const isShowIcon = getShowIcon();
const type = getType();
const closable = 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 = renderCloseIcon({ prefixCls });

const dataOrAriaProps = getDataOrAriaProps(props);

const iconNode = renderIconNode({ prefixCls });

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 ? iconNode : null}
<span className={`${prefixCls}-message`}>{message}</span>
<span className={`${prefixCls}-description`}>{description}</span>
{closeIcon}
</div>
</Animate>
);
};

Alert.ErrorBoundary = ErrorBoundary;

export default Alert;