Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 | /**
* @fileoverview Toast Notification Component
* @description Provides toast notifications for user feedback
* @module components/Toast
*/
import { useState, useEffect, createContext, useContext, useCallback } from 'react';
import './Toast.css';
const ToastContext = createContext();
/**
* Toast types and their icons
*/
const TOAST_TYPES = {
success: { icon: '✓', className: 'toast-success' },
error: { icon: '✕', className: 'toast-error' },
warning: { icon: '⚠', className: 'toast-warning' },
info: { icon: 'ℹ', className: 'toast-info' },
};
/**
* Individual Toast Component
*/
function ToastItem({ id, message, type, onClose }) {
const [isExiting, setIsExiting] = useState(false);
const config = TOAST_TYPES[type] || TOAST_TYPES.info;
useEffect(() => {
const timer = setTimeout(() => {
setIsExiting(true);
setTimeout(() => onClose(id), 300);
}, 3000);
return () => clearTimeout(timer);
}, [id, onClose]);
const handleClose = () => {
setIsExiting(true);
setTimeout(() => onClose(id), 300);
};
return (
<div className={`toast ${config.className} ${isExiting ? 'toast-exit' : ''}`}>
<span className="toast-icon">{config.icon}</span>
<span className="toast-message">{message}</span>
<button className="toast-close" onClick={handleClose}>×</button>
</div>
);
}
/**
* Toast Container Component
*/
function ToastContainer({ toasts, removeToast }) {
return (
<div className="toast-container">
{toasts.map(toast => (
<ToastItem
key={toast.id}
{...toast}
onClose={removeToast}
/>
))}
</div>
);
}
/**
* Toast Provider Component
*/
export function ToastProvider({ children }) {
const [toasts, setToasts] = useState([]);
const addToast = useCallback((message, type = 'info') => {
const id = Date.now() + Math.random();
setToasts(prev => [...prev, { id, message, type }]);
}, []);
const removeToast = useCallback((id) => {
setToasts(prev => prev.filter(toast => toast.id !== id));
}, []);
const toast = {
success: (msg) => addToast(msg, 'success'),
error: (msg) => addToast(msg, 'error'),
warning: (msg) => addToast(msg, 'warning'),
info: (msg) => addToast(msg, 'info'),
};
return (
<ToastContext.Provider value={toast}>
{children}
<ToastContainer toasts={toasts} removeToast={removeToast} />
</ToastContext.Provider>
);
}
/**
* Hook to use toast notifications
* @returns {Object} Toast methods (success, error, warning, info)
*/
export function useToast() {
const context = useContext(ToastContext);
if (!context) {
throw new Error('useToast must be used within a ToastProvider');
}
return context;
}
export default ToastProvider;
|