feat: implement modal service and components for enhanced user interaction, including modal showcase in demo page

This commit is contained in:
2026-07-02 11:24:31 -03:00
parent d31cb65d65
commit b3782a6cc1
15 changed files with 1031 additions and 2 deletions

View File

@@ -0,0 +1,93 @@
import { DOCUMENT, NgComponentOutlet, isPlatformBrowser } from '@angular/common';
import {
ChangeDetectionStrategy,
Component,
Injector,
PLATFORM_ID,
computed,
effect,
inject,
viewChild
} from '@angular/core';
import {
MODAL_DATA,
ModalRef,
ModalService
} from '../../../core/services/modal.service';
import { ModalShellComponent } from '../modal-shell/modal-shell.component';
@Component({
selector: 'app-modal-host',
imports: [NgComponentOutlet, ModalShellComponent],
templateUrl: './modal-host.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ModalHostComponent {
private readonly modalService = inject(ModalService);
private readonly injector = inject(Injector);
private readonly document = inject(DOCUMENT);
private readonly platformId = inject(PLATFORM_ID);
private readonly modalShell = viewChild(ModalShellComponent);
private readonly isBrowser = isPlatformBrowser(this.platformId);
readonly activeModal = this.modalService.activeModal;
protected readonly contentInjector = computed<Injector | undefined>(() => {
const modal = this.activeModal();
if (!modal) {
return undefined;
}
return Injector.create({
providers: [
{ provide: ModalRef, useValue: modal.ref },
{ provide: MODAL_DATA, useValue: modal.config.data ?? null }
],
parent: this.injector
});
});
constructor() {
effect((onCleanup) => {
const modal = this.activeModal();
if (!this.isBrowser || !modal) {
return;
}
const body = this.document.body;
const previousOverflow = body.style.overflow;
body.style.overflow = 'hidden';
const onKeyDown = (event: KeyboardEvent) => {
if (event.key !== 'Escape' || !this.activeModal()?.config.closeOnEscape) {
return;
}
event.preventDefault();
this.activeModal()?.ref.dismiss('escape');
};
this.document.addEventListener('keydown', onKeyDown);
queueMicrotask(() => this.modalShell()?.focusInitialElement());
onCleanup(() => {
this.document.removeEventListener('keydown', onKeyDown);
body.style.overflow = previousOverflow;
});
});
}
protected onBackdropClick(): void {
const modal = this.activeModal();
if (modal?.config.closeOnBackdrop) {
modal.ref.dismiss('backdrop');
}
}
protected onCloseRequested(): void {
this.activeModal()?.ref.dismiss('programmatic');
}
}