UNPKG

@plan-three/material-ui-autosuggest

Version:
288 lines (268 loc) 7.6 kB
import React from 'react' import PropTypes from 'prop-types' import ReactAutosuggest from 'react-autosuggest' import { withStyles } from 'material-ui/styles' import Fuse from 'fuse.js' import renderInput from './lib/render-input' import renderSuggestion from './lib/render-suggestion' import renderSuggestionsContainer from './lib/render-suggestions-container' import JssProvider from 'react-jss/lib/JssProvider' import { create } from 'jss' import { createGenerateClassName, jssPreset } from 'material-ui/styles' const styles = theme => ({ container: { flexGrow: 1, position: 'relative', height: 200 }, containerFitContent: { width: 'fit-content' }, suggestionsContainerOpen: { position: 'absolute', marginTop: theme.spacing.unit, marginBottom: theme.spacing.unit * 3, left: 0, right: 0, zIndex: 9 }, suggestion: { display: 'block' }, suggestionsList: { margin: 0, padding: 0, listStyleType: 'none' } }) class Autosuggest extends React.Component { constructor(props) { super(props) this.state = { value: props.value, suggestions: [] } this.fuzzySearcher = new Fuse( props.suggestions, props.fuzzySearchOpts ) this.handleSuggestionsFetchRequested = this.handleSuggestionsFetchRequested.bind(this) this.handleSuggestionsClearRequested = this.handleSuggestionsClearRequested.bind(this) this.handleChange = this.handleChange.bind(this) this.handleBlur = this.handleBlur.bind(this) this.getSuggestions = this.getSuggestions.bind(this) this.getSuggestionValue = this.getSuggestionValue.bind(this) this.renderSuggestion = props.renderSuggestion && typeof props.renderSuggestion === 'function' ? props.renderSuggestion : renderSuggestion.bind(this) this.renderInput = props.renderInput && typeof props.renderInput === 'function' ? props.renderInput : renderInput.bind(this) this.renderSuggestionsContainer = props.renderSuggestionsContainer && typeof props.renderSuggestionsContainer === 'function' ? props.renderSuggestionsContainer : renderSuggestionsContainer.bind(this) this.handleSuggestionsChange = this.handleSuggestionsChange.bind(this) } get suggestionLimit() { const { suggestionLimit, suggestions } = this.props return suggestionLimit < suggestions.length ? suggestionLimit : suggestions.length } getSuggestions(value) { return this.fuzzySearcher .search(value) .slice(0, this.suggestionLimit) } getSuggestionValue(suggestion) { return suggestion.item[this.props.labelKey] } handleBlur() { const { labelKey } = this.props let value = this.state.value if (this.props.selectClosestMatch) { const suggestion = this.state.suggestions[0] if (suggestion) { const match = suggestion.matches.find(item => (item.key === labelKey)) value = match && match.hasOwnProperty('value') && suggestion.hasOwnProperty('item') && suggestion.item.hasOwnProperty(labelKey) ? match.value : value } this.setState({ value }) this.props.onChange(value) } if (typeof this.props.onBlur === 'function') { this.props.onBlur(value) } } handleSuggestionsChange() { const { onSuggestionsChange } = this.props if (typeof onSuggestionsChange === 'function') { onSuggestionsChange(this.state.suggestions.slice(0, this.suggestionLimit)) } } handleSuggestionsFetchRequested({ value }) { this.setState({ suggestions: this.getSuggestions(value) }, this.handleSuggestionsChange()) } handleSuggestionsClearRequested() { this.setState({ suggestions: [] }, this.handleSuggestionsChange()) } handleChange(event, { newValue }) { this.setState({ value: newValue }) this.props.onChange(newValue) } render() { const { classes, fullWidth, productionPrefix } = this.props if (!fullWidth) { classes.container += ` ${classes.containerFitContent}` } const generateClassName = createGenerateClassName({ productionPrefix: productionPrefix }) const jss = create(jssPreset()) return ( <JssProvider jss={jss} generateClassName={generateClassName}> <ReactAutosuggest theme={{ container: classes.container, suggestionsContainerOpen: classes.suggestionsContainerOpen, suggestionsList: classes.suggestionsList, suggestion: classes.suggestion }} renderInputComponent={this.renderInput} suggestions={this.state.suggestions} onSuggestionsFetchRequested={ this.handleSuggestionsFetchRequested } onSuggestionsClearRequested={ this.handleSuggestionsClearRequested } renderSuggestionsContainer={this.renderSuggestionsContainer} getSuggestionValue={this.getSuggestionValue} renderSuggestion={this.renderSuggestion} ref='foo' inputProps={{ classes, value: this.state.value, onChange: this.handleChange, onBlur: this.handleBlur, label: this.props.label, inputLabelProps: this.props.inputLabelProps, fullWidth: this.props.fullWidth, error: this.props.error, helperText: this.props.helperText, ...this.props.inputProps }} /> </JssProvider> ) } } Autosuggest.defaultProps = { selectClosestMatch: false, suggestionLimit: 5, error: false, value: '', labelKey: 'label', renderSuggestionProps: { highlight: true, renderSecondaryMatches: true }, fuzzySearchOpts: { shouldSort: true, includeMatches: true, findAllMatches: false, threshold: 0.6, location: 0, distance: 100, maxPatternLength: 32, minMatchCharLength: 1, keys: [ 'label' ] }, fullWidth: true, productionPrefix: 'mui-autosuggest' } Autosuggest.propTypes = { /** * A custom function for rendering the input component */ renderInput: PropTypes.func, /** * A custom function for rendering an individual suggestion element */ renderSuggestion: PropTypes.func, /** * A custom function for rendering the suggestion containing element */ renderSuggestionsContainer: PropTypes.func, /** * The array of suggestions */ suggestions: PropTypes.array.isRequired, labelKey: PropTypes.string.isRequired, classes: PropTypes.object.isRequired, /** * The function to call when the value is changed */ onChange: PropTypes.func.isRequired, /** * The function to call when the input element blurs */ onBlur: PropTypes.func, /** * Addition inputProps for the input component */ inputProps: PropTypes.object, /** * The value of the input */ value: PropTypes.string, /** * Additional props for the inputLabel */ inputLabelProps: PropTypes.object, /** * Whether or not the input should be rendered at full width */ fullWidth: PropTypes.bool, /** * Whether or not the input should have error stylings */ error: PropTypes.bool, /** * The helper text of the input element */ helperText: PropTypes.string, /** * Select the closest match onBlur */ selectClosestMatch: PropTypes.bool, /** * A function to call when suggestions are changed */ onSuggestionsChange: PropTypes.func, /** * @see http://fusejs.io/#live-demo */ fuzzySearchOpts: PropTypes.object.isRequired, /** * The number of suggestions to render */ suggestionLimit: PropTypes.number, /** * The label for the rendered component */ label: PropTypes.string, /** * Props used by the `renderSuggestion` function */ renderSuggestionProps: PropTypes.object, /** * The prefix for generated JSS classNames */ productionPrefix: PropTypes.string.isRequired } export default withStyles(styles, { withTheme: true })(Autosuggest) exports.defaultProps = Autosuggest.defaultProps exports.propTypes = Autosuggest.propTypes