57 lines
1.8 KiB
TypeScript
57 lines
1.8 KiB
TypeScript
import { createContext, useContext, useState, useCallback, ReactNode } from 'react';
|
|
import { X } from 'lucide-react';
|
|
|
|
type ToastType = 'success' | 'error' | 'info' | 'like';
|
|
|
|
interface ToastItem {
|
|
id: number;
|
|
message: string;
|
|
type: ToastType;
|
|
}
|
|
|
|
interface ToastContextType {
|
|
toast: (message: string, type?: ToastType) => void;
|
|
}
|
|
|
|
const ToastContext = createContext<ToastContextType>(null!);
|
|
let nextId = 0;
|
|
|
|
export function ToastProvider({ children }: { children: ReactNode }) {
|
|
const [toasts, setToasts] = useState<ToastItem[]>([]);
|
|
|
|
const addToast = useCallback((message: string, type: ToastType = 'info') => {
|
|
const id = nextId++;
|
|
setToasts((prev) => [...prev, { id, message, type }]);
|
|
setTimeout(() => setToasts((prev) => prev.filter((t) => t.id !== id)), 2500);
|
|
}, []);
|
|
|
|
const remove = (id: number) => setToasts((prev) => prev.filter((t) => t.id !== id));
|
|
|
|
return (
|
|
<ToastContext.Provider value={{ toast: addToast }}>
|
|
{children}
|
|
<div className="fixed bottom-5 right-5 z-[100] flex flex-col gap-2">
|
|
{toasts.map((t) => (
|
|
<div
|
|
key={t.id}
|
|
className={`animate-slide-up flex items-center gap-2.5 rounded-xl px-4 py-2.5 text-sm font-medium shadow-lg backdrop-blur-sm ${
|
|
t.type === 'error'
|
|
? 'bg-destructive text-destructive-foreground'
|
|
: 'bg-foreground/90 text-background'
|
|
}`}
|
|
>
|
|
<span>{t.message}</span>
|
|
<button onClick={() => remove(t.id)} className="opacity-60 hover:opacity-100 transition-opacity">
|
|
<X className="h-3.5 w-3.5" />
|
|
</button>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</ToastContext.Provider>
|
|
);
|
|
}
|
|
|
|
export function useToast() {
|
|
return useContext(ToastContext);
|
|
}
|