164 lines
7.9 KiB
Markdown
164 lines
7.9 KiB
Markdown
# 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
|
||
as `wwwroot/css/fonts/…` — a 404. The browser silently falls back to system fonts. The
|
||
canonical RCL path `url('../fonts/…')` fixes this permanently.
|
||
- Wired in `App.razor` line 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.razor` line 18:
|
||
`<link rel="stylesheet" href="_content/ZB.MOM.WW.OtOpcUa.AdminUI/css/site.css"/>`.
|
||
- After adoption: the `.side-rail`, `.rail-*`, `.login-wrap`, `.login-title` rules are
|
||
superseded by the RCL's `layout.css`. The page-layout residuals (body padding, page-
|
||
specific overrides) stay in `site.css`.
|
||
|
||
---
|
||
|
||
## 2. IBM Plex fonts
|
||
|
||
Three `.woff2` files vendored into:
|
||
`src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/wwwroot/fonts/`
|
||
- `ibm-plex-sans-400.woff2`
|
||
- `ibm-plex-sans-600.woff2`
|
||
- `ibm-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 `NavSection` groups ("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_nav` cookie via
|
||
`wwwroot/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 `returnUrl` input (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` (default `chip-idle`).
|
||
- After adoption: replaced by `<StatusPill State="…">` — caller maps state to `StatusState`
|
||
enum 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:**
|
||
|
||
1. **Delete copies.** Remove `wwwroot/css/theme.css` and `wwwroot/fonts/ibm-plex-*.woff2`
|
||
from `ZB.MOM.WW.OtOpcUa.AdminUI`. This also fixes the latent font-path 404.
|
||
|
||
2. **Reference RCL.** Add `<PackageReference Include="ZB.MOM.WW.Theme" />` to
|
||
`ZB.MOM.WW.OtOpcUa.AdminUI.csproj`. Add `@using ZB.MOM.WW.Theme` to `_Imports.razor`.
|
||
|
||
3. **Wire `ThemeHead`.** In `App.razor` replace lines 17–18:
|
||
```diff
|
||
- <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.css` for the page-layout residuals.)
|
||
|
||
4. **Replace `MainLayout`.** Delete the current 28-line `MainLayout.razor`. Create a new
|
||
thin `MainLayout.razor` that delegates to `<ThemeShell Product="OtOpcUa Admin">` with
|
||
`Nav` and `RailFooter` slots (carry the session/sign-out block from `NavSidebar`'s
|
||
`.rail-foot` into `RailFooter`).
|
||
|
||
5. **Port nav.** Rebuild the `Nav` slot using `<NavRailSection>` + `<NavRailItem>`. The
|
||
four section groups ("Navigation", "Scripting", "Live", "Config") map directly to
|
||
`NavRailSection Title="…"` with `NavRailItem` children.
|
||
|
||
**Cookie nav state:** OtOpcUa's `otopcua_nav` cookie persistence requires JS and an
|
||
`InteractiveServer` component. If this feature is retained, keep a bespoke interactive
|
||
`NavSection` (the current `NavSection.razor` or a refactored version) alongside — it
|
||
is compatible with `ThemeShell`'s `Nav` slot. If cookie persistence is acceptable to
|
||
drop, `NavRailSection` (CSS-only `<details>`) is a drop-in replacement.
|
||
|
||
6. **Replace `StatusBadge`.** Find all usages of `<StatusBadge CssClass="chip-*">` and
|
||
replace with `<StatusPill State="StatusState.*">`. Delete `StatusBadge.razor`.
|
||
|
||
7. **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` / `ReturnUrl` supply-from-query properties) stays unchanged.
|
||
|
||
8. **Keep:** `site.css` page-layout residuals; scoped `.razor.css` files (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).
|