109 lines
2.5 KiB
TypeScript
109 lines
2.5 KiB
TypeScript
import React, { useEffect } from 'react';
|
||
|
||
export type ToastType = 'success' | 'error' | 'info' | 'warning';
|
||
|
||
interface ToastProps {
|
||
message: string;
|
||
type?: ToastType;
|
||
isVisible: boolean;
|
||
onClose: () => void;
|
||
duration?: number;
|
||
}
|
||
|
||
const toastConfig = {
|
||
success: { bg: '#d1fae5', color: '#065f46', icon: '✓' },
|
||
error: { bg: '#fee2e2', color: '#991b1b', icon: '✗' },
|
||
info: { bg: '#dbeafe', color: '#1e40af', icon: 'ℹ' },
|
||
warning: { bg: '#fef3c7', color: '#92400e', icon: '⚠' },
|
||
};
|
||
|
||
export function Toast({ message, type = 'info', isVisible, onClose, duration = 5000 }: ToastProps) {
|
||
useEffect(() => {
|
||
if (isVisible && duration > 0) {
|
||
const timer = setTimeout(onClose, duration);
|
||
return () => clearTimeout(timer);
|
||
}
|
||
}, [isVisible, duration, onClose]);
|
||
|
||
if (!isVisible) return null;
|
||
|
||
const config = toastConfig[type];
|
||
|
||
return (
|
||
<div
|
||
style={{
|
||
position: 'fixed',
|
||
bottom: 'var(--spacing-6)',
|
||
right: 'var(--spacing-6)',
|
||
background: config.bg,
|
||
color: config.color,
|
||
padding: 'var(--spacing-4) var(--spacing-5)',
|
||
borderRadius: 'var(--border-radius-lg)',
|
||
boxShadow: 'var(--shadow-lg)',
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
gap: 'var(--spacing-3)',
|
||
maxWidth: '400px',
|
||
zIndex: 2000,
|
||
}}
|
||
className="animate-slide-up"
|
||
>
|
||
<span
|
||
style={{
|
||
fontSize: 'var(--font-size-lg)',
|
||
fontWeight: 700,
|
||
}}
|
||
>
|
||
{config.icon}
|
||
</span>
|
||
<span
|
||
style={{
|
||
fontSize: 'var(--font-size-sm)',
|
||
fontWeight: 600,
|
||
flex: 1,
|
||
}}
|
||
>
|
||
{message}
|
||
</span>
|
||
<button
|
||
onClick={onClose}
|
||
style={{
|
||
background: 'transparent',
|
||
border: 'none',
|
||
color: config.color,
|
||
fontSize: 'var(--font-size-lg)',
|
||
cursor: 'pointer',
|
||
padding: '0',
|
||
lineHeight: 1,
|
||
opacity: 0.7,
|
||
}}
|
||
>
|
||
×
|
||
</button>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
// Hook for managing toast state
|
||
export function useToast() {
|
||
const [toast, setToast] = React.useState<{
|
||
message: string;
|
||
type: ToastType;
|
||
isVisible: boolean;
|
||
}>({
|
||
message: '',
|
||
type: 'info',
|
||
isVisible: false,
|
||
});
|
||
|
||
const showToast = (message: string, type: ToastType = 'info') => {
|
||
setToast({ message, type, isVisible: true });
|
||
};
|
||
|
||
const hideToast = () => {
|
||
setToast((prev) => ({ ...prev, isVisible: false }));
|
||
};
|
||
|
||
return { toast, showToast, hideToast };
|
||
}
|