Skip to content

Commit 8c3aa86

Browse files
jnrpalmaanderson-gregorio-totvs
authored andcommitted
fix(tab-dropdown): corrige posicionamento
Corrige o posicionamento do `po-listbox` possibilitando uma visibilidade melhor dos itens. fixes DTHFUI-8917
1 parent b575b81 commit 8c3aa86

File tree

7 files changed

+191
-62
lines changed

7 files changed

+191
-62
lines changed

projects/ui/src/lib/components/po-listbox/po-listbox.component.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export class PoListBoxComponent extends PoListBoxBaseComponent implements AfterV
4747
ngAfterViewInit(): void {
4848
this.setListBoxMaxHeight();
4949
this.listboxItemList?.nativeElement.focus();
50+
this.changeDetector.detectChanges();
5051
}
5152

5253
ngOnChanges(changes?: SimpleChanges): void {
Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
11
<div class="po-tab-dropdown-content po-tab-button-default">
2-
<po-button #button p-kind="tertiary" [p-aria-label]="label" p-icon="ICON_ARROW_DOWN"> </po-button>
2+
<po-button #button p-kind="tertiary" [p-aria-label]="label" p-icon="ICON_ARROW_DOWN" (p-click)="toggleDropdown()">
3+
</po-button>
34
</div>
45

5-
<po-popover #popover p-hide-arrow p-position="bottom" [p-target]="buttonElement">
6-
<div class="po-tab-dropdown-container">
7-
<po-listbox
8-
#listbox
9-
#poListBoxRef
10-
p-type="action"
11-
[p-items]="tabs"
12-
[p-is-tabs]="true"
13-
(p-activated-tabs)="activated.emit($event)"
14-
(p-change-state-tabs)="changeState.emit($event)"
15-
(p-click-tabs)="popover.close(); click.emit($event)"
16-
(p-close)="closeAndReturnToButtom()"
17-
>
18-
</po-listbox>
19-
</div>
20-
</po-popover>
6+
<div class="po-tab-dropdown-container" *ngIf="isDropdownOpen" [ngStyle]="dropdownStyles">
7+
<po-listbox
8+
#listbox
9+
#poListBoxRef
10+
p-type="action"
11+
[p-items]="tabs"
12+
[p-is-tabs]="true"
13+
(p-activated-tabs)="activated.emit($event)"
14+
(p-change-state-tabs)="changeState.emit($event)"
15+
(p-click-tabs)="closeDropdown(); click.emit($event)"
16+
(p-close)="closeAndReturnToButtom()"
17+
>
18+
</po-listbox>
19+
</div>

projects/ui/src/lib/components/po-tabs/po-tab-dropdown/po-tab-dropdown.component.spec.ts

Lines changed: 102 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { ComponentFixture, TestBed } from '@angular/core/testing';
22
import { RouterTestingModule } from '@angular/router/testing';
3+
import { ElementRef } from '@angular/core';
34

45
import { configureTestSuite } from './../../../util-test/util-expect.spec';
56

@@ -19,20 +20,38 @@ describe('PoTabDropdownComponent:', () => {
1920
{ label: 'Tab 4', overflow: true, click: () => {} }
2021
];
2122

23+
const buttonElementRefMock = {
24+
nativeElement: {
25+
getBoundingClientRect: () => ({
26+
right: 100,
27+
bottom: 100
28+
}),
29+
closest: (selector: string) => ({
30+
getBoundingClientRect: () => ({
31+
width: 300,
32+
bottom: 120
33+
})
34+
})
35+
}
36+
};
37+
2238
configureTestSuite(() => {
2339
TestBed.configureTestingModule({
2440
imports: [PoPopoverModule, RouterTestingModule.withRoutes([])],
25-
declarations: [PoTabDropdownComponent]
41+
declarations: [PoTabDropdownComponent],
42+
providers: [{ provide: ElementRef, useValue: buttonElementRefMock }]
2643
});
2744
});
2845

2946
beforeEach(() => {
3047
fixture = TestBed.createComponent(PoTabDropdownComponent);
3148
component = fixture.componentInstance;
3249
nativeElement = fixture.debugElement.nativeElement;
50+
Object.defineProperty(window, 'scrollY', { value: 50, writable: true });
3351

3452
component.tabs = tabs;
3553
component.button = new PoButtonComponent();
54+
component.button.buttonElement = buttonElementRefMock as ElementRef;
3655
component.popover = new PoPopoverComponent(null, null);
3756
fixture.detectChanges();
3857
});
@@ -42,13 +61,92 @@ describe('PoTabDropdownComponent:', () => {
4261
});
4362

4463
describe('Methods:', () => {
45-
it('closeAndReturnToButtom: should close popover and focus on the button', () => {
46-
spyOn(component.popover, 'close');
64+
it('closeAndReturnToButtom: should close dropdown and focus on the button', () => {
65+
spyOn(component, 'closeDropdown');
4766
spyOn(component.button, 'focus');
67+
4868
component.closeAndReturnToButtom();
4969

50-
expect(component.popover.close).toHaveBeenCalled();
70+
expect(component.closeDropdown).toHaveBeenCalled();
5171
expect(component.button.focus).toHaveBeenCalled();
5272
});
73+
74+
it('toggleDropdown: should toggle isDropdownOpen and call setDropdownPosition if isDropdownOpen is true', () => {
75+
spyOn(component, 'setDropdownPosition');
76+
77+
expect(component.isDropdownOpen).toBeFalse();
78+
79+
component.toggleDropdown();
80+
expect(component.isDropdownOpen).toBeTrue();
81+
expect(component.setDropdownPosition).toHaveBeenCalled();
82+
83+
component.toggleDropdown();
84+
expect(component.isDropdownOpen).toBeFalse();
85+
expect(component.setDropdownPosition).toHaveBeenCalledTimes(1);
86+
});
87+
88+
it('closeDropdown: should set isDropdownOpen to false', () => {
89+
component.isDropdownOpen = true;
90+
component.closeDropdown();
91+
92+
expect(component.isDropdownOpen).toBeFalse();
93+
});
94+
95+
it('onClickOutside: should call closeDropdown if click is outside and dropdown is open', () => {
96+
spyOn(component, 'closeDropdown');
97+
98+
component.isDropdownOpen = true;
99+
100+
const event = new MouseEvent('click', {
101+
view: window,
102+
bubbles: true,
103+
cancelable: true
104+
});
105+
106+
document.dispatchEvent(event);
107+
108+
expect(component.closeDropdown).toHaveBeenCalled();
109+
});
110+
111+
it('setDropdownPosition: should set dropdownStyles with correct values when rightPosition is positive', () => {
112+
component.setDropdownPosition();
113+
114+
const expectedStyles = {
115+
top: `${120 + 4 + 50}px`,
116+
maxWidth: '300px',
117+
right: `${300 - 100}px`
118+
};
119+
120+
expect(component.dropdownStyles).toEqual(expectedStyles);
121+
});
122+
123+
it('setDropdownPosition: should set dropdownStyles with correct values when rightPosition is zero', () => {
124+
const buttonElementRefMockZero = {
125+
nativeElement: {
126+
getBoundingClientRect: () => ({
127+
right: 350,
128+
bottom: 100
129+
}),
130+
closest: (selector: string) => ({
131+
getBoundingClientRect: () => ({
132+
width: 300,
133+
bottom: 120
134+
})
135+
})
136+
}
137+
};
138+
139+
component.button.buttonElement = buttonElementRefMockZero as ElementRef;
140+
141+
component.setDropdownPosition();
142+
143+
const expectedStyles = {
144+
top: `${120 + 4 + 50}px`,
145+
maxWidth: '300px',
146+
right: '0px'
147+
};
148+
149+
expect(component.dropdownStyles).toEqual(expectedStyles);
150+
});
53151
});
54152
});

projects/ui/src/lib/components/po-tabs/po-tab-dropdown/po-tab-dropdown.component.ts

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,13 @@
1-
import { Component, EventEmitter, Input, Output, ViewChild } from '@angular/core';
1+
import {
2+
AfterViewInit,
3+
Component,
4+
ElementRef,
5+
EventEmitter,
6+
HostListener,
7+
Input,
8+
Output,
9+
ViewChild
10+
} from '@angular/core';
211

312
import { PoPopoverComponent } from '../../po-popover/po-popover.component';
413
import { PoTabComponent } from '../po-tab/po-tab.component';
@@ -16,7 +25,7 @@ import { PoButtonComponent } from '../../po-button/po-button.component';
1625
selector: 'po-tab-dropdown',
1726
templateUrl: './po-tab-dropdown.component.html'
1827
})
19-
export class PoTabDropdownComponent {
28+
export class PoTabDropdownComponent implements AfterViewInit {
2029
@ViewChild('popover', { static: true }) popover: PoPopoverComponent;
2130

2231
@ViewChild(PoButtonComponent, { static: true }) button: PoButtonComponent;
@@ -39,12 +48,56 @@ export class PoTabDropdownComponent {
3948
// Evento de click
4049
@Output('p-click') click = new EventEmitter<any>();
4150

51+
isDropdownOpen: boolean = false;
52+
dropdownStyles: any = {};
53+
54+
constructor(private elementRef: ElementRef) {}
55+
56+
ngAfterViewInit(): void {
57+
this.setDropdownPosition();
58+
}
59+
60+
toggleDropdown() {
61+
this.isDropdownOpen = !this.isDropdownOpen;
62+
if (this.isDropdownOpen) {
63+
this.setDropdownPosition();
64+
}
65+
}
66+
4267
closeAndReturnToButtom() {
43-
this.popover.close();
68+
this.closeDropdown();
4469
this.button.focus();
4570
}
4671

72+
closeDropdown() {
73+
this.isDropdownOpen = false;
74+
}
75+
4776
get buttonElement() {
4877
return this.button.buttonElement;
4978
}
79+
80+
setDropdownPosition() {
81+
const buttonRect = this.buttonElement.nativeElement.getBoundingClientRect();
82+
const tabsContainerRect = this.buttonElement.nativeElement.closest('.po-tabs-container').getBoundingClientRect();
83+
const dropdownWidth = 300;
84+
85+
let rightPosition = tabsContainerRect.width - buttonRect.right;
86+
if (rightPosition < 0) {
87+
rightPosition = 0;
88+
}
89+
90+
this.dropdownStyles = {
91+
top: `${tabsContainerRect.bottom + 4 + window.scrollY}px`,
92+
maxWidth: `${dropdownWidth}px`,
93+
right: `${rightPosition}px`
94+
};
95+
}
96+
97+
@HostListener('document:click', ['$event'])
98+
onClickOutside(event: MouseEvent) {
99+
if (this.isDropdownOpen && !this.elementRef.nativeElement.contains(event.target)) {
100+
this.closeDropdown();
101+
}
102+
}
50103
}

projects/ui/src/lib/components/po-tabs/po-tabs.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
[p-label]="tab.label"
1515
[p-small]="small"
1616
[id]="tab.id"
17-
(keyup.enter)="closePopover()"
17+
(keyup.enter)="closeListbox()"
1818
(p-activated)="onTabActive(tab)"
1919
(p-change-state)="onTabChangeState(tab)"
2020
(p-click)="selectedTab(tab)"

projects/ui/src/lib/components/po-tabs/po-tabs.component.spec.ts

Lines changed: 12 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -198,34 +198,30 @@ describe('PoTabsComponent:', () => {
198198
expect(tab.active).toBeFalsy();
199199
});
200200

201-
it(`closePopover: should call 'popover.close' if 'popover.isHidden' is 'false'.`, () => {
201+
it(`closeListbox: should call 'closeDropdown' if 'isDropdownOpen' is 'true'.`, () => {
202202
const fakeThis = {
203203
tabDropdown: {
204-
popover: {
205-
isHidden: false,
206-
close: () => {}
207-
}
204+
isDropdownOpen: true,
205+
closeDropdown: () => {}
208206
}
209207
};
210-
const spyOnClose = spyOn(fakeThis.tabDropdown.popover, 'close');
208+
const spyOnCloseDropdown = spyOn(fakeThis.tabDropdown, 'closeDropdown');
211209

212-
component.closePopover.call(fakeThis);
213-
expect(spyOnClose).toHaveBeenCalled();
210+
component.closeListbox.call(fakeThis);
211+
expect(spyOnCloseDropdown).toHaveBeenCalled();
214212
});
215213

216-
it(`closePopover: shouldn't call 'popover.close' if 'popover.isHidden' is 'true'.`, () => {
214+
it(`closeListbox: shouldn't call 'closeDropdown' if 'isDropdownOpen' is 'false'.`, () => {
217215
const fakeThis = {
218216
tabDropdown: {
219-
popover: {
220-
isHidden: true,
221-
close: () => {}
222-
}
217+
isDropdownOpen: false,
218+
closeDropdown: () => {}
223219
}
224220
};
225-
const spyOnClose = spyOn(fakeThis.tabDropdown.popover, 'close');
221+
const spyOnCloseDropdown = spyOn(fakeThis.tabDropdown, 'closeDropdown');
226222

227-
component.closePopover.call(fakeThis);
228-
expect(spyOnClose).not.toHaveBeenCalled();
223+
component.closeListbox.call(fakeThis);
224+
expect(spyOnCloseDropdown).not.toHaveBeenCalled();
229225
});
230226

231227
it('deactivateTab: should deactive `previousActiveTab` if it`s truthy', () => {
@@ -645,20 +641,4 @@ describe('PoTabsComponent:', () => {
645641
expect(component.defaultLastTabWidth).toBe(0);
646642
});
647643
});
648-
649-
describe('Templates:', () => {
650-
it('should call `closePopover` if `enter` is pressed in `po-tab-button`.', () => {
651-
spyOnProperty(component, 'tabs').and.returnValue([{ id: '0', label: '0' }]);
652-
653-
fixture.detectChanges();
654-
655-
const eventEnterKey = new KeyboardEvent('keyup', { 'key': 'Enter' });
656-
const poTabButton = fixture.debugElement.query(By.css('.po-tab-button')).nativeElement;
657-
const spyOnClosePopover = spyOn(component, 'closePopover');
658-
659-
poTabButton.dispatchEvent(eventEnterKey);
660-
661-
expect(spyOnClosePopover).toHaveBeenCalled();
662-
});
663-
});
664644
});

projects/ui/src/lib/components/po-tabs/po-tabs.component.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -154,11 +154,9 @@ export class PoTabsComponent extends PoTabsBaseComponent implements OnInit, Afte
154154
return this.tabsChildren.toArray();
155155
}
156156

157-
closePopover(): void {
158-
const containsPopoverVisible = this.tabDropdown && this.tabDropdown.popover && !this.tabDropdown.popover.isHidden;
159-
160-
if (containsPopoverVisible) {
161-
this.tabDropdown.popover.close();
157+
closeListbox(): void {
158+
if (this.tabDropdown && this.tabDropdown.isDropdownOpen) {
159+
this.tabDropdown.closeDropdown();
162160
}
163161
}
164162

0 commit comments

Comments
 (0)