fix(frontend): stack the warning banner + tab bar in one sticky wrapper (#1587)
Per mara's review: instead of measuring the banner height in JS and offsetting the chrome's sticky top, put the warning banner and the page chrome in the same sticky div so they stack naturally. common.js builds the wrapper: ensureStickyTop() wraps the page's chrome (.dashboard-chrome / .page-header) in a single .sticky-top container and injects the warning banner as its first child. The banner and the chrome are no longer individually sticky — .sticky-top owns the stickiness, so they pin together in one context instead of two top:0 stickies colliding (the banner used to overlay the tab bar). Pages without a chrome (the H0M3 hub) get a banner-only sticky region. No per-page markup needed; no JS height measurement. Build green.
This commit is contained in:
parent
0dc85e8261
commit
d7bba48176
4 changed files with 46 additions and 19 deletions
|
|
@ -501,22 +501,27 @@ body.side-panel-resizing * { cursor: ew-resize !important; }
|
||||||
ST4TS / S3TT1NGS are full-bleed with their own `.<page>-main` padding. */
|
ST4TS / S3TT1NGS are full-bleed with their own `.<page>-main` padding. */
|
||||||
.page-content { padding: 0 1.5em; }
|
.page-content { padding: 0 1.5em; }
|
||||||
|
|
||||||
/* ─── server warnings banner ──────────────────────────────────────────
|
/* ─── sticky top region ───────────────────────────────────────────────
|
||||||
Generic top-of-page strip, injected at the top of <body> by
|
One sticky container per page holding the warning banner above the
|
||||||
common.js (renderServerWarnings) on every page. One row per warning;
|
page chrome (tab bar / page header). common.js (ensureStickyTop)
|
||||||
colour comes from the per-warning `level` (warn = amber, crit = red).
|
wraps the page's chrome in this on first render and injects the banner
|
||||||
`position: sticky; top: 0` keeps it pinned above the page chrome so an
|
as its first child, so the banner + chrome stack in a single sticky
|
||||||
operator sees it on any page no matter how far they've scrolled. */
|
context — pinned together — instead of two separate `top:0` stickies
|
||||||
.server-warnings {
|
colliding (the banner used to overlay the tab bar). */
|
||||||
|
.sticky-top {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
z-index: 50;
|
z-index: 50;
|
||||||
}
|
}
|
||||||
/* The bar is prepended as a direct child of <body> and spans the full
|
|
||||||
width edge-to-edge. The dashboard + H0M3 are full-bleed at the body
|
/* ─── server warnings banner ──────────────────────────────────────────
|
||||||
level — their 1.5em horizontal gutter lives on an inner `.page-content`
|
Generic top-of-page strip, injected as the first child of `.sticky-top`
|
||||||
wrapper (see below), so the banner is full-width for free with no
|
by common.js (renderServerWarnings) on every page. One row per warning;
|
||||||
breakout. FL0W + L0GS are full-bleed too. */
|
colour comes from the per-warning `level` (warn = amber, crit = red).
|
||||||
|
Stickiness lives on the `.sticky-top` wrapper, not here. The bar spans
|
||||||
|
the full width edge-to-edge; the dashboard + H0M3 are full-bleed at the
|
||||||
|
body level (their 1.5em gutter lives on an inner `.page-content`
|
||||||
|
wrapper), so the banner is full-width for free. */
|
||||||
.server-warnings[hidden] { display: none; }
|
.server-warnings[hidden] { display: none; }
|
||||||
.server-warn {
|
.server-warn {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
|
||||||
|
|
@ -634,6 +634,28 @@ export const NOTIF = (() => {
|
||||||
// `level` (`warn` amber / `crit` red). Adding a new system warning is a
|
// `level` (`warn` amber / `crit` red). Adding a new system warning is a
|
||||||
// backend-only change. The bar is injected at the top of <body> so no
|
// backend-only change. The bar is injected at the top of <body> so no
|
||||||
// page needs to add markup.
|
// page needs to add markup.
|
||||||
|
|
||||||
|
// The sticky top region — one sticky container holding the warning
|
||||||
|
// banner above the page's chrome (tab bar / page header), so the banner
|
||||||
|
// stacks with the chrome instead of being overlaid by it (two separate
|
||||||
|
// `top:0` stickies would otherwise collide). Built once by wrapping the
|
||||||
|
// page's existing chrome element; pages without a chrome (e.g. the H0M3
|
||||||
|
// hub) get a banner-only sticky region at the top of <body>.
|
||||||
|
function ensureStickyTop() {
|
||||||
|
let top = document.querySelector('.sticky-top');
|
||||||
|
if (top) return top;
|
||||||
|
top = document.createElement('div');
|
||||||
|
top.className = 'sticky-top';
|
||||||
|
const chrome = document.querySelector('.dashboard-chrome, .page-header');
|
||||||
|
if (chrome && chrome.parentNode) {
|
||||||
|
chrome.parentNode.insertBefore(top, chrome);
|
||||||
|
top.append(chrome);
|
||||||
|
} else {
|
||||||
|
document.body.prepend(top);
|
||||||
|
}
|
||||||
|
return top;
|
||||||
|
}
|
||||||
|
|
||||||
function ensureServerWarningsBar() {
|
function ensureServerWarningsBar() {
|
||||||
let bar = document.getElementById('server-warnings');
|
let bar = document.getElementById('server-warnings');
|
||||||
if (!bar) {
|
if (!bar) {
|
||||||
|
|
@ -642,7 +664,7 @@ function ensureServerWarningsBar() {
|
||||||
bar.className = 'server-warnings';
|
bar.className = 'server-warnings';
|
||||||
bar.setAttribute('role', 'alert');
|
bar.setAttribute('role', 'alert');
|
||||||
bar.hidden = true;
|
bar.hidden = true;
|
||||||
document.body.prepend(bar);
|
ensureStickyTop().prepend(bar);
|
||||||
}
|
}
|
||||||
return bar;
|
return bar;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,9 +27,9 @@ body.dashboard-shell {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.75em;
|
gap: 0.75em;
|
||||||
position: sticky;
|
/* Stickiness lives on the `.sticky-top` wrapper (common.css) that
|
||||||
top: 0;
|
common.js wraps this chrome in, alongside the server-warning banner,
|
||||||
z-index: 25;
|
so they stack in one sticky context instead of colliding. */
|
||||||
background: color-mix(in srgb, var(--bg) 86%, transparent);
|
background: color-mix(in srgb, var(--bg) 86%, transparent);
|
||||||
-webkit-backdrop-filter: blur(8px) saturate(120%);
|
-webkit-backdrop-filter: blur(8px) saturate(120%);
|
||||||
backdrop-filter: blur(8px) saturate(120%);
|
backdrop-filter: blur(8px) saturate(120%);
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,9 @@
|
||||||
</header>
|
</header>
|
||||||
*/
|
*/
|
||||||
.page-header {
|
.page-header {
|
||||||
position: sticky;
|
/* Stickiness lives on the `.sticky-top` wrapper (common.css) that
|
||||||
top: 0;
|
common.js wraps this header in, alongside the server-warning banner,
|
||||||
z-index: 25;
|
so the two stack instead of colliding. */
|
||||||
background: color-mix(in srgb, var(--bg) 92%, transparent);
|
background: color-mix(in srgb, var(--bg) 92%, transparent);
|
||||||
-webkit-backdrop-filter: blur(8px) saturate(120%);
|
-webkit-backdrop-filter: blur(8px) saturate(120%);
|
||||||
backdrop-filter: blur(8px) saturate(120%);
|
backdrop-filter: blur(8px) saturate(120%);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue