Skip to content

Commit

Permalink
feat(material/tabs): label & body classes (#23691)
Browse files Browse the repository at this point in the history
  • Loading branch information
lekhmanrus authored Jan 15, 2022
1 parent 653e46b commit f10d245
Show file tree
Hide file tree
Showing 11 changed files with 201 additions and 23 deletions.
3 changes: 2 additions & 1 deletion src/material-experimental/mdc-tabs/tab-body.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ export class MatTabBodyPortal extends BaseMatTabBodyPortal {
templateUrl: 'tab-body.html',
styleUrls: ['tab-body.css'],
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
// tslint:disable-next-line:validate-decorators
changeDetection: ChangeDetectionStrategy.Default,
animations: [matTabsAnimations.translateTab],
host: {
'class': 'mat-mdc-tab-body',
Expand Down
10 changes: 6 additions & 4 deletions src/material-experimental/mdc-tabs/tab-group.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@
[attr.aria-posinset]="i + 1"
[attr.aria-setsize]="_tabs.length"
[attr.aria-controls]="_getTabContentId(i)"
[attr.aria-selected]="selectedIndex == i"
[attr.aria-selected]="selectedIndex === i"
[attr.aria-label]="tab.ariaLabel || null"
[attr.aria-labelledby]="(!tab.ariaLabel && tab.ariaLabelledby) ? tab.ariaLabelledby : null"
[class.mdc-tab--active]="selectedIndex == i"
[class.mdc-tab--active]="selectedIndex === i"
[ngClass]="tab.labelClass"
[disabled]="tab.disabled"
[fitInkBarToContent]="fitInkBarToContent"
(click)="_handleClick(tab, tabHeader, i)"
Expand All @@ -36,12 +37,12 @@
<span class="mdc-tab__content">
<span class="mdc-tab__text-label">
<!-- If there is a label template, use it. -->
<ng-template [ngIf]="tab.templateLabel">
<ng-template [ngIf]="tab.templateLabel" [ngIfElse]="tabTextLabel">
<ng-template [cdkPortalOutlet]="tab.templateLabel"></ng-template>
</ng-template>

<!-- If there is not a label template, fall back to the text label. -->
<ng-template [ngIf]="!tab.templateLabel">{{tab.textLabel}}</ng-template>
<ng-template #tabTextLabel>{{tab.textLabel}}</ng-template>
</span>
</span>
</div>
Expand All @@ -57,6 +58,7 @@
[attr.tabindex]="(contentTabIndex != null && selectedIndex === i) ? contentTabIndex : null"
[attr.aria-labelledby]="_getTabLabelId(i)"
[class.mat-mdc-tab-body-active]="selectedIndex === i"
[ngClass]="tab.bodyClass"
[content]="tab.content!"
[position]="tab.position!"
[origin]="tab.origin"
Expand Down
85 changes: 81 additions & 4 deletions src/material-experimental/mdc-tabs/tab-group.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {LEFT_ARROW} from '@angular/cdk/keycodes';
import {dispatchFakeEvent, dispatchKeyboardEvent} from '../../cdk/testing/private';
import {Component, OnInit, QueryList, ViewChild, ViewChildren} from '@angular/core';
import {Component, DebugElement, OnInit, QueryList, ViewChild, ViewChildren} from '@angular/core';
import {
waitForAsync,
ComponentFixture,
Expand Down Expand Up @@ -40,6 +40,7 @@ describe('MDC-based MatTabGroup', () => {
TabGroupWithIndirectDescendantTabs,
TabGroupWithSpaceAbove,
NestedTabGroupWithLabel,
TabsWithClassesTestApp,
],
});

Expand Down Expand Up @@ -420,11 +421,16 @@ describe('MDC-based MatTabGroup', () => {

expect(tab.getAttribute('aria-label')).toBe('Fruit');
expect(tab.hasAttribute('aria-labelledby')).toBe(false);

fixture.componentInstance.ariaLabel = 'Veggie';
fixture.detectChanges();
expect(tab.getAttribute('aria-label')).toBe('Veggie');
});
});

describe('disable tabs', () => {
let fixture: ComponentFixture<DisabledTabsTestApp>;

beforeEach(() => {
fixture = TestBed.createComponent(DisabledTabsTestApp);
});
Expand Down Expand Up @@ -780,6 +786,62 @@ describe('MDC-based MatTabGroup', () => {
}));
});

