BROOKO icon
BROOKO UK NETWORK
Where code meets creativity & adventure
File viewer

modal.js

Type
js
Size
7.16 KB
Modified
15 May
modal.js 7.16 KB
/**
 * WorkersPanel Modal System
 * Reusable modal component for consistent popups across the app
 * 
 * Usage:
 * const modal = new Modal({
 *   title: 'Event Details',
 *   content: '<p>Your content here</p>',
 *   buttons: [
 *     { text: 'Close', class: 'btn-secondary', onClick: () => modal.close() },
 *     { text: 'Save', class: 'btn-primary', onClick: handleSave }
 *   ]
 * });
 * modal.open();
 */

class Modal {
    constructor(options = {}) {
        this.options = {
            title: options.title || 'Modal',
            content: options.content || '',
            buttons: options.buttons || [{ text: 'Close', class: 'btn-secondary', onClick: () => this.close() }],
            size: options.size || 'medium', // small, medium, large, fullscreen
            closeOnOverlay: options.closeOnOverlay !== false,
            onOpen: options.onOpen || null,
            onClose: options.onClose || null,
            className: options.className || ''
        };
        
        this.element = null;
        this.isOpen = false;
        this.create();
    }
    
    create() {
        // Create overlay
        const overlay = document.createElement('div');
        overlay.className = 'wp-modal-overlay';
        overlay.id = `modal-${Date.now()}`;
        
        if (this.options.closeOnOverlay) {
            overlay.addEventListener('click', (e) => {
                if (e.target === overlay) {
                    this.close();
                }
            });
        }
        
        // Create modal container
        const modal = document.createElement('div');
        modal.className = `wp-modal wp-modal-${this.options.size} ${this.options.className}`;
        
        // Create header
        const header = document.createElement('div');
        header.className = 'wp-modal-header';
        
        const title = document.createElement('h3');
        title.className = 'wp-modal-title';
        title.textContent = this.options.title;
        
        const closeBtn = document.createElement('button');
        closeBtn.className = 'wp-modal-close';
        closeBtn.innerHTML = '×';
        closeBtn.addEventListener('click', () => this.close());
        
        header.appendChild(title);
        header.appendChild(closeBtn);
        
        // Create body
        const body = document.createElement('div');
        body.className = 'wp-modal-body';
        if (typeof this.options.content === 'string') {
            body.innerHTML = this.options.content;
        } else {
            body.appendChild(this.options.content);
        }
        
        // Create footer with buttons (optional)
        let footer = null;
        if (Array.isArray(this.options.buttons) && this.options.buttons.length > 0) {
            footer = document.createElement('div');
            footer.className = 'wp-modal-footer';
            
            this.options.buttons.forEach(btnConfig => {
                const btn = document.createElement('button');
                btn.className = `btn ${btnConfig.class || 'btn-secondary'}`;
                btn.textContent = btnConfig.text;
                if (btnConfig.id) btn.id = btnConfig.id;
                btn.addEventListener('click', (e) => {
                    if (btnConfig.onClick) {
                        btnConfig.onClick(e, this);
                    }
                });
                footer.appendChild(btn);
            });
        }
        
        // Assemble modal
        modal.appendChild(header);
        modal.appendChild(body);
        if (footer) modal.appendChild(footer);
        overlay.appendChild(modal);
        
        this.element = overlay;
    }
    
    open() {
        if (this.isOpen) return;

        document.querySelectorAll('.wp-modal-overlay').forEach((existing) => {
            if (existing !== this.element && existing.parentNode) {
                existing.parentNode.removeChild(existing);
            }
        });

        // If the app is in fullscreen (e.g. map fullscreen), append the modal
        // inside the fullscreen element so it actually renders on top.
        const host = document.fullscreenElement
            || document.webkitFullscreenElement
            || document.mozFullScreenElement
            || document.msFullscreenElement
            || document.body;

        host.appendChild(this.element);
        
        // Trigger animation
        requestAnimationFrame(() => {
            this.element.classList.add('active');
        });
        
        this.isOpen = true;
        
        if (this.options.onOpen) {
            this.options.onOpen(this);
        }
        
        // Prevent body scroll
        document.body.style.overflow = 'hidden';
    }
    
    close() {
        if (!this.isOpen) return;
        
        this.element.classList.remove('active');
        
        setTimeout(() => {
            if (this.element && this.element.parentNode) {
                this.element.parentNode.removeChild(this.element);
            }
        }, 300);
        
        this.isOpen = false;
        
        if (this.options.onClose) {
            this.options.onClose(this);
        }
        
        // Restore body scroll when no other overlay is open
        if (!document.querySelector('.wp-modal-overlay.active')) {
            document.body.style.overflow = '';
        }
    }
    
    updateContent(content) {
        const body = this.element.querySelector('.wp-modal-body');
        if (typeof content === 'string') {
            body.innerHTML = content;
        } else {
            body.innerHTML = '';
            body.appendChild(content);
        }
    }
    
    updateTitle(title) {
        const titleEl = this.element.querySelector('.wp-modal-title');
        titleEl.textContent = title;
    }
    
    setLoading(isLoading) {
        const modal = this.element.querySelector('.wp-modal');
        if (isLoading) {
            modal.classList.add('loading');
        } else {
            modal.classList.remove('loading');
        }
    }
}

// Helper function to create a simple alert modal
function showAlert(title, message, buttonText = 'OK') {
    const modal = new Modal({
        title: title,
        content: `<p style="line-height: 1.6;">${message}</p>`,
        buttons: [
            { text: buttonText, class: 'btn-primary', onClick: (e, m) => m.close() }
        ]
    });
    modal.open();
    return modal;
}

// Helper function to create a confirmation modal
function showConfirm(title, message, onConfirm, onCancel) {
    const modal = new Modal({
        title: title,
        content: `<p style="line-height: 1.6;">${message}</p>`,
        buttons: [
            { 
                text: 'Cancel', 
                class: 'btn-secondary', 
                onClick: (e, m) => {
                    if (onCancel) onCancel();
                    m.close();
                }
            },
            { 
                text: 'Confirm', 
                class: 'btn-primary', 
                onClick: (e, m) => {
                    if (onConfirm) onConfirm();
                    m.close();
                }
            }
        ]
    });
    modal.open();
    return modal;
}

// Export for use in other modules
if (typeof module !== 'undefined' && module.exports) {
    module.exports = { Modal, showAlert, showConfirm };
}