add {cmd,ctrl} + l for wunderbar focus
This commit is contained in:
parent
3815d36f58
commit
71327875f8
7 changed files with 189 additions and 399 deletions
|
@ -1,5 +1,5 @@
|
||||||
|
// @flow
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import * as icons from 'constants/icons';
|
import * as icons from 'constants/icons';
|
||||||
|
|
||||||
|
@ -7,30 +7,29 @@ let scriptLoading = false;
|
||||||
let scriptLoaded = false;
|
let scriptLoaded = false;
|
||||||
let scriptDidError = 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 {
|
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) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
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 */
|
/* eslint-disable */
|
||||||
|
|
||||||
const React = require('react');
|
const React = require('react');
|
||||||
const PropTypes = require('prop-types');
|
|
||||||
const { findDOMNode } = require('react-dom');
|
const { findDOMNode } = require('react-dom');
|
||||||
const scrollIntoView = require('dom-scroll-into-view');
|
const scrollIntoView = require('dom-scroll-into-view');
|
||||||
|
|
||||||
|
@ -45,129 +44,129 @@ function getScrollOffset() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class Autocomplete extends React.Component {
|
export default class Autocomplete extends React.Component {
|
||||||
static propTypes = {
|
// static propTypes = {
|
||||||
/**
|
// /**
|
||||||
* The items to display in the dropdown menu
|
// * The items to display in the dropdown menu
|
||||||
*/
|
// */
|
||||||
items: PropTypes.array.isRequired,
|
// items: PropTypes.array.isRequired,
|
||||||
/**
|
// /**
|
||||||
* The value to display in the input field
|
// * The value to display in the input field
|
||||||
*/
|
// */
|
||||||
value: PropTypes.any,
|
// value: PropTypes.any,
|
||||||
/**
|
// /**
|
||||||
* Arguments: `event: Event, value: String`
|
// * Arguments: `event: Event, value: String`
|
||||||
*
|
// *
|
||||||
* Invoked every time the user changes the input's value.
|
// * Invoked every time the user changes the input's value.
|
||||||
*/
|
// */
|
||||||
onChange: PropTypes.func,
|
// onChange: PropTypes.func,
|
||||||
/**
|
// /**
|
||||||
* Arguments: `value: String, item: Any`
|
// * Arguments: `value: String, item: Any`
|
||||||
*
|
// *
|
||||||
* Invoked when the user selects an item from the dropdown menu.
|
// * Invoked when the user selects an item from the dropdown menu.
|
||||||
*/
|
// */
|
||||||
onSelect: PropTypes.func,
|
// onSelect: PropTypes.func,
|
||||||
/**
|
// /**
|
||||||
* Arguments: `item: Any, value: String`
|
// * Arguments: `item: Any, value: String`
|
||||||
*
|
// *
|
||||||
* Invoked for each entry in `items` and its return value is used to
|
// * 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.
|
// * determine whether or not it should be displayed in the dropdown menu.
|
||||||
* By default all items are always rendered.
|
// * By default all items are always rendered.
|
||||||
*/
|
// */
|
||||||
shouldItemRender: PropTypes.func,
|
// shouldItemRender: PropTypes.func,
|
||||||
/**
|
// /**
|
||||||
* Arguments: `itemA: Any, itemB: Any, value: String`
|
// * Arguments: `itemA: Any, itemB: Any, value: String`
|
||||||
*
|
// *
|
||||||
* The function which is used to sort `items` before display.
|
// * The function which is used to sort `items` before display.
|
||||||
*/
|
// */
|
||||||
sortItems: PropTypes.func,
|
// sortItems: PropTypes.func,
|
||||||
/**
|
// /**
|
||||||
* Arguments: `item: Any`
|
// * Arguments: `item: Any`
|
||||||
*
|
// *
|
||||||
* Used to read the display value from each entry in `items`.
|
// * Used to read the display value from each entry in `items`.
|
||||||
*/
|
// */
|
||||||
getItemValue: PropTypes.func.isRequired,
|
// getItemValue: PropTypes.func.isRequired,
|
||||||
/**
|
// /**
|
||||||
* Arguments: `item: Any, isHighlighted: Boolean, styles: Object`
|
// * Arguments: `item: Any, isHighlighted: Boolean, styles: Object`
|
||||||
*
|
// *
|
||||||
* Invoked for each entry in `items` that also passes `shouldItemRender` to
|
// * Invoked for each entry in `items` that also passes `shouldItemRender` to
|
||||||
* generate the render tree for each item in the dropdown menu. `styles` is
|
// * 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
|
// * an optional set of styles that can be applied to improve the look/feel
|
||||||
* of the items in the dropdown menu.
|
// * of the items in the dropdown menu.
|
||||||
*/
|
// */
|
||||||
renderItem: PropTypes.func.isRequired,
|
// renderItem: PropTypes.func.isRequired,
|
||||||
/**
|
// /**
|
||||||
* Arguments: `items: Array<Any>, value: String, styles: Object`
|
// * Arguments: `items: Array<Any>, value: String, styles: Object`
|
||||||
*
|
// *
|
||||||
* Invoked to generate the render tree for the dropdown menu. Ensure the
|
// * Invoked to generate the render tree for the dropdown menu. Ensure the
|
||||||
* returned tree includes every entry in `items` or else the highlight order
|
// * returned tree includes every entry in `items` or else the highlight order
|
||||||
* and keyboard navigation logic will break. `styles` will contain
|
// * and keyboard navigation logic will break. `styles` will contain
|
||||||
* { top, left, minWidth } which are the coordinates of the top-left corner
|
// * { top, left, minWidth } which are the coordinates of the top-left corner
|
||||||
* and the width of the dropdown menu.
|
// * and the width of the dropdown menu.
|
||||||
*/
|
// */
|
||||||
renderMenu: PropTypes.func,
|
// renderMenu: PropTypes.func,
|
||||||
/**
|
// /**
|
||||||
* Styles that are applied to the dropdown menu in the default `renderMenu`
|
// * Styles that are applied to the dropdown menu in the default `renderMenu`
|
||||||
* implementation. If you override `renderMenu` and you want to use
|
// * implementation. If you override `renderMenu` and you want to use
|
||||||
* `menuStyle` you must manually apply them (`this.props.menuStyle`).
|
// * `menuStyle` you must manually apply them (`this.props.menuStyle`).
|
||||||
*/
|
// */
|
||||||
menuStyle: PropTypes.object,
|
// menuStyle: PropTypes.object,
|
||||||
/**
|
// /**
|
||||||
* Arguments: `props: Object`
|
// * Arguments: `props: Object`
|
||||||
*
|
// *
|
||||||
* Invoked to generate the input element. The `props` argument is the result
|
// * Invoked to generate the input element. The `props` argument is the result
|
||||||
* of merging `props.inputProps` with a selection of props that are required
|
// * of merging `props.inputProps` with a selection of props that are required
|
||||||
* both for functionality and accessibility. At the very least you need to
|
// * 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
|
// * apply `props.ref` and all `props.on<event>` event handlers. Failing to do
|
||||||
* this will cause `Autocomplete` to behave unexpectedly.
|
// * this will cause `Autocomplete` to behave unexpectedly.
|
||||||
*/
|
// */
|
||||||
renderInput: PropTypes.func,
|
// renderInput: PropTypes.func,
|
||||||
/**
|
// /**
|
||||||
* Props passed to `props.renderInput`. By default these props will be
|
// * Props passed to `props.renderInput`. By default these props will be
|
||||||
* applied to the `<input />` element rendered by `Autocomplete`, unless you
|
// * applied to the `<input />` element rendered by `Autocomplete`, unless you
|
||||||
* have specified a custom value for `props.renderInput`. Any properties
|
// * have specified a custom value for `props.renderInput`. Any properties
|
||||||
* supported by `HTMLInputElement` can be specified, apart from the
|
// * supported by `HTMLInputElement` can be specified, apart from the
|
||||||
* following which are set by `Autocomplete`: value, autoComplete, role,
|
// * following which are set by `Autocomplete`: value, autoComplete, role,
|
||||||
* aria-autocomplete. `inputProps` is commonly used for (but not limited to)
|
// * aria-autocomplete. `inputProps` is commonly used for (but not limited to)
|
||||||
* placeholder, event handlers (onFocus, onBlur, etc.), autoFocus, etc..
|
// * placeholder, event handlers (onFocus, onBlur, etc.), autoFocus, etc..
|
||||||
*/
|
// */
|
||||||
inputProps: PropTypes.object,
|
// inputProps: PropTypes.object,
|
||||||
/**
|
// /**
|
||||||
* Props that are applied to the element which wraps the `<input />` and
|
// * Props that are applied to the element which wraps the `<input />` and
|
||||||
* dropdown menu elements rendered by `Autocomplete`.
|
// * dropdown menu elements rendered by `Autocomplete`.
|
||||||
*/
|
// */
|
||||||
wrapperProps: PropTypes.object,
|
// wrapperProps: PropTypes.object,
|
||||||
/**
|
// /**
|
||||||
* This is a shorthand for `wrapperProps={{ style: <your styles> }}`.
|
// * This is a shorthand for `wrapperProps={{ style: <your styles> }}`.
|
||||||
* Note that `wrapperStyle` is applied before `wrapperProps`, so the latter
|
// * Note that `wrapperStyle` is applied before `wrapperProps`, so the latter
|
||||||
* will win if it contains a `style` entry.
|
// * will win if it contains a `style` entry.
|
||||||
*/
|
// */
|
||||||
wrapperStyle: PropTypes.object,
|
// wrapperStyle: PropTypes.object,
|
||||||
/**
|
// /**
|
||||||
* Whether or not to automatically highlight the top match in the dropdown
|
// * Whether or not to automatically highlight the top match in the dropdown
|
||||||
* menu.
|
// * menu.
|
||||||
*/
|
// */
|
||||||
autoHighlight: PropTypes.bool,
|
// autoHighlight: PropTypes.bool,
|
||||||
/**
|
// /**
|
||||||
* Whether or not to automatically select the highlighted item when the
|
// * Whether or not to automatically select the highlighted item when the
|
||||||
* `<input>` loses focus.
|
// * `<input>` loses focus.
|
||||||
*/
|
// */
|
||||||
selectOnBlur: PropTypes.bool,
|
// selectOnBlur: PropTypes.bool,
|
||||||
/**
|
// /**
|
||||||
* Arguments: `isOpen: Boolean`
|
// * Arguments: `isOpen: Boolean`
|
||||||
*
|
// *
|
||||||
* Invoked every time the dropdown menu's visibility changes (i.e. every
|
// * Invoked every time the dropdown menu's visibility changes (i.e. every
|
||||||
* time it is displayed/hidden).
|
// * time it is displayed/hidden).
|
||||||
*/
|
// */
|
||||||
onMenuVisibilityChange: PropTypes.func,
|
// onMenuVisibilityChange: PropTypes.func,
|
||||||
/**
|
// /**
|
||||||
* Used to override the internal logic which displays/hides the dropdown
|
// * 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
|
// * menu. This is useful if you want to force a certain state based on your
|
||||||
* UX/business logic. Use it together with `onMenuVisibilityChange` for
|
// * UX/business logic. Use it together with `onMenuVisibilityChange` for
|
||||||
* fine-grained control over the dropdown menu dynamics.
|
// * fine-grained control over the dropdown menu dynamics.
|
||||||
*/
|
// */
|
||||||
open: PropTypes.bool,
|
// open: PropTypes.bool,
|
||||||
debug: PropTypes.bool,
|
// debug: PropTypes.bool,
|
||||||
};
|
// };
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
value: '',
|
value: '',
|
||||||
|
|
|
@ -7,6 +7,9 @@ import { parseQueryParams } from 'util/query_params';
|
||||||
import * as icons from 'constants/icons';
|
import * as icons from 'constants/icons';
|
||||||
import Autocomplete from './internal/autocomplete';
|
import Autocomplete from './internal/autocomplete';
|
||||||
|
|
||||||
|
const L_KEY_CODE = 76;
|
||||||
|
const ESC_KEY_CODE = 27;
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
updateSearchQuery: string => void,
|
updateSearchQuery: string => void,
|
||||||
onSearch: string => void,
|
onSearch: string => void,
|
||||||
|
@ -16,15 +19,23 @@ type Props = {
|
||||||
doFocus: () => void,
|
doFocus: () => void,
|
||||||
doBlur: () => void,
|
doBlur: () => void,
|
||||||
resultCount: number,
|
resultCount: number,
|
||||||
|
focused: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
class WunderBar extends React.PureComponent<Props> {
|
class WunderBar extends React.PureComponent<Props> {
|
||||||
constructor(props: Props) {
|
constructor() {
|
||||||
super(props);
|
super();
|
||||||
|
|
||||||
(this: any).handleSubmit = this.handleSubmit.bind(this);
|
(this: any).handleSubmit = this.handleSubmit.bind(this);
|
||||||
(this: any).handleChange = this.handleChange.bind(this);
|
(this: any).handleChange = this.handleChange.bind(this);
|
||||||
this.input = undefined;
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
window.addEventListener('keydown', this.handleKeyDown.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
window.removeEventListener('keydown', this.handleKeyDown);
|
||||||
}
|
}
|
||||||
|
|
||||||
getSuggestionIcon = (type: string) => {
|
getSuggestionIcon = (type: string) => {
|
||||||
|
@ -38,6 +49,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<*>) {
|
handleChange(e: SyntheticInputEvent<*>) {
|
||||||
const { updateSearchQuery } = this.props;
|
const { updateSearchQuery } = this.props;
|
||||||
const { value } = e.target;
|
const { value } = e.target;
|
||||||
|
@ -106,6 +142,10 @@ class WunderBar extends React.PureComponent<Props> {
|
||||||
renderInput={props => (
|
renderInput={props => (
|
||||||
<input
|
<input
|
||||||
{...props}
|
{...props}
|
||||||
|
ref={el => {
|
||||||
|
props.ref(el);
|
||||||
|
this.input = el;
|
||||||
|
}}
|
||||||
className="wunderbar__input"
|
className="wunderbar__input"
|
||||||
placeholder="Enter LBRY URL here or search for videos, music, games and more"
|
placeholder="Enter LBRY URL here or search for videos, music, games and more"
|
||||||
/>
|
/>
|
||||||
|
|
Loading…
Reference in a new issue