Files
scadaproj/ZB.MOM.WW.Theme
Joseph Doherty 5fc75d93fd feat(theme): rail-btn-block full-width rail-footer modifier (0.4.0)
HistorianGateway hand-rolled a .rail-btn override purely to make the rail-foot
sign-out button span the rail; its own comment flagged folding it into the kit.
Adds a composable modifier instead: class="rail-btn rail-btn-block".

Uses display:block + width:auto, NOT width:100%. The hand-rolled overrides set
width:100%, which resolves against the containing block and then adds .rail-btn's
0 0.6rem side margins on top, overflowing the rail by 1.2rem. width:auto fills
the same inset correctly.

Scope note: the app overrides that motivated this do more than full-width (they
also restyle font-size, padding, border, background and hover). Adopting the
modifier removes the width hack; whether to also drop the restyle is each app's
call, since it changes their rail appearance.

Version 0.3.1 -> 0.4.0 (additive). Build 0 errors, 48/48 tests, nupkg verified
to carry the new rule in staticwebassets/css/layout.css. Not yet published.
2026-08-11 02:13:37 -04:00
..

ZB.MOM.WW.Theme

Shared Technical-Light UI kit for the ZB.MOM.WW SCADA family: design tokens + IBM Plex fonts (static web assets) and a canonical side-rail shell + widgets. The kit ships one .NET 10 Razor Class Library (RCL) with CSS custom-property tokens, the three IBM Plex woff2 fonts, and side-rail layout CSS — all served from _content/ZB.MOM.WW.Theme/… — plus a set of Blazor SSR components that carry no inline colours and reuse the token classes. Bootstrap 5 is not vendored; each app keeps its own Bootstrap link.

The kit ships no JavaScript: every interactive affordance — the narrow-viewport side-rail hamburger and the collapsible nav sections — is a native CSS-only <details>/<summary> disclosure, so the chrome works unchanged in static Blazor SSR with only Bootstrap CSS present (no Bootstrap collapse JS bundle required).

Adopt

  1. Reference the NuGet package in your app; keep your own Bootstrap 5 <link> in App.razor.

  2. In App.razor <head>, after your Bootstrap link, add <ThemeHead />:

    <link rel="stylesheet" href="..." />  @* Bootstrap — yours, not vendored by the kit *@
    <ThemeHead />
    
  3. Make your MainLayout a thin delegate to ThemeShell — @layout cannot pass parameters, so ThemeShell is a regular component and MainLayout is a 3-line wrapper:

    @* Layouts/MainLayout.razor *@
    @inherits LayoutComponentBase
    <ThemeShell Product="OtOpcUa" Accent="#2f5fd0">
        <Nav>
            <NavRailSection Title="Navigation">
                <NavRailItem Href="/" Text="Overview" Match="NavLinkMatch.All" />
                <NavRailItem Href="/clusters" Text="Clusters" />
            </NavRailSection>
        </Nav>
        <RailFooter>@* session info / sign-out link *@</RailFooter>
        <ChildContent>@Body</ChildContent>
    </ThemeShell>
    

Add @using ZB.MOM.WW.Theme to your _Imports.razor so all components are available without per-file usings.

Login page

Use <LoginCard> for the sign-in form. The card posts to a server endpoint (/auth/login by default) so SignInAsync can run before the response starts. You must inject <AntiforgeryToken/> inside the card and validate ReturnUrl server-side before redirecting (open-redirect risk):

<LoginCard Product="OtOpcUa" Action="/auth/login" ReturnUrl="@safeReturnUrl" Error="@errorMsg">
    <AntiforgeryToken />
</LoginCard>

Components

Component Parameters (key) Notes
ThemeHead — Emits <link> tags for theme.css and layout.css. Place in <head> after Bootstrap.
ThemeShell Product*, Accent, Logo, Nav, RailFooter, ChildContent Side-rail chassis. Not a layout — delegated to from MainLayout. Accent overrides --accent token.
BrandBar Product*, Logo Brand glyph + product name; rendered inside ThemeShell's rail header.
NavRailItem Href, Text, Icon, Match Wraps <NavLink class="rail-link">.
NavRailSection Title*, Expanded (default true), ChildContent CSS-only collapsible <details> group; no JS, works in static SSR.
StatusPill (StatusState) State* (Ok/Warn/Bad/Idle/Info), ChildContent Inline chip. StatusState enum is in ZB.MOM.WW.Theme.
LoginCard Product*, Action (default /auth/login), ReturnUrl, Error, ChildContent Static form-POST sign-in card. Inject <AntiforgeryToken/> via ChildContent; validate ReturnUrl server-side.
TechButton (ButtonVariant) Variant (Primary/Secondary/Danger/Ghost), Type, Busy, ChildContent, splatted attrs Busy disables the button and shows a spinner. ButtonVariant enum is in ZB.MOM.WW.Theme.
TechCard Title, Header, ChildContent, Footer, Class Panel with optional head/body/footer slots.
TechField Label*, Hint, Error, ChildContent Form field wrapper with label, hint text, and inline error.

* EditorRequired parameter.

Flat namespace: all components and enums live in ZB.MOM.WW.Theme. One @using covers everything.

Static assets

Served at _content/ZB.MOM.WW.Theme/… by the ASP.NET static-web-asset pipeline:

Path Contents
css/theme.css Design tokens (--accent, --ok, --warn, …), typography, utility helpers
css/layout.css Side-rail shell layout, collapsible nav, StatusPill variants, card/field helpers

Rail-footer actions: class="rail-btn" is the inline default; add rail-btn-block (class="rail-btn rail-btn-block") for a full-width footer button. Use the modifier rather than redeclaring .rail-btn in an app stylesheet — a local override that sets width: 100% overflows the rail, because the width resolves against the containing block and the inline version's 0 0.6rem side margins are then added on top.

| fonts/ibm-plex-sans-400.woff2 | IBM Plex Sans Regular | | fonts/ibm-plex-sans-600.woff2 | IBM Plex Sans SemiBold | | fonts/ibm-plex-mono-500.woff2 | IBM Plex Mono Medium |

theme.css declares @font-face with url('../fonts/…') — correct relative path from css/ to fonts/. (OtOpcUa's original url('fonts/…') was a latent 404; the kit fixes it.)

All colours are CSS custom-property tokens declared once in theme.css's :root block — no hardcoded hex appears outside it. Derived border/hover/wash shades (e.g. --ok-border, --warn-ink, --info-bg, --hover-bg, --active-bg, --zebra-bg) are named tokens too, so a token-block edit re-themes the whole kit.

Build

# from ZB.MOM.WW.Theme/
dotnet build -c Release   # TreatWarningsAsErrors — expect 0 warnings
dotnet test               # 38 bUnit tests
./build/pack.sh           # → ./artifacts/ZB.MOM.WW.Theme.0.1.0.nupkg