Skip to content

Commit 85c16fe

Browse files
authored
feat(material/tabs): add support for separate tab animation durations (#32869)
Currently users can control the animation duration for the tabs through the `animationDuration` input, however it's not particularly flexible because it sets the same duration for the header and body. These changes add an extra signature that allows them to set the header/body durations separately.
1 parent b4a89d5 commit 85c16fe

8 files changed

Lines changed: 77 additions & 20 deletions

File tree

goldens/material/tabs/index.api.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -243,15 +243,17 @@ export class MatTabGroup implements AfterViewInit, AfterContentInit, AfterConten
243243
alignTabs: string | null;
244244
_allTabs: QueryList<MatTab>;
245245
readonly animationDone: EventEmitter<void>;
246-
get animationDuration(): string;
247-
set animationDuration(value: string | number);
248-
// (undocumented)
249-
protected _animationsDisabled(): boolean;
246+
get animationDuration(): MatTabGroupAnimationDuration;
247+
set animationDuration(value: MatTabGroupAnimationDuration);
250248
ariaLabel: string;
251249
ariaLabelledby: string;
252250
// @deprecated
253251
get backgroundColor(): ThemePalette;
254252
set backgroundColor(value: ThemePalette);
253+
// (undocumented)
254+
protected _bodyAnimationDuration: string;
255+
// (undocumented)
256+
protected _bodyAnimationsDisabled(): boolean;
255257
protected _bodyCentered(isCenter: boolean): void;
256258
color: ThemePalette;
257259
get contentTabIndex(): number | null;
@@ -271,6 +273,8 @@ export class MatTabGroup implements AfterViewInit, AfterContentInit, AfterConten
271273
_getTabIndex(index: number): number;
272274
_getTabLabelId(tab: MatTab, index: number): string;
273275
_handleClick(tab: MatTab, tabHeader: MatTabGroupBaseHeader, index: number): void;
276+
// (undocumented)
277+
protected _headerAnimationDuration: string;
274278
headerPosition: MatTabHeaderPosition;
275279
protected _isServer: boolean;
276280
// (undocumented)

src/material/tabs/_tabs-common.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ $mat-tab-animation-duration: 500ms !default;
247247
}
248248

249249
.mdc-tab-indicator .mdc-tab-indicator__content {
250-
transition-duration: var(--mat-tab-animation-duration, 250ms);
250+
transition-duration: var(--mat-tab-header-animation-duration, 250ms);
251251
}
252252

