-
Notifications
You must be signed in to change notification settings - Fork 357
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(breadcrumbs) *experimental* #1183
Changes from 15 commits
60462cd
09b4339
f07235a
6225fec
00872d7
1f8d71a
99d4d43
e1289b2
2ff7bb2
e238436
993ab15
d00587d
d1c0325
adccda6
9ce8512
45f2740
7d0ca24
9bd7209
560d290
95aef11
29f1310
e2c8e9d
fd3799d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
# 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: function(): number | ||
+ The total count of individual breadcrumbs | ||
|
||
#### Attributes | ||
|
||
+ hiddenBreadcrumbs: TdBreadcrumbComponent[] | ||
+ Array of currently hidden breadcrumbs (responsive) | ||
|
||
# td-breadcrumb | ||
|
||
`td-breadcrumb` element generates an individual breadcrumb component. | ||
|
||
## API Summary | ||
|
||
#### Methods | ||
|
||
+ displayIcon: function(): boolean | ||
+ Getter for whether to display the spacer icon for the individual breadcrumb or not | ||
+ displayIcon: function(shouldDisplay: boolean): void | ||
+ Setter for whether to display the spacer icon for the individual breadcrumb or not | ||
+ displayCrumb: function(): boolean | ||
+ Getter for whether to display the individual breadcrumb or not | ||
+ displayCrumb: function(shouldDisplay: boolean): void | ||
+ Setter for whether to display the individual breadcrumb or not | ||
+ width: function(): number | ||
+ The current width of the individual breadcrumb | ||
|
||
## 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 | ||
<div layout-gt-sm="row" tdMediaToggle="gt-xs" [mediaClasses]="['push-sm']"> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Lets remove this div and just keep the example with the component usage. |
||
<td-breadcrumbs #breadcrumbs class="pad-left" 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> | ||
</div> | ||
<mat-divider></mat-divider> | ||
<div layout-gt-sm="row" tdMediaToggle="gt-xs" [mediaClasses]="['push-sm']"> | ||
Total Breadcrumbs Count: {{breadcrumbs.count}} | ||
</div> | ||
<mat-divider></mat-divider> | ||
<div layout-gt-sm="row" tdMediaToggle="gt-xs" [mediaClasses]="['push-sm']"> | ||
Hidden Breadcrumbs Count (shrink window to see): | ||
</div> | ||
<div layout-gt-sm="row" tdMediaToggle="gt-xs" [mediaClasses]="['push-sm']"> | ||
{{breadcrumbs.hiddenBreadcrumbs.length}} | ||
</div> | ||
<ng-template let-breadcrumb let-index="index" ngFor [ngForOf]="breadcrumbs.hiddenBreadcrumbs"> | ||
<div layout-gt-sm="row" tdMediaToggle="gt-xs" [mediaClasses]="['push-sm']"> | ||
<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,4 @@ | ||
<span *ngIf="displayCrumb" class="td-breadcrumb"> | ||
<mat-icon *ngIf="displayIcon">{{separatorIcon}}</mat-icon> | ||
<ng-content></ng-content> | ||
</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; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import { Component, ElementRef, Renderer2, HostBinding, Host, AfterViewInit } from '@angular/core'; | ||
|
||
@Component({ | ||
selector: 'td-breadcrumb, a[td-breadcrumb]', | ||
styleUrls: ['./breadcrumb.component.scss'], | ||
templateUrl: './breadcrumb.component.html', | ||
}) | ||
export class TdBreadcrumbComponent implements AfterViewInit { | ||
|
||
// Should show the right chevron or not before the label | ||
private _displayIcon: boolean = true; | ||
// Whether to display the crumb or not | ||
private _displayCrumb: boolean = true; | ||
// Width of the DOM element of the crumb | ||
private _width: number = 0; | ||
// Sets the icon url shown between breadcrumbs. Defaults to right chevron | ||
separatorIcon: string = 'navigate_next'; | ||
|
||
get displayIcon(): boolean { | ||
return this._displayIcon; | ||
} | ||
|
||
set displayIcon(shouldDisplay: boolean) { | ||
this._displayIcon = shouldDisplay; | ||
} | ||
|
||
get displayCrumb(): boolean { | ||
return this._displayCrumb; | ||
} | ||
|
||
set displayCrumb(shouldDisplay: boolean) { | ||
this._displayCrumb = shouldDisplay; | ||
} | ||
|
||
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) { | ||
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; | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
<span class="td-breadcrumbs"> | ||
<ng-content></ng-content> | ||
</span> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
:host { | ||
.td-breadcrumbs { | ||
white-space: nowrap; | ||
} | ||
} |
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([ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there any reason to use mock routes? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. just so the test doesn't fail with the routherlink in there |
||
{ 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 = '50px'; | ||
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'; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if we state that these properties are public. Since we use them internally to change the state of it.. which we should probably add a
_
as a prefix, since that is the standard forinternal
properties.