Skip to content

Commit

Permalink
feat(tabs): simplify api (#1645)
Browse files Browse the repository at this point in the history
  • Loading branch information
jelbourn authored Oct 29, 2016
1 parent 04e2201 commit ea6c817
Show file tree
Hide file tree
Showing 8 changed files with 174 additions and 74 deletions.
35 changes: 21 additions & 14 deletions src/demo-app/tabs/tabs-demo.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,11 @@ <h1>Tab Group Demo</h1>
<md-tab-group class="demo-tab-group">
<md-tab *ngFor="let tab of tabs; let i = index" [disabled]="i == 1">
<template md-tab-label>{{tab.label}}</template>
<template md-tab-content>
{{tab.content}}
<br>
<br>
<br>
<md-input placeholder="Tab Label" [(ngModel)]="tab.label"></md-input>
</template>
{{tab.content}}
<br>
<br>
<br>
<md-input placeholder="Tab Label" [(ngModel)]="tab.label"></md-input>
</md-tab>
</md-tab-group>

Expand All @@ -35,12 +33,21 @@ <h1>Async Tabs</h1>
<md-tab-group class="demo-tab-group">
<md-tab *ngFor="let tab of asyncTabs | async; let i = index" [disabled]="i == 1">
<template md-tab-label>{{tab.label}}</template>
<template md-tab-content>
{{tab.content}}
<br>
<br>
<br>
<md-input placeholder="Tab Label" [(ngModel)]="tab.label"></md-input>
</template>
{{tab.content}}
<br>
<br>
<br>
<md-input placeholder="Tab Label" [(ngModel)]="tab.label"></md-input>
</md-tab>
</md-tab-group>

<!-- Simple tabs api -->
<h1>Tabs with simplified api</h1>
<md-tab-group class="demo-tab-group">
<md-tab label="Earth">
This tab is about the Earth!
</md-tab>
<md-tab label="Fire">
This tab is about combustion!
</md-tab>
</md-tab-group>
12 changes: 6 additions & 6 deletions src/demo-app/tabs/tabs-demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,16 @@ import {Observable} from 'rxjs/Observable';
})
export class TabsDemo {
tabLinks = [
{ label: 'Sun', link: 'sunny-tab'},
{ label: 'Rain', link: 'rainy-tab'},
{ label: 'Fog', link: 'foggy-tab'},
{label: 'Sun', link: 'sunny-tab'},
{label: 'Rain', link: 'rainy-tab'},
{label: 'Fog', link: 'foggy-tab'},
];
activeLinkIndex = 0;

tabs = [
{ label: 'Tab One', content: 'This is the body of the first tab' },
{ label: 'Tab Two', content: 'This is the body of the second tab' },
{ label: 'Tab Three', content: 'This is the body of the third tab' },
{label: 'Tab One', content: 'This is the body of the first tab'},
{label: 'Tab Two', content: 'This is the body of the second tab'},
{label: 'Tab Three', content: 'This is the body of the third tab'},
];

asyncTabs: Observable<any>;
Expand Down
45 changes: 31 additions & 14 deletions src/lib/tabs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,28 +15,22 @@ Tab groups allow the user to organize their content by labels such that only one
| `focusChange` | `Event` | Fired when focus changes from one label to another |
| `selectChange` | `Event` | Fired when the selected tab changes |

### Examples
### Basic use
A basic tab group would have the following markup.
```html
<md-tab-group>
<md-tab>
<template md-tab-label>One</template>
<template md-tab-content>
<h1>Some tab content</h1>
<p>...</p>
</template>
<md-tab label="One">
<h1>Some tab content</h1>
<p>...</p>
</md-tab>
<md-tab>
<template md-tab-label>Two</template>
<template md-tab-content>
<h1>Some more tab content</h1>
<p>...</p>
</template>
<md-tab label="Two">
<h1>Some more tab content</h1>
<p>...</p>
</md-tab>
</md-tab-group>
```

It is also possible to specifiy the active tab by using the `selectedIndex` property.
You can specifiy the active tab by using the `selectedIndex` property.

```html
<md-tab-group [selectedIndex]="1">
Expand All @@ -45,3 +39,26 @@ It is also possible to specifiy the active tab by using the `selectedIndex` prop
```

**Note**: The index always starts counting from `zero`.


### Tabs with label templates
If you want to use an arbitrary template for your tab, you can use the `md-tab-label` directive to
provide the label template:
```html
<md-tab-group>
<md-tab>
<template md-tab-label>
The <em>best</em> pasta
</template>
<h1>Best pasta restaurants</h1>
<p>...</p>
</md-tab>
<md-tab>
<template md-tab-label>
<md-icon>thumb_down</md-icon> The worst sushi
</template>
<h1>Terrible sushi restaurants</h1>
<p>...</p>
</md-tab>
</md-tab-group>
```
12 changes: 0 additions & 12 deletions src/lib/tabs/tab-content.ts

This file was deleted.

9 changes: 8 additions & 1 deletion src/lib/tabs/tab-group.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,14 @@
[class.md-tab-active]="selectedIndex == i"
[class.md-tab-disabled]="tab.disabled"
(click)="focusIndex = selectedIndex = i">
<template [portalHost]="tab.label"></template>

<!-- If there is a label template, use it. -->
<template [ngIf]="tab.templateLabel">
<template [portalHost]="tab.templateLabel"></template>
</template>

<!-- If there is not a label template, fall back to the text label. -->
<template [ngIf]="!tab.templateLabel">{{tab.textLabel}}</template>
</div>
<md-ink-bar></md-ink-bar>
</div>
Expand Down
73 changes: 63 additions & 10 deletions src/lib/tabs/tab-group.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ describe('MdTabGroup', () => {
declarations: [
SimpleTabsTestApp,
AsyncTabsTestApp,
DisabledTabsTestApp
DisabledTabsTestApp,
TabGroupWithSimpleApi,
],
});

Expand Down Expand Up @@ -241,6 +242,37 @@ describe('MdTabGroup', () => {
}));
});