253253
.mat-mdc-tab-header-pagination {

src/material/tabs/tab-body.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
.mat-tab-body-content-can-animate {
5252
// Note: there's a 1ms delay so that transition events
5353
// still fire even if the duration is set to zero.
54-
transition: transform var(--mat-tab-animation-duration) 1ms cubic-bezier(0.35, 0, 0.25, 1);
54+
transition: transform var(--mat-tab-body-animation-duration) 1ms cubic-bezier(0.35, 0, 0.25, 1);
5555

5656
.mat-mdc-tab-body-wrapper._mat-animation-noopable & {
5757
transition: none;

src/material/tabs/tab-group.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@
6565

6666
<div
6767
class="mat-mdc-tab-body-wrapper"
68-
[class._mat-animation-noopable]="_animationsDisabled()"
68+
[class._mat-animation-noopable]="_bodyAnimationsDisabled()"
6969
#tabBodyWrapper>
7070
@for (tab of _tabs; track tab;) {
7171
<mat-tab-body role="tabpanel"
@@ -76,7 +76,7 @@
7676
[class]="tab.bodyClass"
7777
[content]="tab.content!"
7878
[position]="tab.position!"
79-
[animationDuration]="animationDuration"
79+
[animationDuration]="_bodyAnimationDuration"
8080
[preserveContent]="preserveContent"
8181
(_onCentered)="_removeTabBodyWrapperHeight()"
8282
(_onCentering)="_setTabBodyWrapperHeight($event)"

src/material/tabs/tab-group.spec.ts

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,26 @@ describe('MatTabGroup', () => {
460460
});
461461
});
462462

463+
describe('animation duration', () => {
464+
it('should set the body and header animation duration when value is a string', () => {
465+
const fixture = TestBed.createComponent(TabsWithCustomAnimationDuration);
466+
fixture.detectChanges();
467+
468+
const tabGroup = fixture.nativeElement.querySelector('.mat-mdc-tab-group');
469+
expect(tabGroup.style.getPropertyValue('--mat-tab-body-animation-duration')).toBe('500ms');
470+
expect(tabGroup.style.getPropertyValue('--mat-tab-header-animation-duration')).toBe('500ms');
471+
});
472+
473+
it('should set the body and header animation duration when value is an object', () => {
474+
const fixture = TestBed.createComponent(TabsWithObjectAnimationDuration);
475+
fixture.detectChanges();
476+
477+
const tabGroup = fixture.nativeElement.querySelector('.mat-mdc-tab-group');
478+
expect(tabGroup.style.getPropertyValue('--mat-tab-body-animation-duration')).toBe('100ms');
479+
expect(tabGroup.style.getPropertyValue('--mat-tab-header-animation-duration')).toBe('200ms');
480+
});
481+
});
482+
463483
describe('aria labelling', () => {
464484
let fixture: ComponentFixture<TabGroupWithAriaInputs>;
465485
let tab: HTMLElement;
@@ -1062,7 +1082,8 @@ describe('nested MatTabGroup with enabled animations', () => {
10621082
tick();
10631083

10641084
const tabGroup = fixture.nativeElement.querySelector('.mat-mdc-tab-group');
1065-
expect(tabGroup.style.getPropertyValue('--mat-tab-animation-duration')).toBe('500ms');
1085+
expect(tabGroup.style.getPropertyValue('--mat-tab-body-animation-duration')).toBe('500ms');
1086+
expect(tabGroup.style.getPropertyValue('--mat-tab-header-animation-duration')).toBe('500ms');
10661087
}));
10671088
});
10681089

@@ -1473,6 +1494,17 @@ class TabGroupWithIsActiveBinding {}
14731494
})
14741495
class TabsWithCustomAnimationDuration {}
14751496