describe('tabs with custom css classes', () => {
let fixture: ComponentFixture<TabsWithClassesTestApp>;
let labelElements: DebugElement[];
let bodyElements: DebugElement[];

beforeEach(() => {
fixture = TestBed.createComponent(TabsWithClassesTestApp);
fixture.detectChanges();
labelElements = fixture.debugElement.queryAll(By.css('.mdc-tab'));
bodyElements = fixture.debugElement.queryAll(By.css('mat-tab-body'));
});

it('should apply label/body classes', () => {
expect(labelElements[1].nativeElement.classList).toContain('hardcoded-label-class');
expect(bodyElements[1].nativeElement.classList).toContain('hardcoded-body-class');
});

it('should set classes as strings dynamically', () => {
expect(labelElements[0].nativeElement.classList).not.toContain('custom-label-class');
expect(bodyElements[0].nativeElement.classList).not.toContain('custom-body-class');

fixture.componentInstance.labelClassList = 'custom-label-class';
fixture.componentInstance.bodyClassList = 'custom-body-class';
fixture.detectChanges();

expect(labelElements[0].nativeElement.classList).toContain('custom-label-class');
expect(bodyElements[0].nativeElement.classList).toContain('custom-body-class');

delete fixture.componentInstance.labelClassList;
delete fixture.componentInstance.bodyClassList;
fixture.detectChanges();

expect(labelElements[0].nativeElement.classList).not.toContain('custom-label-class');
expect(bodyElements[0].nativeElement.classList).not.toContain('custom-body-class');
});

it('should set classes as strings array dynamically', () => {
expect(labelElements[0].nativeElement.classList).not.toContain('custom-label-class');
expect(bodyElements[0].nativeElement.classList).not.toContain('custom-body-class');

fixture.componentInstance.labelClassList = ['custom-label-class'];
fixture.componentInstance.bodyClassList = ['custom-body-class'];
fixture.detectChanges();

expect(labelElements[0].nativeElement.classList).toContain('custom-label-class');
expect(bodyElements[0].nativeElement.classList).toContain('custom-body-class');

delete fixture.componentInstance.labelClassList;
delete fixture.componentInstance.bodyClassList;
fixture.detectChanges();

expect(labelElements[0].nativeElement.classList).not.toContain('custom-label-class');
expect(bodyElements[0].nativeElement.classList).not.toContain('custom-body-class');
});
});

