Skip to content

Controls: Prevent the save bar from covering the last control#35136

Merged
Sidnioulz merged 1 commit into
storybookjs:nextfrom
TheSeydiCharyyev:fix/34531-save-banner-overlaps-last-control
Jun 17, 2026
Merged

Controls: Prevent the save bar from covering the last control#35136
Sidnioulz merged 1 commit into
storybookjs:nextfrom
TheSeydiCharyyev:fix/34531-save-banner-overlaps-last-control

Conversation

@TheSeydiCharyyev

@TheSeydiCharyyev TheSeydiCharyyev commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Closes #34531

What I did

The "save from controls" bar (shown after you edit a story's args in development) is absolutely positioned at the bottom of the controls panel. The panel reserved room for it with padding-bottom: 41px, but that padding sat on a wrapper with a fixed height: 100%. Once the controls overflowed the panel and you scrolled to the bottom, the padding stayed pinned at the wrapper's fixed height — in the middle of the scrolled content — so the last control ended up underneath the bar.

Changing the wrapper from a fixed height: 100% (capped at 100vh) to min-height: 100% keeps it filling a short panel as before, but lets it grow with tall content so the reserved bottom padding always falls after the last control. The last control now clears the save bar.

Checklist for Contributors

Testing

The changes in this PR are covered in the following automated tests:

  • stories
  • unit tests
  • integration tests
  • end-to-end tests

A new SaveBarDoesNotCoverLastControl story in ControlsPanel.stories.tsx renders an overflowing controls panel with the save bar inside a short scroll container; its play function scrolls to the bottom and asserts the last control's bottom clears the top of the bar. It fails on the previous code and passes with this change.

Manual testing

  1. Run a sandbox, e.g. yarn task --task sandbox --start-from auto --template react-vite/default-ts
  2. Open Storybook and select a story whose component has enough args that the Controls panel scrolls (shrink the panel or window if needed).
  3. Edit any control so the "You modified this story…" save bar appears at the bottom of the panel.
  4. Scroll the Controls panel to the very bottom.
  5. Expected: the last control row is fully visible above the save bar. Before this fix it was partially hidden behind the bar.

Documentation

  • Add or update documentation reflecting your changes
  • If you are deprecating/removing a feature, make sure to update
    MIGRATION.MD

Checklist for Maintainers

  • When this PR is ready for testing, make sure to add ci:normal, ci:merged or ci:daily GH label to it to run a specific set of sandboxes. The particular set of sandboxes can be found in code/lib/cli-storybook/src/sandbox-templates.ts

  • Declare whether manual QA will be needed for this PR during the next release, through qa:needed or qa:skip

  • Make sure this PR contains one of the labels below:

    Available labels
    • bug: Internal changes that fixes incorrect behavior.
    • maintenance: User-facing maintenance tasks.
    • dependencies: Upgrading (sometimes downgrading) dependencies.
    • build: Internal-facing build tooling & test updates. Will not show up in release changelog.
    • cleanup: Minor cleanup style change. Will not show up in release changelog.
    • documentation: Documentation only changes. Will not show up in release changelog.
    • feature request: Introducing a new feature.
    • BREAKING CHANGE: Changes that break compatibility in some way with current major version.
    • other: Changes that don't fit in the above categories.

🦋 Canary release

This PR does not have a canary release associated. You can request a canary release of this pull request by mentioning the @storybookjs/core team here.

core team members can create a canary release here or locally with gh workflow run --repo storybookjs/storybook publish.yml --field pr=<PR_NUMBER>

Summary by CodeRabbit

  • Bug Fixes

    • Prevented the save bar from overlapping or clipping the last control when the controls panel is scrolled with overflowing content.
  • Tests

    • Added a Storybook regression story (SaveBarDoesNotCoverLastControl) that renders an overflow/unsaved-changes scenario in a scrollable container, scrolls to the bottom, and verifies the last control remains visible.

@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3790c53f-9a8e-4401-8668-42d0e6e81440

📥 Commits

Reviewing files that changed from the base of the PR and between b493084 and 70a2ae3.

📒 Files selected for processing (2)
  • code/core/src/controls/components/ControlsPanel.stories.tsx
  • code/core/src/controls/components/ControlsPanel.tsx
✅ Files skipped from review due to trivial changes (1)
  • code/core/src/controls/components/ControlsPanel.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • code/core/src/controls/components/ControlsPanel.stories.tsx

📝 Walkthrough

Walkthrough

This PR fixes a save bar overlap issue in the ControlsPanel by updating component styling and adding a regression test. The component now uses minHeight: '100%' instead of fixed height to accommodate scrolled content, and a new regression story validates that the save bar does not cover the last control row when the panel is scrolled to the bottom.

Changes

Save bar overlap fix and regression test

Layer / File(s) Summary
Component styling fix
code/core/src/controls/components/ControlsPanel.tsx
Updated AddonWrapper to use minHeight: '100%' instead of fixed height/maxHeight, preventing bottom padding clipping and save bar overlap during scrolling.
Regression test for save bar overlap
code/core/src/controls/components/ControlsPanel.stories.tsx
Added test imports (global and within), overflow fixtures with many arg rows, ManagerContext setup, and the SaveBarDoesNotCoverLastControl story that forces global.CONFIG_TYPE to DEVELOPMENT, renders the panel in a scrollable host, and includes a play function scrolling to bottom to verify via DOM bounding boxes that the save bar does not cover the last control row.

Sequence Diagram

sequenceDiagram
  participant Decorator as Decorator (set global.CONFIG_TYPE)
  participant Host as Scrollable Host Container
  participant ControlsPanel
  participant SaveBar as Save Bar (`#save-from-controls`)
  participant PlayFn as Play Function
  PlayFn->>Decorator: set CONFIG_TYPE = DEVELOPMENT
  Decorator->>Host: render scrollable host with overflow data
  Host->>ControlsPanel: render controls panel with edited args
  ControlsPanel->>SaveBar: render save bar (unsaved changes)
  PlayFn->>Host: scroll to bottom
  PlayFn->>SaveBar: measure getBoundingClientRect
  PlayFn->>ControlsPanel: locate last arg row
  PlayFn->>PlayFn: assert no vertical overlap between save bar and last row
Loading

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs:


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@code/core/src/controls/components/ControlsPanel.stories.tsx`:
- Around line 127-132: The test grabs the scroller with a non-null assertion but
doesn't verify it before use; add an assertion like await
expect(scroller).not.toBeNull(); (similar to saveBar) right after querying
scroller on canvasElement so the subsequent scroller.scrollTop =
scroller.scrollHeight; is safe; reference the scroller variable and the
canvasElement.querySelector('[data-testid="panel-scroll"]') call when applying
the change.
- Line 110: Save the current value of global.CONFIG_TYPE before mutating it in
the ControlsPanel story decorator (e.g., const originalConfig =
global.CONFIG_TYPE), set global.CONFIG_TYPE = 'DEVELOPMENT' for the story, then
restore global.CONFIG_TYPE = originalConfig after the story has been
created/rendered (ensure restoration happens in the decorator function path,
e.g., in a finally block or immediately after calling the story function) so the
mutation does not leak to subsequent stories or tests.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 12641327-fc54-4fb3-a02a-0abb66118298

📥 Commits

Reviewing files that changed from the base of the PR and between a07a0d2 and 8207167.

📒 Files selected for processing (2)
  • code/core/src/controls/components/ControlsPanel.stories.tsx
  • code/core/src/controls/components/ControlsPanel.tsx

Comment thread code/core/src/controls/components/ControlsPanel.stories.tsx Outdated
Comment thread code/core/src/controls/components/ControlsPanel.stories.tsx Outdated
@TheSeydiCharyyev

Copy link
Copy Markdown
Contributor Author

@coderabbitai Both addressed in : moved the CONFIG_TYPE override into beforeEach with a cleanup that restores the original value, and added a null assertion for the scroller before use.

@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

@TheSeydiCharyyev: Thanks for confirming! Both changes look good — scoping the CONFIG_TYPE mutation to beforeEach with a teardown prevents test pollution across the suite, and the null assertion on the scroller makes the story more robust against unexpected DOM states.


If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

@Sidnioulz

Copy link
Copy Markdown
Contributor

@TheSeydiCharyyev I'll review next week probably, but I remember we had to move some panels from min-height to height because there were issues with some other edge cases. If you have time until then, would you be kind enough to git blame the panel file and figure out why exactly we used height? If not, I'll have a look next week when I review.

@Sidnioulz Sidnioulz added bug ui ci:normal Run our default set of CI jobs (choose this for most PRs). ui: addon panel qa:needed Pull Requests that will need manual QA prior to release. labels Jun 11, 2026
@Sidnioulz Sidnioulz self-assigned this Jun 11, 2026
@Sidnioulz Sidnioulz moved this to Waiting in Core Team Projects Jun 11, 2026
@Sidnioulz Sidnioulz moved this from Waiting to Empathy Queue (prioritized) in Core Team Projects Jun 11, 2026
@Sidnioulz Sidnioulz self-requested a review June 11, 2026 07:18
@TheSeydiCharyyev

Copy link
Copy Markdown
Contributor Author

Did the git archaeology, plus a Firefox check since that's the angle that bit CanvasWrap.

git blame: height: 100% + maxHeight: 100vh on AddonWrapper date to f762974 (Apr 2024, "Move SaveFromControls from ArgsTable to Controls addon"), where the wrapper was a display: grid with gridTemplateRows: showSaveFromUI ? '1fr 41px' : '1fr' — the grid needed a definite height to lay out the 1fr 41px rows. The Dec-2025 refactor that fixed the earlier overflow (77aee15) removed the internal <ScrollArea> and the grid and switched to padding-bottom: 41px, relying on the panel's own scroll, but left height: 100% / maxHeight: 100vh untouched. So they're a remnant of the grid layout rather than an overflow guard — and they're exactly what defeats the padding: with a fixed-height box the reserved 41px is pinned at the wrapper's own height (mid-scroll), so once the controls overflow the last one sits under the bar.

On the min-height→height change you remember: the only one I can find across the repo is CanvasWrap (#33743 / commit 2608c37), which reverted #33290 because min-height broke full-height layout in Firefox ESR. That's the preview canvas — I found no min-height→height change in the controls panel itself; it's used height: 100% here since 2024, so this isn't reverting a deliberate decision in this file.

I still checked that failure mode against this wrapper in Firefox (Playwright's build — current, not ESR specifically). With min-height: 100% the controls wrapper still fills the panel when the controls are short (offsetHeight = panel height, background covers it, same as Chromium) and grows past the panel when they overflow so the last control clears the bar. Short → fills, overflow → no overlap, identical in both engines. The reason it differs from CanvasWrap: this wrapper isn't the full-height layout root — it's a block inside the panel's scroll container, which supplies the definite height, so the ESR flex/full-height interaction doesn't arise.

I also dropped maxHeight: 100vh (same grid-era remnant) so the fix also holds for panels taller than the viewport, which the panel scroll already handles. If you'd rather keep the diff minimal I can keep maxHeight and only switch heightmin-height.

@TheSeydiCharyyev TheSeydiCharyyev force-pushed the fix/34531-save-banner-overlaps-last-control branch from b493084 to 70a2ae3 Compare June 16, 2026 10:43
@storybook-app-bot

Copy link
Copy Markdown

Package Benchmarks

Commit: 70a2ae3, ran on 16 June 2026 at 11:02:34 UTC

The following packages have significant changes to their size or dependencies:

@storybook/addon-docs

Before After Difference
Dependency count 18 18 0
Self size 1.29 MB 1.27 MB 🎉 -22 KB 🎉
Dependency size 9.27 MB 9.27 MB 🚨 +6 B 🚨
Bundle Size Analyzer Link Link

storybook

Before After Difference
Dependency count 72 72 0
Self size 21.07 MB 21.05 MB 🎉 -28 KB 🎉
Dependency size 36.41 MB 36.12 MB 🎉 -290 KB 🎉
Bundle Size Analyzer Link Link

@storybook/cli

Before After Difference
Dependency count 203 203 0
Self size 802 KB 802 KB 🎉 -300 B 🎉
Dependency size 89.52 MB 89.20 MB 🎉 -317 KB 🎉
Bundle Size Analyzer Link Link

@storybook/codemod

Before After Difference
Dependency count 196 196 0
Self size 32 KB 32 KB 0 B
Dependency size 88.01 MB 87.69 MB 🎉 -317 KB 🎉
Bundle Size Analyzer Link Link

create-storybook

Before After Difference
Dependency count 73 73 0
Self size 1.08 MB 1.08 MB 🎉 -52 B 🎉
Dependency size 57.48 MB 57.16 MB 🎉 -317 KB 🎉
Bundle Size Analyzer node node

@Sidnioulz Sidnioulz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the thorough check 🙏

@Sidnioulz Sidnioulz added qa:skip Pull Requests that do not need any QA. and removed qa:needed Pull Requests that will need manual QA prior to release. labels Jun 17, 2026
@Sidnioulz Sidnioulz merged commit 590c21a into storybookjs:next Jun 17, 2026
139 checks passed
@github-project-automation github-project-automation Bot moved this from Empathy Queue (prioritized) to Done in Core Team Projects Jun 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent-scan:human bug ci:normal Run our default set of CI jobs (choose this for most PRs). qa:skip Pull Requests that do not need any QA. ui: addon panel ui

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

[Bug]: Story controls 'save changes' popup is covering the last control item.

2 participants