forked from hangshuo652/aurak
0a9588abb7
- Add pagination support to findAll (page, limit query params) - Add findByTemplateId method to service - Add GET /by-template/:templateId endpoint to controller - Service already includes CRUD for QuestionBank and QuestionBankItem
86 lines
2.5 KiB
TypeScript
86 lines
2.5 KiB
TypeScript
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); // Wait for animation to complete
|
|
}, 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; |