From 19df39f8a349c38242f96d0aeef4c78b70cb20b3 Mon Sep 17 00:00:00 2001 From: Quinn Slack Date: Sat, 20 Oct 2018 01:36:46 -0700 Subject: [PATCH] feat: support configurable keybindings for CommandList/PopoverButton --- package.json | 1 + src/app/CommandList.tsx | 8 ++-- src/app/ExtensionStatus.tsx | 1 - src/ui/generic/PopoverButton.tsx | 69 +++++++------------------------- yarn.lock | 7 ++++ 5 files changed, 27 insertions(+), 59 deletions(-) diff --git a/package.json b/package.json index f1e2aec..32b4f2d 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ }, "sideEffects": false, "dependencies": { + "@shopify/react-shortcuts": "^1.0.2", "bootstrap": "^4.1.3", "lodash-es": "^4.17.10", "react": "^16.4.2", diff --git a/src/app/CommandList.tsx b/src/app/CommandList.tsx index 0fffe85..804db7d 100644 --- a/src/app/CommandList.tsx +++ b/src/app/CommandList.tsx @@ -1,3 +1,4 @@ +import { Shortcut } from '@shopify/react-shortcuts' import { isArray, sortBy, uniq } from 'lodash-es' import * as React from 'react' import { Subscription } from 'rxjs' @@ -251,7 +252,9 @@ export function filterAndRankItems( } export class CommandListPopoverButton extends React.PureComponent< - Props, + Props & { + toggleVisibilityKeybinding?: Pick[] + }, { hideOnChange?: any } > { public state: { hideOnChange?: any } = {} @@ -262,8 +265,7 @@ export class CommandListPopoverButton} > diff --git a/src/app/ExtensionStatus.tsx b/src/app/ExtensionStatus.tsx index c4a6b17..18a3508 100644 --- a/src/app/ExtensionStatus.tsx +++ b/src/app/ExtensionStatus.tsx @@ -143,7 +143,6 @@ export class ExtensionStatusPopover} > diff --git a/src/ui/generic/PopoverButton.tsx b/src/ui/generic/PopoverButton.tsx index 1835b11..54b559e 100644 --- a/src/ui/generic/PopoverButton.tsx +++ b/src/ui/generic/PopoverButton.tsx @@ -1,7 +1,6 @@ +import { Shortcut } from '@shopify/react-shortcuts' import * as React from 'react' import Popover, { PopoverProps } from 'reactstrap/lib/Popover' -import { Subscription } from 'rxjs' -import { Key } from 'ts-key-enum' import { LinkOrSpan } from './LinkOrSpan' interface Props { @@ -35,17 +34,10 @@ interface Props { */ hideOnChange?: any - /** If set, pressing this key toggles the popover's open state. */ - globalKeyBinding?: string - /** - * Whether the global keybinding should be active even when the user has an input element focused. This should - * only be used for keybindings that would not conflict with routine user input. For example, use it for a - * keybinding to F1, which will ensure that F1 doesn't open Chrome help when the user is focused in an input - * field. - * + * A keybinding that toggles the visibility of this element. */ - globalKeyBindingActiveInInputs?: boolean + toggleVisibilityKeybinding?: Pick[] /** Popover placement. */ placement?: PopoverProps['placement'] @@ -65,25 +57,14 @@ interface State { export class PopoverButton extends React.PureComponent { public state: State = { open: false } - private subscriptions = new Subscription() - private rootRef: HTMLElement | null = null - public componentDidMount(): void { - window.addEventListener('keydown', this.onGlobalKeyDown) - } - public componentWillReceiveProps(props: Props): void { if (props.hideOnChange !== this.props.hideOnChange) { this.setState({ open: false }) } } - public componentWillUnmount(): void { - this.subscriptions.unsubscribe() - window.removeEventListener('keydown', this.onGlobalKeyDown) - } - public render(): React.ReactFragment { const isOpen = this.state.open || this.props.open @@ -91,13 +72,15 @@ export class PopoverButton extends React.PureComponent { + {isOpen && } {this.props.popoverElement} ) + return (
{ : 'popover-button2__container' } to={this.props.link} - onClick={this.props.link ? this.onClickLink : this.onPopoverVisibilityToggle} + onClick={this.props.link ? this.onClickLink : this.toggleVisibility} > {this.props.children}{' '} {!this.props.link && } @@ -121,13 +104,18 @@ export class PopoverButton extends React.PureComponent {
{popoverAnchor}
) : ( popoverAnchor )} + {this.props.toggleVisibilityKeybinding && + !isOpen && + this.props.toggleVisibilityKeybinding.map((keybinding, i) => ( + + ))}
) } @@ -136,36 +124,7 @@ export class PopoverButton extends React.PureComponent { this.setState({ open: false }) } - private onGlobalKeyDown = (event: KeyboardEvent) => { - if (event.key === Key.Escape) { - // Always close the popover when Escape is pressed, even when in an input. - this.setState({ open: false }) - return - } - - // Otherwise don't interfere with user keyboard input. - if (!this.props.globalKeyBindingActiveInInputs && isInputLike(event.target as HTMLElement)) { - return - } - - if ( - this.props.globalKeyBinding && - !event.ctrlKey && - !event.altKey && - !event.metaKey && - event.key === this.props.globalKeyBinding - ) { - event.preventDefault() - this.onPopoverVisibilityToggle() - } - } - private setRootRef = (e: HTMLElement | null) => (this.rootRef = e) - private onPopoverVisibilityToggle = () => this.setState(prevState => ({ open: !prevState.open })) -} - -/** Reports whether elem is a field that accepts user keyboard input. */ -function isInputLike(elem: HTMLElement): boolean { - return elem.tagName === 'INPUT' || elem.tagName === 'TEXTAREA' || elem.tagName === 'SELECT' + private toggleVisibility = () => this.setState(prevState => ({ open: !prevState.open })) } diff --git a/yarn.lock b/yarn.lock index c868eef..5baf5d9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -348,6 +348,13 @@ into-stream "^3.1.0" lodash "^4.17.4" +"@shopify/react-shortcuts@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@shopify/react-shortcuts/-/react-shortcuts-1.0.2.tgz#01d9f6979ff5cce1d70474e1452fb750f7bbe819" + integrity sha512-KEQd0IKSE/IRmPn5MfboeBxJzh5KGb7OLvpfoxjhcbijj/YsU117H62PKsjBuPMc0fi0f8jTIeg938w2y0U5xg== + dependencies: + prop-types "^15.6.2" + "@sourcegraph/prettierrc@^2.2.0": version "2.2.0" resolved "https://registry.yarnpkg.com/@sourcegraph/prettierrc/-/prettierrc-2.2.0.tgz#af4a6fcd465b0a39a07ffbd8f2d3414d01e603e8"