feat: Escape hotkey enters 'navigation mode' (#37167)

* fix: escape to navigation mode more generally

Previously only 'escape'ing from the editor prevented auto focus on the
editor, now you can 'escape' from anywhere that listens to Hotkeys.

Also the outline for HotKeys is hidden as it is not used by tab
navigation.

* feat: require escape to enter navigation mode
This commit is contained in:
Oliver Eyton-Williams
2019-10-14 18:50:58 +02:00
committed by mrugesh
parent 26eacf6f47
commit e256dbc611
2 changed files with 44 additions and 6 deletions

View File

@ -2,29 +2,48 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { HotKeys, GlobalHotKeys } from 'react-hotkeys'; import { HotKeys, GlobalHotKeys } from 'react-hotkeys';
import { navigate } from 'gatsby'; import { navigate } from 'gatsby';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { canFocusEditorSelector, setEditorFocusability } from '../redux';
import './hotkeys.css';
const mapStateToProps = createSelector(
canFocusEditorSelector,
canFocusEditor => ({
canFocusEditor
})
);
const mapDispatchToProps = { setEditorFocusability };
const keyMap = { const keyMap = {
NAVIGATION_MODE: 'escape',
EXECUTE_CHALLENGE: ['ctrl+enter', 'command+enter'], EXECUTE_CHALLENGE: ['ctrl+enter', 'command+enter'],
NAVIGATE_PREV: ['p'], NAVIGATE_PREV: ['p'],
NAVIGATE_NEXT: ['n'] NAVIGATE_NEXT: ['n']
}; };
const propTypes = { const propTypes = {
canFocusEditor: PropTypes.bool,
children: PropTypes.any, children: PropTypes.any,
executeChallenge: PropTypes.func, executeChallenge: PropTypes.func,
innerRef: PropTypes.any, innerRef: PropTypes.any,
introPath: PropTypes.string, introPath: PropTypes.string,
nextChallengePath: PropTypes.string, nextChallengePath: PropTypes.string,
prevChallengePath: PropTypes.string prevChallengePath: PropTypes.string,
setEditorFocusability: PropTypes.func.isRequired
}; };
function Hotkeys({ function Hotkeys({
canFocusEditor,
children, children,
executeChallenge, executeChallenge,
introPath, introPath,
innerRef, innerRef,
nextChallengePath, nextChallengePath,
prevChallengePath prevChallengePath,
setEditorFocusability
}) { }) {
const handlers = { const handlers = {
EXECUTE_CHALLENGE: e => { EXECUTE_CHALLENGE: e => {
@ -35,14 +54,26 @@ function Hotkeys({
e.preventDefault(); e.preventDefault();
if (executeChallenge) executeChallenge(); if (executeChallenge) executeChallenge();
}, },
NAVIGATE_PREV: () => navigate(prevChallengePath), NAVIGATION_MODE: () => setEditorFocusability(false),
NAVIGATE_NEXT: () => navigate(introPath ? introPath : nextChallengePath) NAVIGATE_PREV: () => {
if (!canFocusEditor) navigate(prevChallengePath);
},
NAVIGATE_NEXT: () => {
if (!canFocusEditor) navigate(introPath ? introPath : nextChallengePath);
}
}; };
// GlobalHotKeys is always mounted and tracks all keypresses. Without it, // GlobalHotKeys is always mounted and tracks all keypresses. Without it,
// keyup events can be missed and react-hotkeys assumes that that key is still // keyup events can be missed and react-hotkeys assumes that that key is still
// being pressed. // being pressed.
// allowChanges is necessary if the handlers depend on props (in this case
// canFocusEditor)
return ( return (
<HotKeys handlers={handlers} innerRef={innerRef} keyMap={keyMap}> <HotKeys
allowChanges={true}
handlers={handlers}
innerRef={innerRef}
keyMap={keyMap}
>
{children} {children}
<GlobalHotKeys /> <GlobalHotKeys />
</HotKeys> </HotKeys>
@ -52,4 +83,7 @@ function Hotkeys({
Hotkeys.displayName = 'Hotkeys'; Hotkeys.displayName = 'Hotkeys';
Hotkeys.propTypes = propTypes; Hotkeys.propTypes = propTypes;
export default Hotkeys; export default connect(
mapStateToProps,
mapDispatchToProps
)(Hotkeys);

View File

@ -0,0 +1,4 @@
/* hide the outline for the HotKeys component */
div[tabindex='-1']:focus {
outline: none;
}