Files
scadaproj/ZB.MOM.WW.Theme/README.md
T

181 lines
11 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`, `ButtonSize`) | `Variant` (`Primary`/`Secondary`/`Danger`/`Ghost`), `Size` (`Default`/`Small`/`Large`), `Class`, `Type`, `Busy`, `ChildContent`, splatted attrs | `Busy` disables the button and shows a spinner. Caller classes are composed with required variant/size classes. |
| `TreeTable<TRow>` (`ITreeRow`) | `Rows`*, `Head`*, `Row`*, `Label`*, `Collapsed`, `OnToggle` | Stateless preorder tree table. The caller filters visible rows and owns expansion state. |
| `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, shared page/group headers, badges, row actions, info notices, payload blocks and tree-table styles |
**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`. Several consuming apps had written this same block into their own
stylesheets; **trim the local copy when you bump to 0.4.0** — trim rather than delete, because a
local `.btn` may also carry app-specific *shape* declarations the kit does not ship.
**This is not universal, and checking it takes two greps, not one.** An earlier version of this note
claimed all five consumers carried the block. They do not — and the follow-up correction, that the
missing-block app would therefore be *restyled* by the bump, was also wrong.
| Question | Check |
|---|---|
| Does the app declare its own `.btn` sizing? | `grep bs-btn-font-size`, excluding `lib/bootstrap` |
| Does the app **use** Bootstrap buttons at all? | count `class="…"` values carrying a bare `btn` token |
HistorianGateway answers **no to both** — all 15 of its buttons use the kit's own `.rail-btn` chassis
class — so the kit's `.btn` rule matches nothing there and bumping it changes nothing visible. The
first question alone is misleading in both directions: "no local block" does **not** imply "the kit
now supplies sizing the app was missing."
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 # 60 tests verified for 0.5.0
./build/pack.sh # → ./artifacts/ZB.MOM.WW.Theme.0.5.0.nupkg
```
## 0.5.0 migration (published 2026-11-04)
The tested package was published to the shared Gitea feed on 2026-11-04.
An independent feed download exactly matched the reviewed artifact (SHA256
`374511ebaaa15be71024051e896c8e7e59a8951b0d51a990fa48fbe378ac6485`).
The ignitionoee router deployed this package to CR on 2026-11-04; other consumer
rollouts remain separate. It retains the 0.4.2 `.rise` animation
fill-mode fix. The published package is immutable; this source documentation
update does not repack or replace it.
The shell now keeps page content visible when mobile navigation is collapsed.
The outer disclosure's `::details-content` wrapper is dissolved at every width;
an explicit mobile closed-state selector hides only the rail. Author display
rules retain the page for older browsers without `::details-content` support.
The menu remains a native keyboard-accessible disclosure without custom JavaScript.
Mobile pages are constrained to the shell width so wide tables scroll inside their
table wrapper instead of widening the whole document.
The remaining ignitionoee `app.css` rules now ship in `layout.css`: page and section
headers/meta, group headings/meta, count badges, `.notice.info`, `.row-actions`,
**`td.row-actions`**, and `.check-data`. Consumers may delete matching local copies
after adopting this package. The element-qualified row-action selector must remain:
the bare class loses to `.data-table td` regardless of stylesheet load order.
Use `Size="ButtonSize.Small"` or `Size="ButtonSize.Large"` on `TechButton`.
`Class="my-action"` and an attribute dictionary's `class` value are composed with
the required button classes. A lowercase `class` supplied in Razor binds to `Class`
as normal Blazor parameter binding. Supplying both spellings as separate component
parameters is unnecessary; use `Class` plus `AdditionalAttributes` when merging two
sources. Explicit `Busy` and `Type` continue to control the underlying attributes.
Implement `ZB.MOM.WW.Theme.ITreeRow` on a consumer's row model (`Key`, `Depth`,
`HasChildren`). Feed `TreeTable<TRow>` visible rows in preorder, with each subtree
contiguous after its parent. `Head` supplies all header cells; `Row` supplies cells
after the component's name cell; `Label` supplies the name. `OnToggle` reports the
stable row key, and the caller supplies a new `Collapsed` state and filters `Rows`.
The component never filters or mutates caller state. Indentation supports arbitrary
depth through `--tt-depth`; styles ship once, with no per-instance stylesheet.
Leaf controls preserve label alignment and omit subtree ARIA attributes. Interactive
expand/collapse requires an interactive Blazor host and Bootstrap Icons for chevrons.
The test harness uses bUnit 2.7.2 and directly pins AngleSharp 1.5.0. This avoids
the vulnerable parser minimum ([GHSA-pgww-w46g-26qg](https://github.com/advisories/GHSA-pgww-w46g-26qg))
and the binary incompatibility between bUnit 1.x and the patched parser. These
dependencies belong to the test project; the Theme runtime package gains none.