From 42667e70903c20b66fa32fbf1fd14b59a839bb1a Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Wed, 20 Mar 2019 04:42:53 -0400 Subject: [PATCH] Switch to Search component in tag selector and support X/Esc to clear This moves debouncing into the search component and adds cancel behavior from the XUL search textbox. For now, this uses the X button from Firefox. --- chrome/content/zotero/components/search.jsx | 93 +++++++++++++++++++ .../zotero/components/tag-selector.jsx | 12 +-- .../content/zotero/containers/tagSelector.jsx | 12 ++- scss/_zotero-react-client.scss | 3 +- scss/components/_search.scss | 21 +++++ scss/components/_tag-selector.scss | 18 ++-- 6 files changed, 138 insertions(+), 21 deletions(-) create mode 100644 chrome/content/zotero/components/search.jsx create mode 100644 scss/components/_search.scss diff --git a/chrome/content/zotero/components/search.jsx b/chrome/content/zotero/components/search.jsx new file mode 100644 index 0000000000..7fed3ca4e4 --- /dev/null +++ b/chrome/content/zotero/components/search.jsx @@ -0,0 +1,93 @@ +'use strict'; + +const React = require('react'); +const PropTypes = require('prop-types'); + +class Search extends React.PureComponent { + constructor(props) { + super(props); + this.inputRef = React.createRef(); + } + + state = { + immediateValue: this.props.value + }; + + static getDerivedStateFromProps(props, state) { + var prevProps = state.prevProps || {}; + return { + prevProps: props, + immediateValue: prevProps.value !== props.value + ? props.value + : state.immediateValue + }; + } + + handleInput = (event) => { + var value = event.target.value; + // Update controlled value and cancel button immediately + this.setState({ + immediateValue: value + }); + // Debounce the search based on the timeout + if (this._timeout) { + clearTimeout(this._timeout); + } + this._timeout = this.props.timeout + && setTimeout(() => this.props.onSearch(value), this.props.timeout); + } + + handleClear = () => { + if (this._timeout) { + clearTimeout(this._timeout); + } + this.setState({ + immediateValue: '' + }); + this.props.onSearch(''); + } + + handleKeyDown = (event) => { + if (event.key == 'Escape') { + this.handleClear(); + } + } + + focus() { + this.inputRef.focus(); + } + + render() { + return ( +
+ + {this.state.immediateValue !== '' + ?
+ : ''} +
+ ); + } + + static propTypes = { + inputRef: PropTypes.object, + onSearch: PropTypes.func, + timeout: PropTypes.number, + value: PropTypes.string, + }; + + static defaultProps = { + onSearch: () => {}, + timeout: 300, + value: '', + }; +} + +module.exports = Search; diff --git a/chrome/content/zotero/components/tag-selector.jsx b/chrome/content/zotero/components/tag-selector.jsx index 4d2bd11f74..bf415fa671 100644 --- a/chrome/content/zotero/components/tag-selector.jsx +++ b/chrome/content/zotero/components/tag-selector.jsx @@ -6,6 +6,7 @@ const TagList = require('./tag-selector/tag-list'); const Input = require('./form/input'); const { Button } = require('./button'); const { IconTagSelectorMenu } = require('./icons'); +const Search = require('./search'); class TagSelector extends React.Component { render() { @@ -13,13 +14,11 @@ class TagSelector extends React.Component {
- this.focusTextbox = ref && ref.focus} +