import * as React from 'react'; import { useNotification as useRcNotification } from 'rc-notification'; import type { NotificationAPI } from 'rc-notification/lib'; import classNames from 'classnames'; import { ConfigContext } from '../config-provider'; import type { NotificationInstance, ArgsProps, NotificationPlacement, NotificationConfig, } from './interface'; import { getPlacementStyle, getMotion } from './util'; import warning from '../_util/warning'; import useStyle from './style'; import { getCloseIcon, PureContent } from './PurePanel'; const DEFAULT_OFFSET = 24; const DEFAULT_DURATION = 4.5; // ============================================================================== // == Holder == // ============================================================================== type HolderProps = NotificationConfig & { onAllRemoved?: VoidFunction; }; interface HolderRef extends NotificationAPI { prefixCls: string; hashId: string; } const Holder = React.forwardRef((props, ref) => { const { top, bottom, prefixCls: staticPrefixCls, getContainer: staticGetContainer, maxCount, rtl, onAllRemoved, } = props; const { getPrefixCls, getPopupContainer } = React.useContext(ConfigContext); const prefixCls = staticPrefixCls || getPrefixCls('notification'); // =============================== Style =============================== const getStyle = (placement: NotificationPlacement) => getPlacementStyle(placement, top ?? DEFAULT_OFFSET, bottom ?? DEFAULT_OFFSET); // Style const [, hashId] = useStyle(prefixCls); const getClassName = () => classNames(hashId, { [`${prefixCls}-rtl`]: rtl }); // ============================== Motion =============================== const getNotificationMotion = () => getMotion(prefixCls); // ============================== Origin =============================== const [api, holder] = useRcNotification({ prefixCls, style: getStyle, className: getClassName, motion: getNotificationMotion, closable: true, closeIcon: getCloseIcon(prefixCls), duration: DEFAULT_DURATION, getContainer: () => staticGetContainer?.() || getPopupContainer?.() || document.body, maxCount, onAllRemoved, }); // ================================ Ref ================================ React.useImperativeHandle(ref, () => ({ ...api, prefixCls, hashId, })); return holder; }); // ============================================================================== // == Hook == // ============================================================================== export function useInternalNotification( notificationConfig?: HolderProps, ): readonly [NotificationInstance, React.ReactElement] { const holderRef = React.useRef(null); // ================================ API ================================ const wrapAPI = React.useMemo(() => { // Wrap with notification content // >>> Open const open = (config: ArgsProps) => { if (!holderRef.current) { warning( false, 'Notification', 'You are calling notice in render which will break in React 18 concurrent mode. Please trigger in effect instead.', ); return; } const { open: originOpen, prefixCls, hashId } = holderRef.current; const noticePrefixCls = `${prefixCls}-notice`; const { message, description, icon, type, btn, className, ...restConfig } = config; return originOpen({ placement: 'topRight', ...restConfig, content: ( ), className: classNames(type && `${noticePrefixCls}-${type}`, hashId, className), }); }; // >>> destroy const destroy = (key?: React.Key) => { if (key !== undefined) { holderRef.current?.close(key); } else { holderRef.current?.destroy(); } }; const clone = { open, destroy, } as NotificationInstance; const keys = ['success', 'info', 'warning', 'error'] as const; keys.forEach((type) => { clone[type] = (config) => open({ ...config, type, }); }); return clone; }, []); // ============================== Return =============================== return [ wrapAPI, , ] as const; } export default function useNotification(notificationConfig?: NotificationConfig) { return useInternalNotification(notificationConfig); }