Skip to content

Commit

Permalink
feat(core): migrate breadcrumbs component
Browse files Browse the repository at this point in the history
  • Loading branch information
N1XUS committed Jul 21, 2022
1 parent e79e943 commit f45308c
Show file tree
Hide file tree
Showing 24 changed files with 810 additions and 453 deletions.
2 changes: 1 addition & 1 deletion e2e/wdio/core/pages/dynamic-page.po.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export class DynamicPagePo extends CoreBaseComponentPo {
flexibleColumn = '.fd-flexible-column-layout__column ';
article = '.fd-dynamic-page-section-example';
breadcrumbLink = '.fd-dynamic-page__breadcrumb-wrapper a';
currentBreadcrumbLink = '.fd-dynamic-page__breadcrumb-wrapper fd-breadcrumb-item:last-child span';
currentBreadcrumbLink = '.fd-dynamic-page__breadcrumb-wrapper .fd-overflow-layout__item--last span';

open(): void {
super.open(this.url);
Expand Down
82 changes: 56 additions & 26 deletions libs/core/src/lib/breadcrumb/breadcrumb-item.component.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { DomPortal } from '@angular/cdk/portal';
import {
AfterViewInit,
ChangeDetectorRef,
ChangeDetectionStrategy,
Component,
ContentChild,
ElementRef,
forwardRef,
Renderer2
ViewEncapsulation
} from '@angular/core';
import { LinkComponent } from '@fundamental-ngx/core/link';
import { DomPortal } from '@angular/cdk/portal';

/**
* Breadcrumb item directive. Must have child breadcrumb link directives.
Expand All @@ -21,54 +21,84 @@ import { DomPortal } from '@angular/cdk/portal';
*/
@Component({
selector: 'fd-breadcrumb-item',
template: '<div style="display: inline"><ng-content></ng-content></div>',
template: '<ng-content></ng-content>',
host: {
class: 'fd-breadcrumb__item'
}
},
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class BreadcrumbItemComponent implements AfterViewInit {
/** @hidden */
get elementRef(): ElementRef<HTMLElement> {
return this._elementRef;
}

/** @hidden */
@ContentChild(forwardRef(() => LinkComponent))
breadcrumbLink: LinkComponent;

/** @hidden */
get width(): number {
return this._elementRef.nativeElement.getBoundingClientRect().width;
}

/** In case there is no link in Item and breadcrumb item is non-interactive, we move whole item content to menu item title */
breadcrumbItemPortal: DomPortal<Element>;

/** When breadcrumb item has link in it, we are moving link content to menu item title */
linkContentPortal: DomPortal;

constructor(
private _elementRef: ElementRef<HTMLElement>,
private renderer2: Renderer2,
private _cdR: ChangeDetectorRef
) {}
/**
* Breadcrumb item dom portal.
*/
portal: DomPortal;

/** @hidden */
private _attached = false;

/** @hidden */
constructor(public readonly elementRef: ElementRef<HTMLElement>) {}

/** @hidden */
get needsClickProxy(): boolean {
get _needsClickProxy(): boolean {
return (
!!this.breadcrumbLink?.elementRef().nativeElement.getAttribute('href') || !!this.breadcrumbLink.routerLink
);
}

show = (): void => this.renderer2.setStyle(this._elementRef.nativeElement, 'display', 'inline-block');
hide = (): void => this.renderer2.setStyle(this._elementRef.nativeElement, 'display', 'none');

/** @hidden */
ngAfterViewInit(): void {
if (this.breadcrumbLink) {
this._attach();
}

/**
* Sets breadcrumb item dom portal.
*/
setPortal(): void {
if (!this.portal) {
this.portal = new DomPortal(this.elementRef);
}
}

/** @hidden */
_detach(): void {
if (!this._attached) {
return;
}

if (this.linkContentPortal?.isAttached) {
this.linkContentPortal?.detach();
}

if (this.breadcrumbItemPortal?.isAttached) {
this.breadcrumbItemPortal?.detach();
}

this._attached = false;
}

/** @hidden */
_attach(): void {
if (this._attached) {
return;
}

if (this.breadcrumbLink && this.breadcrumbLink.contentSpan) {
this.linkContentPortal = new DomPortal<HTMLElement>(this.breadcrumbLink.contentSpan.nativeElement);
}

this.breadcrumbItemPortal = new DomPortal(this.elementRef.nativeElement.firstElementChild as Element);
this._cdR.detectChanges();
this._attached = true;
}
}
114 changes: 68 additions & 46 deletions libs/core/src/lib/breadcrumb/breadcrumb.component.html
Original file line number Diff line number Diff line change
@@ -1,52 +1,74 @@
<fd-menu #menu [closeOnEscapeKey]="true" [focusAutoCapture]="true" [placement]="_placement$ | async">
<ng-container *ngFor="let breadcrumbItem of _collapsedBreadcrumbItems">
<li fd-menu-item [disabled]="breadcrumbItem.breadcrumbLink ? breadcrumbItem.breadcrumbLink.disabled : false">
<fd-overflow-layout
[reverseHiddenItems]="reverse"
showMorePosition="left"
[enableKeyboardNavigation]="false"
(visibleItemsCount)="_onVisibleItemsCountChange($event)"
(hiddenItemsCount)="_onHiddenItemsCountChange($event)"
>
<ng-container *ngFor="let breadcrumb of _items">
<div
*fdOverflowItemRef="breadcrumb; let hidden"
fdOverflowLayoutItem
(hiddenChange)="_onHiddenChange($event, breadcrumb)"
>
<ng-template [cdkPortalOutlet]="breadcrumb.portal"></ng-template>
</div>
</ng-container>
<ng-container *fdOverflowExpand="let breadcrumbs; items: _items">
<fd-menu #menu [closeOnEscapeKey]="true" [focusAutoCapture]="true" [placement]="_placement$ | async">
<ng-container *ngFor="let breadcrumbItem of breadcrumbs">
<li
fd-menu-item
[disabled]="
breadcrumbItem.item.breadcrumbLink ? breadcrumbItem.item.breadcrumbLink.disabled : false
"
>
<a
fd-menu-interactive
(click)="itemClicked(breadcrumbItem.item, $event)"
(keydown.enter)="itemClicked(breadcrumbItem.item, $event)"
(keydown.space)="itemClicked(breadcrumbItem.item, $event)"
>
<ng-container *ngIf="breadcrumbItem?.item.breadcrumbLink; else portalContent">
<ng-container *ngIf="breadcrumbItem.item.breadcrumbLink._prefixIconName">
<fd-menu-addon
position="before"
[glyph]="breadcrumbItem.item.breadcrumbLink._prefixIconName"
></fd-menu-addon>
</ng-container>
<span fd-menu-title>
<ng-container [cdkPortalOutlet]="breadcrumbItem.item.linkContentPortal"></ng-container>
</span>
<ng-container *ngIf="breadcrumbItem.item.breadcrumbLink._postfixIconName">
<fd-menu-addon
[glyph]="breadcrumbItem.item.breadcrumbLink._postfixIconName"
></fd-menu-addon>
</ng-container>
</ng-container>
<ng-template #portalContent>
<span fd-menu-title>
<ng-container
[cdkPortalOutlet]="breadcrumbItem.item.breadcrumbItemPortal"
></ng-container>
</span>
</ng-template>
</a>
</li>
</ng-container>
</fd-menu>
<span class="fd-breadcrumb__item" *ngIf="breadcrumbs.length > 0" [fdMenuTrigger]="menu">
<a
fd-menu-interactive
(click)="itemClicked(breadcrumbItem, $event)"
(keydown.enter)="itemClicked(breadcrumbItem, $event)"
(keydown.space)="itemClicked(breadcrumbItem, $event)"
fd-link
tabindex="0"
class="fd-breadcrumb__collapsed"
(keydown.enter)="_keyDownHandle($event)"
(keydown.space)="_keyDownHandle($event)"
>
<ng-container *ngIf="breadcrumbItem?.breadcrumbLink; else portalContent">
<ng-container *ngIf="breadcrumbItem.breadcrumbLink._prefixIconName">
<fd-menu-addon
position="before"
[glyph]="breadcrumbItem.breadcrumbLink._prefixIconName"
></fd-menu-addon>
</ng-container>
<span fd-menu-title>
<ng-container [cdkPortalOutlet]="breadcrumbItem.linkContentPortal"></ng-container>
</span>
<ng-container *ngIf="breadcrumbItem.breadcrumbLink._postfixIconName">
<fd-menu-addon [glyph]="breadcrumbItem.breadcrumbLink._postfixIconName"></fd-menu-addon>
</ng-container>
</ng-container>
<ng-template #portalContent>
<span fd-menu-title>
<ng-container [cdkPortalOutlet]="breadcrumbItem.breadcrumbItemPortal"></ng-container>
</span>
</ng-template>
<fd-icon glyph="slim-arrow-down"></fd-icon>
</a>
</li>
</span>
</ng-container>
</fd-menu>

<span
class="fd-breadcrumb__item"
*ngIf="_collapsedBreadcrumbItems.length"
[fdMenuTrigger]="menu"
#overflowBreadcrumbsContainer
>
<a
fd-link
tabindex="0"
class="fd-breadcrumb__collapsed"
(keydown.enter)="_keyDownHandle($event)"
(keydown.space)="_keyDownHandle($event)"
>
...
<fd-icon glyph="slim-arrow-down"></fd-icon>
</a>
</span>
</fd-overflow-layout>

<ng-content></ng-content>
10 changes: 9 additions & 1 deletion libs/core/src/lib/breadcrumb/breadcrumb.component.scss
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
@import '~fundamental-styles/dist/breadcrumb';

.fd-breadcrumb {
display: inline-block;
display: flex;
white-space: nowrap;

.fd-breadcrumb__collapsed {
cursor: pointer;
}
}

.fd-breadcrumb__item:last-child::after {
content: '/';
}

.fd-overflow-layout__item--last .fd-breadcrumb__item::after {
content: none;
}
42 changes: 27 additions & 15 deletions libs/core/src/lib/breadcrumb/breadcrumb.component.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { PortalModule } from '@angular/cdk/portal';
import { Component, NO_ERRORS_SCHEMA, ViewChild } from '@angular/core';
import { RouterModule } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';

import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { ComponentFixture, fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing';
import { OverflowLayoutModule } from '@fundamental-ngx/core/overflow-layout';
import { PopoverModule } from '@fundamental-ngx/core/popover';
import { MenuModule } from '@fundamental-ngx/core/menu';
import { IconModule } from '@fundamental-ngx/core/icon';
Expand Down Expand Up @@ -40,7 +42,16 @@ describe('BreadcrumbComponent', () => {
waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [BreadcrumbComponent, BreadcrumbItemComponent, BreadcrumbWrapperComponent],
imports: [PopoverModule, MenuModule, IconModule, LinkModule, RouterModule, RouterTestingModule],
imports: [
PopoverModule,
MenuModule,
IconModule,
LinkModule,
RouterModule,
RouterTestingModule,
OverflowLayoutModule,
PortalModule
],
providers: [RtlService],
schemas: [NO_ERRORS_SCHEMA]
}).compileComponents();
Expand All @@ -59,25 +70,26 @@ describe('BreadcrumbComponent', () => {
expect(component).toBeTruthy();
});

it('should handle onResize - enlarging the screen', async () => {
spyOn(component.elementRef.nativeElement.parentElement as Element, 'getBoundingClientRect').and.returnValue({
width: component.elementRef.nativeElement.getBoundingClientRect().width + 100
} as any);
it('should handle onResize - enlarging the screen', fakeAsync(() => {
const hiddenItemsCountSpy = spyOn(component, '_onHiddenItemsCountChange').and.callThrough();

component.elementRef.nativeElement.parentElement!.style.width = '500px';
component.onResize();

await whenStable(fixture);
tick(1000);

expect(component._collapsedBreadcrumbItems.length).toBe(0);
});
expect(hiddenItemsCountSpy).toHaveBeenCalledWith(0);
}));

it('should handle onResize - shrinking the screen', () => {
spyOn(component.elementRef.nativeElement.parentElement as Element, 'getBoundingClientRect').and.returnValue({
width: component.elementRef.nativeElement.getBoundingClientRect().width / 2
} as any);
it('should handle onResize - shrinking the screen', fakeAsync(() => {
const hiddenItemsCountSpy = spyOn(component, '_onHiddenItemsCountChange').and.callThrough();

component.elementRef.nativeElement.parentElement!.style.width = '200px';
component.onResize();
fixture.detectChanges();

expect(component._collapsedBreadcrumbItems.length).toBeGreaterThan(1);
});
tick(1000);

expect(hiddenItemsCountSpy).toHaveBeenCalledWith(2);
}));
});
Loading

0 comments on commit f45308c

Please sign in to comment.