describe('with simple api', () => {
let fixture: ComponentFixture<TabGroupWithSimpleApi>;
let tabGroup: MdTabGroup;

beforeEach(() => {
fixture = TestBed.createComponent(TabGroupWithSimpleApi);
fixture.detectChanges();

tabGroup =
fixture.debugElement.query(By.directive(MdTabGroup)).componentInstance as MdTabGroup;
});

it('should support a tab-group with the simple api', () => {
expect(getSelectedLabel(fixture).textContent).toMatch('Junk food');
expect(getSelectedContent(fixture).textContent).toMatch('Pizza, fries');

tabGroup.selectedIndex = 2;
fixture.detectChanges();

expect(getSelectedLabel(fixture).textContent).toMatch('Fruit');
expect(getSelectedContent(fixture).textContent).toMatch('Apples, grapes');

fixture.componentInstance.otherLabel = 'Chips';
fixture.componentInstance.otherContent = 'Salt, vinegar';
fixture.detectChanges();

expect(getSelectedLabel(fixture).textContent).toMatch('Chips');
expect(getSelectedContent(fixture).textContent).toMatch('Salt, vinegar');
});
});

/**
* Checks that the `selectedIndex` has been updated; checks that the label and body have the
* `md-tab-active` class
Expand All @@ -260,26 +292,33 @@ describe('MdTabGroup', () => {
.query(By.css(`#${tabLabelElement.id}`)).nativeElement;
expect(tabContentElement.classList.contains('md-tab-active')).toBe(true);
}

function getSelectedLabel(fixture: ComponentFixture<any>): HTMLElement {
return fixture.nativeElement.querySelector('.md-tab-label.md-tab-active');
}

function getSelectedContent(fixture: ComponentFixture<any>): HTMLElement {
return fixture.nativeElement.querySelector('.md-tab-body.md-tab-active');
}
});

@Component({
selector: 'test-app',
template: `
<md-tab-group class="tab-group"
[(selectedIndex)]="selectedIndex"
(focusChange)="handleFocus($event)"
(selectChange)="handleSelection($event)">
<md-tab>
<template md-tab-label>Tab One</template>
<template md-tab-content>Tab one content</template>
Tab one content
</md-tab>
<md-tab>
<template md-tab-label>Tab Two</template>
<template md-tab-content>Tab two content</template>
Tab two content
</md-tab>
<md-tab>
<template md-tab-label>Tab Three</template>
<template md-tab-content>Tab three content</template>
Tab three content
</md-tab>
</md-tab-group>
`
Expand All @@ -302,28 +341,27 @@ class SimpleTabsTestApp {
<md-tab-group class="tab-group">
<md-tab>
<template md-tab-label>Tab One</template>
<template md-tab-content>Tab one content</template>
Tab one content
</md-tab>
<md-tab disabled>
<template md-tab-label>Tab Two</template>
<template md-tab-content>Tab two content</template>
Tab two content
</md-tab>
<md-tab>
<template md-tab-label>Tab Three</template>
<template md-tab-content>Tab three content</template>
Tab three content
</md-tab>
</md-tab-group>
`,
})
class DisabledTabsTestApp {}

@Component({
selector: 'test-app',
template: `
<md-tab-group class="tab-group">
<md-tab *ngFor="let tab of tabs | async">
<template md-tab-label>{{ tab.label }}</template>
<template md-tab-content>{{ tab.content }}</template>
{{ tab.content }}
</md-tab>
</md-tab-group>
`
Expand All @@ -343,3 +381,18 @@ class AsyncTabsTestApp {
});
}
}


@Component({
template: `
<md-tab-group>
<md-tab label="Junk food"> Pizza, fries </md-tab>
<md-tab label="Vegetables"> Broccoli, spinach </md-tab>
<md-tab [label]="otherLabel"> {{otherContent}} </md-tab>
</md-tab-group>
`
})
class TabGroupWithSimpleApi {
otherLabel = 'Fruit';
otherContent = 'Apples, grapes';
}
4 changes: 4 additions & 0 deletions src/lib/tabs/tab.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<!-- Create a template for the content of the <md-tab> so that we can grab a reference to this
TemplateRef and use it in a Portal to render the tab content in the appropriate place in the
tab-group. -->
<template><ng-content></ng-content></template>
58 changes: 41 additions & 17 deletions src/lib/tabs/tabs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,29 @@ import {
NgModule,
ModuleWithProviders,
ContentChild,
Directive,
ViewChild,
Component,
Input,
Output,
ViewChildren,
NgZone,
EventEmitter,
QueryList,
ContentChildren
ContentChildren,
TemplateRef,
ViewContainerRef,
OnInit,
} from '@angular/core';
import {CommonModule} from '@angular/common';
import {PortalModule, RIGHT_ARROW, LEFT_ARROW, ENTER, coerceBooleanProperty} from '../core';
import {
PortalModule,
TemplatePortal,
RIGHT_ARROW,
LEFT_ARROW,
ENTER,
coerceBooleanProperty,
} from '../core';
import {MdTabLabel} from './tab-label';
import {MdTabContent} from './tab-content';
import {MdTabLabelWrapper} from './tab-label-wrapper';
import {MdTabNavBar, MdTabLink} from './tab-nav-bar/tab-nav-bar';
import {MdInkBar} from './ink-bar';
Expand All @@ -32,20 +41,35 @@ export class MdTabChangeEvent {
tab: MdTab;
}

@Directive({
selector: 'md-tab'
@Component({
moduleId: module.id,
selector: 'md-tab',
templateUrl: 'tab.html',
})
export class MdTab {
@ContentChild(MdTabLabel) label: MdTabLabel;
@ContentChild(MdTabContent) content: MdTabContent;
export class MdTab implements OnInit {
/** Content for the tab label given by <template md-tab-label>. */
@ContentChild(MdTabLabel) templateLabel: MdTabLabel;

