Created
February 11, 2017 17:26
-
-
Save yury-n/ae7ca333cefe4c8f04dba061d2bc4b57 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react' | |
import capitalize from 'lodash/capitalize' | |
import classNames from 'classnames' | |
import Input from 'react-toolbox/lib/input' | |
import Modifier from 'components/Form/Modifier' | |
import Card from 'react-toolbox/lib/card' | |
import { List, ListItem, ListSubHeader, ListDivider } from 'react-toolbox/lib/list' | |
import './styles.scss' | |
const InputM = Modifier(Input, { noPadding: true }) | |
class Autocomplete extends React.Component { | |
constructor(props) { | |
super(props) | |
this.state = { | |
isActive: true, | |
suggestionCursor: null, | |
suggestions: this.getSuggestions(props), | |
} | |
} | |
componentWillReceiveProps(nextProps) { | |
this.setState({ | |
suggestions: this.getSuggestions(nextProps), | |
suggestionCursor: null, | |
}) | |
} | |
onInputFocus = () => { | |
this.setState({ isActive: true }) | |
if (this.props.onFocus) { | |
this.props.onFocus() | |
} | |
} | |
onInputBlur = () => { | |
this.setState({ isActive: false }) | |
if (this.props.onBlur) { | |
this.props.onBlur() | |
} | |
} | |
onInputKeyDown = (e) => { | |
if ([40, 38].includes(e.which)) { | |
e.preventDefault() | |
const suggestionsCount = this.getSuggestionsCount() | |
let suggestionCursor = this.state.suggestionCursor | |
if (e.which === 40) { | |
if (this.state.suggestionCursor === null || this.state.suggestionCursor === suggestionsCount - 1) { | |
suggestionCursor = 0 | |
} else { | |
suggestionCursor++ | |
} | |
} else if (e.which === 38) { | |
if (this.state.suggestionCursor === null || this.state.suggestionCursor === 0) { | |
suggestionCursor = suggestionsCount - 1 | |
} else { | |
suggestionCursor-- | |
} | |
} | |
this.setState({ suggestionCursor }) | |
} else if (e.which === 13) { | |
this.props.onChange(this.getSuggestionByGlobalIndex(this.state.suggestionCursor)) | |
} | |
} | |
onInputChange = (value) => { | |
this.props.onChange({ value }) | |
} | |
onSuggestionMouseDown = (e, groupKey, suggestionIndex) => { | |
e.stopPropagation() | |
e.preventDefault() | |
const props = this.props | |
props.onChange(props.source[groupKey][suggestionIndex]) | |
} | |
onSuggestionMouseMove = (index) => { | |
this.setState({ suggestionCursor: null }) | |
} | |
getSuggestions = (props) => { // eslint-disable-line class-methods-use-this | |
const suggestions = props.source | |
Object.keys(suggestions).forEach(key => { | |
suggestions[key] = suggestions[key].filter( | |
suggestion => !props.value | |
|| (suggestion.value.toLowerCase().startsWith(props.value.toLowerCase()) | |
&& suggestion.value.toLowerCase() !== props.value.toLowerCase()), | |
) | |
if (!suggestions[key].length) { | |
delete suggestions[key] | |
} | |
}) | |
return suggestions | |
} | |
getSuggestionsCount = () => { | |
let counter = 0 | |
Object.keys(this.state.suggestions).forEach(groupKey => { | |
counter += this.state.suggestions[groupKey].length | |
}) | |
return counter | |
} | |
getSuggestionByGlobalIndex = (requestedIndex) => { | |
let suggestionGlobalIndex = 0 // across all groups | |
let requestedSuggestion | |
Object.keys(this.state.suggestions).forEach(groupKey => { | |
this.state.suggestions[groupKey].forEach(suggestion => { | |
if (suggestionGlobalIndex === requestedIndex) { | |
requestedSuggestion = suggestion | |
} | |
suggestionGlobalIndex++ | |
}) | |
}) | |
return requestedSuggestion | |
} | |
render() { | |
const props = this.props | |
const state = this.state | |
const suggestionList = [] | |
let suggestionGlobalIndex = 0 // across all groups | |
Object.keys(state.suggestions).forEach(groupKey => { | |
suggestionList.push(<ListSubHeader key={groupKey} caption={capitalize(groupKey)} />) | |
state.suggestions[groupKey].forEach((suggestion, suggestionIndex) => { | |
suggestionList.push( | |
<ListItem | |
key={`${groupKey}-${suggestionIndex}`} | |
caption={suggestion.value} | |
legend={suggestion.legend} | |
onMouseEnter={(e) => this.onSuggestionMouseOver(suggestionGlobalIndex)} | |
onMouseDown={(e) => this.onSuggestionMouseDown(e, groupKey, suggestionIndex)} | |
className={classNames( | |
'lq-autocomplete__list-item', | |
suggestionGlobalIndex === state.suggestionCursor && 'lq-autocomplete__list-item--highlighted', | |
)} | |
/>, | |
) | |
suggestionGlobalIndex++ | |
}) | |
}) | |
return ( | |
<div | |
className={classNames( | |
'lq-autocomplete', | |
!!suggestionList.length && 'lq-autocomplete--with-arrow', | |
)} | |
> | |
<InputM | |
fullWidth | |
className="lq-autocomplete__input" | |
value={props.value} | |
label={props.label} | |
placeholder={props.placeholder} | |
onChange={this.onInputChange} | |
onFocus={this.onInputFocus} | |
onBlur={this.onInputBlur} | |
onKeyDown={this.onInputKeyDown} | |
/> | |
{state.isActive && !!suggestionList.length && | |
<Card className="lq-autocomplete__card" onMouseMove={this.onSuggestionMouseMove}> | |
<List selectable ripple> | |
{suggestionList} | |
</List> | |
</Card> | |
} | |
</div> | |
) | |
} | |
} | |
export default Autocomplete |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment