Implemented complete suggestion interface

This commit is contained in:
Cássio Zen 2016-07-06 15:18:01 -03:00
parent 473357b6b7
commit 0a359f253e
2 changed files with 210 additions and 33 deletions

View File

@ -0,0 +1,38 @@
.root {
width: 350px;
}
.input {
background: none transparent;
border: 0 none;
font-size: inherit;
outline: none;
width: 100%;
}
.inputArea {
border: solid 1px #000;
}
.menu {
border-radius: 3px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
background: rgba(255, 255, 255, 0.9);
padding: 2px 0;
overflow: auto;
width: 350px;
height: 100px;
}
.command {
padding: 2px 6px;
cursor: default;
}
.highlightedCommand {
color: white;
background: hsl(200, 50%, 50%);
padding: 2px 6px;
cursor: default;
}

View File

@ -1,26 +1,39 @@
import React, { PropTypes } from 'react';
import React, { Component, PropTypes } from 'react';
import fuzzy from 'fuzzy';
import _ from 'lodash';
import { connect } from 'react-redux';
import styles from './FindBar.css';
class FindBar extends React.Component {
class FindBar extends Component {
constructor(props) {
super(props);
this.compiledCommands = {};
this._compiledCommands = {};
this.state = {
prompt: '',
value: '',
suggestions: []
value: this.props.initialValue,
isOpen: false,
highlightedIndex: 0,
};
this.compileCommand = this.compileCommand.bind(this);
this.matchCommand = this.matchCommand.bind(this);
this.getSuggestions = _.throttle(this.getSuggestions.bind(this), 200);
this.handleInputChange = this.handleInputChange.bind(this);
this.handleChange = this.handleChange.bind(this);
this.handleKeyDown = this.handleKeyDown.bind(this);
this.handleInputBlur = this.handleInputBlur.bind(this);
this.handleInputFocus = this.handleInputFocus.bind(this);
this.handleInputClick = this.handleInputClick.bind(this);
this.getSuggestions = _.memoize(this.getSuggestions);
this.highlightCommandFromMouse = this.highlightCommandFromMouse.bind(this);
this.selectCommandFromMouse = this.selectCommandFromMouse.bind(this);
this.setIgnoreBlur = this.setIgnoreBlur.bind(this);
this.renderMenu = this.renderMenu.bind(this);
}
componentWillMount() {
this._ignoreBlur = false;
}
componentDidMount() {
this.compiledCommands = this.props.commands.map(this.compileCommand);
this._compiledCommands = this.props.commands.map(this.compileCommand);
}
_escapeRegExp(string) {
@ -28,7 +41,7 @@ class FindBar extends React.Component {
}
_camelCaseToSpace(string) {
var result = string.replace(/([A-Z])/g, ' $1');
const result = string.replace(/([A-Z])/g, ' $1');
return result.charAt(0).toUpperCase() + result.slice(1);
}
@ -68,40 +81,166 @@ class FindBar extends React.Component {
};
}
getSuggestions(value) {
console.log(value);
const options = {
//pre: '<strong>',
//post: '</strong>',
extract: el => el. token
};
const results = fuzzy.filter(value, this.compiledCommands, options);
return results;
handleChange(event) {
this.setState({
value: event.target.value,
}, () => {
this.props.onChange(event, this.state.value);
});
}
handleInputChange(e) {
const newValue = e.target.value;
handleKeyDown(event) {
switch (event.key) {
case 'ArrowDown':
event.preventDefault();
let { highlightedIndex } = this.state;
let index = (
highlightedIndex === this.getSuggestions(this.state.value, this._compiledCommands).length - 1 ||
this.state.isOpen === false
) ? 0 : highlightedIndex + 1;
this.setState({
value: newValue,
suggestions: this.getSuggestions(newValue)
highlightedIndex: index,
isOpen: true,
});
break;
case 'ArrowUp':
event.preventDefault();
let { highlightedIndex } = this.state;
let index = (
highlightedIndex === 0
) ? this.getSuggestions(this.state.value, this._compiledCommands).length - 1 : highlightedIndex - 1;
this.setState({
highlightedIndex: index,
isOpen: true,
});
break;
case 'Enter':
if (this.state.isOpen) {
const command = this.getSuggestions(this.state.value, this._compiledCommands)[this.state.highlightedIndex];
this.setState({
value: command.token,
isOpen: false,
highlightedIndex: 0
}, () => {
this.refs.input.focus();
this.refs.input.setSelectionRange(
this.state.value.length,
this.state.value.length
);
this.props.onSelect(this.state.value, command);
});
}
break;
case 'Escape':
this.setState({
highlightedIndex: 0,
isOpen: false
});
break;
default:
this.setState({
highlightedIndex: 0,
isOpen: true
});
}
}
handleInputBlur() {
if (this._ignoreBlur) return;
this.setState({
isOpen: false,
highlightedIndex: 0
});
}
handleInputFocus() {
if (this._ignoreBlur) return;
this.setState({ isOpen: true });
}
handleInputClick() {
if (this.state.isOpen === false)
this.setState({ isOpen: true });
}
getSuggestions(value, commands) {
const results = fuzzy.filter(value, commands, {
//pre: '<strong>',
//post: '</strong>',
extract: el => el.token
});
return results.map(result => result.original);
}
highlightCommandFromMouse(index) {
this.setState({ highlightedIndex: index });
}
selectCommandFromMouse(command) {
this.setState({
value: command.token,
isOpen: false,
highlightedIndex: 0
}, () => {
this.props.onSelect(this.state.value, command);
this.refs.input.focus();
this.setIgnoreBlur(false);
});
}
setIgnoreBlur(ignore) {
this._ignoreBlur = ignore;
}
renderMenu() {
const commands = this.getSuggestions(this.state.value, this._compiledCommands).map((command, index) => {
return (
<div
className={this.state.highlightedIndex === index ? styles.highlightedCommand : styles.command}
key={command.token.trim().replace(/[^a-z0-9]+/gi, '-')}
onMouseDown={() => this.setIgnoreBlur(true)}
onMouseEnter={() => this.highlightCommandFromMouse(index)}
onClick={() => this.selectCommandFromMouse(command)}
ref={`command-${index}`}
>{command.token}</div>
);
});
return <div className={styles.menu} children={commands} ref='menu'/>;
}
render() {
return (
<div className={styles.root}>
<div className={styles.inputArea}>
<input
type='text'
prompt={this.state.prompt}
ref="input"
className={styles.input}
onFocus={this.handleInputFocus}
onBlur={this.handleInputBlur}
onChange={(event) => this.handleChange(event)}
onKeyDown={(event) => this.handleKeyDown(event)}
onClick={this.handleInputClick}
value={this.state.value}
onChange={this.handleInputChange}
/>
</div>
{this.state.isOpen && this.renderMenu()}
</div>
);
}
}
FindBar.propTypes = {
commands: PropTypes.array.isRequired
commands: PropTypes.array,
initialValue: PropTypes.any,
onChange: PropTypes.func,
onSelect: PropTypes.func,
};
export default connect(null)(FindBar);
FindBar.defaultProps = {
initialValue: '',
onChange() {},
onSelect(value, command) {}
};
module.exports = FindBar;