Skip to content

feat(stepper): Add animation to stepper #6361

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 5 commits into from
Aug 10, 2017
Merged
Show file tree
Hide file tree
Changes from 4 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
44 changes: 42 additions & 2 deletions src/cdk/stepper/stepper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
Component,
ContentChild,
ViewChild,
TemplateRef
TemplateRef, AfterContentChecked
} from '@angular/core';
import {LEFT_ARROW, RIGHT_ARROW, ENTER, SPACE} from '@angular/cdk/keyboard';
import {CdkStepLabel} from './step-label';
Expand All @@ -30,6 +30,12 @@ import {AbstractControl} from '@angular/forms';
/** Used to generate unique ID for each stepper component. */
let nextId = 0;

/**
* Position state of the content of each step in horizontal stepper that is used for transitioning
* the content into correct position upon step selection change.
*/
export type CdkStepContentPositionState = 'left' | 'center' | 'right';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this need to take RTL into consideration?

Copy link
Author

@g1shin g1shin Aug 9, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it would need to once the Directionality related things are added to stepper, but I haven't added any code dealing with Directionality. I am thinking about addressing them all at once, since it will also influence other interactions such as _foucsIndex management for horizontal stepper.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be generalized such that it isn't only for horizontal steps? Maybe previous / current / next?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't need to prefix this with Cdk; my stance is that only directives/components with cdk- selector should have that reflected in the identifier name


/** Change event emitted on selection changes. */
export class CdkStepperSelectionEvent {
/** Index of the step now selected. */
Expand Down Expand Up @@ -71,6 +77,20 @@ export class CdkStep {
@Input()
label: string;

/** Position state of step. */
_position: CdkStepContentPositionState;

/** Sets the CdkStepContentPositionState of step. */
set position(position: number) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

position by itself doesn't seem specific enough. Maybe animationDirection?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than having a setter for this and reading the property, why not just always read a function like getAnimationDirection that's based off of the current index?

if (position < 0) {
this._position = 'left';
} else if (position > 0) {
this._position = 'right';
} else {
this._position = 'center';
}
}

constructor(private _stepper: CdkStepper) { }

/** Selects this step component. */
Expand All @@ -86,7 +106,7 @@ export class CdkStep {
'(keydown)': '_onKeydown($event)'
},
})
export class CdkStepper {
export class CdkStepper implements AfterContentChecked {
/** The list of step components that the stepper is holding. */
@ContentChildren(CdkStep) _steps: QueryList<CdkStep>;

Expand All @@ -106,6 +126,7 @@ export class CdkStepper {
if (this._selectedIndex != index && !this._anyControlsInvalid(index)) {
this._emitStepperSelectionEvent(index);
this._focusStep(this._selectedIndex);
this._setStepPosition();
}
}
private _selectedIndex: number = 0;
Expand All @@ -131,6 +152,10 @@ export class CdkStepper {
this._groupId = nextId++;
}

ngAfterContentChecked(): void {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is called every change detection run, which seems like it may be overkill. Can you clarify why this is necessary? Would ngAfterContentInit work here?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now neither is needed with the new change :)

this._setStepPosition();
}

/** Selects and focuses the next step in list. */
next(): void {
this.selectedIndex = Math.min(this._selectedIndex + 1, this._steps.length - 1);
Expand Down Expand Up @@ -181,6 +206,11 @@ export class CdkStepper {
event.preventDefault();
}

/** Get whether a step has 'expanded' or 'collapsed' state for vertical stepper. */
_getExpandedState(index: number) {
return index == this._selectedIndex ? 'expanded' : 'collapsed';
}

private _focusStep(index: number) {
this._focusIndex = index;
this._stepHeader.toArray()[this._focusIndex].nativeElement.focus();
Expand All @@ -198,4 +228,14 @@ export class CdkStepper {
}
return false;
}

/**
* Set the shifted index position of each step in horizontal stepper, where zero represents
* the selected step.
*/
private _setStepPosition() {
this._steps.forEach((step: CdkStep, index: number) => {
step.position = index - this._selectedIndex;
});
}
}
16 changes: 9 additions & 7 deletions src/lib/stepper/stepper-horizontal.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@
<div *ngIf="!isLast" class="connector-line"></div>
</ng-container>
</div>

<div *ngFor="let step of _steps; let i = index"
class="mat-horizontal-stepper-content" role="tabpanel"
[id]="_getStepContentId(i)"
[attr.aria-labelledby]="_getStepLabelId(i)"
[attr.aria-expanded]="selectedIndex == i">
<ng-container [ngTemplateOutlet]="step.content"></ng-container>
<div class="horizontal-content-container">
<div *ngFor="let step of _steps; let i = index"
class="mat-horizontal-stepper-content" role="tabpanel"
[@stepEnterExit]="step._position"
[id]="_getStepContentId(i)"
[attr.aria-labelledby]="_getStepLabelId(i)"
[attr.aria-expanded]="selectedIndex == i">
<ng-container [ngTemplateOutlet]="step.content"></ng-container>
</div>
</div>
10 changes: 10 additions & 0 deletions src/lib/stepper/stepper-horizontal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

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

@Component({
moduleId: module.id,
Expand All @@ -19,6 +20,15 @@ import {MdStepper} from './stepper';
'class': 'mat-stepper-horizontal',
'role': 'tablist',
},
animations: [
trigger('stepEnterExit', [
state('left', style({transform: 'translate3d(-100%, 0, 0)'})),
state('center', style({transform: 'translate3d(0%, 0, 0)'})),
state('right', style({transform: 'translate3d(100%, 0, 0)'})),
transition('* => left, * => right, left => center, right => center',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you do * => center as well, or will that not work?

animate('500ms cubic-bezier(0.35, 0, 0.25, 1)'))
])
],
providers: [{provide: MdStepper, useExisting: MdHorizontalStepper}]
})
export class MdHorizontalStepper extends MdStepper { }
5 changes: 4 additions & 1 deletion src/lib/stepper/stepper-vertical.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,13 @@
</div>
<div class="vertical-content-container">
<div class="mat-vertical-stepper-content" role="tabpanel"
[@stepExpandCollapse]="_getExpandedState(i)"
[id]="_getStepContentId(i)"
[attr.aria-labelledby]="_getStepLabelId(i)"
[attr.aria-expanded]="selectedIndex == i">
<ng-container [ngTemplateOutlet]="step.content"></ng-container>
<div class="vertical-content">
<ng-container[ngTemplateOutlet]="step.content"></ng-container>
</div>
</div>
</div>
</div>
8 changes: 8 additions & 0 deletions src/lib/stepper/stepper-vertical.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

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

