3777e50564
HistorianGateway has no local .btn sizing in either its committed tree or its sweep branch (only vendored bootstrap.min.css), so the shipped comment and README naming all five consumers were wrong. Consequence worth stating: HG's bump to 0.4.0+ is a VISIBLE change (buttons shrink to .85rem), not the no-op cleanup it is for consumers that already carried the block.
125 lines
6.8 KiB
Markdown
125 lines
6.8 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`. 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 — check before assuming your app already had it.** HistorianGateway has no
|
|
local `.btn` sizing at all, so for it the bump to 0.4.0+ is a **visible change** (buttons shrink from
|
|
Bootstrap's 1rem to `.85rem`) rather than a no-op cleanup. An earlier version of this note claimed
|
|
all five consumers carried the block; that was wrong.
|
|
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
|
|
```
|