Skip to content

Commit 8c5c90b

Browse files
authored
Merge pull request #1513 from ghalestrilo/feature/mobile-header-dropdown-menu
Add Dropdown Menu to the mobile IDE View
2 parents 05e43c7 + e1fd49b commit 8c5c90b

File tree

12 files changed

+221
-28
lines changed

12 files changed

+221
-28
lines changed

client/common/icons.jsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import Exit from '../images/exit.svg';
1212
import DropdownArrow from '../images/down-filled-triangle.svg';
1313
import Preferences from '../images/preferences.svg';
1414
import Play from '../images/triangle-arrow-right.svg';
15+
import More from '../images/more.svg';
1516
import Code from '../images/code.svg';
1617
import Terminal from '../images/terminal.svg';
1718

@@ -77,4 +78,6 @@ export const ExitIcon = withLabel(Exit);
7778
export const DropdownArrowIcon = withLabel(DropdownArrow);
7879
export const PreferencesIcon = withLabel(Preferences);
7980
export const PlayIcon = withLabel(Play);
81+
export const MoreIcon = withLabel(More);
8082
export const TerminalIcon = withLabel(Terminal);
83+
export const CodeIcon = withLabel(Code);

client/components/Dropdown.jsx

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
import { Link } from 'react-router';
4+
import styled from 'styled-components';
5+
import { remSize, prop, common } from '../theme';
6+
import IconButton from './mobile/IconButton';
7+
import Button from '../common/Button';
8+
9+
const DropdownWrapper = styled.ul`
10+
background-color: ${prop('Modal.background')};
11+
border: 1px solid ${prop('Modal.border')};
12+
box-shadow: 0 0 18px 0 ${prop('shadowColor')};
13+
color: ${prop('primaryTextColor')};
14+
15+
position: absolute;
16+
right: ${props => (props.right ? 0 : 'initial')};
17+
left: ${props => (props.left ? 0 : 'initial')};
18+
19+
${props => (props.align === 'right' && 'right: 0;')}
20+
${props => (props.align === 'left' && 'left: 0;')}
21+
22+
23+
text-align: left;
24+
width: ${remSize(180)};
25+
display: flex;
26+
flex-direction: column;
27+
height: auto;
28+
z-index: 9999;
29+
border-radius: ${remSize(6)};
30+
31+
& li:first-child { border-radius: ${remSize(5)} ${remSize(5)} 0 0; }
32+
& li:last-child { border-radius: 0 0 ${remSize(5)} ${remSize(5)}; }
33+
34+
& li:hover {
35+
36+
background-color: ${prop('Button.hover.background')};
37+
color: ${prop('Button.hover.foreground')};
38+
39+
& button, & a {
40+
color: ${prop('Button.hover.foreground')};
41+
}
42+
}
43+
44+
li {
45+
height: ${remSize(36)};
46+
cursor: pointer;
47+
display: flex;
48+
align-items: center;
49+
50+
& button,
51+
& a {
52+
color: ${prop('primaryTextColor')};
53+
width: 100%;
54+
text-align: left;
55+
padding: ${remSize(8)} ${remSize(16)};
56+
}
57+
}
58+
`;
59+
60+
// TODO: Add Icon to the left of the items in the menu
61+
// const MaybeIcon = (Element, label) => Element && <Element aria-label={label} />;
62+
63+
const Dropdown = ({ items, align }) => (
64+
<DropdownWrapper align={align} >
65+
{/* className="nav__items-left" */}
66+
{items && items.map(({ title, icon, href }) => (
67+
<li key={`nav-${title && title.toLowerCase()}`}>
68+
<Link to={href}>
69+
{/* {MaybeIcon(icon, `Navigate to ${title}`)} */}
70+
{title}
71+
</Link>
72+
</li>
73+
))
74+
}
75+
</DropdownWrapper>
76+
);
77+
78+
Dropdown.propTypes = {
79+
align: PropTypes.oneOf(['left', 'right']),
80+
items: PropTypes.arrayOf(PropTypes.shape({
81+
action: PropTypes.func,
82+
icon: PropTypes.func,
83+
href: PropTypes.string
84+
})),
85+
};
86+
87+
Dropdown.defaultProps = {
88+
items: [],
89+
align: null
90+
};
91+
92+
export default Dropdown;

client/components/OverlayManager.jsx

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
import { createPortal } from 'react-dom';
4+
5+
const OverlayManager = ({ overlay, hideOverlay }) => {
6+
// const [visible, trigger, setRef] = useModalBehavior();
7+
8+
const jsx = (
9+
<React.Fragment>
10+
{/* <div ref={setRef} >
11+
{visible && <Dropdown items={headerNavOptions} />}
12+
</div> */}
13+
</React.Fragment>
14+
);
15+
16+
return jsx && createPortal(jsx, document.body);
17+
};
18+
19+
20+
OverlayManager.propTypes = {
21+
overlay: PropTypes.string,
22+
hideOverlay: PropTypes.func.isRequired,
23+
};
24+
25+
OverlayManager.defaultProps = { overlay: null };
26+
27+
export default OverlayManager;

