From b09de9b7778a7b5f6e3c475486ea873d71b35deb Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Mon, 1 Jun 2026 04:53:52 -0400 Subject: [PATCH] feat(theme): ThemeShell canonical side-rail Add ThemeShell.razor (regular component, not LayoutComponentBase) with Product, Accent, Logo, Nav, RailFooter, and ChildContent parameters. Accent uses nullable AccentStyle so the style attribute is entirely absent when null. Composes BrandBar inside .side-rail, wraps page in
. Add ThemeShellTests.cs (4 tests: product/nav/body, accent sets css var, no-accent emits no style, RailFooter). All 18 tests green, 0 build warnings. --- .../Components/ThemeShell.razor | 32 ++++++++++++++ .../ZB.MOM.WW.Theme.Tests/ThemeShellTests.cs | 43 +++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 ZB.MOM.WW.Theme/src/ZB.MOM.WW.Theme/Components/ThemeShell.razor create mode 100644 ZB.MOM.WW.Theme/tests/ZB.MOM.WW.Theme.Tests/ThemeShellTests.cs diff --git a/ZB.MOM.WW.Theme/src/ZB.MOM.WW.Theme/Components/ThemeShell.razor b/ZB.MOM.WW.Theme/src/ZB.MOM.WW.Theme/Components/ThemeShell.razor new file mode 100644 index 0000000..0e4977b --- /dev/null +++ b/ZB.MOM.WW.Theme/src/ZB.MOM.WW.Theme/Components/ThemeShell.razor @@ -0,0 +1,32 @@ +@* Components/ThemeShell.razor — the one canonical side-rail chassis. + Not a LayoutComponentBase: the app's thin MainLayout delegates to this. *@ +@namespace ZB.MOM.WW.Theme +
+ +
+ +
+
@ChildContent
+
+ +@code { + [Parameter, EditorRequired] public string Product { get; set; } = string.Empty; + [Parameter] public string? Accent { get; set; } + [Parameter] public RenderFragment? Logo { get; set; } + [Parameter] public RenderFragment? Nav { get; set; } + [Parameter] public RenderFragment? RailFooter { get; set; } + [Parameter] public RenderFragment? ChildContent { get; set; } + + private string? AccentStyle => Accent is null ? null : $"--accent: {Accent}"; +} diff --git a/ZB.MOM.WW.Theme/tests/ZB.MOM.WW.Theme.Tests/ThemeShellTests.cs b/ZB.MOM.WW.Theme/tests/ZB.MOM.WW.Theme.Tests/ThemeShellTests.cs new file mode 100644 index 0000000..cb8c6c8 --- /dev/null +++ b/ZB.MOM.WW.Theme/tests/ZB.MOM.WW.Theme.Tests/ThemeShellTests.cs @@ -0,0 +1,43 @@ +namespace ZB.MOM.WW.Theme.Tests; + +public class ThemeShellTests : TestContext +{ + [Fact] + public void Renders_product_nav_and_body() + { + var cut = RenderComponent(p => p + .Add(x => x.Product, "OtOpcUa") + .Add(x => x.Nav, (RenderFragment)(b => b.AddMarkupContent(0, "N"))) + .AddChildContent("
BODY
")); + Assert.NotNull(cut.Find(".side-rail .brand")); + Assert.Contains("OtOpcUa", cut.Markup); + Assert.NotNull(cut.Find(".side-rail .rail-link")); + Assert.NotNull(cut.Find("main.page .pagebody")); + } + + [Fact] + public void Accent_sets_css_variable_on_shell_root() + { + var cut = RenderComponent(p => p + .Add(x => x.Product, "ScadaBridge") + .Add(x => x.Accent, "#2f855a")); + var shell = cut.Find(".app-shell"); + Assert.Contains("--accent: #2f855a", shell.GetAttribute("style")); + } + + [Fact] + public void No_accent_emits_no_style() + { + var cut = RenderComponent(p => p.Add(x => x.Product, "MXAccess Gateway")); + Assert.False(cut.Find(".app-shell").HasAttribute("style")); + } + + [Fact] + public void RailFooter_renders_when_supplied() + { + var cut = RenderComponent(p => p + .Add(x => x.Product, "OtOpcUa") + .Add(x => x.RailFooter, (RenderFragment)(b => b.AddMarkupContent(0, "S")))); + Assert.NotNull(cut.Find(".rail-foot .sess")); + } +}