import React, { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, } from 'react' /** Kurze Meldungen ohne OK-Dialog; echte Bestätigungen erfolgen gesondert (modale Wahlmöglichkeiten). */ const ToastContext = createContext(null) function nextId() { return `toast-${Date.now()}-${Math.random().toString(36).slice(2, 7)}` } export function ToastProvider({ children }) { const [items, setItems] = useState([]) const timers = useRef(new Map()) const removeToast = useCallback((id) => { const t = timers.current.get(id) if (t) { window.clearTimeout(t) timers.current.delete(id) } setItems((prev) => prev.filter((x) => x.id !== id)) }, []) const pushToast = useCallback( (message, { variant = 'info', duration = 3200, id: forcedId } = {}) => { const id = forcedId || nextId() setItems((prev) => [...prev, { id, message: String(message || '').trim(), variant }]) const ms = variant === 'error' ? Math.max(duration, 5200) : Math.max(duration, 2400) const handle = window.setTimeout(() => removeToast(id), ms) timers.current.set(id, handle) return id }, [removeToast], ) useEffect( () => () => { timers.current.forEach((h) => window.clearTimeout(h)) timers.current.clear() }, [], ) const api = useMemo( () => ({ /** Erfolgs- und Hinweis-Meldungen (auto-dismiss). */ show: pushToast, success: (msg, opts) => pushToast(msg, { ...opts, variant: 'success' }), info: (msg, opts) => pushToast(msg, { ...opts, variant: 'info' }), error: (msg, opts) => pushToast(msg, { ...opts, variant: 'error' }), dismiss: removeToast, }), [pushToast, removeToast], ) return ( {children}
{items.map((t) => ( ))}
) } export function useToast() { const ctx = useContext(ToastContext) if (!ctx) { throw new Error('useToast muss innerhalb von ToastProvider verwendet werden.') } return ctx }