From b9a109dfc14bcd92b96053910df4f41bb51a214a Mon Sep 17 00:00:00 2001
From: agriffard
Date: Fri, 20 Mar 2026 13:37:55 +0100
Subject: [PATCH 01/27] Wizard component
---
.../Wizard/Examples/WizardCustomized.razor | 113 +++++
.../Wizard/Examples/WizardDefault.razor | 73 ++++
.../Wizard/Examples/WizardEditForms.razor | 140 +++++++
.../Components/Wizard/FluentWizard.md | 61 +++
.../Migration/MigrationFluentWizard.md | 86 +---
src/Core/Components/Icons/CoreIcons.cs | 4 +
src/Core/Components/Wizard/FluentWizard.razor | 69 +++
.../Components/Wizard/FluentWizard.razor.cs | 395 ++++++++++++++++++
.../Components/Wizard/FluentWizard.razor.css | 80 ++++
.../Components/Wizard/FluentWizardStep.razor | 42 ++
.../Wizard/FluentWizardStep.razor.cs | 300 +++++++++++++
.../Wizard/FluentWizardStep.razor.css | 103 +++++
.../Components/Wizard/FluentWizardStepArgs.cs | 27 ++
.../Wizard/FluentWizardStepChangeEventArgs.cs | 33 ++
.../Wizard/FluentWizardStepValidator.cs | 49 +++
src/Core/Enums/StepperPosition.cs | 21 +
src/Core/Enums/WizardBorder.cs | 32 ++
src/Core/Enums/WizardStepSequence.cs | 27 ++
src/Core/Enums/WizardStepStatus.cs | 42 ++
...ts.FluentWizard_Border.verified.razor.html | 39 ++
...izard_CancelStepChange.verified.razor.html | 39 ++
...s.FluentWizard_Default.verified.razor.html | 51 +++
...Wizard_DeferredLoading.verified.razor.html | 38 ++
...entWizard_DisabledStep.verified.razor.html | 51 +++
...luentWizard_NextButton.verified.razor.html | 53 +++
....FluentWizard_OnFinish.verified.razor.html | 41 ++
...tWizard_PreviousButton.verified.razor.html | 51 +++
...Wizard_StepSequenceAny.verified.razor.html | 53 +++
...ard_StepperPositionTop.verified.razor.html | 39 ++
...entWizard_ValueBinding.verified.razor.html | 53 +++
...FluentWizard_WithSteps.verified.razor.html | 39 ++
.../Components/Wizard/FluentWizardTests.razor | 258 ++++++++++++
32 files changed, 2435 insertions(+), 67 deletions(-)
create mode 100644 examples/Demo/FluentUI.Demo.Client/Documentation/Components/Wizard/Examples/WizardCustomized.razor
create mode 100644 examples/Demo/FluentUI.Demo.Client/Documentation/Components/Wizard/Examples/WizardDefault.razor
create mode 100644 examples/Demo/FluentUI.Demo.Client/Documentation/Components/Wizard/Examples/WizardEditForms.razor
create mode 100644 examples/Demo/FluentUI.Demo.Client/Documentation/Components/Wizard/FluentWizard.md
create mode 100644 src/Core/Components/Wizard/FluentWizard.razor
create mode 100644 src/Core/Components/Wizard/FluentWizard.razor.cs
create mode 100644 src/Core/Components/Wizard/FluentWizard.razor.css
create mode 100644 src/Core/Components/Wizard/FluentWizardStep.razor
create mode 100644 src/Core/Components/Wizard/FluentWizardStep.razor.cs
create mode 100644 src/Core/Components/Wizard/FluentWizardStep.razor.css
create mode 100644 src/Core/Components/Wizard/FluentWizardStepArgs.cs
create mode 100644 src/Core/Components/Wizard/FluentWizardStepChangeEventArgs.cs
create mode 100644 src/Core/Components/Wizard/FluentWizardStepValidator.cs
create mode 100644 src/Core/Enums/StepperPosition.cs
create mode 100644 src/Core/Enums/WizardBorder.cs
create mode 100644 src/Core/Enums/WizardStepSequence.cs
create mode 100644 src/Core/Enums/WizardStepStatus.cs
create mode 100644 tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_Border.verified.razor.html
create mode 100644 tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_CancelStepChange.verified.razor.html
create mode 100644 tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_Default.verified.razor.html
create mode 100644 tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_DeferredLoading.verified.razor.html
create mode 100644 tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_DisabledStep.verified.razor.html
create mode 100644 tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_NextButton.verified.razor.html
create mode 100644 tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_OnFinish.verified.razor.html
create mode 100644 tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_PreviousButton.verified.razor.html
create mode 100644 tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_StepSequenceAny.verified.razor.html
create mode 100644 tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_StepperPositionTop.verified.razor.html
create mode 100644 tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_ValueBinding.verified.razor.html
create mode 100644 tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_WithSteps.verified.razor.html
create mode 100644 tests/Core/Components/Wizard/FluentWizardTests.razor
diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/Wizard/Examples/WizardCustomized.razor b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/Wizard/Examples/WizardCustomized.razor
new file mode 100644
index 0000000000..426380eff6
--- /dev/null
+++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/Wizard/Examples/WizardCustomized.razor
@@ -0,0 +1,113 @@
+
+
+
+
+
+
+
+ Intro
+
+
+
+ Introduction
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec ut nisi eget dolor semper
+ luctus vitae a nulla. Cras semper eros sed lacinia tincidunt. Mauris dignissim ullamcorper dolor,
+ ut blandit dui ullamcorper faucibus. Interdum et malesuada fames ac ante ipsum.
+
+
+
+
+
+ Get Started
+
+
+
+ Get Started
+ Maecenas sed justo ac sapien venenatis ullamcorper. Sed maximus nunc non venenatis euismod.
+ Fusce vel porta ex, imperdiet molestie nisl. Vestibulum eu ultricies mauris, eget aliquam quam.
+
+
+
+
+
+ Set budget
+
+
+
+ Set budget
+ Phasellus quis augue convallis, congue velit ac, aliquam ex. In egestas porttitor massa
+ aliquet porttitor. Donec bibendum faucibus urna vitae elementum. Phasellus vitae efficitur
+ turpis, eget molestie ipsum.
+
+
+
+
+
+ Summary
+
+
+
+ Summary
+ Ut iaculis sed magna efficitur tempor. Vestibulum est erat, imperdiet in diam ac,
+ aliquam tempus sapien. Nam rutrum mi at enim mattis, non mollis diam molestie.
+ Cras sodales dui libero, sit amet cursus sapien elementum ac. Nulla euismod nisi sem.
+
+
+
+
+
+ @{
+ var index = context;
+ var lastStepIndex = 3;
+
+
+ @if (index > 0)
+ {
+ Go to first page
+ Previous
+ }
+
+
+
+ @if (index != lastStepIndex)
+ {
+ Next
+ Go to last page
+ }
+ else
+ {
+ Finish
+ }
+
+ }
+
+
+
+@code
+{
+ FluentWizard MyWizard = default!;
+ int Value = 0;
+
+ void OnStepChange(FluentWizardStepChangeEventArgs e)
+ {
+ }
+
+ async Task OnFinish()
+ {
+ await Task.CompletedTask;
+ }
+}
diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/Wizard/Examples/WizardDefault.razor b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/Wizard/Examples/WizardDefault.razor
new file mode 100644
index 0000000000..57dad5302e
--- /dev/null
+++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/Wizard/Examples/WizardDefault.razor
@@ -0,0 +1,73 @@
+
+
+
+ WizardStepSequence:
+
+
+
+
+
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec ut nisi eget dolor semper
+ luctus vitae a nulla. Cras semper eros sed lacinia tincidunt. Mauris dignissim ullamcorper dolor,
+ ut blandit dui ullamcorper faucibus. Interdum et malesuada fames ac ante ipsum.
+
+
+ Maecenas sed justo ac sapien venenatis ullamcorper. Sed maximus nunc non venenatis euismod.
+ Fusce vel porta ex, imperdiet molestie nisl. Vestibulum eu ultricies mauris, eget aliquam quam.
+
+
+ Nunc dignissim tortor eget lacus porta tristique. Nunc in posuere dui. Cras ligula ex,
+ ullamcorper in gravida in, euismod vitae purus. Lorem ipsum dolor sit amet, consectetur
+ adipiscing elit. Aliquam at velit leo. Suspendisse potenti. Cras dictum eu augue in laoreet.
+
+
+ Phasellus quis augue convallis, congue velit ac, aliquam ex. In egestas porttitor massa
+ aliquet porttitor. Donec bibendum faucibus urna vitae elementum. Phasellus vitae efficitur
+ turpis, eget molestie ipsum.
+
+
+ Ut iaculis sed magna efficitur tempor. Vestibulum est erat, imperdiet in diam ac,
+ aliquam tempus sapien. Nam rutrum mi at enim mattis, non mollis diam molestie.
+ Cras sodales dui libero, sit amet cursus sapien elementum ac. Nulla euismod nisi sem.
+
+
+
+
+@code
+{
+ bool IsTop = false;
+ WizardStepSequence StepSequence = WizardStepSequence.Linear;
+
+ void OnStepChange(FluentWizardStepChangeEventArgs e)
+ {
+ }
+
+ async Task OnFinishedAsync()
+ {
+ await Task.CompletedTask;
+ }
+}
diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/Wizard/Examples/WizardEditForms.razor b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/Wizard/Examples/WizardEditForms.razor
new file mode 100644
index 0000000000..e5cd3cedc7
--- /dev/null
+++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/Wizard/Examples/WizardEditForms.razor
@@ -0,0 +1,140 @@
+@using System.ComponentModel.DataAnnotations
+
+@inject IDialogService DialogService
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Phasellus quis augue convallis, congue velit ac, aliquam ex. In egestas porttitor massa
+ aliquet porttitor. Donec bibendum faucibus urna vitae elementum. Phasellus vitae efficitur
+ turpis, eget molestie ipsum.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+@if (_overlayIsVisible)
+{
+
+
+
+}
+
+@code
+{
+ private FormData1 _formData1 = new FormData1();
+ private FormData2 _formData2 = new FormData2();
+ private FinishFormData _finishFormData = new FinishFormData();
+ private bool _overlayIsVisible = false;
+
+ void OnStepChange(FluentWizardStepChangeEventArgs e)
+ {
+ }
+
+ async Task OnFinishedAsync()
+ {
+ await DialogService.ShowInfoAsync("Wizard completed");
+ }
+
+ async Task OnValidSubmit()
+ {
+ _overlayIsVisible = true;
+ await Task.Delay(2000);
+ _overlayIsVisible = false;
+ }
+
+ void OnInvalidSubmit()
+ {
+ }
+
+ private class FormData1
+ {
+ [Required]
+ [MaxLength(3)]
+ public string? FirstName { get; set; }
+
+ [Required]
+ [MinLength(10)]
+ public string? LastName { get; set; }
+ }
+
+ private class FormData2
+ {
+ [Required]
+ public string? AddressLine1 { get; set; }
+
+ public string? AddressLine2 { get; set; }
+
+ [Required]
+ public string? City { get; set; }
+
+ [Required]
+ public string? StateOrProvince { get; set; }
+
+ [Required]
+ public string? Country { get; set; }
+
+ [Required]
+ public string? PostalCode { get; set; }
+ }
+
+ private class FinishFormData
+ {
+ [Required]
+ [MinLength(5)]
+ public string? Signature { get; set; }
+ }
+}
diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/Wizard/FluentWizard.md b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/Wizard/FluentWizard.md
new file mode 100644
index 0000000000..f682d31550
--- /dev/null
+++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/Wizard/FluentWizard.md
@@ -0,0 +1,61 @@
+---
+title: Wizard
+route: /Wizard
+icon: Steps
+---
+
+# Wizard
+
+**Wizards** are a step-by-step user interface used to break down complex tasks into digestible pieces.
+The simplified layout allows the reader to more easily understand the scope of a given task and the actions
+needed to complete the task.
+
+By default, steps are displayed on the left, but you can move them to the top of the component.
+They are in the form of circular bubbles, with a check mark indicating whether it has been processed or not.
+They are not numbered, but the **DisplayStepNumber** property can be used to add this numbering.
+It's also possible to customize these bubbles via the **IconPrevious**, **IconCurrent**
+and **IconNext** properties.
+
+The order of the steps must be defined when designing the Wizard.
+However, it is possible to enable or disable a step via the **Disabled** property.
+
+By default, the contents of all steps are hidden and displayed when the user arrives at that
+that step (for display performance reasons). But the **DeferredLoading** property
+property reverses this process and generates the contents of the active step only.
+
+The **Label** and **Summary** properties display the name and a small summary of the step below or next to the bubble.
+The **StepTitleHiddenWhen** property is used to hide this title and summary when the screen width
+is reduced, for example on mobile devices. By default, the value `XsAndDown` is applied
+to hide this data on cell phones (< 600px).
+
+All these areas (bubbles on the left/top and navigation buttons at the bottom) are fully customizable
+using the **StepTemplate** and **ButtonTemplate** properties (see the second example).
+You can customize button labels using the **ButtonTemplate** or by modifying
+the static properties **FluentWizard.LabelButtonPrevious / LabelButtonNext / LabelButtonDone**.
+
+> **note**: this FluentWizard is not yet fully compatible with accessibility.
+
+{{ WizardDefault }}
+
+## Customized
+
+You can customize the wizard with a **ButtonTemplate** to replace the default Previous/Next/Done buttons,
+and **StepTemplate** to fully control how each step indicator is rendered.
+
+{{ WizardCustomized }}
+
+## EditForms
+
+The wizard supports **EditForm** validation. When a step contains an `EditForm`, the wizard will
+automatically validate the form before navigating to the next step. If validation fails,
+the step change is cancelled.
+
+{{ WizardEditForms }}
+
+## API FluentWizard
+
+{{ API Type=FluentWizard }}
+
+## API FluentWizardStep
+
+{{ API Type=FluentWizardStep }}
diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/GetStarted/Migration/MigrationFluentWizard.md b/examples/Demo/FluentUI.Demo.Client/Documentation/GetStarted/Migration/MigrationFluentWizard.md
index 9469dc5470..5019745d07 100644
--- a/examples/Demo/FluentUI.Demo.Client/Documentation/GetStarted/Migration/MigrationFluentWizard.md
+++ b/examples/Demo/FluentUI.Demo.Client/Documentation/GetStarted/Migration/MigrationFluentWizard.md
@@ -4,80 +4,32 @@ route: /Migration/Wizard
hidden: true
---
-- ### Component removed 💥
+- ### Component re-introduced
- `FluentWizard` and `FluentWizardStep` have been **removed** in V5.
- There is no direct replacement component.
+ `FluentWizard` and `FluentWizardStep` have been **re-introduced** in V5.
+ The component preserves the same API and functionality from V4 with the following changes.
-- ### V4 FluentWizard parameters (removed)
+- ### Breaking changes 💥
- | Parameter | Type | Default |
- |-----------|------|---------|
- | `Height` | `string` | `"400px"` |
- | `Width` | `string` | `"100%"` |
- | `OnFinish` | `EventCallback` | — |
- | `StepperPosition` | `StepperPosition` | `Left` |
- | `StepperSize` | `string?` | — |
- | `StepperBulletSpace` | `string?` | — |
- | `Border` | `WizardBorder` | `None` |
- | `DisplayStepNumber` | `WizardStepStatus` | `None` |
- | `Value` / `ValueChanged` | `int` / `EventCallback` | `0` |
- | `ButtonTemplate` | `RenderFragment?` | — |
- | `Steps` | `RenderFragment?` | — |
- | `StepTitleHiddenWhen` | `GridItemHidden?` | `XsAndDown` |
- | `StepSequence` | `WizardStepSequence` | `Linear` |
+ | V4 | V5 |
+ |----|-----|
+ | `Appearance.Neutral` (in ButtonTemplate) | `ButtonAppearance.Default` |
+ | `Appearance.Accent` (in ButtonTemplate) | `ButtonAppearance.Primary` |
+ | `FluentLabel Typo="Typography.Body"` | `FluentLabel` (no `Typo` parameter; use `Size` / `Weight`) |
+ | `FluentLabel Typo="Typography.Header"` | `FluentLabel Weight="LabelWeight.Bold"` |
+ | `Icons.*.Size24.*` (icon defaults) | `CoreIcons.*.Size20.*` |
+ | `FluentTextField` | `FluentTextInput` |
+ | `FluentEditForm` | `EditForm` (standard Blazor) |
-- ### V4 FluentWizardStep parameters (removed)
+- ### Icon defaults changed
- | Parameter | Type | Default |
- |-----------|------|---------|
- | `Label` | `string` | `""` |
- | `Summary` | `string` | `""` |
- | `Disabled` | `bool` | `false` |
- | `DeferredLoading` | `bool` | `false` |
- | `OnChange` | `EventCallback` | — |
- | `IconPrevious` / `IconCurrent` / `IconNext` | `Icon` | — |
- | `StepTemplate` | `RenderFragment?` | — |
+ The default icons for wizard steps now use **Size20** instead of Size24:
+ - `IconPrevious` = `CoreIcons.Filled.Size20.CheckmarkCircle()`
+ - `IconCurrent` = `CoreIcons.Filled.Size20.Circle()`
+ - `IconNext` = `CoreIcons.Regular.Size20.Circle()`
-- ### Removed enums
+- ### Re-introduced enums
- `WizardBorder`
- `WizardStepSequence`
- `WizardStepStatus`
- `StepperPosition`
-
-- ### Migration strategy
-
- Build a custom wizard using `FluentTabs` for step navigation,
- or implement step-based logic with conditional rendering:
-
- ```xml
-
-
-
-
- @for (int i = 0; i < steps.Length; i++)
- {
-
- @(i + 1). @steps[i]
-
- }
-
-
-
- @switch (currentStep)
- {
- case 0: break;
- case 1: break;
- case 2: break;
- }
-
-
-
- Previous
-
- @(currentStep == steps.Length - 1 ? "Finish" : "Next")
-
-
-
- ```
diff --git a/src/Core/Components/Icons/CoreIcons.cs b/src/Core/Components/Icons/CoreIcons.cs
index 0f97af663c..bb0b42a1ee 100644
--- a/src/Core/Components/Icons/CoreIcons.cs
+++ b/src/Core/Components/Icons/CoreIcons.cs
@@ -95,6 +95,8 @@ public class PresenceTentative : Icon { public PresenceTentative() : base("Prese
public class PresenceUnknown : Icon { public PresenceUnknown() : base("PresenceUnknown", IconVariant.Regular, IconSize.Size20, " ") { } }
+ public class Circle : Icon { public Circle() : base("Circle", IconVariant.Regular, IconSize.Size20, " ") { } }
+
public class RadioButton : Icon { public RadioButton() : base("RadioButton", IconVariant.Regular, IconSize.Size20, " ") { } };
public class Search : Icon { public Search() : base("Search", IconVariant.Regular, IconSize.Size20, " ") { } }
@@ -133,6 +135,8 @@ public class PresenceBusy : Icon { public PresenceBusy() : base("PresenceBusy",
public class PresenceDnd : Icon { public PresenceDnd() : base("PresenceDnd", IconVariant.Filled, IconSize.Size20, " ") { } }
+ public class Circle : Icon { public Circle() : base("Circle", IconVariant.Filled, IconSize.Size20, " ") { } }
+
public class RadioButton : Icon { public RadioButton() : base("RadioButton", IconVariant.Regular, IconSize.Size20, " ") { } };
public class Star : Icon { public Star() : base("Star", IconVariant.Filled, IconSize.Size20, " ") { } };
diff --git a/src/Core/Components/Wizard/FluentWizard.razor b/src/Core/Components/Wizard/FluentWizard.razor
new file mode 100644
index 0000000000..4aeab0a69e
--- /dev/null
+++ b/src/Core/Components/Wizard/FluentWizard.razor
@@ -0,0 +1,69 @@
+@namespace Microsoft.FluentUI.AspNetCore.Components
+@inherits FluentComponentBase
+
+
+
+
+
+ @Steps
+
+
+
+ @foreach (var step in _steps.Where(i => i.Index == Value || !i.DeferredLoading))
+ {
+
+
+ @(step.ChildContent)
+
+
+ }
+
+
+
+ @if (ButtonTemplate == null)
+ {
+ string buttonWidth = "80px;";
+
+ @if (DisplayPreviousButton)
+ {
+
+ @LabelButtonPrevious
+
+ }
+
+
+
+ @if (DisplayNextButton)
+ {
+
+ @LabelButtonNext
+
+ }
+ else
+ {
+
+ @LabelButtonDone
+
+ }
+ }
+ else
+ {
+ @ButtonTemplate(Value)
+ }
+
+
+
diff --git a/src/Core/Components/Wizard/FluentWizard.razor.cs b/src/Core/Components/Wizard/FluentWizard.razor.cs
new file mode 100644
index 0000000000..9b0c05c31f
--- /dev/null
+++ b/src/Core/Components/Wizard/FluentWizard.razor.cs
@@ -0,0 +1,395 @@
+// ------------------------------------------------------------------------
+// This file is licensed to you under the MIT License.
+// ------------------------------------------------------------------------
+
+using Microsoft.AspNetCore.Components;
+using Microsoft.AspNetCore.Components.Web;
+using Microsoft.FluentUI.AspNetCore.Components.Utilities;
+
+namespace Microsoft.FluentUI.AspNetCore.Components;
+
+///
+/// A wizard component that provides a step-by-step user interface.
+///
+public partial class FluentWizard : FluentComponentBase
+{
+ ///
+ /// Gets or sets the label for the Previous button.
+ ///
+ public static string LabelButtonPrevious { get; set; } = "Previous";
+
+ ///
+ /// Gets or sets the label for the Next button.
+ ///
+ public static string LabelButtonNext { get; set; } = "Next";
+
+ ///
+ /// Gets or sets the label for the Done button.
+ ///
+ public static string LabelButtonDone { get; set; } = "Done";
+
+ private readonly List _steps = new();
+ private int _value;
+ internal int _maxStepVisited;
+
+ ///
+ public FluentWizard(LibraryConfiguration configuration) : base(configuration)
+ {
+ Id = Identifier.NewId();
+ }
+
+ ///
+ protected string? ClassValue => DefaultClassBuilder
+ .AddClass("fluent-wizard")
+ .Build();
+
+ ///
+ protected string? StyleValue => DefaultStyleBuilder
+ .AddStyle("width", Width)
+ .AddStyle("height", Height)
+ .Build();
+
+ ///
+ /// Gets or sets the height of the wizard.
+ ///
+ [Parameter]
+ public string Height { get; set; } = "400px";
+
+ ///
+ /// Gets or sets the width of the wizard.
+ ///
+ [Parameter]
+ public string Width { get; set; } = "100%";
+
+ ///
+ /// Triggers when the done button is clicked.
+ ///
+ [Parameter]
+ public EventCallback OnFinish { get; set; }
+
+ ///
+ /// Gets or sets the stepper position in the wizard (Top or Left).
+ ///
+ [Parameter]
+ public StepperPosition StepperPosition { get; set; } = StepperPosition.Left;
+
+ ///
+ /// Gets or sets the stepper width (if position is Left)
+ /// or the stepper height (if position is Top).
+ ///
+ [Parameter]
+ public string? StepperSize { get; set; }
+
+ ///
+ /// Gets or sets the space between two bullets (ex. 120px).
+ ///
+ [Parameter]
+ public string? StepperBulletSpace { get; set; }
+
+ ///
+ /// Display a border of the Wizard.
+ ///
+ [Parameter]
+ public WizardBorder Border { get; set; } = WizardBorder.None;
+
+ ///
+ /// Display a number on each step icon. Can be overridden by the step property.
+ ///
+ [Parameter]
+ public WizardStepStatus DisplayStepNumber { get; set; } = WizardStepStatus.None;
+
+ ///
+ /// Gets or sets the step index of the current step.
+ /// This value is bindable.
+ ///
+#pragma warning disable BL0007 // Component parameters should be auto properties
+ [Parameter]
+ public int Value
+#pragma warning restore BL0007
+ {
+ get
+ {
+ return _value;
+ }
+
+ set
+ {
+ if (value < 0 || _steps.Count <= 0)
+ {
+ _value = 0;
+ }
+ else if (value > _steps.Count - 1)
+ {
+ _value = _steps.Count - 1;
+ }
+ else
+ {
+ _value = value;
+ }
+
+ _maxStepVisited = Math.Max(_value, _maxStepVisited);
+
+ SetCurrentStatusToStep(_value);
+ }
+ }
+
+ ///
+ /// Triggers when the value has changed.
+ ///
+ [Parameter]
+ public EventCallback ValueChanged { get; set; }
+
+ ///
+ /// Gets or sets the buttons section of the wizard.
+ /// This configuration overrides the whole rendering of the bottom-right section of the Wizard,
+ /// including the built-in buttons and thus provides a full control over it.
+ /// Custom Wizard buttons do not trigger the component OnChange and OnFinish events.
+ /// The OnChange event can be triggered using the method from your code.
+ ///
+ [Parameter]
+ public RenderFragment? ButtonTemplate { get; set; }
+
+ ///
+ /// Gets or sets the wizard steps. Add WizardStep tags inside this tag.
+ ///
+ [Parameter]
+ public RenderFragment? Steps { get; set; }
+
+ ///
+ /// Hide step titles and summaries on specified sizes (you can combine several values: GridItemHidden.Sm | GridItemHidden.Xl).
+ /// The default value is to adapt to mobile devices.
+ ///
+ [Parameter]
+ public GridItemHidden? StepTitleHiddenWhen { get; set; } = GridItemHidden.XsAndDown;
+
+ ///
+ /// Gets or sets the way to navigate in the Wizard Steps.
+ /// Default is .
+ ///
+ [Parameter]
+ public WizardStepSequence StepSequence { get; set; } = WizardStepSequence.Linear;
+
+ ///
+ protected virtual async Task OnNextHandlerAsync(MouseEventArgs e)
+ {
+ // Target step index
+ var targetIndex = Value;
+ do
+ {
+ targetIndex++;
+ }
+ while (_steps[targetIndex].Disabled && targetIndex < _steps.Count - 1);
+
+ // StepChange event
+ var stepChangeArgs = await OnStepChangeHandlerAsync(targetIndex, true);
+ var isCanceled = stepChangeArgs?.IsCancelled ?? false;
+
+ if (!isCanceled)
+ {
+ Value = targetIndex;
+ await ValueChanged.InvokeAsync(targetIndex);
+ StateHasChanged();
+ }
+ }
+
+ ///
+ protected virtual async Task OnPreviousHandlerAsync(MouseEventArgs e)
+ {
+ // Target step index
+ var targetIndex = Value;
+ do
+ {
+ targetIndex--;
+ }
+ while (_steps[targetIndex].Disabled && targetIndex > 0);
+
+ // StepChange event
+ var stepChangeArgs = await OnStepChangeHandlerAsync(targetIndex, false);
+ var isCanceled = stepChangeArgs?.IsCancelled ?? false;
+
+ if (!isCanceled)
+ {
+ Value = targetIndex;
+ await ValueChanged.InvokeAsync(targetIndex);
+ StateHasChanged();
+ }
+ }
+
+ ///
+ protected virtual async Task OnStepChangeHandlerAsync(int targetIndex, bool validateEditContexts)
+ {
+ var stepChangeArgs = new FluentWizardStepChangeEventArgs(targetIndex, _steps[targetIndex].Label);
+
+ if (validateEditContexts)
+ {
+ var allEditContextsAreValid = _steps[Value].ValidateEditContexts();
+ stepChangeArgs.IsCancelled = !allEditContextsAreValid;
+
+ if (!allEditContextsAreValid)
+ {
+ await _steps[Value].InvokeOnInValidSubmitForEditFormsAsync();
+ }
+
+ if (!stepChangeArgs.IsCancelled && allEditContextsAreValid)
+ {
+ // Invoke the 'OnValidSubmit' handlers for the Edit Forms
+ await _steps[Value].InvokeOnValidSubmitForEditFormsAsync();
+ }
+
+ await _steps[Value].InvokeOnSubmitForEditFormsAsync();
+ }
+
+ return await OnStepChangeHandlerAsync(stepChangeArgs);
+ }
+
+ ///
+ protected virtual async Task OnStepChangeHandlerAsync(FluentWizardStepChangeEventArgs args)
+ {
+ if (_steps[Value].OnChange.HasDelegate)
+ {
+ await _steps[Value].OnChange.InvokeAsync(args);
+ }
+
+ if (_steps[Value].DeferredLoading && !args.IsCancelled)
+ {
+ _steps[Value].ClearEditFormAndContext();
+ }
+
+ return args;
+ }
+
+ ///
+ protected virtual async Task OnFinishHandlerAsync(MouseEventArgs e)
+ {
+ await FinishAsync(true);
+ }
+
+ ///
+ /// Optionally validate and invoke the handler.
+ ///
+ /// Validate the EditContext. Default is false.
+ ///
+ public async Task FinishAsync(bool validateEditContexts = false)
+ {
+ if (validateEditContexts)
+ {
+ // Validate any form edit contexts
+ var allEditContextsAreValid = _steps[Value].ValidateEditContexts();
+ if (!allEditContextsAreValid)
+ {
+ // Invoke the 'OnInvalidSubmit' handlers for the edit forms.
+ await _steps[Value].InvokeOnInValidSubmitForEditFormsAsync();
+ return;
+ }
+ }
+
+ // Invoke the 'OnValidSubmit' handlers for the edit forms.
+ await _steps[Value].InvokeOnValidSubmitForEditFormsAsync();
+ await _steps[Value].InvokeOnSubmitForEditFormsAsync();
+
+ _steps[Value].Status = WizardStepStatus.Previous;
+
+ if (OnFinish.HasDelegate)
+ {
+ await OnFinish.InvokeAsync();
+ }
+ }
+
+ ///
+ /// Navigate to the specified step, with or without validate the current EditContexts.
+ ///
+ /// Index number of the step to display
+ /// Validate the EditContext. Default is false.
+ ///
+ public Task GoToStepAsync(int step, bool validateEditContexts = false)
+ {
+ return ValidateAndGoToStepAsync(step, validateEditContexts);
+ }
+
+ internal async Task ValidateAndGoToStepAsync(int targetIndex, bool validateEditContexts)
+ {
+ var stepChangeArgs = await OnStepChangeHandlerAsync(targetIndex, validateEditContexts);
+ var isCanceled = stepChangeArgs?.IsCancelled ?? false;
+
+ if (!isCanceled)
+ {
+ Value = targetIndex;
+ await ValueChanged.InvokeAsync(targetIndex);
+ StateHasChanged();
+ }
+ }
+
+ internal int AddStep(FluentWizardStep step)
+ {
+ _steps.Add(step);
+ var index = _steps.Count - 1;
+
+ if (index == Value)
+ {
+ SetCurrentStatusToStep(index);
+ }
+
+ StateHasChanged();
+
+ return index;
+ }
+
+ internal int StepCount => _steps.Count;
+
+ internal void RemoveStep(FluentWizardStep step)
+ {
+ _steps.Remove(step);
+ }
+
+ private void SetCurrentStatusToStep(int stepIndex)
+ {
+ for (var i = 0; i < _steps.Count; i++)
+ {
+ // Step disabled
+ if (_steps[i].Disabled)
+ {
+ _steps[i].Status = WizardStepStatus.Next;
+ }
+
+ // Step enabled
+ else
+ {
+ if (i < stepIndex)
+ {
+ _steps[i].Status = WizardStepStatus.Previous;
+ }
+ else if (i == stepIndex)
+ {
+ _steps[i].Status = WizardStepStatus.Current;
+ }
+ else
+ {
+ _steps[i].Status = WizardStepStatus.Next;
+ }
+ }
+ }
+ }
+
+ private string? GetStepperWidthOrHeight()
+ {
+ if (string.IsNullOrEmpty(StepperSize))
+ {
+ return null;
+ }
+
+ switch (StepperPosition)
+ {
+ case StepperPosition.Top:
+ return $"height: {StepperSize}";
+
+ case StepperPosition.Left:
+ return $"width: {StepperSize}";
+ }
+
+ return null;
+ }
+
+ private bool DisplayPreviousButton => Value > 0 && _steps[..Value].Any(i => !i.Disabled);
+
+ private bool DisplayNextButton => Value < _steps.Count - 1 && _steps[(Value + 1)..].Any(i => !i.Disabled);
+}
diff --git a/src/Core/Components/Wizard/FluentWizard.razor.css b/src/Core/Components/Wizard/FluentWizard.razor.css
new file mode 100644
index 0000000000..c948191cc7
--- /dev/null
+++ b/src/Core/Components/Wizard/FluentWizard.razor.css
@@ -0,0 +1,80 @@
+.fluent-wizard {
+ display: grid;
+ height: 100%;
+ --fluent-wizard-circle-size: 24px;
+ --fluent-wizard-spacing: 4px;
+}
+
+ .fluent-wizard[border-outside] {
+ border: 1px solid var(--colorNeutralStroke1);
+ }
+
+ .fluent-wizard > ol {
+ display: flex;
+ list-style-type: none;
+ padding-inline-start: 0px;
+ margin-block-start: 0px;
+ margin-block-end: 0px;
+ padding: 10px;
+ }
+
+ .fluent-wizard .fluent-wizard-buttons {
+ display: flex;
+ justify-content: end;
+ }
+
+ .fluent-wizard .fluent-wizard-buttons[border-inside] {
+ border-top: 1px solid var(--colorNeutralStroke1);
+ }
+
+ /* Wizard with steps on Left */
+ .fluent-wizard[position="left"] {
+ grid-template-columns: auto 1fr;
+ grid-template-rows: 1fr auto;
+ }
+
+ .fluent-wizard[position="left"] > ol {
+ flex-direction: column;
+ grid-column: 1;
+ grid-row: 1 / span 2;
+ }
+
+ .fluent-wizard[position="left"] > ol[border-inside] {
+ border-right: 1px solid var(--colorNeutralStroke1);
+ }
+
+ .fluent-wizard[position="left"] .fluent-wizard-content {
+ grid-column: 2;
+ grid-row: 1;
+ margin: 5px 10px 0px 15px;
+ }
+
+ .fluent-wizard[position="left"] .fluent-wizard-buttons {
+ grid-column: 2;
+ grid-row: 2;
+ text-align-last: end;
+ padding: 10px;
+ }
+
+ /* Wizard with steps on Top */
+ .fluent-wizard[position="top"] {
+ grid-template-columns: auto;
+ grid-template-rows: auto 1fr auto;
+ }
+
+ .fluent-wizard[position="top"] > ol {
+ flex-direction: row;
+ justify-content: center;
+ }
+
+ .fluent-wizard[position="top"] > ol[border-inside] {
+ border-bottom: 1px solid var(--colorNeutralStroke1);
+ }
+
+ .fluent-wizard[position="top"] .fluent-wizard-content {
+ margin: 5px 10px 0px 10px;
+ }
+
+ .fluent-wizard[position="top"] .fluent-wizard-buttons {
+ padding: 10px;
+ }
diff --git a/src/Core/Components/Wizard/FluentWizardStep.razor b/src/Core/Components/Wizard/FluentWizardStep.razor
new file mode 100644
index 0000000000..4177ef0bb3
--- /dev/null
+++ b/src/Core/Components/Wizard/FluentWizardStep.razor
@@ -0,0 +1,42 @@
+@namespace Microsoft.FluentUI.AspNetCore.Components
+@using Microsoft.FluentUI.AspNetCore.Components.Extensions
+@inherits FluentComponentBase
+
+
+ @if (StepTemplate is null)
+ {
+
+
+
+ @if (DisplayStepNumber ?? FluentWizard.DisplayStepNumber.HasFlag(Status))
+ {
+
+ @(Index + 1)
+
+ }
+
+
+ @Label
+ @if (!string.IsNullOrEmpty(Summary))
+ {
+ @Summary
+ }
+
+ }
+ else
+ {
+ @StepTemplate(new FluentWizardStepArgs(Index, FluentWizard.Value))
+ }
+
+ @if (!IsLastStep)
+ {
+
+
+ }
+
diff --git a/src/Core/Components/Wizard/FluentWizardStep.razor.cs b/src/Core/Components/Wizard/FluentWizardStep.razor.cs
new file mode 100644
index 0000000000..85da886171
--- /dev/null
+++ b/src/Core/Components/Wizard/FluentWizardStep.razor.cs
@@ -0,0 +1,300 @@
+// ------------------------------------------------------------------------
+// This file is licensed to you under the MIT License.
+// ------------------------------------------------------------------------
+
+using Microsoft.AspNetCore.Components;
+using Microsoft.AspNetCore.Components.Forms;
+using Microsoft.FluentUI.AspNetCore.Components.Utilities;
+
+namespace Microsoft.FluentUI.AspNetCore.Components;
+
+///
+/// Represents an individual step within a component.
+///
+public partial class FluentWizardStep : FluentComponentBase
+{
+ private readonly Dictionary _editForms = new Dictionary();
+ private readonly List _editContexts = new List();
+
+ ///
+ public FluentWizardStep(LibraryConfiguration configuration) : base(configuration)
+ {
+ Id = Identifier.NewId();
+ }
+
+ ///
+ protected string? ClassValue => DefaultClassBuilder.Build();
+
+ ///
+ protected string? StyleValue => DefaultStyleBuilder
+ .AddStyle("position", "relative")
+ .AddStyle("display", "flex")
+ .AddStyle("gap", "10px", when: FluentWizard.StepperPosition == StepperPosition.Left)
+ .AddStyle("flex-direction", "column", when: FluentWizard.StepperPosition == StepperPosition.Top)
+ .AddStyle("align-items", "center", when: FluentWizard.StepperPosition == StepperPosition.Top)
+ .AddStyle("flex", "1", when: FluentWizard.StepperPosition == StepperPosition.Top)
+ .AddStyle("text-align", "center", when: FluentWizard.StepperPosition == StepperPosition.Top)
+ .AddStyle("max-width", FluentWizard.StepperBulletSpace ?? "100%", when: FluentWizard.StepperPosition == StepperPosition.Top)
+ .AddStyle("height", IsLastStep ? "auto" : (FluentWizard.StepperBulletSpace ?? "100%"), when: FluentWizard.StepperPosition == StepperPosition.Left)
+ .AddStyle("cursor", "pointer", when: IsStepClickable)
+ .Build();
+
+ ///
+ /// Gets or sets the content of the step.
+ ///
+ [Parameter]
+ public RenderFragment? ChildContent { get; set; }
+
+ ///
+ /// Gets or sets the template of the step icon.
+ ///
+ [Parameter]
+ public RenderFragment? StepTemplate { get; set; }
+
+ ///
+ /// Gets the step index.
+ ///
+ public int Index { get; private set; }
+
+ ///
+ /// Gets or sets whether the step is disabled.
+ ///
+ [Parameter]
+ public bool Disabled { get; set; } = false;
+
+ ///
+ /// Render the Wizard Step content only when the Step is selected.
+ ///
+ [Parameter]
+ public bool DeferredLoading { get; set; } = false;
+
+ ///
+ /// Gets or sets the label of the step.
+ ///
+ [Parameter]
+ public string Label { get; set; } = string.Empty;
+
+ ///
+ /// Display a number on the step icon.
+ /// By default, this is the value.
+ ///
+ [Parameter]
+ public bool? DisplayStepNumber { get; set; }
+
+ ///
+ /// The OnChange event fires before the current step has changed.
+ /// The EventArgs contains a field of the targeted new step and a field to cancel the built-in action.
+ ///
+ [Parameter]
+ public EventCallback OnChange { get; set; }
+
+ ///
+ /// Reference to the parent component.
+ /// For internal use only.
+ ///
+ [CascadingParameter]
+ public FluentWizard FluentWizard { get; set; } = default!;
+
+ ///
+ /// Gets or sets the summary of the step, to display near the label.
+ ///
+ [Parameter]
+ public string Summary { get; set; } = string.Empty;
+
+ ///
+ /// Gets or sets the icon to display for the past/previous step.
+ /// By default, it is a checkmark circle.
+ ///
+ [Parameter]
+ public Icon IconPrevious { get; set; } = new CoreIcons.Filled.Size20.CheckmarkCircle();
+
+ ///
+ /// Gets or sets the icon to display for the current/active step.
+ /// By default, it is a filled circle.
+ ///
+ [Parameter]
+ public Icon IconCurrent { get; set; } = new CoreIcons.Filled.Size20.Circle();
+
+ ///
+ /// Gets or sets the icon to display for the future/next step.
+ /// By default, it is a regular circle.
+ ///
+ [Parameter]
+ public Icon IconNext { get; set; } = new CoreIcons.Regular.Size20.Circle();
+
+ internal WizardStepStatus Status { get; set; } = WizardStepStatus.Next;
+
+ private bool IsLastStep => Index >= FluentWizard.StepCount - 1;
+
+ private string IconStyle => "width: var(--fluent-wizard-circle-size);" +
+ (Disabled ? " fill-opacity: 0.4;" : string.Empty);
+
+ private Icon StepIcon
+ {
+ get
+ {
+ switch (Status)
+ {
+ case WizardStepStatus.Previous:
+ return IconPrevious;
+
+ case WizardStepStatus.Current:
+ return IconCurrent;
+
+ case WizardStepStatus.Next:
+ return IconNext;
+
+ default:
+ return new CoreIcons.Regular.Size20.Circle();
+ }
+ }
+ }
+
+ ///
+ protected override void OnInitialized()
+ {
+ if (FluentWizard == null)
+ {
+ throw new ArgumentException("The FluentWizardStep must be included in the FluentWizard component.");
+ }
+
+ Index = FluentWizard.AddStep(this);
+ base.OnInitialized();
+ }
+
+ ///
+ public override async ValueTask DisposeAsync()
+ {
+ FluentWizard?.RemoveStep(this);
+ await base.DisposeAsync();
+ }
+
+ ///
+ /// Registers an EditForm and its EditContext for validation tracking.
+ ///
+ public void RegisterEditFormAndContext(EditForm editForm, EditContext editContext)
+ {
+ if (!_editForms.ContainsKey(editForm))
+ {
+ _editForms.Add(editForm, editContext);
+ }
+ }
+
+ ///
+ /// Clears all registered EditForm and EditContext pairs.
+ ///
+ public void ClearEditFormAndContext()
+ {
+ _editForms.Clear();
+ }
+
+ ///
+ /// Registers an for validation tracking.
+ /// This is typically called by the component.
+ ///
+ public void RegisterEditContext(EditContext editContext)
+ {
+ if (!_editContexts.Contains(editContext))
+ {
+ _editContexts.Add(editContext);
+ }
+ }
+
+ ///
+ /// Unregisters an from validation tracking.
+ ///
+ public void UnregisterEditContext(EditContext editContext)
+ {
+ _editContexts.Remove(editContext);
+ }
+
+ ///
+ /// Validates all registered EditContexts.
+ ///
+ public bool ValidateEditContexts()
+ {
+ var isValid = true;
+ foreach (var editForm in _editForms)
+ {
+ var contextIsValid = editForm.Value.Validate();
+ if (!contextIsValid)
+ {
+ isValid = false;
+ }
+ }
+
+ foreach (var editContext in _editContexts)
+ {
+ var contextIsValid = editContext.Validate();
+ if (!contextIsValid)
+ {
+ isValid = false;
+ }
+ }
+
+ return isValid;
+ }
+
+ internal async Task InvokeOnValidSubmitForEditFormsAsync()
+ {
+ foreach (var editForm in _editForms)
+ {
+ await editForm.Key.OnValidSubmit.InvokeAsync(editForm.Value);
+ }
+ }
+
+ internal async Task InvokeOnInValidSubmitForEditFormsAsync()
+ {
+ foreach (var editForm in _editForms)
+ {
+ await editForm.Key.OnInvalidSubmit.InvokeAsync(editForm.Value);
+ }
+ }
+
+ internal async Task InvokeOnSubmitForEditFormsAsync()
+ {
+ foreach (var editForm in _editForms)
+ {
+ await editForm.Key.OnSubmit.InvokeAsync(editForm.Value);
+ }
+ }
+
+ private async Task OnClickHandlerAsync()
+ {
+ if (!IsStepClickable)
+ {
+ return;
+ }
+
+ await FluentWizard.ValidateAndGoToStepAsync(Index, validateEditContexts: Index > FluentWizard.Value);
+ }
+
+ private bool IsStepClickable
+ {
+ get
+ {
+ if (Disabled)
+ {
+ return false;
+ }
+
+ if (FluentWizard.Value == Index)
+ {
+ return false;
+ }
+
+ if (FluentWizard.StepSequence == WizardStepSequence.Linear)
+ {
+ return false;
+ }
+
+ if (FluentWizard.StepSequence == WizardStepSequence.Visited &&
+ Index > FluentWizard._maxStepVisited)
+ {
+ return false;
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/src/Core/Components/Wizard/FluentWizardStep.razor.css b/src/Core/Components/Wizard/FluentWizardStep.razor.css
new file mode 100644
index 0000000000..4df4551bbc
--- /dev/null
+++ b/src/Core/Components/Wizard/FluentWizardStep.razor.css
@@ -0,0 +1,103 @@
+/* Icon */
+.fluent-wizard-icon {
+ position: relative;
+ width: var(--fluent-wizard-circle-size);
+ height: var(--fluent-wizard-circle-size);
+}
+
+/* Icon Number */
+.fluent-wizard-icon-number {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: var(--fluent-wizard-circle-size);
+ height: var(--fluent-wizard-circle-size);
+ font-size: small;
+ color: var(--colorBrandForeground1);
+ text-align: center;
+ align-content: center;
+}
+
+.fluent-wizard-icon-number[disabled] {
+ opacity: 0.4;
+}
+
+.fluent-wizard-icon-number[status='previous']:not([disabled]) {
+ color: var(--colorNeutralForegroundOnBrand);
+}
+
+.fluent-wizard-icon-number[status='current']:not([disabled]) {
+ color: var(--colorNeutralForegroundOnBrand);
+}
+
+.fluent-wizard-icon-number[status='next']:not([disabled]) {
+ color: var(--colorBrandForeground1);
+}
+
+/* Connector line between steps */
+.fluent-wizard-step-connector[position="left"] {
+ position: absolute;
+ left: 0;
+ top: calc(var(--fluent-wizard-circle-size) + var(--fluent-wizard-spacing));
+ bottom: var(--fluent-wizard-spacing);
+ transform: translateX(calc(var(--fluent-wizard-circle-size) / 2));
+ width: 2px;
+ background-color: var(--colorNeutralStroke1);
+}
+
+[dir="rtl"] .fluent-wizard-step-connector[position="left"] {
+ left: revert;
+ transform: translateX(calc(var(--fluent-wizard-circle-size) / -2));
+}
+
+.fluent-wizard-step-connector[position="top"] {
+ position: absolute;
+ left: calc(50% + calc(var(--fluent-wizard-circle-size) / 2 + var(--fluent-wizard-spacing)));
+ right: unset;
+ top: calc(var(--fluent-wizard-circle-size) / 2);
+ width: calc(100% - var(--fluent-wizard-circle-size) - calc(var(--fluent-wizard-spacing) * 2));
+ height: 2px;
+ background-color: var(--colorNeutralStroke1);
+}
+
+[dir="rtl"] .fluent-wizard-step-connector[position="top"] {
+ left: unset;
+ right: calc(50% + calc(var(--fluent-wizard-circle-size) / 2 + var(--fluent-wizard-spacing)));
+}
+
+/* Hidden (responsive) */
+@media (max-width: 599.98px) {
+ div[hidden-when~="xs"] {
+ display: none;
+ }
+}
+
+@media (min-width: 600px) and (max-width: 959.98px) {
+ div[hidden-when~="sm"] {
+ display: none;
+ }
+}
+
+@media (min-width: 960px) and (max-width: 1279.98px) {
+ div[hidden-when~="md"] {
+ display: none;
+ }
+}
+
+@media (min-width: 1280px) and (max-width: 1919.98px) {
+ div[hidden-when~="lg"] {
+ display: none;
+ }
+}
+
+@media (min-width: 1920px) and (max-width: 2559.98px) {
+ div[hidden-when~="xl"] {
+ display: none;
+ }
+}
+
+@media (min-width: 2560px) {
+ div[hidden-when~="xxl"] {
+ display: none;
+ }
+}
diff --git a/src/Core/Components/Wizard/FluentWizardStepArgs.cs b/src/Core/Components/Wizard/FluentWizardStepArgs.cs
new file mode 100644
index 0000000000..82472c70fd
--- /dev/null
+++ b/src/Core/Components/Wizard/FluentWizardStepArgs.cs
@@ -0,0 +1,27 @@
+// ------------------------------------------------------------------------
+// This file is licensed to you under the MIT License.
+// ------------------------------------------------------------------------
+
+namespace Microsoft.FluentUI.AspNetCore.Components;
+
+///
+/// Arguments passed to the render fragment.
+///
+public class FluentWizardStepArgs
+{
+ internal FluentWizardStepArgs(int index, int active)
+ {
+ Index = index;
+ Active = index == active;
+ }
+
+ ///
+ /// Gets the index of the step.
+ ///
+ public int Index { get; }
+
+ ///
+ /// Gets a value indicating whether the step is the currently active step.
+ ///
+ public bool Active { get; }
+}
diff --git a/src/Core/Components/Wizard/FluentWizardStepChangeEventArgs.cs b/src/Core/Components/Wizard/FluentWizardStepChangeEventArgs.cs
new file mode 100644
index 0000000000..1cb602bb46
--- /dev/null
+++ b/src/Core/Components/Wizard/FluentWizardStepChangeEventArgs.cs
@@ -0,0 +1,33 @@
+// ------------------------------------------------------------------------
+// This file is licensed to you under the MIT License.
+// ------------------------------------------------------------------------
+
+namespace Microsoft.FluentUI.AspNetCore.Components;
+
+///
+/// Event arguments for the event.
+///
+public class FluentWizardStepChangeEventArgs
+{
+ ///
+ internal FluentWizardStepChangeEventArgs(int targetIndex, string targetLabel)
+ {
+ TargetIndex = targetIndex;
+ TargetLabel = targetLabel;
+ }
+
+ ///
+ /// Gets the index of the target step.
+ ///
+ public int TargetIndex { get; }
+
+ ///
+ /// Gets the label of the target step.
+ ///
+ public string TargetLabel { get; }
+
+ ///
+ /// Gets or sets a value indicating whether the step change should be cancelled.
+ ///
+ public bool IsCancelled { get; set; }
+}
diff --git a/src/Core/Components/Wizard/FluentWizardStepValidator.cs b/src/Core/Components/Wizard/FluentWizardStepValidator.cs
new file mode 100644
index 0000000000..5d7541a292
--- /dev/null
+++ b/src/Core/Components/Wizard/FluentWizardStepValidator.cs
@@ -0,0 +1,49 @@
+// ------------------------------------------------------------------------
+// This file is licensed to you under the MIT License.
+// ------------------------------------------------------------------------
+
+using Microsoft.AspNetCore.Components;
+using Microsoft.AspNetCore.Components.Forms;
+
+namespace Microsoft.FluentUI.AspNetCore.Components;
+
+///
+/// A component that automatically registers an with a parent
+/// for validation when navigating between wizard steps.
+/// Place this component inside an within a .
+///
+public class FluentWizardStepValidator : ComponentBase, IDisposable
+{
+ [CascadingParameter]
+ private FluentWizardStep? WizardStep { get; set; }
+
+ [CascadingParameter]
+ private EditContext? EditContext { get; set; }
+
+ ///
+ protected override void OnInitialized()
+ {
+ if (WizardStep is null)
+ {
+ throw new InvalidOperationException(
+ $"{nameof(FluentWizardStepValidator)} must be used inside a {nameof(FluentWizardStep)}.");
+ }
+
+ if (EditContext is null)
+ {
+ throw new InvalidOperationException(
+ $"{nameof(FluentWizardStepValidator)} must be used inside an {nameof(EditForm)}.");
+ }
+
+ WizardStep.RegisterEditContext(EditContext);
+ }
+
+ ///
+ public void Dispose()
+ {
+ if (WizardStep is not null && EditContext is not null)
+ {
+ WizardStep.UnregisterEditContext(EditContext);
+ }
+ }
+}
diff --git a/src/Core/Enums/StepperPosition.cs b/src/Core/Enums/StepperPosition.cs
new file mode 100644
index 0000000000..c4dd6bea25
--- /dev/null
+++ b/src/Core/Enums/StepperPosition.cs
@@ -0,0 +1,21 @@
+// ------------------------------------------------------------------------
+// This file is licensed to you under the MIT License.
+// ------------------------------------------------------------------------
+
+namespace Microsoft.FluentUI.AspNetCore.Components;
+
+///
+/// Defines the position of the stepper in the component.
+///
+public enum StepperPosition
+{
+ ///
+ /// Steps are displayed at the top of the wizard.
+ ///
+ Top,
+
+ ///
+ /// Steps are displayed on the left side of the wizard.
+ ///
+ Left,
+}
diff --git a/src/Core/Enums/WizardBorder.cs b/src/Core/Enums/WizardBorder.cs
new file mode 100644
index 0000000000..75808c18b3
--- /dev/null
+++ b/src/Core/Enums/WizardBorder.cs
@@ -0,0 +1,32 @@
+// ------------------------------------------------------------------------
+// This file is licensed to you under the MIT License.
+// ------------------------------------------------------------------------
+
+namespace Microsoft.FluentUI.AspNetCore.Components;
+
+///
+/// Defines the border display options for the component.
+///
+[Flags]
+public enum WizardBorder
+{
+ ///
+ /// No border.
+ ///
+ None = 0,
+
+ ///
+ /// Border inside (between sections).
+ ///
+ Inside = 1,
+
+ ///
+ /// Border outside (around the wizard).
+ ///
+ Outside = 2,
+
+ ///
+ /// Both inside and outside borders.
+ ///
+ All = Inside | Outside,
+}
diff --git a/src/Core/Enums/WizardStepSequence.cs b/src/Core/Enums/WizardStepSequence.cs
new file mode 100644
index 0000000000..29de9c30f5
--- /dev/null
+++ b/src/Core/Enums/WizardStepSequence.cs
@@ -0,0 +1,27 @@
+// ------------------------------------------------------------------------
+// This file is licensed to you under the MIT License.
+// ------------------------------------------------------------------------
+
+namespace Microsoft.FluentUI.AspNetCore.Components;
+
+///
+/// Defines the navigation mode for wizard steps.
+///
+public enum WizardStepSequence
+{
+ ///
+ /// The user can go to the next/previous step only, using the Next/Previous button.
+ ///
+ Linear,
+
+ ///
+ /// The user can go to any steps (not disabled) clicking on an item.
+ ///
+ Any,
+
+ ///
+ /// The user can go to the next step using the Next button,
+ /// or go to any previous step, already visited.
+ ///
+ Visited,
+}
diff --git a/src/Core/Enums/WizardStepStatus.cs b/src/Core/Enums/WizardStepStatus.cs
new file mode 100644
index 0000000000..ace05ba92a
--- /dev/null
+++ b/src/Core/Enums/WizardStepStatus.cs
@@ -0,0 +1,42 @@
+// ------------------------------------------------------------------------
+// This file is licensed to you under the MIT License.
+// ------------------------------------------------------------------------
+
+using System.ComponentModel;
+
+namespace Microsoft.FluentUI.AspNetCore.Components;
+
+///
+/// Defines the status of a wizard step.
+///
+[Flags]
+public enum WizardStepStatus
+{
+ ///
+ /// No status.
+ ///
+ None = 0,
+
+ ///
+ /// The step has been completed.
+ ///
+ [Description("previous")]
+ Previous = 1,
+
+ ///
+ /// The step is the current active step.
+ ///
+ [Description("current")]
+ Current = 2,
+
+ ///
+ /// The step has not been visited yet.
+ ///
+ [Description("next")]
+ Next = 4,
+
+ ///
+ /// All statuses.
+ ///
+ All = Previous | Current | Next
+}
diff --git a/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_Border.verified.razor.html b/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_Border.verified.razor.html
new file mode 100644
index 0000000000..6c32129d2f
--- /dev/null
+++ b/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_Border.verified.razor.html
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+ Step 1
+
+
+
+
+
+
+ Step 2
+
+
+
+
+
Content 1
+
Content 2
+
+
+
+
+ Next
+
+
+
+
\ No newline at end of file
diff --git a/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_CancelStepChange.verified.razor.html b/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_CancelStepChange.verified.razor.html
new file mode 100644
index 0000000000..87fdf077e1
--- /dev/null
+++ b/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_CancelStepChange.verified.razor.html
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+ Step 1
+
+
+
+
+
+
+ Step 2
+
+
+
+
+
Content 1
+
Content 2
+
+
+
+
+ Next
+
+
+
+
\ No newline at end of file
diff --git a/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_Default.verified.razor.html b/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_Default.verified.razor.html
new file mode 100644
index 0000000000..26aeb84f64
--- /dev/null
+++ b/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_Default.verified.razor.html
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+ Step 1
+
+
+
+
+
+
+ Step 2
+
+
+
+
+
+
+ Step 3
+
+
+
+
+
Content 1
+
Content 2
+
Content 3
+
+
+
+
+ Next
+
+
+
+
\ No newline at end of file
diff --git a/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_DeferredLoading.verified.razor.html b/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_DeferredLoading.verified.razor.html
new file mode 100644
index 0000000000..3ae935efbd
--- /dev/null
+++ b/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_DeferredLoading.verified.razor.html
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+ Step 1
+
+
+
+
+
+
+ Step 2
+
+
+
+
+
+
+
+ Next
+
+
+
+
\ No newline at end of file
diff --git a/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_DisabledStep.verified.razor.html b/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_DisabledStep.verified.razor.html
new file mode 100644
index 0000000000..cab85167d1
--- /dev/null
+++ b/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_DisabledStep.verified.razor.html
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+ Step 1
+
+
+
+
+
+
+ Disabled
+
+
+
+
+
+
+ Step 3
+
+
+
+
+
Content 1
+
Disabled content
+
Content 3
+
+
+
+
+ Next
+
+
+
+
\ No newline at end of file
diff --git a/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_NextButton.verified.razor.html b/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_NextButton.verified.razor.html
new file mode 100644
index 0000000000..e3975032b6
--- /dev/null
+++ b/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_NextButton.verified.razor.html
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+ Step 1
+
+
+
+
+
+
+ Step 2
+
+
+
+
+
+
+ Step 3
+
+
+
+
+
Content 1
+
Content 2
+
Content 3
+
+
+
+ Previous
+
+
+ Next
+
+
+
+
\ No newline at end of file
diff --git a/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_OnFinish.verified.razor.html b/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_OnFinish.verified.razor.html
new file mode 100644
index 0000000000..6bceceea20
--- /dev/null
+++ b/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_OnFinish.verified.razor.html
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+ Step 1
+
+
+
+
+
+
+ Step 2
+
+
+
+
+
Content 1
+
Content 2
+
+
+
+ Previous
+
+
+ Done
+
+
+
+
\ No newline at end of file
diff --git a/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_PreviousButton.verified.razor.html b/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_PreviousButton.verified.razor.html
new file mode 100644
index 0000000000..26aeb84f64
--- /dev/null
+++ b/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_PreviousButton.verified.razor.html
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+ Step 1
+
+
+
+
+
+
+ Step 2
+
+
+
+
+
+
+ Step 3
+
+
+
+
+
Content 1
+
Content 2
+
Content 3
+
+
+
+
+ Next
+
+
+
+
\ No newline at end of file
diff --git a/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_StepSequenceAny.verified.razor.html b/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_StepSequenceAny.verified.razor.html
new file mode 100644
index 0000000000..c26f52bdf3
--- /dev/null
+++ b/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_StepSequenceAny.verified.razor.html
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+ Step 1
+
+
+
+
+
+
+ Step 2
+
+
+
+
+
+
+ Step 3
+
+
+
+
+
Content 1
+
Content 2
+
Content 3
+
+
+
+ Previous
+
+
+ Done
+
+
+
+
\ No newline at end of file
diff --git a/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_StepperPositionTop.verified.razor.html b/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_StepperPositionTop.verified.razor.html
new file mode 100644
index 0000000000..b86a2ed5c4
--- /dev/null
+++ b/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_StepperPositionTop.verified.razor.html
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+ Step 1
+
+
+
+
+
+
+ Step 2
+
+
+
+
+
Content 1
+
Content 2
+
+
+
+
+ Next
+
+
+
+
\ No newline at end of file
diff --git a/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_ValueBinding.verified.razor.html b/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_ValueBinding.verified.razor.html
new file mode 100644
index 0000000000..e3975032b6
--- /dev/null
+++ b/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_ValueBinding.verified.razor.html
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+ Step 1
+
+
+
+
+
+
+ Step 2
+
+
+
+
+
+
+ Step 3
+
+
+
+
+
Content 1
+
Content 2
+
Content 3
+
+
+
+ Previous
+
+
+ Next
+
+
+
+
\ No newline at end of file
diff --git a/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_WithSteps.verified.razor.html b/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_WithSteps.verified.razor.html
new file mode 100644
index 0000000000..87fdf077e1
--- /dev/null
+++ b/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_WithSteps.verified.razor.html
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+ Step 1
+
+
+
+
+
+
+ Step 2
+
+
+
+
+
Content 1
+
Content 2
+
+
+
+
+ Next
+
+
+
+
\ No newline at end of file
diff --git a/tests/Core/Components/Wizard/FluentWizardTests.razor b/tests/Core/Components/Wizard/FluentWizardTests.razor
new file mode 100644
index 0000000000..8bda081241
--- /dev/null
+++ b/tests/Core/Components/Wizard/FluentWizardTests.razor
@@ -0,0 +1,258 @@
+@using Microsoft.FluentUI.AspNetCore.Components.Extensions
+@using Microsoft.FluentUI.AspNetCore.Components.Tests.Extensions
+@using Microsoft.FluentUI.AspNetCore.Components.Utilities
+@using Xunit;
+@inherits FluentUITestContext
+
+@code
+{
+ public FluentWizardTests()
+ {
+ JSInterop.Mode = JSRuntimeMode.Loose;
+ Services.AddFluentUIComponents();
+ }
+
+ [Fact]
+ public void FluentWizard_Default()
+ {
+ // Arrange && Act
+ var cut = Render(
+ @
+
+ Content 1
+ Content 2
+ Content 3
+
+ );
+
+ // Assert
+ cut.Verify();
+ }
+
+ [Fact]
+ public void FluentWizard_WithSteps()
+ {
+ // Arrange && Act
+ var cut = Render(
+ @
+
+ Content 1
+ Content 2
+
+ );
+
+ // Assert
+ cut.Verify();
+ }
+
+ [Fact]
+ public void FluentWizard_NextButton()
+ {
+ // Arrange
+ var value = 0;
+ var cut = Render(
+ @
+
+ Content 1
+ Content 2
+ Content 3
+
+ );
+
+ // Act
+ var nextButton = cut.FindAll("fluent-button").First(b => b.TextContent.Trim() == "Next");
+ nextButton.Click();
+ Assert.Equal(1, value);
+
+ // Assert
+ cut.Verify();
+ }
+
+ [Fact]
+ public void FluentWizard_PreviousButton()
+ {
+ // Arrange
+ var value = 0;
+ var cut = Render(
+ @
+
+ Content 1
+ Content 2
+ Content 3
+
+ );
+
+ // Act - click Next then Previous
+ var nextButton = cut.FindAll("fluent-button").First(b => b.TextContent.Trim() == "Next");
+ nextButton.Click();
+ Assert.Equal(1, value);
+
+ var prevButton = cut.FindAll("fluent-button").First(b => b.TextContent.Trim() == "Previous");
+ prevButton.Click();
+ Assert.Equal(0, value);
+
+ // Assert
+ cut.Verify();
+ }
+
+ [Fact]
+ public void FluentWizard_DisabledStep()
+ {
+ // Arrange && Act
+ var cut = Render(
+ @
+
+ Content 1
+ Disabled content
+ Content 3
+
+ );
+
+ // Assert
+ cut.Verify();
+ }
+
+ [Fact]
+ public void FluentWizard_OnFinish()
+ {
+ // Arrange
+ var finishCalled = false;
+ var value = 0;
+ var cut = Render(
+ @
+
+ Content 1
+ Content 2
+
+ );
+
+ // Act - navigate to last step, click Done
+ var nextButton = cut.FindAll("fluent-button").First(b => b.TextContent.Trim() == "Next");
+ nextButton.Click();
+ Assert.Equal(1, value);
+
+ var doneButton = cut.FindAll("fluent-button").First(b => b.TextContent.Trim() == "Done");
+ doneButton.Click();
+ Assert.True(finishCalled);
+
+ // Assert
+ cut.Verify();
+ }
+
+ [Fact]
+ public void FluentWizard_ValueBinding()
+ {
+ // Arrange
+ var value = 0;
+ var cut = Render(
+ @
+
+ Content 1
+ Content 2
+ Content 3
+
+ );
+
+ // Act
+ Assert.Equal(0, value);
+ var nextButton = cut.FindAll("fluent-button").First(b => b.TextContent.Trim() == "Next");
+ nextButton.Click();
+ Assert.Equal(1, value);
+
+ // Assert
+ cut.Verify();
+ }
+
+ [Fact]
+ public void FluentWizard_StepperPositionTop()
+ {
+ // Arrange && Act
+ var cut = Render(
+ @
+
+ Content 1
+ Content 2
+
+ );
+
+ // Assert
+ cut.Verify();
+ }
+
+ [Fact]
+ public void FluentWizard_Border()
+ {
+ // Arrange && Act
+ var cut = Render(
+ @
+
+ Content 1
+ Content 2
+
+ );
+
+ // Assert
+ cut.Verify();
+ }
+
+ [Fact]
+ public void FluentWizard_DeferredLoading()
+ {
+ // Arrange && Act
+ var cut = Render(
+ @
+
+ Content 1
+ Deferred content
+
+ );
+
+ // Assert
+ cut.Verify();
+ }
+
+ [Fact]
+ public void FluentWizard_CancelStepChange()
+ {
+ // Arrange
+ var value = 0;
+ var cut = Render(
+ @
+
+ Content 1
+ Content 2
+
+ );
+
+ // Act - try to click next
+ var nextButton = cut.FindAll("fluent-button").First(b => b.TextContent.Trim() == "Next");
+ nextButton.Click();
+ Assert.Equal(0, value);
+
+ // Assert
+ cut.Verify();
+ }
+
+ [Fact]
+ public void FluentWizard_StepSequenceAny()
+ {
+ // Arrange
+ var value = 0;
+ var cut = Render(
+ @
+
+ Content 1
+ Content 2
+ Content 3
+
+ );
+
+ // Act - click on step 3 directly
+ var steps = cut.FindAll("li");
+ steps[2].Click();
+ Assert.Equal(2, value);
+
+ // Assert
+ cut.Verify();
+ }
+}
From c4b6e9b45e742841ef4b3b23682eb9769ebcb8bb Mon Sep 17 00:00:00 2001
From: agriffard
Date: Fri, 20 Mar 2026 15:46:09 +0100
Subject: [PATCH 02/27] Fix ComponentBaseTests. Check if
ValueChanged.HasDelegate. internal FluentWizard in FluentWizardStep
---
.../Components/Wizard/FluentWizard.razor.cs | 26 ++++++++++++++++---
.../Wizard/FluentWizardStep.razor.cs | 2 +-
.../Components/Base/ComponentBaseTests.cs | 1 +
3 files changed, 24 insertions(+), 5 deletions(-)
diff --git a/src/Core/Components/Wizard/FluentWizard.razor.cs b/src/Core/Components/Wizard/FluentWizard.razor.cs
index 9b0c05c31f..a096290b93 100644
--- a/src/Core/Components/Wizard/FluentWizard.razor.cs
+++ b/src/Core/Components/Wizard/FluentWizard.razor.cs
@@ -187,7 +187,11 @@ protected virtual async Task OnNextHandlerAsync(MouseEventArgs e)
if (!isCanceled)
{
Value = targetIndex;
- await ValueChanged.InvokeAsync(targetIndex);
+ if (ValueChanged.HasDelegate)
+ {
+ await ValueChanged.InvokeAsync(targetIndex);
+ }
+
StateHasChanged();
}
}
@@ -210,7 +214,11 @@ protected virtual async Task OnPreviousHandlerAsync(MouseEventArgs e)
if (!isCanceled)
{
Value = targetIndex;
- await ValueChanged.InvokeAsync(targetIndex);
+ if (ValueChanged.HasDelegate)
+ {
+ await ValueChanged.InvokeAsync(targetIndex);
+ }
+
StateHasChanged();
}
}
@@ -314,7 +322,11 @@ internal async Task ValidateAndGoToStepAsync(int targetIndex, bool validateEditC
if (!isCanceled)
{
Value = targetIndex;
- await ValueChanged.InvokeAsync(targetIndex);
+ if (ValueChanged.HasDelegate)
+ {
+ await ValueChanged.InvokeAsync(targetIndex);
+ }
+
StateHasChanged();
}
}
@@ -329,7 +341,13 @@ internal int AddStep(FluentWizardStep step)
SetCurrentStatusToStep(index);
}
- StateHasChanged();
+ try
+ {
+ StateHasChanged();
+ }
+ catch (InvalidOperationException ex) when (ex.Message.Contains("render handle is not yet assigned", StringComparison.OrdinalIgnoreCase))
+ {
+ }
return index;
}
diff --git a/src/Core/Components/Wizard/FluentWizardStep.razor.cs b/src/Core/Components/Wizard/FluentWizardStep.razor.cs
index 85da886171..09e601f991 100644
--- a/src/Core/Components/Wizard/FluentWizardStep.razor.cs
+++ b/src/Core/Components/Wizard/FluentWizardStep.razor.cs
@@ -93,7 +93,7 @@ public FluentWizardStep(LibraryConfiguration configuration) : base(configuration
/// For internal use only.
///
[CascadingParameter]
- public FluentWizard FluentWizard { get; set; } = default!;
+ internal FluentWizard FluentWizard { get; set; } = default!;
///
/// Gets or sets the summary of the step, to display near the label.
diff --git a/tests/Core/Components/Base/ComponentBaseTests.cs b/tests/Core/Components/Base/ComponentBaseTests.cs
index 7a8e760f28..237ed44bb1 100644
--- a/tests/Core/Components/Base/ComponentBaseTests.cs
+++ b/tests/Core/Components/Base/ComponentBaseTests.cs
@@ -70,6 +70,7 @@ public class ComponentBaseTests : Bunit.BunitContext
{ typeof(FluentNavSectionHeader), Loader.Default.WithCascadingValue(new FluentNav(new LibraryConfiguration())) },
{ typeof(FluentAppBarItem), Loader.Default.WithCascadingValue(new InternalAppBarContext(new FluentAppBar(new LibraryConfiguration()))) },
{ typeof(FluentSortableList<>), Loader.MakeGenericType(typeof(string)).WithRequiredParameter("ItemTemplate", (RenderFragment)(p => builder => builder.AddContent(0, "MyItemTemplate")))},
+ { typeof(FluentWizardStep), Loader.Default.WithCascadingValue(new FluentWizard(new LibraryConfiguration())) },
};
///
From cbd2561b5be418e0338083a85fb079b411be4d34 Mon Sep 17 00:00:00 2001
From: agriffard
Date: Fri, 20 Mar 2026 15:48:55 +0100
Subject: [PATCH 03/27] WizardStepStatus : Add [Description("all")] to All
---
src/Core/Enums/WizardStepStatus.cs | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/Core/Enums/WizardStepStatus.cs b/src/Core/Enums/WizardStepStatus.cs
index ace05ba92a..ce5c336fec 100644
--- a/src/Core/Enums/WizardStepStatus.cs
+++ b/src/Core/Enums/WizardStepStatus.cs
@@ -38,5 +38,6 @@ public enum WizardStepStatus
///
/// All statuses.
///
+ [Description("all")]
All = Previous | Current | Next
}
From ccab369a7bb53e568554553629e0cf9c1d1e399a Mon Sep 17 00:00:00 2001
From: agriffard
Date: Fri, 20 Mar 2026 16:01:20 +0100
Subject: [PATCH 04/27] Retrieve @media styles in FluentWizardStep.razor.css
from v4
---
.../Wizard/FluentWizardStep.razor.css | 38 +++++++++----------
1 file changed, 19 insertions(+), 19 deletions(-)
diff --git a/src/Core/Components/Wizard/FluentWizardStep.razor.css b/src/Core/Components/Wizard/FluentWizardStep.razor.css
index 4df4551bbc..a8c1f867d6 100644
--- a/src/Core/Components/Wizard/FluentWizardStep.razor.css
+++ b/src/Core/Components/Wizard/FluentWizardStep.razor.css
@@ -65,39 +65,39 @@
right: calc(50% + calc(var(--fluent-wizard-circle-size) / 2 + var(--fluent-wizard-spacing)));
}
-/* Hidden (responsive) */
+/* Hidden */
@media (max-width: 599.98px) {
- div[hidden-when~="xs"] {
- display: none;
- }
+ .fluent-wizard > ol > li ::deep > div[hidden-when~="xs"] {
+ display: none;
+ }
}
@media (min-width: 600px) and (max-width: 959.98px) {
- div[hidden-when~="sm"] {
- display: none;
- }
+ .fluent-wizard > ol > li ::deep > div[hidden-when~="sm"] {
+ display: none;
+ }
}
@media (min-width: 960px) and (max-width: 1279.98px) {
- div[hidden-when~="md"] {
- display: none;
- }
+ .fluent-wizard > ol > li ::deep > div[hidden-when~="md"] {
+ display: none;
+ }
}
@media (min-width: 1280px) and (max-width: 1919.98px) {
- div[hidden-when~="lg"] {
- display: none;
- }
+ .fluent-wizard > ol > li ::deep > div[hidden-when~="lg"] {
+ display: none;
+ }
}
@media (min-width: 1920px) and (max-width: 2559.98px) {
- div[hidden-when~="xl"] {
- display: none;
- }
+ .fluent-wizard > ol > li ::deep > div[hidden-when~="xl"] {
+ display: none;
+ }
}
@media (min-width: 2560px) {
- div[hidden-when~="xxl"] {
- display: none;
- }
+ .fluent-wizard > ol > li ::deep > div[hidden-when~="xxl"] {
+ display: none;
+ }
}
From 34c85ca034423457fb9d60946eba895b96550fee Mon Sep 17 00:00:00 2001
From: agriffard
Date: Fri, 20 Mar 2026 16:11:11 +0100
Subject: [PATCH 05/27] Use Localizer for default label properties values
---
src/Core/Components/Wizard/FluentWizard.razor | 8 +++----
.../Components/Wizard/FluentWizard.razor.cs | 21 ++++++++++++++++---
src/Core/Localization/LanguageResource.resx | 11 +++++++++-
3 files changed, 32 insertions(+), 8 deletions(-)
diff --git a/src/Core/Components/Wizard/FluentWizard.razor b/src/Core/Components/Wizard/FluentWizard.razor
index 4aeab0a69e..82a0ff44d0 100644
--- a/src/Core/Components/Wizard/FluentWizard.razor
+++ b/src/Core/Components/Wizard/FluentWizard.razor
@@ -1,4 +1,4 @@
-@namespace Microsoft.FluentUI.AspNetCore.Components
+@namespace Microsoft.FluentUI.AspNetCore.Components
@inherits FluentComponentBase
@@ -37,7 +37,7 @@
- @LabelButtonPrevious
+ @LabelButtonPreviousValue
}
@@ -48,7 +48,7 @@
- @LabelButtonNext
+ @LabelButtonNextValue
}
else
@@ -56,7 +56,7 @@
- @LabelButtonDone
+ @LabelButtonDoneValue
}
}
diff --git a/src/Core/Components/Wizard/FluentWizard.razor.cs b/src/Core/Components/Wizard/FluentWizard.razor.cs
index a096290b93..160fe4808b 100644
--- a/src/Core/Components/Wizard/FluentWizard.razor.cs
+++ b/src/Core/Components/Wizard/FluentWizard.razor.cs
@@ -15,18 +15,33 @@ public partial class FluentWizard : FluentComponentBase
{
///
/// Gets or sets the label for the Previous button.
+ /// If null or empty, the localized value is used.
///
- public static string LabelButtonPrevious { get; set; } = "Previous";
+ public static string? LabelButtonPrevious { get; set; }
///
/// Gets or sets the label for the Next button.
+ /// If null or empty, the localized value is used.
///
- public static string LabelButtonNext { get; set; } = "Next";
+ public static string? LabelButtonNext { get; set; }
///
/// Gets or sets the label for the Done button.
+ /// If null or empty, the localized value is used.
///
- public static string LabelButtonDone { get; set; } = "Done";
+ public static string? LabelButtonDone { get; set; }
+
+ private string LabelButtonPreviousValue => string.IsNullOrWhiteSpace(LabelButtonPrevious)
+ ? Localizer[Localization.LanguageResource.Wizard_LabelButtonPrevious]
+ : LabelButtonPrevious;
+
+ private string LabelButtonNextValue => string.IsNullOrWhiteSpace(LabelButtonNext)
+ ? Localizer[Localization.LanguageResource.Wizard_LabelButtonNext]
+ : LabelButtonNext;
+
+ private string LabelButtonDoneValue => string.IsNullOrWhiteSpace(LabelButtonDone)
+ ? Localizer[Localization.LanguageResource.Wizard_LabelButtonDone]
+ : LabelButtonDone;
private readonly List _steps = new();
private int _value;
diff --git a/src/Core/Localization/LanguageResource.resx b/src/Core/Localization/LanguageResource.resx
index 7eb4af6f18..95fab87e96 100644
--- a/src/Core/Localization/LanguageResource.resx
+++ b/src/Core/Localization/LanguageResource.resx
@@ -342,4 +342,13 @@
Toggle nesting
-
\ No newline at end of file
+
+ Previous
+
+
+ Next
+
+
+ Done
+
+
From 1db349c21fc6f50c8fb7ef2066376226c850b8f6 Mon Sep 17 00:00:00 2001
From: agriffard
Date: Fri, 20 Mar 2026 16:22:50 +0100
Subject: [PATCH 06/27] Change Value to public int Value { get; set; }
---
.../Components/Wizard/FluentWizard.razor.cs | 73 ++++++++++---------
1 file changed, 37 insertions(+), 36 deletions(-)
diff --git a/src/Core/Components/Wizard/FluentWizard.razor.cs b/src/Core/Components/Wizard/FluentWizard.razor.cs
index 160fe4808b..91e2a65271 100644
--- a/src/Core/Components/Wizard/FluentWizard.razor.cs
+++ b/src/Core/Components/Wizard/FluentWizard.razor.cs
@@ -44,7 +44,6 @@ public partial class FluentWizard : FluentComponentBase
: LabelButtonDone;
private readonly List _steps = new();
- private int _value;
internal int _maxStepVisited;
///
@@ -117,36 +116,8 @@ public FluentWizard(LibraryConfiguration configuration) : base(configuration)
/// Gets or sets the step index of the current step.
/// This value is bindable.
///
-#pragma warning disable BL0007 // Component parameters should be auto properties
[Parameter]
- public int Value
-#pragma warning restore BL0007
- {
- get
- {
- return _value;
- }
-
- set
- {
- if (value < 0 || _steps.Count <= 0)
- {
- _value = 0;
- }
- else if (value > _steps.Count - 1)
- {
- _value = _steps.Count - 1;
- }
- else
- {
- _value = value;
- }
-
- _maxStepVisited = Math.Max(_value, _maxStepVisited);
-
- SetCurrentStatusToStep(_value);
- }
- }
+ public int Value { get; set; }
///
/// Triggers when the value has changed.
@@ -184,6 +155,13 @@ public int Value
[Parameter]
public WizardStepSequence StepSequence { get; set; } = WizardStepSequence.Linear;
+ ///
+ protected override void OnParametersSet()
+ {
+ SetCurrentValue(Value);
+ base.OnParametersSet();
+ }
+
///
protected virtual async Task OnNextHandlerAsync(MouseEventArgs e)
{
@@ -201,10 +179,10 @@ protected virtual async Task OnNextHandlerAsync(MouseEventArgs e)
if (!isCanceled)
{
- Value = targetIndex;
+ SetCurrentValue(targetIndex);
if (ValueChanged.HasDelegate)
{
- await ValueChanged.InvokeAsync(targetIndex);
+ await ValueChanged.InvokeAsync(Value);
}
StateHasChanged();
@@ -228,10 +206,10 @@ protected virtual async Task OnPreviousHandlerAsync(MouseEventArgs e)
if (!isCanceled)
{
- Value = targetIndex;
+ SetCurrentValue(targetIndex);
if (ValueChanged.HasDelegate)
{
- await ValueChanged.InvokeAsync(targetIndex);
+ await ValueChanged.InvokeAsync(Value);
}
StateHasChanged();
@@ -336,10 +314,10 @@ internal async Task ValidateAndGoToStepAsync(int targetIndex, bool validateEditC
if (!isCanceled)
{
- Value = targetIndex;
+ SetCurrentValue(targetIndex);
if (ValueChanged.HasDelegate)
{
- await ValueChanged.InvokeAsync(targetIndex);
+ await ValueChanged.InvokeAsync(Value);
}
StateHasChanged();
@@ -374,6 +352,29 @@ internal void RemoveStep(FluentWizardStep step)
_steps.Remove(step);
}
+ private void SetCurrentValue(int value)
+ {
+ Value = NormalizeValue(value);
+ _maxStepVisited = Math.Max(Value, _maxStepVisited);
+
+ SetCurrentStatusToStep(Value);
+ }
+
+ private int NormalizeValue(int value)
+ {
+ if (value < 0 || _steps.Count <= 0)
+ {
+ return 0;
+ }
+
+ if (value > _steps.Count - 1)
+ {
+ return _steps.Count - 1;
+ }
+
+ return value;
+ }
+
private void SetCurrentStatusToStep(int stepIndex)
{
for (var i = 0; i < _steps.Count; i++)
From 3e52b8550df15d7426d3835fa70b302b8a7dcefe Mon Sep 17 00:00:00 2001
From: agriffard
Date: Fri, 20 Mar 2026 16:26:11 +0100
Subject: [PATCH 07/27] Add [Description("none")] to None in WizardStepStatus
---
src/Core/Enums/WizardStepStatus.cs | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/Core/Enums/WizardStepStatus.cs b/src/Core/Enums/WizardStepStatus.cs
index ce5c336fec..2485f0af4b 100644
--- a/src/Core/Enums/WizardStepStatus.cs
+++ b/src/Core/Enums/WizardStepStatus.cs
@@ -15,6 +15,7 @@ public enum WizardStepStatus
///
/// No status.
///
+ [Description("none")]
None = 0,
///
From 7da6b3ec2f37d6899d61d8b4394cd480ed3d8101 Mon Sep 17 00:00:00 2001
From: Marvin Klein <32510006+MarvinKlein1508@users.noreply.github.com>
Date: Thu, 2 Apr 2026 16:43:48 +0200
Subject: [PATCH 08/27] Code review fixes
---
.../Components/Wizard/FluentWizard.razor.cs | 4 +--
.../Wizard/FluentWizardStep.razor.cs | 32 +++++++------------
src/Core/Enums/WizardStepStatus.cs | 2 +-
3 files changed, 14 insertions(+), 24 deletions(-)
diff --git a/src/Core/Components/Wizard/FluentWizard.razor.cs b/src/Core/Components/Wizard/FluentWizard.razor.cs
index 91e2a65271..66a4042d9e 100644
--- a/src/Core/Components/Wizard/FluentWizard.razor.cs
+++ b/src/Core/Components/Wizard/FluentWizard.razor.cs
@@ -260,9 +260,9 @@ protected virtual async Task OnStepChangeHandle
}
///
- protected virtual async Task OnFinishHandlerAsync(MouseEventArgs e)
+ protected virtual Task OnFinishHandlerAsync(MouseEventArgs e)
{
- await FinishAsync(true);
+ return FinishAsync(validateEditContexts: true);
}
///
diff --git a/src/Core/Components/Wizard/FluentWizardStep.razor.cs b/src/Core/Components/Wizard/FluentWizardStep.razor.cs
index 09e601f991..6a0801b637 100644
--- a/src/Core/Components/Wizard/FluentWizardStep.razor.cs
+++ b/src/Core/Components/Wizard/FluentWizardStep.razor.cs
@@ -13,8 +13,8 @@ namespace Microsoft.FluentUI.AspNetCore.Components;
///
public partial class FluentWizardStep : FluentComponentBase
{
- private readonly Dictionary _editForms = new Dictionary();
- private readonly List _editContexts = new List();
+ private readonly Dictionary _editForms = [];
+ private readonly List _editContexts = [];
///
public FluentWizardStep(LibraryConfiguration configuration) : base(configuration)
@@ -60,13 +60,13 @@ public FluentWizardStep(LibraryConfiguration configuration) : base(configuration
/// Gets or sets whether the step is disabled.
///
[Parameter]
- public bool Disabled { get; set; } = false;
+ public bool Disabled { get; set; }
///
/// Render the Wizard Step content only when the Step is selected.
///
[Parameter]
- public bool DeferredLoading { get; set; } = false;
+ public bool DeferredLoading { get; set; }
///
/// Gets or sets the label of the step.
@@ -133,20 +133,13 @@ private Icon StepIcon
{
get
{
- switch (Status)
+ return Status switch
{
- case WizardStepStatus.Previous:
- return IconPrevious;
-
- case WizardStepStatus.Current:
- return IconCurrent;
-
- case WizardStepStatus.Next:
- return IconNext;
-
- default:
- return new CoreIcons.Regular.Size20.Circle();
- }
+ WizardStepStatus.Previous => IconPrevious,
+ WizardStepStatus.Current => IconCurrent,
+ WizardStepStatus.Next => IconNext,
+ _ => new CoreIcons.Regular.Size20.Circle(),
+ };
}
}
@@ -174,10 +167,7 @@ public override async ValueTask DisposeAsync()
///
public void RegisterEditFormAndContext(EditForm editForm, EditContext editContext)
{
- if (!_editForms.ContainsKey(editForm))
- {
- _editForms.Add(editForm, editContext);
- }
+ _editForms.TryAdd(editForm, editContext);
}
///
diff --git a/src/Core/Enums/WizardStepStatus.cs b/src/Core/Enums/WizardStepStatus.cs
index 2485f0af4b..97f25858b4 100644
--- a/src/Core/Enums/WizardStepStatus.cs
+++ b/src/Core/Enums/WizardStepStatus.cs
@@ -40,5 +40,5 @@ public enum WizardStepStatus
/// All statuses.
///
[Description("all")]
- All = Previous | Current | Next
+ All = Previous | Current | Next,
}
From 7ab0ccfc36a2cc522c1afd8f1a26b763291cc0fb Mon Sep 17 00:00:00 2001
From: Marvin Klein
Date: Fri, 3 Apr 2026 21:52:54 +0200
Subject: [PATCH 09/27] Remove hidden styles
---
.../Wizard/FluentWizardStep.razor.css | 37 -------------------
1 file changed, 37 deletions(-)
diff --git a/src/Core/Components/Wizard/FluentWizardStep.razor.css b/src/Core/Components/Wizard/FluentWizardStep.razor.css
index a8c1f867d6..4cb0865901 100644
--- a/src/Core/Components/Wizard/FluentWizardStep.razor.css
+++ b/src/Core/Components/Wizard/FluentWizardStep.razor.css
@@ -64,40 +64,3 @@
left: unset;
right: calc(50% + calc(var(--fluent-wizard-circle-size) / 2 + var(--fluent-wizard-spacing)));
}
-
-/* Hidden */
-@media (max-width: 599.98px) {
- .fluent-wizard > ol > li ::deep > div[hidden-when~="xs"] {
- display: none;
- }
-}
-
-@media (min-width: 600px) and (max-width: 959.98px) {
- .fluent-wizard > ol > li ::deep > div[hidden-when~="sm"] {
- display: none;
- }
-}
-
-@media (min-width: 960px) and (max-width: 1279.98px) {
- .fluent-wizard > ol > li ::deep > div[hidden-when~="md"] {
- display: none;
- }
-}
-
-@media (min-width: 1280px) and (max-width: 1919.98px) {
- .fluent-wizard > ol > li ::deep > div[hidden-when~="lg"] {
- display: none;
- }
-}
-
-@media (min-width: 1920px) and (max-width: 2559.98px) {
- .fluent-wizard > ol > li ::deep > div[hidden-when~="xl"] {
- display: none;
- }
-}
-
-@media (min-width: 2560px) {
- .fluent-wizard > ol > li ::deep > div[hidden-when~="xxl"] {
- display: none;
- }
-}
From 3b644b47ef68f290888fe2e1eca45fd5ab0b0546 Mon Sep 17 00:00:00 2001
From: Marvin Klein
Date: Fri, 3 Apr 2026 22:10:15 +0200
Subject: [PATCH 10/27] Add console log
---
.../Components/Wizard/Examples/WizardDefault.razor | 13 +++++++------
1 file changed, 7 insertions(+), 6 deletions(-)
diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/Wizard/Examples/WizardDefault.razor b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/Wizard/Examples/WizardDefault.razor
index 57dad5302e..85b8d790cb 100644
--- a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/Wizard/Examples/WizardDefault.razor
+++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/Wizard/Examples/WizardDefault.razor
@@ -1,4 +1,4 @@
-
+
@@ -59,12 +59,13 @@
@code
{
- bool IsTop = false;
- WizardStepSequence StepSequence = WizardStepSequence.Linear;
+ bool IsTop = false;
+ WizardStepSequence StepSequence = WizardStepSequence.Linear;
- void OnStepChange(FluentWizardStepChangeEventArgs e)
- {
- }
+ void OnStepChange(FluentWizardStepChangeEventArgs e)
+ {
+ Console.WriteLine("Step changed to {0}", e.TargetIndex);
+ }
async Task OnFinishedAsync()
{
From 708828b94df26e3552a83dea9cac850312051cbf Mon Sep 17 00:00:00 2001
From: Marvin Klein
Date: Fri, 3 Apr 2026 22:11:44 +0200
Subject: [PATCH 11/27] Use switch expression
---
src/Core/Components/Wizard/FluentWizard.razor.cs | 14 +++++---------
1 file changed, 5 insertions(+), 9 deletions(-)
diff --git a/src/Core/Components/Wizard/FluentWizard.razor.cs b/src/Core/Components/Wizard/FluentWizard.razor.cs
index 66a4042d9e..3a145afb4f 100644
--- a/src/Core/Components/Wizard/FluentWizard.razor.cs
+++ b/src/Core/Components/Wizard/FluentWizard.razor.cs
@@ -411,16 +411,12 @@ private void SetCurrentStatusToStep(int stepIndex)
return null;
}
- switch (StepperPosition)
+ return StepperPosition switch
{
- case StepperPosition.Top:
- return $"height: {StepperSize}";
-
- case StepperPosition.Left:
- return $"width: {StepperSize}";
- }
-
- return null;
+ StepperPosition.Top => $"height: {StepperSize}",
+ StepperPosition.Left => $"width: {StepperSize}",
+ _ => null,
+ };
}
private bool DisplayPreviousButton => Value > 0 && _steps[..Value].Any(i => !i.Disabled);
From 7a3bc4b767662c938229a6c855806ec3be5a4495 Mon Sep 17 00:00:00 2001
From: Marvin Klein
Date: Fri, 3 Apr 2026 22:16:27 +0200
Subject: [PATCH 12/27] Update default sample
---
.../Wizard/Examples/WizardDefault.razor | 30 ++++---------------
1 file changed, 6 insertions(+), 24 deletions(-)
diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/Wizard/Examples/WizardDefault.razor b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/Wizard/Examples/WizardDefault.razor
index 85b8d790cb..0c2ceceb99 100644
--- a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/Wizard/Examples/WizardDefault.razor
+++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/Wizard/Examples/WizardDefault.razor
@@ -1,21 +1,6 @@
-
-
-
- WizardStepSequence:
-
-
-
-
+ OnFinish="@OnFinished">
@@ -59,16 +44,13 @@
@code
{
- bool IsTop = false;
- WizardStepSequence StepSequence = WizardStepSequence.Linear;
-
void OnStepChange(FluentWizardStepChangeEventArgs e)
{
Console.WriteLine("Step changed to {0}", e.TargetIndex);
}
- async Task OnFinishedAsync()
- {
- await Task.CompletedTask;
- }
+ void OnFinished()
+ {
+ Console.WriteLine("Wizard has been finished");
+ }
}
From e71067ce27bbd72330dbdc444b2609aea4aa8632 Mon Sep 17 00:00:00 2001
From: Marvin Klein
Date: Fri, 3 Apr 2026 22:23:01 +0200
Subject: [PATCH 13/27] Add test for FluentWizardStepChangeEventArgs
---
.../Components/Wizard/FluentWizardTests.razor | 527 +++++++++---------
1 file changed, 277 insertions(+), 250 deletions(-)
diff --git a/tests/Core/Components/Wizard/FluentWizardTests.razor b/tests/Core/Components/Wizard/FluentWizardTests.razor
index 8bda081241..c289fef7e6 100644
--- a/tests/Core/Components/Wizard/FluentWizardTests.razor
+++ b/tests/Core/Components/Wizard/FluentWizardTests.razor
@@ -1,4 +1,4 @@
-@using Microsoft.FluentUI.AspNetCore.Components.Extensions
+@using Microsoft.FluentUI.AspNetCore.Components.Extensions
@using Microsoft.FluentUI.AspNetCore.Components.Tests.Extensions
@using Microsoft.FluentUI.AspNetCore.Components.Utilities
@using Xunit;
@@ -6,253 +6,280 @@
@code
{
- public FluentWizardTests()
- {
- JSInterop.Mode = JSRuntimeMode.Loose;
- Services.AddFluentUIComponents();
- }
-
- [Fact]
- public void FluentWizard_Default()
- {
- // Arrange && Act
- var cut = Render(
- @
-
- Content 1
- Content 2
- Content 3
-
- );
-
- // Assert
- cut.Verify();
- }
-
- [Fact]
- public void FluentWizard_WithSteps()
- {
- // Arrange && Act
- var cut = Render(
- @
-
- Content 1
- Content 2
-
- );
-
- // Assert
- cut.Verify();
- }
-
- [Fact]
- public void FluentWizard_NextButton()
- {
- // Arrange
- var value = 0;
- var cut = Render(
- @
-
- Content 1
- Content 2
- Content 3
-
- );
-
- // Act
- var nextButton = cut.FindAll("fluent-button").First(b => b.TextContent.Trim() == "Next");
- nextButton.Click();
- Assert.Equal(1, value);
-
- // Assert
- cut.Verify();
- }
-
- [Fact]
- public void FluentWizard_PreviousButton()
- {
- // Arrange
- var value = 0;
- var cut = Render(
- @
-
- Content 1
- Content 2
- Content 3
-
- );
-
- // Act - click Next then Previous
- var nextButton = cut.FindAll("fluent-button").First(b => b.TextContent.Trim() == "Next");
- nextButton.Click();
- Assert.Equal(1, value);
-
- var prevButton = cut.FindAll("fluent-button").First(b => b.TextContent.Trim() == "Previous");
- prevButton.Click();
- Assert.Equal(0, value);
-
- // Assert
- cut.Verify();
- }
-
- [Fact]
- public void FluentWizard_DisabledStep()
- {
- // Arrange && Act
- var cut = Render(
- @
-
- Content 1
- Disabled content
- Content 3
-
- );
-
- // Assert
- cut.Verify();
- }
-
- [Fact]
- public void FluentWizard_OnFinish()
- {
- // Arrange
- var finishCalled = false;
- var value = 0;
- var cut = Render(
- @
-
- Content 1
- Content 2
-
- );
-
- // Act - navigate to last step, click Done
- var nextButton = cut.FindAll("fluent-button").First(b => b.TextContent.Trim() == "Next");
- nextButton.Click();
- Assert.Equal(1, value);
-
- var doneButton = cut.FindAll("fluent-button").First(b => b.TextContent.Trim() == "Done");
- doneButton.Click();
- Assert.True(finishCalled);
-
- // Assert
- cut.Verify();
- }
-
- [Fact]
- public void FluentWizard_ValueBinding()
- {
- // Arrange
- var value = 0;
- var cut = Render(
- @
-
- Content 1
- Content 2
- Content 3
-
- );
-
- // Act
- Assert.Equal(0, value);
- var nextButton = cut.FindAll("fluent-button").First(b => b.TextContent.Trim() == "Next");
- nextButton.Click();
- Assert.Equal(1, value);
-
- // Assert
- cut.Verify();
- }
-
- [Fact]
- public void FluentWizard_StepperPositionTop()
- {
- // Arrange && Act
- var cut = Render(
- @
-
- Content 1
- Content 2
-
- );
-
- // Assert
- cut.Verify();
- }
-
- [Fact]
- public void FluentWizard_Border()
- {
- // Arrange && Act
- var cut = Render(
- @
-
- Content 1
- Content 2
-
- );
-
- // Assert
- cut.Verify();
- }
-
- [Fact]
- public void FluentWizard_DeferredLoading()
- {
- // Arrange && Act
- var cut = Render(
- @
-
- Content 1
- Deferred content
-
- );
-
- // Assert
- cut.Verify();
- }
-
- [Fact]
- public void FluentWizard_CancelStepChange()
- {
- // Arrange
- var value = 0;
- var cut = Render(
- @
-
- Content 1
- Content 2
-
- );
-
- // Act - try to click next
- var nextButton = cut.FindAll("fluent-button").First(b => b.TextContent.Trim() == "Next");
- nextButton.Click();
- Assert.Equal(0, value);
-
- // Assert
- cut.Verify();
- }
-
- [Fact]
- public void FluentWizard_StepSequenceAny()
- {
- // Arrange
- var value = 0;
- var cut = Render(
- @
-
- Content 1
- Content 2
- Content 3
-
- );
-
- // Act - click on step 3 directly
- var steps = cut.FindAll("li");
- steps[2].Click();
- Assert.Equal(2, value);
-
- // Assert
- cut.Verify();
- }
+ public FluentWizardTests()
+ {
+ JSInterop.Mode = JSRuntimeMode.Loose;
+ Services.AddFluentUIComponents();
+ }
+
+ [Fact]
+ public void FluentWizard_Default()
+ {
+ // Arrange && Act
+ var cut = Render(
+ @
+
+ Content 1
+ Content 2
+ Content 3
+
+ );
+
+ // Assert
+ cut.Verify();
+ }
+
+ [Fact]
+ public void FluentWizard_WithSteps()
+ {
+ // Arrange && Act
+ var cut = Render(
+ @
+
+ Content 1
+ Content 2
+
+ );
+
+ // Assert
+ cut.Verify();
+ }
+
+ [Fact]
+ public void FluentWizard_NextButton()
+ {
+ // Arrange
+ var value = 0;
+ var cut = Render(
+ @
+
+ Content 1
+ Content 2
+ Content 3
+
+ );
+
+ // Act
+ var nextButton = cut.FindAll("fluent-button").First(b => b.TextContent.Trim() == "Next");
+ nextButton.Click();
+ Assert.Equal(1, value);
+
+ // Assert
+ cut.Verify();
+ }
+
+ [Fact]
+ public void FluentWizard_PreviousButton()
+ {
+ // Arrange
+ var value = 0;
+ var cut = Render(
+ @
+
+ Content 1
+ Content 2
+ Content 3
+
+ );
+
+ // Act - click Next then Previous
+ var nextButton = cut.FindAll("fluent-button").First(b => b.TextContent.Trim() == "Next");
+ nextButton.Click();
+ Assert.Equal(1, value);
+
+ var prevButton = cut.FindAll("fluent-button").First(b => b.TextContent.Trim() == "Previous");
+ prevButton.Click();
+ Assert.Equal(0, value);
+
+ // Assert
+ cut.Verify();
+ }
+
+ [Fact]
+ public void FluentWizard_DisabledStep()
+ {
+ // Arrange && Act
+ var cut = Render(
+ @
+
+ Content 1
+ Disabled content
+ Content 3
+
+ );
+
+ // Assert
+ cut.Verify();
+ }
+
+ [Fact]
+ public void FluentWizard_OnFinish()
+ {
+ // Arrange
+ var finishCalled = false;
+ var value = 0;
+ var cut = Render(
+ @
+
+ Content 1
+ Content 2
+
+ );
+
+ // Act - navigate to last step, click Done
+ var nextButton = cut.FindAll("fluent-button").First(b => b.TextContent.Trim() == "Next");
+ nextButton.Click();
+ Assert.Equal(1, value);
+
+ var doneButton = cut.FindAll("fluent-button").First(b => b.TextContent.Trim() == "Done");
+ doneButton.Click();
+ Assert.True(finishCalled);
+
+ // Assert
+ cut.Verify();
+ }
+
+ [Fact]
+ public void FluentWizard_ValueBinding()
+ {
+ // Arrange
+ var value = 0;
+ var cut = Render(
+ @
+
+ Content 1
+ Content 2
+ Content 3
+
+ );
+
+ // Act
+ Assert.Equal(0, value);
+ var nextButton = cut.FindAll("fluent-button").First(b => b.TextContent.Trim() == "Next");
+ nextButton.Click();
+ Assert.Equal(1, value);
+
+ // Assert
+ cut.Verify();
+ }
+
+ [Fact]
+ public void FluentWizard_StepperPositionTop()
+ {
+ // Arrange && Act
+ var cut = Render(
+ @
+
+ Content 1
+ Content 2
+
+ );
+
+ // Assert
+ cut.Verify();
+ }
+
+ [Fact]
+ public void FluentWizard_Border()
+ {
+ // Arrange && Act
+ var cut = Render(
+ @
+
+ Content 1
+ Content 2
+
+ );
+
+ // Assert
+ cut.Verify();
+ }
+
+ [Fact]
+ public void FluentWizard_DeferredLoading()
+ {
+ // Arrange && Act
+ var cut = Render(
+ @
+
+ Content 1
+ Deferred content
+
+ );
+
+ // Assert
+ cut.Verify();
+ }
+
+ [Fact]
+ public void FluentWizard_CancelStepChange()
+ {
+ // Arrange
+ var value = 0;
+ var cut = Render(
+ @
+
+ Content 1
+ Content 2
+
+ );
+
+ // Act - try to click next
+ var nextButton = cut.FindAll("fluent-button").First(b => b.TextContent.Trim() == "Next");
+ nextButton.Click();
+ Assert.Equal(0, value);
+
+ // Assert
+ cut.Verify();
+ }
+
+ [Fact]
+ public void FluentWizard_StepSequenceAny()
+ {
+ // Arrange
+ var value = 0;
+ var cut = Render(
+ @
+
+ Content 1
+ Content 2
+ Content 3
+
+ );
+
+ // Act - click on step 3 directly
+ var steps = cut.FindAll("li");
+ steps[2].Click();
+ Assert.Equal(2, value);
+
+ // Assert
+ cut.Verify();
+ }
+
+ [Fact]
+ public void FluentWizard_ChangeEventArgs()
+ {
+ // Arrange
+ FluentWizardStepChangeEventArgs? eventArgs = null;
+
+ var cut = Render(
+ @
+
+ Content 1
+ Content 2
+ Content 3
+
+
+ );
+
+ // Act
+ var nextButton = cut.FindAll("fluent-button").First(b => b.TextContent.Trim() == "Next");
+ nextButton.Click();
+
+ // Assert
+ Assert.NotNull(eventArgs);
+ Assert.Equal(1, eventArgs.TargetIndex);
+ Assert.Equal("Step 2", eventArgs.TargetLabel);
+ Assert.False(eventArgs.IsCancelled);
+ }
}
From bd177af4c269eb2972ee2c611facd4ff66cbaaac Mon Sep 17 00:00:00 2001
From: Marvin Klein
Date: Fri, 3 Apr 2026 22:25:43 +0200
Subject: [PATCH 14/27] On new line
---
src/Core/Components/Wizard/FluentWizardStep.razor | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/src/Core/Components/Wizard/FluentWizardStep.razor b/src/Core/Components/Wizard/FluentWizardStep.razor
index 4177ef0bb3..a391f585a0 100644
--- a/src/Core/Components/Wizard/FluentWizardStep.razor
+++ b/src/Core/Components/Wizard/FluentWizardStep.razor
@@ -1,11 +1,14 @@
-@namespace Microsoft.FluentUI.AspNetCore.Components
+@namespace Microsoft.FluentUI.AspNetCore.Components
@using Microsoft.FluentUI.AspNetCore.Components.Extensions
@inherits FluentComponentBase
-
+ class="@ClassValue"
+ style="@StyleValue"
+ @attributes="@AdditionalAttributes">
@if (StepTemplate is null)
{
From 91e9d18e3d1b61df9c339c5311132a61c57afa7f Mon Sep 17 00:00:00 2001
From: Marvin Klein
Date: Mon, 6 Apr 2026 12:25:58 +0200
Subject: [PATCH 15/27] Add position demo
---
.../Wizard/Examples/WizardPosition.razor | 57 +++++++++++++++++++
.../Components/Wizard/FluentWizard.md | 6 ++
2 files changed, 63 insertions(+)
create mode 100644 examples/Demo/FluentUI.Demo.Client/Documentation/Components/Wizard/Examples/WizardPosition.razor
diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/Wizard/Examples/WizardPosition.razor b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/Wizard/Examples/WizardPosition.razor
new file mode 100644
index 0000000000..90de4bd2f3
--- /dev/null
+++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/Wizard/Examples/WizardPosition.razor
@@ -0,0 +1,57 @@
+
+
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec ut nisi eget dolor semper
+ luctus vitae a nulla. Cras semper eros sed lacinia tincidunt. Mauris dignissim ullamcorper dolor,
+ ut blandit dui ullamcorper faucibus. Interdum et malesuada fames ac ante ipsum.
+
+
+ Maecenas sed justo ac sapien venenatis ullamcorper. Sed maximus nunc non venenatis euismod.
+ Fusce vel porta ex, imperdiet molestie nisl. Vestibulum eu ultricies mauris, eget aliquam quam.
+
+
+ Nunc dignissim tortor eget lacus porta tristique. Nunc in posuere dui. Cras ligula ex,
+ ullamcorper in gravida in, euismod vitae purus. Lorem ipsum dolor sit amet, consectetur
+ adipiscing elit. Aliquam at velit leo. Suspendisse potenti. Cras dictum eu augue in laoreet.
+
+
+ Phasellus quis augue convallis, congue velit ac, aliquam ex. In egestas porttitor massa
+ aliquet porttitor. Donec bibendum faucibus urna vitae elementum. Phasellus vitae efficitur
+ turpis, eget molestie ipsum.
+
+
+ Ut iaculis sed magna efficitur tempor. Vestibulum est erat, imperdiet in diam ac,
+ aliquam tempus sapien. Nam rutrum mi at enim mattis, non mollis diam molestie.
+ Cras sodales dui libero, sit amet cursus sapien elementum ac. Nulla euismod nisi sem.
+
+
+
+
+@code
+{
+ void OnStepChange(FluentWizardStepChangeEventArgs e)
+ {
+ Console.WriteLine("Step changed to {0}", e.TargetIndex);
+ }
+
+ void OnFinished()
+ {
+ Console.WriteLine("Wizard has been finished");
+ }
+}
diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/Wizard/FluentWizard.md b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/Wizard/FluentWizard.md
index f682d31550..725beddba9 100644
--- a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/Wizard/FluentWizard.md
+++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/Wizard/FluentWizard.md
@@ -37,6 +37,12 @@ the static properties **FluentWizard.LabelButtonPrevious / LabelButtonNext / Lab
{{ WizardDefault }}
+## Positioning
+
+You can choose to display the steps on the left (default) or on the top of the component using the **StepperPosition** parameter.
+
+{{ WizardPosition }}
+
## Customized
You can customize the wizard with a **ButtonTemplate** to replace the default Previous/Next/Done buttons,
From 1f93f2cb927e929f4159189ee3f65cfb8efc9716 Mon Sep 17 00:00:00 2001
From: Marvin Klein
Date: Mon, 6 Apr 2026 12:27:00 +0200
Subject: [PATCH 16/27] Clear also _editContext
---
src/Core/Components/Wizard/FluentWizardStep.razor.cs | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/Core/Components/Wizard/FluentWizardStep.razor.cs b/src/Core/Components/Wizard/FluentWizardStep.razor.cs
index 6a0801b637..186db90ab1 100644
--- a/src/Core/Components/Wizard/FluentWizardStep.razor.cs
+++ b/src/Core/Components/Wizard/FluentWizardStep.razor.cs
@@ -173,9 +173,10 @@ public void RegisterEditFormAndContext(EditForm editForm, EditContext editContex
///
/// Clears all registered EditForm and EditContext pairs.
///
- public void ClearEditFormAndContext()
+ internal void ClearEditFormAndContext()
{
_editForms.Clear();
+ _editContexts.Clear();
}
///
From 1f210777ce856390c1703eb2883bc78e0cea1f11 Mon Sep 17 00:00:00 2001
From: Marvin Klein
Date: Mon, 6 Apr 2026 12:27:19 +0200
Subject: [PATCH 17/27] Make internal
---
src/Core/Components/Wizard/FluentWizardStep.razor.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Core/Components/Wizard/FluentWizardStep.razor.cs b/src/Core/Components/Wizard/FluentWizardStep.razor.cs
index 186db90ab1..d8a0821f77 100644
--- a/src/Core/Components/Wizard/FluentWizardStep.razor.cs
+++ b/src/Core/Components/Wizard/FluentWizardStep.razor.cs
@@ -183,7 +183,7 @@ internal void ClearEditFormAndContext()
/// Registers an for validation tracking.
/// This is typically called by the component.
///
- public void RegisterEditContext(EditContext editContext)
+ internal void RegisterEditContext(EditContext editContext)
{
if (!_editContexts.Contains(editContext))
{
From e678c81c99899a93368c35699884f06d330f5825 Mon Sep 17 00:00:00 2001
From: Marvin Klein
Date: Mon, 6 Apr 2026 12:27:32 +0200
Subject: [PATCH 18/27] make internal
---
src/Core/Components/Wizard/FluentWizardStep.razor.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Core/Components/Wizard/FluentWizardStep.razor.cs b/src/Core/Components/Wizard/FluentWizardStep.razor.cs
index d8a0821f77..efeeda710f 100644
--- a/src/Core/Components/Wizard/FluentWizardStep.razor.cs
+++ b/src/Core/Components/Wizard/FluentWizardStep.razor.cs
@@ -194,7 +194,7 @@ internal void RegisterEditContext(EditContext editContext)
///
/// Unregisters an from validation tracking.
///
- public void UnregisterEditContext(EditContext editContext)
+ internal void UnregisterEditContext(EditContext editContext)
{
_editContexts.Remove(editContext);
}
From 33d78089f914ff42c69dc971c71fbfcb61a0c48a Mon Sep 17 00:00:00 2001
From: Marvin Klein
Date: Mon, 6 Apr 2026 12:42:37 +0200
Subject: [PATCH 19/27] Add ButtonTemplate test
---
...tWizard_ButtonTemplate.verified.razor.html | 56 +++++++++++++++++++
.../Components/Wizard/FluentWizardTests.razor | 54 +++++++++++++++++-
2 files changed, 107 insertions(+), 3 deletions(-)
create mode 100644 tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_ButtonTemplate.verified.razor.html
diff --git a/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_ButtonTemplate.verified.razor.html b/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_ButtonTemplate.verified.razor.html
new file mode 100644
index 0000000000..0a6871650b
--- /dev/null
+++ b/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_ButtonTemplate.verified.razor.html
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+ Step 1
+
+
+
+
+
+
+ Step 2
+
+
+
+
+
+
+ Step 3
+
+
+
+
+
Content 1
+
Content 2
+
Content 3
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/Core/Components/Wizard/FluentWizardTests.razor b/tests/Core/Components/Wizard/FluentWizardTests.razor
index c289fef7e6..56c40c995c 100644
--- a/tests/Core/Components/Wizard/FluentWizardTests.razor
+++ b/tests/Core/Components/Wizard/FluentWizardTests.razor
@@ -163,12 +163,14 @@
cut.Verify();
}
- [Fact]
- public void FluentWizard_StepperPositionTop()
+ [Theory]
+ [InlineData(StepperPosition.Top)]
+ [InlineData(StepperPosition.Left)]
+ public void FluentWizard_StepperPosition(StepperPosition position)
{
// Arrange && Act
var cut = Render(
- @
+ @
Content 1
Content 2
@@ -233,6 +235,52 @@
cut.Verify();
}
+ [Fact]
+ public void FluentWizard_ButtonTemplate()
+ {
+ FluentWizard wizard = default!;
+ int Value = 0;
+
+ // Arrange && Act
+ var cut = Render(
+ @
+
+ Content 1
+ Content 2
+ Content 3
+
+
+ @{
+ var index = context;
+ var lastStepIndex = 3;
+
+
+ @if (index > 0)
+ {
+ Go to first page
+ Previous
+ }
+
+
+
+ @if (index != lastStepIndex)
+ {
+ Next
+ Go to last page
+ }
+ else
+ {
+ Finish
+ }
+
+ }
+
+ );
+
+ // Assert
+ cut.Verify();
+ }
+
[Fact]
public void FluentWizard_StepSequenceAny()
{
From 1598a8ecc5793ebf2ed9918c058d9496e146dcf4 Mon Sep 17 00:00:00 2001
From: Marvin Klein
Date: Mon, 6 Apr 2026 12:48:15 +0200
Subject: [PATCH 20/27] Update tests
---
...sts.FluentWizard_StepperPosition.verified.razor.html} | 8 ++++----
tests/Core/Components/Wizard/FluentWizardTests.razor | 9 +++++----
2 files changed, 9 insertions(+), 8 deletions(-)
rename tests/Core/Components/Wizard/{FluentWizardTests.FluentWizard_StepperPositionTop.verified.razor.html => FluentWizardTests.FluentWizard_StepperPosition.verified.razor.html} (83%)
diff --git a/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_StepperPositionTop.verified.razor.html b/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_StepperPosition.verified.razor.html
similarity index 83%
rename from tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_StepperPositionTop.verified.razor.html
rename to tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_StepperPosition.verified.razor.html
index b86a2ed5c4..87fdf077e1 100644
--- a/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_StepperPositionTop.verified.razor.html
+++ b/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_StepperPosition.verified.razor.html
@@ -1,9 +1,9 @@
-
+
-
+
@@ -12,9 +12,9 @@
Step 1
-
+
-
+
diff --git a/tests/Core/Components/Wizard/FluentWizardTests.razor b/tests/Core/Components/Wizard/FluentWizardTests.razor
index 56c40c995c..fc88ab6c2b 100644
--- a/tests/Core/Components/Wizard/FluentWizardTests.razor
+++ b/tests/Core/Components/Wizard/FluentWizardTests.razor
@@ -164,9 +164,9 @@
}
[Theory]
- [InlineData(StepperPosition.Top)]
- [InlineData(StepperPosition.Left)]
- public void FluentWizard_StepperPosition(StepperPosition position)
+ [InlineData(StepperPosition.Top, "top")]
+ [InlineData(StepperPosition.Left, "left")]
+ public void FluentWizard_StepperPosition(StepperPosition position, string expectedValue)
{
// Arrange && Act
var cut = Render(
@@ -178,7 +178,8 @@
);
// Assert
- cut.Verify();
+ var attribute = cut.Find("div").GetAttribute("position") ?? string.Empty;
+ Assert.Equal(expectedValue, attribute); ;
}
[Fact]
From 9f77c028cc8699975e3bda06e54c06d4127ecd5f Mon Sep 17 00:00:00 2001
From: Marvin Klein
Date: Mon, 6 Apr 2026 12:56:24 +0200
Subject: [PATCH 21/27] Add StepTemplate test
---
...entWizard_StepTemplate.verified.razor.html | 42 +++++++++++++++++
.../Components/Wizard/FluentWizardTests.razor | 46 +++++++++++++++++++
2 files changed, 88 insertions(+)
create mode 100644 tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_StepTemplate.verified.razor.html
diff --git a/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_StepTemplate.verified.razor.html b/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_StepTemplate.verified.razor.html
new file mode 100644
index 0000000000..b5d5578f7b
--- /dev/null
+++ b/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_StepTemplate.verified.razor.html
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+ Intro
+
+
+
+
+
+ Details
+
+
+
+
+
+ Conclusion
+
+
+
+
+
+ Content 1
+
+
+ Content 2
+
+
+ Content 3
+
+
+
+
+
+ Next
+
+
+
+
\ No newline at end of file
diff --git a/tests/Core/Components/Wizard/FluentWizardTests.razor b/tests/Core/Components/Wizard/FluentWizardTests.razor
index fc88ab6c2b..5271a6e7ec 100644
--- a/tests/Core/Components/Wizard/FluentWizardTests.razor
+++ b/tests/Core/Components/Wizard/FluentWizardTests.razor
@@ -331,4 +331,50 @@
Assert.Equal("Step 2", eventArgs.TargetLabel);
Assert.False(eventArgs.IsCancelled);
}
+
+
+ [Fact]
+ public void FluentWizard_StepTemplate()
+ {
+
+ // Arrange && Act
+ var cut = Render(
+ @
+
+
+
+
+ Intro
+
+
+
+ Content 1
+
+
+
+
+
+ Details
+
+
+
+ Content 2
+
+
+
+
+
+ Conclusion
+
+
+
+ Content 3
+
+
+
+ );
+
+ // Assert
+ cut.Verify();
+ }
}
From a39367191f1aa9590be5c1891b591a1ee6c400fc Mon Sep 17 00:00:00 2001
From: Marvin Klein
Date: Sat, 11 Apr 2026 13:57:53 +0200
Subject: [PATCH 22/27] Remove static properties
---
src/Core/Components/Wizard/FluentWizard.razor | 6 ++--
.../Components/Wizard/FluentWizard.razor.cs | 32 +------------------
src/Core/Localization/LanguageResource.resx | 1 -
3 files changed, 4 insertions(+), 35 deletions(-)
diff --git a/src/Core/Components/Wizard/FluentWizard.razor b/src/Core/Components/Wizard/FluentWizard.razor
index 82a0ff44d0..fefc6d85c3 100644
--- a/src/Core/Components/Wizard/FluentWizard.razor
+++ b/src/Core/Components/Wizard/FluentWizard.razor
@@ -37,7 +37,7 @@
- @LabelButtonPreviousValue
+ @Localizer[Localization.LanguageResource.Wizard_LabelButtonPrevious]
}
@@ -48,7 +48,7 @@
- @LabelButtonNextValue
+ @Localizer[Localization.LanguageResource.Wizard_LabelButtonNext]
}
else
@@ -56,7 +56,7 @@
- @LabelButtonDoneValue
+ @Localizer[Localization.LanguageResource.Wizard_LabelButtonDone]
}
}
diff --git a/src/Core/Components/Wizard/FluentWizard.razor.cs b/src/Core/Components/Wizard/FluentWizard.razor.cs
index 3a145afb4f..121ac75a1f 100644
--- a/src/Core/Components/Wizard/FluentWizard.razor.cs
+++ b/src/Core/Components/Wizard/FluentWizard.razor.cs
@@ -13,37 +13,7 @@ namespace Microsoft.FluentUI.AspNetCore.Components;
///
public partial class FluentWizard : FluentComponentBase
{
- ///
- /// Gets or sets the label for the Previous button.
- /// If null or empty, the localized value is used.
- ///
- public static string? LabelButtonPrevious { get; set; }
-
- ///
- /// Gets or sets the label for the Next button.
- /// If null or empty, the localized value is used.
- ///
- public static string? LabelButtonNext { get; set; }
-
- ///
- /// Gets or sets the label for the Done button.
- /// If null or empty, the localized value is used.
- ///
- public static string? LabelButtonDone { get; set; }
-
- private string LabelButtonPreviousValue => string.IsNullOrWhiteSpace(LabelButtonPrevious)
- ? Localizer[Localization.LanguageResource.Wizard_LabelButtonPrevious]
- : LabelButtonPrevious;
-
- private string LabelButtonNextValue => string.IsNullOrWhiteSpace(LabelButtonNext)
- ? Localizer[Localization.LanguageResource.Wizard_LabelButtonNext]
- : LabelButtonNext;
-
- private string LabelButtonDoneValue => string.IsNullOrWhiteSpace(LabelButtonDone)
- ? Localizer[Localization.LanguageResource.Wizard_LabelButtonDone]
- : LabelButtonDone;
-
- private readonly List _steps = new();
+ private readonly List _steps = [];
internal int _maxStepVisited;
///
diff --git a/src/Core/Localization/LanguageResource.resx b/src/Core/Localization/LanguageResource.resx
index af65c48388..8724a3be6d 100644
--- a/src/Core/Localization/LanguageResource.resx
+++ b/src/Core/Localization/LanguageResource.resx
@@ -354,7 +354,6 @@
The maximum number of {0} selected items has been reached.
-
Previous
From d75cd91d594a32c16810fe5aa427d7f6edcce893 Mon Sep 17 00:00:00 2001
From: Marvin Klein
Date: Sat, 11 Apr 2026 13:58:35 +0200
Subject: [PATCH 23/27] Remove button width because it can overflow when the
user overwrite the translations
---
src/Core/Components/Wizard/FluentWizard.razor | 5 -----
1 file changed, 5 deletions(-)
diff --git a/src/Core/Components/Wizard/FluentWizard.razor b/src/Core/Components/Wizard/FluentWizard.razor
index fefc6d85c3..e500bd0978 100644
--- a/src/Core/Components/Wizard/FluentWizard.razor
+++ b/src/Core/Components/Wizard/FluentWizard.razor
@@ -30,12 +30,9 @@
border-inside=@Border.HasFlag(WizardBorder.Inside)>
@if (ButtonTemplate == null)
{
- string buttonWidth = "80px;";
-
@if (DisplayPreviousButton)
{
@Localizer[Localization.LanguageResource.Wizard_LabelButtonPrevious]
@@ -46,7 +43,6 @@
@if (DisplayNextButton)
{
@Localizer[Localization.LanguageResource.Wizard_LabelButtonNext]
@@ -54,7 +50,6 @@
else
{
@Localizer[Localization.LanguageResource.Wizard_LabelButtonDone]
From 8991e906fb5552c8e208918daa06e6d269fcd112 Mon Sep 17 00:00:00 2001
From: Marvin Klein
Date: Sat, 11 Apr 2026 14:01:12 +0200
Subject: [PATCH 24/27] Use FluentStack
---
src/Core/Components/Wizard/FluentWizard.razor | 110 +++++++++---------
1 file changed, 55 insertions(+), 55 deletions(-)
diff --git a/src/Core/Components/Wizard/FluentWizard.razor b/src/Core/Components/Wizard/FluentWizard.razor
index e500bd0978..e591d09f28 100644
--- a/src/Core/Components/Wizard/FluentWizard.razor
+++ b/src/Core/Components/Wizard/FluentWizard.razor
@@ -3,62 +3,62 @@
-
-
- @Steps
-
+
+
+ @Steps
+
-
- @foreach (var step in _steps.Where(i => i.Index == Value || !i.DeferredLoading))
- {
-
-
- @(step.ChildContent)
-
-
- }
-
+
+ @foreach (var step in _steps.Where(i => i.Index == Value || !i.DeferredLoading))
+ {
+
+
+ @(step.ChildContent)
+
+
+ }
+
-
+ @if (DisplayNextButton)
+ {
+
+ @Localizer[Localization.LanguageResource.Wizard_LabelButtonNext]
+
+ }
+ else
+ {
+
+ @Localizer[Localization.LanguageResource.Wizard_LabelButtonDone]
+
+ }
+
+ }
+ else
+ {
+ @ButtonTemplate(Value)
+ }
+
+
From 471e76a4f2ed68eb6d8b39f3610fdb62ff55fcdd Mon Sep 17 00:00:00 2001
From: Marvin Klein
Date: Fri, 17 Apr 2026 22:57:38 +0200
Subject: [PATCH 25/27] Use SetParametersAsync instead on OnParameterSet
---
src/Core/Components/Wizard/FluentWizard.razor.cs | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)
diff --git a/src/Core/Components/Wizard/FluentWizard.razor.cs b/src/Core/Components/Wizard/FluentWizard.razor.cs
index 121ac75a1f..11f402b02e 100644
--- a/src/Core/Components/Wizard/FluentWizard.razor.cs
+++ b/src/Core/Components/Wizard/FluentWizard.razor.cs
@@ -126,10 +126,15 @@ public FluentWizard(LibraryConfiguration configuration) : base(configuration)
public WizardStepSequence StepSequence { get; set; } = WizardStepSequence.Linear;
///
- protected override void OnParametersSet()
+ public override Task SetParametersAsync(ParameterView parameters)
{
- SetCurrentValue(Value);
- base.OnParametersSet();
+ // If Value parameter changes, we need to switch to the new step.
+ if (parameters.TryGetValue(nameof(Value), out var newValue) && !Equals(newValue, Value))
+ {
+ SetCurrentValue(newValue);
+ }
+
+ return base.SetParametersAsync(parameters);
}
///
From 683d9dc158716b15850a8ba4d13850d032755473 Mon Sep 17 00:00:00 2001
From: Marvin Klein
Date: Fri, 17 Apr 2026 23:07:22 +0200
Subject: [PATCH 26/27] Order code according to blazor-code.md
---
.../Components/Wizard/FluentWizard.razor.cs | 118 +++++------
.../Wizard/FluentWizardStep.razor.cs | 185 +++++++++---------
2 files changed, 151 insertions(+), 152 deletions(-)
diff --git a/src/Core/Components/Wizard/FluentWizard.razor.cs b/src/Core/Components/Wizard/FluentWizard.razor.cs
index 11f402b02e..f4c7f12f63 100644
--- a/src/Core/Components/Wizard/FluentWizard.razor.cs
+++ b/src/Core/Components/Wizard/FluentWizard.razor.cs
@@ -22,17 +22,6 @@ public FluentWizard(LibraryConfiguration configuration) : base(configuration)
Id = Identifier.NewId();
}
- ///
- protected string? ClassValue => DefaultClassBuilder
- .AddClass("fluent-wizard")
- .Build();
-
- ///
- protected string? StyleValue => DefaultStyleBuilder
- .AddStyle("width", Width)
- .AddStyle("height", Height)
- .Build();
-
///
/// Gets or sets the height of the wizard.
///
@@ -125,6 +114,23 @@ public FluentWizard(LibraryConfiguration configuration) : base(configuration)
[Parameter]
public WizardStepSequence StepSequence { get; set; } = WizardStepSequence.Linear;
+ ///
+ protected string? ClassValue => DefaultClassBuilder
+ .AddClass("fluent-wizard")
+ .Build();
+
+ ///
+ protected string? StyleValue => DefaultStyleBuilder
+ .AddStyle("width", Width)
+ .AddStyle("height", Height)
+ .Build();
+
+ internal int StepCount => _steps.Count;
+
+ private bool DisplayPreviousButton => Value > 0 && _steps[..Value].Any(i => !i.Disabled);
+
+ private bool DisplayNextButton => Value < _steps.Count - 1 && _steps[(Value + 1)..].Any(i => !i.Disabled);
+
///
public override Task SetParametersAsync(ParameterView parameters)
{
@@ -137,6 +143,48 @@ public override Task SetParametersAsync(ParameterView parameters)
return base.SetParametersAsync(parameters);
}
+ ///
+ /// Optionally validate and invoke the handler.
+ ///
+ /// Validate the EditContext. Default is false.
+ ///
+ public async Task FinishAsync(bool validateEditContexts = false)
+ {
+ if (validateEditContexts)
+ {
+ // Validate any form edit contexts
+ var allEditContextsAreValid = _steps[Value].ValidateEditContexts();
+ if (!allEditContextsAreValid)
+ {
+ // Invoke the 'OnInvalidSubmit' handlers for the edit forms.
+ await _steps[Value].InvokeOnInValidSubmitForEditFormsAsync();
+ return;
+ }
+ }
+
+ // Invoke the 'OnValidSubmit' handlers for the edit forms.
+ await _steps[Value].InvokeOnValidSubmitForEditFormsAsync();
+ await _steps[Value].InvokeOnSubmitForEditFormsAsync();
+
+ _steps[Value].Status = WizardStepStatus.Previous;
+
+ if (OnFinish.HasDelegate)
+ {
+ await OnFinish.InvokeAsync();
+ }
+ }
+
+ ///
+ /// Navigate to the specified step, with or without validate the current EditContexts.
+ ///
+ /// Index number of the step to display
+ /// Validate the EditContext. Default is false.
+ ///
+ public Task GoToStepAsync(int step, bool validateEditContexts = false)
+ {
+ return ValidateAndGoToStepAsync(step, validateEditContexts);
+ }
+
///
protected virtual async Task OnNextHandlerAsync(MouseEventArgs e)
{
@@ -240,48 +288,6 @@ protected virtual Task OnFinishHandlerAsync(MouseEventArgs e)
return FinishAsync(validateEditContexts: true);
}
- ///
- /// Optionally validate and invoke the handler.
- ///
- /// Validate the EditContext. Default is false.
- ///
- public async Task FinishAsync(bool validateEditContexts = false)
- {
- if (validateEditContexts)
- {
- // Validate any form edit contexts
- var allEditContextsAreValid = _steps[Value].ValidateEditContexts();
- if (!allEditContextsAreValid)
- {
- // Invoke the 'OnInvalidSubmit' handlers for the edit forms.
- await _steps[Value].InvokeOnInValidSubmitForEditFormsAsync();
- return;
- }
- }
-
- // Invoke the 'OnValidSubmit' handlers for the edit forms.
- await _steps[Value].InvokeOnValidSubmitForEditFormsAsync();
- await _steps[Value].InvokeOnSubmitForEditFormsAsync();
-
- _steps[Value].Status = WizardStepStatus.Previous;
-
- if (OnFinish.HasDelegate)
- {
- await OnFinish.InvokeAsync();
- }
- }
-
- ///
- /// Navigate to the specified step, with or without validate the current EditContexts.
- ///
- /// Index number of the step to display
- /// Validate the EditContext. Default is false.
- ///
- public Task GoToStepAsync(int step, bool validateEditContexts = false)
- {
- return ValidateAndGoToStepAsync(step, validateEditContexts);
- }
-
internal async Task ValidateAndGoToStepAsync(int targetIndex, bool validateEditContexts)
{
var stepChangeArgs = await OnStepChangeHandlerAsync(targetIndex, validateEditContexts);
@@ -320,8 +326,6 @@ internal int AddStep(FluentWizardStep step)
return index;
}
- internal int StepCount => _steps.Count;
-
internal void RemoveStep(FluentWizardStep step)
{
_steps.Remove(step);
@@ -393,8 +397,4 @@ private void SetCurrentStatusToStep(int stepIndex)
_ => null,
};
}
-
- private bool DisplayPreviousButton => Value > 0 && _steps[..Value].Any(i => !i.Disabled);
-
- private bool DisplayNextButton => Value < _steps.Count - 1 && _steps[(Value + 1)..].Any(i => !i.Disabled);
}
diff --git a/src/Core/Components/Wizard/FluentWizardStep.razor.cs b/src/Core/Components/Wizard/FluentWizardStep.razor.cs
index efeeda710f..d45426ab61 100644
--- a/src/Core/Components/Wizard/FluentWizardStep.razor.cs
+++ b/src/Core/Components/Wizard/FluentWizardStep.razor.cs
@@ -22,23 +22,6 @@ public FluentWizardStep(LibraryConfiguration configuration) : base(configuration
Id = Identifier.NewId();
}
- ///
- protected string? ClassValue => DefaultClassBuilder.Build();
-
- ///
- protected string? StyleValue => DefaultStyleBuilder
- .AddStyle("position", "relative")
- .AddStyle("display", "flex")
- .AddStyle("gap", "10px", when: FluentWizard.StepperPosition == StepperPosition.Left)
- .AddStyle("flex-direction", "column", when: FluentWizard.StepperPosition == StepperPosition.Top)
- .AddStyle("align-items", "center", when: FluentWizard.StepperPosition == StepperPosition.Top)
- .AddStyle("flex", "1", when: FluentWizard.StepperPosition == StepperPosition.Top)
- .AddStyle("text-align", "center", when: FluentWizard.StepperPosition == StepperPosition.Top)
- .AddStyle("max-width", FluentWizard.StepperBulletSpace ?? "100%", when: FluentWizard.StepperPosition == StepperPosition.Top)
- .AddStyle("height", IsLastStep ? "auto" : (FluentWizard.StepperBulletSpace ?? "100%"), when: FluentWizard.StepperPosition == StepperPosition.Left)
- .AddStyle("cursor", "pointer", when: IsStepClickable)
- .Build();
-
///
/// Gets or sets the content of the step.
///
@@ -51,11 +34,6 @@ public FluentWizardStep(LibraryConfiguration configuration) : base(configuration
[Parameter]
public RenderFragment? StepTemplate { get; set; }
- ///
- /// Gets the step index.
- ///
- public int Index { get; private set; }
-
///
/// Gets or sets whether the step is disabled.
///
@@ -88,13 +66,6 @@ public FluentWizardStep(LibraryConfiguration configuration) : base(configuration
[Parameter]
public EventCallback OnChange { get; set; }
- ///
- /// Reference to the parent component.
- /// For internal use only.
- ///
- [CascadingParameter]
- internal FluentWizard FluentWizard { get; set; } = default!;
-
///
/// Gets or sets the summary of the step, to display near the label.
///
@@ -122,13 +93,41 @@ public FluentWizardStep(LibraryConfiguration configuration) : base(configuration
[Parameter]
public Icon IconNext { get; set; } = new CoreIcons.Regular.Size20.Circle();
+ ///
+ /// Reference to the parent component.
+ /// For internal use only.
+ ///
+ [CascadingParameter]
+ internal FluentWizard FluentWizard { get; set; } = default!;
+
+ ///
+ /// Gets the step index.
+ ///
+ public int Index { get; private set; }
+
+ ///
+ protected string? ClassValue => DefaultClassBuilder.Build();
+
+ ///
+ protected string? StyleValue => DefaultStyleBuilder
+ .AddStyle("position", "relative")
+ .AddStyle("display", "flex")
+ .AddStyle("gap", "10px", when: FluentWizard.StepperPosition == StepperPosition.Left)
+ .AddStyle("flex-direction", "column", when: FluentWizard.StepperPosition == StepperPosition.Top)
+ .AddStyle("align-items", "center", when: FluentWizard.StepperPosition == StepperPosition.Top)
+ .AddStyle("flex", "1", when: FluentWizard.StepperPosition == StepperPosition.Top)
+ .AddStyle("text-align", "center", when: FluentWizard.StepperPosition == StepperPosition.Top)
+ .AddStyle("max-width", FluentWizard.StepperBulletSpace ?? "100%", when: FluentWizard.StepperPosition == StepperPosition.Top)
+ .AddStyle("height", IsLastStep ? "auto" : (FluentWizard.StepperBulletSpace ?? "100%"), when: FluentWizard.StepperPosition == StepperPosition.Left)
+ .AddStyle("cursor", "pointer", when: IsStepClickable)
+ .Build();
+
internal WizardStepStatus Status { get; set; } = WizardStepStatus.Next;
private bool IsLastStep => Index >= FluentWizard.StepCount - 1;
private string IconStyle => "width: var(--fluent-wizard-circle-size);" +
(Disabled ? " fill-opacity: 0.4;" : string.Empty);
-
private Icon StepIcon
{
get
@@ -143,6 +142,35 @@ private Icon StepIcon
}
}
+ private bool IsStepClickable
+ {
+ get
+ {
+ if (Disabled)
+ {
+ return false;
+ }
+
+ if (FluentWizard.Value == Index)
+ {
+ return false;
+ }
+
+ if (FluentWizard.StepSequence == WizardStepSequence.Linear)
+ {
+ return false;
+ }
+
+ if (FluentWizard.StepSequence == WizardStepSequence.Visited &&
+ Index > FluentWizard._maxStepVisited)
+ {
+ return false;
+ }
+
+ return true;
+ }
+ }
+
///
protected override void OnInitialized()
{
@@ -155,13 +183,6 @@ protected override void OnInitialized()
base.OnInitialized();
}
- ///
- public override async ValueTask DisposeAsync()
- {
- FluentWizard?.RemoveStep(this);
- await base.DisposeAsync();
- }
-
///
/// Registers an EditForm and its EditContext for validation tracking.
///
@@ -170,6 +191,40 @@ public void RegisterEditFormAndContext(EditForm editForm, EditContext editContex
_editForms.TryAdd(editForm, editContext);
}
+ ///
+ /// Validates all registered EditContexts.
+ ///
+ public bool ValidateEditContexts()
+ {
+ var isValid = true;
+ foreach (var editForm in _editForms)
+ {
+ var contextIsValid = editForm.Value.Validate();
+ if (!contextIsValid)
+ {
+ isValid = false;
+ }
+ }
+
+ foreach (var editContext in _editContexts)
+ {
+ var contextIsValid = editContext.Validate();
+ if (!contextIsValid)
+ {
+ isValid = false;
+ }
+ }
+
+ return isValid;
+ }
+
+ ///
+ public override async ValueTask DisposeAsync()
+ {
+ FluentWizard?.RemoveStep(this);
+ await base.DisposeAsync();
+ }
+
///
/// Clears all registered EditForm and EditContext pairs.
///
@@ -199,33 +254,6 @@ internal void UnregisterEditContext(EditContext editContext)
_editContexts.Remove(editContext);
}
- ///
- /// Validates all registered EditContexts.
- ///
- public bool ValidateEditContexts()
- {
- var isValid = true;
- foreach (var editForm in _editForms)
- {
- var contextIsValid = editForm.Value.Validate();
- if (!contextIsValid)
- {
- isValid = false;
- }
- }
-
- foreach (var editContext in _editContexts)
- {
- var contextIsValid = editContext.Validate();
- if (!contextIsValid)
- {
- isValid = false;
- }
- }
-
- return isValid;
- }
-
internal async Task InvokeOnValidSubmitForEditFormsAsync()
{
foreach (var editForm in _editForms)
@@ -259,33 +287,4 @@ private async Task OnClickHandlerAsync()
await FluentWizard.ValidateAndGoToStepAsync(Index, validateEditContexts: Index > FluentWizard.Value);
}
-
- private bool IsStepClickable
- {
- get
- {
- if (Disabled)
- {
- return false;
- }
-
- if (FluentWizard.Value == Index)
- {
- return false;
- }
-
- if (FluentWizard.StepSequence == WizardStepSequence.Linear)
- {
- return false;
- }
-
- if (FluentWizard.StepSequence == WizardStepSequence.Visited &&
- Index > FluentWizard._maxStepVisited)
- {
- return false;
- }
-
- return true;
- }
- }
}
From 22b78814482dae0e6a4410a064660d3d5ada452d Mon Sep 17 00:00:00 2001
From: Marvin Klein <32510006+MarvinKlein1508@users.noreply.github.com>
Date: Fri, 24 Apr 2026 16:37:04 +0200
Subject: [PATCH 27/27] Fix verified files
---
...ts.FluentWizard_Border.verified.razor.html | 7 ++--
...izard_CancelStepChange.verified.razor.html | 7 ++--
...s.FluentWizard_Default.verified.razor.html | 7 ++--
...Wizard_DeferredLoading.verified.razor.html | 7 ++--
...entWizard_DisabledStep.verified.razor.html | 7 ++--
...luentWizard_NextButton.verified.razor.html | 11 +++---
....FluentWizard_OnFinish.verified.razor.html | 11 +++---
...tWizard_PreviousButton.verified.razor.html | 7 ++--
...Wizard_StepSequenceAny.verified.razor.html | 11 +++---
...entWizard_StepTemplate.verified.razor.html | 7 ++--
...Wizard_StepperPosition.verified.razor.html | 39 -------------------
...entWizard_ValueBinding.verified.razor.html | 11 +++---
...FluentWizard_WithSteps.verified.razor.html | 7 ++--
13 files changed, 56 insertions(+), 83 deletions(-)
delete mode 100644 tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_StepperPosition.verified.razor.html
diff --git a/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_Border.verified.razor.html b/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_Border.verified.razor.html
index 6c32129d2f..1a9403712f 100644
--- a/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_Border.verified.razor.html
+++ b/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_Border.verified.razor.html
@@ -30,9 +30,10 @@
Content 2
diff --git a/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_CancelStepChange.verified.razor.html b/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_CancelStepChange.verified.razor.html
index 87fdf077e1..f60b138e7d 100644
--- a/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_CancelStepChange.verified.razor.html
+++ b/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_CancelStepChange.verified.razor.html
@@ -30,9 +30,10 @@
Content 2
diff --git a/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_Default.verified.razor.html b/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_Default.verified.razor.html
index 26aeb84f64..d8e5501463 100644
--- a/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_Default.verified.razor.html
+++ b/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_Default.verified.razor.html
@@ -42,9 +42,10 @@
Content 3
-
-
-
-
-
- Step 1
-
-
-
-
-
-
- Step 2
-
-
-
-
-
Content 1
-
Content 2
-
-
-
-
- Next
-
-
-
diff --git a/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_DeferredLoading.verified.razor.html b/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_DeferredLoading.verified.razor.html
index 3ae935efbd..f845d65e6e 100644
--- a/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_DeferredLoading.verified.razor.html
+++ b/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_DeferredLoading.verified.razor.html
@@ -29,9 +29,10 @@
diff --git a/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_DisabledStep.verified.razor.html b/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_DisabledStep.verified.razor.html
index cab85167d1..acc3d86351 100644
--- a/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_DisabledStep.verified.razor.html
+++ b/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_DisabledStep.verified.razor.html
@@ -42,9 +42,10 @@
diff --git a/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_NextButton.verified.razor.html b/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_NextButton.verified.razor.html
index e3975032b6..728ce98e36 100644
--- a/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_NextButton.verified.razor.html
+++ b/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_NextButton.verified.razor.html
@@ -42,11 +42,12 @@
diff --git a/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_OnFinish.verified.razor.html b/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_OnFinish.verified.razor.html
index 6bceceea20..a6fa02d2ae 100644
--- a/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_OnFinish.verified.razor.html
+++ b/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_OnFinish.verified.razor.html
@@ -30,11 +30,12 @@
diff --git a/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_PreviousButton.verified.razor.html b/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_PreviousButton.verified.razor.html
index 26aeb84f64..d8e5501463 100644
--- a/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_PreviousButton.verified.razor.html
+++ b/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_PreviousButton.verified.razor.html
@@ -42,9 +42,10 @@
diff --git a/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_StepSequenceAny.verified.razor.html b/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_StepSequenceAny.verified.razor.html
index c26f52bdf3..781a551f00 100644
--- a/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_StepSequenceAny.verified.razor.html
+++ b/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_StepSequenceAny.verified.razor.html
@@ -42,11 +42,12 @@
diff --git a/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_StepTemplate.verified.razor.html b/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_StepTemplate.verified.razor.html
index b5d5578f7b..7bb22767f8 100644
--- a/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_StepTemplate.verified.razor.html
+++ b/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_StepTemplate.verified.razor.html
@@ -33,9 +33,10 @@
diff --git a/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_StepperPosition.verified.razor.html b/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_StepperPosition.verified.razor.html
deleted file mode 100644
index 87fdf077e1..0000000000
--- a/tests/Core/Components/Wizard/FluentWizardTests.FluentWizard_StepperPosition.verified.razor.html
+++ /dev/null
@@ -1,39 +0,0 @@
-
-