From 16fdaf2750eb3a7fc3f20a220807aa3f545fbf26 Mon Sep 17 00:00:00 2001 From: Darren Thompson Date: Sun, 26 Oct 2025 22:05:06 -0400 Subject: [PATCH] feat(material/stepper): add a prefix section to the horizontal stepper header Adds a prefix section to the header of the horizontal stepper header. --- goldens/material/stepper/index.api.md | 3 +- src/material/stepper/stepper.html | 43 +++++++++++----- src/material/stepper/stepper.scss | 6 +++ src/material/stepper/stepper.spec.ts | 74 +++++++++++++++++++++++++++ src/material/stepper/stepper.ts | 4 ++ 5 files changed, 116 insertions(+), 14 deletions(-) diff --git a/goldens/material/stepper/index.api.md b/goldens/material/stepper/index.api.md index ee565c8a8339..89debf3c8a63 100644 --- a/goldens/material/stepper/index.api.md +++ b/goldens/material/stepper/index.api.md @@ -119,6 +119,7 @@ export class MatStepper extends CdkStepper implements AfterViewInit, AfterConten // (undocumented) _getAnimationDuration(): string; headerPosition: 'top' | 'bottom'; + readonly headerPrefix: i0.InputSignal | null>; _iconOverrides: Record>; _icons: QueryList; // (undocumented) @@ -135,7 +136,7 @@ export class MatStepper extends CdkStepper implements AfterViewInit, AfterConten readonly steps: QueryList; _steps: QueryList; // (undocumented) - static ɵcmp: i0.ɵɵComponentDeclaration; + static ɵcmp: i0.ɵɵComponentDeclaration; // (undocumented) static ɵfac: i0.ɵɵFactoryDeclaration; } diff --git a/src/material/stepper/stepper.html b/src/material/stepper/stepper.html index 6f4e3660f668..1d281f98657f 100644 --- a/src/material/stepper/stepper.html +++ b/src/material/stepper/stepper.html @@ -11,19 +11,16 @@ @switch (orientation) { @case ('horizontal') {
-
- @for (step of steps; track step) { - - @if (!$last) { -
- } - } -
+ @if (headerPrefix()) { +
+ + +
+ } @else { + + }
@for (step of steps; track step) { @@ -44,6 +41,10 @@ @case ('vertical') {
+ @if (headerPrefix()) { + + } + @for (step of steps; track step) {
+ + +
+ @for (step of steps; track step) { + + @if (!$last) { +
+ } + } +
+
diff --git a/src/material/stepper/stepper.scss b/src/material/stepper/stepper.scss index 7dc209dd91e5..22358d223427 100644 --- a/src/material/stepper/stepper.scss +++ b/src/material/stepper/stepper.scss @@ -19,10 +19,16 @@ $fallbacks: m3-stepper.get-tokens(); background: token-utils.slot(stepper-container-color, $fallbacks); } +.mat-horizontal-stepper-header-wrapper { + align-items: center; + display: flex; +} + .mat-horizontal-stepper-header-container { white-space: nowrap; display: flex; align-items: center; + flex-grow: 1; .mat-stepper-label-position-bottom & { align-items: flex-start; diff --git a/src/material/stepper/stepper.spec.ts b/src/material/stepper/stepper.spec.ts index 3c35b79a8deb..380aa4a50ab0 100644 --- a/src/material/stepper/stepper.spec.ts +++ b/src/material/stepper/stepper.spec.ts @@ -1561,6 +1561,44 @@ describe('MatStepper', () => { expect(fixture.componentInstance.index).toBe(0); }); }); + + describe('stepper with header prefix', () => { + it('should render the horizontal prefix content before the header', () => { + const fixture = createComponent(HorizontalStepperWithHeaderPrefix); + fixture.detectChanges(); + + const stepperHeaderWrapper = fixture.nativeElement.querySelector( + '.mat-horizontal-stepper-header-wrapper', + ); + + expect(stepperHeaderWrapper.children.length).toBe(2); + + const stepperHeaderWrapperChildrenTags = Array.from( + stepperHeaderWrapper.children as HTMLElement[], + ).map((child: HTMLElement) => child.tagName); + const stepperHeaderPrefix = stepperHeaderWrapper.children[0]; + + expect(stepperHeaderWrapperChildrenTags).toEqual(['H2', 'DIV']); + expect(stepperHeaderPrefix.textContent).toContain('This is a header prefix'); + }); + + it('should render the vertical prefix content before the first step', () => { + const fixture = createComponent(VerticalStepperWithHeaderPrefix); + fixture.detectChanges(); + + const stepperWrapper = fixture.nativeElement.querySelector('.mat-vertical-stepper-wrapper'); + + expect(stepperWrapper.children.length).toBe(4); + + const stepperHeaderWrapperChildrenTags = Array.from( + stepperWrapper.children as HTMLElement[], + ).map((child: HTMLElement) => child.tagName); + const stepperHeaderPrefix = stepperWrapper.children[0]; + + expect(stepperHeaderWrapperChildrenTags).toEqual(['H2', 'DIV', 'DIV', 'DIV']); + expect(stepperHeaderPrefix.textContent).toContain('This is a header prefix'); + }); + }); }); /** Asserts that keyboard interaction works correctly. */ @@ -2258,3 +2296,39 @@ class HorizontalStepperWithDelayedStep { class StepperWithTwoWayBindingOnSelectedIndex { index: number = 0; } + +@Component({ + template: ` + + + + + + + +

This is a header prefix

+
+ `, + imports: [MatStepperModule], +}) +class HorizontalStepperWithHeaderPrefix { + @ViewChild(MatStepper) stepper: MatStepper; +} + +@Component({ + template: ` + + + + + + + +

This is a header prefix

+
+ `, + imports: [MatStepperModule], +}) +class VerticalStepperWithHeaderPrefix { + @ViewChild(MatStepper) stepper: MatStepper; +} diff --git a/src/material/stepper/stepper.ts b/src/material/stepper/stepper.ts index 82f3f6db9079..48e543cc021f 100644 --- a/src/material/stepper/stepper.ts +++ b/src/material/stepper/stepper.ts @@ -18,6 +18,7 @@ import { EventEmitter, inject, Input, + input, NgZone, OnDestroy, Output, @@ -187,6 +188,9 @@ export class MatStepper extends CdkStepper implements AfterViewInit, AfterConten @Input() headerPosition: 'top' | 'bottom' = 'top'; + /** The content prefix to use in the stepper header. */ + readonly headerPrefix = input | null>(null); + /** Consumer-specified template-refs to be used to override the header icons. */ _iconOverrides: Record> = {};