65 lines
1.7 KiB
TypeScript
65 lines
1.7 KiB
TypeScript
'use client';
|
|
|
|
import { CheckCircle, Info, X,XCircle } from 'lucide-react';
|
|
import { useEffect, useState } from 'react';
|
|
|
|
export interface ToastProps {
|
|
message: string;
|
|
type?: 'success' | 'error' | 'info';
|
|
duration?: number;
|
|
onClose?: () => void;
|
|
}
|
|
|
|
export default function Toast({ message, type = 'info', duration = 3000, onClose }: ToastProps) {
|
|
const [isVisible, setIsVisible] = useState(true);
|
|
|
|
useEffect(() => {
|
|
const timer = setTimeout(() => {
|
|
setIsVisible(false);
|
|
setTimeout(() => {
|
|
onClose?.();
|
|
}, 300); // 等待动画完成
|
|
}, duration);
|
|
|
|
return () => clearTimeout(timer);
|
|
}, [duration, onClose]);
|
|
|
|
const handleClose = () => {
|
|
setIsVisible(false);
|
|
setTimeout(() => {
|
|
onClose?.();
|
|
}, 300);
|
|
};
|
|
|
|
const icons = {
|
|
success: <CheckCircle className="w-5 h-5" />,
|
|
error: <XCircle className="w-5 h-5" />,
|
|
info: <Info className="w-5 h-5" />,
|
|
};
|
|
|
|
const colors = {
|
|
success: 'bg-green-500/90',
|
|
error: 'bg-red-500/90',
|
|
info: 'bg-blue-500/90',
|
|
};
|
|
|
|
return (
|
|
<div
|
|
className={`fixed top-20 left-1/2 -translate-x-1/2 z-[9999] transition-all duration-300 ${
|
|
isVisible ? 'opacity-100 translate-y-0' : 'opacity-0 -translate-y-4'
|
|
}`}
|
|
>
|
|
<div className={`${colors[type]} text-white px-6 py-3 rounded-lg shadow-lg flex items-center gap-3 min-w-[300px]`}>
|
|
<div className="flex-shrink-0">{icons[type]}</div>
|
|
<div className="flex-1 text-sm font-medium">{message}</div>
|
|
<button
|
|
onClick={handleClose}
|
|
className="flex-shrink-0 hover:bg-white/20 rounded p-1 transition-colors"
|
|
>
|
|
<X className="w-4 h-4" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|