Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Keyboard shortcuts modal #6112

Merged
merged 41 commits into from
Nov 24, 2021
Merged
Show file tree
Hide file tree
Changes from 38 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
baf320a
Basic component with props added
akshayasalvi Oct 29, 2021
fdf81af
Converted component to class component
akshayasalvi Oct 29, 2021
e00816c
Added mapper function to the keyboardshortcut lib
akshayasalvi Oct 29, 2021
28855fb
Translation files
akshayasalvi Oct 29, 2021
cb54938
Removed modal props from ScreenWrapper
akshayasalvi Oct 29, 2021
a6c28bd
Modified the shortcut trigger logic to modal
akshayasalvi Oct 29, 2021
6bad775
Added keys for existing shortcuts
akshayasalvi Oct 29, 2021
290acb8
Styling updates for container
akshayasalvi Oct 29, 2021
f31f5ed
Key handling for cmd
akshayasalvi Oct 29, 2021
2b6e319
Moved styling to style.js
akshayasalvi Oct 29, 2021
32622ab
Changed border styling
akshayasalvi Oct 29, 2021
2e0d4c7
Added spanish translations
akshayasalvi Oct 29, 2021
2096196
Added styling options for modal
akshayasalvi Nov 3, 2021
18fbadf
Added borderRadius to the table
akshayasalvi Nov 3, 2021
640687d
Fixed border styling for the table
akshayasalvi Nov 3, 2021
c18e2a4
Removed height hardcoding
akshayasalvi Nov 3, 2021
ea2a41f
Allow modal to open in input focus as well
akshayasalvi Nov 7, 2021
e1a8e96
Merge branch 'main' of github-personal:akshayasalvi/App into keyboard…
akshayasalvi Nov 7, 2021
e39b198
Updated copy for the modal subtitle
akshayasalvi Nov 7, 2021
77c9381
Code cleanup
akshayasalvi Nov 8, 2021
111edbc
Moved object to map to Keyboardshortcut lib
akshayasalvi Nov 8, 2021
06e42c4
Reordered params for KeyboardShortcut.subscribe
akshayasalvi Nov 8, 2021
1018fb5
Styling fixes
akshayasalvi Nov 8, 2021
dbfa47e
Changes for code cleanup
akshayasalvi Nov 8, 2021
048c729
Merge branch 'main' of github-personal:akshayasalvi/App into keyboard…
akshayasalvi Nov 8, 2021
666e499
Added check for descriptionKey
akshayasalvi Nov 8, 2021
eeb16d1
Fixed escape key for dialog
akshayasalvi Nov 8, 2021
fa76933
Lint fixes
akshayasalvi Nov 8, 2021
99e7a4f
Made modal bottom docked for small screens and changed function name
akshayasalvi Nov 8, 2021
1f57f37
Moved shortcut modifiers (control/meta) logic to utils
akshayasalvi Nov 9, 2021
6943aa1
Merge branch 'main' of github-personal:akshayasalvi/App into keyboard…
akshayasalvi Nov 14, 2021
4c29fc0
Changed logic for modifiers
akshayasalvi Nov 19, 2021
7de6c54
Fixed typo for KeyboardShortcut
akshayasalvi Nov 19, 2021
e5b034d
Split toggle func to hide and show
akshayasalvi Nov 19, 2021
3cbd1bf
Removed hardcoding of the shortcuts to CONST
akshayasalvi Nov 19, 2021
fc03898
Pick all shortcut configs from CONST
akshayasalvi Nov 19, 2021
eb6ef1c
Added enter configuration for keyboard shortcuts
akshayasalvi Nov 19, 2021
e2e456c
Fixed typo for Button
akshayasalvi Nov 19, 2021
f09b027
Added jsdocs and changed function calls
akshayasalvi Nov 23, 2021
2eeed69
Unified representation of shortcut object
akshayasalvi Nov 23, 2021
06c77ce
Fixed typos in shortcuts
akshayasalvi Nov 23, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 43 additions & 1 deletion src/CONST.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const CLOUDFRONT_URL = 'https://d2k5nsl2zxldvw.cloudfront.net';
const NEW_EXPENSIFY_URL = 'https://new.expensify.com';
const PLATFORM_OS_MACOS = 'Mac OS';

