Skip to content

Commit 38d1e5d

Browse files
committed
fix(page): corrige cálculo de altura do po-page-content
fixes DTHFUI-12541
1 parent c6ae123 commit 38d1e5d

12 files changed

Lines changed: 246 additions & 182 deletions

projects/portal/src/styles.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -615,7 +615,7 @@ blockquote {
615615
}
616616

617617
.app-portal-home .po-page .po-page-content {
618-
padding: 0 0 4.5rem;
618+
padding: 0 0 var(--spacing-xs);
619619
}
620620

621621
.guides-grid-system-box {

projects/ui/src/lib/components/po-dropdown/po-dropdown-action.interface.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,22 @@ import { PoPopupAction } from '../po-popup/po-popup-action.interface';
22

33
/**
44
* @description
5-
* Interface do componente po-dropdown
5+
*
6+
* Interface para as ações do componente `po-dropdown`.
67
*
78
* @docsExtends PoPopupAction
89
*
910
* @usedBy PoDropdownComponent
1011
*/
1112
export interface PoDropdownAction extends PoPopupAction {
1213
/**
13-
* Array de ações (`PoDropdownAction`) usado para criar agrupadores de subitens.
14+
* @optional
15+
*
16+
* @description
1417
*
15-
* - Permite a criação de menus aninhados (submenus).
18+
* Array de ações (`PoDropdownAction`) usado para criar agrupadores de subitens (submenus).
1619
*
17-
* > Boas práticas de desenvolvimento:
18-
* Recomenda-se limitar a navegação a, no máximo, três níveis hierárquicos.
19-
* Isso evita sobrecarga cognitiva, facilita a memorização da estrutura e garante uma melhor experiência de uso.
20+
* > Recomenda-se limitar a navegação a, no máximo, três níveis hierárquicos.
2021
*/
2122
subItems?: Array<PoDropdownAction>;
2223
}

projects/ui/src/lib/components/po-page/interfaces/po-page-action.interface.ts

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,35 +5,36 @@ import { PoDropdownAction } from '../../po-dropdown';
55
*
66
* Interface para as ações dos componentes `po-page-default` e `po-page-list`.
77
*
8-
* As ações são exibidas como botões no cabeçalho e, caso excedam o limite de exibição ou o layout
9-
* seja configurado para tal, são agrupadas automaticamente em um *dropdown*.
8+
* As ações podem ser exibidas como botões no cabeçalho ou agrupadas em um *dropdown*,
9+
* conforme o `PoPageActionsLayout` e o tamanho da tela.
1010
*
1111
* > As propriedades `separator`, `selected` e `subItems` possuem efeito apenas quando
1212
* a ação é exibida dentro do *dropdown*.
1313
*
14+
* @docsExtends PoDropdownAction
15+
*
1416
* @ignoreExtendedDescription
1517
*
1618
* @usedBy PoPageDefaultComponent, PoPageListComponent
1719
*/
1820
export interface PoPageAction extends PoDropdownAction {
1921
/**
22+
* @optional
23+
*
2024
* @description
2125
*
22-
* Define o estilo visual da ação quando ela é exibida como botão fora do *dropdown*.
26+
* Define o estilo visual da ação quando exibida como botão fora do *dropdown*.
2327
*
2428
* Valores permitidos:
25-
* - `primary`: Botão com maior destaque visual.
26-
* - `secondary`: Estilo padrão para a maioria das ações.
27-
*
28-
* > Valores inválidos são ignorados, mantendo o valor padrão da posição da ação.
29+
* - `primary`: botão com maior destaque visual.
30+
* - `secondary`: estilo padrão.
2931
*
30-
* > Aplicável apenas a ações exibidas como botões (fora do *dropdown*). Ações dentro do *dropdown* não utilizam esta propriedade.
32+
* > Valores inválidos são ignorados e o componente aplica o estilo padrão da posição.
3133
*
32-
* > Funciona independentemente da posição da ação e com qualquer `PoPageHeaderType` ou `PoPageActionsLayout`.
34+
* > Somente uma ação pode ter `kind` igual a `primary`. Caso mais de uma defina `primary`,
35+
* apenas a primeira será mantida e as demais receberão `secondary`.
3336
*
34-
* **Valores padrão por posição (quando `kind` não é definido):**
35-
* - Layout `default`: primeira ação = `primary`, demais = `secondary`.
36-
* - Layout `mixed`: primeira ação = `primary` (header primary) ou `secondary` (header secondary/tertiary).
37+
* > Quando não definido, o estilo é determinado pelo `PoPageActionsLayout`.
3738
*/
3839
kind?: string;
3940
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
<div class="po-page-content" [style.height]="height" [style.opacity]="contentOpacity" [style.overflow-y]="overflowY">
1+
<div class="po-page-content" [style.height]="height" [style.opacity]="contentOpacity">
22
<ng-content></ng-content>
33
</div>
Lines changed: 39 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,23 @@
11
import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
2-
import { Component } from '@angular/core';
32

43
import { changeBrowserInnerWidth, configureTestSuite } from '../../../util-test/util-expect.spec';
54

65
import { PoPageContentComponent } from './po-page-content.component';
76

8-
@Component({
9-
selector: 'po-page-content-div',
10-
template: `
11-
<div class="po-toolbar"></div>
12-
<div class="po-page-header"></div>
13-
`,
14-
styles: [
15-
`
16-
.po-page-header {
17-
height: 100px;
18-
width: 100%;
19-
}
20-
.po-toolbar {
21-
height: 33px;
22-
width: 100%;
23-
}
24-
`
25-
],
26-
standalone: false
27-
})
28-
class ContentDivComponent {}
29-
307
describe('PoPageContentComponent:', () => {
318
let component: PoPageContentComponent;
329
let fixture: ComponentFixture<PoPageContentComponent>;
3310

34-
let fixtureDiv: ComponentFixture<ContentDivComponent>;
35-
3611
const eventResize = document.createEvent('Event');
3712
eventResize.initEvent('resize', false, true);
3813

3914
configureTestSuite(() => {
4015
TestBed.configureTestingModule({
41-
declarations: [ContentDivComponent, PoPageContentComponent]
16+
declarations: [PoPageContentComponent]
4217
});
4318
});
4419

4520
beforeEach(() => {
46-
fixtureDiv = TestBed.createComponent(ContentDivComponent);
47-
fixtureDiv.detectChanges();
48-
4921
fixture = TestBed.createComponent(PoPageContentComponent);
5022
component = fixture.componentInstance;
5123
fixture.detectChanges();
@@ -55,7 +27,7 @@ describe('PoPageContentComponent:', () => {
5527
expect(component instanceof PoPageContentComponent).toBeTruthy();
5628
});
5729

58-
it('constructor: should call recalculateHeaderSize', () => {
30+
it('constructor: should call recalculateHeaderSize on resize', () => {
5931
component['initializeListeners']();
6032
spyOn(component, 'recalculateHeaderSize');
6133

@@ -66,12 +38,12 @@ describe('PoPageContentComponent:', () => {
6638
});
6739

6840
describe('Methods:', () => {
69-
it('recalculateHeaderSize: should call setHeightContent and set `contentOpacity` to equal 1.', fakeAsync(() => {
70-
spyOn(component, 'setHeightContent');
41+
it('recalculateHeaderSize: should set height and contentOpacity to 1', fakeAsync(() => {
7142
component.recalculateHeaderSize();
7243
tick(100);
73-
expect(component.setHeightContent).toHaveBeenCalled();
44+
7445
expect(component.contentOpacity).toBe(1);
46+
expect(component.height).toBeTruthy();
7547
}));
7648

7749
it('ngAfterViewInit: should call recalculateHeaderSize', () => {
@@ -82,24 +54,44 @@ describe('PoPageContentComponent:', () => {
8254
expect(component.recalculateHeaderSize).toHaveBeenCalled();
8355
});
8456

85-
it('setHeightContent: should calculate height without pageHeader', () => {
86-
component.setHeightContent(undefined);
57+
it('should calculate height based on viewport when no .po-page ancestor', fakeAsync(() => {
58+
component.recalculateHeaderSize();
59+
tick(100);
60+
61+
expect(component.height).toMatch(/^\d+px$|^auto$/);
62+
}));
8763

88-
const bodyHeight = document.body.clientHeight;
89-
const valueExpected = bodyHeight;
64+
it('should set height to auto when inside nested po-page-content', fakeAsync(() => {
65+
const wrapper = document.createElement('div');
66+
wrapper.classList.add('po-page-content');
67+
const poPage = document.createElement('div');
68+
poPage.classList.add('po-page');
69+
wrapper.appendChild(poPage);
70+
poPage.appendChild(fixture.nativeElement);
71+
document.body.appendChild(wrapper);
9072

91-
expect(component.height).toBe(`${valueExpected}px`);
92-
});
73+
component.recalculateHeaderSize();
74+
tick(100);
9375

94-
it('setHeightContent: should calculate height with bottom actions', () => {
95-
const pageHeaderElement = fixtureDiv.debugElement.nativeElement.querySelector('.po-page-header') as HTMLElement;
96-
const pageHeaderHeight = pageHeaderElement.offsetTop + pageHeaderElement.offsetHeight;
97-
const bodyHeight = document.body.clientHeight;
98-
const valueExpected = bodyHeight - pageHeaderHeight;
76+
expect(component.height).toBe('auto');
9977

100-
component.setHeightContent(pageHeaderElement);
78+
document.body.removeChild(wrapper);
79+
}));
10180

102-
expect(component.height).toBe(`${valueExpected}px`);
103-
});
81+
it('should fallback to 90% when calculated height is zero or negative without .po-page', fakeAsync(() => {
82+
spyOn(fixture.nativeElement, 'getBoundingClientRect').and.returnValue({
83+
top: window.innerHeight + 100,
84+
bottom: window.innerHeight + 200,
85+
left: 0,
86+
right: 0,
87+
width: 0,
88+
height: 0
89+
});
90+
91+
component.recalculateHeaderSize();
92+
tick(100);
93+
94+
expect(component.height).toBe('90%');
95+
}));
10496
});
10597
});
Lines changed: 52 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { AfterViewInit, Component, OnDestroy, Renderer2, inject } from '@angular/core';
1+
import { AfterViewInit, Component, ElementRef, NgZone, OnDestroy, Renderer2, inject } from '@angular/core';
22

33
import { PoPageContentBaseComponent } from './po-page-content-base.component';
44

@@ -13,49 +13,82 @@ import { PoPageContentBaseComponent } from './po-page-content-base.component';
1313
standalone: false
1414
})
1515
export class PoPageContentComponent extends PoPageContentBaseComponent implements AfterViewInit, OnDestroy {
16-
renderer = inject(Renderer2);
16+
private readonly renderer = inject(Renderer2);
17+
private readonly elementRef = inject(ElementRef);
18+
private readonly ngZone = inject(NgZone);
1719

1820
contentOpacity: number = 0;
21+
1922
height: string = '90%';
20-
overflowY: string = 'none';
2123

2224
constructor() {
2325
super();
2426
this.initializeListeners();
2527
}
2628

27-
ngAfterViewInit() {
29+
ngAfterViewInit(): void {
2830
this.recalculateHeaderSize();
2931
}
3032

31-
ngOnDestroy() {
33+
ngOnDestroy(): void {
3234
this.removeListeners();
3335
}
3436

35-
recalculateHeaderSize() {
37+
/**
38+
* Recalcula a altura do po-page-content.
39+
* Chamado internamente pelo po-page-default quando inputs que afetam
40+
* o tamanho do header mudam (title, subtitle, breadcrumb, headerType, theme).
41+
*/
42+
recalculateHeaderSize(): void {
3643
setTimeout(() => {
37-
const pageHeaderElement: HTMLElement = document.querySelector('div.po-page-header');
38-
39-
this.setHeightContent(pageHeaderElement);
44+
this.setHeightContent();
4045
this.contentOpacity = 1;
4146
});
4247
}
4348

44-
setHeightContent(poPageHeader: HTMLElement) {
45-
const bodyHeight = document.body.clientHeight;
46-
const pageHeaderHeight = poPageHeader ? poPageHeader.offsetTop + poPageHeader.offsetHeight : 0;
47-
const newHeight = bodyHeight - pageHeaderHeight;
49+
private setHeightContent(): void {
50+
const poPageDiv: HTMLElement = this.elementRef.nativeElement.closest('.po-page');
51+
const contentHost: HTMLElement = this.elementRef.nativeElement;
52+
53+
if (!poPageDiv) {
54+
const contentTop = contentHost.getBoundingClientRect().top;
55+
const newHeight = window.innerHeight - contentTop;
56+
this.height = newHeight > 0 ? `${newHeight}px` : '90%';
57+
return;
58+
}
59+
60+
// Page nested (dentro de outro po-page-content): height controlado externamente.
61+
if (poPageDiv.closest('.po-page-content')) {
62+
this.height = 'auto';
63+
return;
64+
}
65+
66+
const pageHeader: HTMLElement = poPageDiv.querySelector(':scope > po-page-header');
67+
const gap = parseFloat(getComputedStyle(poPageDiv).gap) || 0;
68+
69+
let contentTop: number;
70+
71+
if (pageHeader) {
72+
contentTop = pageHeader.getBoundingClientRect().bottom + gap;
73+
} else {
74+
contentTop = poPageDiv.getBoundingClientRect().top;
75+
}
4876

49-
this.height = `${newHeight}px`;
77+
const newHeight = window.innerHeight - contentTop;
78+
this.height = newHeight > 0 ? `${newHeight}px` : '90%';
5079
}
5180

52-
private initializeListeners() {
53-
this.resizeListener = this.renderer.listen('window', 'resize', () => {
54-
this.recalculateHeaderSize();
81+
private initializeListeners(): void {
82+
this.ngZone.runOutsideAngular(() => {
83+
this.resizeListener = this.renderer.listen('window', 'resize', () => {
84+
this.ngZone.run(() => this.recalculateHeaderSize());
85+
});
5586
});
5687
}
5788

58-
private removeListeners() {
59-
this.resizeListener();
89+
private removeListeners(): void {
90+
if (this.resizeListener) {
91+
this.resizeListener();
92+
}
6093
}
6194
}

projects/ui/src/lib/components/po-page/po-page-default/enums/po-page-actions-layout.enum.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,26 @@
33
*
44
* @description
55
*
6-
* Enum que define os layouts de ações disponíveis no `po-page-default`.
6+
* Define os layouts de exibição das ações no cabeçalho do `po-page-default`.
77
*
8-
* > Compatível com todos os valores de `PoPageHeaderType` (`primary`, `secondary` e `tertiary`).
9-
*
10-
* Define os layouts de exibição das ações no cabeçalho.
8+
* > Compatível com todos os valores de `PoPageHeaderType`.
119
*/
1210
export enum PoPageActionsLayout {
1311
/**
14-
* Comportamento padrão: as ações são exibidas como botões (até 3 em desktop e 2 em mobile)
15-
* e as demais são agrupadas no *dropdown*.
12+
* Exibe as ações como botões (até 3 em desktop e 2 em mobile), agrupando as demais no *dropdown*.
13+
*
14+
* Quando `PoPageAction.kind` não é definido, a primeira ação recebe o estilo `primary`
15+
* e as demais recebem `secondary`.
1616
*/
1717
default = 'default',
1818

19-
/** Todas as ações são agrupadas exclusivamente dentro do menu *dropdown*. */
19+
/**
20+
* Agrupa todas as ações exclusivamente dentro do menu *dropdown*.
21+
*/
2022
dropdown = 'dropdown',
2123

2224
/**
23-
* A primeira ação é exibida como um botão de destaque e todas as demais são movidas para o *dropdown*.
25+
* Exibe a primeira ação como botão e agrupa as demais no *dropdown*.
2426
*/
2527
mixed = 'mixed'
2628
}

0 commit comments

Comments
 (0)