Skip to content

Commit e4c6cc9

Browse files
IlCallommalerba
authored andcommitted
Stepper alternative label (#12674)
Added the "alternative label" version, as by [(old) spec](https://material.io/archive/guidelines/components/steppers.html#steppers-types-of-steppers). Tryed on Chrome, Firefox, Edge and IE11 and everything seems fine. Added a paragraph in the stepper documentation and an example. I'm not sure the code style is ok so if you have suggestions I'll be happy to clean it up a bit, just tell me how :)
1 parent d15431b commit e4c6cc9

File tree

6 files changed

+134
-2
lines changed

6 files changed

+134
-2
lines changed

src/lib/stepper/stepper.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ For more complex labels, add a template with the `matStepLabel` directive inside
4040
</mat-vertical-stepper>
4141
```
4242

43+
#### Label position
44+
For `mat-horizontal-stepper` it's possible to define the position of the label. `end` is the default value, while `bottom` will place it under the step icon instead of at its side.
45+
This behaviour is controlled by `labelPosition` property.
46+
47+
<!-- example(stepper-label-position-bottom) -->
48+
4349
### Stepper buttons
4450
There are two button directives to support navigation between different steps:
4551
`matStepperPrevious` and `matStepperNext`.

src/lib/stepper/stepper.scss

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
$mat-horizontal-stepper-header-height: 72px !default;
44
$mat-stepper-label-header-height: 24px !default;
5+
$mat-stepper-label-position-bottom-top-gap: 16px !default;
56
$mat-stepper-side-gap: 24px !default;
67
$mat-vertical-stepper-content-margin: 36px !default;
78
$mat-stepper-line-width: 1px !default;
@@ -16,6 +17,10 @@ $mat-stepper-line-gap: 8px !default;
1617
white-space: nowrap;
1718
display: flex;
1819
align-items: center;
20+
21+
.mat-stepper-label-position-bottom & {
22+
align-items: flex-start;
23+
}
1924
}
2025

2126
.mat-stepper-horizontal-line {
@@ -25,6 +30,25 @@ $mat-stepper-line-gap: 8px !default;
2530
height: 0;
2631
margin: 0 $mat-stepper-line-gap - $mat-stepper-side-gap;
2732
min-width: $mat-stepper-line-gap + $mat-stepper-side-gap;
33+
34+
.mat-stepper-label-position-bottom & {
35+
margin: 0;
36+
min-width: 0;
37+
position: relative;
38+
top: $mat-stepper-side-gap + $mat-stepper-label-header-height / 2;
39+
}
40+
}
41+
42+
%mat-header-horizontal-line-label-position-bottom {
43+
border-top-color: rgba(0, 0, 0, 0.12);
44+
border-top-width: $mat-stepper-line-width;
45+
border-top-style: solid;
46+
content: '';
47+
display: inline-block;
48+
height: 0;
49+
position: absolute;
50+
top: $mat-stepper-side-gap + $mat-stepper-label-header-height / 2;
51+
width: calc(50% - #{$mat-stepper-label-header-height / 2 + $mat-stepper-line-gap});
2852
}
2953

3054
.mat-horizontal-stepper-header {
@@ -43,6 +67,40 @@ $mat-stepper-line-gap: 8px !default;
4367
margin-left: $mat-stepper-line-gap;
4468
}
4569
}
70+
71+
.mat-stepper-label-position-bottom & {
72+
box-sizing: border-box;
73+
flex-direction: column;
74+
// We use auto instead of fixed 104px (by spec) because when there is an optional step
75+
// the height is greater than that
76+
height: auto;
77+
padding: $mat-stepper-side-gap;
78+
79+
[dir='ltr'] &:not(:last-child)::after,
80+
[dir='rtl'] &:not(:first-child)::after {
81+
@extend %mat-header-horizontal-line-label-position-bottom;
82+
right: 0;
83+
}
84+
85+
[dir='ltr'] &:not(:first-child)::before,
86+
[dir='rtl'] &:not(:last-child)::before {
87+
@extend %mat-header-horizontal-line-label-position-bottom;
88+
left: 0;
89+
}
90+
91+
& .mat-step-icon,
92+
& .mat-step-icon-not-touched {
93+
// Cleans margin both for ltr and rtl direction
94+
margin-right: 0;
95+
margin-left: 0;
96+
}
97+
98+
& .mat-step-label {
99+
padding: $mat-stepper-label-position-bottom-top-gap 0 0 0;
100+
text-align: center;
101+
width: 100%;
102+
}
103+
}
46104
}
47105

48106
.mat-vertical-stepper-header {

src/lib/stepper/stepper.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
EventEmitter,
2828
forwardRef,
2929
Inject,
30+
Input,
3031
Optional,
3132
Output,
3233
QueryList,
@@ -38,9 +39,10 @@ import {
3839
import {FormControl, FormGroupDirective, NgForm} from '@angular/forms';
3940
import {DOCUMENT} from '@angular/common';
4041
import {ErrorStateMatcher} from '@angular/material/core';
42+
import {takeUntil} from 'rxjs/operators';
43+
4144
import {MatStepHeader} from './step-header';
4245
import {MatStepLabel} from './step-label';
43-
import {takeUntil} from 'rxjs/operators';
4446
import {matStepperAnimations} from './stepper-animations';
4547
import {MatStepperIcon, MatStepperIconContext} from './stepper-icon';
4648

@@ -124,6 +126,8 @@ export class MatStepper extends _CdkStepper implements AfterContentInit {
124126
inputs: ['selectedIndex'],
125127
host: {
126128
'class': 'mat-stepper-horizontal',
129+
'[class.mat-stepper-label-position-end]': 'labelPosition == "end"',
130+
'[class.mat-stepper-label-position-bottom]': 'labelPosition == "bottom"',
127131
'aria-orientation': 'horizontal',
128132
'role': 'tablist',
129133
},
@@ -132,7 +136,11 @@ export class MatStepper extends _CdkStepper implements AfterContentInit {
132136
encapsulation: ViewEncapsulation.None,
133137
changeDetection: ChangeDetectionStrategy.OnPush,
134138
})
135-
export class MatHorizontalStepper extends MatStepper { }
139+
export class MatHorizontalStepper extends MatStepper {
140+
/** Whether the label should display in bottom or end position. */
141+
@Input()
142+
labelPosition: 'bottom' | 'end' = 'end';
143+
}
136144

137145
@Component({
138146
moduleId: module.id,
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/** No CSS for this example */
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<mat-horizontal-stepper labelPosition="bottom" #stepper>
2+
<mat-step [stepControl]="firstFormGroup">
3+
<form [formGroup]="firstFormGroup">
4+
<ng-template matStepLabel>Fill out your name</ng-template>
5+
<mat-form-field>
6+
<input matInput placeholder="Last name, First name" formControlName="firstCtrl" required>
7+
</mat-form-field>
8+
<div>
9+
<button mat-button matStepperNext>Next</button>
10+
</div>
11+
</form>
12+
</mat-step>
13+
<mat-step [stepControl]="secondFormGroup" optional>
14+
<form [formGroup]="secondFormGroup">
15+
<ng-template matStepLabel>Fill out your address</ng-template>
16+
<mat-form-field>
17+
<input matInput placeholder="Address" formControlName="secondCtrl" required>
18+
</mat-form-field>
19+
<div>
20+
<button mat-button matStepperPrevious>Back</button>
21+
<button mat-button matStepperNext>Next</button>
22+
</div>
23+
</form>
24+
</mat-step>
25+
<mat-step>
26+
<ng-template matStepLabel>Done</ng-template>
27+
You are now done.
28+
<div>
29+
<button mat-button matStepperPrevious>Back</button>
30+
<button mat-button (click)="stepper.reset()">Reset</button>
31+
</div>
32+
</mat-step>
33+
</mat-horizontal-stepper>
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import {Component, OnInit} from '@angular/core';
2+
import {FormBuilder, FormGroup, Validators} from '@angular/forms';
3+
4+
/**
5+
* @title Stepper label bottom position
6+
*/
7+
@Component({
8+
selector: 'stepper-label-position-bottom-example',
9+
templateUrl: 'stepper-label-position-bottom-example.html',
10+
styleUrls: ['stepper-label-position-bottom-example.css'],
11+
})
12+
export class StepperLabelPositionBottomExample implements OnInit {
13+
firstFormGroup: FormGroup;
14+
secondFormGroup: FormGroup;
15+
16+
constructor(private _formBuilder: FormBuilder) {}
17+
18+
ngOnInit() {
19+
this.firstFormGroup = this._formBuilder.group({
20+
firstCtrl: ['', Validators.required]
21+
});
22+
this.secondFormGroup = this._formBuilder.group({
23+
secondCtrl: ['', Validators.required]
24+
});
25+
}
26+
}

0 commit comments

Comments
 (0)