7.9 KiB
UI Theme — current state: OtOpcUa
Repo: ~/Desktop/OtOpcUa (Gitea lmxopcua). Stack: .NET 10, Blazor SSR.
UI surface: src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/ (Razor Class Library).
All paths below are relative to the repo root. Verified against source on 2026-06-01.
Summary: OtOpcUa already uses a side-rail layout and the full Technical-Light token set. Adoption is lowest effort of the three apps — the shell shape already matches the canonical target. The one bug fixed by adoption: a latent font-path 404 that silently falls back to system fonts today.
1. CSS / design tokens
theme.css — 379-line hand copy of the Technical-Light design system.
- Path:
src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/wwwroot/css/theme.css - Font path:
url('fonts/ibm-plex-sans-400.woff2')(lines 24, 29, 34) - Bug: the path is relative to the CSS file location (
wwwroot/css/), so it resolves aswwwroot/css/fonts/…— a 404. The browser silently falls back to system fonts. The canonical RCL pathurl('../fonts/…')fixes this permanently. - Wired in
App.razorline 17:<link rel="stylesheet" href="_content/ZB.MOM.WW.OtOpcUa.AdminUI/css/theme.css"/>.
site.css — 174 lines of per-app page layout (side-rail shell, login card layout,
page body padding, miscellaneous overrides).
- Path:
src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/wwwroot/css/site.css - Wired in
App.razorline 18:<link rel="stylesheet" href="_content/ZB.MOM.WW.OtOpcUa.AdminUI/css/site.css"/>. - After adoption: the
.side-rail,.rail-*,.login-wrap,.login-titlerules are superseded by the RCL'slayout.css. The page-layout residuals (body padding, page- specific overrides) stay insite.css.
2. IBM Plex fonts
Three .woff2 files vendored into:
src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/wwwroot/fonts/
ibm-plex-sans-400.woff2ibm-plex-sans-600.woff2ibm-plex-mono-500.woff2
After adoption: delete all three; the RCL serves them from
_content/ZB.MOM.WW.Theme/fonts/.
3. Layout shell
MainLayout.razor — 28-line static layout (no @rendermode). Renders .app-shell
flex row, hamburger toggle, <NavSidebar/> inside a Bootstrap collapse div, and
<main class="page">.
- Path:
src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Layout/MainLayout.razor - Structure:
.app-shell d-flex flex-column flex-lg-row(line 8), hamburger (lines 11–18),<div class="collapse d-lg-block" id="sidebar-collapse">(line 21),<NavSidebar />(line 22),<main class="page">@Body</main>(lines 25–27).
NavSidebar.razor — 160-line interactive (@rendermode InteractiveServer) sidebar.
Hosts the collapsible NavSection groups and cookie-persisted expand state.
- Path:
src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Layout/NavSidebar.razor - Brand:
<div class="brand"><span class="mark">▮</span> OtOpcUa</div>(lines 14–14). - Nav sections: two
NavSectiongroups ("Navigation", "Scripting", "Live", "Config") with<NavLink class="rail-link">children. - Rail foot (lines 44–62):
<div class="rail-foot"><AuthorizeView>— session info + sign-out<form method="post" action="/auth/logout">. - Nav expand state persisted in
otopcua_navcookie viawwwroot/js/nav-state.js(cookie:otopcua_nav=<comma-separated ids>).
NavSection.razor — 36-line NavSection component (interactive; uses EventCallback
OnToggle for expand/collapse, not CSS <details>).
- Path:
src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Layout/NavSection.razor
LoginLayout.razor — plain layout (no sidebar) used by the login page.
- Path:
src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Layout/LoginLayout.razor
4. Login page
Login.razor — 50-line static login page. Uses @layout LoginLayout,
@attribute [AllowAnonymous].
- Path:
src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Pages/Login.razor - Form:
<form method="post" action="/auth/login" data-enhance="false">(line 21). - Hidden
returnUrlinput (line 22–25), username/password inputs, error notice panel. - The form structure exactly matches what
<LoginCard>emits; migration is direct.
5. StatusBadge component
StatusBadge.razor — thin wrapper over .chip classes.
- Path:
src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Shared/StatusBadge.razor - Parameters:
string Text,string CssClass(defaultchip-idle). - After adoption: replaced by
<StatusPill State="…">— caller maps state toStatusStateenum rather than passing CSS class strings directly.
6. Divergences from spec
| Item | Current state | Spec |
|---|---|---|
theme.css |
Hand copy, 379 lines | Single canonical copy in RCL |
Font-path url() |
url('fonts/…') — latent 404 |
url('../fonts/…') — correct |
| IBM Plex fonts | Vendored 3× in wwwroot/fonts/ |
Single copy in RCL wwwroot/fonts/ |
| Shell layout | .app-shell + NavSidebar component (matches target shape) |
ThemeShell + thin MainLayout |
| Nav items | <NavLink class="rail-link"> inside interactive NavSidebar |
NavRailItem inside NavRailSection |
| Nav expand state | Cookie-persisted via otopcua_nav + JS |
CSS-only <details> in NavRailSection |
| Status chip | StatusBadge (string CSS class param) |
StatusPill (StatusState enum param) |
| Login card | Inline markup in Login.razor |
<LoginCard> |
7. Adoption plan
Effort: Low. The shell shape already matches the target. No layout migration needed.
Steps:
-
Delete copies. Remove
wwwroot/css/theme.cssandwwwroot/fonts/ibm-plex-*.woff2fromZB.MOM.WW.OtOpcUa.AdminUI. This also fixes the latent font-path 404. -
Reference RCL. Add
<PackageReference Include="ZB.MOM.WW.Theme" />toZB.MOM.WW.OtOpcUa.AdminUI.csproj. Add@using ZB.MOM.WW.Themeto_Imports.razor. -
Wire
ThemeHead. InApp.razorreplace lines 17–18:- <link rel="stylesheet" href="_content/ZB.MOM.WW.OtOpcUa.AdminUI/css/theme.css"/> - <link rel="stylesheet" href="_content/ZB.MOM.WW.OtOpcUa.AdminUI/css/site.css"/> + <ThemeHead /> + <link rel="stylesheet" href="_content/ZB.MOM.WW.OtOpcUa.AdminUI/css/site.css"/>(Keep
site.cssfor the page-layout residuals.) -
Replace
MainLayout. Delete the current 28-lineMainLayout.razor. Create a new thinMainLayout.razorthat delegates to<ThemeShell Product="OtOpcUa Admin">withNavandRailFooterslots (carry the session/sign-out block fromNavSidebar's.rail-footintoRailFooter). -
Port nav. Rebuild the
Navslot using<NavRailSection>+<NavRailItem>. The four section groups ("Navigation", "Scripting", "Live", "Config") map directly toNavRailSection Title="…"withNavRailItemchildren.Cookie nav state: OtOpcUa's
otopcua_navcookie persistence requires JS and anInteractiveServercomponent. If this feature is retained, keep a bespoke interactiveNavSection(the currentNavSection.razoror a refactored version) alongside — it is compatible withThemeShell'sNavslot. If cookie persistence is acceptable to drop,NavRailSection(CSS-only<details>) is a drop-in replacement. -
Replace
StatusBadge. Find all usages of<StatusBadge CssClass="chip-*">and replace with<StatusPill State="StatusState.*">. DeleteStatusBadge.razor. -
Replace login card. In
Login.razor, replace the inline<div class="login-wrap"> … </div>block with<LoginCard Product="OtOpcUa Admin" Action="/auth/login" ReturnUrl="@ReturnUrl" Error="@Error"><AntiforgeryToken /></LoginCard>. The code-behind (Error/ReturnUrlsupply-from-query properties) stays unchanged. -
Keep:
site.csspage-layout residuals; scoped.razor.cssfiles (none currently in AdminUI);LoginLayout.razor; auth endpoints; all page components.
Risk: Low — layout shape already matches, no top-bar migration. Cookie nav state is the only optional complexity (decide retain vs drop).