97 lines
3 KiB
TypeScript
97 lines
3 KiB
TypeScript
import { createContext, useContext, useState, useCallback, useRef, useEffect } from 'react';
|
|
import { ToastContainer, ToastData, ToastType } from '../components/UiComponents/Toast';
|
|
|
|
interface ToastOptions {
|
|
title: string;
|
|
message?: string;
|
|
duration?: number;
|
|
}
|
|
|
|
interface ToastContextValue {
|
|
showToast: (type: ToastType, options: ToastOptions) => void;
|
|
showSuccess: (title: string, message?: string) => void;
|
|
showError: (title: string, message?: string) => void;
|
|
showWarning: (title: string, message?: string) => void;
|
|
showInfo: (title: string, message?: string) => void;
|
|
closeToast: (id: string) => void;
|
|
}
|
|
|
|
const ToastContext = createContext<ToastContextValue | null>(null);
|
|
|
|
const DEFAULT_DURATION = 5000;
|
|
|
|
export const ToastProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
|
const [toasts, setToasts] = useState<ToastData[]>([]);
|
|
const timeoutRefs = useRef<Map<string, NodeJS.Timeout>>(new Map());
|
|
|
|
const closeToast = useCallback((id: string) => {
|
|
// Clear timeout if exists
|
|
const timeout = timeoutRefs.current.get(id);
|
|
if (timeout) {
|
|
clearTimeout(timeout);
|
|
timeoutRefs.current.delete(id);
|
|
}
|
|
setToasts((prev) => prev.filter((toast) => toast.id !== id));
|
|
}, []);
|
|
|
|
const showToast = useCallback((type: ToastType, options: ToastOptions) => {
|
|
const id = `toast-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
const duration = options.duration ?? DEFAULT_DURATION;
|
|
|
|
const newToast: ToastData = {
|
|
id,
|
|
type,
|
|
title: options.title,
|
|
message: options.message,
|
|
duration,
|
|
};
|
|
|
|
setToasts((prev) => [...prev, newToast]);
|
|
|
|
// Auto-close after duration
|
|
if (duration > 0) {
|
|
const timeout = setTimeout(() => {
|
|
closeToast(id);
|
|
}, duration);
|
|
timeoutRefs.current.set(id, timeout);
|
|
}
|
|
}, [closeToast]);
|
|
|
|
const showSuccess = useCallback((title: string, message?: string) => {
|
|
showToast('success', { title, message });
|
|
}, [showToast]);
|
|
|
|
const showError = useCallback((title: string, message?: string) => {
|
|
showToast('error', { title, message, duration: 8000 }); // Errors stay longer
|
|
}, [showToast]);
|
|
|
|
const showWarning = useCallback((title: string, message?: string) => {
|
|
showToast('warning', { title, message, duration: 6000 });
|
|
}, [showToast]);
|
|
|
|
const showInfo = useCallback((title: string, message?: string) => {
|
|
showToast('info', { title, message });
|
|
}, [showToast]);
|
|
|
|
// Cleanup timeouts on unmount
|
|
useEffect(() => {
|
|
return () => {
|
|
timeoutRefs.current.forEach((timeout) => clearTimeout(timeout));
|
|
};
|
|
}, []);
|
|
|
|
return (
|
|
<ToastContext.Provider value={{ showToast, showSuccess, showError, showWarning, showInfo, closeToast }}>
|
|
{children}
|
|
<ToastContainer toasts={toasts} onClose={closeToast} />
|
|
</ToastContext.Provider>
|
|
);
|
|
};
|
|
|
|
export const useToast = (): ToastContextValue => {
|
|
const context = useContext(ToastContext);
|
|
if (!context) {
|
|
throw new Error('useToast must be used within a ToastProvider');
|
|
}
|
|
return context;
|
|
};
|