e7a126a9fc
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.
119 lines
6.3 KiB
Markdown
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
|
|
```
|