Skip to content

feat(tabs): simplify api #1645

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

Merged
merged 1 commit into from
Oct 29, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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