Skip to content

Commit 7cdbc40

Browse files
authored
feat(stepper): Add animation to stepper (#6361)
* Add animation * Implement Angular animation * Clean up unnecessary code * Generalize animation so that vertical and horizontal steppers can use the same function
1 parent dfecded commit 7cdbc40

7 files changed

+79
-28
lines changed

src/cdk/stepper/stepper.ts

+21-3
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,14 @@ import {AbstractControl} from '@angular/forms';
3030
/** Used to generate unique ID for each stepper component. */
3131
let nextId = 0;
3232

33+
/**
34+
* Position state of the content of each step in stepper that is used for transitioning
35+
* the content into correct position upon step selection change.
36+
*/
37+
export type StepContentPositionState = 'previous' | 'current' | 'next';
38+
3339
/** Change event emitted on selection changes. */
34-
export class CdkStepperSelectionEvent {
40+
export class StepperSelectionEvent {
3541
/** Index of the step now selected. */
3642
selectedIndex: number;
3743

@@ -119,7 +125,7 @@ export class CdkStepper {
119125
}
120126

121127
/** Event emitted when the selected step has changed. */
122-
@Output() selectionChange = new EventEmitter<CdkStepperSelectionEvent>();
128+
@Output() selectionChange = new EventEmitter<StepperSelectionEvent>();
123129

124130
/** The index of the step that the focus can be set. */
125131
_focusIndex: number = 0;
@@ -146,11 +152,23 @@ export class CdkStepper {
146152
return `mat-step-label-${this._groupId}-${i}`;
147153
}
148154

149-
/** Returns nique id for each step content element. */
155+
/** Returns unique id for each step content element. */
150156
_getStepContentId(i: number): string {
151157
return `mat-step-content-${this._groupId}-${i}`;
152158
}
153159

160+
/** Returns position state of the step with the given index. */
161+
_getAnimationDirection(index: number): StepContentPositionState {
162+
const position = index - this._selectedIndex;
163+
if (position < 0) {
164+
return 'previous';
165+
} else if (position > 0) {
166+
return 'next';
167+
} else {
168+
return 'current';
169+
}
170+
}
171+
154172
private _emitStepperSelectionEvent(newIndex: number): void {
155173
const stepsArray = this._steps.toArray();
156174
this.selectionChange.emit({

src/lib/stepper/_stepper-theme.scss

+2-2
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,11 @@
3333
background-color: mat-color($background, card);
3434
}
3535

36-
.vertical-content-container {
36+
.mat-vertical-content-container {
3737
border-left-color: mat-color($foreground, divider);
3838
}
3939

40-
.connector-line {
40+
.mat-connector-line {
4141
border-top-color: mat-color($foreground, divider);
4242
}
4343
}

src/lib/stepper/stepper-horizontal.html

+10-8
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,16 @@
2121
</div>
2222
</div>
2323

24-
<div *ngIf="!isLast" class="connector-line"></div>
24+
<div *ngIf="!isLast" class="mat-connector-line"></div>
2525
</ng-container>
2626
</div>
27-
28-
<div *ngFor="let step of _steps; let i = index"
29-
class="mat-horizontal-stepper-content" role="tabpanel"
30-
[id]="_getStepContentId(i)"
31-
[attr.aria-labelledby]="_getStepLabelId(i)"
32-
[attr.aria-expanded]="selectedIndex == i">
33-
<ng-container [ngTemplateOutlet]="step.content"></ng-container>
27+
<div class="mat-horizontal-content-container">
28+
<div *ngFor="let step of _steps; let i = index"
29+
class="mat-horizontal-stepper-content" role="tabpanel"
30+
[@stepTransition]="_getAnimationDirection(i)"
31+
[id]="_getStepContentId(i)"
32+
[attr.aria-labelledby]="_getStepLabelId(i)"
33+
[attr.aria-expanded]="selectedIndex == i">
34+
<ng-container [ngTemplateOutlet]="step.content"></ng-container>
35+
</div>
3436
</div>

src/lib/stepper/stepper-horizontal.ts

+10
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import {Component} from '@angular/core';
1010
import {MdStepper} from './stepper';
11+
import {animate, state, style, transition, trigger} from '@angular/animations';
1112

1213
@Component({
1314
moduleId: module.id,
@@ -19,6 +20,15 @@ import {MdStepper} from './stepper';
1920
'class': 'mat-stepper-horizontal',
2021
'role': 'tablist',
2122
},
23+
animations: [
24+
trigger('stepTransition', [
25+
state('previous', style({transform: 'translate3d(-100%, 0, 0)', visibility: 'hidden'})),
26+
state('current', style({transform: 'translate3d(0%, 0, 0)', visibility: 'visible'})),
27+
state('next', style({transform: 'translate3d(100%, 0, 0)', visibility: 'hidden'})),
28+
transition('* => *',
29+
animate('500ms cubic-bezier(0.35, 0, 0.25, 1)'))
30+
])
31+
],
2232
providers: [{provide: MdStepper, useExisting: MdHorizontalStepper}]
2333
})
2434
export class MdHorizontalStepper extends MdStepper { }

src/lib/stepper/stepper-vertical.html

+5-2
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,15 @@
1919
</div>
2020

2121
</div>
22-
<div class="vertical-content-container">
22+
<div class="mat-vertical-content-container">
2323
<div class="mat-vertical-stepper-content" role="tabpanel"
24+
[@stepTransition]="_getAnimationDirection(i)"
2425
[id]="_getStepContentId(i)"
2526
[attr.aria-labelledby]="_getStepLabelId(i)"
2627
[attr.aria-expanded]="selectedIndex == i">
27-
<ng-container [ngTemplateOutlet]="step.content"></ng-container>
28+
<div class="mat-vertical-content">
29+
<ng-container[ngTemplateOutlet]="step.content"></ng-container>
30+
</div>
2831
</div>
2932
</div>
3033
</div>

src/lib/stepper/stepper-vertical.ts

+9
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import {Component} from '@angular/core';
1010
import {MdStepper} from './stepper';
11+
import {animate, state, style, transition, trigger} from '@angular/animations';
1112

1213
@Component({
1314
moduleId: module.id,
@@ -19,6 +20,14 @@ import {MdStepper} from './stepper';
1920
'class': 'mat-stepper-vertical',
2021
'role': 'tablist',
2122
},
23+
animations: [
24+
trigger('stepTransition', [
25+
state('previous', style({height: '0px', visibility: 'hidden'})),
26+
state('next', style({height: '0px', visibility: 'hidden'})),
27+
state('current', style({height: '*', visibility: 'visible'})),
28+
transition('* <=> current', animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)'))
29+
])
30+
],
2231
providers: [{provide: MdStepper, useExisting: MdVerticalStepper}]
2332
})
2433
export class MdVerticalStepper extends MdStepper { }

src/lib/stepper/stepper.scss

+22-13
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
@import '../core/style/variables';
2+
13
$mat-horizontal-stepper-header-height: 72px !default;
24
$mat-label-header-height: 24px !default;
35
$mat-stepper-label-min-width: 50px !default;
46
$mat-stepper-side-gap: 24px !default;
57
$mat-vertical-stepper-content-margin: 12px !default;
6-
$mat-vertical-stepper-content-padding: 16px 0 32px $mat-stepper-side-gap !default;
8+
$mat-vertical-content-padding-bottom: 32px !default;
79
$mat-vertical-content-container-padding: 8px !default;
810
$mat-connector-line-width: 1px !default;
911
$mat-connector-line-gap: 8px !default;
@@ -43,6 +45,7 @@ $mat-vertical-stepper-margin-top: $mat-stepper-side-gap - $mat-connector-line-ga
4345
line-height: $mat-horizontal-stepper-header-height;
4446
overflow: hidden;
4547
align-items: center;
48+
outline: none;
4649

4750
.mat-stepper-index {
4851
margin-right: $mat-connector-line-gap;
@@ -55,13 +58,14 @@ $mat-vertical-stepper-margin-top: $mat-stepper-side-gap - $mat-connector-line-ga
5558
display: flex;
5659
align-items: center;
5760
margin: $mat-connector-line-gap 0;
61+
outline: none;
5862

5963
.mat-stepper-index {
6064
margin-right: $mat-vertical-stepper-content-margin;
6165
}
6266
}
6367

64-
.connector-line {
68+
.mat-connector-line {
6569
border-top-width: $mat-connector-line-width;
6670
border-top-style: solid;
6771
width: $mat-horizontal-connector-line-size;
@@ -70,11 +74,19 @@ $mat-vertical-stepper-margin-top: $mat-stepper-side-gap - $mat-connector-line-ga
7074
height: 0;
7175
}
7276

73-
.mat-horizontal-stepper-content[aria-expanded='false'] {
74-
display: none;
77+
.mat-horizontal-stepper-content {
78+
overflow: hidden;
79+
80+
&[aria-expanded='false'] {
81+
height: 0;
82+
}
83+
}
84+
85+
.mat-horizontal-content-container {
86+
overflow: hidden;
7587
}
7688

77-
.vertical-content-container {
89+
.mat-vertical-content-container {
7890
border-left-width: $mat-connector-line-width;
7991
border-left-style: solid;
8092
margin-left: $mat-vertical-stepper-content-margin;
@@ -83,21 +95,18 @@ $mat-vertical-stepper-margin-top: $mat-stepper-side-gap - $mat-connector-line-ga
8395

8496
.mat-vertical-stepper-content {
8597
padding-left: $mat-stepper-side-gap;
98+
overflow: hidden;
99+
}
86100

87-
&[aria-expanded='false'] {
88-
display: none;
89-
}
90-
91-
&[aria-expanded='true'] {
92-
padding: $mat-vertical-stepper-content-padding;
93-
}
101+
.mat-vertical-content {
102+
padding-bottom: $mat-vertical-content-padding-bottom;
94103
}
95104

96105
.mat-step {
97106
margin-top: $mat-connector-line-gap;
98107

99108
&:last-child {
100-
.vertical-content-container {
109+
.mat-vertical-content-container {
101110
border: none;
102111
}
103112
}

0 commit comments

Comments
 (0)