Spaces:
Sleeping
Sleeping
| import { useState, useCallback, useEffect } from 'react'; | |
| import { createPortal } from 'react-dom'; | |
| import Toast, { ToastType } from './Toast'; | |
| export interface ToastItem { | |
| id: string; | |
| message: string; | |
| type: ToastType; | |
| duration?: number; | |
| } | |
| let toastCounter = 0; | |
| let addToastExternal: ((message: string, type: ToastType, duration?: number) => void) | null = null; | |
| export const showToast = (message: string, type: ToastType = 'info', duration: number = 3000) => { | |
| if (addToastExternal) { | |
| addToastExternal(message, type, duration); | |
| } else { | |
| console.warn('ToastContainer not mounted'); | |
| } | |
| }; | |
| const ToastContainer = () => { | |
| const [toasts, setToasts] = useState<ToastItem[]>([]); | |
| const addToast = useCallback((message: string, type: ToastType, duration?: number) => { | |
| const id = `toast-${Date.now()}-${toastCounter++}`; | |
| setToasts(prev => [...prev, { id, message, type, duration }]); | |
| }, []); | |
| const removeToast = useCallback((id: string) => { | |
| setToasts(prev => prev.filter(t => t.id !== id)); | |
| }, []); | |
| useEffect(() => { | |
| addToastExternal = addToast; | |
| return () => { | |
| addToastExternal = null; | |
| }; | |
| }, [addToast]); | |
| return createPortal( | |
| <div className="fixed top-24 right-8 z-[200] flex flex-col gap-3 pointer-events-none"> | |
| <div className="flex flex-col gap-3 pointer-events-auto"> | |
| {toasts.map(toast => ( | |
| <Toast | |
| key={toast.id} | |
| {...toast} | |
| onClose={removeToast} | |
| /> | |
| ))} | |
| </div> | |
| </div>, | |
| document.body | |
| ); | |
| }; | |
| export default ToastContainer; | |