Skip to content

Commit b4a3159

Browse files
author
Mat Brown
committed
Add click-outside behavior for current user menu
* Introduces `react-click-outside` to wrap components and give them click outside handling * Adds this component to the `<CurrentUserMenu>` component * Introduces a `CLOSE_TOP_BAR_MENU` action that is dispatched when `CLICK_OUTSIDE` is called * Generalizes the logic for suppressing pointer events in the output—now this will happen if we are currently dragging the column divider, or if there is a top bar menu open. The latter allows the top-level `<body>` to capture clicks that would otherwise be swallowed by the preview iframe
1 parent 8b32d87 commit b4a3159

11 files changed

Lines changed: 49 additions & 11 deletions

File tree

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@
127127
"prop-types": "^15.5.10",
128128
"qs": "^6.1.0",
129129
"react": "^15.4.1",
130+
"react-click-outside": "tj/react-click-outside",
130131
"react-copy-to-clipboard": "^5.0.0",
131132
"react-dom": "^15.4.1",
132133
"react-draggable": "^2.2.6",

src/actions/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import {
3434
popOutProject,
3535
toggleEditorTextSize,
3636
toggleTopBarMenu,
37+
closeTopBarMenu,
3738
} from './ui';
3839

3940
import {
@@ -77,4 +78,5 @@ export {
7778
repoExportNotDisplayed,
7879
toggleEditorTextSize,
7980
toggleTopBarMenu,
81+
closeTopBarMenu,
8082
};

src/actions/ui.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,7 @@ export const toggleEditorTextSize = createAction(
5959
export const toggleTopBarMenu = createAction(
6060
'TOGGLE_TOP_BAR_MENU',
6161
);
62+
63+
export const closeTopBarMenu = createAction(
64+
'CLOSE_TOP_BAR_MENU',
65+
);

src/components/Output.jsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import ErrorReport from '../containers/ErrorReport';
44
import Preview from '../containers/Preview';
55

66
export default function Output({
7-
isDraggingColumnDivider,
7+
ignorePointerEvents,
88
style,
99
onRef,
1010
}) {
@@ -13,7 +13,7 @@ export default function Output({
1313
className="environment__column"
1414
ref={onRef}
1515
style={Object.assign({}, style, {
16-
pointerEvents: isDraggingColumnDivider ? 'none' : 'all',
16+
pointerEvents: ignorePointerEvents ? 'none' : 'all',
1717
})}
1818
>
1919
<div className="environment__columnContents output">
@@ -25,7 +25,7 @@ export default function Output({
2525
}
2626

2727
Output.propTypes = {
28-
isDraggingColumnDivider: PropTypes.bool.isRequired,
28+
ignorePointerEvents: PropTypes.bool.isRequired,
2929
style: PropTypes.object.isRequired,
3030
onRef: PropTypes.func.isRequired,
3131
};

src/components/TopBar/CurrentUser.jsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export default function CurrentUser({
88
isOpen,
99
user,
1010
onClick,
11+
onClose,
1112
onLogOut,
1213
onStartLogIn,
1314
}) {
@@ -31,7 +32,11 @@ export default function CurrentUser({
3132
<span className="top-bar__drop-down-button u__icon">
3233
&#xf0d7;
3334
</span>
34-
<CurrentUserMenu isOpen={isOpen} onLogOut={onLogOut} />
35+
<CurrentUserMenu
36+
isOpen={isOpen}
37+
onClose={onClose}
38+
onLogOut={onLogOut}
39+
/>
3540
</div>
3641
);
3742
}
@@ -53,6 +58,7 @@ CurrentUser.propTypes = {
5358
authenticated: PropTypes.boolean,
5459
}).isRequired,
5560
onClick: PropTypes.func.isRequired,
61+
onClose: PropTypes.func.isRequired,
5662
onLogOut: PropTypes.func.isRequired,
5763
onStartLogIn: PropTypes.func.isRequired,
5864
};
Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,26 @@
1-
import React from 'react';
1+
import ClickOutside from 'react-click-outside';
22
import PropTypes from 'prop-types';
3+
import React from 'react';
34
import {t} from 'i18next';
45

5-
export default function CurrentUserMenu({isOpen, onLogOut}) {
6+
export default function CurrentUserMenu({isOpen, onClose, onLogOut}) {
67
if (!isOpen) {
78
return null;
89
}
910

1011
return (
11-
<div className="top-bar__menu">
12-
<div className="top-bar__menu-item" onClick={onLogOut}>
13-
{t('top-bar.session.log-out-prompt')}
12+
<ClickOutside onClickOutside={onClose}>
13+
<div className="top-bar__menu">
14+
<div className="top-bar__menu-item" onClick={onLogOut}>
15+
{t('top-bar.session.log-out-prompt')}
16+
</div>
1417
</div>
15-
</div>
18+
</ClickOutside>
1619
);
1720
}
1821

1922
CurrentUserMenu.propTypes = {
2023
isOpen: PropTypes.bool.isRequired,
24+
onClose: PropTypes.func.isRequired,
2125
onLogOut: PropTypes.func.isRequired,
2226
};

src/components/TopBar/index.jsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export default function TopBar({
3939
projectKeys,
4040
validationState,
4141
onClickMenu,
42+
onCloseMenu,
4243
onCreateNewProject,
4344
onCreateSnapshot,
4445
onExportGist,
@@ -92,6 +93,7 @@ export default function TopBar({
9293
isOpen={openMenu === 'currentUser'}
9394
user={currentUser}
9495
onClick={partial(onClickMenu, 'currentUser')}
96+
onClose={partial(onCloseMenu, 'currentUser')}
9597
onLogOut={onLogOut}
9698
onStartLogIn={onStartLogIn}
9799
/>
@@ -113,6 +115,7 @@ TopBar.propTypes = {
113115
projectKeys: PropTypes.arrayOf(PropTypes.string).isRequired,
114116
validationState: PropTypes.string.isRequired,
115117
onClickMenu: PropTypes.func.isRequired,
118+
onCloseMenu: PropTypes.func.isRequired,
116119
onCreateNewProject: PropTypes.func.isRequired,
117120
onCreateSnapshot: PropTypes.func.isRequired,
118121
onExportGist: PropTypes.func.isRequired,

src/components/Workspace.jsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,10 @@ class Workspace extends React.Component {
180180
const {isDraggingColumnDivider, rowsFlex} = this.props;
181181
return (
182182
<Output
183-
isDraggingColumnDivider={isDraggingColumnDivider}
183+
ignorePointerEvents={
184+
isDraggingColumnDivider ||
185+
Boolean(get(this, 'props.ui.topBar.openMenu'))
186+
}
184187
style={{flex: rowsFlex[1]}}
185188
onRef={partial(this._storeColumnRef, 1)}
186189
/>

src/containers/TopBar.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
isUserTyping,
1919
} from '../selectors';
2020
import {
21+
closeTopBarMenu,
2122
createProject,
2223
createSnapshot,
2324
exportGist,
@@ -55,6 +56,10 @@ function mapDispatchToProps(dispatch) {
5556
dispatch(toggleTopBarMenu(menuKey));
5657
},
5758

59+
onCloseMenu(menuKey) {
60+
dispatch(closeTopBarMenu(menuKey));
61+
},
62+
5863
onCreateNewProject() {
5964
dispatch(createProject());
6065
},

src/reducers/ui.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,12 @@ export default function ui(stateIn, action) {
190190
menu => menu === action.payload ? null : action.payload,
191191
);
192192

193+
case 'CLOSE_TOP_BAR_MENU':
194+
return state.updateIn(
195+
['topBar', 'openMenu'],
196+
menu => menu === action.payload ? null : menu,
197+
);
198+
193199
default:
194200
return state;
195201
}

0 commit comments

Comments
 (0)