/**
* Enhanced Notification System
* Beautiful toast notifications with animations and queuing
*/
export class NotificationSystem {
constructor() {
this.container = null;
this.queue = [];
this.activeToasts = new Set();
this.maxToasts = 3;
this.init();
}
/**
* Initialize notification container
*/
init() {
if (!this.container) {
this.container = document.createElement('div');
this.container.id = 'notification-container';
this.container.className = 'notification-container';
this.container.setAttribute('aria-live', 'polite');
this.container.setAttribute('aria-atomic', 'true');
document.body.appendChild(this.container);
}
}
/**
* Show notification
* @param {Object} options - Notification options
*/
show(options = {}) {
const defaults = {
type: 'info', // 'success', 'error', 'warning', 'info'
title: '',
message: '',
duration: 4000,
closable: true,
icon: null,
action: null,
position: 'top-right' // 'top-right', 'top-left', 'bottom-right', 'bottom-left', 'top-center'
};
const config = { ...defaults, ...options };
// Queue if too many active toasts
if (this.activeToasts.size >= this.maxToasts) {
this.queue.push(config);
return;
}
this.createToast(config);
}
/**
* Create toast element
* @param {Object} config - Toast configuration
*/
createToast(config) {
const toast = document.createElement('div');
toast.className = `notification notification-${config.type}`;
toast.setAttribute('role', 'alert');
// Icon
const icon = this.getIcon(config.type, config.icon);
// Content
const content = `
${icon}
${config.title ? `
${config.title}
` : ''}
${config.message}
${config.action ? `
` : ''}
${config.closable ? `
` : ''}
`;
toast.innerHTML = content;
// Progress bar
if (config.duration > 0) {
const progress = document.createElement('div');
progress.className = 'notification-progress';
progress.style.animationDuration = `${config.duration}ms`;
toast.appendChild(progress);
}
// Add to container
this.container.appendChild(toast);
this.activeToasts.add(toast);
// Animate in
requestAnimationFrame(() => {
toast.classList.add('notification-show');
});
// Close button
if (config.closable) {
const closeBtn = toast.querySelector('.notification-close');
closeBtn.addEventListener('click', () => this.removeToast(toast));
}
// Auto remove
if (config.duration > 0) {
setTimeout(() => this.removeToast(toast), config.duration);
}
// Pause on hover
toast.addEventListener('mouseenter', () => {
const progress = toast.querySelector('.notification-progress');
if (progress) progress.style.animationPlayState = 'paused';
});
toast.addEventListener('mouseleave', () => {
const progress = toast.querySelector('.notification-progress');
if (progress) progress.style.animationPlayState = 'running';
});
}
/**
* Remove toast
* @param {HTMLElement} toast - Toast element
*/
removeToast(toast) {
if (!toast || !this.activeToasts.has(toast)) return;
toast.classList.remove('notification-show');
toast.classList.add('notification-hide');
setTimeout(() => {
if (toast.parentNode) {
toast.parentNode.removeChild(toast);
}
this.activeToasts.delete(toast);
// Process queue
if (this.queue.length > 0) {
const next = this.queue.shift();
this.createToast(next);
}
}, 300);
}
/**
* Get icon for notification type
* @param {string} type - Notification type
* @param {string} customIcon - Custom icon HTML
* @returns {string} Icon HTML
*/
getIcon(type, customIcon) {
if (customIcon) return customIcon;
const icons = {
success: `
`,
error: `
`,
warning: `
`,
info: `
`
};
return icons[type] || icons.info;
}
/**
* Shorthand methods
*/
success(message, title = 'Success', options = {}) {
this.show({ type: 'success', message, title, ...options });
}
error(message, title = 'Error', options = {}) {
this.show({ type: 'error', message, title, ...options });
}
warning(message, title = 'Warning', options = {}) {
this.show({ type: 'warning', message, title, ...options });
}
info(message, title = 'Info', options = {}) {
this.show({ type: 'info', message, title, ...options });
}
/**
* Clear all notifications
*/
clearAll() {
this.activeToasts.forEach(toast => this.removeToast(toast));
this.queue = [];
}
/**
* Inject styles
*/
static injectStyles() {
if (document.querySelector('#notification-system-styles')) return;
const style = document.createElement('style');
style.id = 'notification-system-styles';
style.textContent = `
.notification-container {
position: fixed;
top: 70px;
right: 20px;
z-index: 10000;
display: flex;
flex-direction: column;
gap: 12px;
max-width: 400px;
pointer-events: none;
}
.notification {
display: flex;
align-items: flex-start;
gap: 12px;
padding: 16px;
background: white;
border: 1px solid rgba(20, 184, 166, 0.15);
border-radius: 12px;
box-shadow: 0 8px 24px rgba(13, 115, 119, 0.12);
pointer-events: all;
opacity: 0;
transform: translateX(100%);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
overflow: hidden;
}
.notification-show {
opacity: 1;
transform: translateX(0);
}
.notification-hide {
opacity: 0;
transform: translateX(100%);
}
.notification-icon {
flex-shrink: 0;
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
}
.notification-success {
border-left: 4px solid #10b981;
}
.notification-success .notification-icon {
color: #10b981;
}
.notification-error {
border-left: 4px solid #ef4444;
}
.notification-error .notification-icon {
color: #ef4444;
}
.notification-warning {
border-left: 4px solid #f59e0b;
}
.notification-warning .notification-icon {
color: #f59e0b;
}
.notification-info {
border-left: 4px solid #22d3ee;
}
.notification-info .notification-icon {
color: #22d3ee;
}
.notification-content {
flex: 1;
min-width: 0;
}
.notification-title {
font-size: 14px;
font-weight: 600;
color: #0f2926;
margin-bottom: 4px;
}
.notification-message {
font-size: 13px;
color: #2a5f5a;
line-height: 1.5;
}
.notification-action {
margin-top: 8px;
padding: 4px 12px;
background: linear-gradient(135deg, #2dd4bf, #22d3ee);
color: white;
border: none;
border-radius: 6px;
font-size: 12px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
}
.notification-action:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(20, 184, 166, 0.3);
}
.notification-close {
flex-shrink: 0;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
background: transparent;
border: none;
color: #6bb8ae;
cursor: pointer;
border-radius: 6px;
transition: all 0.2s;
}
.notification-close:hover {
background: rgba(20, 184, 166, 0.1);
color: #14b8a6;
}
.notification-progress {
position: absolute;
bottom: 0;
left: 0;
height: 3px;
background: linear-gradient(90deg, #2dd4bf, #22d3ee);
animation: notificationProgress linear forwards;
}
@keyframes notificationProgress {
from { width: 100%; }
to { width: 0%; }
}
@media (max-width: 768px) {
.notification-container {
left: 12px;
right: 12px;
max-width: none;
}
.notification {
width: 100%;
}
}
[data-theme="dark"] .notification {
background: rgba(19, 46, 42, 0.95);
border-color: rgba(45, 212, 191, 0.25);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
}
[data-theme="dark"] .notification-title {
color: #f0fdfa;
}
[data-theme="dark"] .notification-message {
color: #99f6e4;
}
[data-theme="dark"] .notification-close {
color: #5eead4;
}
[data-theme="dark"] .notification-close:hover {
background: rgba(45, 212, 191, 0.15);
color: #2dd4bf;
}
`;
document.head.appendChild(style);
}
}
// Inject styles and create global instance
NotificationSystem.injectStyles();
const notifications = new NotificationSystem();
// Export as default and named
export default notifications;
export { notifications };