From b6c7c70c02005fc760b1cd2900b5b765f9e9e17c Mon Sep 17 00:00:00 2001 From: Shephard Tseisi Date: Sun, 18 Oct 2026 17:59:55 +0200 Subject: [PATCH 1/4] fix: bring the Stepper templates up to the package The CLI Stepper gains the data-driven StepItems mode, and the parts read ActiveStep instead of the CurrentStep parameter, so the stepper no longer overwrites a parameter it doesn't own. StepItem ships as a new stepper-models template that stepper depends on. Part of #52 --- src/ShellUI.Templates/ComponentRegistry.cs | 2 + .../Templates/StepperContentTemplate.cs | 2 +- .../Templates/StepperModelsTemplate.cs | 31 ++++ .../Templates/StepperStepTemplate.cs | 3 +- .../Templates/StepperTemplate.cs | 154 ++++++++++++++---- 5 files changed, 154 insertions(+), 38 deletions(-) create mode 100644 src/ShellUI.Templates/Templates/StepperModelsTemplate.cs diff --git a/src/ShellUI.Templates/ComponentRegistry.cs b/src/ShellUI.Templates/ComponentRegistry.cs index 215d750..1106bb8 100644 --- a/src/ShellUI.Templates/ComponentRegistry.cs +++ b/src/ShellUI.Templates/ComponentRegistry.cs @@ -250,6 +250,7 @@ public static class ComponentRegistry { "chat-input", ChatInputTemplate.Metadata }, { "chat", ChatTemplate.Metadata }, { "command-models", CommandModelsTemplate.Metadata }, + { "stepper-models", StepperModelsTemplate.Metadata }, }; public static string? GetComponentContent(string componentName) @@ -499,6 +500,7 @@ public static class ComponentRegistry "chat-input" => ChatInputTemplate.Content, "chat" => ChatTemplate.Content, "command-models" => CommandModelsTemplate.Content, + "stepper-models" => StepperModelsTemplate.Content, _ => null }; } diff --git a/src/ShellUI.Templates/Templates/StepperContentTemplate.cs b/src/ShellUI.Templates/Templates/StepperContentTemplate.cs index 7b5e9d2..bc56482 100644 --- a/src/ShellUI.Templates/Templates/StepperContentTemplate.cs +++ b/src/ShellUI.Templates/Templates/StepperContentTemplate.cs @@ -16,7 +16,7 @@ public static class StepperContentTemplate public static string Content => @"@namespace YourProjectNamespace.Components.UI -@if (ParentStepper != null && ParentStepper.CurrentStep == Index) +@if (ParentStepper != null && ParentStepper.ActiveStep == Index) {
@ChildContent diff --git a/src/ShellUI.Templates/Templates/StepperModelsTemplate.cs b/src/ShellUI.Templates/Templates/StepperModelsTemplate.cs new file mode 100644 index 0000000..8951498 --- /dev/null +++ b/src/ShellUI.Templates/Templates/StepperModelsTemplate.cs @@ -0,0 +1,31 @@ +using ShellUI.Core.Models; + +namespace ShellUI.Templates.Templates; + +public static class StepperModelsTemplate +{ + public static ComponentMetadata Metadata => new() + { + Name = "stepper-models", + DisplayName = "Stepper Models", + Description = "Models for Stepper", + Category = ComponentCategory.Navigation, + FilePath = "Models/StepperModels.cs", + IsAvailable = false, + Dependencies = new List() + }; + + public static string Content => @"using Microsoft.AspNetCore.Components; + +namespace YourProjectNamespace.Components.Models; + +public class StepItem +{ + public string Title { get; set; } = string.Empty; + public string? Description { get; set; } + public RenderFragment? Content { get; set; } + public bool IsCompleted { get; set; } + public bool IsOptional { get; set; } +} +"; +} diff --git a/src/ShellUI.Templates/Templates/StepperStepTemplate.cs b/src/ShellUI.Templates/Templates/StepperStepTemplate.cs index 7d2ab02..2229788 100644 --- a/src/ShellUI.Templates/Templates/StepperStepTemplate.cs +++ b/src/ShellUI.Templates/Templates/StepperStepTemplate.cs @@ -18,10 +18,11 @@ public static class StepperStepTemplate @{ var index = int.TryParse(Value, out var v) ? v : 0; - var isActive = ParentStepper?.CurrentStep == index; + var isActive = ParentStepper?.ActiveStep == index; var isCompleted = ParentStepper != null && index < ParentStepper.HighestStepReached; var isClickable = ParentStepper != null && index <= ParentStepper.HighestStepReached; + // Active wins over completed, so a revisited step shows as current. string stateClass; if (isActive) stateClass = ""border-primary bg-primary text-primary-foreground ring-2 ring-primary/20 ring-offset-2 ring-offset-background""; diff --git a/src/ShellUI.Templates/Templates/StepperTemplate.cs b/src/ShellUI.Templates/Templates/StepperTemplate.cs index 1bbe0ae..db5e680 100644 --- a/src/ShellUI.Templates/Templates/StepperTemplate.cs +++ b/src/ShellUI.Templates/Templates/StepperTemplate.cs @@ -12,52 +12,122 @@ public static class StepperTemplate Category = ComponentCategory.Navigation, FilePath = "Stepper.razor", Tags = new List { "stepper", "steps", "progress", "wizard", "multi-step" }, - Dependencies = new List { "stepper-list", "stepper-step", "stepper-content" } + Dependencies = new List { "stepper-models", "stepper-list", "stepper-step", "stepper-content" } }; public const string Content = """ @namespace YourProjectNamespace.Components.UI +@using YourProjectNamespace.Components.Models
- @ChildContent + @if (StepItems.Count > 0) + { + @* Data-driven mode (StepItems) *@ +
+ @for (int i = 0; i < StepItems.Count; i++) + { + var step = StepItems[i]; + var isActive = i == _activeStep; + var isCompleted = i < _highestStepReached; + var isClickable = i <= _highestStepReached; + var capturedIndex = i; + + string stateClass; + if (isActive) + stateClass = "border-primary bg-primary text-primary-foreground ring-2 ring-primary/20 ring-offset-2 ring-offset-background"; + else if (isCompleted) + stateClass = "border-primary bg-primary/10 text-primary"; + else + stateClass = "border-muted bg-background text-muted-foreground"; + +
+ +
+

@step.Title

+ @if (!string.IsNullOrEmpty(step.Description)) + { +

@step.Description

+ } +
+ @if (i < StepItems.Count - 1) + { +
+ } +
+ } +
+ @if (_activeStep < StepItems.Count) + { +
+ @StepItems[_activeStep].Content +
+ } + } + else + { + @* Compositional mode (StepperList + StepperContent) *@ + @ChildContent + } + @if (ShowNavigation) { @if (NavigationContent != null) { -
@NavigationContent
+
+ @NavigationContent +
} else { -
- - @if (IsLastStep && ShowConfirmOnLast) - { - - } - else - { - - } -
+ + @if (IsLastStep && ShowConfirmOnLast) + { + + } + else + { + + } +
} }
@code { - [Parameter] public int TotalSteps { get; set; } = 3; + [Parameter] public List StepItems { get; set; } = new(); + [Parameter] public int TotalSteps { get; set; } [Parameter] public int CurrentStep { get; set; } = 0; [Parameter] public EventCallback CurrentStepChanged { get; set; } [Parameter] public EventCallback OnConfirm { get; set; } @@ -71,13 +141,19 @@ @namespace YourProjectNamespace.Components.UI [Parameter] public string Class { get; set; } = string.Empty; private int _highestStepReached = 0; - private bool IsLastStep => CurrentStep >= TotalSteps - 1; + private int _activeStep = 0; + private bool _justNavigated; public int HighestStepReached => _highestStepReached; - public int StepCount => TotalSteps; + public int ActiveStep => _activeStep; + public int EffectiveStepCount => StepItems.Count > 0 ? StepItems.Count : (TotalSteps > 0 ? TotalSteps : 0); + public int StepCount => EffectiveStepCount; + private bool IsLastStep => _activeStep >= EffectiveStepCount - 1; protected override void OnParametersSet() { + if (!_justNavigated) + _activeStep = CurrentStep; if (CurrentStep > _highestStepReached) _highestStepReached = CurrentStep; } @@ -85,34 +161,40 @@ protected override void OnParametersSet() public async Task GoToStepAsync(int step) { if (step > _highestStepReached || step < 0) return; - CurrentStep = step; + _justNavigated = true; + _activeStep = step; await CurrentStepChanged.InvokeAsync(step); + _justNavigated = false; + StateHasChanged(); } private async Task PreviousStep() { - if (CurrentStep > 0) - await GoToStepAsync(CurrentStep - 1); + if (_activeStep > 0) + await GoToStepAsync(_activeStep - 1); } private async Task NextStep() { - if (CurrentStep < TotalSteps - 1) + if (_activeStep < EffectiveStepCount - 1) { - var nextStep = CurrentStep + 1; + var nextStep = _activeStep + 1; if (nextStep > _highestStepReached) _highestStepReached = nextStep; - CurrentStep = nextStep; + _justNavigated = true; + _activeStep = nextStep; await CurrentStepChanged.InvokeAsync(nextStep); + _justNavigated = false; + StateHasChanged(); } } private async Task HandleConfirm() { - if (TotalSteps > 0 && CurrentStep >= TotalSteps - 1) + if (EffectiveStepCount > 0 && _activeStep >= EffectiveStepCount - 1) { - if (_highestStepReached < TotalSteps - 1) - _highestStepReached = TotalSteps - 1; + if (_highestStepReached < EffectiveStepCount - 1) + _highestStepReached = EffectiveStepCount - 1; await OnConfirm.InvokeAsync(); } } From 084d5568bce092523b66b99e025ce3ceafa36cea Mon Sep 17 00:00:00 2001 From: Shephard Tseisi Date: Sun, 18 Oct 2026 18:01:31 +0200 Subject: [PATCH 2/4] fix: move the Input and Textarea variants into variants templates The Input template nested InputVariant inside the component, so callers had to write Input.InputVariant.Error, and Textarea had no variant at all. Both now match the package: input-variants and textarea-variants templates in Components/UI/Variants, like button-variants, and Textarea gains Variant, ReadOnly, OnFocus and OnBlur. Part of #52 --- src/ShellUI.Templates/ComponentRegistry.cs | 4 ++ .../Templates/InputTemplate.cs | 23 ++------- .../Templates/InputVariantsTemplate.cs | 41 +++++++++++++++ .../Templates/TextareaTemplate.cs | 50 ++++++++++++------- .../Templates/TextareaVariantsTemplate.cs | 36 +++++++++++++ 5 files changed, 116 insertions(+), 38 deletions(-) create mode 100644 src/ShellUI.Templates/Templates/InputVariantsTemplate.cs create mode 100644 src/ShellUI.Templates/Templates/TextareaVariantsTemplate.cs diff --git a/src/ShellUI.Templates/ComponentRegistry.cs b/src/ShellUI.Templates/ComponentRegistry.cs index 1106bb8..dc9687e 100644 --- a/src/ShellUI.Templates/ComponentRegistry.cs +++ b/src/ShellUI.Templates/ComponentRegistry.cs @@ -11,6 +11,8 @@ public static class ComponentRegistry { "shellui-js", ShellUIJsTemplate.Metadata }, { "button", ButtonTemplate.Metadata }, { "button-variants", ButtonVariantsTemplate.Metadata }, + { "input-variants", InputVariantsTemplate.Metadata }, + { "textarea-variants", TextareaVariantsTemplate.Metadata }, { "theme-toggle", ThemeToggleTemplate.Metadata }, { "input", InputTemplate.Metadata }, { "card", CardTemplate.Metadata }, @@ -261,6 +263,8 @@ public static class ComponentRegistry "shellui-js" => ShellUIJsTemplate.Content, "button" => ButtonTemplate.Content, "button-variants" => ButtonVariantsTemplate.Content, + "input-variants" => InputVariantsTemplate.Content, + "textarea-variants" => TextareaVariantsTemplate.Content, "theme-toggle" => ThemeToggleTemplate.Content, "input" => InputTemplate.Content, "card" => CardTemplate.Content, diff --git a/src/ShellUI.Templates/Templates/InputTemplate.cs b/src/ShellUI.Templates/Templates/InputTemplate.cs index 1003eb0..3c70c61 100644 --- a/src/ShellUI.Templates/Templates/InputTemplate.cs +++ b/src/ShellUI.Templates/Templates/InputTemplate.cs @@ -12,12 +12,13 @@ public static class InputTemplate Category = ComponentCategory.Form, FilePath = "Input.razor", - Dependencies = new List() + Dependencies = new List { "input-variants" } }; public static string Content => @"@namespace YourProjectNamespace.Components.UI +@using YourProjectNamespace.Components.UI.Variants - ""border-destructive focus-visible:ring-destructive"", - _ => """" - }; - - return Shell.Cn(baseClasses, variantClasses, className); - } - } } "; } diff --git a/src/ShellUI.Templates/Templates/InputVariantsTemplate.cs b/src/ShellUI.Templates/Templates/InputVariantsTemplate.cs new file mode 100644 index 0000000..181bded --- /dev/null +++ b/src/ShellUI.Templates/Templates/InputVariantsTemplate.cs @@ -0,0 +1,41 @@ +using ShellUI.Core.Models; + +namespace ShellUI.Templates.Templates; + +public static class InputVariantsTemplate +{ + public static ComponentMetadata Metadata => new() + { + Name = "input-variants", + DisplayName = "Input Variants", + Description = "Input variant enum and utility classes", + Category = ComponentCategory.Utility, + FilePath = "Variants/InputVariants.cs", + IsAvailable = false + }; + + public static string Content => @"namespace YourProjectNamespace.Components.UI.Variants; + +public enum InputVariant +{ + Default, + Error +} + +public static class InputVariants +{ + public static string Get(InputVariant variant = InputVariant.Default, string? className = null) + { + var baseClasses = ""flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50""; + + var variantClasses = variant switch + { + InputVariant.Error => ""border-destructive focus-visible:ring-destructive"", + _ => """" + }; + + return Shell.Cn(baseClasses, variantClasses, className); + } +} +"; +} diff --git a/src/ShellUI.Templates/Templates/TextareaTemplate.cs b/src/ShellUI.Templates/Templates/TextareaTemplate.cs index 8b9bc35..3c86448 100644 --- a/src/ShellUI.Templates/Templates/TextareaTemplate.cs +++ b/src/ShellUI.Templates/Templates/TextareaTemplate.cs @@ -12,39 +12,53 @@ public static class TextareaTemplate Category = ComponentCategory.Form, FilePath = "Textarea.razor", - Dependencies = new List() + Dependencies = new List { "textarea-variants" } }; public static string Content => @"@namespace YourProjectNamespace.Components.UI +@using YourProjectNamespace.Components.UI.Variants - + @onfocus=""HandleFocus"" + @onblur=""HandleBlur""> @code { - [Parameter] - public string? Value { get; set; } - - [Parameter] - public EventCallback ValueChanged { get; set; } - - [Parameter] - public string? Placeholder { get; set; } - - [Parameter] - public bool Disabled { get; set; } - + [Parameter] public InputVariant Variant { get; set; } = InputVariant.Default; + [Parameter] public string Value { get; set; } = """"; + [Parameter] public string Placeholder { get; set; } = """"; + [Parameter] public bool Disabled { get; set; } + [Parameter] public bool ReadOnly { get; set; } + [Parameter] public string? Class { get; set; } + [Parameter] public EventCallback ValueChanged { get; set; } + [Parameter] public EventCallback OnFocus { get; set; } + [Parameter] public EventCallback OnBlur { get; set; } [Parameter(CaptureUnmatchedValues = true)] public Dictionary? AdditionalAttributes { get; set; } + private string ComputedClass => TextareaVariants.Get(Variant, Class); + private async Task HandleInput(ChangeEventArgs e) { - Value = e.Value?.ToString(); - await ValueChanged.InvokeAsync(Value); + var newValue = e.Value?.ToString() ?? """"; + Value = newValue; + await ValueChanged.InvokeAsync(newValue); + } + + private async Task HandleFocus(FocusEventArgs e) + { + await OnFocus.InvokeAsync(e); + } + + private async Task HandleBlur(FocusEventArgs e) + { + await OnBlur.InvokeAsync(e); } } "; diff --git a/src/ShellUI.Templates/Templates/TextareaVariantsTemplate.cs b/src/ShellUI.Templates/Templates/TextareaVariantsTemplate.cs new file mode 100644 index 0000000..d3d688b --- /dev/null +++ b/src/ShellUI.Templates/Templates/TextareaVariantsTemplate.cs @@ -0,0 +1,36 @@ +using ShellUI.Core.Models; + +namespace ShellUI.Templates.Templates; + +public static class TextareaVariantsTemplate +{ + public static ComponentMetadata Metadata => new() + { + Name = "textarea-variants", + DisplayName = "Textarea Variants", + Description = "Textarea utility classes (uses InputVariant)", + Category = ComponentCategory.Utility, + FilePath = "Variants/TextareaVariants.cs", + IsAvailable = false, + Dependencies = new List { "input-variants" } + }; + + public static string Content => @"namespace YourProjectNamespace.Components.UI.Variants; + +public static class TextareaVariants +{ + public static string Get(InputVariant variant = InputVariant.Default, string? className = null) + { + var baseClasses = ""flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50""; + + var variantClasses = variant switch + { + InputVariant.Error => ""border-destructive focus-visible:ring-destructive"", + _ => """" + }; + + return Shell.Cn(baseClasses, variantClasses, className); + } +} +"; +} From ccd8df1843848b12d646e48f6ac26ac17372242d Mon Sep 17 00:00:00 2001 From: Shephard Tseisi Date: Sun, 18 Oct 2026 18:02:32 +0200 Subject: [PATCH 3/4] fix: switch the Sheet and Drawer templates to compositional mode by themselves Like the package, they now use the compositional parts when Title and Description are both empty, so Compositional="true" is no longer needed. The flag still works. Part of #52 --- .../Components/Drawer.razor | 2 +- src/ShellUI.Components/Components/Sheet.razor | 2 +- .../Templates/DrawerTemplate.cs | 41 ++++++++++--------- .../Templates/SheetTemplate.cs | 11 +++-- 4 files changed, 31 insertions(+), 25 deletions(-) diff --git a/src/ShellUI.Components/Components/Drawer.razor b/src/ShellUI.Components/Components/Drawer.razor index 6c01d76..999e497 100644 --- a/src/ShellUI.Components/Components/Drawer.razor +++ b/src/ShellUI.Components/Components/Drawer.razor @@ -42,7 +42,7 @@ else if (Open) [Parameter(CaptureUnmatchedValues = true)] public Dictionary? AdditionalAttributes { get; set; } - // CLI templates need Compositional="true"; the package also switches when Title and Description are empty. + // Compositional mode also turns on when Title and Description are both empty. [Parameter] public bool Compositional { get; set; } private bool UseCompositional => Compositional || (string.IsNullOrEmpty(Title) && string.IsNullOrEmpty(Description)); diff --git a/src/ShellUI.Components/Components/Sheet.razor b/src/ShellUI.Components/Components/Sheet.razor index 5a46821..632b0c4 100644 --- a/src/ShellUI.Components/Components/Sheet.razor +++ b/src/ShellUI.Components/Components/Sheet.razor @@ -50,7 +50,7 @@ else if (Open) [Parameter(CaptureUnmatchedValues = true)] public Dictionary? AdditionalAttributes { get; set; } - // CLI templates need Compositional="true"; the package also switches when Title and Description are empty. + // Compositional mode also turns on when Title and Description are both empty. [Parameter] public bool Compositional { get; set; } private bool UseCompositional => Compositional || (string.IsNullOrEmpty(Title) && string.IsNullOrEmpty(Description)); diff --git a/src/ShellUI.Templates/Templates/DrawerTemplate.cs b/src/ShellUI.Templates/Templates/DrawerTemplate.cs index d3bab8a..94d5d4c 100644 --- a/src/ShellUI.Templates/Templates/DrawerTemplate.cs +++ b/src/ShellUI.Templates/Templates/DrawerTemplate.cs @@ -16,12 +16,11 @@ public class DrawerTemplate }; public static string Content => @"@namespace YourProjectNamespace.Components.UI -@using YourProjectNamespace.Components.UI.Variants @using Microsoft.JSInterop @implements IAsyncDisposable @inject IJSRuntime JS -@if (Compositional) +@if (UseCompositional) { @ChildContent @@ -31,22 +30,22 @@ @inject IJSRuntime JS {
-
-
- @if (!string.IsNullOrEmpty(Title)) - { -

@Title

- } - @if (!string.IsNullOrEmpty(Description)) - { -

@Description

- } +
+
+ @if (!string.IsNullOrEmpty(Title)) + { +

@Title

+ } + @if (!string.IsNullOrEmpty(Description)) + { +

@Description

+ } +
+
@ChildContent
+
+ +
-
@ChildContent
-
- -
-
} @code { @@ -57,10 +56,14 @@ @inject IJSRuntime JS [Parameter] public DrawerSide Side { get; set; } = DrawerSide.Bottom; [Parameter] public RenderFragment? ChildContent { get; set; } [Parameter] public string? Class { get; set; } - [Parameter] public bool Compositional { get; set; } [Parameter(CaptureUnmatchedValues = true)] public Dictionary? AdditionalAttributes { get; set; } + // Compositional mode also turns on when Title and Description are both empty. + [Parameter] public bool Compositional { get; set; } + + private bool UseCompositional => Compositional || (string.IsNullOrEmpty(Title) && string.IsNullOrEmpty(Description)); + private async Task Close() { Open = false; @@ -85,7 +88,7 @@ private async Task OnPanelKeyDownAsync(KeyboardEventArgs e) protected override async Task OnAfterRenderAsync(bool firstRender) { - var panelOpen = Open && !Compositional; + var panelOpen = Open && !UseCompositional; if (panelOpen && !_focused) { _focused = true; diff --git a/src/ShellUI.Templates/Templates/SheetTemplate.cs b/src/ShellUI.Templates/Templates/SheetTemplate.cs index 669030d..efeb419 100644 --- a/src/ShellUI.Templates/Templates/SheetTemplate.cs +++ b/src/ShellUI.Templates/Templates/SheetTemplate.cs @@ -16,12 +16,11 @@ public class SheetTemplate }; public static string Content => @"@namespace YourProjectNamespace.Components.UI -@using YourProjectNamespace.Components.UI.Variants @using Microsoft.JSInterop @implements IAsyncDisposable @inject IJSRuntime JS -@if (Compositional) +@if (UseCompositional) { @ChildContent @@ -65,10 +64,14 @@ @inject IJSRuntime JS [Parameter] public SheetSide Side { get; set; } = SheetSide.Right; [Parameter] public RenderFragment? ChildContent { get; set; } [Parameter] public string? Class { get; set; } - [Parameter] public bool Compositional { get; set; } [Parameter(CaptureUnmatchedValues = true)] public Dictionary? AdditionalAttributes { get; set; } + // Compositional mode also turns on when Title and Description are both empty. + [Parameter] public bool Compositional { get; set; } + + private bool UseCompositional => Compositional || (string.IsNullOrEmpty(Title) && string.IsNullOrEmpty(Description)); + private async Task Close() { Open = false; @@ -93,7 +96,7 @@ private async Task OnPanelKeyDownAsync(KeyboardEventArgs e) protected override async Task OnAfterRenderAsync(bool firstRender) { - var panelOpen = Open && !Compositional; + var panelOpen = Open && !UseCompositional; if (panelOpen && !_focused) { _focused = true; From 4708761647aa0775e50d628804521de05f00babe Mon Sep 17 00:00:00 2001 From: Shephard Tseisi Date: Sun, 18 Oct 2026 18:02:32 +0200 Subject: [PATCH 4/4] test: sync-check Stepper, Input, Textarea, Sheet and Drawer Adds them to both sync theories, parses the new stepper-models, input-variants and textarea-variants templates, and checks that the compositional Stepper shows the active step after Next. Part of #52 --- ShellUI.Tests/PackageBehaviorTests.cs | 23 +++++++++++++++++++++++ ShellUI.Tests/TemplateCompileTests.cs | 3 +++ ShellUI.Tests/TemplateSyncTests.cs | 14 ++++++++++++++ 3 files changed, 40 insertions(+) diff --git a/ShellUI.Tests/PackageBehaviorTests.cs b/ShellUI.Tests/PackageBehaviorTests.cs index 0b77dee..39dfce5 100644 --- a/ShellUI.Tests/PackageBehaviorTests.cs +++ b/ShellUI.Tests/PackageBehaviorTests.cs @@ -186,6 +186,29 @@ public void ScrollArea_SizesTheScrollingElement(string? height, string? maxHeigh Assert.Equal(expected, area.Find("div.overflow-auto").GetAttribute("style")); } + [Fact] + public void CompositionalStepper_ShowsTheActiveStepAfterNext() + { + using var ctx = new BunitContext(); + var changes = new List(); + + var stepper = ctx.Render(p => p + .Add(c => c.TotalSteps, 2) + .Add(c => c.CurrentStepChanged, EventCallback.Factory.Create(this, v => changes.Add(v))) + .AddChildContent(l => l + .AddChildContent(s => s.Add(c => c.Value, "0").Add(c => c.Title, "Account")) + .AddChildContent(s => s.Add(c => c.Value, "1").Add(c => c.Title, "Profile"))) + .AddChildContent(c => c.Add(x => x.Value, "0").AddChildContent("First")) + .AddChildContent(c => c.Add(x => x.Value, "1").AddChildContent("Second"))); + + Assert.Contains("First", stepper.Markup); + stepper.FindAll("button").Single(b => b.TextContent.Trim() == "Next").Click(); + + Assert.Equal(new[] { 1 }, changes); + Assert.Contains("Second", stepper.Markup); + Assert.DoesNotContain("First", stepper.Markup); + } + [Fact] public void TimePicker_ShowsASetValue() { diff --git a/ShellUI.Tests/TemplateCompileTests.cs b/ShellUI.Tests/TemplateCompileTests.cs index 0c09a16..362a787 100644 --- a/ShellUI.Tests/TemplateCompileTests.cs +++ b/ShellUI.Tests/TemplateCompileTests.cs @@ -16,6 +16,9 @@ public class TemplateCompileTests [InlineData("alert-variants")] [InlineData("badge-variants")] [InlineData("button-variants")] + [InlineData("stepper-models")] + [InlineData("input-variants")] + [InlineData("textarea-variants")] public void CsharpTemplate_GeneratedContentParses(string componentName) { var content = ComponentRegistry.GetComponentContent(componentName); diff --git a/ShellUI.Tests/TemplateSyncTests.cs b/ShellUI.Tests/TemplateSyncTests.cs index a687499..617a3d1 100644 --- a/ShellUI.Tests/TemplateSyncTests.cs +++ b/ShellUI.Tests/TemplateSyncTests.cs @@ -163,6 +163,13 @@ public class TemplateSyncTests [InlineData("time-picker", "TimePicker.razor")] [InlineData("date-picker", "DatePicker.razor")] [InlineData("navigation-menu-item", "NavigationMenuItem.razor")] + [InlineData("stepper", "Stepper.razor")] + [InlineData("stepper-step", "StepperStep.razor")] + [InlineData("stepper-content", "StepperContent.razor")] + [InlineData("input", "Input.razor")] + [InlineData("textarea", "Textarea.razor")] + [InlineData("sheet", "Sheet.razor")] + [InlineData("drawer", "Drawer.razor")] [InlineData("copy-button", "CopyButton.razor")] [InlineData("sidebar-provider", "SidebarProvider.razor")] [InlineData("app-sidebar", "AppSidebar.razor")] @@ -222,6 +229,13 @@ public void TemplateCodeBlock_MatchesLiveLibrary(string templateName, string raz [InlineData("time-picker", "TimePicker.razor")] [InlineData("date-picker", "DatePicker.razor")] [InlineData("navigation-menu-item", "NavigationMenuItem.razor")] + [InlineData("stepper", "Stepper.razor")] + [InlineData("stepper-step", "StepperStep.razor")] + [InlineData("stepper-content", "StepperContent.razor")] + [InlineData("input", "Input.razor")] + [InlineData("textarea", "Textarea.razor")] + [InlineData("sheet", "Sheet.razor")] + [InlineData("drawer", "Drawer.razor")] public void TemplateMarkup_MatchesLiveLibrary(string templateName, string razorFileName) { var liveContent = WithoutClassNameAlias(File.ReadAllText(GetLiveRazorPath(razorFileName)));