Skip to content
Snippets Groups Projects
Commit bc3df2c3 authored by chrisgarrity's avatar chrisgarrity
Browse files

Enable language selection menu

Allows the language selection menu to be active if `?enable=language` is in the URL, otherwise shows a ‘coming soon’ tooltip. This can also land on a branch if we don’t want that available on preview.

- language menu opens if the globe is clicked
- language menu closes if mouse is clicked anywhere outside the language selector
- language list is imported from scratch-l10n
- current locale is updated if another language is selected (i.e. the current language selected changes)

Not in this PR:
- styling of language selector
- actually changing the messages!
parent 634e476f
No related branches found
No related tags found
No related merge requests found
......@@ -2,10 +2,20 @@
@import "../../css/units.css";
.group {
display: inline-flex;
flex-direction: row; /* makes columns, for each label/form group */
align-items: center;
vertical-align: middle;
z-index: 50;
top: $menu-bar-height;
left: 0;
position: absolute;
border: 1px solid $ui-black-transparent;
border-radius: 0 0 8px 8px;
background-color: $motion-primary;
padding: 0;
margin: 0;
min-width: 186px;
max-width: 260px;
overflow: visible;
color: $ui-white;
box-shadow: 0 8px 8px 0 $ui-black-transparent;
}
.language-icon {
......@@ -17,7 +27,8 @@
}
.language-select {
width: 100%;
margin: .5rem;
width: calc(100% - 1rem);
height: 1.85rem;
border: 1px solid $motion-primary;
user-select: none;
......
import classNames from 'classnames';
import PropTypes from 'prop-types';
import React from 'react';
import Box from '../box/box.jsx';
import locales from 'scratch-l10n';
import languageIcon from './language-icon.svg';
import dropdownCaret from './dropdown-caret.svg';
import styles from './language-selector.css';
const LanguageSelector = ({
currentLocale,
onChange,
open,
...props
}) => (
<Box {...props}>
<div className={styles.group}>
{open ? (
<select
disabled
aria-label="language selector"
className={styles.languageSelect}
value={currentLocale}
onChange={onChange}
class LanguageSelector extends React.Component {
render () {
const {
componentRef,
currentLocale,
onChange,
...componentProps
} = this.props;
return (
<Box
{...componentProps}
>
<div
className={styles.group}
ref={componentRef}
>
{Object.keys(locales).map(locale => (
<option
key={locale}
value={locale}
>
{locales[locale].name}
</option>
))}
</select>
) : (
<React.Fragment>
<img
className={classNames(styles.languageIcon, styles.disabled)}
src={languageIcon}
/>
<img
className={classNames(styles.dropdownCaret, styles.disabled)}
src={dropdownCaret}
/>
</React.Fragment>
)}
</div>
</Box>
);
<select
aria-label="language selector"
className={styles.languageSelect}
value={currentLocale}
onChange={onChange}
>
{Object.keys(locales).map(locale => (
<option
key={locale}
value={locale}
>
{locales[locale].name}
</option>
))}
</select>
</div>
</Box>
);
}
}
LanguageSelector.propTypes = {
componentRef: PropTypes.func,
currentLocale: PropTypes.string,
onChange: PropTypes.func,
open: PropTypes.bool
......
......@@ -44,6 +44,14 @@
vertical-align: middle;
}
.language-icon {
height: 1.5rem;
}
.language-menu {
display: inline-flex;
}
.menu {
z-index: $z-index-menu-bar;
top: $menu-bar-height;
......
......@@ -22,7 +22,10 @@ import {
fileMenuOpen,
openEditMenu,
closeEditMenu,
editMenuOpen
editMenuOpen,
openLanguageMenu,
closeLanguageMenu,
languageMenuOpen
} from '../../reducers/menus';
import styles from './menu-bar.css';
......@@ -32,6 +35,8 @@ import feedbackIcon from './icon--feedback.svg';
import profileIcon from './icon--profile.png';
import communityIcon from './icon--see-community.svg';
import dropdownCaret from '../language-selector/dropdown-caret.svg';
import languageIcon from '../language-selector/language-icon.svg';
import scratchLogo from './scratch-logo.svg';
import helpIcon from './icon--help.svg';
......@@ -39,22 +44,34 @@ import helpIcon from './icon--help.svg';
const MenuBarItemTooltip = ({
children,
className,
enable,
id,
place = 'bottom'
}) => (
<ComingSoonTooltip
className={classNames(styles.comingSoon, className)}
place={place}
tooltipClassName={styles.comingSoonTooltip}
tooltipId={id}
>
{children}
</ComingSoonTooltip>
);
}) => {
if (enable) {
return (
<React.Fragment>
{children}
</React.Fragment>
);
}
return (
<ComingSoonTooltip
className={classNames(styles.comingSoon, className)}
place={place}
tooltipClassName={styles.comingSoonTooltip}
tooltipId={id}
>
{children}
</ComingSoonTooltip>
);
};
MenuBarItemTooltip.propTypes = {
children: PropTypes.node,
className: PropTypes.string,
enable: PropTypes.bool,
id: PropTypes.string,
place: PropTypes.oneOf(['top', 'bottom', 'left', 'right'])
};
......@@ -111,12 +128,31 @@ const MenuBar = props => (
src={scratchLogo}
/>
</div>
<div className={classNames(styles.menuBarItem, styles.hoverable)}>
<div
className={classNames(styles.menuBarItem, styles.hoverable, {
[styles.active]: props.languageMenuOpen
})}
onMouseUp={props.onClickLanguage}
>
<MenuBarItemTooltip
enable={window.location.search.indexOf('enable=language') !== -1}
id="menubar-selector"
place="right"
>
<LanguageSelector />
<div className={classNames(styles.languageMenu)}>
<img
className={styles.languageIcon}
src={languageIcon}
/>
<img
className={styles.dropdownCaret}
src={dropdownCaret}
/>
</div>
<LanguageSelector
open={props.languageMenuOpen}
onRequestClose={props.onRequestCloseLanguage}
/>
</MenuBarItemTooltip>
</div>
<div
......@@ -369,17 +405,21 @@ MenuBar.propTypes = {
editMenuOpen: PropTypes.bool,
enableCommunity: PropTypes.bool,
fileMenuOpen: PropTypes.bool,
languageMenuOpen: PropTypes.bool,
onClickEdit: PropTypes.func,
onClickFile: PropTypes.func,
onClickLanguage: PropTypes.func,
onOpenTipLibrary: PropTypes.func,
onRequestCloseEdit: PropTypes.func,
onRequestCloseFile: PropTypes.func,
onRequestCloseLanguage: PropTypes.func,
onSeeCommunity: PropTypes.func
};
const mapStateToProps = state => ({
fileMenuOpen: fileMenuOpen(state),
editMenuOpen: editMenuOpen(state)
editMenuOpen: editMenuOpen(state),
languageMenuOpen: languageMenuOpen(state)
});
const mapDispatchToProps = dispatch => ({
......@@ -388,6 +428,8 @@ const mapDispatchToProps = dispatch => ({
onRequestCloseFile: () => dispatch(closeFileMenu()),
onClickEdit: () => dispatch(openEditMenu()),
onRequestCloseEdit: () => dispatch(closeEditMenu()),
onClickLanguage: () => dispatch(openLanguageMenu()),
onRequestCloseLanguage: () => dispatch(closeLanguageMenu()),
onSeeCommunity: () => dispatch(setPlayer(true))
});
......
import bindAll from 'lodash.bindall';
import PropTypes from 'prop-types';
import React from 'react';
import {connect} from 'react-redux';
import {updateIntl} from 'react-intl-redux';
import {closeLanguageMenu} from '../reducers/menus';
import LanguageSelectorComponent from '../components/language-selector/language-selector.jsx';
class LanguageSelector extends React.Component {
constructor (props) {
super(props);
bindAll(this, [
'addListeners',
'removeListeners',
'handleChange',
'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);
}
handleChange (e) {
this.props.onChangeLanguage(e.target.value);
}
handleClick (e) {
if (this.props.open && this.selector && !this.selector.contains(e.target)) {
this.props.onRequestClose();
}
}
ref (c) {
this.selector = c;
}
render () {
const {
open,
onChangeLanguage, // eslint-disable-line no-unused-vars
onRequestClose, // eslint-disable-line no-unused-vars
children,
...props
} = this.props;
if (!open) return null;
return (
<LanguageSelectorComponent
componentRef={this.ref}
onChange={this.handleChange}
{...props}
>
{children}
</LanguageSelectorComponent>
);
}
}
LanguageSelector.propTypes = {
children: PropTypes.node,
onChangeLanguage: PropTypes.func.isRequired,
onRequestClose: PropTypes.func.isRequired,
open: PropTypes.bool.isRequired
};
const mapStateToProps = state => ({
currentLocale: state.intl.locale
});
const mapDispatchToProps = () => ({
onChange: e => {
e.preventDefault();
const mapDispatchToProps = dispatch => ({
onChangeLanguage: locale => {
dispatch(updateIntl({locale: locale, messages: {}}));
dispatch(closeLanguageMenu());
}
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(LanguageSelectorComponent);
)(LanguageSelector);
......@@ -3,11 +3,13 @@ const CLOSE_MENU = 'scratch-gui/menus/CLOSE_MENU';
const MENU_FILE = 'fileMenu';
const MENU_EDIT = 'editMenu';
const MENU_LANGUAGE = 'languageMenu';
const initialState = {
[MENU_FILE]: false,
[MENU_EDIT]: false
[MENU_EDIT]: false,
[MENU_LANGUAGE]: false
};
const reducer = function (state, action) {
......@@ -39,6 +41,9 @@ const fileMenuOpen = state => state.scratchGui.menus[MENU_FILE];
const openEditMenu = () => openMenu(MENU_EDIT);
const closeEditMenu = () => closeMenu(MENU_EDIT);
const editMenuOpen = state => state.scratchGui.menus[MENU_EDIT];
const openLanguageMenu = () => openMenu(MENU_LANGUAGE);
const closeLanguageMenu = () => closeMenu(MENU_LANGUAGE);
const languageMenuOpen = state => state.scratchGui.menus[MENU_LANGUAGE];
export {
reducer as default,
......@@ -47,6 +52,9 @@ export {
closeFileMenu,
openEditMenu,
closeEditMenu,
openLanguageMenu,
closeLanguageMenu,
fileMenuOpen,
editMenuOpen
editMenuOpen,
languageMenuOpen
};
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment