import React, { useState, useEffect, useRef, useCallback } from "react"; import classNames from "classnames"; import { StyledProps } from "../_type"; import { useDefault } from "../_util/use-default"; import { Icon } from "../icon"; import { FadeTransition } from "../transition"; import { useConfig } from "../_util/config-context"; import { Button } from "../button"; import { AlertNotice } from "./AlertNotice"; import { useTranslation } from "../i18n"; import { forwardRefWithStatics } from "../_util/forward-ref-with-statics"; import { callBoth } from "../util"; import { uuid } from "../_util/uuid"; export interface AlertProps extends StyledProps { /** * 提示类型 * @default "info" */ type?: "info" | "success" | "warning" | "error"; /** * 提示内容 */ children?: React.ReactNode; /** * 控制 Alert 是否显示,如果没传,默认显示 * 配合 `onClose` 回调可以实现关闭效果 */ visible?: boolean; /** * 默认是否显示。如果不想自己控制 Alert 的显示状态,可以传入 defaultVisible 为 true,此时会渲染关闭图标,并且用户点击关闭时隐藏。 * @default true */ defaultVisible?: boolean; /** * 传入 visible 或者 defaultVisible 都会渲染关闭图标,用户点击关闭图标时回调 onClose */ onClose?: () => void; /** * 头部右侧渲染内容 */ extra?: React.ReactNode; /** * 隐藏图标 * @default false * @since 2.3.6 */ hideIcon?: boolean; /** * 轮播模式 * @default false */ carouselMode?: boolean; /** * 轮播时间间隔 * @default 5000 * @since 2.3.0 */ interval?: number; } const iconMap = { info: "infoblue", success: "success", warning: "warning", error: "error", }; export const Alert = forwardRefWithStatics( function Alert( { type, children, className, onClose, extra, hideIcon, carouselMode, interval = 5000, visible: _visible, defaultVisible: _defaultVisible, ...props }: AlertProps, ref: React.Ref ) { const { classPrefix } = useConfig(); const t = useTranslation(); const [visible, onVisibleChange] = useDefault( _visible, _defaultVisible, visible => !visible && onClose && onClose() ); const isClosable = typeof visible === "boolean"; const timerRef = useRef(null); const [currentIndex, setCurrentIndex] = useState(0); const { length } = React.Children.toArray(children).filter(Boolean); const circulate = useCallback(() => { if (!carouselMode) { return; } if (timerRef.current) { clearTimeout(timerRef.current); } timerRef.current = setTimeout(() => { setCurrentIndex(i => (i + 1) % length); circulate(); }, interval); }, [carouselMode, interval, length]); useEffect(() => { circulate(); return () => clearTimeout(timerRef.current); }, [circulate]); const [id, setId] = useState(uuid()); useEffect(() => { setId(uuid()); }, [children]); const alertClassName = classNames( `${classPrefix}-alert`, { [`${classPrefix}-alert--${type}`]: type, }, className ); const alertElement = (children: React.ReactNode) => (
{ if (timerRef.current) { clearTimeout(timerRef.current); } }, (props as any)?.onMouseEnter)} onMouseLeave={callBoth(circulate, (props as any)?.onMouseLeave)} > {!hideIcon && ( )}
{children}
{extra} {carouselMode && ( { setCurrentIndex(index); circulate(); }} /> )} {isClosable && (
); const content = !carouselMode ? alertElement(children) : alertElement( React.Children.toArray(children) .filter(Boolean) .map((c, i) => (
{c}
)) ); if (isClosable) { return {content}; } return content; }, { Notice: AlertNotice, } ); Alert.displayName = "Alert"; function Dot({ classPrefix, total, currentIndex, onChange, }: { classPrefix: string; total: number; currentIndex: number; onChange: (index: number) => void; }) { return (
{Array(total) .fill(null) .map((_, i) => ( onChange(i)} /> ))}
); }