feat: improve modal styling and enhance padding management in modal host component

This commit is contained in:
2026-08-11 10:14:59 -03:00
parent e3599a48e4
commit 5d760bfd80
3 changed files with 39 additions and 44 deletions

View File

@@ -3,25 +3,10 @@ import { Component, inject } from '@angular/core';
import { DOCUMENT } from '@angular/common'; import { DOCUMENT } from '@angular/common';
import { TestBed, getTestBed } from '@angular/core/testing'; import { TestBed, getTestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser'; import { By } from '@angular/platform-browser';
import { import { BrowserTestingModule, platformBrowserTesting } from '@angular/platform-browser/testing';
BrowserTestingModule, import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
platformBrowserTesting
} from '@angular/platform-browser/testing';
import {
afterEach,
beforeAll,
beforeEach,
describe,
expect,
it,
vi
} from 'vitest';
import { import { MODAL_DATA, ModalRef, ModalService } from '../../../core/services/modal.service';
MODAL_DATA,
ModalRef,
ModalService
} from '../../../core/services/modal.service';
import { ModalHostComponent } from './modal-host.component'; import { ModalHostComponent } from './modal-host.component';
@Component({ @Component({
@@ -31,7 +16,7 @@ import { ModalHostComponent } from './modal-host.component';
<span class="modal-test-title">{{ data?.title }}</span> <span class="modal-test-title">{{ data?.title }}</span>
<button type="button" class="modal-test-close" (click)="close()">Cerrar</button> <button type="button" class="modal-test-close" (click)="close()">Cerrar</button>
</div> </div>
` `,
}) })
class ModalContentTestComponent { class ModalContentTestComponent {
readonly data = inject<{ title: string } | null>(MODAL_DATA); readonly data = inject<{ title: string } | null>(MODAL_DATA);
@@ -48,10 +33,7 @@ describe('ModalHostComponent', () => {
beforeAll(() => { beforeAll(() => {
try { try {
getTestBed().initTestEnvironment( getTestBed().initTestEnvironment(BrowserTestingModule, platformBrowserTesting());
BrowserTestingModule,
platformBrowserTesting()
);
} catch { } catch {
// Test environment may already be initialized by another setup entrypoint. // Test environment may already be initialized by another setup entrypoint.
} }
@@ -59,7 +41,7 @@ describe('ModalHostComponent', () => {
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
imports: [ModalHostComponent] imports: [ModalHostComponent],
}).compileComponents(); }).compileComponents();
service = TestBed.inject(ModalService); service = TestBed.inject(ModalService);
@@ -68,6 +50,7 @@ describe('ModalHostComponent', () => {
afterEach(() => { afterEach(() => {
doc.body.style.overflow = ''; doc.body.style.overflow = '';
doc.body.style.paddingRight = '';
TestBed.resetTestingModule(); TestBed.resetTestingModule();
}); });
@@ -84,7 +67,7 @@ describe('ModalHostComponent', () => {
service.open(ModalContentTestComponent, { service.open(ModalContentTestComponent, {
title: 'Editar producto', title: 'Editar producto',
data: { title: 'Contenido del modal' } data: { title: 'Contenido del modal' },
}); });
fixture.detectChanges(); fixture.detectChanges();
@@ -99,27 +82,30 @@ describe('ModalHostComponent', () => {
it('closes with a result from the child component', () => { it('closes with a result from the child component', () => {
const fixture = TestBed.createComponent(ModalHostComponent); const fixture = TestBed.createComponent(ModalHostComponent);
const ref = service.open(ModalContentTestComponent, { const ref = service.open(ModalContentTestComponent, {
data: { title: 'Cerrar' } data: { title: 'Cerrar' },
}); });
const closedSpy = vi.fn(); const closedSpy = vi.fn();
ref.afterClosed$.subscribe(closedSpy); ref.afterClosed$.subscribe(closedSpy);
fixture.detectChanges(); fixture.detectChanges();
const closeButton = fixture.nativeElement.querySelector('.modal-test-close') as HTMLButtonElement; const closeButton = fixture.nativeElement.querySelector(
'.modal-test-close',
) as HTMLButtonElement;
closeButton.click(); closeButton.click();
fixture.detectChanges(); fixture.detectChanges();
expect(closedSpy).toHaveBeenCalledWith('accepted'); expect(closedSpy).toHaveBeenCalledWith('accepted');
expect(service.activeModal()).toBeNull(); expect(service.activeModal()).toBeNull();
expect(doc.body.style.overflow).toBe(''); expect(doc.body.style.overflow).toBe('');
expect(doc.body.style.paddingRight).toBe('');
}); });
it('closes on backdrop click when enabled', () => { it('closes on backdrop click when enabled', () => {
const fixture = TestBed.createComponent(ModalHostComponent); const fixture = TestBed.createComponent(ModalHostComponent);
const ref = service.open(ModalContentTestComponent, { const ref = service.open(ModalContentTestComponent, {
data: { title: 'Backdrop' }, data: { title: 'Backdrop' },
closeOnBackdrop: true closeOnBackdrop: true,
}); });
fixture.detectChanges(); fixture.detectChanges();
@@ -136,7 +122,7 @@ describe('ModalHostComponent', () => {
const fixture = TestBed.createComponent(ModalHostComponent); const fixture = TestBed.createComponent(ModalHostComponent);
const ref = service.open(ModalContentTestComponent, { const ref = service.open(ModalContentTestComponent, {
data: { title: 'Persistente' }, data: { title: 'Persistente' },
closeOnBackdrop: false closeOnBackdrop: false,
}); });
fixture.detectChanges(); fixture.detectChanges();
@@ -152,7 +138,7 @@ describe('ModalHostComponent', () => {
const fixture = TestBed.createComponent(ModalHostComponent); const fixture = TestBed.createComponent(ModalHostComponent);
const ref = service.open(ModalContentTestComponent, { const ref = service.open(ModalContentTestComponent, {
data: { title: 'Escape' }, data: { title: 'Escape' },
closeOnEscape: true closeOnEscape: true,
}); });
fixture.detectChanges(); fixture.detectChanges();
@@ -167,7 +153,7 @@ describe('ModalHostComponent', () => {
const fixture = TestBed.createComponent(ModalHostComponent); const fixture = TestBed.createComponent(ModalHostComponent);
const ref = service.open(ModalContentTestComponent, { const ref = service.open(ModalContentTestComponent, {
data: { title: 'No Escape' }, data: { title: 'No Escape' },
closeOnEscape: false closeOnEscape: false,
}); });
fixture.detectChanges(); fixture.detectChanges();
@@ -181,12 +167,13 @@ describe('ModalHostComponent', () => {
const fixture = TestBed.createComponent(ModalHostComponent); const fixture = TestBed.createComponent(ModalHostComponent);
const ref = service.open(ModalContentTestComponent, { const ref = service.open(ModalContentTestComponent, {
title: 'Con cierre', title: 'Con cierre',
data: { title: 'Boton' } data: { title: 'Boton' },
}); });
fixture.detectChanges(); fixture.detectChanges();
const closeButton = fixture.debugElement.query(By.css('.btn-close')).nativeElement as HTMLButtonElement; const closeButton = fixture.debugElement.query(By.css('.btn-close'))
.nativeElement as HTMLButtonElement;
closeButton.click(); closeButton.click();
fixture.detectChanges(); fixture.detectChanges();

View File

@@ -7,21 +7,17 @@ import {
computed, computed,
effect, effect,
inject, inject,
viewChild viewChild,
} from '@angular/core'; } from '@angular/core';
import { import { MODAL_DATA, ModalRef, ModalService } from '../../../core/services/modal.service';
MODAL_DATA,
ModalRef,
ModalService
} from '../../../core/services/modal.service';
import { ModalShellComponent } from '../modal-shell/modal-shell.component'; import { ModalShellComponent } from '../modal-shell/modal-shell.component';
@Component({ @Component({
selector: 'app-modal-host', selector: 'app-modal-host',
imports: [NgComponentOutlet, ModalShellComponent], imports: [NgComponentOutlet, ModalShellComponent],
templateUrl: './modal-host.component.html', templateUrl: './modal-host.component.html',
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush,
}) })
export class ModalHostComponent { export class ModalHostComponent {
private readonly modalService = inject(ModalService); private readonly modalService = inject(ModalService);
@@ -42,9 +38,9 @@ export class ModalHostComponent {
return Injector.create({ return Injector.create({
providers: [ providers: [
{ provide: ModalRef, useValue: modal.ref }, { provide: ModalRef, useValue: modal.ref },
{ provide: MODAL_DATA, useValue: modal.config.data ?? null } { provide: MODAL_DATA, useValue: modal.config.data ?? null },
], ],
parent: this.injector parent: this.injector,
}); });
}); });
@@ -58,6 +54,18 @@ export class ModalHostComponent {
const body = this.document.body; const body = this.document.body;
const previousOverflow = body.style.overflow; const previousOverflow = body.style.overflow;
const previousPaddingRight = body.style.paddingRight;
const view = this.document.defaultView;
const scrollbarWidth = view
? Math.max(0, view.innerWidth - this.document.documentElement.clientWidth)
: 0;
if (scrollbarWidth > 0 && view) {
const currentPaddingRight =
Number.parseFloat(view.getComputedStyle(body).paddingRight) || 0;
body.style.paddingRight = `${currentPaddingRight + scrollbarWidth}px`;
}
body.style.overflow = 'hidden'; body.style.overflow = 'hidden';
const onKeyDown = (event: KeyboardEvent) => { const onKeyDown = (event: KeyboardEvent) => {
@@ -75,6 +83,7 @@ export class ModalHostComponent {
onCleanup(() => { onCleanup(() => {
this.document.removeEventListener('keydown', onKeyDown); this.document.removeEventListener('keydown', onKeyDown);
body.style.overflow = previousOverflow; body.style.overflow = previousOverflow;
body.style.paddingRight = previousPaddingRight;
}); });
}); });
} }

View File

@@ -10,8 +10,7 @@
align-items: center; align-items: center;
justify-content: center; justify-content: center;
padding: 1rem; padding: 1rem;
background: rgba(16, 18, 22, 0.48); background: rgba(16, 18, 22, 0.56);
backdrop-filter: blur(2px);
} }
.modal-shell__dialog { .modal-shell__dialog {