diff --git a/src/components-examples/material/stepper/index.ts b/src/components-examples/material/stepper/index.ts index 4cf6781d0871..3c8d2c61aa36 100644 --- a/src/components-examples/material/stepper/index.ts +++ b/src/components-examples/material/stepper/index.ts @@ -17,6 +17,7 @@ import {StepperHarnessExample} from './stepper-harness/stepper-harness-example'; import {StepperIntlExample} from './stepper-intl/stepper-intl-example'; import {StepperLazyContentExample} from './stepper-lazy-content/stepper-lazy-content-example'; import {StepperResponsiveExample} from './stepper-responsive/stepper-responsive-example'; +import {StepperHeaderPositionExample} from './stepper-header-position/stepper-header-position-example'; export { StepperEditableExample, @@ -30,6 +31,7 @@ export { StepperVerticalExample, StepperLazyContentExample, StepperResponsiveExample, + StepperHeaderPositionExample, }; const EXAMPLES = [ @@ -44,6 +46,7 @@ const EXAMPLES = [ StepperVerticalExample, StepperLazyContentExample, StepperResponsiveExample, + StepperHeaderPositionExample, ]; @NgModule({ diff --git a/src/components-examples/material/stepper/stepper-header-position/stepper-header-position-example.css b/src/components-examples/material/stepper/stepper-header-position/stepper-header-position-example.css new file mode 100644 index 000000000000..7432308753e6 --- /dev/null +++ b/src/components-examples/material/stepper/stepper-header-position/stepper-header-position-example.css @@ -0,0 +1 @@ +/** No CSS for this example */ diff --git a/src/components-examples/material/stepper/stepper-header-position/stepper-header-position-example.html b/src/components-examples/material/stepper/stepper-header-position/stepper-header-position-example.html new file mode 100644 index 000000000000..7f092843f2f7 --- /dev/null +++ b/src/components-examples/material/stepper/stepper-header-position/stepper-header-position-example.html @@ -0,0 +1,33 @@ + + +
+ Fill out your name + + + +
+ +
+
+
+ +
+ Fill out your address + + + +
+ + +
+
+
+ + Done + You are now done. +
+ + +
+
+
diff --git a/src/components-examples/material/stepper/stepper-header-position/stepper-header-position-example.ts b/src/components-examples/material/stepper/stepper-header-position/stepper-header-position-example.ts new file mode 100644 index 000000000000..62b57ed53cb3 --- /dev/null +++ b/src/components-examples/material/stepper/stepper-header-position/stepper-header-position-example.ts @@ -0,0 +1,26 @@ +import {Component, OnInit} from '@angular/core'; +import {FormBuilder, FormGroup, Validators} from '@angular/forms'; + +/** + * @title Stepper header position + */ +@Component({ + selector: 'stepper-header-position-example', + templateUrl: 'stepper-header-position-example.html', + styleUrls: ['stepper-header-position-example.css'], +}) +export class StepperHeaderPositionExample 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], + }); + } +} diff --git a/src/material/stepper/stepper.html b/src/material/stepper/stepper.html index b074d0c2e6e2..dadc6b4f2860 100644 --- a/src/material/stepper/stepper.html +++ b/src/material/stepper/stepper.html @@ -1,6 +1,6 @@ - +
-
+ diff --git a/src/material/stepper/stepper.md b/src/material/stepper/stepper.md index 229fb7044498..d2e486892697 100644 --- a/src/material/stepper/stepper.md +++ b/src/material/stepper/stepper.md @@ -33,6 +33,13 @@ This behaviour is controlled by `labelPosition` property. "file": "stepper-label-position-bottom-example.html", "region": "label-position"}) --> +#### Header position +If you're using a horizontal stepper, you can control where the stepper's content is positioned +using the `headerPosition` input. By default it's on top of the content, but it can also be placed +under it. + + + ### Stepper buttons There are two button directives to support navigation between different steps: `matStepperPrevious` and `matStepperNext`. diff --git a/src/material/stepper/stepper.scss b/src/material/stepper/stepper.scss index 1d73e0a0178a..6d0ba7c07f20 100644 --- a/src/material/stepper/stepper.scss +++ b/src/material/stepper/stepper.scss @@ -16,6 +16,10 @@ .mat-stepper-label-position-bottom & { align-items: flex-start; } + + .mat-stepper-header-position-bottom & { + order: 1; + } } .mat-stepper-horizontal-line { @@ -116,6 +120,11 @@ } } +.mat-horizontal-stepper-wrapper { + display: flex; + flex-direction: column; +} + .mat-horizontal-stepper-content { outline: 0; @@ -132,6 +141,10 @@ overflow: hidden; padding: 0 stepper-variables.$side-gap stepper-variables.$side-gap stepper-variables.$side-gap; + + .mat-stepper-header-position-bottom & { + padding: stepper-variables.$side-gap stepper-variables.$side-gap 0 stepper-variables.$side-gap; + } } .mat-vertical-content-container { diff --git a/src/material/stepper/stepper.spec.ts b/src/material/stepper/stepper.spec.ts index b627a1e79f5a..cf36fd75ebee 100644 --- a/src/material/stepper/stepper.spec.ts +++ b/src/material/stepper/stepper.spec.ts @@ -1238,6 +1238,19 @@ describe('MatStepper', () => { expect(interactedSteps).toEqual([0, 1, 2]); subscription.unsubscribe(); }); + + it('should set a class on the host if the header is positioned at the bottom', () => { + const fixture = createComponent(SimpleMatHorizontalStepperApp); + fixture.detectChanges(); + const stepperHost = fixture.nativeElement.querySelector('.mat-stepper-horizontal'); + + expect(stepperHost.classList).not.toContain('mat-stepper-header-position-bottom'); + + fixture.componentInstance.headerPosition = 'bottom'; + fixture.detectChanges(); + + expect(stepperHost.classList).toContain('mat-stepper-header-position-bottom'); + }); }); describe('linear stepper with valid step', () => { @@ -1815,7 +1828,10 @@ class MatHorizontalStepperWithErrorsApp implements OnInit { @Component({ template: ` - + Step 1 Content 1 @@ -1847,6 +1863,7 @@ class SimpleMatHorizontalStepperApp { disableRipple = false; stepperTheme: ThemePalette; secondStepTheme: ThemePalette; + headerPosition: string; } @Component({ diff --git a/src/material/stepper/stepper.ts b/src/material/stepper/stepper.ts index 55e8b6d155cb..078986f25280 100644 --- a/src/material/stepper/stepper.ts +++ b/src/material/stepper/stepper.ts @@ -131,6 +131,7 @@ export class MatStep extends CdkStep implements ErrorStateMatcher, AfterContentI 'orientation === "horizontal" && labelPosition == "end"', '[class.mat-stepper-label-position-bottom]': 'orientation === "horizontal" && labelPosition == "bottom"', + '[class.mat-stepper-header-position-bottom]': 'headerPosition === "bottom"', '[attr.aria-orientation]': 'orientation', 'role': 'tablist', }, @@ -171,6 +172,13 @@ export class MatStepper extends CdkStepper implements AfterContentInit { @Input() labelPosition: 'bottom' | 'end' = 'end'; + /** + * Position of the stepper's header. + * Only applies in the `horizontal` orientation. + */ + @Input() + headerPosition: 'top' | 'bottom' = 'top'; + /** Consumer-specified template-refs to be used to override the header icons. */ _iconOverrides: Record> = {}; diff --git a/tools/public_api_guard/material/stepper.md b/tools/public_api_guard/material/stepper.md index 9bd7ba21bae9..3144750cb0c2 100644 --- a/tools/public_api_guard/material/stepper.md +++ b/tools/public_api_guard/material/stepper.md @@ -132,6 +132,7 @@ export class MatStepper extends CdkStepper implements AfterContentInit { readonly _animationDone: Subject; color: ThemePalette; disableRipple: boolean; + headerPosition: 'top' | 'bottom'; _iconOverrides: Record>; _icons: QueryList; labelPosition: 'bottom' | 'end'; @@ -143,7 +144,7 @@ export class MatStepper extends CdkStepper implements AfterContentInit { readonly steps: QueryList; _steps: QueryList; // (undocumented) - static ɵcmp: i0.ɵɵComponentDeclaration; + static ɵcmp: i0.ɵɵComponentDeclaration; // (undocumented) static ɵfac: i0.ɵɵFactoryDeclaration; }