diff --git a/src/lib/stepper/stepper.md b/src/lib/stepper/stepper.md index b392351a46c5..2c933c33deed 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 012a25fee59c..1c469570ae72 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 21b784e1161e..00f37d2b13e2 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 000000000000..7432308753e6 --- /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 000000000000..931635a17ea8 --- /dev/null +++ b/src/material-examples/stepper-label-position-bottom/stepper-label-position-bottom-example.html @@ -0,0 +1,33 @@ + + +
+ Fill out your name + + + +
+ +
+
+
+ +
+ Fill out your address + + + +
+ + +
+
+
+ + 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 000000000000..406dccb64d1b --- /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] + }); + } +}