box-ui-elements-mlh
Version:
193 lines (166 loc) • 6.6 kB
JavaScript
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import commonMessages from '../../common/messages';
import { ModalActions } from '../modal';
import Button from '../button';
import PlainButton from '../plain-button';
import DropdownMenu, { MenuToggle } from '../dropdown-menu';
import { Menu, MenuItem } from '../menu';
import HotkeyFriendlyModal from './HotkeyFriendlyModal'; // eslint-disable-line import/no-cycle
import messages from './messages';
import './HotkeyHelpModal.scss';
const specialCharacters = {
backspace: '\u232b',
down: '\u2193',
left: '\u2190',
meta: '\u2318',
right: '\u2192',
up: '\u2191',
enter: <FormattedMessage {...messages.enterKey} />,
spacebar: <FormattedMessage {...messages.spacebarKey} />,
shift: '\u21e7',
ctrl: <FormattedMessage {...messages.ctrlKey} />,
alt: <FormattedMessage {...messages.altKey} />,
esc: <FormattedMessage {...messages.escKey} />,
};
class HotkeyHelpModal extends Component {
static propTypes = {
isOpen: PropTypes.bool,
onRequestClose: PropTypes.func.isRequired,
};
static contextTypes = {
hotkeyLayer: PropTypes.object,
};
constructor(props, context) {
super(props);
this.hotkeys = context.hotkeyLayer.getActiveHotkeys();
this.types = context.hotkeyLayer.getActiveTypes();
this.state = {
currentType: this.types.length ? this.types[0] : null,
};
}
componentDidUpdate({ isOpen: prevIsOpen }, { currentType: prevType }) {
const { isOpen } = this.props;
if (!isOpen) {
return;
}
// modal is being opened; refresh hotkeys
if (!prevIsOpen && isOpen) {
this.hotkeys = this.context.hotkeyLayer.getActiveHotkeys();
this.types = this.context.hotkeyLayer.getActiveTypes();
}
if (!prevType) {
this.setState({
currentType: this.types.length ? this.types[0] : null,
});
}
}
/**
* Converts a "raw" hotkey to translated JSX version
*/
prettyPrintHotkey = hotkeyConfig => {
const hotkeys = Array.isArray(hotkeyConfig.key) ? hotkeyConfig.key : [hotkeyConfig.key];
return hotkeys
.map(hotkey =>
hotkey.split(' ').reduce((prettyHotkey, combo, i) => {
// Convert a "raw" combo to a "pretty" combo:
// e.g. "shift+g" => [ <kbd>Shift</kbd>, '+', <kbd>G</kbd> ]
const prettyCombo = combo
.split('+')
.map(key => {
// Convert special key characters into their respective icons or translated components:
// e.g. "shift" => "Shift", "meta" => "⌘"
if (key in specialCharacters) {
return specialCharacters[key];
}
// If it's not a special character, just return the uppercased key:
// e.g. "g" => "G"
return key.length === 1 ? key.toUpperCase() : key;
})
.map((key, j) => <kbd key={j}>{key}</kbd>);
// If this hotkey is a sequence of keys, return a translated message to combine them:
// e.g. "Shift+G Shift+A" => "Shift+G then Shift+A"
return i === 0 ? (
prettyCombo
) : (
<FormattedMessage
values={{
key1: <span>{prettyHotkey}</span>,
key2: <span>{prettyCombo}</span>,
}}
{...messages.hotkeySequence}
/>
);
}, []),
)
.reduce(
(finalHotkey, hotkey, i) =>
// For shortcuts with multiple hotkeys, separate each hotkey with a "/" joiner:
// e.g. "Cmd+S Ctrl+S" => "Cmd+S / Ctrl+S"
i === 0 ? [hotkey] : [...finalHotkey, ' / ', hotkey],
[],
)
.map((element, i) => <span key={i}>{element}</span>);
};
renderDropdownMenu() {
const { currentType } = this.state;
if (!currentType) {
return null;
}
return (
<div className="hotkey-dropdown">
<DropdownMenu>
<PlainButton className="lnk" type="button">
<MenuToggle>{currentType}</MenuToggle>
</PlainButton>
<Menu>
{this.types.map((hotkeyType, i) => (
<MenuItem key={i} onClick={() => this.setState({ currentType: hotkeyType })}>
{hotkeyType}
</MenuItem>
))}
</Menu>
</DropdownMenu>
</div>
);
}
renderHotkey = (hotkey, i) => (
<li key={i} className="hotkey-item">
<div className="hotkey-description">{hotkey.description}</div>
<div className="hotkey-key">{this.prettyPrintHotkey(hotkey)}</div>
</li>
);
renderHotkeyList() {
const { currentType } = this.state;
if (!currentType) {
return null;
}
const hotkeys = this.hotkeys[currentType];
return <ul className="hotkey-list">{hotkeys.map(this.renderHotkey)}</ul>;
}
render() {
const { isOpen, onRequestClose } = this.props;
const { currentType } = this.state;
if (!currentType) {
return null;
}
return (
<HotkeyFriendlyModal
className="hotkey-modal"
isOpen={isOpen}
onRequestClose={onRequestClose}
title={<FormattedMessage {...messages.hotkeyModalTitle} />}
>
{this.renderDropdownMenu()}
{this.renderHotkeyList()}
<ModalActions>
<Button onClick={onRequestClose}>
<FormattedMessage {...commonMessages.cancel} />
</Button>
</ModalActions>
</HotkeyFriendlyModal>
);
}
}
export default HotkeyHelpModal;