const CONST = {
// 50 megabytes in bytes
Expand Down Expand Up @@ -119,6 +120,47 @@ const CONST = {
IOS: 'ios',
ANDROID: 'android',
},
KEYBOARD_SHORTCUT_MODIFIERS: {
CTRL: {
DEFAULT: 'control',
[PLATFORM_OS_MACOS]: 'meta',
},
SHIFT: {
DEFAULT: 'shift',
},
},
KEYBOARD_SHORTCUTS: {
SEARCH: {
descriptionKey: 'search',
shortcutKey: 'K',
modifiers: ['CTRL'],
},
NEW_GROUP: {
descriptionKey: 'newGroup',
shortcutKey: 'K',
modifiers: ['CTRL', 'SHIFT'],
},
SHORTCUT_MODAL: {
descriptionKey: 'openShortcutDialog',
shortcutKey: '?',
modifiers: ['CTRL', 'SHIFT'],
},
ESCAPE: {
descriptionKey: 'escape',
shortcutKey: 'Escape',
modifiers: [],
},
ENTER: {
descriptionKey: null,
shortcutKey: 'Enter',
modifiers: [],
},
},
KEYBOARD_SHORTCUT_KEY_DISPLAY_NAME: {
CONTROL: 'Ctrl',
META: 'Cmd',
SHIFT: 'Shift',
},
CURRENCY: {
USD: 'USD',
},
Expand Down Expand Up @@ -360,7 +402,7 @@ const CONST = {

OS: {
WINDOWS: 'Windows',
MAC_OS: 'Mac OS',
MAC_OS: PLATFORM_OS_MACOS,
ANDROID: 'Android',
IOS: 'iOS',
LINUX: 'Linux',
Expand Down
8 changes: 5 additions & 3 deletions src/components/Button.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import OpacityView from './OpacityView';
import Text from './Text';
import KeyboardShortcut from '../libs/KeyboardShortcut';
import Icon from './Icon';
import CONST from '../CONST';

const propTypes = {
/** The text for the button label */
Expand Down Expand Up @@ -90,14 +91,15 @@ class Button extends Component {
return;
}

const shortcutConfig = CONST.KEYBOARD_SHORTCUTS.ENTER;

// Setup and attach keypress handler for pressing the button with Enter key
this.unsubscribe = KeyboardShortcut.subscribe('Enter', () => {
this.unsubscribe = KeyboardShortcut.subscribe(shortcutConfig.shortcutKey, () => {
if (this.props.isDisabled || this.props.isLoading) {
return;
}

this.props.onPress();
}, [], true);
}, shortcutConfig.descriptionKey, shortcutConfig.modifiers, true);
}

componentWillUnmount() {
Expand Down
109 changes: 109 additions & 0 deletions src/components/KeyboardShortcutsModal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import React from 'react';
import {View} from 'react-native';
import _ from 'underscore';
import HeaderWithCloseButton from './HeaderWithCloseButton';
import Text from './Text';
import Modal from './Modal';
import CONST from '../CONST';
import styles from '../styles/styles';
import withWindowDimensions, {windowDimensionsPropTypes} from './withWindowDimensions';
import withLocalize, {withLocalizePropTypes} from './withLocalize';
import compose from '../libs/compose';
import KeyboardShortcut from '../libs/KeyboardShortcut';

const propTypes = {
/** prop to fetch screen width */
...windowDimensionsPropTypes,

/** props to fetch translation functions */
...withLocalizePropTypes,
};

class KeyboardShortcutsModal extends React.Component {
constructor(props) {
super(props);

this.state = {
isOpen: false,
};

this.showKeyboardShortcutModal = this.showKeyboardShortcutModal.bind(this);
this.hideKeyboardShortcutModal = this.hideKeyboardShortcutModal.bind(this);
}

componentDidMount() {
const shortcutConfig = CONST.KEYBOARD_SHORTCUTS.SHORTCUT_MODAL;
const shortcutModifiers = KeyboardShortcut.getShortcutModifiers(shortcutConfig.modifiers);
this.unsubscribeShortcutModal = KeyboardShortcut.subscribe(shortcutConfig.shortcutKey, () => {
this.showKeyboardShortcutModal();
}, shortcutConfig.descriptionKey, shortcutModifiers, true);
}

componentWillUnmount() {
if (!this.unsubscribeShortcutModal) {
return;
}
this.unsubscribeShortcutModal();
}

showKeyboardShortcutModal() {
this.setState({isOpen: true});
}

hideKeyboardShortcutModal() {
this.setState({isOpen: false});
}

renderRow(shortcut, isFirstRow) {
roryabraham marked this conversation as resolved.
Show resolved Hide resolved
return (
<View
style={[
styles.keyboardShortcutTableRow,
styles.flex1,
isFirstRow && styles.keyboardShortcutTableFirstRow,
]}
key={shortcut.key}
>
<View style={[styles.dFlex, styles.p2, styles.keyboardShortcutTablePrefix]}>
<Text>{shortcut.key}</Text>
</View>
<View style={[styles.flex1, styles.p2, styles.alignSelfStretch]}>
<Text>{this.props.translate(`keyboardShortcutModal.shortcuts.${shortcut.descriptionKey}`)}</Text>
</View>
</View>
);
}

render() {
const shortcuts = KeyboardShortcut.getKeyboardShortcuts();
const modalType = this.props.isSmallScreenWidth ? CONST.MODAL.MODAL_TYPE.BOTTOM_DOCKED : CONST.MODAL.MODAL_TYPE.CENTERED;
return (
<Modal
isVisible={this.state.isOpen}
type={modalType}
containerStyle={styles.keyboardShortcutModalContainer}
onClose={() => this.hideKeyboardShortcutModal()}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
onClose={() => this.hideKeyboardShortcutModal()}
onClose={this.hideKeyboardShortcutModal}

>
<HeaderWithCloseButton title={this.props.translate('keyboardShortcutModal.title')} onCloseButtonPress={() => this.hideKeyboardShortcutModal()} />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<HeaderWithCloseButton title={this.props.translate('keyboardShortcutModal.title')} onCloseButtonPress={() => this.hideKeyboardShortcutModal()} />
<HeaderWithCloseButton title={this.props.translate('keyboardShortcutModal.title')} onCloseButtonPress={this.hideKeyboardShortcutModal} />

<View style={[styles.p5, styles.pt0]}>
<Text style={styles.mb5}>{this.props.translate('keyboardShortcutModal.subtitle')}</Text>
<View style={[styles.keyboardShortcutTableWrapper]}>
<View style={[styles.alignItemsCenter, styles.keyboardShortcutTableContainer]}>
{_.map(shortcuts, (shortcut, index) => {
const isFirstRow = index === 0;
return this.renderRow(shortcut, isFirstRow);
})}
</View>
</View>
</View>
</Modal>
);
}
}

KeyboardShortcutsModal.propTypes = propTypes;

export default compose(
withWindowDimensions,
withLocalize,
)(KeyboardShortcutsModal);
1 change: 1 addition & 0 deletions src/components/Modal/BaseModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ class BaseModal extends PureComponent {
isSmallScreenWidth: this.props.isSmallScreenWidth,
},
this.props.popoverAnchorPosition,
this.props.containerStyle,
);
return (
<ReactNativeModal
Expand Down
5 changes: 5 additions & 0 deletions src/components/Modal/modalPropTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
import _ from 'underscore';
import CONST from '../../CONST';
import {windowDimensionsPropTypes} from '../withWindowDimensions';
import stylePropTypes from '../../styles/stylePropTypes';

const propTypes = {
/** Decides whether the modal should cover fullscreen. FullScreen modal has backdrop */
Expand Down Expand Up @@ -54,6 +55,9 @@ const propTypes = {
left: PropTypes.number,
}),

/** Modal container styles */
containerStyle: stylePropTypes,

...windowDimensionsPropTypes,
};

Expand All @@ -68,6 +72,7 @@ const defaultProps = {
animationIn: null,
animationOut: null,
popoverAnchorPosition: {},
containerStyle: {},
};

export {propTypes, defaultProps};
9 changes: 7 additions & 2 deletions src/components/ScreenWrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ import {SafeAreaInsetsContext} from 'react-native-safe-area-context';
import {withOnyx} from 'react-native-onyx';
import styles, {getSafeAreaPadding} from '../styles/styles';
import HeaderGap from './HeaderGap';
import KeyboardShortcutsModal from './KeyboardShortcutsModal';
import KeyboardShortcut from '../libs/KeyboardShortcut';
import onScreenTransitionEnd from '../libs/onScreenTransitionEnd';
import Navigation from '../libs/Navigation/Navigation';
import compose from '../libs/compose';
import ONYXKEYS from '../ONYXKEYS';
import CONST from '../CONST';

const propTypes = {
/** Array of additional styles to add */
Expand Down Expand Up @@ -60,19 +62,21 @@ const defaultProps = {
class ScreenWrapper extends React.Component {
constructor(props) {
super(props);

this.state = {
didScreenTransitionEnd: false,
};
}

componentDidMount() {
this.unsubscribeEscapeKey = KeyboardShortcut.subscribe('Escape', () => {
const shortcutConfig = CONST.KEYBOARD_SHORTCUTS.ESCAPE;
this.unsubscribeEscapeKey = KeyboardShortcut.subscribe(shortcutConfig.shortcutKey, () => {
if (this.props.modal.willAlertModalBecomeVisible) {
return;
}

Navigation.dismissModal();
}, [], true);
}, shortcutConfig.descriptionKey, shortcutConfig.modifiers, true);

this.unsubscribeTransitionEnd = onScreenTransitionEnd(this.props.navigation, () => {
this.setState({didScreenTransitionEnd: true});
Expand Down Expand Up @@ -120,6 +124,7 @@ class ScreenWrapper extends React.Component {
})
: this.props.children
}
<KeyboardShortcutsModal />
</View>
);
}}
Expand Down
10 changes: 10 additions & 0 deletions src/languages/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -774,4 +774,14 @@ export default {
emojiPicker: {
skinTonePickerLabel: 'Change default skin tone',
},
keyboardShortcutModal: {
title: 'Keyboard Shortcuts',
subtitle: 'Save time with these handy keyboard shortcuts:',
shortcuts: {
openShortcutDialog: 'Opens the keyboard shortcuts dialog',
escape: 'Escape Dialogs',
search: 'Open search dialog',
newGroup: 'New group screen',
},
},
};
10 changes: 10 additions & 0 deletions src/languages/es.js
Original file line number Diff line number Diff line change
Expand Up @@ -776,4 +776,14 @@ export default {
emojiPicker: {
skinTonePickerLabel: 'Elige el tono de piel por defecto',
},
keyboardShortcutModal: {
title: 'Atajos de teclado',
subtitle: 'Ahorra tiempo con estos atajos de teclado:',
shortcuts: {
openShortcutDialog: 'Abre el cuadro de diálogo de métodos abreviados de teclado',
escape: 'Diálogos de escape',
search: 'Abrir diálogo de búsqueda',
newGroup: 'Nueva pantalla de grupo',
},
},
};
Loading