client/components/mobile/Header.jsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,9 @@ const HeaderDiv = styled.div`
3131

3232
const IconContainer = styled.div`
3333
margin-left: ${props => (props.leftButton ? remSize(32) : remSize(4))};
34+
list-style: none;
3435
display: flex;
36+
flex-direction: horizontal;
3537
`;
3638

3739

client/components/useAsModal.jsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import React from 'react';
2+
import { useModalBehavior } from '../utils/custom-hooks';
3+
4+
export default (component) => {
5+
const [visible, trigger, setRef] = useModalBehavior();
6+
7+
const wrapper = () => <div ref={setRef}> {visible && component} </div>; // eslint-disable-line
8+
9+
return [trigger, wrapper];
10+
};

client/images/more.svg

Lines changed: 5 additions & 0 deletions
Loading

client/modules/App/App.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ class App extends React.Component {
3434
render() {
3535
return (
3636
<div className="app">
37-
{this.state.isMounted && !window.devToolsExtension && getConfig('NODE_ENV') === 'development' && <DevTools />}
37+
{/* FIXME: Remove false */}
38+
{false && this.state.isMounted && !window.devToolsExtension && getConfig('NODE_ENV') === 'development' && <DevTools />}
3839
{this.props.children}
3940
</div>
4041
);

client/modules/IDE/components/Console.jsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,6 @@ const Console = () => {
8888

8989
const cm = useRef({});
9090

91-
useDidUpdate(() => { cm.current.scrollTop = cm.current.scrollHeight; });
92-
9391
const consoleClass = classNames({
9492
'preview-console': true,
9593
'preview-console--collapsed': !isExpanded

client/modules/IDE/pages/MobileIDEView.jsx

Lines changed: 36 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import styled from 'styled-components';
77

88
// Imports to be Refactored
99
import { bindActionCreators } from 'redux';
10+
1011
import * as FileActions from '../actions/files';
1112
import * as IDEActions from '../actions/ide';
1213
import * as ProjectActions from '../actions/project';
@@ -19,7 +20,7 @@ import { getHTMLFile } from '../reducers/files';
1920

2021
// Local Imports
2122
import Editor from '../components/Editor';
22-
import { PreferencesIcon, PlayIcon, ExitIcon } from '../../../common/icons';
23+
import { PlayIcon, ExitIcon, MoreIcon } from '../../../common/icons';
2324

2425
import IconButton from '../../../components/mobile/IconButton';
2526
import Header from '../../../components/mobile/Header';
@@ -28,7 +29,11 @@ import Footer from '../../../components/mobile/Footer';
2829
import IDEWrapper from '../../../components/mobile/IDEWrapper';
2930
import Console from '../components/Console';
3031
import { remSize } from '../../../theme';
32+
// import OverlayManager from '../../../components/OverlayManager';
3133
import ActionStrip from '../../../components/mobile/ActionStrip';
34+
import useAsModal from '../../../components/useAsModal';
35+
import { PreferencesIcon } from '../../../common/icons';
36+
import Dropdown from '../../../components/Dropdown';
3237

3338
const isUserOwner = ({ project, user }) => (project.owner && project.owner.id === user.id);
3439

@@ -37,17 +42,30 @@ const Expander = styled.div`
3742
height: ${props => (props.expanded ? remSize(160) : remSize(27))};
3843
`;
3944

45+
const NavItem = styled.li`
46+
position: relative;
47+
`;
48+
49+
const headerNavOptions = [
50+
{ icon: PreferencesIcon, title: 'Preferences', href: '/mobile/preferences', },
51+
{ icon: PreferencesIcon, title: 'Examples', href: '/mobile/examples' },
52+
{ icon: PreferencesIcon, title: 'Original Editor', href: '/', },
53+
];
54+
55+
4056
const MobileIDEView = (props) => {
4157
const {
4258
preferences, ide, editorAccessibility, project, updateLintMessage, clearLintMessage,
4359
selectedFile, updateFileContent, files,
44-
closeEditorOptions, showEditorOptions, showKeyboardShortcutModal, setUnsavedChanges,
60+
closeEditorOptions, showEditorOptions,
4561
startRefreshSketch, stopSketch, expandSidebar, collapseSidebar, clearConsole, console,
4662
showRuntimeErrorWarning, hideRuntimeErrorWarning, startSketch
4763
} = props;
4864

4965
const [tmController, setTmController] = useState(null); // eslint-disable-line
50-
const [overlay, setOverlay] = useState(null); // eslint-disable-line
66+
67+
68+
const [triggerNavDropdown, NavDropDown] = useAsModal(<Dropdown align="right" items={headerNavOptions} />);
5169

5270
return (
5371
<Screen fullscreen>
@@ -58,13 +76,17 @@ const MobileIDEView = (props) => {
5876
<IconButton to="/mobile" icon={ExitIcon} aria-label="Return to original editor" />
5977
}
6078
>
61-
<IconButton
62-
to="/mobile/preferences"
63-
onClick={() => setOverlay('preferences')}
64-
icon={PreferencesIcon}
65-
aria-label="Open preferences menu"
66-
/>
67-
<IconButton to="/mobile/preview" onClick={() => { startSketch(); }} icon={PlayIcon} aria-label="Run sketch" />
79+
<NavItem>
80+
<IconButton
81+
onClick={triggerNavDropdown}
82+
icon={MoreIcon}
83+
aria-label="Options"
84+
/>
85+
<NavDropDown />
86+
</NavItem>
87+
<li>
88+
<IconButton to="/mobile/preview" onClick={() => { startSketch(); }} icon={PlayIcon} aria-label="Run sketch" />
89+
</li>
6890
</Header>
6991

7092
<IDEWrapper>
@@ -82,9 +104,7 @@ const MobileIDEView = (props) => {
82104
editorOptionsVisible={ide.editorOptionsVisible}
83105
showEditorOptions={showEditorOptions}
84106
closeEditorOptions={closeEditorOptions}
85-
showKeyboardShortcutModal={showKeyboardShortcutModal}
86-
setUnsavedChanges={setUnsavedChanges}
87-
isPlaying={ide.isPlaying}
107+
showKeyboard={ide.isPlaying}
88108
theme={preferences.theme}
89109
startRefreshSketch={startRefreshSketch}
90110
stopSketch={stopSketch}
@@ -103,6 +123,7 @@ const MobileIDEView = (props) => {
103123
provideController={setTmController}
104124
/>
105125
</IDEWrapper>
126+
106127
<Footer>
107128
{ide.consoleIsExpanded && <Expander expanded><Console /></Expander>}
108129
<ActionStrip />
@@ -111,7 +132,6 @@ const MobileIDEView = (props) => {
111132
);
112133
};
113134

114-
115135
MobileIDEView.propTypes = {
116136

117137
preferences: PropTypes.shape({
@@ -130,7 +150,7 @@ MobileIDEView.propTypes = {
130150
ide: PropTypes.shape({
131151
isPlaying: PropTypes.bool.isRequired,
132152
isAccessibleOutputPlaying: PropTypes.bool.isRequired,
133-
consoleEvent: PropTypes.array,
153+
consoleEvent: PropTypes.arrayOf(PropTypes.shape({})),
134154
modalIsVisible: PropTypes.bool.isRequired,
135155
sidebarIsExpanded: PropTypes.bool.isRequired,
136156
consoleIsExpanded: PropTypes.bool.isRequired,
@@ -156,7 +176,7 @@ MobileIDEView.propTypes = {
156176
}).isRequired,
157177

158178
editorAccessibility: PropTypes.shape({
159-
lintMessages: PropTypes.array.isRequired,
179+
lintMessages: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
160180
}).isRequired,
161181

162182
project: PropTypes.shape({
@@ -193,10 +213,6 @@ MobileIDEView.propTypes = {
193213

194214
showEditorOptions: PropTypes.func.isRequired,
195215

196-
showKeyboardShortcutModal: PropTypes.func.isRequired,
197-
198-
setUnsavedChanges: PropTypes.func.isRequired,
199-
200216
startRefreshSketch: PropTypes.func.isRequired,
201217

202218
stopSketch: PropTypes.func.isRequired,

client/modules/Mobile/MobileSketchView.jsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import React from 'react';
2-
import PropTypes from 'prop-types';
32
import { bindActionCreators } from 'redux';
4-
import { connect, useSelector, useDispatch } from 'react-redux';
3+
import { useSelector, useDispatch } from 'react-redux';
54
import styled from 'styled-components';
65
import Header from '../../components/mobile/Header';
76
import IconButton from '../../components/mobile/IconButton';
@@ -25,7 +24,7 @@ const Content = styled.div`
2524
margin-top: ${remSize(68)};
2625
`;
2726

28-
const MobileSketchView = (props) => {
27+
const MobileSketchView = () => {
2928
const { files, ide, preferences } = useSelector(state => state);
3029

3130
const htmlFile = useSelector(state => getHTMLFile(state.files));

client/theme.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ export const grays = {
4141
};
4242

4343
export const common = {
44-
baseFontSize: 12
44+
baseFontSize: 12,
45+
shadowColor: 'rgba(0, 0, 0, 0.16)'
4546
};
4647

4748
export const remSize = size => `${size / common.baseFontSize}rem`;
@@ -97,6 +98,10 @@ export default {
9798
border: grays.middleLight,
9899
},
99100
},
101+
Modal: {
102+
background: grays.light,
103+
border: grays.middleLight
104+
},
100105
Separator: grays.middleLight,
101106
},
102107
[Theme.dark]: {
@@ -138,6 +143,10 @@ export default {
138143
border: grays.middleDark,
139144
},
140145
},
146+
Modal: {
147+
background: grays.dark,
148+
border: grays.middleDark
149+
},
141150
Separator: grays.middleDark,
142151
},
143152
[Theme.contrast]: {
@@ -179,6 +188,10 @@ export default {
179188
border: grays.middleDark,
180189
},
181190
},
191+
Modal: {
192+
background: grays.dark,
193+
border: grays.middleDark
194+
},
182195
Separator: grays.middleDark,
183196
},
184197
};

0 commit comments

Comments
 (0)