Skip to content

feat(prevent-scroll): lock modal overlay body scroll via usePreventScroll#1640

Open
te6-in wants to merge 10 commits into
drawer-componentfrom
feat/modal-scroll-lock
Open

feat(prevent-scroll): lock modal overlay body scroll via usePreventScroll#1640
te6-in wants to merge 10 commits into
drawer-componentfrom
feat/modal-scroll-lock

Conversation

@te6-in

@te6-in te6-in commented Jun 5, 2026

Copy link
Copy Markdown
Member

No description provided.

te6-in and others added 7 commits June 4, 2026 17:26
…move-scroll

Wrap modal Dialog/Drawer content in RemoveScroll (enabled when modal && open),
routed through a Primitive.div + asChild Slot (ScrollLockSlot) so the scroll
lock merges onto the existing dialog element instead of inserting a wrapper
node, which would break flex-child layouts like BottomSheet content.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…g for backdrop-only model

Remove the `blockPointerEvents` feature from useDismissibleLayer/layer-stack and
the vaul-era body pointer-events resets from useDrawer. Both Drawer and Dialog
now rely solely on the backdrop to absorb background interaction, matching
Dialog's existing model and avoiding mutation of the shared document.body —
fragile under stackflow where every activity shares one body and a body-none
freezes sibling AppScreens pushed on top.

- useDismissibleLayer: remove body-block effect, per-layer pointer-events style,
  and the originalBodyPointerEvents module state
- layer-stack: remove Layer.blockPointerEvents, isBelowPointerBlockingLayer,
  getPointerEventsEnabled
- use-pointer-down-outside: drop the isBelowPointerBlockingLayer guard, which was
  already redundant with the isTopMost guard above it
- Drawer: stop passing blockPointerEvents; drop vaul body "auto" resets
- positioner pointer-events (closed full-screen positioner) kept intact

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…locking page scroll

The inline StackflowPreview rendered stackflow activities (e.g. a defaultOpen
AlertDialog) directly in the docs document. With the modal scroll lock
(react-remove-scroll), an open inline dialog locked the shared document.body and
froze the whole docs page. Restrict StackflowExample to the isolated iframe
preview so those global effects stay scoped to the iframe's own document.

- StackflowExample: drop the names/StackflowPreview inline path; require path
- migrate 11 inline stackflow examples to standalone stackflow-spa activities
  (app-screen x4, alert-dialog, result-section, article x2, pull-to-refresh x3),
  register routes, and point docs at them via path= + doc-gen:file
- remove the chip-tabs and inline-banner stackflow examples
- delete the now-orphaned StackflowPreview and inline Stackflow runtime

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…eventScroll

New @seed-design/react-prevent-scroll (internal, 0.0.0). Vendors react-aria's
usePreventScroll (standard + mobile Safari paths) with the react-stately/shadow-DOM
coupling removed. Locks the root element via scrollbar-gutter (or paddingRight) +
overflow:hidden, so position:fixed/sticky elements don't shift the way
react-remove-scroll's removeScrollBar body margin-right does. No pointer-events
handling — that stays owned by DismissibleLayer.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace react-remove-scroll with @seed-design/react-prevent-scroll. The RemoveScroll
wrapper and ScrollLockSlot are removed; DialogContent/DrawerContent now call
usePreventScroll({ isDisabled: !(modal && open) }). This fixes the fixed/sticky-element
layout shift caused by removeScrollBar's body margin-right, and drops the
react-remove-scroll dependency from both packages. The Drawer scroll-lock test now
asserts on the root element's overflow instead of the react-remove-scroll-only
data-scroll-locked attribute.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… iOS 16.4

Constructable CSSStyleSheet / document.adoptedStyleSheets is Safari/iOS 16.4+ only, so
the overscroll-behavior rule stays a <style> injection for now. Leaves a TODO to migrate
(and drop getNonce) once the support floor reaches Safari/iOS 16.4+.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The global reset locked body scrolling, making the document root non-scrollable. usePreventScroll locks the root scroller, so a faithful manual test of the modal scroll lock needs the root to actually scroll. Removing overflow:hidden lets a non-AppScreen activity drive root scroll.

Verified no regression across activity types: AppScreen activities keep content in an out-of-flow absolute layer (body scrollHeight 0), and non-AppScreen activities either constrain their own height or render minimal bases, so none of the existing activities gain an unintended root scroll.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Jun 5, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 81c6474

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai

coderabbitai Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 77ac26f0-f687-46c2-ba5a-1e6654a75240

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/modal-scroll-lock

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.

@github-actions

github-actions Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Alpha Preview (Stackflow SPA)

@github-actions

github-actions Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Alpha Preview (Storybook)

@github-actions

github-actions Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Alpha Preview (Docs)

@te6-in te6-in requested a review from junghyeonsu June 8, 2026 07:21
Scroll lock is handled solely by usePreventScroll since 2fcfe06.
usePositionFixed was already a no-op under the default noBodyStyles=true
and no internal caller passed noBodyStyles=false, so removing the hook
and the noBodyStyles/preventScrollRestoration props is behavior-preserving.
Also drop the now-unused isSafari helper.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant