Files
2025-12-26 16:28:41 +08:00

65 lines
2.1 KiB
TypeScript

import React, { useEffect, useState } from 'react';
export type ToastType = 'success' | 'error' | 'info';
export interface ToastProps {
message: string;
type: ToastType;
onClose: () => void;
}
export const Toast: React.FC<ToastProps> = ({ message, type, onClose }) => {
const [visible, setVisible] = useState(false);
useEffect(() => {
// Start entry animation
requestAnimationFrame(() => setVisible(true));
// Auto dismiss
const timer = setTimeout(() => {
setVisible(false);
// Allow exit animation to complete before unmounting
setTimeout(onClose, 300);
}, 3000);
return () => clearTimeout(timer);
}, [onClose]);
const baseClasses = "fixed top-6 left-1/2 transform -translate-x-1/2 z-[100] flex items-center gap-3 px-5 py-3 rounded-lg shadow-xl text-white font-medium text-sm transition-all duration-300 ease-out";
const typeClasses = {
success: "bg-emerald-600 ring-1 ring-emerald-500",
error: "bg-rose-600 ring-1 ring-rose-500",
info: "bg-indigo-600 ring-1 ring-indigo-500"
};
// Animation states
const opacityClass = visible ? "opacity-100 translate-y-0 scale-100" : "opacity-0 -translate-y-4 scale-95";
const icons = {
success: (
<svg className="w-5 h-5 text-emerald-100" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2.5" d="M5 13l4 4L19 7" />
</svg>
),
error: (
<svg className="w-5 h-5 text-rose-100" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2.5" d="M6 18L18 6M6 6l12 12" />
</svg>
),
info: (
<svg className="w-5 h-5 text-indigo-100" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2.5" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
)
};
return (
<div className={`${baseClasses} ${typeClasses[type]} ${opacityClass}`}>
{icons[type]}
<span className="drop-shadow-sm">{message}</span>
</div>
);
};