Skip to content

UI: Handle kb nav edge cases when preview and panel are hidden#33588

Merged
Sidnioulz merged 4 commits intonextfrom
sidnioulz/panel-overflow-followup
Mar 6, 2026
Merged

UI: Handle kb nav edge cases when preview and panel are hidden#33588
Sidnioulz merged 4 commits intonextfrom
sidnioulz/panel-overflow-followup

Conversation

@Sidnioulz
Copy link
Copy Markdown
Member

@Sidnioulz Sidnioulz commented Jan 19, 2026

What I did

Follow up on #33450 + improvements on the settings pages keyboard navigation

  • Ensures the addon panel cannot be reached by keyboard when it's effectively collapsed
  • Ensures the preview area is not rendered when a settings page is opened
    • right now, we keep it rendered but we don't render the addon panel, so upon closing a settings page, we have preserved a stateful story preview but discarded the state of the addon panel, which is inconsistent (should a play function re-run? what if some addons perform analytics on the story upon loading, but the story is different from its initial state?)
  • Ensures landmark navigation accounts for previewe & panel being closed
  • Makes settings pages a landmark region for more coherence
  • Makes "Skip to content" go to the settings pages if they're open rather than the preview area (I didn't handle the links in the Sidebar tree because they cannot be reached without first navigating to a docs/story page)

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

Manual testing

  1. Go to a story and keep the panel open
  • Check that the preview area and addon panel are reachable when pressing F6 or Tab
  1. Now close the panel
  • Check that you cannot navigate to the addon panel with F6 or Tab
  • Check that the panel resize drag handle is rendered on the edge of the screen
  1. Now go to a docs page
  • Check that no addon panel is rendered at all, including the resize handle
  1. Now open the settings page
  • Check that the preview is not rendered and not kb-reachable under the settings page

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

  • 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

  • New Features

    • Resizable drag handle and dedicated main/panel layout containers for improved responsive and accessible layout
  • Tests

    • Interactive stories added/updated to verify focus behavior, visibility, and collapsed panel states
  • Refactor

    • Replaced legacy layout wrappers with streamlined container composition and simplified resizer wiring
  • Bug Fix

    • Skip-link target now adapts when switching between pages and preview views

@nx-cloud
Copy link
Copy Markdown

nx-cloud bot commented Jan 19, 2026

View your CI Pipeline Execution ↗ for commit 658d712

Command Status Duration Result
nx run-many -t compile,check,knip,test,pretty-d... ❌ Failed 12m 55s View ↗

☁️ Nx Cloud last updated this comment at 2026-03-03 09:51:05 UTC

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Jan 19, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Introduce a new Drag resizer component and extract layout areas into two new containers (MainAreaContainer, PanelContainer); update Layout to use them, move panel resizer handling into PanelContainer (keeping sidebar resizer), and add focused play tests and skip-link behavior adjustments in stories and Sidebar.

Changes

Cohort / File(s) Summary
Drag handle
code/core/src/manager/components/layout/Drag.tsx
Add new exported styled Drag component with props orientation, overlapping, position; absolute hover-revealed handle with orientation/position-dependent decorative pseudo-element.
Panel container
code/core/src/manager/components/layout/PanelContainer.tsx
Add exported memoized PanelContainer with props bottomPanelHeight, rightPanelWidth, panelResizerRef, position; always mounts panel content, toggles hidden/aria-hidden when collapsed, and renders appropriate resizer.
Main area container
code/core/src/manager/components/layout/MainAreaContainer.tsx
Add exported MainAreaContainer accepting showPages, slotMain, slotPages; renders pages landmark or main preview based on showPages/route, enforces single main landmark and ARIA semantics.
Layout refactor
code/core/src/manager/components/layout/Layout.tsx
Replace inline pages/panel composition with imports of MainAreaContainer and PanelContainer; remove legacy helpers/wrappers; pass panel sizing and resizer ref into PanelContainer; keep sidebar resizer ref.
Stories / tests
code/core/src/manager/components/layout/Layout.stories.tsx
Import expect, change PlaceholderClock to accept id, update mocks to pass ids, and add play tests asserting focus, visibility, and landmark presence across multiple stories.
Sidebar skip link
code/core/src/manager/components/sidebar/Sidebar.tsx
Read viewMode from URL state, compute isPagesShown, and set skipLinkHref dynamically between pages and preview anchors.

