Files
scadaproj/ZB.MOM.WW.Theme/README.md
T
Joseph Doherty e7a126a9fc feat(theme): upstream the Bootstrap .btn sizing block into the kit (0.4.0)
Bootstrap hard-codes --bs-btn-font-size: 1rem, oversized against the kit's
0.9rem body. All five consuming apps had independently written the identical
block into their own stylesheets (ignitionoee app.css, OtOpcUa site.css, mxgw
site.css, HistorianGateway site.css, ScadaBridge site.css), each carrying a
'delete this when the kit ships a .btn rule' comment. This is that release.

Sized by CSS-variable override only, never by redeclaring box properties:
Bootstrap's .btn-group seam/radius machinery reads the same variables, so a
literal font-size at equal specificity flattens .btn-sm into a padding-only
difference. That was a real defect in mxgw's local copy, so the rationale is
recorded in the CSS to stop it being 'simplified' back.

Lands in layout.css, which ThemeHead emits after theme.css and both after
Bootstrap, so the override wins on source order at equal specificity.

Ships in the same 0.4.0 release as rail-btn-block. Consumers get the sizing on
bump (not opt-in, unlike rail-btn-block) and should delete their local copies
in the same change. Build 0 errors, 48/48 tests, packed asset verified to carry
.btn, .btn-sm/.btn-group-sm and rail-btn-block with no literal font-size.
2026-08-11 02:17:16 -04:00

119 lines
6.3 KiB
Markdown

# 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 />`:
```razor
<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:
```razor
@* 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):
```razor
<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, Bootstrap `.btn` sizing |
**Bootstrap button sizing (new in 0.4.0).** The kit now normalizes `.btn` / `.btn-sm` against its
0.9rem body via `--bs-btn-*` variable overrides, because Bootstrap hard-codes
`--bs-btn-font-size: 1rem`. All five consuming apps had written this same block into their own
stylesheets; **delete the local copy when you bump to 0.4.0** (each carries a comment saying so).
Size buttons by overriding the `--bs-btn-*` variables, never by redeclaring box properties — the
`.btn-group` seam and radius machinery reads those variables, so a literal `font-size` at equal
specificity silently reduces `.btn-sm` to a padding-only difference.
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
```bash
# 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
```