UNPKG

react-tag-box

Version:

A tagging component in React. Select, create, and delete tags.

177 lines (151 loc) 4 kB
import React, { Component } from 'react' import PropTypes from 'prop-types' import classNames from 'classnames' import TagProp from './utils' export default class extends Component { static propTypes = { input: PropTypes.string, selected: PropTypes.arrayOf(TagProp), tags: PropTypes.arrayOf(TagProp).isRequired, select: PropTypes.func.isRequired, create: PropTypes.func.isRequired, considering: TagProp, consider: PropTypes.func.isRequired, renderNewOption: PropTypes.func.isRequired, loadingText: PropTypes.string, selectedText: PropTypes.string, loading: PropTypes.bool, search: PropTypes.func.isRequired, exactMatch: PropTypes.func.isRequired } static defaultProps = { selected: [] } componentWillReceiveProps(nextProps) { const { tags, input, consider, selected } = nextProps if (this.props.input && !input) { consider(null) } const noChange = (input === this.props.input) && (tags === this.props.tags) if (!input || noChange) { return } const matches = tags.filter(t => this.props.search(t, input) && !selected.includes(t) ) if (matches.length) { consider(matches[0]) } else { consider(null) } } matchingOptions() { const { tags, input, selected, search } = this.props const matches = tags.filter(t => search(t, input) && !selected.map(s => s.value).includes(t.value) ) const values = matches.map(t => t.value) return { matches, values } } considerNext() { const { consider, considering } = this.props const { matches, values } = this.matchingOptions() if (!matches.length) { return } if (!considering) { consider(matches[0]) } else { const nextIndex = Math.min(matches.length - 1, values.indexOf(considering.value) + 1) consider(matches[nextIndex]) } } considerPrevious() { const { consider, considering } = this.props const { matches, values } = this.matchingOptions() if (!matches.length) { return } const currentIndex = values.indexOf(considering.value) if (currentIndex === 0) { consider(null) } else { consider(matches[currentIndex - 1]) } } render() { const { input, select, create, considering, consider, renderNewOption, loadingText, selectedText, loading, selected, exactMatch } = this.props if (loading) { return ( <ul className="autocomplete"> <li> <span className="option-text"> {loadingText} </span> </li> </ul> ) } if (!input) { return false } const { matches } = this.matchingOptions() const foundExactMatch = matches.find(t => exactMatch(t, input)) const alreadySelected = selected.find(t => exactMatch(t, input)) const addNewOption = !foundExactMatch && ( <li className={classNames('add-new', { considering: !considering })} onClick={create} onMouseOver={() => consider(null)} > <span className="option-text"> {renderNewOption(input)} </span> </li> ) const selectedNotice = alreadySelected && ( <li> <span className="option-text"> {selectedText} </span> </li> ) const content = alreadySelected ? selectedNotice : addNewOption const matching = matches.map(t => { const className = classNames({ considering: considering === t }) return ( <li key={t.value} className={className} onClick={() => select(t)} onMouseOver={() => consider(t)} > <span className="option-text"> {t.label} </span> </li> ) }) return ( <ul className="autocomplete"> {content} {matching} </ul> ) } }