From 8f0fbf87a6243af6fd0f2f1d52532fd5803e0538 Mon Sep 17 00:00:00 2001 From: Patrick Wees Date: Sat, 10 Oct 2020 00:00:57 -0700 Subject: [PATCH] Add `showDropdownAtStart` prop Allow the searchbox to act as a `filter` dropdown with initial values. --- README.md | 1 + src/index.js | 50 +++++++++++++++++++++++++++++++++++++++----- src/stories/index.js | 9 ++++++++ src/tests/index.js | 20 ++++++++++++++++++ 4 files changed, 75 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index c05a53f..88301ca 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,7 @@ keys|all[Array]|List of properties that will be searched. This also supports nes list|null|Array of properties to be filtered. placeholder|'Search'|Placeholder of the searchbox resultsTemplate| Func | Template of the dropdown divs +showDropdownAtStart|false|Allow the searchbox to act as a `filter` dropdown with initial values. Yields all results when the search value is blank. shouldSort| true | Whether to sort the result list, by score. sortFn|`Array.prototype.sort`|The function that is used for sorting the result list. threshold|0.6|At what point does the match algorithm give up. A threshold of `0.0` requires a perfect match (of both letters and location), a threshold of `1.0` would match anything. diff --git a/src/index.js b/src/index.js index 2e25cc7..20a615f 100644 --- a/src/index.js +++ b/src/index.js @@ -56,7 +56,7 @@ function defaultResultsTemplate(props, state, styl, clickHandler) { return state.results.map((val, i) => { const style = state.selectedIndex === i ? {...styl.selectedResultStyle, ...props.selectedListItemStyle} : {...styl.resultsStyle, ...props.listItemStyle }; return ( -
clickHandler(i)}> +
clickHandler(i)}> {val[props.keyForDisplayName]}
); @@ -79,6 +79,7 @@ export default class FuzzySearch extends Component { location: PropTypes.number, placeholder: PropTypes.string, resultsTemplate: PropTypes.func, + showDropdownAtStart: PropTypes.bool, shouldSort: PropTypes.bool, sortFn: PropTypes.func, threshold: PropTypes.number, @@ -103,6 +104,7 @@ export default class FuzzySearch extends Component { width: 430, placeholder: 'Search', resultsTemplate: defaultResultsTemplate, + showDropdownAtStart: false, shouldSort: true, sortFn(a, b) { return a.score - b.score; @@ -122,6 +124,7 @@ export default class FuzzySearch extends Component { constructor(props) { super(props); this.state = { + isOpen: !this.props.showDropdownAtStart, results: [], selectedIndex: 0, selectedValue: {}, @@ -131,6 +134,9 @@ export default class FuzzySearch extends Component { this.handleKeyDown = this.handleKeyDown.bind(this); this.handleMouseClick = this.handleMouseClick.bind(this); this.fuse = new Fuse(props.list, this.getOptions()); + this.setDropdownRef = ref => { + this.dropdownRef = ref; + }; } getOptions() { @@ -168,8 +174,12 @@ export default class FuzzySearch extends Component { } handleChange(e) { + const shouldDisplayAllListItems = this.props.showDropdownAtStart && !e.target.value; + this.setState({ - results: this.fuse.search(e.target.value).slice(0, this.props.maxResults - 1), + results: shouldDisplayAllListItems + ? this.props.list + : this.fuse.search(e.target.value).slice(0, this.props.maxResults - 1), value: e.target.value, }); } @@ -219,7 +229,15 @@ export default class FuzzySearch extends Component { } render() { - const { autoFocus, className, list, placeholder, resultsTemplate, width } = this.props; + const { + autoFocus, + className, + list, + placeholder, + resultsTemplate, + showDropdownAtStart, + width, + } = this.props; // Update the search space list if (this.fuse.setCollection && list) { @@ -229,7 +247,21 @@ export default class FuzzySearch extends Component { const mainClass = classNames('react-fuzzy-search', className); return ( -
+
{ + if (this.dropdownRef.contains(e.relatedTarget)) return; + + if (showDropdownAtStart) { + this.setState({ + isOpen: false, + }); + } + }} + onKeyDown={this.handleKeyDown} + >
{ + if (showDropdownAtStart) { + this.setState({ + isOpen: true, + results: this.state.value ? this.state.results : list, + }); + } + }} />
- {this.state.results && this.state.results.length > 0 && ( + {this.state.isOpen && this.state.results && this.state.results.length > 0 && (
{resultsTemplate(this.props, this.state, styles, this.handleMouseClick)}
diff --git a/src/stories/index.js b/src/stories/index.js index 54096a5..75c4172 100644 --- a/src/stories/index.js +++ b/src/stories/index.js @@ -119,6 +119,15 @@ storiesOf('SearchBox', module) /> ); }) + .add('Show Dropdown at Start', () => ( + + )) .add('Passthrough Options', () => { const template = (props, state, styles, click) => state.results.map(({ item, matches }, i) => { diff --git a/src/tests/index.js b/src/tests/index.js index f27a300..8eff304 100644 --- a/src/tests/index.js +++ b/src/tests/index.js @@ -170,4 +170,24 @@ describe('', () => { expect(wrapper.find('input')).to.exist; }) + + it('should display all options onFocus when showDropdownAtStart passed in', () => { + const onChange = sinon.spy(); + const wrapper = mount( + , + ); + + const input = wrapper.find('input'); + + input.simulate('focus'); + + expect(wrapper.state('results').length).to.not.equal(0); + }); });