feat(centralui): adopt Theme 0.5.3 ScrollNav rail scroll pane (#61)

Bump ZB.MOM.WW.Theme 0.5.0 -> 0.5.3, set ThemeShell ScrollNav=true, drop
NavMenu's own .rail-scroll wrapper and the site.css rail scroll block
(keeps .rail-updates*). NavMenu test now asserts no local wrapper.

Claude-Session: https://claude.ai/code/session_01GT67ACmpMR7b9LGRPLijMi
This commit is contained in:
Joseph Doherty
2026-11-12 11:27:01 -05:00
parent 94ae186050
commit 25015442e5
5 changed files with 10 additions and 38 deletions
+1 -1
View File
@@ -114,7 +114,7 @@
<PackageVersion Include="ZB.MOM.WW.Auth.ApiKeys.SqlServer" Version="0.4.1" />
<PackageVersion Include="ZB.MOM.WW.Auth.AspNetCore" Version="0.4.1" />
<PackageVersion Include="ZB.MOM.WW.Audit" Version="0.1.0" />
<PackageVersion Include="ZB.MOM.WW.Theme" Version="0.5.0" />
<PackageVersion Include="ZB.MOM.WW.Theme" Version="0.5.3" />
<PackageVersion Include="ZB.MOM.WW.Secrets" Version="0.6.2" />
<PackageVersion Include="ZB.MOM.WW.Secrets.Abstractions" Version="0.6.2" />
<PackageVersion Include="ZB.MOM.WW.Secrets.Ui" Version="0.6.2" />
@@ -8,7 +8,7 @@
style="--accent: …" on the shell root, which outranks the dark-mode token
override on <html> and silently killed the dark accent app-wide. The accent
is declared as a :root token in site.css instead. *@
<ThemeShell Product="ScadaBridge">
<ThemeShell Product="ScadaBridge" ScrollNav="true">
<Nav>
<NavMenu />
</Nav>
@@ -6,11 +6,9 @@
contributes only the nav items. Collapsible sections use the kit's
NavRailSection (<details>); their open/closed state is persisted client-side
by the kit's nav-state.js (localStorage, keyed by Key) — no JS interop here.
Everything below is wrapped in .rail-scroll, the scroll pane defined in
site.css: a long nav scrolls under the brand bar while the rail footer
stays pinned, and the kit itself is untouched. *@
The scroll pane (long nav scrolls, footer pinned) is provided by the kit's
ThemeShell ScrollNav="true" (Theme 0.5.3). *@
<div class="rail-scroll">
<NavRailItem Href="/" Text="Dashboard" Match="NavLinkMatch.All" />
<AuthorizeView>
@@ -157,4 +155,3 @@
</AuthorizeView>
</Authorized>
</AuthorizeView>
</div>
@@ -171,27 +171,6 @@
--sb-backdrop: rgba(0, 0, 0, 0.6);
}
/* ── Rail nav scroll pane ────────────────────────────────────────────────
ThemeShell's nav.side-rail is a flex column inside the sticky 100vh
#theme-rail (lg+), but it never fills that height and nothing inside it
scrolls, so a long nav grows past the viewport and carries the footer
with it. Fill the rail and let the nav items (NavMenu's .rail-scroll
wrapper) scroll while the brand bar and .rail-foot stay put. Below lg the
rail is inline and scrolls with the page, so the rule is scoped to lg+.
The kit is untouched (deferred-work register: upstream into
ZB.MOM.WW.Theme so the other consumers get it too). */
@media (min-width: 992px) {
#theme-rail > .side-rail {
height: 100%;
}
.rail-scroll {
flex: 1 1 auto;
min-height: 0;
overflow-y: auto;
overscroll-behavior: contain;
}
}
/* ── Rail pending-updates indicator ──────────────────────────────────────
Sits in .rail-foot just above .rail-user. One line when quiet, two when
something is pending; tokens only so dark mode follows for free. */
@@ -254,23 +254,19 @@ public class NavMenuTests : BunitContext
}
/// <summary>
/// ThemeShell's <c>nav.side-rail</c> never fills the sticky 100vh rail, so
/// a long expanded nav grows past the viewport and pushes
/// <c>.rail-foot</c> off-screen. <c>NavMenu</c> wraps its items in a
/// <c>div.rail-scroll</c> pane that <c>site.css</c> makes scroll on its
/// own (lg+), leaving the brand bar and rail footer pinned. This asserts
/// the wrapper exists and actually contains the nav sections, not just
/// that the class string appears somewhere in the markup.
/// The rail scroll pane is owned by the kit (ThemeShell ScrollNav="true",
/// Theme 0.5.3). NavMenu must not add its own <c>.rail-scroll</c> wrapper,
/// or the rail gets two nested scroll panes.
/// </summary>
[Fact]
public void NavItems_RenderInsideTheRailScrollPane()
public void NavItems_DoNotCarryTheirOwnRailScrollWrapper()
{
var cut = RenderWithRoles("Administrator", "Designer", "Deployer");
cut.WaitForAssertion(() =>
{
Assert.Contains("class=\"rail-scroll\"", cut.Markup);
Assert.NotEmpty(cut.FindAll("div.rail-scroll details.rail-section"));
Assert.NotEmpty(cut.FindAll("details.rail-section"));
Assert.DoesNotContain("rail-scroll", cut.Markup);
});
}
}