Skip to content

DashboardLayout: sticky sidebar sizes itself from sidebarHeight independently of sidebarTopOffset, so any non-zero offset pushes the panel's bottom off-viewport #1843

Description

@fitzergerald

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions