Merge pull request #2003 from lbryio/wunderbar-shortcut
add {cmd,ctrl} + l for wunderbar focus
This commit is contained in:
commit
3dc3f9bc55
8 changed files with 192 additions and 401 deletions
|
@ -7,7 +7,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
|
|||
|
||||
### Added
|
||||
* Allow typing of encryption password without clicking entry box ([#1977](https://github.com/lbryio/lbry-desktop/pull/1977))
|
||||
|
||||
* Focus on search bar with {cmd,ctrl} + "l" ([#2003](https://github.com/lbryio/lbry-desktop/pull/2003))
|
||||
### Changed
|
||||
* Make tooltip smarter ([#1979](https://github.com/lbryio/lbry-desktop/pull/1979))
|
||||
* Change channel pages to have 48 items instead of 10 ([#2002](https://github.com/lbryio/lbry-desktop/pull/2002))
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Button from 'component/button';
|
||||
import * as icons from 'constants/icons';
|
||||
|
||||
|
@ -7,30 +7,29 @@ let scriptLoading = false;
|
|||
let scriptLoaded = false;
|
||||
let scriptDidError = false;
|
||||
|
||||
type Props = {
|
||||
disabled: boolean,
|
||||
label: ?string,
|
||||
|
||||
// =====================================================
|
||||
// Required by stripe
|
||||
// see Stripe docs for more info:
|
||||
// https://stripe.com/docs/checkout#integration-custom
|
||||
// =====================================================
|
||||
|
||||
// Your publishable key (test or live).
|
||||
// can't use "key" as a prop in react, so have to change the keyname
|
||||
stripeKey: string,
|
||||
|
||||
// The callback to invoke when the Checkout process is complete.
|
||||
// function(token)
|
||||
// token is the token object created.
|
||||
// token.id can be used to create a charge or customer.
|
||||
// token.email contains the email address entered by the user.
|
||||
token: string,
|
||||
};
|
||||
|
||||
class CardVerify extends React.Component {
|
||||
static propTypes = {
|
||||
disabled: PropTypes.bool,
|
||||
|
||||
label: PropTypes.string,
|
||||
|
||||
// =====================================================
|
||||
// Required by stripe
|
||||
// see Stripe docs for more info:
|
||||
// https://stripe.com/docs/checkout#integration-custom
|
||||
// =====================================================
|
||||
|
||||
// Your publishable key (test or live).
|
||||
// can't use "key" as a prop in react, so have to change the keyname
|
||||
stripeKey: PropTypes.string.isRequired,
|
||||
|
||||
// The callback to invoke when the Checkout process is complete.
|
||||
// function(token)
|
||||
// token is the token object created.
|
||||
// token.id can be used to create a charge or customer.
|
||||
// token.email contains the email address entered by the user.
|
||||
token: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import FormField from './view';
|
||||
|
||||
export default connect(null, null, null, { withRef: true })(FormField);
|
|
@ -1,200 +0,0 @@
|
|||
// This file is going to die
|
||||
/* eslint-disable */
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import FileSelector from 'component/common/file-selector';
|
||||
import SimpleMDE from 'react-simplemde-editor';
|
||||
import { formFieldNestedLabelTypes, formFieldId } from 'component/common/form';
|
||||
import style from 'react-simplemde-editor/dist/simplemde.min.css';
|
||||
|
||||
const formFieldFileSelectorTypes = ['file', 'directory'];
|
||||
|
||||
class FormField extends React.PureComponent {
|
||||
static propTypes = {
|
||||
type: PropTypes.string.isRequired,
|
||||
prefix: PropTypes.string,
|
||||
postfix: PropTypes.string,
|
||||
hasError: PropTypes.bool,
|
||||
trim: PropTypes.bool,
|
||||
regexp: PropTypes.oneOfType([PropTypes.instanceOf(RegExp), PropTypes.string]),
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
trim: false,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this._fieldRequiredText = __('This field is required');
|
||||
this._type = null;
|
||||
this._element = null;
|
||||
this._extraElementProps = {};
|
||||
|
||||
this.state = {
|
||||
isError: null,
|
||||
errorMessage: null,
|
||||
};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
if (['text', 'number', 'radio', 'checkbox'].includes(this.props.type)) {
|
||||
this._element = 'input';
|
||||
this._type = this.props.type;
|
||||
} else if (this.props.type == 'text-number') {
|
||||
this._element = 'input';
|
||||
this._type = 'text';
|
||||
} else if (this.props.type == 'SimpleMDE') {
|
||||
this._element = SimpleMDE;
|
||||
this._type = 'textarea';
|
||||
this._extraElementProps.options = {
|
||||
placeholder: this.props.placeholder,
|
||||
hideIcons: ['heading', 'image', 'fullscreen', 'side-by-side'],
|
||||
};
|
||||
} else if (formFieldFileSelectorTypes.includes(this.props.type)) {
|
||||
this._element = 'input';
|
||||
this._type = 'hidden';
|
||||
} else {
|
||||
// Non <input> field, e.g. <select>, <textarea>
|
||||
this._element = this.props.type;
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
/**
|
||||
* We have to add the webkitdirectory attribute here because React doesn't allow it in JSX
|
||||
* https://github.com/facebook/react/issues/3468
|
||||
*/
|
||||
if (this.props.type == 'directory') {
|
||||
this.refs.field.webkitdirectory = true;
|
||||
}
|
||||
}
|
||||
|
||||
handleFileChosen(path) {
|
||||
this.refs.field.value = path;
|
||||
if (this.props.onChange) {
|
||||
// Updating inputs programmatically doesn't generate an event, so we have to make our own
|
||||
const event = new Event('change', { bubbles: true });
|
||||
this.refs.field.dispatchEvent(event); // This alone won't generate a React event, but we use it to attach the field as a target
|
||||
this.props.onChange(event);
|
||||
}
|
||||
}
|
||||
|
||||
showError(text) {
|
||||
this.setState({
|
||||
isError: true,
|
||||
errorMessage: text,
|
||||
});
|
||||
}
|
||||
|
||||
clearError() {
|
||||
this.setState({
|
||||
isError: false,
|
||||
errorMessage: '',
|
||||
});
|
||||
}
|
||||
|
||||
getValue() {
|
||||
if (this.props.type == 'checkbox') {
|
||||
return this.refs.field.checked;
|
||||
} else if (this.props.type == 'SimpleMDE') {
|
||||
return this.refs.field.simplemde.value();
|
||||
}
|
||||
return this.props.trim ? this.refs.field.value.trim() : this.refs.field.value;
|
||||
}
|
||||
|
||||
getSelectedElement() {
|
||||
return this.refs.field.options[this.refs.field.selectedIndex];
|
||||
}
|
||||
|
||||
getOptions() {
|
||||
return this.refs.field.options;
|
||||
}
|
||||
|
||||
validate() {
|
||||
if ('regexp' in this.props) {
|
||||
if (!this.getValue().match(this.props.regexp)) {
|
||||
this.showError(__('Invalid format.'));
|
||||
} else {
|
||||
this.clearError();
|
||||
}
|
||||
}
|
||||
this.props.onBlur && this.props.onBlur();
|
||||
}
|
||||
|
||||
focus() {
|
||||
this.refs.field.focus();
|
||||
}
|
||||
|
||||
render() {
|
||||
// Pass all unhandled props to the field element
|
||||
const otherProps = Object.assign({}, this.props),
|
||||
isError = this.state.isError !== null ? this.state.isError : this.props.hasError,
|
||||
elementId = this.props.elementId ? this.props.elementId : formFieldId(),
|
||||
renderElementInsideLabel =
|
||||
this.props.label && formFieldNestedLabelTypes.includes(this.props.type);
|
||||
|
||||
delete otherProps.type;
|
||||
delete otherProps.label;
|
||||
delete otherProps.hasError;
|
||||
delete otherProps.className;
|
||||
delete otherProps.postfix;
|
||||
delete otherProps.prefix;
|
||||
delete otherProps.dispatch;
|
||||
delete otherProps.regexp;
|
||||
delete otherProps.trim;
|
||||
|
||||
const element = (
|
||||
<this._element
|
||||
id={elementId}
|
||||
type={this._type}
|
||||
name={this.props.name}
|
||||
ref="field"
|
||||
placeholder={this.props.placeholder}
|
||||
onBlur={() => this.validate()}
|
||||
onFocus={() => this.props.onFocus && this.props.onFocus()}
|
||||
className={`form-field__input form-field__input-${this.props.type} ${this.props.className ||
|
||||
''}${isError ? 'form-field__input--error' : ''}`}
|
||||
{...otherProps}
|
||||
{...this._extraElementProps}
|
||||
>
|
||||
{this.props.children}
|
||||
</this._element>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={`form-field form-field--${this.props.type}`}>
|
||||
{this.props.prefix ? <span className="form-field__prefix">{this.props.prefix}</span> : ''}
|
||||
{element}
|
||||
{renderElementInsideLabel && (
|
||||
<label
|
||||
htmlFor={elementId}
|
||||
className={`form-field__label ${isError ? 'form-field__label--error' : ''}`}
|
||||
>
|
||||
{this.props.label}
|
||||
</label>
|
||||
)}
|
||||
{formFieldFileSelectorTypes.includes(this.props.type) ? (
|
||||
<FileSelector
|
||||
type={this.props.type}
|
||||
onFileChosen={this.handleFileChosen.bind(this)}
|
||||
{...(this.props.defaultValue ? { initPath: this.props.defaultValue } : {})}
|
||||
/>
|
||||
) : null}
|
||||
{this.props.postfix ? (
|
||||
<span className="form-field__postfix">{this.props.postfix}</span>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
{isError && this.state.errorMessage ? (
|
||||
<div className="form-field__error">{this.state.errorMessage}</div>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default FormField;
|
||||
/* eslint-enable */
|
|
@ -1,5 +0,0 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import TruncatedMarkdown from './view';
|
||||
|
||||
export default connect()(TruncatedMarkdown);
|
|
@ -1,38 +0,0 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import ReactDOMServer from 'react-dom/server';
|
||||
|
||||
class TruncatedMarkdown extends React.PureComponent {
|
||||
static propTypes = {
|
||||
lines: PropTypes.number,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
lines: null,
|
||||
};
|
||||
|
||||
transformMarkdown(text) {
|
||||
// render markdown to html string then trim html tag
|
||||
const htmlString = ReactDOMServer.renderToStaticMarkup(
|
||||
<ReactMarkdown source={this.props.children} />
|
||||
);
|
||||
const txt = document.createElement('textarea');
|
||||
txt.innerHTML = htmlString;
|
||||
return txt.value.replace(/<(?:.|\n)*?>/gm, '');
|
||||
}
|
||||
|
||||
render() {
|
||||
const content =
|
||||
this.props.children && typeof this.props.children === 'string'
|
||||
? this.transformMarkdown(this.props.children)
|
||||
: this.props.children;
|
||||
return (
|
||||
<span className="truncated-text" style={{ WebkitLineClamp: this.props.lines }}>
|
||||
{content}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default TruncatedMarkdown;
|
|
@ -16,7 +16,6 @@ https://github.com/reactjs/react-autocomplete/issues/239
|
|||
/* eslint-disable */
|
||||
|
||||
const React = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
const { findDOMNode } = require('react-dom');
|
||||
const scrollIntoView = require('dom-scroll-into-view');
|
||||
|
||||
|
@ -45,129 +44,129 @@ function getScrollOffset() {
|
|||
}
|
||||
|
||||
export default class Autocomplete extends React.Component {
|
||||
static propTypes = {
|
||||
/**
|
||||
* The items to display in the dropdown menu
|
||||
*/
|
||||
items: PropTypes.array.isRequired,
|
||||
/**
|
||||
* The value to display in the input field
|
||||
*/
|
||||
value: PropTypes.any,
|
||||
/**
|
||||
* Arguments: `event: Event, value: String`
|
||||
*
|
||||
* Invoked every time the user changes the input's value.
|
||||
*/
|
||||
onChange: PropTypes.func,
|
||||
/**
|
||||
* Arguments: `value: String, item: Any`
|
||||
*
|
||||
* Invoked when the user selects an item from the dropdown menu.
|
||||
*/
|
||||
onSelect: PropTypes.func,
|
||||
/**
|
||||
* Arguments: `item: Any, value: String`
|
||||
*
|
||||
* Invoked for each entry in `items` and its return value is used to
|
||||
* determine whether or not it should be displayed in the dropdown menu.
|
||||
* By default all items are always rendered.
|
||||
*/
|
||||
shouldItemRender: PropTypes.func,
|
||||
/**
|
||||
* Arguments: `itemA: Any, itemB: Any, value: String`
|
||||
*
|
||||
* The function which is used to sort `items` before display.
|
||||
*/
|
||||
sortItems: PropTypes.func,
|
||||
/**
|
||||
* Arguments: `item: Any`
|
||||
*
|
||||
* Used to read the display value from each entry in `items`.
|
||||
*/
|
||||
getItemValue: PropTypes.func.isRequired,
|
||||
/**
|
||||
* Arguments: `item: Any, isHighlighted: Boolean, styles: Object`
|
||||
*
|
||||
* Invoked for each entry in `items` that also passes `shouldItemRender` to
|
||||
* generate the render tree for each item in the dropdown menu. `styles` is
|
||||
* an optional set of styles that can be applied to improve the look/feel
|
||||
* of the items in the dropdown menu.
|
||||
*/
|
||||
renderItem: PropTypes.func.isRequired,
|
||||
/**
|
||||
* Arguments: `items: Array<Any>, value: String, styles: Object`
|
||||
*
|
||||
* Invoked to generate the render tree for the dropdown menu. Ensure the
|
||||
* returned tree includes every entry in `items` or else the highlight order
|
||||
* and keyboard navigation logic will break. `styles` will contain
|
||||
* { top, left, minWidth } which are the coordinates of the top-left corner
|
||||
* and the width of the dropdown menu.
|
||||
*/
|
||||
renderMenu: PropTypes.func,
|
||||
/**
|
||||
* Styles that are applied to the dropdown menu in the default `renderMenu`
|
||||
* implementation. If you override `renderMenu` and you want to use
|
||||
* `menuStyle` you must manually apply them (`this.props.menuStyle`).
|
||||
*/
|
||||
menuStyle: PropTypes.object,
|
||||
/**
|
||||
* Arguments: `props: Object`
|
||||
*
|
||||
* Invoked to generate the input element. The `props` argument is the result
|
||||
* of merging `props.inputProps` with a selection of props that are required
|
||||
* both for functionality and accessibility. At the very least you need to
|
||||
* apply `props.ref` and all `props.on<event>` event handlers. Failing to do
|
||||
* this will cause `Autocomplete` to behave unexpectedly.
|
||||
*/
|
||||
renderInput: PropTypes.func,
|
||||
/**
|
||||
* Props passed to `props.renderInput`. By default these props will be
|
||||
* applied to the `<input />` element rendered by `Autocomplete`, unless you
|
||||
* have specified a custom value for `props.renderInput`. Any properties
|
||||
* supported by `HTMLInputElement` can be specified, apart from the
|
||||
* following which are set by `Autocomplete`: value, autoComplete, role,
|
||||
* aria-autocomplete. `inputProps` is commonly used for (but not limited to)
|
||||
* placeholder, event handlers (onFocus, onBlur, etc.), autoFocus, etc..
|
||||
*/
|
||||
inputProps: PropTypes.object,
|
||||
/**
|
||||
* Props that are applied to the element which wraps the `<input />` and
|
||||
* dropdown menu elements rendered by `Autocomplete`.
|
||||
*/
|
||||
wrapperProps: PropTypes.object,
|
||||
/**
|
||||
* This is a shorthand for `wrapperProps={{ style: <your styles> }}`.
|
||||
* Note that `wrapperStyle` is applied before `wrapperProps`, so the latter
|
||||
* will win if it contains a `style` entry.
|
||||
*/
|
||||
wrapperStyle: PropTypes.object,
|
||||
/**
|
||||
* Whether or not to automatically highlight the top match in the dropdown
|
||||
* menu.
|
||||
*/
|
||||
autoHighlight: PropTypes.bool,
|
||||
/**
|
||||
* Whether or not to automatically select the highlighted item when the
|
||||
* `<input>` loses focus.
|
||||
*/
|
||||
selectOnBlur: PropTypes.bool,
|
||||
/**
|
||||
* Arguments: `isOpen: Boolean`
|
||||
*
|
||||
* Invoked every time the dropdown menu's visibility changes (i.e. every
|
||||
* time it is displayed/hidden).
|
||||
*/
|
||||
onMenuVisibilityChange: PropTypes.func,
|
||||
/**
|
||||
* Used to override the internal logic which displays/hides the dropdown
|
||||
* menu. This is useful if you want to force a certain state based on your
|
||||
* UX/business logic. Use it together with `onMenuVisibilityChange` for
|
||||
* fine-grained control over the dropdown menu dynamics.
|
||||
*/
|
||||
open: PropTypes.bool,
|
||||
debug: PropTypes.bool,
|
||||
};
|
||||
// static propTypes = {
|
||||
// /**
|
||||
// * The items to display in the dropdown menu
|
||||
// */
|
||||
// items: PropTypes.array.isRequired,
|
||||
// /**
|
||||
// * The value to display in the input field
|
||||
// */
|
||||
// value: PropTypes.any,
|
||||
// /**
|
||||
// * Arguments: `event: Event, value: String`
|
||||
// *
|
||||
// * Invoked every time the user changes the input's value.
|
||||
// */
|
||||
// onChange: PropTypes.func,
|
||||
// /**
|
||||
// * Arguments: `value: String, item: Any`
|
||||
// *
|
||||
// * Invoked when the user selects an item from the dropdown menu.
|
||||
// */
|
||||
// onSelect: PropTypes.func,
|
||||
// /**
|
||||
// * Arguments: `item: Any, value: String`
|
||||
// *
|
||||
// * Invoked for each entry in `items` and its return value is used to
|
||||
// * determine whether or not it should be displayed in the dropdown menu.
|
||||
// * By default all items are always rendered.
|
||||
// */
|
||||
// shouldItemRender: PropTypes.func,
|
||||
// /**
|
||||
// * Arguments: `itemA: Any, itemB: Any, value: String`
|
||||
// *
|
||||
// * The function which is used to sort `items` before display.
|
||||
// */
|
||||
// sortItems: PropTypes.func,
|
||||
// /**
|
||||
// * Arguments: `item: Any`
|
||||
// *
|
||||
// * Used to read the display value from each entry in `items`.
|
||||
// */
|
||||
// getItemValue: PropTypes.func.isRequired,
|
||||
// /**
|
||||
// * Arguments: `item: Any, isHighlighted: Boolean, styles: Object`
|
||||
// *
|
||||
// * Invoked for each entry in `items` that also passes `shouldItemRender` to
|
||||
// * generate the render tree for each item in the dropdown menu. `styles` is
|
||||
// * an optional set of styles that can be applied to improve the look/feel
|
||||
// * of the items in the dropdown menu.
|
||||
// */
|
||||
// renderItem: PropTypes.func.isRequired,
|
||||
// /**
|
||||
// * Arguments: `items: Array<Any>, value: String, styles: Object`
|
||||
// *
|
||||
// * Invoked to generate the render tree for the dropdown menu. Ensure the
|
||||
// * returned tree includes every entry in `items` or else the highlight order
|
||||
// * and keyboard navigation logic will break. `styles` will contain
|
||||
// * { top, left, minWidth } which are the coordinates of the top-left corner
|
||||
// * and the width of the dropdown menu.
|
||||
// */
|
||||
// renderMenu: PropTypes.func,
|
||||
// /**
|
||||
// * Styles that are applied to the dropdown menu in the default `renderMenu`
|
||||
// * implementation. If you override `renderMenu` and you want to use
|
||||
// * `menuStyle` you must manually apply them (`this.props.menuStyle`).
|
||||
// */
|
||||
// menuStyle: PropTypes.object,
|
||||
// /**
|
||||
// * Arguments: `props: Object`
|
||||
// *
|
||||
// * Invoked to generate the input element. The `props` argument is the result
|
||||
// * of merging `props.inputProps` with a selection of props that are required
|
||||
// * both for functionality and accessibility. At the very least you need to
|
||||
// * apply `props.ref` and all `props.on<event>` event handlers. Failing to do
|
||||
// * this will cause `Autocomplete` to behave unexpectedly.
|
||||
// */
|
||||
// renderInput: PropTypes.func,
|
||||
// /**
|
||||
// * Props passed to `props.renderInput`. By default these props will be
|
||||
// * applied to the `<input />` element rendered by `Autocomplete`, unless you
|
||||
// * have specified a custom value for `props.renderInput`. Any properties
|
||||
// * supported by `HTMLInputElement` can be specified, apart from the
|
||||
// * following which are set by `Autocomplete`: value, autoComplete, role,
|
||||
// * aria-autocomplete. `inputProps` is commonly used for (but not limited to)
|
||||
// * placeholder, event handlers (onFocus, onBlur, etc.), autoFocus, etc..
|
||||
// */
|
||||
// inputProps: PropTypes.object,
|
||||
// /**
|
||||
// * Props that are applied to the element which wraps the `<input />` and
|
||||
// * dropdown menu elements rendered by `Autocomplete`.
|
||||
// */
|
||||
// wrapperProps: PropTypes.object,
|
||||
// /**
|
||||
// * This is a shorthand for `wrapperProps={{ style: <your styles> }}`.
|
||||
// * Note that `wrapperStyle` is applied before `wrapperProps`, so the latter
|
||||
// * will win if it contains a `style` entry.
|
||||
// */
|
||||
// wrapperStyle: PropTypes.object,
|
||||
// /**
|
||||
// * Whether or not to automatically highlight the top match in the dropdown
|
||||
// * menu.
|
||||
// */
|
||||
// autoHighlight: PropTypes.bool,
|
||||
// /**
|
||||
// * Whether or not to automatically select the highlighted item when the
|
||||
// * `<input>` loses focus.
|
||||
// */
|
||||
// selectOnBlur: PropTypes.bool,
|
||||
// /**
|
||||
// * Arguments: `isOpen: Boolean`
|
||||
// *
|
||||
// * Invoked every time the dropdown menu's visibility changes (i.e. every
|
||||
// * time it is displayed/hidden).
|
||||
// */
|
||||
// onMenuVisibilityChange: PropTypes.func,
|
||||
// /**
|
||||
// * Used to override the internal logic which displays/hides the dropdown
|
||||
// * menu. This is useful if you want to force a certain state based on your
|
||||
// * UX/business logic. Use it together with `onMenuVisibilityChange` for
|
||||
// * fine-grained control over the dropdown menu dynamics.
|
||||
// */
|
||||
// open: PropTypes.bool,
|
||||
// debug: PropTypes.bool,
|
||||
// };
|
||||
|
||||
static defaultProps = {
|
||||
value: '',
|
||||
|
|
|
@ -7,24 +7,36 @@ import { parseQueryParams } from 'util/query_params';
|
|||
import * as icons from 'constants/icons';
|
||||
import Autocomplete from './internal/autocomplete';
|
||||
|
||||
const L_KEY_CODE = 76;
|
||||
const ESC_KEY_CODE = 27;
|
||||
|
||||
type Props = {
|
||||
updateSearchQuery: string => void,
|
||||
onSearch: string => void,
|
||||
onSearch: (string, ?number) => void,
|
||||
onSubmit: (string, {}) => void,
|
||||
wunderbarValue: ?string,
|
||||
suggestions: Array<string>,
|
||||
doFocus: () => void,
|
||||
doBlur: () => void,
|
||||
resultCount: number,
|
||||
focused: boolean,
|
||||
};
|
||||
|
||||
class WunderBar extends React.PureComponent<Props> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
(this: any).handleSubmit = this.handleSubmit.bind(this);
|
||||
(this: any).handleChange = this.handleChange.bind(this);
|
||||
this.input = undefined;
|
||||
(this: any).handleKeyDown = this.handleKeyDown.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
window.addEventListener('keydown', this.handleKeyDown);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener('keydown', this.handleKeyDown);
|
||||
}
|
||||
|
||||
getSuggestionIcon = (type: string) => {
|
||||
|
@ -38,6 +50,31 @@ class WunderBar extends React.PureComponent<Props> {
|
|||
}
|
||||
};
|
||||
|
||||
handleKeyDown(event: SyntheticKeyboardEvent<*>) {
|
||||
const { ctrlKey, metaKey, keyCode } = event;
|
||||
const { doFocus, doBlur, focused } = this.props;
|
||||
|
||||
if (!this.input) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (focused && keyCode === ESC_KEY_CODE) {
|
||||
doBlur();
|
||||
this.input.blur();
|
||||
return;
|
||||
}
|
||||
|
||||
const shouldFocus =
|
||||
process.platform === 'darwin'
|
||||
? keyCode === L_KEY_CODE && metaKey
|
||||
: keyCode === L_KEY_CODE && ctrlKey;
|
||||
|
||||
if (shouldFocus) {
|
||||
doFocus();
|
||||
this.input.focus();
|
||||
}
|
||||
}
|
||||
|
||||
handleChange(e: SyntheticInputEvent<*>) {
|
||||
const { updateSearchQuery } = this.props;
|
||||
const { value } = e.target;
|
||||
|
@ -106,6 +143,10 @@ class WunderBar extends React.PureComponent<Props> {
|
|||
renderInput={props => (
|
||||
<input
|
||||
{...props}
|
||||
ref={el => {
|
||||
props.ref(el);
|
||||
this.input = el;
|
||||
}}
|
||||
className="wunderbar__input"
|
||||
placeholder="Enter LBRY URL here or search for videos, music, games and more"
|
||||
/>
|
||||
|
|
Loading…
Reference in a new issue