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 };
}