File size: 1,844 Bytes
6dd9bad | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | import { useContext } from 'react';
import { CheckCircle2, XCircle, Info, AlertTriangle, X } from 'lucide-react';
import { ToastContext, Toast, ToastType } from '@/hooks/useToast';
const ICONS: Record<ToastType, React.ReactNode> = {
success: <CheckCircle2 className="w-5 h-5 text-emerald-500 flex-shrink-0" />,
error: <XCircle className="w-5 h-5 text-red-500 flex-shrink-0" />,
info: <Info className="w-5 h-5 text-blue-500 flex-shrink-0" />,
warning: <AlertTriangle className="w-5 h-5 text-amber-500 flex-shrink-0" />,
};
const BG: Record<ToastType, string> = {
success: 'border-emerald-100 bg-white',
error: 'border-red-100 bg-white',
info: 'border-blue-100 bg-white',
warning: 'border-amber-100 bg-white',
};
function ToastItem({ toast, onRemove }: { toast: Toast; onRemove: () => void }) {
return (
<div className={`flex items-start gap-3 px-4 py-3 rounded-xl border shadow-lg text-sm font-medium text-slate-800 min-w-[280px] max-w-[400px] animate-[fadeSlideIn_0.2s_ease] ${BG[toast.type]}`}>
{ICONS[toast.type]}
<span className="flex-1 leading-snug">{toast.message}</span>
<button onClick={onRemove} className="text-slate-300 hover:text-slate-500 transition-colors flex-shrink-0 mt-0.5">
<X className="w-4 h-4" />
</button>
</div>
);
}
export function Toaster() {
const { toasts, removeToast } = useContext(ToastContext);
if (toasts.length === 0) return null;
return (
<div className="fixed top-5 right-5 z-[9999] flex flex-col gap-2 pointer-events-none">
{toasts.map(t => (
<div key={t.id} className="pointer-events-auto">
<ToastItem toast={t} onRemove={() => removeToast(t.id)} />
</div>
))}
</div>
);
}
|