Make the seven sidebar section groups (Admin, Design, Deployment, Notifications, Site Calls, Monitoring, Audit) collapsible. New NavSection component renders a header toggle button (chevron) and reveals its items only while expanded; NavMenu owns the expanded-section set. Behaviour: sections are collapsed by default; state persists in the `scadabridge_nav` cookie (written/read via the new nav-state.js JS interop, mirroring treeview-storage.js) so it survives reloads and reconnects; navigating into a section auto-expands it and remembers it. The Dashboard item stays sectionless and always visible. Tests: NavMenu bUnit tests expand sections before asserting items and add collapsed-by-default / toggle / cookie-persistence cases; Playwright nav tests expand sections before clicking links; new NavCollapseTests covers the feature E2E. Build 0 warnings; bUnit 545 passed; Playwright nav suite green (the unrelated AuditGridColumnTests resize-reload case remains pre-existing flaky — an un-awaited save race in that test).
129 lines
3.0 KiB
CSS
129 lines
3.0 KiB
CSS
/* ScadaLink Central UI – global styles. Loaded from Host App.razor as
|
||
`_content/ScadaLink.CentralUI/css/site.css`. */
|
||
|
||
.sidebar {
|
||
min-width: 220px;
|
||
max-width: 220px;
|
||
height: 100vh;
|
||
background: var(--card);
|
||
border-right: 1px solid var(--rule-strong);
|
||
}
|
||
|
||
/* Keep the sidebar pinned to the viewport on lg+ so it stays visible even
|
||
when the main content scrolls past 100vh. The wrapper is the flex child
|
||
of MainLayout; align-self prevents the flex row from stretching it. */
|
||
@media (min-width: 992px) {
|
||
#sidebar-collapse {
|
||
position: sticky;
|
||
top: 0;
|
||
height: 100vh;
|
||
align-self: flex-start;
|
||
z-index: 1020;
|
||
}
|
||
}
|
||
|
||
.sidebar .nav-link {
|
||
color: var(--ink-soft);
|
||
padding: 0.4rem 1rem;
|
||
font-size: 0.9rem;
|
||
}
|
||
|
||
.sidebar .nav-link:hover {
|
||
color: var(--ink);
|
||
background-color: var(--paper);
|
||
}
|
||
|
||
.sidebar .nav-link.active {
|
||
color: var(--accent-deep);
|
||
background-color: var(--paper);
|
||
font-weight: 600;
|
||
/* Left accent so active state isn't carried by color alone. */
|
||
border-left: 3px solid var(--accent);
|
||
padding-left: calc(1rem - 3px);
|
||
}
|
||
|
||
/* Collapsible section header — a full-width button styled as an uppercase
|
||
eyebrow with a leading expand/collapse chevron. */
|
||
.sidebar .nav-section-toggle {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.4rem;
|
||
width: 100%;
|
||
background: none;
|
||
border: 0;
|
||
cursor: pointer;
|
||
text-align: left;
|
||
color: var(--ink-faint);
|
||
font-size: 0.7rem;
|
||
font-weight: 600;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.07em;
|
||
padding: 0.75rem 1rem 0.25rem;
|
||
margin-top: 0.5rem;
|
||
}
|
||
|
||
.sidebar .nav-section-toggle:hover {
|
||
color: var(--ink);
|
||
}
|
||
|
||
.sidebar .nav-section-toggle .bi {
|
||
font-size: 0.8rem;
|
||
line-height: 1;
|
||
}
|
||
|
||
.sidebar .brand {
|
||
color: var(--ink);
|
||
font-size: 1.1rem;
|
||
font-weight: 600;
|
||
letter-spacing: 0.02em;
|
||
padding: 1rem;
|
||
border-bottom: 1px solid var(--rule);
|
||
}
|
||
|
||
/* The single accent glyph in the brand mark. */
|
||
.sidebar .brand .mark {
|
||
color: var(--accent);
|
||
}
|
||
|
||
/* When the sidebar is collapsed under <lg viewports the Bootstrap collapse
|
||
container removes the fixed width; restore full width on mobile. */
|
||
@media (max-width: 991.98px) {
|
||
.sidebar {
|
||
min-width: 100%;
|
||
max-width: 100%;
|
||
height: auto;
|
||
}
|
||
}
|
||
|
||
#reconnect-modal {
|
||
display: none;
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
z-index: 9999;
|
||
background-color: rgba(0, 0, 0, 0.5);
|
||
}
|
||
|
||
#reconnect-modal .modal-content {
|
||
max-width: 400px;
|
||
padding: 2rem;
|
||
text-align: center;
|
||
background: var(--bs-white);
|
||
border-radius: 0.5rem;
|
||
}
|
||
|
||
/* Script editor modal — the tabbed Trigger/Code/Parameters/Return content is
|
||
substantial, so the dialog fills most of the viewport. Pairs with
|
||
.modal-dialog-scrollable so the body scrolls within the fixed height. */
|
||
.modal-dialog.script-editor-modal {
|
||
max-width: 96vw;
|
||
width: 96vw;
|
||
height: calc(100vh - 2rem);
|
||
}
|
||
|
||
.modal-dialog.script-editor-modal .modal-content {
|
||
height: 100%;
|
||
}
|