UNPKG

besper-frontend-site-dev-main

Version:

Professional B-esper Frontend Site - Site-wide integration toolkit for full website bot deployment

135 lines (117 loc) 3.8 kB
// Modal component for dialog and overlay interfaces import { Component } from './Component.js'; import { ModalProps } from '../../types/components.types.js'; import { createElement } from '../../utils/dom.js'; export class Modal extends Component { private props: ModalProps; private overlay: HTMLElement; private modalContent: HTMLElement; private closeButton: HTMLElement; constructor(props: ModalProps) { super(props); this.props = props; this.updateVisibility(); } protected createElement(): HTMLElement { const modal = createElement('div', `besper-modal ${this.className}`); modal.id = this.id; modal.innerHTML = this.render(); this.overlay = modal.querySelector('.modal-overlay') as HTMLElement; this.modalContent = modal.querySelector('.modal-content') as HTMLElement; this.closeButton = modal.querySelector('.modal-close') as HTMLElement; this.setupEventListeners(); return modal; } protected render(): string { return ` <div class="modal-overlay"> <div class="modal-content modal-${this.props.size || 'md'}"> <div class="modal-header"> <h2 class="modal-title">${this.props.title}</h2> <button class="modal-close" aria-label="Close modal"> <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <line x1="18" y1="6" x2="6" y2="18"></line> <line x1="6" y1="6" x2="18" y2="18"></line> </svg> </button> </div> <div class="modal-body"> <!-- Content will be injected here --> </div> <div class="modal-footer"> <!-- Footer content will be injected here --> </div> </div> </div> `; } private setupEventListeners(): void { // Close on overlay click this.addEventListener(this.overlay, 'click', e => { if (e.target === this.overlay) { this.close(); } }); // Close on close button click this.addEventListener(this.closeButton, 'click', () => { this.close(); }); // Close on escape key this.addEventListener(document, 'keydown', e => { if ((e as KeyboardEvent).key === 'Escape' && this.props.isOpen) { this.close(); } }); } public setContent(content: string | HTMLElement): void { const body = this.element.querySelector('.modal-body') as HTMLElement; if (typeof content === 'string') { body.innerHTML = content; } else { body.innerHTML = ''; body.appendChild(content); } } public setFooter(footer: string | HTMLElement): void { const footerElement = this.element.querySelector( '.modal-footer' ) as HTMLElement; if (typeof footer === 'string') { footerElement.innerHTML = footer; } else { footerElement.innerHTML = ''; footerElement.appendChild(footer); } } public open(): void { this.props.isOpen = true; this.updateVisibility(); document.body.classList.add('modal-open'); } public close(): void { this.props.isOpen = false; this.updateVisibility(); document.body.classList.remove('modal-open'); if (this.props.onClose) { this.props.onClose(); } } private updateVisibility(): void { if (this.element) { this.element.style.display = this.props.isOpen ? 'flex' : 'none'; } } public setTitle(title: string): void { this.props.title = title; const titleElement = this.element.querySelector( '.modal-title' ) as HTMLElement; if (titleElement) { titleElement.textContent = title; } } protected onUnmount(): void { super.onUnmount(); document.body.classList.remove('modal-open'); } }