mirror of
https://github.com/Sonarr/Sonarr.git
synced 2024-11-28 08:58:41 +02:00
Scrolling and hotkey improvements
New: Use Esc/Enter for cancel/accept in confirmation modals Fixed: Modals focused when opened Fixed: Scrolling with keyboard unless focus is shifted out of scrollable area Closes #3291
This commit is contained in:
parent
52e5d4d0f1
commit
506023b0f3
@ -1,6 +1,7 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { kinds, sizes } from 'Helpers/Props';
|
import { kinds, sizes } from 'Helpers/Props';
|
||||||
|
import keyboardShortcuts from 'Components/keyboardShortcuts';
|
||||||
import Button from 'Components/Link/Button';
|
import Button from 'Components/Link/Button';
|
||||||
import SpinnerButton from 'Components/Link/SpinnerButton';
|
import SpinnerButton from 'Components/Link/SpinnerButton';
|
||||||
import Modal from 'Components/Modal/Modal';
|
import Modal from 'Components/Modal/Modal';
|
||||||
@ -21,9 +22,14 @@ function ConfirmModal(props) {
|
|||||||
hideCancelButton,
|
hideCancelButton,
|
||||||
isSpinning,
|
isSpinning,
|
||||||
onConfirm,
|
onConfirm,
|
||||||
onCancel
|
onCancel,
|
||||||
|
bindShortcut
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
bindShortcut('enter', onConfirm);
|
||||||
|
}, [onConfirm]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
@ -49,7 +55,7 @@ function ConfirmModal(props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
<SpinnerButton
|
<SpinnerButton
|
||||||
data-autofocus={true}
|
autoFocus={true}
|
||||||
kind={kind}
|
kind={kind}
|
||||||
isSpinning={isSpinning}
|
isSpinning={isSpinning}
|
||||||
onPress={onConfirm}
|
onPress={onConfirm}
|
||||||
@ -74,7 +80,8 @@ ConfirmModal.propTypes = {
|
|||||||
hideCancelButton: PropTypes.bool,
|
hideCancelButton: PropTypes.bool,
|
||||||
isSpinning: PropTypes.bool.isRequired,
|
isSpinning: PropTypes.bool.isRequired,
|
||||||
onConfirm: PropTypes.func.isRequired,
|
onConfirm: PropTypes.func.isRequired,
|
||||||
onCancel: PropTypes.func.isRequired
|
onCancel: PropTypes.func.isRequired,
|
||||||
|
bindShortcut: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
ConfirmModal.defaultProps = {
|
ConfirmModal.defaultProps = {
|
||||||
@ -85,4 +92,4 @@ ConfirmModal.defaultProps = {
|
|||||||
isSpinning: false
|
isSpinning: false
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ConfirmModal;
|
export default keyboardShortcuts(ConfirmModal);
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
|
import FocusLock from 'react-focus-lock';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import elementClass from 'element-class';
|
import elementClass from 'element-class';
|
||||||
import getUniqueElememtId from 'Utilities/getUniqueElementId';
|
import getUniqueElememtId from 'Utilities/getUniqueElementId';
|
||||||
@ -181,31 +182,33 @@ class Modal extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return ReactDOM.createPortal(
|
return ReactDOM.createPortal(
|
||||||
<div
|
<FocusLock disabled={false}>
|
||||||
className={styles.modalContainer}
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
ref={this._setBackgroundRef}
|
className={styles.modalContainer}
|
||||||
className={backdropClassName}
|
|
||||||
onMouseDown={this.onBackdropBeginPress}
|
|
||||||
onMouseUp={this.onBackdropEndPress}
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
ref={this._setBackgroundRef}
|
||||||
className,
|
className={backdropClassName}
|
||||||
styles[size]
|
onMouseDown={this.onBackdropBeginPress}
|
||||||
)}
|
onMouseUp={this.onBackdropEndPress}
|
||||||
style={style}
|
|
||||||
>
|
>
|
||||||
<ErrorBoundary
|
<div
|
||||||
errorComponent={ModalError}
|
className={classNames(
|
||||||
onModalClose={onModalClose}
|
className,
|
||||||
|
styles[size]
|
||||||
|
)}
|
||||||
|
style={style}
|
||||||
>
|
>
|
||||||
{children}
|
<ErrorBoundary
|
||||||
</ErrorBoundary>
|
errorComponent={ModalError}
|
||||||
|
onModalClose={onModalClose}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</ErrorBoundary>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>,
|
</FocusLock>,
|
||||||
this._node
|
this._node
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,8 @@ class Scroller extends Component {
|
|||||||
if (this.props.scrollTop != null) {
|
if (this.props.scrollTop != null) {
|
||||||
this._scroller.scrollTop = scrollTop;
|
this._scroller.scrollTop = scrollTop;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._scroller.focus({ preventScroll: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
@ -58,6 +60,7 @@ class Scroller extends Component {
|
|||||||
styles[scrollDirection],
|
styles[scrollDirection],
|
||||||
autoScroll && styles.autoScroll
|
autoScroll && styles.autoScroll
|
||||||
)}
|
)}
|
||||||
|
tabIndex={-1}
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
@ -8,6 +8,16 @@ export const shortcuts = {
|
|||||||
name: 'Open This Modal'
|
name: 'Open This Modal'
|
||||||
},
|
},
|
||||||
|
|
||||||
|
CLOSE_MODAL: {
|
||||||
|
key: 'Esc',
|
||||||
|
name: 'Close Current Modal'
|
||||||
|
},
|
||||||
|
|
||||||
|
ACCEPT_CONFIRM_MODAL: {
|
||||||
|
key: 'Enter',
|
||||||
|
name: 'Accept Confirmation Modal'
|
||||||
|
},
|
||||||
|
|
||||||
SERIES_SEARCH_INPUT: {
|
SERIES_SEARCH_INPUT: {
|
||||||
key: 's',
|
key: 's',
|
||||||
name: 'Focus Search Box'
|
name: 'Focus Search Box'
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { kinds } from 'Helpers/Props';
|
import { kinds } from 'Helpers/Props';
|
||||||
|
import keyboardShortcuts from 'Components/keyboardShortcuts';
|
||||||
import Button from 'Components/Link/Button';
|
import Button from 'Components/Link/Button';
|
||||||
import Modal from 'Components/Modal/Modal';
|
import Modal from 'Components/Modal/Modal';
|
||||||
import ModalContent from 'Components/Modal/ModalContent';
|
import ModalContent from 'Components/Modal/ModalContent';
|
||||||
@ -12,9 +13,14 @@ function PendingChangesModal(props) {
|
|||||||
const {
|
const {
|
||||||
isOpen,
|
isOpen,
|
||||||
onConfirm,
|
onConfirm,
|
||||||
onCancel
|
onCancel,
|
||||||
|
bindShortcut
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
bindShortcut('enter', onConfirm);
|
||||||
|
}, [onConfirm]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
@ -36,6 +42,7 @@ function PendingChangesModal(props) {
|
|||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
|
autoFocus={true}
|
||||||
kind={kinds.DANGER}
|
kind={kinds.DANGER}
|
||||||
onPress={onConfirm}
|
onPress={onConfirm}
|
||||||
>
|
>
|
||||||
@ -52,11 +59,12 @@ PendingChangesModal.propTypes = {
|
|||||||
isOpen: PropTypes.bool.isRequired,
|
isOpen: PropTypes.bool.isRequired,
|
||||||
kind: PropTypes.oneOf(kinds.all),
|
kind: PropTypes.oneOf(kinds.all),
|
||||||
onConfirm: PropTypes.func.isRequired,
|
onConfirm: PropTypes.func.isRequired,
|
||||||
onCancel: PropTypes.func.isRequired
|
onCancel: PropTypes.func.isRequired,
|
||||||
|
bindShortcut: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
PendingChangesModal.defaultProps = {
|
PendingChangesModal.defaultProps = {
|
||||||
kind: kinds.PRIMARY
|
kind: kinds.PRIMARY
|
||||||
};
|
};
|
||||||
|
|
||||||
export default PendingChangesModal;
|
export default keyboardShortcuts(PendingChangesModal);
|
||||||
|
@ -96,6 +96,7 @@
|
|||||||
"react-dnd-html5-backend": "9.3.2",
|
"react-dnd-html5-backend": "9.3.2",
|
||||||
"react-document-title": "2.0.3",
|
"react-document-title": "2.0.3",
|
||||||
"react-dom": "16.8.6",
|
"react-dom": "16.8.6",
|
||||||
|
"react-focus-lock": "2.2.1",
|
||||||
"react-google-recaptcha": "1.1.0",
|
"react-google-recaptcha": "1.1.0",
|
||||||
"react-lazyload": "2.6.2",
|
"react-lazyload": "2.6.2",
|
||||||
"react-measure": "1.4.7",
|
"react-measure": "1.4.7",
|
||||||
|
49
yarn.lock
49
yarn.lock
@ -825,6 +825,13 @@
|
|||||||
"@babel/plugin-transform-react-jsx-self" "^7.0.0"
|
"@babel/plugin-transform-react-jsx-self" "^7.0.0"
|
||||||
"@babel/plugin-transform-react-jsx-source" "^7.0.0"
|
"@babel/plugin-transform-react-jsx-source" "^7.0.0"
|
||||||
|
|
||||||
|
"@babel/runtime@^7.0.0":
|
||||||
|
version "7.8.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.8.4.tgz#d79f5a2040f7caa24d53e563aad49cbc05581308"
|
||||||
|
integrity sha512-neAp3zt80trRVBI1x0azq6c57aNBqYZH8KhMm3TaB7wEI5Q4A2SHfBHE8w9gOhI/lrqxtEbXZgQIrHP+wvSGwQ==
|
||||||
|
dependencies:
|
||||||
|
regenerator-runtime "^0.13.2"
|
||||||
|
|
||||||
"@babel/runtime@^7.1.2", "@babel/runtime@^7.4.0", "@babel/runtime@^7.4.5":
|
"@babel/runtime@^7.1.2", "@babel/runtime@^7.4.0", "@babel/runtime@^7.4.5":
|
||||||
version "7.5.5"
|
version "7.5.5"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.5.5.tgz#74fba56d35efbeca444091c7850ccd494fd2f132"
|
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.5.5.tgz#74fba56d35efbeca444091c7850ccd494fd2f132"
|
||||||
@ -2857,6 +2864,11 @@ detect-newline@2.X:
|
|||||||
resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-2.1.0.tgz#f41f1c10be4b00e87b5f13da680759f2c5bfd3e2"
|
resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-2.1.0.tgz#f41f1c10be4b00e87b5f13da680759f2c5bfd3e2"
|
||||||
integrity sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=
|
integrity sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=
|
||||||
|
|
||||||
|
detect-node@^2.0.4:
|
||||||
|
version "2.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c"
|
||||||
|
integrity sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==
|
||||||
|
|
||||||
diff@^1.3.2:
|
diff@^1.3.2:
|
||||||
version "1.4.0"
|
version "1.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/diff/-/diff-1.4.0.tgz#7f28d2eb9ee7b15a97efd89ce63dcfdaa3ccbabf"
|
resolved "https://registry.yarnpkg.com/diff/-/diff-1.4.0.tgz#7f28d2eb9ee7b15a97efd89ce63dcfdaa3ccbabf"
|
||||||
@ -3817,6 +3829,11 @@ flush-write-stream@^1.0.0, flush-write-stream@^1.0.2:
|
|||||||
inherits "^2.0.3"
|
inherits "^2.0.3"
|
||||||
readable-stream "^2.3.6"
|
readable-stream "^2.3.6"
|
||||||
|
|
||||||
|
focus-lock@^0.6.6:
|
||||||
|
version "0.6.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/focus-lock/-/focus-lock-0.6.6.tgz#98119a755a38cfdbeda0280eaa77e307eee850c7"
|
||||||
|
integrity sha512-Dx69IXGCq1qsUExWuG+5wkiMqVM/zGx/reXSJSLogECwp3x6KeNQZ+NAetgxEFpnC41rD8U3+jRCW68+LNzdtw==
|
||||||
|
|
||||||
for-in@^1.0.1, for-in@^1.0.2:
|
for-in@^1.0.1, for-in@^1.0.2:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
|
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
|
||||||
@ -7268,6 +7285,13 @@ react-autowhatever@^10.1.2:
|
|||||||
react-themeable "^1.1.0"
|
react-themeable "^1.1.0"
|
||||||
section-iterator "^2.0.0"
|
section-iterator "^2.0.0"
|
||||||
|
|
||||||
|
react-clientside-effect@^1.2.2:
|
||||||
|
version "1.2.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-clientside-effect/-/react-clientside-effect-1.2.2.tgz#6212fb0e07b204e714581dd51992603d1accc837"
|
||||||
|
integrity sha512-nRmoyxeok5PBO6ytPvSjKp9xwXg9xagoTK1mMjwnQxqM9Hd7MNPl+LS1bOSOe+CV2+4fnEquc7H/S8QD3q697A==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.0.0"
|
||||||
|
|
||||||
react-custom-scrollbars@4.2.1:
|
react-custom-scrollbars@4.2.1:
|
||||||
version "4.2.1"
|
version "4.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-custom-scrollbars/-/react-custom-scrollbars-4.2.1.tgz#830fd9502927e97e8a78c2086813899b2a8b66db"
|
resolved "https://registry.yarnpkg.com/react-custom-scrollbars/-/react-custom-scrollbars-4.2.1.tgz#830fd9502927e97e8a78c2086813899b2a8b66db"
|
||||||
@ -7313,6 +7337,18 @@ react-dom@16.8.6:
|
|||||||
prop-types "^15.6.2"
|
prop-types "^15.6.2"
|
||||||
scheduler "^0.13.6"
|
scheduler "^0.13.6"
|
||||||
|
|
||||||
|
react-focus-lock@2.2.1:
|
||||||
|
version "2.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-focus-lock/-/react-focus-lock-2.2.1.tgz#1d12887416925dc53481914b7cedd39494a3b24a"
|
||||||
|
integrity sha512-47g0xYcCTZccdzKRGufepY8oZ3W1Qg+2hn6u9SHZ0zUB6uz/4K4xJe7yYFNZ1qT6m+2JDm82F6QgKeBTbjW4PQ==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.0.0"
|
||||||
|
focus-lock "^0.6.6"
|
||||||
|
prop-types "^15.6.2"
|
||||||
|
react-clientside-effect "^1.2.2"
|
||||||
|
use-callback-ref "^1.2.1"
|
||||||
|
use-sidecar "^1.0.1"
|
||||||
|
|
||||||
react-google-recaptcha@1.1.0:
|
react-google-recaptcha@1.1.0:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-google-recaptcha/-/react-google-recaptcha-1.1.0.tgz#f33bef3e22e8c016820e80da48d573f516bb99e8"
|
resolved "https://registry.yarnpkg.com/react-google-recaptcha/-/react-google-recaptcha-1.1.0.tgz#f33bef3e22e8c016820e80da48d573f516bb99e8"
|
||||||
@ -9210,6 +9246,19 @@ url@^0.11.0:
|
|||||||
punycode "1.3.2"
|
punycode "1.3.2"
|
||||||
querystring "0.2.0"
|
querystring "0.2.0"
|
||||||
|
|
||||||
|
use-callback-ref@^1.2.1:
|
||||||
|
version "1.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.2.1.tgz#898759ccb9e14be6c7a860abafa3ffbd826c89bb"
|
||||||
|
integrity sha512-C3nvxh0ZpaOxs9RCnWwAJ+7bJPwQI8LHF71LzbQ3BvzH5XkdtlkMadqElGevg5bYBDFip4sAnD4m06zAKebg1w==
|
||||||
|
|
||||||
|
use-sidecar@^1.0.1:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/use-sidecar/-/use-sidecar-1.0.2.tgz#e72f582a75842f7de4ef8becd6235a4720ad8af6"
|
||||||
|
integrity sha512-287RZny6m5KNMTb/Kq9gmjafi7lQL0YHO1lYolU6+tY1h9+Z3uCtkJJ3OSOq3INwYf2hBryCcDh4520AhJibMA==
|
||||||
|
dependencies:
|
||||||
|
detect-node "^2.0.4"
|
||||||
|
tslib "^1.9.3"
|
||||||
|
|
||||||
use@^3.1.0:
|
use@^3.1.0:
|
||||||
version "3.1.1"
|
version "3.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"
|
resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"
|
||||||
|
Loading…
Reference in New Issue
Block a user