import { IconX as X } from '@tabler/icons-react' import { createContext, type ReactNode, useCallback, useContext, useMemo, useRef, useState } from 'react' export interface ToastAction { label: string onClick: () => void } export interface ToastInput { title: string description?: string action?: ToastAction /** 毫秒;0 表示不自动关闭。 */ timeout?: number } interface Toast extends ToastInput { id: number } interface ToastApi { push(input: ToastInput): number dismiss(id: number): void } const ToastContext = createContext(null) export function ToastProvider({ children }: { children: ReactNode }) { const [items, setItems] = useState([]) const seq = useRef(0) const timers = useRef(new Map()) const dismiss = useCallback((id: number) => { setItems((current) => current.filter((item) => item.id !== id)) const timer = timers.current.get(id) if (timer) { window.clearTimeout(timer) timers.current.delete(id) } }, []) const push = useCallback( (input: ToastInput) => { seq.current += 1 const id = seq.current setItems((current) => [...current.slice(-4), { ...input, id }]) const timeout = input.timeout ?? 6_000 if (timeout > 0) { const timer = window.setTimeout(() => dismiss(id), timeout) timers.current.set(id, timer) } return id }, [dismiss], ) const value = useMemo(() => ({ push, dismiss }), [push, dismiss]) return ( {children}
{items.map((item) => (

{item.title}

{item.description ? (

{item.description}

) : null} {item.action ? ( ) : null}
))}
) } const NOOP: ToastApi = { push: () => 0, dismiss: () => {} } export function useToast(): ToastApi { return useContext(ToastContext) ?? NOOP }