From e4c6cc98bff8282b9c3bf2b8461660362b68f7be Mon Sep 17 00:00:00 2001
From: Paolo Caleffi
Date: Fri, 21 Sep 2018 19:16:53 +0200
Subject: [PATCH] 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 :)
---
src/lib/stepper/stepper.md | 6 ++
src/lib/stepper/stepper.scss | 58 +++++++++++++++++++
src/lib/stepper/stepper.ts | 12 +++-
.../stepper-label-position-bottom-example.css | 1 +
...stepper-label-position-bottom-example.html | 33 +++++++++++
.../stepper-label-position-bottom-example.ts | 26 +++++++++
6 files changed, 134 insertions(+), 2 deletions(-)
create mode 100644 src/material-examples/stepper-label-position-bottom/stepper-label-position-bottom-example.css
create mode 100644 src/material-examples/stepper-label-position-bottom/stepper-label-position-bottom-example.html
create mode 100644 src/material-examples/stepper-label-position-bottom/stepper-label-position-bottom-example.ts
diff --git a/src/lib/stepper/stepper.md b/src/lib/stepper/stepper.md
index b392351a46c..2c933c33dee 100644
--- a/src/lib/stepper/stepper.md
+++ b/src/lib/stepper/stepper.md
@@ -40,6 +40,12 @@ For more complex labels, add a template with the `matStepLabel` directive inside
```
+#### Label position
+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.
+This behaviour is controlled by `labelPosition` property.
+
+
+
### Stepper buttons
There are two button directives to support navigation between different steps:
`matStepperPrevious` and `matStepperNext`.
diff --git a/src/lib/stepper/stepper.scss b/src/lib/stepper/stepper.scss
index 012a25fee59..1c469570ae7 100644
--- a/src/lib/stepper/stepper.scss
+++ b/src/lib/stepper/stepper.scss
@@ -2,6 +2,7 @@
$mat-horizontal-stepper-header-height: 72px !default;
$mat-stepper-label-header-height: 24px !default;
+$mat-stepper-label-position-bottom-top-gap: 16px !default;
$mat-stepper-side-gap: 24px !default;
$mat-vertical-stepper-content-margin: 36px !default;
$mat-stepper-line-width: 1px !default;
@@ -16,6 +17,10 @@ $mat-stepper-line-gap: 8px !default;
white-space: nowrap;
display: flex;
align-items: center;
+
+ .mat-stepper-label-position-bottom & {
+ align-items: flex-start;
+ }
}
.mat-stepper-horizontal-line {
@@ -25,6 +30,25 @@ $mat-stepper-line-gap: 8px !default;
height: 0;
margin: 0 $mat-stepper-line-gap - $mat-stepper-side-gap;
min-width: $mat-stepper-line-gap + $mat-stepper-side-gap;
+
+ .mat-stepper-label-position-bottom & {
+ margin: 0;
+ min-width: 0;
+ position: relative;
+ top: $mat-stepper-side-gap + $mat-stepper-label-header-height / 2;
+ }
+}
+
+%mat-header-horizontal-line-label-position-bottom {
+ border-top-color: rgba(0, 0, 0, 0.12);
+ border-top-width: $mat-stepper-line-width;
+ border-top-style: solid;
+ content: '';
+ display: inline-block;
+ height: 0;
+ position: absolute;
+ top: $mat-stepper-side-gap + $mat-stepper-label-header-height / 2;
+ width: calc(50% - #{$mat-stepper-label-header-height / 2 + $mat-stepper-line-gap});
}
.mat-horizontal-stepper-header {
@@ -43,6 +67,40 @@ $mat-stepper-line-gap: 8px !default;
margin-left: $mat-stepper-line-gap;
}
}
+
+ .mat-stepper-label-position-bottom & {
+ box-sizing: border-box;
+ flex-direction: column;
+ // We use auto instead of fixed 104px (by spec) because when there is an optional step
+ // the height is greater than that
+ height: auto;
+ padding: $mat-stepper-side-gap;
+
+ [dir='ltr'] &:not(:last-child)::after,
+ [dir='rtl'] &:not(:first-child)::after {
+ @extend %mat-header-horizontal-line-label-position-bottom;
+ right: 0;
+ }
+
+ [dir='ltr'] &:not(:first-child)::before,
+ [dir='rtl'] &:not(:last-child)::before {
+ @extend %mat-header-horizontal-line-label-position-bottom;
+ left: 0;
+ }
+
+ & .mat-step-icon,
+ & .mat-step-icon-not-touched {
+ // Cleans margin both for ltr and rtl direction
+ margin-right: 0;
+ margin-left: 0;
+ }
+
+ & .mat-step-label {
+ padding: $mat-stepper-label-position-bottom-top-gap 0 0 0;
+ text-align: center;
+ width: 100%;
+ }
+ }
}
.mat-vertical-stepper-header {
diff --git a/src/lib/stepper/stepper.ts b/src/lib/stepper/stepper.ts
index 21b784e1161..00f37d2b13e 100644
--- a/src/lib/stepper/stepper.ts
+++ b/src/lib/stepper/stepper.ts
@@ -27,6 +27,7 @@ import {
EventEmitter,
forwardRef,
Inject,
+ Input,
Optional,
Output,
QueryList,
@@ -38,9 +39,10 @@ import {
import {FormControl, FormGroupDirective, NgForm} from '@angular/forms';
import {DOCUMENT} from '@angular/common';
import {ErrorStateMatcher} from '@angular/material/core';
+import {takeUntil} from 'rxjs/operators';
+
import {MatStepHeader} from './step-header';
import {MatStepLabel} from './step-label';
-import {takeUntil} from 'rxjs/operators';
import {matStepperAnimations} from './stepper-animations';
import {MatStepperIcon, MatStepperIconContext} from './stepper-icon';
@@ -124,6 +126,8 @@ export class MatStepper extends _CdkStepper implements AfterContentInit {
inputs: ['selectedIndex'],
host: {
'class': 'mat-stepper-horizontal',
+ '[class.mat-stepper-label-position-end]': 'labelPosition == "end"',
+ '[class.mat-stepper-label-position-bottom]': 'labelPosition == "bottom"',
'aria-orientation': 'horizontal',
'role': 'tablist',
},
@@ -132,7 +136,11 @@ export class MatStepper extends _CdkStepper implements AfterContentInit {
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
})
-export class MatHorizontalStepper extends MatStepper { }
+export class MatHorizontalStepper extends MatStepper {
+ /** Whether the label should display in bottom or end position. */
+ @Input()
+ labelPosition: 'bottom' | 'end' = 'end';
+}
@Component({
moduleId: module.id,
diff --git a/src/material-examples/stepper-label-position-bottom/stepper-label-position-bottom-example.css b/src/material-examples/stepper-label-position-bottom/stepper-label-position-bottom-example.css
new file mode 100644
index 00000000000..7432308753e
--- /dev/null
+++ b/src/material-examples/stepper-label-position-bottom/stepper-label-position-bottom-example.css
@@ -0,0 +1 @@
+/** No CSS for this example */
diff --git a/src/material-examples/stepper-label-position-bottom/stepper-label-position-bottom-example.html b/src/material-examples/stepper-label-position-bottom/stepper-label-position-bottom-example.html
new file mode 100644
index 00000000000..931635a17ea
--- /dev/null
+++ b/src/material-examples/stepper-label-position-bottom/stepper-label-position-bottom-example.html
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+ Done
+ You are now done.
+
+
+
+
+
+
diff --git a/src/material-examples/stepper-label-position-bottom/stepper-label-position-bottom-example.ts b/src/material-examples/stepper-label-position-bottom/stepper-label-position-bottom-example.ts
new file mode 100644
index 00000000000..406dccb64d1
--- /dev/null
+++ b/src/material-examples/stepper-label-position-bottom/stepper-label-position-bottom-example.ts
@@ -0,0 +1,26 @@
+import {Component, OnInit} from '@angular/core';
+import {FormBuilder, FormGroup, Validators} from '@angular/forms';
+
+/**
+ * @title Stepper label bottom position
+ */
+@Component({
+ selector: 'stepper-label-position-bottom-example',
+ templateUrl: 'stepper-label-position-bottom-example.html',
+ styleUrls: ['stepper-label-position-bottom-example.css'],
+})
+export class StepperLabelPositionBottomExample implements OnInit {
+ firstFormGroup: FormGroup;
+ secondFormGroup: FormGroup;
+
+ constructor(private _formBuilder: FormBuilder) {}
+
+ ngOnInit() {
+ this.firstFormGroup = this._formBuilder.group({
+ firstCtrl: ['', Validators.required]
+ });
+ this.secondFormGroup = this._formBuilder.group({
+ secondCtrl: ['', Validators.required]
+ });
+ }
+}