Skip to content

Commit

Permalink
feat(breadcrumbs): initial implementation *experimental* (#1183)
Browse files Browse the repository at this point in the history
* feat(breadcrumbs): Initial scaffolding for breadcrumbs

* feat(breadcrumbs): Checkpoint checkin with breadcrumbs showing up

* feat(breadcrumbs): code cleanup and adding graying out to last element

* feat(breadcrumbs): Adding themes to experimental area. Using scss to style breadcrumbs

* add to demo

* feat(breadcrumbs): Responsive Breadcrumbs, removes the left-most crumb as the window shrinks

* Adding sandbox area with routes to test-beds

* Using ngAfterContentChecked lifecycle method

* Adding title to demo

* Update documentation Readme with usage

* Moving tab-select into separate demo directory

* Unit Tests for Breadcrumbs

* Unit Tests for Breadcrumbs

* chore(breadcrumbs): Update code from code review

* fix(breadcrumb): Fix unit test

* chore(breadcrumbs): Use Subscription.EMPTY to avoid needing null checks

* chore(breadcrumbs): remove console.log

* chore(): updates on README

 Showcase get/set methods as properties since thats how they are used

* chore(breadcrumbs): remove media usage from breadcrumbs to reduce noise

* fix(breadcrumbs): stop click event on the breadcrumb separator

this is done because the navigation or event shouldnt be triggered in that scenario
  • Loading branch information
jeremysmartt authored and emoralesb05 committed Jul 13, 2018
1 parent d9d8055 commit 256491e
Show file tree
Hide file tree
Showing 30 changed files with 712 additions and 76 deletions.
89 changes: 89 additions & 0 deletions src/platform/experimental/breadcrumbs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# td-breadcrumbs (experimental)

`td-breadcrumbs` element generates breadcrumbs for navigation. Handles Responsive by removing breadcrumbs from the beginning of the list as allowable space decreases.

## API Summary

#### Inputs

+ separatorIcon?: string
+ Sets the icon url shown between breadcrumbs. Defaults to right chevron.

#### Methods

+ count: number
+ The total count of individual breadcrumbs (read only)

#### Attributes

+ hiddenBreadcrumbs: TdBreadcrumbComponent[]
+ Array of currently hidden breadcrumbs (responsive)

# td-breadcrumb

`td-breadcrumb` element generates an individual breadcrumb component.

## API Summary

#### Methods

+ displayCrumb: boolean
+ Whether to display the individual breadcrumb or not
+ width: number
+ The current width of the individual breadcrumb (read only)

## Setup

Import the [CovalentBreadcrumbsModule] in your NgModule:

```typescript
import { CovalentBreadcrumbsModule } from '@covalent/experimental/breadcrumbs';
@NgModule({
imports: [
CovalentBreadcrumbsModule,
...
],
...
})
export class MyModule {}
```

## Usage

Basic Example:

```html
<td-breadcrumbs class="pad-left">
<a td-breadcrumb [routerLink]="'/'">Home</a>
<a td-breadcrumb [routerLink]="'/layouts'">Layouts</a>
<a td-breadcrumb [routerLink]="'/layouts2'">Layouts2</a>
<a td-breadcrumb [routerLink]="'/layouts3'">Layouts3</a>
<td-breadcrumb class="tc-grey-500">Manage List</td-breadcrumb>
</td-breadcrumbs>
```

Example with all inputs/outputs:

```html
<td-breadcrumbs #breadcrumbs separatorIcon="motorcycle">
<a td-breadcrumb [routerLink]="'/'">Home</a>
<a td-breadcrumb [routerLink]="'/layouts'">Layouts</a>
<a td-breadcrumb [routerLink]="'/layouts2'">Layouts2</a>
<a td-breadcrumb [routerLink]="'/layouts3'">Layouts3</a>
<td-breadcrumb class="tc-grey-500">Manage List</td-breadcrumb>
</td-breadcrumbs>
<mat-divider></mat-divider>
<div>
Total Breadcrumbs Count: {{breadcrumbs.count}}
</div>
<div>
Hidden Breadcrumbs Count (shrink window to see): {{breadcrumbs.hiddenBreadcrumbs.length}}
</div>
<ng-template let-breadcrumb let-index="index" ngFor [ngForOf]="breadcrumbs.hiddenBreadcrumbs">
<div>
<p>Breadcrumb Number: {{index}}</p>
<p>Breadcrumb Width: {{breadcrumb?.width}}</p>
<mat-divider></mat-divider>
</div>
</ng-template>
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@

@mixin td-breadcrumb-theme($theme) {
$foreground: map-get($theme, foreground);

td-breadcrumb {
&:last-of-type {
color: mat-color($foreground, disabled);
cursor: default;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<span *ngIf="displayCrumb" class="td-breadcrumb">
<ng-content></ng-content>
<mat-icon *ngIf="_displayIcon"
[style.cursor]="'default'"
(click)="_handleIconClick($event)">
{{separatorIcon}}
</mat-icon>
</span>
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
:host {
.td-breadcrumb {
height: 48px;
box-sizing: border-box;
flex-direction: row;
align-items: center;
align-content: center;
max-width: 100%;
justify-content: flex-end;
::ng-deep > * {
margin: 0 10px;
}
}
mat-icon.material-icons.mat-icon {
display: inline-flex;
vertical-align: middle;
}
&.mat-button {
min-width: 0;
padding: 0;
}
}
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import {
Component,
ElementRef,
Renderer2,
HostBinding,
AfterViewInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
} from '@angular/core';

@Component({
selector: 'td-breadcrumb, a[td-breadcrumb]',
styleUrls: ['./breadcrumb.component.scss'],
templateUrl: './breadcrumb.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class TdBreadcrumbComponent implements AfterViewInit {

private _displayCrumb: boolean = true;
private _width: number = 0;
// Sets the icon url shown between breadcrumbs. Defaults to right chevron
separatorIcon: string = 'navigate_next';
// Should show the right chevron or not before the label
_displayIcon: boolean = true;

get displayCrumb(): boolean {
return this._displayCrumb;
}

/**
* Whether to display the crumb or not
*/
set displayCrumb(shouldDisplay: boolean) {
this._displayCrumb = shouldDisplay;
this._changeDetectorRef.markForCheck();
}

/**
* Width of the DOM element of the crumb
*/
get width(): number {
return this._width;
}

// Set the display to none on the component, just in case the end user is hiding
// and showing them instead of the component doing itself for reasons like responsive
@HostBinding('style.display')
private get displayBinding(): string {
return this._displayCrumb ? undefined : 'none';
}

constructor(private _elementRef: ElementRef,
private _renderer: Renderer2,
private _changeDetectorRef: ChangeDetectorRef) {
this._renderer.addClass(this._elementRef.nativeElement, 'mat-button');
}

ngAfterViewInit(): void {
// set the width from the actual rendered DOM element
this._width = (<HTMLElement>this._elementRef.nativeElement).getBoundingClientRect().width;
this._changeDetectorRef.markForCheck();
}

/**
* Stop click propagation when clicking on icon
*/
_handleIconClick(event: Event): void {
event.stopPropagation();
event.preventDefault();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<div class="td-breadcrumbs">
<ng-content></ng-content>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
:host {
.td-breadcrumbs {
white-space: nowrap;
}
}
106 changes: 106 additions & 0 deletions src/platform/experimental/breadcrumbs/breadcrumbs.component.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import {
TestBed,
inject,
async,
ComponentFixture,
} from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import {
Component,
DebugElement,
} from '@angular/core';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { By } from '@angular/platform-browser';
import {
CovalentBreadcrumbsModule,
} from './public-api';
import {
TdBreadcrumbsComponent,
} from './breadcrumbs.component';

@Component({
selector: 'fake',
template: `<router-outlet></router-outlet><div>fake</div>`,
})
export class FakeComponent {
}

describe('Component: Breadcrumbs', () => {

beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
TdBreadcrumbsTestComponent,
FakeComponent,
],
imports: [
NoopAnimationsModule,
RouterTestingModule.withRoutes([
{ path: '', component: FakeComponent },
{ path: 'layouts', component: FakeComponent },
{ path: 'layouts2', component: FakeComponent },
{ path: 'layouts3', component: FakeComponent },
]),
CovalentBreadcrumbsModule,
],
});
TestBed.compileComponents();
}));

it('should render 5 Breadcrumbs',
async(inject([], () => {
let fixture: ComponentFixture<any> = TestBed.createComponent(TdBreadcrumbsTestComponent);
fixture.detectChanges();
fixture.whenStable().then(() => {
let breadcrumbs: TdBreadcrumbsComponent = fixture.debugElement.query(By.directive(TdBreadcrumbsComponent)).componentInstance;
expect(breadcrumbs.count).toBe(5);
});
}),
));

it('should change the separatorIcon',
async(inject([], () => {
let fixture: ComponentFixture<any> = TestBed.createComponent(TdBreadcrumbsTestComponent);
let component: TdBreadcrumbsTestComponent = fixture.debugElement.componentInstance;
component.separatorIcon = 'flight_land';
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(fixture.debugElement.queryAll(By.css('.td-breadcrumb'))[1].nativeElement.innerHTML.indexOf('flight_land')).toBeGreaterThan(-1);
});
}),
));

it('should resize window and hide breadcrumbs',
async(inject([], () => {
let fixture: ComponentFixture<any> = TestBed.createComponent(TdBreadcrumbsTestComponent);
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.debugElement.query(By.directive(TdBreadcrumbsComponent)).nativeElement.parentElement.style.width = '300px';
window.dispatchEvent(new Event('resize'));
fixture.detectChanges();
fixture.whenStable().then(() => {
let breadcrumbs: TdBreadcrumbsComponent = fixture.debugElement.query(By.directive(TdBreadcrumbsComponent)).componentInstance;
expect(breadcrumbs.hiddenBreadcrumbs.length).toBe(2);
});
});
}),
));
});

@Component({
selector: 'td-breadcrumbs-test',
template: `
<div style="width: {{width}}">
<td-breadcrumbs #breadcrumbs class="pad-left" separatorIcon="{{separatorIcon}}">
<a td-breadcrumb [routerLink]="'/'">Home</a>
<a td-breadcrumb [routerLink]="'/layouts'">Layouts</a>
<a td-breadcrumb [routerLink]="'/layouts2'">Layouts2</a>
<a td-breadcrumb [routerLink]="'/layouts3'">Layouts3</a>
<td-breadcrumb class="tc-grey-500">Manage List</td-breadcrumb>
</td-breadcrumbs>
</div>
`,
})
class TdBreadcrumbsTestComponent {
separatorIcon: string = 'motorcycle';
}
Loading

0 comments on commit 256491e

Please sign in to comment.