| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586 |
- import React, { useEffect, useState } from 'react';
- import { CheckCircle, AlertCircle, XCircle, Info, X } from 'lucide-react';
- export type ToastType = 'success' | 'error' | 'warning' | 'info';
- export interface ToastProps {
- type: ToastType;
- title?: string;
- message: string;
- duration?: number;
- onClose: () => void;
- }
- const Toast: React.FC<ToastProps> = ({ type, title, message, duration = 5000, onClose }) => {
- const [isVisible, setIsVisible] = useState(true);
- useEffect(() => {
- const timer = setTimeout(() => {
- setIsVisible(false);
- setTimeout(onClose, 300); // 等待动画完成
- }, duration);
- return () => clearTimeout(timer);
- }, [duration, onClose]);
- const getIcon = () => {
- switch (type) {
- case 'success':
- return <CheckCircle className="w-5 h-5 text-green-500" />;
- case 'error':
- return <XCircle className="w-5 h-5 text-red-500" />;
- case 'warning':
- return <AlertCircle className="w-5 h-5 text-yellow-500" />;
- case 'info':
- return <Info className="w-5 h-5 text-blue-500" />;
- }
- };
- const getStyles = () => {
- switch (type) {
- case 'success':
- return 'bg-green-50 border-green-200 text-green-800';
- case 'error':
- return 'bg-red-50 border-red-200 text-red-800';
- case 'warning':
- return 'bg-yellow-50 border-yellow-200 text-yellow-800';
- case 'info':
- return 'bg-blue-50 border-blue-200 text-blue-800';
- }
- };
- return (
- <div
- className={`relative w-full transition-all duration-300 ease-in-out ${isVisible ? 'translate-x-0 opacity-100 max-h-40 mb-3' : 'translate-x-full opacity-0 max-h-0 mb-0 overflow-hidden'
- }`}
- role="alert"
- aria-live="polite"
- >
- <div className={`rounded-lg border shadow-lg p-4 ${getStyles()}`}>
- <div className="flex items-start gap-3">
- <div className="flex-shrink-0 mt-0.5">
- {getIcon()}
- </div>
- <div className="flex-1 min-w-0">
- {title && (
- <p className="text-sm font-semibold mb-1">{title}</p>
- )}
- <p className="text-sm break-words">{message}</p>
- </div>
- <button
- onClick={() => {
- setIsVisible(false);
- setTimeout(onClose, 300);
- }}
- className="flex-shrink-0 ml-2 text-gray-400 hover:text-gray-600 transition-colors"
- aria-label="Close"
- >
- <X className="w-4 h-4" />
- </button>
- </div>
- </div>
- </div>
- );
- };
- export default Toast;
|