Files
ICON-CRM/legacy/src/components/Toast.tsx
T
2026-09-14 16:55:32 +02:00

84 lines
3.5 KiB
TypeScript

import React, { useState, useCallback, createContext, useContext } from 'react';
type ToastType = 'success' | 'error' | 'info';
interface Toast {
id: number;
message: string;
type: ToastType;
}
interface ToastContextType {
addToast: (message: string, type?: ToastType) => void;
}
const ToastContext = createContext<ToastContextType>({ addToast: () => { } });
export const useToast = () => useContext(ToastContext);
let toastId = 0;
export const ToastProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [toasts, setToasts] = useState<Toast[]>([]);
const addToast = useCallback((message: string, type: ToastType = 'info') => {
const id = ++toastId;
setToasts(prev => [...prev, { id, message, type }]);
setTimeout(() => {
setToasts(prev => prev.filter(t => t.id !== id));
}, 4000);
}, []);
const removeToast = (id: number) => {
setToasts(prev => prev.filter(t => t.id !== id));
};
const icons: Record<ToastType, string> = {
success: 'M5 13l4 4L19 7',
error: 'M6 18L18 6M6 6l12 12',
info: 'M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z',
};
const colors: Record<ToastType, string> = {
success: 'bg-emerald-500',
error: 'bg-red-500',
info: 'bg-blue-500',
};
const bgColors: Record<ToastType, string> = {
success: 'bg-emerald-50 dark:bg-emerald-900/20 border-emerald-200 dark:border-emerald-800',
error: 'bg-red-50 dark:bg-red-900/20 border-red-200 dark:border-red-800',
info: 'bg-blue-50 dark:bg-blue-900/20 border-blue-200 dark:border-blue-800',
};
return (
<ToastContext.Provider value={{ addToast }}>
{children}
<div className="fixed bottom-6 right-6 z-[200] flex flex-col gap-3 pointer-events-none">
{toasts.map(toast => (
<div
key={toast.id}
className={`pointer-events-auto flex items-center gap-3 px-5 py-3.5 rounded-2xl shadow-xl border backdrop-blur-sm animate-in slide-in-from-right-5 fade-in duration-300 min-w-[300px] max-w-[420px] ${bgColors[toast.type]}`}
>
<div className={`w-7 h-7 rounded-lg ${colors[toast.type]} flex items-center justify-center shrink-0`}>
<svg className="w-4 h-4 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d={icons[toast.type]} />
</svg>
</div>
<p className="text-sm font-semibold text-slate-800 dark:text-slate-100 flex-1">{toast.message}</p>
<button
onClick={() => removeToast(toast.id)}
className="p-1 text-slate-400 hover:text-slate-600 dark:hover:text-white shrink-0"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
))}
</div>
</ToastContext.Provider>
);
};