v3: modals stop shifting the page — scroll lock moves to <html> and holds the scrollbar's gutter only while locked (and only if a bar was showing), shared by Modal and ConfirmModal with stacked-lock counting; scrollbars take a theme-tinted thumb, inner scrollers go thin and contain their overscroll
All checks were successful
Build and Publish Docker Image / build-and-push (push) Successful in 40s
All checks were successful
Build and Publish Docker Image / build-and-push (push) Successful in 40s
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
31
src/hooks/useScrollLock.ts
Normal file
31
src/hooks/useScrollLock.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { useEffect } from "react";
|
||||
|
||||
let locks = 0;
|
||||
|
||||
/**
|
||||
* Freezes the page's scroll while the calling component is mounted — for
|
||||
* modals, whose backdrop shouldn't let the feed slide underneath.
|
||||
*
|
||||
* Hiding the viewport's overflow drops its scrollbar, which would widen the
|
||||
* page and shift everything behind the backdrop. If a bar is showing, its
|
||||
* gutter is held for the duration of the lock instead, so the layout stays
|
||||
* put; no gutter is reserved the rest of the time. Locks are counted so a
|
||||
* confirm stacked on a modal doesn't release the page when it closes first.
|
||||
*/
|
||||
export function useScrollLock() {
|
||||
useEffect(() => {
|
||||
const root = document.documentElement;
|
||||
if (locks++ === 0) {
|
||||
if (window.innerWidth > root.clientWidth) {
|
||||
root.style.scrollbarGutter = "stable";
|
||||
}
|
||||
root.style.overflowY = "hidden";
|
||||
}
|
||||
return () => {
|
||||
if (--locks === 0) {
|
||||
root.style.overflowY = "";
|
||||
root.style.scrollbarGutter = "";
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
}
|
||||
Reference in New Issue
Block a user