# Shared library: `ZB.MOM.WW.Theme` **Status: Built (`0.1.0`).** The RCL lives at [`scadaproj/ZB.MOM.WW.Theme/`](../../../ZB.MOM.WW.Theme/) — built and tested. Adoption by the three apps is follow-on, tracked in [`../GAPS.md`](../GAPS.md). Realizes [`../spec/SPEC.md`](../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/`: ```bash 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 `` tags for `theme.css` and `layout.css`. No parameters. ```razor ``` Place in `App.razor` `` **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`: ```razor @* Components/Layout/MainLayout.razor — replaces the app's existing MainLayout *@ @inherits LayoutComponentBase @* AuthorizeView session block / sign-out link *@ @Body ``` --- ### `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 ``. | 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 `
` — 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 | ```csharp 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 `` | | `Error` | `string?` | No | `null` | Displayed as an error notice above the submit button | | `ChildContent` | `RenderFragment?` | No | `null` | For `` | **Required:** inject `` via `ChildContent` and **validate `ReturnUrl` server-side** before redirecting (open-redirect risk). ```razor ``` --- ### `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?` | No | — | Passes through arbitrary HTML attributes | ```csharp 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 `
` | --- ### `TechField` Labeled form-field wrapper: label, input slot, hint text, and inline error. | Parameter | Type | Required | Notes | |---|---|---|---| | `Label` | `string` | Yes | `