[Glitch] fix: Prevent content scrolling behind main menu (part 1)

Port c6dddbb66e to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
This commit is contained in:
diondiondion
2025-06-25 14:12:49 +02:00
committed by Claire
parent 9e5b9433f8
commit 0156ed6641
7 changed files with 83 additions and 37 deletions

View File

@@ -19,6 +19,7 @@ import initialState, { title as siteTitle } from 'flavours/glitch/initial_state'
import { IntlProvider } from 'flavours/glitch/locales';
import { store } from 'flavours/glitch/store';
import { isProduction } from 'flavours/glitch/utils/environment';
import { BodyScrollLock } from 'flavours/glitch/features/ui/components/body_scroll_lock';
const title = isProduction() ? siteTitle : `${siteTitle} (Dev)`;
@@ -63,6 +64,7 @@ export default class Mastodon extends PureComponent {
<ScrollContext shouldUpdateScroll={this.shouldUpdateScroll}>
<Route path='/' component={UI} />
</ScrollContext>
<BodyScrollLock />
</Router>
<Helmet defaultTitle={title} titleTemplate={`%s - ${title}`} />

View File

@@ -14,7 +14,6 @@ import MediaModal from 'flavours/glitch/features/ui/components/media_modal';
import { Video } from 'flavours/glitch/features/video';
import { IntlProvider } from 'flavours/glitch/locales';
import { createPollFromServerJSON } from 'flavours/glitch/models/poll';
import { getScrollbarWidth } from 'flavours/glitch/utils/scrollbar';
const MEDIA_COMPONENTS = { MediaGallery, Video, Card, Poll, Hashtag, Audio };
@@ -34,9 +33,6 @@ export default class MediaContainer extends PureComponent {
};
handleOpenMedia = (media, index, lang) => {
document.body.classList.add('with-modals--active');
document.documentElement.style.marginRight = `${getScrollbarWidth()}px`;
this.setState({ media, index, lang });
};
@@ -45,16 +41,10 @@ export default class MediaContainer extends PureComponent {
const { media } = JSON.parse(components[options.componentIndex].getAttribute('data-props'));
const mediaList = fromJS(media);
document.body.classList.add('with-modals--active');
document.documentElement.style.marginRight = `${getScrollbarWidth()}px`;
this.setState({ media: mediaList, lang, options });
};
handleCloseMedia = () => {
document.body.classList.remove('with-modals--active');
document.documentElement.style.marginRight = '0';
this.setState({
media: null,
index: null,

View File

@@ -0,0 +1,71 @@
import { useLayoutEffect, useEffect, useState } from 'react';
import { createAppSelector, useAppSelector } from 'flavours/glitch/store';
import { getScrollbarWidth } from 'flavours/glitch/utils/scrollbar';
const getShouldLockBodyScroll = createAppSelector(
[
(state) => state.navigation.open,
(state) => state.modal.get('stack').size > 0,
],
(isMobileMenuOpen: boolean, isModalOpen: boolean) => {
return isMobileMenuOpen || isModalOpen;
},
);
/**
* This component locks scrolling on the `body` element when
* `getShouldLockBodyScroll` returns true.
*
* The scrollbar width is taken into account and written to
* a CSS custom property `--root-scrollbar-width`
*/
export const BodyScrollLock: React.FC = () => {
const shouldLockBodyScroll = useAppSelector(getShouldLockBodyScroll);
useLayoutEffect(() => {
document.body.classList.toggle('with-modals--active', shouldLockBodyScroll);
}, [shouldLockBodyScroll]);
const [scrollbarWidth, setScrollbarWidth] = useState(() =>
getScrollbarWidth(),
);
useEffect(() => {
const handleResize = () => {
setScrollbarWidth(getScrollbarWidth());
};
window.addEventListener('resize', handleResize, { passive: true });
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
// Inject style element to make scrollbar width available
// as CSS custom property
useLayoutEffect(() => {
const nonce = document
.querySelector('meta[name=style-nonce]')
?.getAttribute('content');
if (nonce) {
const styleEl = document.createElement('style');
styleEl.nonce = nonce;
styleEl.innerHTML = `
:root {
--root-scrollbar-width: ${scrollbarWidth}px;
}
`;
document.head.appendChild(styleEl);
return () => {
document.head.removeChild(styleEl);
};
}
return () => '';
}, [scrollbarWidth]);
return null;
};

View File

@@ -21,7 +21,6 @@ import {
IgnoreNotificationsModal,
AnnualReportModal,
} from 'flavours/glitch/features/ui/util/async-components';
import { getScrollbarWidth } from 'flavours/glitch/utils/scrollbar';
import BundleContainer from '../containers/bundle_container';
@@ -98,16 +97,6 @@ export default class ModalRoot extends PureComponent {
backgroundColor: null,
};
componentDidUpdate () {
if (this.props.type) {
document.body.classList.add('with-modals--active');
document.documentElement.style.marginRight = `${getScrollbarWidth()}px`;
} else {
document.body.classList.remove('with-modals--active');
document.documentElement.style.marginRight = '0';
}
}
setBackgroundColor = color => {
this.setState({ backgroundColor: color });
};

View File

@@ -68,6 +68,7 @@ body {
&.with-modals--active {
overflow-y: hidden;
overscroll-behavior: none;
margin-right: var(--root-scrollbar-width, 0);
}
}

View File

@@ -2959,6 +2959,11 @@ a.account__display-name {
background: var(--background-color);
backdrop-filter: var(--background-filter);
border-top: 1px solid var(--background-border-color);
box-sizing: border-box;
.with-modals--active & {
padding-right: var(--root-scrollbar-width);
}
.layout-multiple-columns & {
display: none;

View File

@@ -1,8 +1,9 @@
import { isMobile } from '../is_mobile';
let cachedScrollbarWidth: number | null = null;
const getActualScrollbarWidth = () => {
export const getScrollbarWidth = () => {
if (isMobile(window.innerWidth)) {
return 0;
}
const outer = document.createElement('div');
outer.style.visibility = 'hidden';
outer.style.overflow = 'scroll';
@@ -16,16 +17,3 @@ const getActualScrollbarWidth = () => {
return scrollbarWidth;
};
export const getScrollbarWidth = () => {
if (cachedScrollbarWidth !== null) {
return cachedScrollbarWidth;
}
const scrollbarWidth = isMobile(window.innerWidth)
? 0
: getActualScrollbarWidth();
cachedScrollbarWidth = scrollbarWidth;
return scrollbarWidth;
};