1497+
@Component({
1498+
template: `
1499+
<mat-tab-group [animationDuration]="{body: '100ms', header: '200ms'}">
1500+
<mat-tab label="One">Tab one content</mat-tab>
1501+
<mat-tab label="Two">Tab two content</mat-tab>
1502+
</mat-tab-group>
1503+
`,
1504+
imports: [MatTabsModule],
1505+
})
1506+
class TabsWithObjectAnimationDuration {}
1507+
14761508
@Component({
14771509
template: `
14781510
<mat-tab-group>

src/material/tabs/tab-group.ts

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,12 @@ export interface MatTabGroupBaseHeader {
5050
/** Possible positions for the tab header. */
5151
export type MatTabHeaderPosition = 'above' | 'below';
5252

53+
/** Possible values for the animation duration of a tab group. */
54+
export type MatTabGroupAnimationDuration =
55+
| string
56+
| number
57+
| {body: string | number; header: string | number};
58+
5359
/** Boolean constant that determines whether the tab group supports the `backgroundColor` input */
5460
const ENABLE_BACKGROUND_INPUT = true;
5561

@@ -79,7 +85,8 @@ const ENABLE_BACKGROUND_INPUT = true;
7985
'[class.mat-mdc-tab-group-inverted-header]': 'headerPosition === "below"',
8086
'[class.mat-mdc-tab-group-stretch-tabs]': 'stretchTabs',
8187
'[attr.mat-align-tabs]': 'alignTabs',
82-
'[style.--mat-tab-animation-duration]': 'animationDuration',
88+
'[style.--mat-tab-body-animation-duration]': '_bodyAnimationDuration',
89+
'[style.--mat-tab-header-animation-duration]': '_headerAnimationDuration',
8390
},
8491
imports: [
8592
MatTabHeader,
@@ -100,6 +107,8 @@ export class MatTabGroup
100107
private _tabLabelSubscription = Subscription.EMPTY;
101108
private _tabBodySubscription = Subscription.EMPTY;
102109
private _diAnimationsDisabled = _animationsDisabled();
110+
protected _bodyAnimationDuration!: string;
111+
protected _headerAnimationDuration!: string;
103112

104113
/**
105114
* All tabs inside the tab group. This includes tabs that belong to groups that are nested
@@ -170,14 +179,20 @@ export class MatTabGroup
170179

171180
/** Duration for the tab animation. Will be normalized to milliseconds if no units are set. */
172181
@Input()
173-
get animationDuration(): string {
182+
get animationDuration(): MatTabGroupAnimationDuration {
174183
return this._animationDuration;
175184
}
176-
set animationDuration(value: string | number) {
177-
const stringValue = value + '';
178-
this._animationDuration = /^\d+$/.test(stringValue) ? value + 'ms' : stringValue;
185+
set animationDuration(value: MatTabGroupAnimationDuration) {
186+
this._animationDuration = value;
187+
188+
if (value && typeof value === 'object') {
189+
this._bodyAnimationDuration = normalizeDuration(value.body);
190+
this._headerAnimationDuration = normalizeDuration(value.header);
191+
} else {
192+
this._headerAnimationDuration = this._bodyAnimationDuration = normalizeDuration(value);
193+
}
179194
}
180-
private _animationDuration!: string;
195+
private _animationDuration!: MatTabGroupAnimationDuration;
181196

182197
/**
183198
* `tabindex` to be set on the inner element that wraps the tab content. Can be used for improved
@@ -577,11 +592,11 @@ export class MatTabGroup
577592
}
578593
}
579594

580-
protected _animationsDisabled(): boolean {
595+
protected _bodyAnimationsDisabled(): boolean {
581596
return (
582597
this._diAnimationsDisabled ||
583-
this.animationDuration === '0' ||
584-
this.animationDuration === '0ms'
598+
this._bodyAnimationDuration === '0' ||
599+
this._bodyAnimationDuration === '0ms'
585600
);
586601
}
587602
}
@@ -593,3 +608,9 @@ export class MatTabChangeEvent {
593608
/** Reference to the currently-selected tab. */
594609
tab!: MatTab;
595610
}
611+
612+
/** Normalizes an animation duration value. */
613+
function normalizeDuration(value: string | number): string {
614+
const stringValue = value + '';
615+
return /^\d+$/.test(stringValue) ? value + 'ms' : stringValue;
616+
}

src/material/tabs/tab-nav-bar/tab-nav-bar.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -530,7 +530,7 @@ describe('MatTabNavBar with enabled animations', () => {
530530
tick();
531531

532532
const tabNavBar = fixture.nativeElement.querySelector('.mat-mdc-tab-nav-bar');
533-
expect(tabNavBar.style.getPropertyValue('--mat-tab-animation-duration')).toBe('500ms');
533+
expect(tabNavBar.style.getPropertyValue('--mat-tab-header-animation-duration')).toBe('500ms');
534534
}));
535535
});
536536

src/material/tabs/tab-nav-bar/tab-nav-bar.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ import {_CdkPrivateStyleLoader} from '@angular/cdk/private';
6464
'[class.mat-accent]': 'color === "accent"',
6565
'[class.mat-warn]': 'color === "warn"',
6666
'[class._mat-animation-noopable]': '_animationsDisabled',
67-
'[style.--mat-tab-animation-duration]': 'animationDuration',
67+
'[style.--mat-tab-header-animation-duration]': 'animationDuration',
6868
},
6969
encapsulation: ViewEncapsulation.None,
7070
// tslint:disable-next-line:validate-decorators

0 commit comments

Comments
 (0)