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!
This commit is contained in:
1 parent
634e476f76
commit
bc3df2c33b
6 files changed
+197
-68
No files matched your search
@@ -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;
|
||||
|
||||
@@ -1,56 +1,49 @@
|
||||
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))
|
||||
});
|
||||
|
||||
|
||||
@@ -1,18 +1,85 @@
|
||||
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);
|
||||
+10
-2
@@ -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
|
||||
};
|
||||
Reference in new issue
Block a user