From 40f6962d05bb5b5583dac03b007eb6f066134bbb Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Mon, 1 Jun 2026 04:56:06 -0400 Subject: [PATCH] feat(theme): TechButton/TechCard/TechField --- .../src/ZB.MOM.WW.Theme/ButtonVariant.cs | 3 ++ .../Components/TechButton.razor | 22 ++++++++ .../ZB.MOM.WW.Theme/Components/TechCard.razor | 16 ++++++ .../Components/TechField.razor | 15 ++++++ .../CommonControlsTests.cs | 52 +++++++++++++++++++ 5 files changed, 108 insertions(+) create mode 100644 ZB.MOM.WW.Theme/src/ZB.MOM.WW.Theme/ButtonVariant.cs create mode 100644 ZB.MOM.WW.Theme/src/ZB.MOM.WW.Theme/Components/TechButton.razor create mode 100644 ZB.MOM.WW.Theme/src/ZB.MOM.WW.Theme/Components/TechCard.razor create mode 100644 ZB.MOM.WW.Theme/src/ZB.MOM.WW.Theme/Components/TechField.razor create mode 100644 ZB.MOM.WW.Theme/tests/ZB.MOM.WW.Theme.Tests/CommonControlsTests.cs diff --git a/ZB.MOM.WW.Theme/src/ZB.MOM.WW.Theme/ButtonVariant.cs b/ZB.MOM.WW.Theme/src/ZB.MOM.WW.Theme/ButtonVariant.cs new file mode 100644 index 0000000..37d0146 --- /dev/null +++ b/ZB.MOM.WW.Theme/src/ZB.MOM.WW.Theme/ButtonVariant.cs @@ -0,0 +1,3 @@ +namespace ZB.MOM.WW.Theme; + +public enum ButtonVariant { Primary, Secondary, Danger, Ghost } diff --git a/ZB.MOM.WW.Theme/src/ZB.MOM.WW.Theme/Components/TechButton.razor b/ZB.MOM.WW.Theme/src/ZB.MOM.WW.Theme/Components/TechButton.razor new file mode 100644 index 0000000..c97acb7 --- /dev/null +++ b/ZB.MOM.WW.Theme/src/ZB.MOM.WW.Theme/Components/TechButton.razor @@ -0,0 +1,22 @@ +@namespace ZB.MOM.WW.Theme +@* Components/TechButton.razor *@ + + +@code { + [Parameter] public ButtonVariant Variant { get; set; } = ButtonVariant.Primary; + [Parameter] public string Type { get; set; } = "button"; + [Parameter] public bool Busy { get; set; } + [Parameter] public RenderFragment? ChildContent { get; set; } + [Parameter(CaptureUnmatchedValues = true)] public IDictionary? Extra { get; set; } + + private string VariantClass => Variant switch + { + ButtonVariant.Secondary => "btn-outline-secondary", + ButtonVariant.Danger => "btn-danger", + ButtonVariant.Ghost => "btn-link", + _ => "btn-primary", + }; +} diff --git a/ZB.MOM.WW.Theme/src/ZB.MOM.WW.Theme/Components/TechCard.razor b/ZB.MOM.WW.Theme/src/ZB.MOM.WW.Theme/Components/TechCard.razor new file mode 100644 index 0000000..8277737 --- /dev/null +++ b/ZB.MOM.WW.Theme/src/ZB.MOM.WW.Theme/Components/TechCard.razor @@ -0,0 +1,16 @@ +@namespace ZB.MOM.WW.Theme +@* Components/TechCard.razor *@ +
+ @if (Header is not null) {
@Header
} + else if (!string.IsNullOrEmpty(Title)) {
@Title
} +
@ChildContent
+ @if (Footer is not null) {
@Footer
} +
+ +@code { + [Parameter] public string? Title { get; set; } + [Parameter] public RenderFragment? Header { get; set; } + [Parameter] public RenderFragment? ChildContent { get; set; } + [Parameter] public RenderFragment? Footer { get; set; } + [Parameter] public string? Class { get; set; } +} diff --git a/ZB.MOM.WW.Theme/src/ZB.MOM.WW.Theme/Components/TechField.razor b/ZB.MOM.WW.Theme/src/ZB.MOM.WW.Theme/Components/TechField.razor new file mode 100644 index 0000000..0ba0d55 --- /dev/null +++ b/ZB.MOM.WW.Theme/src/ZB.MOM.WW.Theme/Components/TechField.razor @@ -0,0 +1,15 @@ +@namespace ZB.MOM.WW.Theme +@* Components/TechField.razor *@ +
+ + @ChildContent + @if (!string.IsNullOrEmpty(Hint)) {
@Hint
} + @if (!string.IsNullOrEmpty(Error)) {
@Error
} +
+ +@code { + [Parameter, EditorRequired] public string Label { get; set; } = string.Empty; + [Parameter] public string? Hint { get; set; } + [Parameter] public string? Error { get; set; } + [Parameter] public RenderFragment? ChildContent { get; set; } +} diff --git a/ZB.MOM.WW.Theme/tests/ZB.MOM.WW.Theme.Tests/CommonControlsTests.cs b/ZB.MOM.WW.Theme/tests/ZB.MOM.WW.Theme.Tests/CommonControlsTests.cs new file mode 100644 index 0000000..1dac347 --- /dev/null +++ b/ZB.MOM.WW.Theme/tests/ZB.MOM.WW.Theme.Tests/CommonControlsTests.cs @@ -0,0 +1,52 @@ +namespace ZB.MOM.WW.Theme.Tests; + +public class CommonControlsTests : TestContext +{ + [Theory] + [InlineData(ButtonVariant.Primary, "btn-primary")] + [InlineData(ButtonVariant.Secondary, "btn-outline-secondary")] + [InlineData(ButtonVariant.Danger, "btn-danger")] + [InlineData(ButtonVariant.Ghost, "btn-link")] + public void TechButton_maps_variant(ButtonVariant v, string cls) + { + var cut = RenderComponent(p => p.Add(x => x.Variant, v).AddChildContent("Go")); + var btn = cut.Find("button"); + Assert.Contains("btn", btn.ClassList); + Assert.Contains(cls, btn.ClassList); + } + + [Fact] + public void TechButton_busy_disables_and_passes_through_attributes() + { + var cut = RenderComponent(p => p + .Add(x => x.Busy, true) + .AddUnmatched("id", "save") + .AddChildContent("Save")); + var btn = cut.Find("button"); + Assert.True(btn.HasAttribute("disabled")); + Assert.Equal("save", btn.GetAttribute("id")); + } + + [Fact] + public void TechCard_renders_title_and_body() + { + var cut = RenderComponent(p => p + .Add(x => x.Title, "Drivers") + .AddChildContent("
x
")); + Assert.Contains("Drivers", cut.Find(".panel-head").TextContent); + Assert.NotNull(cut.Find(".panel-body .b")); + } + + [Fact] + public void TechField_renders_label_hint_error() + { + var cut = RenderComponent(p => p + .Add(x => x.Label, "Name") + .Add(x => x.Hint, "required") + .Add(x => x.Error, "missing") + .AddChildContent("")); + Assert.Contains("Name", cut.Find("label").TextContent); + Assert.Contains("required", cut.Find(".form-text").TextContent); + Assert.Contains("missing", cut.Find(".field-error").TextContent); + } +}