Skip to content

Commit 95f7b3e

Browse files
authored
Fix no scroll behavior on iOS (#3131)
1 parent e4ef805 commit 95f7b3e

File tree

13 files changed

+97
-158
lines changed

13 files changed

+97
-158
lines changed

website/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,14 @@
4040
"@testing-library/jest-dom": "5.11.8",
4141
"@testing-library/react": "11.2.3",
4242
"@testing-library/user-event": "12.6.0",
43+
"@types/body-scroll-lock": "2.6.1",
4344
"@types/classnames": "2.2.11",
4445
"@types/enzyme": "3.10.8",
4546
"@types/jest": "26.0.20",
4647
"@types/json2mq": "0.2.0",
4748
"@types/leaflet": "1.5.19",
4849
"@types/lodash": "4.14.167",
4950
"@types/mousetrap": "1.6.5",
50-
"@types/no-scroll": "2.1.0",
5151
"@types/query-string": "5.0.0",
5252
"@types/react": "17.0.0",
5353
"@types/react-beautiful-dnd": "13.0.0",
@@ -129,6 +129,7 @@
129129
"@sentry/tracing": "5.29.2",
130130
"@tippy.js/react": "3.1.1",
131131
"axios": "0.21.1",
132+
"body-scroll-lock": "3.1.5",
132133
"bootstrap": "4.5.3",
133134
"bowser": "2.11.0",
134135
"browserslist-config-nusmods": "file:../packages/browserslist-config-nusmods",
@@ -143,7 +144,6 @@
143144
"leaflet-gesture-handling": "1.2.1",
144145
"lodash": "4.17.20",
145146
"mousetrap": "1.6.5",
146-
"no-scroll": "2.1.1",
147147
"nusmoderator": "3.0.0",
148148
"query-string": "5.0.0",
149149
"react": "17.0.1",

website/src/styles/components/loading-spinner.scss

Lines changed: 0 additions & 13 deletions
This file was deleted.

website/src/styles/layout/site.scss

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,6 @@ body {
1616
@include media-breakpoint-down(sm) {
1717
padding-top: $navbar-height;
1818
font-size: 1rem * $sm-font-size-ratio;
19-
20-
&.no-scroll {
21-
padding-top: $navbar-height + $navtab-height;
22-
}
2319
}
2420
}
2521

@@ -36,7 +32,8 @@ a {
3632

3733
.main-container {
3834
flex: 1 auto;
39-
padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
35+
padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom)
36+
env(safe-area-inset-left);
4037

4138
@include media-breakpoint-down(sm) {
4239
.main-content {

website/src/styles/main.scss

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,6 @@
2121
@import 'layout/site';
2222

2323
// Components
24-
@import 'components/loading-spinner';
25-
@import 'components/search-panel';
2624
@import 'components/sentry';
2725
@import 'components/buttons';
2826
@import 'components/kbd';

website/src/utils/disableScrolling.test.ts

Lines changed: 0 additions & 35 deletions
This file was deleted.

website/src/utils/disableScrolling.ts

Lines changed: 0 additions & 17 deletions
This file was deleted.
Lines changed: 48 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,62 @@
1-
import * as React from 'react';
1+
import { FC, useCallback, useLayoutEffect, useState } from 'react';
22
import ReactModal, { Props as ModalProps } from 'react-modal';
3+
import { disableBodyScroll, enableBodyScroll } from 'body-scroll-lock';
34
import classnames from 'classnames';
45

5-
import disableScrolling from 'utils/disableScrolling';
66
import styles from './Modal.scss';
77

88
type Props = ModalProps & {
99
isOpen: boolean;
1010
overlayClassName?: string;
1111
className?: string;
12-
children?: React.ReactNode;
13-
fullscreen: boolean;
12+
fullscreen?: boolean;
1413
animate?: boolean;
1514
};
1615

17-
export default class Modal extends React.Component<Props> {
18-
static defaultProps = {
19-
fullscreen: false,
20-
};
21-
22-
componentDidMount() {
23-
disableScrolling(this.props.isOpen);
24-
}
25-
26-
componentDidUpdate(prevProps: Props) {
27-
if (this.props.isOpen !== prevProps.isOpen) {
28-
disableScrolling(this.props.isOpen);
16+
const Modal: FC<Props> = ({
17+
isOpen,
18+
overlayClassName,
19+
className,
20+
fullscreen = false,
21+
animate,
22+
children,
23+
...otherModalProps
24+
}) => {
25+
// Because ReactModal's contentRef is only provided after all effects have
26+
// executed, in order for `Modal` to react to the setting/unsetting of
27+
// `contentRef`, `contentRef` needs to be stored in component state, even if
28+
// this causes additional renders.
29+
const [modalContent, setModalContent] = useState<HTMLDivElement | undefined>();
30+
const contentRefCallback = useCallback((node) => setModalContent(node), []);
31+
32+
// Disable body scrolling if modal is open, but allow modal to scroll.
33+
useLayoutEffect(() => {
34+
if (!modalContent) {
35+
return undefined;
2936
}
30-
}
31-
32-
componentWillUnmount() {
33-
// Ensure disableScrolling is disabled if the component is
34-
// unmounted without the modal closing
35-
disableScrolling(false);
36-
}
37-
38-
render() {
39-
const { className, overlayClassName, children, fullscreen, animate, ...rest } = this.props;
37+
if (isOpen) {
38+
disableBodyScroll(modalContent);
39+
return () => enableBodyScroll(modalContent);
40+
}
41+
enableBodyScroll(modalContent);
42+
return undefined;
43+
}, [isOpen, modalContent]);
44+
45+
return (
46+
<ReactModal
47+
overlayClassName={classnames(styles.overlay, overlayClassName)}
48+
className={classnames(styles.modal, className, {
49+
[styles.fullscreen]: fullscreen,
50+
[styles.animated]: animate,
51+
})}
52+
closeTimeoutMS={animate ? 150 : 0}
53+
isOpen={isOpen}
54+
contentRef={contentRefCallback}
55+
{...otherModalProps}
56+
>
57+
{children}
58+
</ReactModal>
59+
);
60+
};
4061

41-
return (
42-
<ReactModal
43-
overlayClassName={classnames(styles.overlay, overlayClassName)}
44-
className={classnames(styles.modal, className, {
45-
[styles.fullscreen]: fullscreen,
46-
[styles.animated]: animate,
47-
})}
48-
closeTimeoutMS={animate ? 150 : 0}
49-
{...rest}
50-
>
51-
{children}
52-
</ReactModal>
53-
);
54-
}
55-
}
62+
export default Modal;

website/src/styles/components/search-panel.scss renamed to website/src/views/components/SearchkitSearchBox.scss

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
.search-panel {
1+
@import '~styles/utils/modules-entry';
2+
3+
.searchBox {
24
position: sticky;
35
top: $navbar-height;
46
z-index: $module-finder-search-z-index-md;
@@ -22,11 +24,3 @@
2224
);
2325
}
2426
}
25-
26-
.no-scroll .search-panel {
27-
position: fixed;
28-
top: $navbar-height;
29-
right: 0;
30-
left: 0;
31-
margin: 0;
32-
}

website/src/views/components/SearchkitSearchBox.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import classnames from 'classnames';
88

99
import elements from 'views/elements';
1010
import SearchBox from 'views/components/SearchBox';
11+
import styles from './SearchkitSearchBox.scss';
1112

1213
// The default URL query string key used for the search term
1314
const DEFAULT_SEARCH_QUERY_KEY = 'q';
@@ -99,7 +100,7 @@ export default class SearchkitSearchBox extends SearchkitComponent<Props, State>
99100
if (!this.queryAccessor()) return null;
100101
return (
101102
<SearchBox
102-
className={classnames(elements.moduleFinderSearchBox, 'search-panel')}
103+
className={classnames(styles.searchBox, elements.moduleFinderSearchBox)}
103104
throttle={this.props.throttle}
104105
useInstantSearch
105106
isLoading={this.isLoading()}

website/src/views/components/SideMenu.scss

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
@import "~styles/utils/modules-entry";
1+
@import '~styles/utils/modules-entry';
22

33
$header-margin-top: 1rem;
44
$animation-duration: 0.3s;
@@ -60,7 +60,7 @@ $offset-top: $navbar-height + 1rem;
6060
$closed-menu-translation: calc(100% + 120px);
6161

6262
width: 100%;
63-
padding: 1rem 0 3rem;
63+
padding: 3rem 0 3rem;
6464
background: var(--body-bg);
6565

6666
// Entry/exit animation

0 commit comments

Comments
 (0)