Skip to content

Instantly share code, notes, and snippets.

@yury-n
Created February 11, 2017 17:26
Show Gist options
  • Save yury-n/ae7ca333cefe4c8f04dba061d2bc4b57 to your computer and use it in GitHub Desktop.
Save yury-n/ae7ca333cefe4c8f04dba061d2bc4b57 to your computer and use it in GitHub Desktop.
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