/**
* Checks that the `selectedIndex` has been updated; checks that the label and body have their
* respective `active` classes
Expand Down Expand Up @@ -1014,7 +1076,6 @@ class BindedTabsTestApp {
}

@Component({
selector: 'test-app',
template: `
<mat-tab-group class="tab-group">
<mat-tab>
Expand Down Expand Up @@ -1080,7 +1141,6 @@ class TabGroupWithSimpleApi {
}

@Component({
selector: 'nested-tabs',
template: `
<mat-tab-group>
<mat-tab label="One">Tab one content</mat-tab>
Expand All @@ -1099,7 +1159,6 @@ class NestedTabs {
}

@Component({
selector: 'template-tabs',
template: `
<mat-tab-group>
<mat-tab label="One">
Expand Down Expand Up @@ -1215,3 +1274,21 @@ class TabGroupWithSpaceAbove {
`,
})
class NestedTabGroupWithLabel {}

@Component({
template: `
<mat-tab-group class="tab-group">
<mat-tab label="Tab One" [labelClass]="labelClassList" [bodyClass]="bodyClassList">
Tab one content
</mat-tab>
<mat-tab label="Tab Two" labelClass="hardcoded-label-class"
bodyClass="hardcoded-body-class">
Tab two content
</mat-tab>
</mat-tab-group>
`,
})
class TabsWithClassesTestApp {
labelClassList?: string | string[];
bodyClassList?: string | string[];
}
3 changes: 2 additions & 1 deletion src/material-experimental/mdc-tabs/tab-group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ import {BooleanInput, coerceBooleanProperty} from '@angular/cdk/coercion';
templateUrl: 'tab-group.html',
styleUrls: ['tab-group.css'],
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
// tslint:disable-next-line:validate-decorators
changeDetection: ChangeDetectionStrategy.Default,
inputs: ['color', 'disableRipple'],
providers: [
{
Expand Down
3 changes: 2 additions & 1 deletion src/material-experimental/mdc-tabs/tab-header.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ import {MatInkBar} from './ink-bar';
inputs: ['selectedIndex'],
outputs: ['selectFocusedIndex', 'indexFocused'],
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
// tslint:disable-next-line:validate-decorators
changeDetection: ChangeDetectionStrategy.Default,
host: {
'class': 'mat-mdc-tab-header',
'[class.mat-mdc-tab-header-pagination-controls-enabled]': '_showPaginationControls',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ import {takeUntil} from 'rxjs/operators';
'[class._mat-animation-noopable]': '_animationMode === "NoopAnimations"',
},
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
// tslint:disable-next-line:validate-decorators
changeDetection: ChangeDetectionStrategy.Default,
})
export class MatTabNav extends _MatTabNavBase implements AfterContentInit {
/** Whether the ink bar should fit its width to the size of the tab label content. */
Expand Down
3 changes: 2 additions & 1 deletion src/material-experimental/mdc-tabs/tab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ import {MatTabLabel} from './tab-label';
// that creating the extra class will generate more code than just duplicating the template.
templateUrl: 'tab.html',
inputs: ['disabled'],
changeDetection: ChangeDetectionStrategy.OnPush,
// tslint:disable-next-line:validate-decorators
changeDetection: ChangeDetectionStrategy.Default,
encapsulation: ViewEncapsulation.None,
exportAs: 'matTab',
providers: [{provide: MAT_TAB, useExisting: MatTab}],
Expand Down
13 changes: 8 additions & 5 deletions src/material/tabs/tab-group.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,19 @@
[disablePagination]="disablePagination"
(indexFocused)="_focusChanged($event)"
(selectFocusedIndex)="selectedIndex = $event">
<div class="mat-tab-label mat-focus-indicator" role="tab" matTabLabelWrapper mat-ripple cdkMonitorElementFocus
<div class="mat-tab-label mat-focus-indicator" role="tab" matTabLabelWrapper mat-ripple
cdkMonitorElementFocus
*ngFor="let tab of _tabs; let i = index"
[id]="_getTabLabelId(i)"
[attr.tabIndex]="_getTabIndex(tab, i)"
[attr.aria-posinset]="i + 1"
[attr.aria-setsize]="_tabs.length"
[attr.aria-controls]="_getTabContentId(i)"
[attr.aria-selected]="selectedIndex == i"
[attr.aria-selected]="selectedIndex === i"
[attr.aria-label]="tab.ariaLabel || null"
[attr.aria-labelledby]="(!tab.ariaLabel && tab.ariaLabelledby) ? tab.ariaLabelledby : null"
[class.mat-tab-label-active]="selectedIndex == i"
[class.mat-tab-label-active]="selectedIndex === i"
[ngClass]="tab.labelClass"
[disabled]="tab.disabled"
[matRippleDisabled]="tab.disabled || disableRipple"
(click)="_handleClick(tab, tabHeader, i)"
Expand All @@ -23,12 +25,12 @@

<div class="mat-tab-label-content">
<!-- If there is a label template, use it. -->
<ng-template [ngIf]="tab.templateLabel">
<ng-template [ngIf]="tab.templateLabel" [ngIfElse]="tabTextLabel">
<ng-template [cdkPortalOutlet]="tab.templateLabel"></ng-template>
</ng-template>

<!-- If there is not a label template, fall back to the text label. -->
<ng-template [ngIf]="!tab.templateLabel">{{tab.textLabel}}</ng-template>
<ng-template #tabTextLabel>{{tab.textLabel}}</ng-template>
</div>
</div>
</mat-tab-header>
Expand All @@ -43,6 +45,7 @@
[attr.tabindex]="(contentTabIndex != null && selectedIndex === i) ? contentTabIndex : null"
[attr.aria-labelledby]="_getTabLabelId(i)"
[class.mat-tab-body-active]="selectedIndex === i"
[ngClass]="tab.bodyClass"
[content]="tab.content!"
[position]="tab.position!"
[origin]="tab.origin"
Expand Down
Loading

0 comments on commit f10d245

Please sign in to comment.