Files
accountbook/frontend/components/ToastProvider.js
T

56 lines
1.6 KiB
JavaScript
Raw Normal View History

2026-03-12 11:24:10 +08:00
import { createContext, useCallback, useContext, useMemo, useRef, useState } from 'react';
const ToastContext = createContext(null);
export function ToastProvider({ children }) {
const [toasts, setToasts] = useState([]);
const timers = useRef(new Map());
const removeToast = useCallback((id) => {
const timer = timers.current.get(id);
if (timer) {
clearTimeout(timer);
timers.current.delete(id);
}
setToasts((current) => current.filter((toast) => toast.id !== id));
}, []);
const showToast = useCallback((message, options = {}) => {
const id = `${Date.now()}-${Math.random()}`;
const toast = {
id,
message,
tone: options.tone || 'info',
};
setToasts((current) => [...current, toast]);
const duration = options.duration ?? 2600;
const timer = setTimeout(() => removeToast(id), duration);
timers.current.set(id, timer);
return id;
}, [removeToast]);
const value = useMemo(() => ({ showToast, removeToast }), [showToast, removeToast]);
return (
<ToastContext.Provider value={value}>
{children}
<div className="toast-stack" aria-live="polite" aria-atomic="true">
{toasts.map((toast) => (
<div key={toast.id} className={`toast toast--${toast.tone}`}>
<span className="toast__message">{toast.message}</span>
</div>
))}
</div>
</ToastContext.Provider>
);
}
export function useToast() {
const context = useContext(ToastContext);
if (!context) {
throw new Error('useToast must be used within ToastProvider');
}
return context;
}