// Highly customizable confirmation modal component // Supports custom titles, messages, buttons, icons, types, and more const ConfirmationModal = { // Default configuration defaults: { title: 'Confirm Action', message: 'Are you sure you want to proceed?', type: 'default', // 'default', 'warning', 'danger', 'info', 'success' confirmText: 'Confirm', cancelText: 'Cancel', confirmButtonClass: '', cancelButtonClass: '', showCancel: true, allowHTML: false, icon: null, // Custom icon HTML or null for default icons onConfirm: null, onCancel: null, closeOnBackdrop: true, closeOnEscape: true, focusConfirm: true, width: 'max-w-md', // Tailwind width class zIndex: 'z-50' }, // Type-specific configurations typeConfigs: { warning: { title: 'Warning', icon: ``, confirmButtonClass: 'admin-btn--warning' }, danger: { title: 'Danger', icon: ``, confirmButtonClass: 'admin-btn--danger' }, info: { title: 'Information', icon: ``, confirmButtonClass: 'admin-btn--primary' }, success: { title: 'Success', icon: ``, confirmButtonClass: 'admin-btn--success' }, default: { title: 'Confirm Action', icon: `` } }, // Create and show the modal show: function(options = {}) { return new Promise((resolve, reject) => { // Merge options with defaults and type config const config = { ...this.defaults, ...options }; const typeConfig = this.typeConfigs[config.type] || this.typeConfigs.default; // Apply type-specific config if (config.type !== 'default' && !options.title) { config.title = typeConfig.title; } if (!config.icon && typeConfig.icon) { config.icon = typeConfig.icon; } if (config.type !== 'default' && !options.confirmButtonClass) { config.confirmButtonClass = typeConfig.confirmButtonClass; } // Get or create modal element let modal = document.getElementById('confirmationModal'); if (!modal) { modal = this._createModalElement(); document.body.appendChild(modal); } // Update modal content this._updateModalContent(modal, config); // Set up event handlers const confirmBtn = modal.querySelector('[data-confirm-btn]'); const cancelBtn = modal.querySelector('[data-cancel-btn]'); const backdrop = modal.querySelector('.modal-backdrop'); // Clean up previous handlers const newConfirmHandler = () => { modal.close(); if (config.onConfirm) { try { const result = config.onConfirm(); if (result instanceof Promise) { result.then(resolve).catch(reject); } else { resolve(result); } } catch (error) { reject(error); } } else { resolve(true); } this._cleanup(modal); }; const newCancelHandler = () => { modal.close(); if (config.onCancel) { try { const result = config.onCancel(); if (result instanceof Promise) { result.then(() => resolve(false)).catch(reject); } else { resolve(false); } } catch (error) { reject(error); } } else { resolve(false); } this._cleanup(modal); }; const escapeHandler = (e) => { if (config.closeOnEscape && e.key === 'Escape') { e.preventDefault(); newCancelHandler(); } else if (e.key === 'Enter' && config.focusConfirm) { e.preventDefault(); newConfirmHandler(); } }; // Attach handlers confirmBtn.addEventListener('click', newConfirmHandler); if (cancelBtn) { cancelBtn.addEventListener('click', newCancelHandler); } document.addEventListener('keydown', escapeHandler); // Store handlers for cleanup modal._handlers = { confirm: newConfirmHandler, cancel: newCancelHandler, escape: escapeHandler }; // Backdrop click handler if (config.closeOnBackdrop) { const backdropHandler = (e) => { if (e.target === modal) { newCancelHandler(); } }; modal.addEventListener('click', backdropHandler); modal._handlers.backdrop = backdropHandler; } // Show modal modal.showModal(); // Focus confirm button if specified if (config.focusConfirm) { setTimeout(() => confirmBtn.focus(), 100); } else if (cancelBtn) { setTimeout(() => cancelBtn.focus(), 100); } }); }, // Create the modal DOM element _createModalElement: function() { const modal = document.createElement('dialog'); modal.id = 'confirmationModal'; modal.className = 'confirmation-modal p-0 bg-transparent border-0 outline-none rounded-lg shadow-2xl w-full max-w-md'; modal.setAttribute('style', 'border: none; outline: none; padding: 0; margin: 0; background: transparent;'); modal.innerHTML = `
`; return modal; }, // Update modal content based on config _updateModalContent: function(modal, config) { const titleEl = modal.querySelector('.modal-title'); const messageEl = modal.querySelector('.modal-message'); const iconEl = modal.querySelector('.modal-icon'); const confirmBtn = modal.querySelector('[data-confirm-btn]'); const cancelBtn = modal.querySelector('[data-cancel-btn]'); const footer = modal.querySelector('.modal-footer'); const content = modal.querySelector('.modal-content'); // Update title if (titleEl) { titleEl.textContent = config.title; } // Update message if (messageEl) { if (config.allowHTML) { messageEl.innerHTML = config.message; } else { messageEl.textContent = config.message; } } // Update icon if (iconEl) { if (config.icon) { iconEl.innerHTML = config.icon; iconEl.classList.remove('hidden'); } else { iconEl.classList.add('hidden'); } } // Update buttons if (confirmBtn) { confirmBtn.textContent = config.confirmText; const confirmVariant = config.confirmButtonClass || 'admin-btn--primary'; confirmBtn.className = `admin-btn ${confirmVariant}`; confirmBtn.style.cssText = ''; } if (cancelBtn) { if (config.showCancel) { cancelBtn.textContent = config.cancelText; cancelBtn.className = 'admin-btn admin-btn--secondary'; cancelBtn.style.cssText = ''; cancelBtn.classList.remove('hidden'); } else { cancelBtn.classList.add('hidden'); } } // Update width if (content && config.width) { content.className = content.className.replace(/max-w-\w+/, ''); content.classList.add(config.width); } }, // Clean up event handlers _cleanup: function(modal) { if (!modal._handlers) return; const confirmBtn = modal.querySelector('[data-confirm-btn]'); const cancelBtn = modal.querySelector('[data-cancel-btn]'); if (confirmBtn && modal._handlers.confirm) { confirmBtn.removeEventListener('click', modal._handlers.confirm); } if (cancelBtn && modal._handlers.cancel) { cancelBtn.removeEventListener('click', modal._handlers.cancel); } if (modal._handlers.escape) { document.removeEventListener('keydown', modal._handlers.escape); } if (modal._handlers.backdrop) { modal.removeEventListener('click', modal._handlers.backdrop); } delete modal._handlers; }, // Convenience methods for common types warning: function(message, options = {}) { return this.show({ ...options, message, type: 'warning' }); }, danger: function(message, options = {}) { return this.show({ ...options, message, type: 'danger' }); }, info: function(message, options = {}) { return this.show({ ...options, message, type: 'info' }); }, success: function(message, options = {}) { return this.show({ ...options, message, type: 'success' }); }, // Simple confirm replacement (backward compatible) confirm: function(message, options = {}) { return this.show({ ...options, message }); }, // Alert-style modal (single button, no cancel) alert: function(message, options = {}) { return this.show({ ...options, message, showCancel: false, confirmText: options.confirmText || 'OK', type: options.type || 'info', focusConfirm: true }); } }; // Make it globally available window.ConfirmationModal = ConfirmationModal; // Also provide a simple showConfirm function for backward compatibility window.showConfirm = function(message, callback, options = {}) { return ConfirmationModal.show({ ...options, message, onConfirm: callback }); };