Initial commit: accountbook project
- Node.js backend server - Frontend application - Backup script - Project specification Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
import { createContext, useCallback, useContext, useMemo, useRef, useState } from 'react';
|
||||
import { Icon } from './icons';
|
||||
|
||||
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__icon">
|
||||
<Icon name={toast.tone === 'success' ? 'check' : toast.tone === 'error' ? 'close' : 'info'} size={16} />
|
||||
</span>
|
||||
<span className="toast__message">{toast.message}</span>
|
||||
<button type="button" className="icon-button icon-button--ghost toast__close" aria-label="关闭提示" onClick={() => removeToast(toast.id)}>
|
||||
<Icon name="close" size={16} />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</ToastContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useToast() {
|
||||
const context = useContext(ToastContext);
|
||||
if (!context) {
|
||||
throw new Error('useToast must be used within ToastProvider');
|
||||
}
|
||||
return context;
|
||||
}
|
||||
Reference in New Issue
Block a user