166 lines
4.6 KiB
JavaScript
Executable file
166 lines
4.6 KiB
JavaScript
Executable file
import React from 'react';
|
|
import classNames from 'classnames';
|
|
import LazyScroller, {Themes} from './common/LazyScroller';
|
|
import i18n from '../i18n';
|
|
import RegexUtils from '../utils/RegexUtils';
|
|
import '../styles/autocomplete_popout.styl';
|
|
|
|
const INPUT_REF = 'input';
|
|
|
|
const AutocompletePopout = React.createClass({
|
|
getDefaultProps() {
|
|
return {
|
|
sections: [null],
|
|
};
|
|
},
|
|
|
|
getInitialState() {
|
|
return this.createState();
|
|
},
|
|
|
|
componentWillReceiveProps() {
|
|
this.setState(this.createState(this.state.prefix));
|
|
},
|
|
|
|
createState(prefix = '') {
|
|
const regex = new RegExp(`^${RegexUtils.escape(prefix.trim())}`, 'i');
|
|
const filter = v => regex.test(v);
|
|
const rows = this.props.sections.map((_, i) => this.props.onFilterResults(filter, i));
|
|
let selectedSection = 0;
|
|
for (; selectedSection < rows.length; selectedSection++) {
|
|
if (rows[selectedSection].length > 0) {
|
|
break;
|
|
}
|
|
}
|
|
return {
|
|
prefix,
|
|
selectedSection,
|
|
selectedRow: 0,
|
|
rows,
|
|
};
|
|
},
|
|
|
|
componentDidMount() {
|
|
process.nextTick(this.focus);
|
|
},
|
|
|
|
handleChange(e) {
|
|
const prefix = e.target.value;
|
|
this.props.onQueryChange && this.props.onQueryChange(prefix);
|
|
this.setState(this.createState(prefix));
|
|
},
|
|
|
|
handleMouseEnter(section, row) {
|
|
this.setState({
|
|
selectedSection: section,
|
|
selectedRow: row,
|
|
});
|
|
},
|
|
|
|
handleClick(section, row) {
|
|
this.props.onSelect(this.state.rows[section][row], section);
|
|
this.props.onClose();
|
|
},
|
|
|
|
handleKeyDown(e) {
|
|
let {selectedSection, selectedRow} = this.state;
|
|
switch (e.keyCode) {
|
|
case 9: // TAB
|
|
case 40: // DOWN
|
|
e.preventDefault();
|
|
if (this.props.sections.length > selectedSection && ++selectedRow >= this.state.rows[selectedSection].length) {
|
|
if (++selectedSection >= this.props.sections.length) {
|
|
selectedSection = 0;
|
|
}
|
|
selectedRow = 0;
|
|
}
|
|
this.setState({selectedSection, selectedRow});
|
|
break;
|
|
case 38: // UP
|
|
e.preventDefault();
|
|
if (--selectedRow < 0) {
|
|
if (--selectedSection < 0) {
|
|
selectedSection = this.props.sections.length - 1;
|
|
}
|
|
selectedRow = this.state.rows[selectedSection].length - 1;
|
|
}
|
|
this.setState({selectedSection, selectedRow});
|
|
break;
|
|
case 13: // ENTER
|
|
e.preventDefault();
|
|
if (this.props.sections.length > selectedSection && this.state.rows[selectedSection].length > selectedRow) {
|
|
this.props.onSelect(this.state.rows[selectedSection][selectedRow], selectedSection);
|
|
this.props.onClose();
|
|
}
|
|
break;
|
|
case 27: // ESCAPE
|
|
e.preventDefault();
|
|
this.props.onSelect(null, null);
|
|
this.props.onClose();
|
|
break;
|
|
}
|
|
},
|
|
|
|
render() {
|
|
const results = [];
|
|
if (this.state.rows.reduce((acc, rows) => acc + rows.length, 0) === 0) {
|
|
results.push(
|
|
<div key="empty" className="empty">
|
|
<h4>{i18n.Messages.AUTOCOMPLETE_NO_RESULTS_HEADER}</h4>
|
|
<p>{i18n.Messages.AUTOCOMPLETE_NO_RESULTS_BODY}</p>
|
|
</div>
|
|
);
|
|
} else {
|
|
const {selectedSection, selectedRow} = this.state;
|
|
this.props.sections.forEach((section, i) => {
|
|
const rows = this.state.rows[i];
|
|
if (rows.length === 0) return;
|
|
results.push(<div key={i} className="section">{section}</div>);
|
|
rows.forEach((result, j) => {
|
|
results.push(
|
|
<div
|
|
key={`${i}-${j}`}
|
|
onClick={this.handleClick.bind(this, i, j)}
|
|
onMouseEnter={this.handleMouseEnter.bind(this, i, j)}
|
|
className={classNames('row', {selected: selectedSection === i && selectedRow === j})}>
|
|
<a>{this.props.onRenderResult(result, i)}</a>
|
|
</div>
|
|
);
|
|
});
|
|
});
|
|
}
|
|
|
|
return (
|
|
<div id="autocomplete-popout" className={this.props.className}>
|
|
<header>
|
|
<h3>{this.props.label}</h3>
|
|
<input
|
|
type="text"
|
|
placeholder={this.props.placeholder}
|
|
ref={INPUT_REF}
|
|
onChange={this.handleChange}
|
|
onKeyDown={this.handleKeyDown}
|
|
/>
|
|
</header>
|
|
<section>
|
|
<LazyScroller fade theme={Themes.LIGHT} elementHeight={44}>
|
|
{results}
|
|
</LazyScroller>
|
|
</section>
|
|
</div>
|
|
);
|
|
},
|
|
|
|
focus() {
|
|
if (this.isMounted()) {
|
|
this.refs[INPUT_REF].focus();
|
|
}
|
|
},
|
|
});
|
|
|
|
export default AutocompletePopout;
|
|
|
|
|
|
|
|
// WEBPACK FOOTER //
|
|
// ./discord_app/components/AutocompletePopout.js
|