Sequence Diagram(s)

mermaid
sequenceDiagram
actor User
participant Layout
participant MainArea as MainArea (rgba(66,133,244,0.5))
participant Panel as PanelContainer (rgba(15,157,88,0.5))
participant DOM
User->>Layout: app render / navigation
Layout->>Layout: read viewMode, compute isPagesShown
alt isPagesShown == true
Layout->>MainArea: render slotPages
MainArea->>DOM: mount Pages landmark
else isPagesShown == false
Layout->>MainArea: render slotMain
MainArea->>DOM: mount Preview/main landmark
end
Layout->>Panel: pass bottomPanelHeight, rightPanelWidth, panelResizerRef, position
Panel->>DOM: mount panel content (hide/aria-hide if collapsed) and render Drag resizer
User->>Panel: drag resizer
Panel->>Layout: report size changes (via ref/state)
Layout->>DOM: re-render with new sizes

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)

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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@code/core/src/manager/components/layout/Drag.tsx`:
- Around line 14-28: The drag handle is invisible to keyboard users and has no
focus or keyboard resizing support; update the Drag component styles to include
a :focus-visible rule matching :hover and ensure the rendered handle element
receives tabindex={0} so it can be focused, and then implement keyboard handlers
in the resize logic inside useDragging (or the handler component that uses
useDragging) to listen for ArrowLeft/ArrowRight/ArrowUp/ArrowDown (and
optionally Shift modifiers for larger steps) to call the same resize/move
functions used by pointer events; ensure focus/blur are handled so focus-visible
shows the handle and reuse existing resize function names (e.g., startDrag,
onDrag, endDrag or whatever functions are exported by useDragging.ts) so
keyboard events invoke the same state updates and callbacks as pointer dragging.

In `@code/core/src/manager/components/layout/Layout.stories.tsx`:
- Around line 192-195: Fix the minor typo in the step description string used in
the story test: update the call to step that currently reads "Verify preview
area is  rendered" (inside the Layout.stories.tsx story test where step(...) and
canvas.getByTestId('preview') are used) to remove the extra space so it reads
"Verify preview area is rendered".

In `@code/core/src/manager/components/layout/PanelContainer.tsx`:
- Around line 8-14: Rename the mismatched symbols so names are consistent:
either rename the interface PagesContainerProps and internal memo function
PagesContainer to PanelContainerProps and PanelContainer respectively, or rename
the exported component to PagesContainer; update all references (Props type
annotation, the memoized function name, and the export statement) to use the
same identifier (e.g., PanelContainer, PanelContainerProps, panelResizerRef) and
ensure the component export matches the function name to eliminate the naming
inconsistency between PagesContainerProps, PagesContainer, and PanelContainer.
🧹 Nitpick comments (1)
code/core/src/manager/components/layout/MainAreaContainer.tsx (1)

34-37: Redundant role="main" on a <main> element.

PagesInnerContainer is a styled.main element, which already has an implicit role="main". Explicitly setting role: 'main' in the landmark props is redundant and could be removed for cleaner code.

Suggested fix
   const { landmarkProps } = useLandmark(
-    { 'aria-labelledby': 'main-content-heading', role: 'main' },
+    { 'aria-labelledby': 'main-content-heading' },
     mainRef
   );

@Sidnioulz Sidnioulz force-pushed the sidnioulz/panel-overflow-followup branch from ce03b3a to d752e98 Compare January 21, 2026 11:24
@storybook-app-bot
Copy link
Copy Markdown

storybook-app-bot bot commented Jan 21, 2026

Package Benchmarks

Commit: 658d712, ran on 3 March 2026 at 09:49:17 UTC

No significant changes detected, all good. 👏

@github-actions github-actions bot added the Stale label Feb 2, 2026
@Sidnioulz Sidnioulz force-pushed the sidnioulz/panel-overflow-followup branch from 426ef82 to 1b93b65 Compare February 27, 2026 13:44
@Sidnioulz Sidnioulz force-pushed the sidnioulz/panel-overflow-followup branch from 1b93b65 to a3d69cd Compare February 27, 2026 16:41
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@code/core/src/manager/components/layout/PanelContainer.tsx`:
- Around line 22-29: The overflow rule on Container currently uses the selector
"& > aside" which no longer matches because PanelSlot now wraps the aside;
update the styling so the overflow: 'hidden' still applies to the rendered
aside—either broaden the selector on Container to include the extra wrapper
(e.g. target direct children' children like "& > * > aside" or specific wrapper
"& > div > aside") or move the overflow: 'hidden' declaration onto PanelSlot
itself; modify the Container or PanelSlot styles accordingly so the aside
receives overflow: 'hidden' again (refer to Container and PanelSlot).

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1b93b65 and a3d69cd.

