import React, { useState, useImperativeHandle, useEffect, forwardRef, useRef, } from "react"; import classNames from "classnames"; import ReactDOM, { unmountComponentAtNode, createPortal } from "react-dom"; import { StyledProps, Omit } from "../_type"; import { useConfig } from "../_util/config-context"; import { Button } from "../button"; import { Justify } from "../justify"; import { Icon, IconProps } from "../icon"; import { Text, TextProps } from "../text"; import { ConfigProvider } from "../configprovider"; import { useVisibleTransition } from "../_util/use-visible-transition"; import { callBoth } from "../_util/call-both"; import { noop } from "../_util/noop"; import { getRoot, NotificationTransition, limit } from "./util"; import { getPlainText } from "../_util/getPlainText"; import { useIsomorphicLayoutEffect } from "../_util/use-isomorphic-layout-effect"; import { useTranslation } from "../i18n"; export interface NotificationProps extends StyledProps { /** * 类型 */ type: "success" | "warning" | "error"; /** * 通知标题 */ title?: React.ReactNode; /** * 通知内容 */ description?: React.ReactNode; /** * 标题右侧渲染内容 */ extra?: React.ReactNode; /** * 底部内容 */ footer?: React.ReactNode; /** * 底部区域点击事件 * * **当该属性不为空时底部区域将变为可点击样式** */ onFooterClick?: (event: React.MouseEvent) => void; /** * 自动关闭延时(单位毫秒) * * 设置为 0 时不自动关闭(`2.5.2` 支持) * * @default 5000 */ duration?: number; /** * 相同内容的通知同时是否只展示一份 * @default false * @since 2.5.0 */ unique?: boolean; /** * 通知关闭时回调 */ onClose?: () => void; /** * 点击通知时回调 * @since 2.4.2 */ onClick?: (event: React.MouseEvent) => void; /** * 通知关闭动画结束时回调 * @deprecated */ onExited?: () => void; } interface NotificationInstance { setVisible: React.Dispatch>; renew: () => void; } const themeMap: { [key in NotificationProps["type"]]: [ IconProps["type"], TextProps<"p">["theme"] ]; } = { // type: [icon, theme] success: ["success", "success"], warning: ["warning", "warning"], error: ["error", "danger"], }; const Notification = forwardRef(function NotificationShow( { type, title, extra, description, footer, duration = 5000, className, style, onClose = noop, onClick = noop, onFooterClick, }: NotificationProps, ref: React.Ref ) { const { classPrefix } = useConfig(); const t = useTranslation(); const [visible, setVisible] = useState(false); const timerRef = useRef(null); useImperativeHandle(ref, () => ({ setVisible, renew: () => { clearTimeout(timerRef.current); if (visible && duration !== 0) { timerRef.current = setTimeout(() => setVisible(false), duration); } }, })); // 渲染之后,马上显示 useEffect(() => { setVisible(true); return () => clearTimeout(timerRef.current); }, []); useIsomorphicLayoutEffect(() => { clearTimeout(timerRef.current); if (visible && duration !== 0) { timerRef.current = setTimeout(() => setVisible(false), duration); } }, [duration, visible]); const { shouldContentEnter, shouldContentRender, onContentExit, } = useVisibleTransition(visible); if (!shouldContentRender) { return null; } const hasHeader = !!title || !!extra; const [icon, theme] = themeMap[type] || themeMap.success; const content = (
clearTimeout(timerRef.current)} onMouseLeave={() => { if (duration !== 0) { timerRef.current = setTimeout(() => setVisible(false), duration); } }} onClick={onClick} >
{hasHeader && (
{title}
} right={
{extra}
} />
)}
{description}
{!!footer && (
{onFooterClick ? ( {footer} } right={} /> ) : ( footer )}
)}
); return createPortal(content, getRoot(classPrefix)); }); Notification.displayName = "Notification"; interface NotificationHandle { /** * 关闭并销毁当前对话框 */ destroy: () => void; } export interface NotificationOptions extends Omit {} let showMethod; /** * API 的方式显示通知 */ function show( type: NotificationProps["type"] = "success", { onClose = noop, onExited = noop, ...options }: NotificationOptions = {} ): NotificationHandle { if (typeof showMethod === "function") { return showMethod(type, options); } const el = document.createElement("div"); const instanceRef = React.createRef(); const show = () => { ReactDOM.render( { onClose(); onExited(); unmountComponentAtNode(el); }} /> , el ); }; const hide = () => { if (instanceRef.current) { instanceRef.current.setVisible(false); } }; const renew = () => { if (instanceRef.current) { instanceRef.current.renew(); } }; const handle = { hide, renew, destroy: hide, }; limit({ show, hide, renew, isExisted: () => !!instanceRef.current, identifier: options.unique ? [ type, getPlainText(options.title), getPlainText(options.description), getPlainText(options.extra), getPlainText(options.footer), ].join("$") : null, }); return handle; } export const notification = { success: (options: NotificationOptions) => show("success", options), warning: (options: NotificationOptions) => show("warning", options), error: (options: NotificationOptions) => show("error", options), }; // eslint-disable-next-line dot-notation notification["setShowMethod"] = method => { showMethod = method; };