Files
scadaproj/components/ui-theme/shared-contract/ZB.MOM.WW.Theme.md
T
2026-06-01 05:11:43 -04:00

8.5 KiB

Shared library: ZB.MOM.WW.Theme

Status: Built (0.1.0). The RCL lives at scadaproj/ZB.MOM.WW.Theme/ — built and tested. Adoption by the three apps is follow-on, tracked in ../GAPS.md. Realizes ../spec/SPEC.md.


Package

One NuGet package — unlike ZB.MOM.WW.Auth's four-package split, there are no tokens-only or components-only consumers; all three apps consume the full kit.

Package Target Notes
ZB.MOM.WW.Theme net10.0 Razor Class Library Tokens + fonts + layout CSS + all components

Published to the Gitea NuGet feed; Version 0.1.0. SemVer — token changes are breaking (major bump). Build from scadaproj/ZB.MOM.WW.Theme/:

dotnet build -c Release   # 0 warnings (TreatWarningsAsErrors)
dotnet test               # 32 bUnit tests
./build/pack.sh           # → ./artifacts/ZB.MOM.WW.Theme.0.1.0.nupkg

Consumer matrix

All three apps consume the single RCL. No optional packages.

Consumer Surface Consumes
OtOpcUa ZB.MOM.WW.OtOpcUa.AdminUI Admin UI (Blazor SSR, side rail) ZB.MOM.WW.Theme
MxAccessGateway ZB.MOM.WW.MxGateway.Server Dashboard (Blazor SSR) ZB.MOM.WW.Theme
ScadaBridge ZB.MOM.WW.ScadaBridge.Host + ZB.MOM.WW.ScadaBridge.CentralUI Central UI (Blazor SSR) ZB.MOM.WW.Theme

Static assets

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

Path Contents
css/theme.css Design tokens, typography, Bootstrap 5 overrides (379 lines)
css/layout.css Side-rail shell layout, collapsible nav CSS, StatusPill variants, TechCard/TechField helpers
fonts/ibm-plex-sans-400.woff2 IBM Plex Sans Regular — vendored, no CDN
fonts/ibm-plex-sans-600.woff2 IBM Plex Sans SemiBold — vendored, no CDN
fonts/ibm-plex-mono-500.woff2 IBM Plex Mono Medium — vendored, no CDN

theme.css uses url('../fonts/ibm-plex-*.woff2') — the correct relative path from css/ to fonts/ in the static-web-asset tree.


Component API

Namespace: ZB.MOM.WW.Theme. All components live in this flat namespace; one @using ZB.MOM.WW.Theme in _Imports.razor covers everything.

ThemeHead

Emits <link> tags for theme.css and layout.css. No parameters.

<ThemeHead />

Place in App.razor <head> after the app's Bootstrap link.


ThemeShell

Canonical side-rail chassis. Not a LayoutComponentBase — delegated to from the app's thin MainLayout. The Accent parameter overrides --accent for the shell subtree.

Parameter Type Required Default Notes
Product string Yes Product name rendered in BrandBar
Accent string? No null Override --accent for this app (e.g. #2f855a)
Logo RenderFragment? No null Custom logo; replaces default glyph
Nav RenderFragment? No null Rail nav items (NavRailSection / NavRailItem)
RailFooter RenderFragment? No null Session block / sign-out at rail bottom
ChildContent RenderFragment? No null Page body (@Body from MainLayout)

Adoption pattern — the thin MainLayout:

@* Components/Layout/MainLayout.razor — replaces the app's existing MainLayout *@
@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>
        @* AuthorizeView session block / sign-out link *@
    </RailFooter>
    <ChildContent>@Body</ChildContent>
</ThemeShell>

BrandBar

Brand glyph + product name. Rendered inside ThemeShell's rail header; also usable standalone.

Parameter Type Required Notes
Product string Yes Displayed product name
Logo RenderFragment? No Replaces default glyph when provided

NavRailItem

One rail navigation link. Wraps Blazor <NavLink class="rail-link">.

Parameter Type Required Default Notes
Href string Yes Link target
Text string Yes Label text
Icon RenderFragment? No null Optional icon span
Match NavLinkMatch No Prefix Active-class matching behavior

NavRailSection

Collapsible nav section group using CSS-only <details open> — no JavaScript, works in static Blazor SSR. Apps that need interactive cookie-persisted expand state may keep a bespoke interactive NavSection alongside this.

Parameter Type Required Default Notes
Title string Yes Eyebrow label
Expanded bool No true Initial open state
ChildContent RenderFragment? No null NavRailItem children

StatusPill

Inline status chip. Maps StatusState to a token-based chip class.

Parameter Type Required Notes
State StatusState Yes Ok, Warn, Bad, Idle, Info
ChildContent RenderFragment? No Label text
public enum StatusState { Ok, Warn, Bad, Idle, Info }

CSS classes emitted: chip chip-ok / chip-warn / chip-bad / chip-idle / chip-info.


LoginCard

Static form-POST sign-in card. Login must use a static form POST — SignInAsync must run before the HTTP response starts; an interactive EventCallback fires too late.

Parameter Type Required Default Notes
Product string Yes Product name in the card heading
Action string No /auth/login Form action attribute
ReturnUrl string? No null Rendered as <input type="hidden" name="returnUrl">
Error string? No null Displayed as an error notice above the submit button
ChildContent RenderFragment? No null For <AntiforgeryToken/>

Required: inject <AntiforgeryToken/> via ChildContent and validate ReturnUrl server-side before redirecting (open-redirect risk).

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

TechButton

Themed button wrapping Bootstrap .btn classes.

Parameter Type Required Default Notes
Variant ButtonVariant No Primary Primary, Secondary, Danger, Ghost
Type string No "button" HTML type attribute
Busy bool No false Disables button + shows spinner
ChildContent RenderFragment? No null Button label
(splatted) IDictionary<string,object>? No Passes through arbitrary HTML attributes
public enum ButtonVariant { Primary, Secondary, Danger, Ghost }

TechCard

Panel with optional header, body, and footer slots.

Parameter Type Required Notes
Title string? No String title for the panel header (alternative to Header slot)
Header RenderFragment? No Custom panel header (takes precedence over Title)
ChildContent RenderFragment? No Panel body content
Footer RenderFragment? No Panel footer (padded, top-bordered)
Class string? No Additional CSS classes on the root <section class="panel">

TechField

Labeled form-field wrapper: label, input slot, hint text, and inline error.

Parameter Type Required Notes
Label string Yes <label> text
Hint string? No Rendered as .form-text below the input
Error string? No Rendered as .field-error.s-bad below the input
ChildContent RenderFragment? No The <input>, <select>, or other control

Notes

  • Bootstrap is not vendored. Each app keeps its own Bootstrap <link>. The RCL's theme.css overrides --bs-* tokens to align Bootstrap with Technical-Light but does not ship Bootstrap itself.
  • No global JavaScript. NavRailSection is CSS-only (<details>). Apps may add their own nav-state.js for interactive expand-state if needed (OtOpcUa has one).
  • No auth logic. The RCL is UI-only. Wire LoginCard to ZB.MOM.WW.Auth endpoints in the app.
  • No data grids, modals, or domain-specific components. These stay per-project.