From 6b06edb2228d7f4c9b225601d2c1b529941ff55b Mon Sep 17 00:00:00 2001 From: Oliver Kieselbach Date: Fri, 6 Nov 2026 18:37:49 +0100 Subject: [PATCH] View as: keep the navbar and sidebar clear of the view-as bar The bar sat in the normal flow above the sticky navbar and scrolled away, while the desktop sidebar is fixed at the navbar's height. In a view-as tab the navbar therefore covered the top of the sidebar (its first entry) by up to the bar's height, depending on the scroll position. The bar now sticks above the navbar at a fixed height: one line, a long tenant name truncates. globals.css derives --view-as-offset from its presence (:root:has([data-view-as-bar])): the bar's height while it is shown, 0 otherwise. Everything positioned against the top of the viewport adds it: the navbar's sticky top, the desktop sidebar, the mobile notification panel, the sticky severity card, the push app's header, and anchor scrolling (scroll-padding-top on the root). The sidebar's section jump and scroll-spy margin add the bar's measured height (viewAsBarHeight in lib/viewAs.ts). Ordinary tabs keep offset 0, so their layout is unchanged. Co-Authored-By: Claude Opus 5.5 --- src/Web/autopilot-monitor-web/app/globals.css | 16 ++++++++++++++ .../app/push/PushNav.tsx | 2 +- .../components/GlobalSidebar.tsx | 9 +++++--- .../components/Navbar.tsx | 6 ++--- .../components/ViewAsBar.tsx | 22 +++++++++++-------- .../vulnerability/SeverityPriorityCard.tsx | 2 +- src/Web/autopilot-monitor-web/lib/viewAs.ts | 8 +++++++ 7 files changed, 48 insertions(+), 17 deletions(-) diff --git a/src/Web/autopilot-monitor-web/app/globals.css b/src/Web/autopilot-monitor-web/app/globals.css index 9369131e8..aaaedf795 100644 --- a/src/Web/autopilot-monitor-web/app/globals.css +++ b/src/Web/autopilot-monitor-web/app/globals.css @@ -22,6 +22,22 @@ html { drawer + overlay) keep sizing against the viewport. */ overflow-x: clip; scrollbar-gutter: stable; + /* Anchor jumps and scrollIntoView stop below the view-as bar as well (see the next block). */ + scroll-padding-top: var(--view-as-offset); +} + +/* ===== View-as bar (components/ViewAsBar.tsx) ===== + While a tab views a tenant as its member, or says once why that was refused, the bar sticks above the + navbar at a fixed height. Everything positioned against the top of the viewport adds --view-as-offset — + the navbar's sticky top, the desktop sidebar, sticky content, anchor scrolling — so nothing slides under + the bar: the offset is the bar's height while it is shown and 0 otherwise. Offsets computed in JS read + the bar's height through viewAsBarHeight() (lib/viewAs.ts). */ +:root { + --view-as-bar-height: 2.25rem; + --view-as-offset: 0px; +} +:root:has([data-view-as-bar]) { + --view-as-offset: var(--view-as-bar-height); } :root { diff --git a/src/Web/autopilot-monitor-web/app/push/PushNav.tsx b/src/Web/autopilot-monitor-web/app/push/PushNav.tsx index 430156eac..cf93802de 100644 --- a/src/Web/autopilot-monitor-web/app/push/PushNav.tsx +++ b/src/Web/autopilot-monitor-web/app/push/PushNav.tsx @@ -12,7 +12,7 @@ const ITEMS = [ export function PushNav() { const pathname = (usePathname() ?? "/push").replace(/\/$/, "") || "/push"; return ( -
+
diff --git a/src/Web/autopilot-monitor-web/components/GlobalSidebar.tsx b/src/Web/autopilot-monitor-web/components/GlobalSidebar.tsx index 7e55b3d4e..9bf336b9d 100644 --- a/src/Web/autopilot-monitor-web/components/GlobalSidebar.tsx +++ b/src/Web/autopilot-monitor-web/components/GlobalSidebar.tsx @@ -13,6 +13,7 @@ import { useAdminMode } from "../hooks/useAdminMode"; import { useMediaQuery } from "../hooks/useMediaQuery"; import { DOCS_URL } from "@/lib/config"; import { isPushAppPath } from "@/lib/push/pushAppPath"; +import { viewAsBarHeight } from "@/lib/viewAs"; // Sidebar pixel widths export const SIDEBAR_PX: Record = { @@ -75,7 +76,8 @@ export function GlobalSidebar({ children }: { children: ReactNode }) { } } }, - { rootMargin: "-80px 0px -60% 0px", threshold: 0 }, + // The top 80 px are the navbar plus a margin; a view-as bar above the navbar adds its own height. + { rootMargin: `-${80 + viewAsBarHeight()}px 0px -60% 0px`, threshold: 0 }, ); observerRef.current = observer; @@ -104,7 +106,8 @@ export function GlobalSidebar({ children }: { children: ReactNode }) { const scrollTo = useCallback((id: string) => { const el = document.getElementById(id); if (el) { - const y = el.getBoundingClientRect().top + window.scrollY - 90; + // 90 px keep the section clear of the navbar; a view-as bar above the navbar adds its own height. + const y = el.getBoundingClientRect().top + window.scrollY - 90 - viewAsBarHeight(); window.scrollTo({ top: y, behavior: "smooth" }); } setActiveSectionId(id); @@ -726,7 +729,7 @@ export function GlobalSidebar({ children }: { children: ReactNode }) { {/* ===== Desktop: fixed sidebar ===== */}