Summary
DashboardLayout pins its sticky sidebar at sidebarTopOffset but sizes it from sidebarHeight, and the two are computed independently. sidebarHeight defaults to 100vh, so any caller that supplies a top offset and leaves the height alone gets a panel exactly sidebarTopOffset taller than the space it can occupy.
Because the panel is position: sticky, that overflow is not recoverable by scrolling: once pinned, its bottom sidebarTopOffset worth of content - including the end of its own internal scroll container - sits permanently below the viewport, and its header scrolls up under the page's navbar.
Where
DashboardLayout, the desktop (non-mobile) branch:
<Box sx={{ width: sidebarWidth, flexShrink: 0, position: "sticky",
top: sidebarTopOffset, alignSelf: "flex-start",
height: sidebarHeight, maxHeight: sidebarHeight }}>
with
sidebarTopOffset = "0",
sidebarHeight = "100vh"
The default pair is self-consistent (offset 0, height 100vh). Every non-zero offset breaks it, and nothing in the types or at runtime says so.
Reproduce
Render DashboardLayout with sidebarTopOffset="64px" and no sidebarHeight (the natural call for any app with a fixed navbar), open the sidebar above the md breakpoint, and scroll the page down. The sidebar's header leaves the top of the viewport and its last 64px never becomes reachable.
Measured, rather than eyeballed:
const rect = stickyPanel.getBoundingClientRect();
rect.height // 1000 at a 1000px viewport
window.innerHeight - 64 // 936 - the space actually available
Suggested fix
Derive the default height from the offset the caller already gave:
sidebarHeight = `calc(100vh - ${sidebarTopOffset})`
An explicit sidebarHeight still wins, so this is backwards compatible for anyone who already worked around it, and it makes the zero-offset default behave exactly as it does today.
The alternative - documenting that callers must pass both - pushes the navbar height into two places in every consumer, where they can silently drift apart.
Consumer status
Worked around locally in layer5io/meshery-cloud#5992 by binding the navbar height once and deriving both props from it. That is a correct caller, not a fix: every other consumer that adopts sidebarTopOffset will hit the same thing first.
Summary
DashboardLayoutpins its sticky sidebar atsidebarTopOffsetbut sizes it fromsidebarHeight, and the two are computed independently.sidebarHeightdefaults to100vh, so any caller that supplies a top offset and leaves the height alone gets a panel exactlysidebarTopOffsettaller than the space it can occupy.Because the panel is
position: sticky, that overflow is not recoverable by scrolling: once pinned, its bottomsidebarTopOffsetworth of content - including the end of its own internal scroll container - sits permanently below the viewport, and its header scrolls up under the page's navbar.Where
DashboardLayout, the desktop (non-mobile) branch:with
The default pair is self-consistent (offset
0, height100vh). Every non-zero offset breaks it, and nothing in the types or at runtime says so.Reproduce
Render
DashboardLayoutwithsidebarTopOffset="64px"and nosidebarHeight(the natural call for any app with a fixed navbar), open the sidebar above themdbreakpoint, and scroll the page down. The sidebar's header leaves the top of the viewport and its last 64px never becomes reachable.Measured, rather than eyeballed:
Suggested fix
Derive the default height from the offset the caller already gave:
An explicit
sidebarHeightstill wins, so this is backwards compatible for anyone who already worked around it, and it makes the zero-offset default behave exactly as it does today.The alternative - documenting that callers must pass both - pushes the navbar height into two places in every consumer, where they can silently drift apart.
Consumer status
Worked around locally in layer5io/meshery-cloud#5992 by binding the navbar height once and deriving both props from it. That is a correct caller, not a fix: every other consumer that adopts
sidebarTopOffsetwill hit the same thing first.