Husr's picture
first commit
d988ae4
'use client';
import React, { createContext, useContext, useState, useCallback } from 'react';
import { AlertCircle, CheckCircle, Info, X, AlertTriangle } from 'lucide-react';
type ToastType = 'success' | 'error' | 'info' | 'warning';
interface Toast {
id: string;
message: string;
type: ToastType;
title?: string;
}
interface ToastContextType {
toasts: Toast[];
addToast: (message: string, type: ToastType, title?: string) => void;
removeToast: (id: string) => void;
}
const ToastContext = createContext<ToastContextType | undefined>(undefined);
export const useToast = () => {
const context = useContext(ToastContext);
if (!context) {
throw new Error('useToast must be used within a ToastProvider');
}
return context;
};
export const ToastProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [toasts, setToasts] = useState<Toast[]>([]);
const addToast = useCallback((message: string, type: ToastType, title?: string) => {
const id = Math.random().toString(36).substring(2, 9);
setToasts((prev) => [...prev, { id, message, type, title }]);
// Auto-remove toast after 5 seconds
setTimeout(() => {
removeToast(id);
}, 5000);
}, []);
const removeToast = useCallback((id: string) => {
setToasts((prev) => prev.filter((toast) => toast.id !== id));
}, []);
return (
<ToastContext.Provider value={{ toasts, addToast, removeToast }}>
{children}
<ToastContainer toasts={toasts} removeToast={removeToast} />
</ToastContext.Provider>
);
};
const getToastIcon = (type: ToastType) => {
switch (type) {
case 'success':
return <CheckCircle className="h-5 w-5 text-white" />;
case 'error':
return <AlertCircle className="h-5 w-5 text-white" />;
case 'warning':
return <AlertTriangle className="h-5 w-5 text-white" />;
case 'info':
return <Info className="h-5 w-5 text-white" />;
}
};
const getToastColor = (type: ToastType) => {
switch (type) {
case 'success':
return 'bg-success';
case 'error':
return 'bg-error';
case 'warning':
return 'bg-warning';
case 'info':
return 'bg-info';
}
};
const ToastContainer: React.FC<{ toasts: Toast[]; removeToast: (id: string) => void }> = ({
toasts,
removeToast,
}) => {
if (toasts.length === 0) return null;
return (
<div className="fixed top-4 right-4 z-50 flex flex-col gap-2 max-w-md">
{toasts.map((toast) => (
<div
key={toast.id}
className={`${getToastColor(toast.type)} text-white p-4 rounded-lg shadow-lg flex items-start animate-slideIn`}
>
<div className="mr-3 flex-shrink-0 mt-0.5">
{getToastIcon(toast.type)}
</div>
<div className="flex-1 mr-2">
{toast.title && <p className="font-medium">{toast.title}</p>}
<p className="text-sm opacity-90">{toast.message}</p>
</div>
<button
onClick={() => removeToast(toast.id)}
className="text-white/80 hover:text-white transition-colors"
>
<X className="h-5 w-5" />
</button>
</div>
))}
</div>
);
};