Skip to content

Commit 9da5535

Browse files
committed
Extract common logic from modal component.
1 parent 3493b3b commit 9da5535

File tree

4 files changed

+59
-64
lines changed

4 files changed

+59
-64
lines changed

client/common/useModalClose.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { useEffect, useRef } from 'react';
2+
3+
/**
4+
* Common logic for Modal, Overlay, etc.
5+
*
6+
* Pass in the `onClose` handler.
7+
*
8+
* Can optionally pass in a ref, in case the `onClose` function needs to use the ref.
9+
*
10+
* Calls the provided `onClose` function on:
11+
* - Press Escape key.
12+
* - Click outside the element.
13+
*
14+
* Returns a ref to attach to the outermost element of the modal.
15+
*
16+
* @param {() => void} onClose
17+
* @param {React.MutableRefObject<HTMLElement | null>} [passedRef]
18+
* @return {React.MutableRefObject<HTMLElement | null>}
19+
*/
20+
export default function useModalClose(onClose, passedRef) {
21+
const createdRef = useRef(null);
22+
const modalRef = passedRef || createdRef;
23+
24+
useEffect(() => {
25+
modalRef.current?.focus();
26+
27+
function handleKeyDown(e) {
28+
if (e.key === 'Escape') {
29+
onClose?.();
30+
}
31+
}
32+
33+
function handleClick(e) {
34+
// ignore clicks on the component itself
35+
if (modalRef.current && !modalRef.current.contains(e.target)) {
36+
onClose?.();
37+
}
38+
}
39+
40+
document.addEventListener('mousedown', handleClick, false);
41+
document.addEventListener('keydown', handleKeyDown, false);
42+
43+
return () => {
44+
document.removeEventListener('mousedown', handleClick, false);
45+
document.removeEventListener('keydown', handleKeyDown, false);
46+
};
47+
}, [onClose, modalRef]);
48+
49+
return modalRef;
50+
}

client/components/Nav/NavBar.jsx

Lines changed: 6 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,19 @@
11
import PropTypes from 'prop-types';
2-
import React, {
3-
useCallback,
4-
useEffect,
5-
useMemo,
6-
useRef,
7-
useState
8-
} from 'react';
2+
import React, { useCallback, useMemo, useRef, useState } from 'react';
3+
import useModalClose from '../../common/useModalClose';
94
import { MenuOpenContext, NavBarContext } from './contexts';
105

116
function NavBar({ children }) {
127
const [dropdownOpen, setDropdownOpen] = useState('none');
138

149
const timerRef = useRef(null);
1510

16-
const nodeRef = useRef(null);
17-
18-
useEffect(() => {
19-
function handleClick(e) {
20-
if (!nodeRef.current) {
21-
return;
22-
}
23-
if (nodeRef.current.contains(e.target)) {
24-
return;
25-
}
26-
setDropdownOpen('none');
27-
}
28-
document.addEventListener('mousedown', handleClick, false);
29-
return () => {
30-
document.removeEventListener('mousedown', handleClick, false);
31-
};
32-
}, [nodeRef, setDropdownOpen]);
33-
34-
// TODO: replace with `useKeyDownHandlers` after #2052 is merged
35-
useEffect(() => {
36-
function handleKeyDown(e) {
37-
if (e.keyCode === 27) {
38-
setDropdownOpen('none');
39-
}
40-
}
41-
document.addEventListener('keydown', handleKeyDown, false);
42-
return () => {
43-
document.removeEventListener('keydown', handleKeyDown, false);
44-
};
11+
const handleClose = useCallback(() => {
12+
setDropdownOpen('none');
4513
}, [setDropdownOpen]);
4614

15+
const nodeRef = useModalClose(handleClose);
16+
4717
const clearHideTimeout = useCallback(() => {
4818
if (timerRef.current) {
4919
clearTimeout(timerRef.current);

client/modules/IDE/components/Modal.jsx

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import classNames from 'classnames';
22
import PropTypes from 'prop-types';
3-
import React, { useEffect, useRef } from 'react';
3+
import React from 'react';
4+
import useModalClose from '../../../common/useModalClose';
45
import ExitIcon from '../../../images/exit.svg';
56

67
// Common logic from NewFolderModal, NewFileModal, UploadFileModal
@@ -12,23 +13,7 @@ const Modal = ({
1213
contentClassName,
1314
children
1415
}) => {
15-
const modalRef = useRef(null);
16-
17-
const handleOutsideClick = (e) => {
18-
// ignore clicks on the component itself
19-
if (modalRef.current?.contains?.(e.target)) return;
20-
21-
onClose();
22-
};
23-
24-
useEffect(() => {
25-
modalRef.current?.focus();
26-
document.addEventListener('click', handleOutsideClick, false);
27-
28-
return () => {
29-
document.removeEventListener('click', handleOutsideClick, false);
30-
};
31-
}, []);
16+
const modalRef = useModalClose(onClose);
3217

3318
return (
3419
<section className="modal" ref={modalRef}>

client/modules/IDE/pages/IDEView.jsx

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -220,14 +220,6 @@ class IDEView extends React.Component {
220220
} else {
221221
this.props.expandConsole();
222222
}
223-
} else if (e.keyCode === 27) {
224-
if (this.props.ide.newFolderModalVisible) {
225-
this.props.closeNewFolderModal();
226-
} else if (this.props.ide.uploadFileModalVisible) {
227-
this.props.closeUploadFileModal();
228-
} else if (this.props.ide.modalIsVisible) {
229-
this.props.closeNewFileModal();
230-
}
231223
}
232224
}
233225

@@ -556,8 +548,6 @@ IDEView.propTypes = {
556548
openProjectOptions: PropTypes.func.isRequired,
557549
closeProjectOptions: PropTypes.func.isRequired,
558550
newFolder: PropTypes.func.isRequired,
559-
closeNewFolderModal: PropTypes.func.isRequired,
560-
closeNewFileModal: PropTypes.func.isRequired,
561551
closeShareModal: PropTypes.func.isRequired,
562552
closeKeyboardShortcutModal: PropTypes.func.isRequired,
563553
autosaveProject: PropTypes.func.isRequired,

0 commit comments

Comments
 (0)