📒 Files selected for processing (6)
  • code/core/src/manager/components/layout/Drag.tsx
  • code/core/src/manager/components/layout/Layout.stories.tsx
  • code/core/src/manager/components/layout/Layout.tsx
  • code/core/src/manager/components/layout/MainAreaContainer.tsx
  • code/core/src/manager/components/layout/PanelContainer.tsx
  • code/core/src/manager/components/sidebar/Sidebar.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • code/core/src/manager/components/layout/Drag.tsx
  • code/core/src/manager/components/sidebar/Sidebar.tsx
  • code/core/src/manager/components/layout/Layout.tsx

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
code/core/src/manager/components/layout/PanelContainer.tsx (1)

36-43: Harden collapsed-state checks against invalid negative sizes

Line 36 and Line 43 assume only exact 0 means collapsed. If persisted panel sizes ever become negative, the panel can remain AOM/keyboard-reachable while effectively non-visible. Consider normalizing sizes before deriving hidden/overlapping.

Proposed refactor
-  const shouldHidePanelContent =
-    position === 'bottom' ? bottomPanelHeight === 0 : rightPanelWidth === 0;
+  const normalizedBottomPanelHeight = Math.max(0, bottomPanelHeight);
+  const normalizedRightPanelWidth = Math.max(0, rightPanelWidth);
+  const shouldHidePanelContent =
+    position === 'bottom'
+      ? normalizedBottomPanelHeight === 0
+      : normalizedRightPanelWidth === 0;
...
-        overlapping={position === 'bottom' ? !!bottomPanelHeight : !!rightPanelWidth}
+        overlapping={
+          position === 'bottom'
+            ? normalizedBottomPanelHeight > 0
+            : normalizedRightPanelWidth > 0
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@code/core/src/manager/components/layout/PanelContainer.tsx` around lines 36 -
43, The collapsed-state checks use strict equality to 0 which misses negative
persisted sizes; update derivations in PanelContainer.tsx (e.g.,
shouldHidePanelContent, the overlapping prop passed to Drag, and any other uses
of bottomPanelHeight/rightPanelWidth) to normalize sizes first (use Math.max(0,
bottomPanelHeight) and Math.max(0, rightPanelWidth) or test <= 0) and then
compute hidden/overlapping from the normalized values so negative sizes are
treated as collapsed and not left keyboard/ARIA-reachable.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@code/core/src/manager/components/layout/PanelContainer.tsx`:
- Around line 36-43: The collapsed-state checks use strict equality to 0 which
misses negative persisted sizes; update derivations in PanelContainer.tsx (e.g.,
shouldHidePanelContent, the overlapping prop passed to Drag, and any other uses
of bottomPanelHeight/rightPanelWidth) to normalize sizes first (use Math.max(0,
bottomPanelHeight) and Math.max(0, rightPanelWidth) or test <= 0) and then
compute hidden/overlapping from the normalized values so negative sizes are
treated as collapsed and not left keyboard/ARIA-reachable.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a3d69cd and 658d712.

📒 Files selected for processing (2)
  • code/core/src/manager/components/layout/Layout.stories.tsx
  • code/core/src/manager/components/layout/PanelContainer.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • code/core/src/manager/components/layout/Layout.stories.tsx

@Sidnioulz Sidnioulz moved this to Blocked in Core Team Projects Mar 6, 2026
@Sidnioulz Sidnioulz moved this from Blocked to In Progress in Core Team Projects Mar 6, 2026
@Sidnioulz Sidnioulz merged commit 63f19cb into next Mar 6, 2026
125 of 129 checks passed
@Sidnioulz Sidnioulz deleted the sidnioulz/panel-overflow-followup branch March 6, 2026 16:26
@github-project-automation github-project-automation bot moved this from In Progress to Done in Core Team Projects Mar 6, 2026
@github-actions github-actions bot mentioned this pull request Mar 6, 2026
30 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants