/** * Toast Notification System * Displays temporary notification messages */ import { CONFIG } from '../core/config.js'; export class Toast { static container = null; static toasts = []; static maxToasts = CONFIG.TOAST.MAX_VISIBLE; /** * Initialize toast container */ static init() { if (this.container) return; this.container = document.getElementById('toast-container'); if (!this.container) { this.container = document.createElement('div'); this.container.id = 'toast-container'; this.container.className = 'toast-container'; document.body.appendChild(this.container); } } /** * Show a toast notification */ static show(message, type = 'info', options = {}) { this.init(); const toast = { id: Date.now() + Math.random(), message, type, duration: options.duration || (type === 'error' ? CONFIG.TOAST.ERROR_DURATION : CONFIG.TOAST.DEFAULT_DURATION), dismissible: options.dismissible !== false, action: options.action || null, }; // Remove oldest toast if at max if (this.toasts.length >= this.maxToasts) { const oldest = this.toasts.shift(); this.dismiss(oldest.id); } this.toasts.push(toast); this.render(toast); // Auto-dismiss if (toast.duration > 0) { setTimeout(() => this.dismiss(toast.id), toast.duration); } return toast.id; } /** * Render toast element */ static render(toast) { const el = document.createElement('div'); el.className = `toast toast-${toast.type}`; el.setAttribute('data-toast-id', toast.id); el.setAttribute('role', 'alert'); el.setAttribute('aria-live', 'polite'); const icon = this.getIcon(toast.type); el.innerHTML = `
${icon}
${this.escapeHtml(toast.message)}
${toast.action ? `` : ''}
${toast.dismissible ? '' : ''} ${toast.duration > 0 ? `
` : ''} `; // Close button handler if (toast.dismissible) { const closeBtn = el.querySelector('.toast-close'); closeBtn.addEventListener('click', () => this.dismiss(toast.id)); } // Action button handler if (toast.action) { const actionBtn = el.querySelector('.toast-action'); actionBtn.addEventListener('click', () => { toast.action.callback(); this.dismiss(toast.id); }); } this.container.appendChild(el); // Trigger animation setTimeout(() => el.classList.add('toast-show'), 10); } /** * Dismiss a toast */ static dismiss(toastId) { const el = this.container.querySelector(`[data-toast-id="${toastId}"]`); if (!el) return; el.classList.remove('toast-show'); el.classList.add('toast-hide'); setTimeout(() => { if (el.parentNode) { el.parentNode.removeChild(el); } }, 300); // Remove from array this.toasts = this.toasts.filter(t => t.id !== toastId); } /** * Dismiss all toasts */ static dismissAll() { this.toasts.forEach(toast => this.dismiss(toast.id)); } /** * Convenience methods */ static success(message, options = {}) { return this.show(message, 'success', options); } static error(message, options = {}) { return this.show(message, 'error', options); } static warning(message, options = {}) { return this.show(message, 'warning', options); } static info(message, options = {}) { return this.show(message, 'info', options); } /** * Get icon for toast type */ static getIcon(type) { const icons = { success: '✅', error: '❌', warning: '⚠️', info: 'ℹ️', }; return icons[type] || 'ℹ️'; } /** * Escape HTML */ static escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } } export default Toast;