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
text/typescript
// 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');
}
}