Skip to content

Commit 26d04ad

Browse files
authored
feat: integrate floating-ui with popover component (#2785)
Signed-off-by: Akshat Patel <akshat@live.ca>
1 parent 73a2ed9 commit 26d04ad

17 files changed

+944
-343
lines changed

package-lock.json

Lines changed: 151 additions & 104 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@
6767
"@angular/common": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0",
6868
"@angular/core": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0",
6969
"@angular/forms": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0",
70-
"@carbon/styles": "^1.48.1",
70+
"@carbon/styles": "^1.54.0",
7171
"rxjs": "^6.0.0 || ^7.0.0",
7272
"zone.js": "^0.11.0"
7373
},
@@ -85,7 +85,7 @@
8585
"@angular/platform-browser-dynamic": "14.3.0",
8686
"@angular/router": "14.3.0",
8787
"@babel/core": "7.9.6",
88-
"@carbon/styles": "1.48.1",
88+
"@carbon/styles": "1.54.0",
8989
"@carbon/themes": "11.24.0",
9090
"@commitlint/cli": "17.0.3",
9191
"@commitlint/config-conventional": "17.0.3",
@@ -142,6 +142,7 @@
142142
"@carbon/icon-helpers": "10.37.0",
143143
"@carbon/icons": "11.14.0",
144144
"@carbon/utils-position": "1.1.4",
145+
"@floating-ui/dom": "1.6.3",
145146
"@ibm/telemetry-js": "^1.2.1",
146147
"flatpickr": "4.6.13",
147148
"tslib": "2.3.0"

src/ng-package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@
66
},
77
"allowedNonPeerDependencies": [
88
"@carbon/utils-position",
9-
"flatpickr",
109
"@carbon/icon-helpers",
1110
"@carbon/icons",
12-
"@carbon/telemetry"
11+
"@carbon/telemetry",
12+
"@floating-ui/dom",
13+
"flatpickr"
1314
]
1415
}

src/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,14 @@
1313
"license": "Apache-2.0",
1414
"author": "IBM",
1515
"peerDependencies": {
16-
"@carbon/styles": "^1.5.0"
16+
"@carbon/styles": "^1.54.0"
1717
},
1818
"dependencies": {
1919
"@carbon/icon-helpers": "10.37.0",
2020
"@carbon/icons": "11.14.0",
2121
"@carbon/telemetry": "0.1.0",
2222
"@carbon/utils-position": "1.1.4",
23+
"@floating-ui/dom": "1.6.3",
2324
"flatpickr": "4.6.13",
2425
"tslib": "2.3.0"
2526
}
Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,40 @@
1-
import { Component, HostBinding } from "@angular/core";
1+
import {
2+
Component,
3+
HostBinding,
4+
ViewChild,
5+
ElementRef,
6+
AfterViewInit,
7+
ChangeDetectorRef
8+
} from "@angular/core";
29

