8.3 KiB
UI Theme — current state: ScadaBridge
Repo: ~/Desktop/ScadaBridge. Stack: .NET 10, Blazor SSR (Akka.NET cluster + central UI).
UI surfaces: src/ZB.MOM.WW.ScadaBridge.CentralUI/ (RCL) and
src/ZB.MOM.WW.ScadaBridge.Host/ (the Blazor host that references it).
All paths below are relative to the repo root. Verified against source on 2026-06-01.
Summary: ScadaBridge uses a sidebar nav layout and the Technical-Light tokens, with the
correct font-path prefix. The sidebar uses .sidebar / .nav-link classes (same idiom as
MxGateway), not .side-rail / .rail-link. Adoption is medium effort — sidebar-class
migration + MainLayout replacement, no layout redesign. ScadaBridge has several
scoped .razor.css files that stay per-project.
1. CSS / design tokens
theme.css — 379-line hand copy of the Technical-Light design system.
- Path:
src/ZB.MOM.WW.ScadaBridge.CentralUI/wwwroot/css/theme.css - Font path:
url('../fonts/ibm-plex-sans-400.woff2')(lines 24, 29, 34) - Correct path — resolves from
wwwroot/css/towwwroot/fonts/without 404. This is the canonicalurl('../fonts/…')that the RCL also uses. - Wired in the Host's
App.razorline 9:<link href="_content/ZB.MOM.WW.ScadaBridge.CentralUI/css/theme.css" rel="stylesheet" />.
site.css — 128 lines of per-app page layout (sidebar shell, nav overrides).
- Path:
src/ZB.MOM.WW.ScadaBridge.CentralUI/wwwroot/css/site.css - Wired in the Host's
App.razorline 11:<link href="_content/ZB.MOM.WW.ScadaBridge.CentralUI/css/site.css" rel="stylesheet" />. - Contains:
.sidebarlayout block (~4–95), Bootstrap-icons integration for nav items. - After adoption: the
.sidebarlayout section is superseded by RCLlayout.css. The remaining rules (Bootstrap-icons, misc overrides) stay insite.css.
Note: ScadaBridge uses the _content/ZB.MOM.WW.ScadaBridge.CentralUI/… static-web-asset
path for its own CentralUI RCL assets — the same mechanism ZB.MOM.WW.Theme will use.
2. IBM Plex fonts
Three .woff2 files vendored into:
src/ZB.MOM.WW.ScadaBridge.CentralUI/wwwroot/fonts/
ibm-plex-sans-400.woff2ibm-plex-sans-600.woff2ibm-plex-mono-500.woff2
After adoption: delete all three from CentralUI/wwwroot/fonts/; the RCL serves them
from _content/ZB.MOM.WW.Theme/fonts/.
3. Layout shell
MainLayout.razor — 29-line static layout. @inherits LayoutComponentBase. No
@rendermode directive (static SSR).
- Path:
src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Layout/MainLayout.razor - Root element:
<div class="d-flex flex-column flex-lg-row" style="min-height: 100vh;">. No.app-shellclass. - Renders
<NavMenu />inside a Bootstrap collapse div,<main class="flex-grow-1 p-3">, plus<DialogHost />and<SessionExpiry />at the bottom.
NavMenu.razor — 200+ line interactive sidebar component. @implements IDisposable.
- Path:
src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Layout/NavMenu.razor - Brand:
<div class="brand"><span class="mark">▮</span> ScadaBridge</div>(lines ~9–9). - Nav structure:
<nav class="sidebar d-flex flex-column">with<ul class="nav flex-column">and<NavSection>groups ("Admin", "Data", "Audit", etc.) with<NavLink class="nav-link">children. UsesAuthorizeView+AuthorizeView Policy="…"to gate admin sections. - Nav state: JS-based expand-state persistence (same pattern as OtOpcUa and MxGateway).
NavSection.razor (same name as OtOpcUa/MxGateway, independent per-project copy).
- Path:
src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Layout/NavSection.razor
4. Login page
Login.razor — 36-line static login page. @layout LoginLayout, @attribute [AllowAnonymous].
- Path:
src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Login.razor - Form:
<form method="post" action="/auth/login" data-enhance="false">(line 16). - Uses Bootstrap
.card/.card-bodymarkup — not the Technical-Light.panel/.login-wrapidiom used in OtOpcUa. Does not use a<LoginCard>yet. - Error notice: Bootstrap
.alert alert-danger(line 12) rather than.panel.notice.
5. Scoped .razor.css files (stays per-project)
ScadaBridge ships several component-scoped CSS files. These are not shared and stay in the CentralUI RCL after adoption:
| File | Path |
|---|---|
MultiSelectDropdown.razor.css |
src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Shared/ |
TreeView.razor.css |
src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Shared/ |
AuditDrilldownDrawer.razor.css |
src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Audit/ |
AuditEventDetail.razor.css |
src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Audit/ |
ExecutionDetailModal.razor.css |
src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Audit/ |
ExecutionTree.razor.css |
src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Audit/ |
AuditResultsGrid.razor.css |
src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Audit/ |
NodeBrowserDialog.razor.css |
src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Dialogs/ |
These scoped styles are component-specific overrides and are unaffected by theme adoption.
6. Divergences from spec
| Item | Current state | Spec |
|---|---|---|
theme.css |
Hand copy, 379 lines | Single canonical copy in RCL |
Font-path url() |
url('../fonts/…') — correct |
url('../fonts/…') — same |
| IBM Plex fonts | Vendored 3× in wwwroot/fonts/ |
Single copy in RCL wwwroot/fonts/ |
| Shell class | d-flex … (no .app-shell) |
ThemeShell + .app-shell |
| Nav class idiom | .sidebar + .nav-link + <ul>/<li> |
.side-rail + .rail-link + <a> |
| Nav items | <NavLink class="nav-link"> inside <li> |
<NavRailItem> |
| Nav sections | NavSection (EventCallback OnToggle + JS) |
NavRailSection (<details>, CSS-only) |
| Status chip | None (uses raw .chip-* classes inline) |
StatusPill (StatusState enum) |
| Login card | Bootstrap .card markup (not Technical-Light .panel) |
<LoginCard> |
Scoped .razor.css |
8 component-scoped files | Stays per-project (no change) |
7. Adoption plan
Effort: Medium. Risk: Medium. The font path is already correct so no 404 fix needed.
The sidebar idiom migration (.sidebar→.side-rail, .nav-link→.rail-link) and the
MainLayout replacement are the main work. The scoped .razor.css files are unaffected.
Steps:
-
Delete copies. Remove
src/ZB.MOM.WW.ScadaBridge.CentralUI/wwwroot/css/theme.cssandwwwroot/fonts/ibm-plex-*.woff2fromCentralUI. -
Reference RCL. Add
<PackageReference Include="ZB.MOM.WW.Theme" />toZB.MOM.WW.ScadaBridge.CentralUI.csproj. Add@using ZB.MOM.WW.ThemetoCentralUI's_Imports.razor. -
Wire
ThemeHead. InHost'sApp.razor, replace line 9 (<link href="_content/…/css/theme.css">) with<ThemeHead />(which now resolves viaZB.MOM.WW.Theme). Keep thesite.csslink on line 11. -
Replace
MainLayout. Replace the 29-lineMainLayout.razorwith a thin wrapper around<ThemeShell Product="ScadaBridge">. Carry<NavMenu />into theNavslot (or replace it — see step 5). Keep<DialogHost />and<SessionExpiry />below theThemeShellor insideChildContentas needed. -
Port nav. Migrate
NavMenu.razorfromnav.sidebar+<ul>/<li>+.nav-linkto<NavRailSection>+<NavRailItem>. TheAuthorizeViewpolicy gating on admin sections stays — wrap<NavRailSection>inside the appropriate<AuthorizeView>just as today. -
Clean
site.css. Remove the.sidebarlayout block. Keep Bootstrap-icons integration and any domain-specific overrides. -
Replace login card. In
Login.razor, replace the Bootstrap.card/.card-bodymarkup with<LoginCard Product="ScadaBridge" Action="/auth/login" ReturnUrl="@ReturnUrl" Error="@ErrorMessage"><AntiforgeryToken /></LoginCard>. Align error display with the Technical-Light.panel.noticestyle fromLoginCard. -
Keep: all scoped
.razor.cssfiles (8 files listed above);site.cssdomain rules; auth and session endpoints; all page components.
Risk note: ScadaBridge's AuthorizeView policy-gated nav sections require careful
testing — verify that <NavRailSection> inside <AuthorizeView> renders correctly and
that the section is fully hidden when the policy fails (not just collapsed).