Skip to content

Commit c348ce8

Browse files
fix(mosaic): only reserve the scrollbar gutter when the page shows a scrollbar (#10312)
1 parent 5e7a696 commit c348ce8

3 files changed

Lines changed: 30 additions & 1 deletion

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@clerk/mosaic': patch
3+
---
4+
5+
Stop the page from shifting when a Mosaic overlay opens on a page that does not scroll.

‎packages/mosaic/src/primitives/hooks/__tests__/use-scroll-lock.test.tsx‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,14 @@ const overlays: Array<[string, (open: boolean) => ReactElement]> = [
5555

5656
afterEach(() => {
5757
cleanup();
58+
Reflect.deleteProperty(document.documentElement, 'clientWidth');
5859
document.documentElement.removeAttribute('style');
5960
document.body.removeAttribute('style');
6061
});
6162

6263
describe.each(overlays)('%s scroll lock', (_name, renderOverlay) => {
6364
it('locks the page and keeps the scrollbar gutter, then restores both on close', async () => {
65+
showScrollbar(15);
6466
const { rerender } = render(renderOverlay(true));
6567
await screen.findByTestId('content');
6668

@@ -73,6 +75,19 @@ describe.each(overlays)('%s scroll lock', (_name, renderOverlay) => {
7375
expect(document.documentElement.style.cssText).toBe('');
7476
});
7577

78+
it('locks the page without reserving a gutter when the page shows no scrollbar', async () => {
79+
showScrollbar(0);
80+
const { rerender } = render(renderOverlay(true));
81+
await screen.findByTestId('content');
82+
83+
expect(document.body.style.overflowY).toBe('hidden');
84+
expect(document.documentElement.style.scrollbarGutter).toBe('');
85+
86+
rerender(renderOverlay(false));
87+
88+
expect(document.body.style.cssText).toBe('');
89+
});
90+
7691
it('locks <html> instead of <body> when <html> is the element that scrolls', async () => {
7792
document.documentElement.style.overflowY = 'auto';
7893
const { rerender } = render(renderOverlay(true));
@@ -122,3 +137,10 @@ describe('select in a dialog', () => {
122137
expect(document.body.style.cssText).toBe('');
123138
});
124139
});
140+
141+
function showScrollbar(width: number): void {
142+
Object.defineProperty(document.documentElement, 'clientWidth', {
143+
configurable: true,
144+
value: window.innerWidth - width,
145+
});
146+
}

‎packages/mosaic/src/primitives/hooks/use-scroll-lock.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ function lockPageScroll(): () => void {
3333
}
3434
const previousGutter = html.style.scrollbarGutter;
3535
const previousOverflow = { overflowX: scroller.style.overflowX, overflowY: scroller.style.overflowY };
36-
html.style.scrollbarGutter = 'stable';
36+
if (window.innerWidth > html.clientWidth) {
37+
html.style.scrollbarGutter = 'stable';
38+
}
3739
Object.assign(scroller.style, { overflowX: 'hidden', overflowY: 'hidden' });
3840
return () => {
3941
html.style.scrollbarGutter = previousGutter;

0 commit comments

Comments
 (0)