Hemang Thakur
deploy
d5c104e
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 <FaCheckCircle />;
case 'error':
return <FaExclamationCircle />;
case 'warning':
return <FaExclamationTriangle />;
case 'info':
return <FaInfoCircle />;
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 (
<div
className={getPositionClasses()}
style={containerStyle}
>
{autoStackCollapse && internalNotifications.length > 3 && (
<button
className="notification-collapse-toggle"
onClick={() => setCollapsed(!collapsed)}
>
{collapsed ? `Show ${internalNotifications.length} notifications` : 'Collapse'}
</button>
)}
<div className={`notification-list ${collapsed ? 'collapsed' : ''}`}>
{internalNotifications.map((notification, index) => (
<div
key={notification.id}
className={`notification notification-${notification.type || 'default'} ${getAnimationClass(index)} ${notification.className || ''}`}
style={{
'--animation-delay': `${index * 0.05}s`,
...notification.style
}}
>
{notification.showProgress && notification.duration && (
<div
className="notification-progress"
style={{
'--duration': `${notification.duration}ms`
}}
/>
)}
<div className="notification-content">
{(notification.icon !== false) && (
<div className="notification-icon">
{getIcon(notification.type, notification.icon)}
</div>
)}
<div className="notification-body">
{notification.title && (
<div className="notification-title">{notification.title}</div>
)}
{notification.message && (
<div className="notification-message">
{typeof notification.message === 'string'
? notification.message
: notification.message
}
</div>
)}
{notification.actions && notification.actions.length > 0 && (
<div className="notification-actions">
{notification.actions.map((action) => (
<button
key={action.id}
className={`notification-action ${action.className || ''}`}
onClick={() => handleAction(notification.id, action.id, action.data)}
style={action.style}
>
{action.label}
</button>
))}
</div>
)}
</div>
{notification.dismissible !== false && (
<button
className="notification-close"
onClick={() => handleDismiss(notification.id)}
aria-label="Dismiss notification"
>
<FaTimes />
</button>
)}
</div>
{notification.footer && (
<div className="notification-footer">
{notification.footer}
</div>
)}
</div>
))}
</div>
</div>
);
};
export default Notification;