Files
ai-app-skr/components/Toast.tsx
2025-11-21 00:24:18 +08:00

64 lines
1.9 KiB
TypeScript

import React, { useEffect } from 'react';
import { CheckCircle, AlertCircle, X } from 'lucide-react';
export interface ToastMessage {
id: string;
type: 'success' | 'error' | 'info';
message: string;
}
interface ToastProps {
toasts: ToastMessage[];
onRemove: (id: string) => void;
}
const ToastContainer: React.FC<ToastProps> = ({ toasts, onRemove }) => {
return (
<div className="fixed top-4 left-1/2 transform -translate-x-1/2 z-[100] flex flex-col gap-2 w-full max-w-md px-4">
{toasts.map((toast) => (
<ToastItem key={toast.id} toast={toast} onRemove={onRemove} />
))}
</div>
);
};
const ToastItem: React.FC<{ toast: ToastMessage; onRemove: (id: string) => void }> = ({ toast, onRemove }) => {
useEffect(() => {
const timer = setTimeout(() => {
onRemove(toast.id);
}, 3000);
return () => clearTimeout(timer);
}, [toast.id, onRemove]);
const getStyles = () => {
switch (toast.type) {
case 'success':
return 'bg-emerald-50 border-emerald-100 text-emerald-700';
case 'error':
return 'bg-red-50 border-red-100 text-red-700';
default:
return 'bg-indigo-50 border-indigo-100 text-indigo-700';
}
};
const getIcon = () => {
switch (toast.type) {
case 'success': return <CheckCircle size={20} className="text-emerald-500" />;
case 'error': return <AlertCircle size={20} className="text-red-500" />;
default: return <AlertCircle size={20} className="text-indigo-500" />;
}
};
return (
<div className={`flex items-center gap-3 p-4 rounded-2xl border shadow-lg animate-fade-in-up ${getStyles()}`}>
{getIcon()}
<p className="text-sm font-bold flex-1">{toast.message}</p>
<button onClick={() => onRemove(toast.id)} className="opacity-50 hover:opacity-100">
<X size={16} />
</button>
</div>
);
};
export default ToastContainer;