import type { ReactElement } from 'react'; import * as React from 'react'; import CheckCircleFilled from '@ant-design/icons/CheckCircleFilled'; import CloseCircleFilled from '@ant-design/icons/CloseCircleFilled'; import CloseOutlined from '@ant-design/icons/CloseOutlined'; import ExclamationCircleFilled from '@ant-design/icons/ExclamationCircleFilled'; import InfoCircleFilled from '@ant-design/icons/InfoCircleFilled'; import classNames from 'classnames'; import CSSMotion from 'rc-motion'; import pickAttrs from 'rc-util/lib/pickAttrs'; import type { ClosableType } from '../_util/hooks/useClosable'; import { replaceElement } from '../_util/reactNode'; import { devUseWarning } from '../_util/warning'; import { ConfigContext } from '../config-provider'; import useStyle from './style'; export interface AlertProps { /** Type of Alert styles, options:`success`, `info`, `warning`, `error` */ type?: 'success' | 'info' | 'warning' | 'error'; /** Whether Alert can be closed */ closable?: ClosableType; /** * @deprecated please use `closable.closeIcon` instead. * Close text to show */ closeText?: React.ReactNode; /** Content of Alert */ message?: React.ReactNode; /** Additional content of Alert */ description?: React.ReactNode; /** Callback when close Alert */ onClose?: React.MouseEventHandler; /** Trigger when animation ending of Alert */ afterClose?: () => void; /** Whether to show icon */ showIcon?: boolean; /** https://www.w3.org/TR/2014/REC-html5-20141028/dom.html#aria-role-attribute */ role?: string; style?: React.CSSProperties; prefixCls?: string; className?: string; rootClassName?: string; banner?: boolean; icon?: React.ReactNode; closeIcon?: React.ReactNode; action?: React.ReactNode; onMouseEnter?: React.MouseEventHandler; onMouseLeave?: React.MouseEventHandler; onClick?: React.MouseEventHandler; } const iconMapFilled = { success: CheckCircleFilled, info: InfoCircleFilled, error: CloseCircleFilled, warning: ExclamationCircleFilled, }; interface IconNodeProps { type: AlertProps['type']; icon: AlertProps['icon']; prefixCls: AlertProps['prefixCls']; description: AlertProps['description']; } const IconNode: React.FC = (props) => { const { icon, prefixCls, type } = props; const iconType = iconMapFilled[type!] || null; if (icon) { return replaceElement(icon, {icon}, () => ({ className: classNames(`${prefixCls}-icon`, { [(icon as ReactElement).props.className]: (icon as ReactElement).props.className, }), })) as ReactElement; } return React.createElement(iconType, { className: `${prefixCls}-icon` }); }; type CloseIconProps = { isClosable: boolean; prefixCls: AlertProps['prefixCls']; closeIcon: AlertProps['closeIcon']; handleClose: AlertProps['onClose']; ariaProps: React.AriaAttributes; }; const CloseIconNode: React.FC = (props) => { const { isClosable, prefixCls, closeIcon, handleClose, ariaProps } = props; const mergedCloseIcon = closeIcon === true || closeIcon === undefined ? : closeIcon; return isClosable ? ( ) : null; }; const Alert: React.FC = (props) => { const { description, prefixCls: customizePrefixCls, message, banner, className, rootClassName, style, onMouseEnter, onMouseLeave, onClick, afterClose, showIcon, closable, closeText, closeIcon, action, ...otherProps } = props; const [closed, setClosed] = React.useState(false); if (process.env.NODE_ENV !== 'production') { const warning = devUseWarning('Alert'); warning.deprecated(!closeText, 'closeText', 'closable.closeIcon'); } const ref = React.useRef(null); const { getPrefixCls, direction, alert } = React.useContext(ConfigContext); const prefixCls = getPrefixCls('alert', customizePrefixCls); const [wrapCSSVar, hashId, cssVarCls] = useStyle(prefixCls); const handleClose = (e: React.MouseEvent) => { setClosed(true); props.onClose?.(e); }; const type = React.useMemo(() => { if (props.type !== undefined) { return props.type; } // banner mode defaults to 'warning' return banner ? 'warning' : 'info'; }, [props.type, banner]); // closeable when closeText or closeIcon is assigned const isClosable = React.useMemo(() => { if (typeof closable === 'object' && closable.closeIcon) return true; if (closeText) { return true; } if (typeof closable === 'boolean') { return closable; } // should be true when closeIcon is 0 or '' if (closeIcon !== false && closeIcon !== null && closeIcon !== undefined) { return true; } return !!alert?.closable; }, [closeText, closeIcon, closable, alert?.closable]); // banner mode defaults to Icon const isShowIcon = banner && showIcon === undefined ? true : showIcon; const alertCls = classNames( prefixCls, `${prefixCls}-${type}`, { [`${prefixCls}-with-description`]: !!description, [`${prefixCls}-no-icon`]: !isShowIcon, [`${prefixCls}-banner`]: !!banner, [`${prefixCls}-rtl`]: direction === 'rtl', }, alert?.className, className, rootClassName, cssVarCls, hashId, ); const restProps = pickAttrs(otherProps, { aria: true, data: true }); const mergedCloseIcon = React.useMemo(() => { if (typeof closable === 'object' && closable.closeIcon) { return closable.closeIcon; } if (closeText) { return closeText; } if (closeIcon !== undefined) { return closeIcon; } if (typeof alert?.closable === 'object' && alert?.closable?.closeIcon) { return alert?.closable?.closeIcon; } return alert?.closeIcon; }, [closeIcon, closable, closeText, alert?.closeIcon]); const mergedAriaProps = React.useMemo(() => { const merged = closable ?? alert?.closable; if (typeof merged === 'object') { // eslint-disable-next-line @typescript-eslint/no-unused-vars const { closeIcon: _, ...ariaProps } = merged; return ariaProps; } return {}; }, [closable, alert?.closable]); return wrapCSSVar( ({ maxHeight: node.offsetHeight })} onLeaveEnd={afterClose} > {({ className: motionClassName, style: motionStyle }) => (
{isShowIcon ? ( ) : null}
{message ?
{message}
: null} {description ?
{description}
: null}
{action ?
{action}
: null}
)}
, ); }; if (process.env.NODE_ENV !== 'production') { Alert.displayName = 'Alert'; } export default Alert;