You've already forked Nano-Banana-AI-Image-Editor
新增 全局错误toast提示;
This commit is contained in:
87
src/components/ToastContext.tsx
Normal file
87
src/components/ToastContext.tsx
Normal file
@@ -0,0 +1,87 @@
|
||||
import React, { createContext, useContext, useReducer, useState, useEffect } from 'react';
|
||||
import { Toast } from './Toast';
|
||||
|
||||
type ToastType = 'success' | 'error' | 'warning' | 'info';
|
||||
|
||||
export interface ToastMessage {
|
||||
id: string;
|
||||
message: string;
|
||||
type: ToastType;
|
||||
duration?: number;
|
||||
}
|
||||
|
||||
export interface ToastContextType {
|
||||
addToast: (message: string, type: ToastType, duration?: number) => void;
|
||||
removeToast: (id: string) => void;
|
||||
}
|
||||
|
||||
const ToastContext = createContext<ToastContextType | undefined>(undefined);
|
||||
|
||||
type ToastAction =
|
||||
| { type: 'ADD_TOAST'; payload: ToastMessage }
|
||||
| { type: 'REMOVE_TOAST'; payload: string };
|
||||
|
||||
const toastReducer = (state: ToastMessage[], action: ToastAction): ToastMessage[] => {
|
||||
switch (action.type) {
|
||||
case 'ADD_TOAST':
|
||||
return [...state, action.payload];
|
||||
case 'REMOVE_TOAST':
|
||||
return state.filter(toast => toast.id !== action.payload);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export const ToastProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
const [toasts, dispatch] = useReducer(toastReducer, []);
|
||||
|
||||
const addToast = (message: string, type: ToastType, duration: number = 5000) => {
|
||||
const id = Date.now().toString();
|
||||
dispatch({ type: 'ADD_TOAST', payload: { id, message, type, duration } });
|
||||
};
|
||||
|
||||
const removeToast = (id: string) => {
|
||||
dispatch({ type: 'REMOVE_TOAST', payload: id });
|
||||
};
|
||||
|
||||
// Auto remove toasts after duration
|
||||
useEffect(() => {
|
||||
const timers = toasts.map(toast => {
|
||||
if (toast.duration === 0) return; // 0 means persistent
|
||||
return setTimeout(() => {
|
||||
removeToast(toast.id);
|
||||
}, toast.duration);
|
||||
});
|
||||
|
||||
return () => {
|
||||
timers.forEach(timer => {
|
||||
if (timer) clearTimeout(timer);
|
||||
});
|
||||
};
|
||||
}, [toasts]);
|
||||
|
||||
return (
|
||||
<ToastContext.Provider value={{ addToast, removeToast }}>
|
||||
{children}
|
||||
<div className="fixed top-4 right-4 z-50 space-y-2">
|
||||
{toasts.map(toast => (
|
||||
<Toast
|
||||
key={toast.id}
|
||||
id={toast.id}
|
||||
message={toast.message}
|
||||
type={toast.type}
|
||||
onClose={removeToast}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</ToastContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useToast = () => {
|
||||
const context = useContext(ToastContext);
|
||||
if (!context) {
|
||||
throw new Error('useToast must be used within a ToastProvider');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
Reference in New Issue
Block a user