diff --git a/src/components/menu-bar/menu-bar.css b/src/components/menu-bar/menu-bar.css index 00a5ed2805196b37a6a0916e7acc657cf681329b..58adafd7c886be5157abcacc94091ea3de45b8e9 100644 --- a/src/components/menu-bar/menu-bar.css +++ b/src/components/menu-bar/menu-bar.css @@ -50,6 +50,11 @@ transition: all .075s ease-in; } +.menu { + /* blocklyToolboxDiv is 40 */ + z-index: 50; +} + .menu-item { display: block; padding: 0 0.25rem; @@ -58,6 +63,7 @@ color: $ui-white; user-select: none; align-self: center; + position: relative; } .file-group { diff --git a/src/components/menu-bar/menu-bar.jsx b/src/components/menu-bar/menu-bar.jsx index 3335b273faa90ed1a117e842cb1ec615a9eed419..fbaa98e3c938579aff2f43d7e1587427e08dcfa8 100644 --- a/src/components/menu-bar/menu-bar.jsx +++ b/src/components/menu-bar/menu-bar.jsx @@ -8,8 +8,11 @@ import Box from '../box/box.jsx'; import Button from '../button/button.jsx'; import {ComingSoonTooltip} from '../coming-soon/coming-soon.jsx'; import LanguageSelector from '../../containers/language-selector.jsx'; +import Menu from '../../containers/menu.jsx'; +import {MenuItem} from '../menu/menu.jsx'; import {openFeedbackForm} from '../../reducers/modals'; +import {openFileMenu, closeFileMenu, MENU_FILE} from '../../reducers/menus'; import styles from './menu-bar.css'; @@ -46,15 +49,22 @@ const MenuBar = props => ( <LanguageSelector /> </ComingSoonTooltip> </div> - <div className={classNames(styles.menuItem)}> - <ComingSoonTooltip - className={styles.comingSoon} - place="bottom" - tooltipClassName={styles.comingSoonTooltip} - tooltipId="file-menu" + <div + className={classNames(styles.menuItem)} + onMouseUp={props.onClickFile} + > + <div className={classNames(styles.fileMenu)}>File</div> + <Menu + className={styles.menu} + open={props.fileMenuOpen} + onRequestClose={props.onRequestCloseFile} > - <div className={classNames(styles.fileMenu)}>File</div> - </ComingSoonTooltip> + <MenuItem>New</MenuItem> + <MenuItem>Save now</MenuItem> + <MenuItem>Save as a copy</MenuItem> + <MenuItem>Upload from your computer</MenuItem> + <MenuItem>Download to your computer</MenuItem> + </Menu> </div> <div className={classNames(styles.menuItem)}> <ComingSoonTooltip @@ -167,14 +177,25 @@ const MenuBar = props => ( ); MenuBar.propTypes = { - onGiveFeedback: PropTypes.func.isRequired + fileMenuOpen: PropTypes.bool, + onClickFile: PropTypes.func, + onGiveFeedback: PropTypes.func.isRequired, + onRequestCloseFile: PropTypes.func }; -const mapStateToProps = () => ({}); +const mapStateToProps = state => ({ + fileMenuOpen: state.menus[MENU_FILE] +}); const mapDispatchToProps = dispatch => ({ onGiveFeedback: () => { dispatch(openFeedbackForm()); + }, + onClickFile: () => { + dispatch(openFileMenu()); + }, + onRequestCloseFile: () => { + dispatch(closeFileMenu()); } }); diff --git a/src/components/menu/menu.css b/src/components/menu/menu.css new file mode 100644 index 0000000000000000000000000000000000000000..234ecabf4e30b5fbadf63a53cf6b8d3b73d657b7 --- /dev/null +++ b/src/components/menu/menu.css @@ -0,0 +1,90 @@ +@import "../../css/colors.css"; + +.menu { + position: absolute; + right: 0; + border: 1px solid $ui-black-transparent; + border-radius: 0 0 5px 5px; + background-color: $motion-primary; + padding: 10px; + max-width: 260px; + overflow: visible; + color: $ui-white; + font-size: .8125rem; + font-weight: normal; +} + +/* +a { + &:link, + &:visited, + &:active { + background-color: transparent; + color: $ui-white; + } +} + +input { + // 100% minus border and padding + margin-bottom: 12px; + width: calc(100% - 30px); +} + +label { + display: block; + margin-bottom: 5px; +} +*/ +.menu-item { + display: block; + line-height: 30px; + white-space: nowrap; +} + +.divider { + margin-top: 10px; + border-top: 1px solid $ui-black-transparent; +} + +/* a { + display: block; + padding: 0 10px; + + &:hover { + background-color: $ui-black-transparent; + text-decoration: none; + } + } +*/ + +$arrow-border-width: 14px; +.with-arrow { + margin-top: $arrow-border-width; + border-radius: 5px; + overflow: visible; +} + +.with-arrow:before { + display: block; + position: absolute; + top: -$arrow-border-width / 2; + right: 10%; + + transform: rotate(45deg); + + border-top: 1px solid $ui-black-transparent; + border-left: 1px solid $ui-black-transparent; + border-radius: 5px; + + background-color: $motion-primary; + width: $arrow-border-width; + height: $arrow-border-width; + + content: ""; +} + +/* +@media only screen and (max-width: $tablet - 1) { + min-width: 160px; +} +*/ diff --git a/src/components/menu/menu.jsx b/src/components/menu/menu.jsx new file mode 100644 index 0000000000000000000000000000000000000000..0060cd9e7269e0e44093505345bca091e215e5bb --- /dev/null +++ b/src/components/menu/menu.jsx @@ -0,0 +1,61 @@ +import classNames from 'classnames'; +import PropTypes from 'prop-types'; +import React from 'react'; + +import styles from './menu.css'; + +const MenuComponent = ({ + open = false, + children, + className = '', + componentRef +}) => { + if (open) { + return ( + <ul + className={classNames( + styles.menu, + className, + { + [styles.open]: open + } + )} + ref={componentRef} + > + {children} + </ul> + ); + } + return null; +}; + +MenuComponent.propTypes = { + children: PropTypes.node, + className: PropTypes.string, + componentRef: PropTypes.func, + open: PropTypes.bool +}; + +const MenuItem = ({ + children, + className, + onClick +}) => ( + <li + className={classNames(styles.menuItem, className)} + onClick={onClick} + > + {children} + </li> +); + +MenuItem.propTypes = { + children: PropTypes.node, + className: PropTypes.string, + onClick: PropTypes.func +}; + +export { + MenuComponent as default, + MenuItem +}; diff --git a/src/containers/menu.jsx b/src/containers/menu.jsx new file mode 100644 index 0000000000000000000000000000000000000000..9096e8617f737bf174409f74cd5fdf97ffbee72f --- /dev/null +++ b/src/containers/menu.jsx @@ -0,0 +1,60 @@ +import bindAll from 'lodash.bindall'; +import PropTypes from 'prop-types'; +import React from 'react'; + +import MenuComponent from '../components/menu/menu.jsx'; + +class Menu extends React.Component { + constructor (props) { + super(props); + bindAll(this, [ + 'addListeners', + 'removeListeners', + 'handleClick', + 'ref' + ]); + if (props.open) this.addListeners(); + } + componentDidUpdate (prevProps) { + if (this.props.open && !prevProps.open) this.addListeners(); + if (!this.props.open && prevProps.open) this.removeListeners(); + } + addListeners () { + document.addEventListener('mouseup', this.handleClick); + } + removeListeners () { + document.removeEventListener('mouseup', this.handleClick); + } + handleClick (e) { + if (this.props.open && !this.menu.contains(e.target)) { + this.props.onRequestClose(); + } + } + ref (c) { + this.menu = c; + } + render () { + const { + open, + children, + ...props + } = this.props; + return ( + <MenuComponent + componentRef={this.ref} + open={open} + {...props} + > + {children} + </MenuComponent> + ); + } +} + +Menu.propTypes = { + children: PropTypes.node, + onRequestClose: PropTypes.func, + open: PropTypes.bool +}; + +export default Menu; diff --git a/src/reducers/gui.js b/src/reducers/gui.js index d89a195c9dc2d485082337e5a0961f18e2e63ddd..64d84634297fb03efefbc5dbc31592d2cdcf65b5 100644 --- a/src/reducers/gui.js +++ b/src/reducers/gui.js @@ -5,6 +5,7 @@ import blockDragReducer from './block-drag'; import editorTabReducer from './editor-tab'; import hoveredTargetReducer from './hovered-target'; import intlReducer from './intl'; +import menuReducer from './menus'; import modalReducer from './modals'; import monitorReducer from './monitors'; import monitorLayoutReducer from './monitor-layout'; @@ -22,6 +23,7 @@ export default combineReducers({ hoveredTarget: hoveredTargetReducer, intl: intlReducer, stageSize: stageSizeReducer, + menus: menuReducer, modals: modalReducer, monitors: monitorReducer, monitorLayout: monitorLayoutReducer, diff --git a/src/reducers/menus.js b/src/reducers/menus.js new file mode 100644 index 0000000000000000000000000000000000000000..d506d90645bb4bfe3bd7cf198c51e89519aab236 --- /dev/null +++ b/src/reducers/menus.js @@ -0,0 +1,50 @@ +const OPEN_MENU = 'scratch-gui/menus/OPEN_MENU'; +const CLOSE_MENU = 'scratch-gui/menus/CLOSE_MENU'; + +const MENU_FILE = 'fileMenu'; + + +const initialState = { + [MENU_FILE]: false +}; + +const reducer = function (state, action) { + if (typeof state === 'undefined') state = initialState; + switch (action.type) { + case OPEN_MENU: + return Object.assign({}, state, { + [action.menu]: true + }); + case CLOSE_MENU: + return Object.assign({}, state, { + [action.menu]: false + }); + default: + return state; + } +}; +const openMenu = function (menu) { + return { + type: OPEN_MENU, + menu: menu + }; +}; +const closeMenu = function (menu) { + return { + type: CLOSE_MENU, + menu: menu + }; +}; +const openFileMenu = function () { + return openMenu(MENU_FILE); +}; +const closeFileMenu = function () { + return closeMenu(MENU_FILE); +}; + +export { + reducer as default, + openFileMenu, + closeFileMenu, + MENU_FILE +}; diff --git a/src/reducers/modals.js b/src/reducers/modals.js index 2a3772e59935d359a8345c8a422cf6a666b20ec1..b1c51c05affdc1e3708478e93101bcef3bae7681 100644 --- a/src/reducers/modals.js +++ b/src/reducers/modals.js @@ -22,7 +22,7 @@ const initialState = { [MODAL_FEEDBACK_FORM]: false, [MODAL_IMPORT_INFO]: false, [MODAL_LOADING_PROJECT]: false, - [MODAL_PREVIEW_INFO]: true, + [MODAL_PREVIEW_INFO]: false, [MODAL_SOUND_LIBRARY]: false, [MODAL_SPRITE_LIBRARY]: false, [MODAL_SOUND_RECORDER]: false