private _disabled = false;
@Input('disabled')
set disabled(value: boolean) {
this._disabled = coerceBooleanProperty(value);
/** Template inside the MdTab view that contains an <ng-content>. */
@ViewChild(TemplateRef) _content: TemplateRef<any>;

/** The plain text label for the tab, used when there is no template label. */
@Input('label') textLabel: string = '';

private _contentPortal: TemplatePortal = null;

constructor(private _viewContainerRef: ViewContainerRef) { }

ngOnInit() {
this._contentPortal = new TemplatePortal(this._content, this._viewContainerRef);
}
get disabled(): boolean {
return this._disabled;

private _disabled = false;
@Input() set disabled(value: boolean) { this._disabled = coerceBooleanProperty(value); }
get disabled(): boolean { return this._disabled; }

get content(): TemplatePortal {
return this._contentPortal;
}
}

Expand Down Expand Up @@ -230,8 +254,8 @@ export class MdTabGroup {
@NgModule({
imports: [CommonModule, PortalModule],
// Don't export MdInkBar or MdTabLabelWrapper, as they are internal implementation details.
exports: [MdTabGroup, MdTabLabel, MdTabContent, MdTab, MdTabNavBar, MdTabLink],
declarations: [MdTabGroup, MdTabLabel, MdTabContent, MdTab, MdInkBar, MdTabLabelWrapper,
exports: [MdTabGroup, MdTabLabel, MdTab, MdTabNavBar, MdTabLink],
declarations: [MdTabGroup, MdTabLabel, MdTab, MdInkBar, MdTabLabelWrapper,
MdTabNavBar, MdTabLink],
})
export class MdTabsModule {
Expand Down

0 comments on commit ea6c817

Please sign in to comment.