import React, { useState, useEffect, useRef, useCallback } from 'react'; import { FaTimes, FaCheckCircle, FaExclamationCircle, FaInfoCircle, FaExclamationTriangle } from 'react-icons/fa'; import './Notification.css'; const Notification = ({ notifications = [], position = 'top-right', animation = 'slide', stackDirection = 'down', maxNotifications = 5, spacing = 10, offset = { x: 20, y: 20 }, onDismiss, onAction, autoStackCollapse = false, theme = 'light' }) => { const [internalNotifications, setInternalNotifications] = useState([]); const [collapsed, setCollapsed] = useState(false); const timersRef = useRef({}); const handleDismiss = useCallback((id) => { if (timersRef.current[id]) { clearTimeout(timersRef.current[id]); delete timersRef.current[id]; } onDismiss?.(id); }, [onDismiss]); useEffect(() => { // Update internal notifications const processedNotifications = notifications.slice( stackDirection === 'up' ? -maxNotifications : 0, stackDirection === 'up' ? undefined : maxNotifications ); setInternalNotifications(processedNotifications); // Keep track of current timer IDs for this effect const currentTimerIds = []; // Set up auto-dismiss timers processedNotifications.forEach(notification => { if (notification.autoDismiss && notification.duration && !timersRef.current[notification.id]) { const timerId = setTimeout(() => { handleDismiss(notification.id); }, notification.duration); timersRef.current[notification.id] = timerId; currentTimerIds.push(notification.id); } }); // Cleanup function return () => { // Use the captured timer IDs and current ref const timers = timersRef.current; // Clear timers for notifications that were removed Object.keys(timers).forEach(id => { if (!processedNotifications.find(n => n.id === id)) { clearTimeout(timers[id]); delete timers[id]; } }); }; }, [notifications, maxNotifications, stackDirection, handleDismiss]); const handleAction = (notificationId, actionId, actionData) => { onAction?.(notificationId, actionId, actionData); }; const getIcon = (type, customIcon) => { if (customIcon) return customIcon; switch (type) { case 'success': return ; case 'error': return ; case 'warning': return ; case 'info': return ; default: return null; } }; const getPositionClasses = () => { const classes = ['notification-container']; // Position classes switch (position) { case 'top-left': classes.push('position-top-left'); break; case 'top-center': classes.push('position-top-center'); break; case 'top-right': classes.push('position-top-right'); break; case 'bottom-left': classes.push('position-bottom-left'); break; case 'bottom-center': classes.push('position-bottom-center'); break; case 'bottom-right': classes.push('position-bottom-right'); break; case 'center': classes.push('position-center'); break; default: classes.push('position-top-right'); } // Stack direction if (stackDirection === 'up') { classes.push('stack-up'); } // Theme classes.push(`theme-${theme}`); return classes.join(' '); }; const getAnimationClass = (index) => { return `animation-${animation} animation-${animation}-${index}`; }; const containerStyle = { '--spacing': `${spacing}px`, '--offset-x': `${offset.x}px`, '--offset-y': `${offset.y}px`, }; if (internalNotifications.length === 0) return null; return (
{autoStackCollapse && internalNotifications.length > 3 && ( )}
{internalNotifications.map((notification, index) => (
{notification.showProgress && notification.duration && (
)}
{(notification.icon !== false) && (
{getIcon(notification.type, notification.icon)}
)}
{notification.title && (
{notification.title}
)} {notification.message && (
{typeof notification.message === 'string' ? notification.message : notification.message }
)} {notification.actions && notification.actions.length > 0 && (
{notification.actions.map((action) => ( ))}
)}
{notification.dismissible !== false && ( )}
{notification.footer && (
{notification.footer}
)}
))}
); }; export default Notification;