310
/**
411
* [See demo](../../?path=/story/components-popover--basic)
512
*/
613
@Component({
714
selector: "cds-popover-content, ibm-popover-content",
815
template: `
9-
<span class="cds--popover-content">
10-
<ng-content></ng-content>
16+
<span class="cds--popover-content" #content>
17+
<div>
18+
<ng-content></ng-content>
19+
</div>
20+
<span *ngIf="autoAlign" class="cds--popover-caret cds--popover--auto-align"></span>
1121
</span>
12-
<span class="cds--popover-caret"></span>
22+
<span *ngIf="!autoAlign" class="cds--popover-caret"></span>
1323
`
1424
})
15-
export class PopoverContent {
25+
export class PopoverContent implements AfterViewInit {
1626
@HostBinding("class.cds--popover") popoverClass = true;
27+
@ViewChild("content") popoverContent: ElementRef;
28+
autoAlign = false;
29+
30+
constructor(private changeDetectorRef: ChangeDetectorRef) {}
31+
32+
ngAfterViewInit(): void {
33+
if (this.popoverContent) {
34+
// Check we are in a popover with autoAlign enabled
35+
this.autoAlign = !!this.popoverContent.nativeElement.closest(".cds--popover--auto-align");
36+
// Run change detection manually to resolve ExpressionHasChanged
37+
this.changeDetectorRef.detectChanges();
38+
}
39+
}
1740
}

src/popover/popover.component.spec.ts

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { TestBed, ComponentFixture } from "@angular/core/testing";
2-
import { Component, Input } from "@angular/core";
2+
import { Component, DebugElement, Input } from "@angular/core";
33
import { By } from "@angular/platform-browser";
44

55
import { PopoverContainer, PopoverContent } from "./";
@@ -12,7 +12,8 @@ import { PopoverContainer, PopoverContent } from "./";
1212
[dropShadow]="dropShadow"
1313
[align]="align"
1414
[caret]="caret"
15-
[highContrast]="highContrast">
15+
[highContrast]="highContrast"
16+
[autoAlign]="autoAlign">
1617
<p>Popover trigger</p>
1718
<cds-popover-content>
1819
<div>
@@ -28,27 +29,31 @@ class TestPopoverComponent {
2829
@Input() align = "bottom";
2930
@Input() caret = true;
3031
@Input() highContrast = false;
32+
@Input() autoAlign = true;
3133
}
3234

3335
describe("Popover", () => {
3436
let fixture: ComponentFixture<TestPopoverComponent>;
3537
let component: TestPopoverComponent;
38+
let popoverContainerElement: DebugElement;
39+
let popoverDirectiveEl: PopoverContainer;
3640

3741
beforeEach(() => {
3842
TestBed.configureTestingModule({
3943
declarations: [TestPopoverComponent, PopoverContainer, PopoverContent]
4044
});
4145
fixture = TestBed.createComponent(TestPopoverComponent);
4246
component = fixture.componentInstance;
47+
popoverContainerElement = fixture.debugElement.query(By.directive(PopoverContainer));
48+
popoverDirectiveEl = popoverContainerElement.injector.get(PopoverContainer);
4349
fixture.detectChanges();
4450
});
4551

4652

4753
it("should create a popover container & content", () => {
4854
expect(component).toBeTruthy();
49-
const directiveEl = fixture.debugElement.query(By.directive(PopoverContainer));
50-
expect(directiveEl).not.toBeNull();
51-
expect(directiveEl.nativeElement.className.includes("cds--popover-container")).toBeTruthy();
55+
expect(popoverContainerElement).not.toBeNull();
56+
expect(popoverContainerElement.nativeElement.className.includes("cds--popover-container")).toBeTruthy();
5257

5358
const componentEl = fixture.debugElement.query(By.css("cds-popover-content"));
5459
expect(componentEl).not.toBeNull();
@@ -79,4 +84,17 @@ describe("Popover", () => {
7984
expect(directiveEl.className.includes("cds--popover--drop-shadow")).toBeFalsy();
8085
expect(directiveEl.className.includes("cds--popover--high-contrast")).toBeTruthy();
8186
});
87+
88+
it("should set auto alignment class to wrapper and caret", () => {
89+
expect(popoverContainerElement.nativeElement.classList.contains("cds--popover--auto-align")).toBeTruthy();
90+
expect(popoverContainerElement.nativeElement.querySelector(".cds--popover-caret.cds--popover--auto-align")).toBeDefined();
91+
});
92+
93+
it("should clean up auto placement on close when auto alignment is enabled", () => {
94+
spyOn(popoverDirectiveEl, "cleanUp");
95+
component.isOpen = true;
96+
fixture.detectChanges();
97+
component.isOpen = false;
98+
expect(popoverDirectiveEl.cleanUp).toHaveBeenCalled();
99+
});
82100
});

0 commit comments

Comments
 (0)