50 lines
1.6 KiB
TypeScript
50 lines
1.6 KiB
TypeScript
import { useCallback, useEffect, useRef, useState } from 'react';
|
||
|
||
export interface ToastItem {
|
||
id: number;
|
||
message: string;
|
||
type: 'success' | 'error' | 'info';
|
||
}
|
||
|
||
interface ToastProps { toasts: ToastItem[]; }
|
||
|
||
export function Toast({ toasts }: ToastProps) {
|
||
return (
|
||
<div className="fixed bottom-4 left-1/2 -translate-x-1/2 z-[100] flex flex-col gap-2 items-center pointer-events-none">
|
||
{toasts.map(t => (
|
||
<div key={t.id}
|
||
className={`px-4 py-2 rounded-xl text-[12px] font-medium shadow-xl border animate-toast-in
|
||
${t.type === 'success' ? 'bg-green-500/20 text-green-300 border-green-500/30' :
|
||
t.type === 'error' ? 'bg-red-500/20 text-red-300 border-red-500/30' :
|
||
'bg-white/10 text-white/70 border-white/15'}`}>
|
||
{t.type === 'success' ? '✓ ' : t.type === 'error' ? '✕ ' : 'ℹ '}{t.message}
|
||
</div>
|
||
))}
|
||
</div>
|
||
);
|
||
}
|
||
|
||
let _toastId = 0;
|
||
|
||
export function useToast() {
|
||
const [toasts, setToasts] = useState<ToastItem[]>([]);
|
||
const timerRef = useRef<Map<number, ReturnType<typeof setTimeout>>>(new Map());
|
||
|
||
const showToast = useCallback((message: string, type: ToastItem['type'] = 'success', duration = 2500) => {
|
||
const id = ++_toastId;
|
||
setToasts(prev => [...prev, { id, message, type }]);
|
||
const timer = setTimeout(() => {
|
||
setToasts(prev => prev.filter(t => t.id !== id));
|
||
timerRef.current.delete(id);
|
||
}, duration);
|
||
timerRef.current.set(id, timer);
|
||
}, []);
|
||
|
||
useEffect(() => {
|
||
const timers = timerRef.current;
|
||
return () => { timers.forEach(t => clearTimeout(t)); };
|
||
}, []);
|
||
|
||
return { toasts, showToast };
|
||
}
|