@Component({
moduleId: module.id,
Expand All @@ -19,6 +20,13 @@ import {MdStepper} from './stepper';
'class': 'mat-stepper-vertical',
'role': 'tablist',
},
animations: [
trigger('stepExpandCollapse', [
state('collapsed', style({height: '0px', visibility: 'hidden'})),
state('expanded', style({height: '*', visibility: 'visible'})),
transition('expanded <=> collapsed', animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)'))
])
],
providers: [{provide: MdStepper, useExisting: MdVerticalStepper}]
})
export class MdVerticalStepper extends MdStepper { }
29 changes: 19 additions & 10 deletions src/lib/stepper/stepper.scss
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
@import '../core/style/variables';

$mat-horizontal-stepper-header-height: 72px !default;
$mat-label-header-height: 24px !default;
$mat-stepper-label-min-width: 50px !default;
$mat-stepper-side-gap: 24px !default;
$mat-vertical-stepper-content-margin: 12px !default;
$mat-vertical-stepper-content-padding: 16px 0 32px $mat-stepper-side-gap !default;
$mat-vertical-content-padding-bottom: 32px !default;
$mat-vertical-content-container-padding: 8px !default;
$mat-connector-line-width: 1px !default;
$mat-connector-line-gap: 8px !default;
Expand Down Expand Up @@ -43,6 +45,7 @@ $mat-vertical-stepper-margin-top: $mat-stepper-side-gap - $mat-connector-line-ga
line-height: $mat-horizontal-stepper-header-height;
overflow: hidden;
align-items: center;
outline: none;

.mat-stepper-index {
margin-right: $mat-connector-line-gap;
Expand All @@ -55,6 +58,7 @@ $mat-vertical-stepper-margin-top: $mat-stepper-side-gap - $mat-connector-line-ga
display: flex;
align-items: center;
margin: $mat-connector-line-gap 0;
outline: none;

.mat-stepper-index {
margin-right: $mat-vertical-stepper-content-margin;
Expand All @@ -70,8 +74,16 @@ $mat-vertical-stepper-margin-top: $mat-stepper-side-gap - $mat-connector-line-ga
height: 0;
}

.mat-horizontal-stepper-content[aria-expanded='false'] {
display: none;
.mat-horizontal-stepper-content {
overflow: hidden;

&[aria-expanded='false'] {
height: 0;
}
}

.horizontal-content-container {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefix all css classes with mat-

overflow: hidden;
}

.vertical-content-container {
Expand All @@ -83,14 +95,11 @@ $mat-vertical-stepper-margin-top: $mat-stepper-side-gap - $mat-connector-line-ga

.mat-vertical-stepper-content {
padding-left: $mat-stepper-side-gap;
overflow: hidden;
}

&[aria-expanded='false'] {
display: none;
}

&[aria-expanded='true'] {
padding: $mat-vertical-stepper-content-padding;
}
.vertical-content {
padding-bottom: $mat-vertical-content-padding-bottom;
}

.mat-step {
Expand Down