From 376ce0db87e5e8e26a3d05b15261f8a1b5a39174 Mon Sep 17 00:00:00 2001 From: Andrew Watkins <watts@influxdb.com> Date: Mon, 26 Mar 2018 15:53:29 -0700 Subject: [PATCH] Introduce filtering to FuncButton --- ui/src/ifql/components/FuncsButton.tsx | 43 +++++++++---- .../{DropdownInput.js => DropdownInput.tsx} | 30 +++++----- ui/test/ifql/components/FuncsButton.test.tsx | 60 ++++++++++++++++++- 3 files changed, 105 insertions(+), 28 deletions(-) rename ui/src/shared/components/{DropdownInput.js => DropdownInput.tsx} (60%) diff --git a/ui/src/ifql/components/FuncsButton.tsx b/ui/src/ifql/components/FuncsButton.tsx index 08e6097948..87abce896e 100644 --- a/ui/src/ifql/components/FuncsButton.tsx +++ b/ui/src/ifql/components/FuncsButton.tsx @@ -1,29 +1,31 @@ -import React, {PureComponent} from 'react' +import React, {PureComponent, ChangeEvent, KeyboardEvent} from 'react' import FancyScrollbar from 'src/shared/components/FancyScrollbar' +import DropdownInput from 'src/shared/components/DropdownInput' import OnClickOutside from 'src/shared/components/OnClickOutside' interface State { isOpen: boolean + inputText: string } interface Props { funcs: string[] } -class FuncsButton extends PureComponent<Props, State> { +export class FuncsButton extends PureComponent<Props, State> { constructor(props) { super(props) this.state = { isOpen: false, + inputText: '', } } public render() { - const {isOpen} = this.state - const {funcs} = this.props + const {isOpen, inputText} = this.state return ( <div className={`dropdown dashboard-switcher ${isOpen ? 'open' : ''}`}> @@ -34,14 +36,17 @@ class FuncsButton extends PureComponent<Props, State> { <span className="icon plus" /> </button> <ul className="dropdown-menu funcs"> + <DropdownInput + buttonSize="btn-xs" + buttonColor="btn-default" + onFilterChange={this.handleInputChange} + onFilterKeyPress={this.handleKeyDown} + searchTerm={inputText} + /> <FancyScrollbar autoHide={false} autoHeight={true} maxHeight={240}> {isOpen && - funcs.map((func, i) => ( - <li - className="dropdown-item func" - data-test="func-item" - key={i} - > + this.availableFuncs.map((func, i) => ( + <li className="dropdown-item func" key={i}> <a>{func}</a> </li> ))} @@ -51,6 +56,24 @@ class FuncsButton extends PureComponent<Props, State> { ) } + private get availableFuncs(): string[] { + return this.props.funcs.filter(f => + f.toLowerCase().includes(this.state.inputText) + ) + } + + private handleInputChange = (e: ChangeEvent<HTMLInputElement>) => { + this.setState({inputText: e.target.value}) + } + + private handleKeyDown = (e: KeyboardEvent<HTMLInputElement>) => { + if (e.key !== 'Escape') { + return + } + + this.setState({inputText: '', isOpen: false}) + } + private handleClick = () => { this.setState({isOpen: !this.state.isOpen}) } diff --git a/ui/src/shared/components/DropdownInput.js b/ui/src/shared/components/DropdownInput.tsx similarity index 60% rename from ui/src/shared/components/DropdownInput.js rename to ui/src/shared/components/DropdownInput.tsx index 279642c4a3..84f92cfaaa 100644 --- a/ui/src/shared/components/DropdownInput.js +++ b/ui/src/shared/components/DropdownInput.tsx @@ -1,9 +1,21 @@ -import React from 'react' -import PropTypes from 'prop-types' +import React, {SFC, ChangeEvent, KeyboardEvent} from 'react' const disabledClass = disabled => (disabled ? ' disabled' : '') -const DropdownInput = ({ +type OnFilterChangeHandler = (e: ChangeEvent<HTMLInputElement>) => void +type OnFilterKeyPress = (e: KeyboardEvent<HTMLInputElement>) => void + +interface Props { + searchTerm: string + buttonSize: string + buttonColor: string + toggleStyle?: object + disabled?: boolean + onFilterChange: OnFilterChangeHandler + onFilterKeyPress: OnFilterKeyPress +} + +const DropdownInput: SFC<Props> = ({ searchTerm, buttonSize, buttonColor, @@ -33,15 +45,3 @@ const DropdownInput = ({ ) export default DropdownInput - -const {bool, func, shape, string} = PropTypes - -DropdownInput.propTypes = { - searchTerm: string, - buttonSize: string, - buttonColor: string, - toggleStyle: shape({}), - disabled: bool, - onFilterChange: func.isRequired, - onFilterKeyPress: func.isRequired, -} diff --git a/ui/test/ifql/components/FuncsButton.test.tsx b/ui/test/ifql/components/FuncsButton.test.tsx index 72a943c034..6882c84bd0 100644 --- a/ui/test/ifql/components/FuncsButton.test.tsx +++ b/ui/test/ifql/components/FuncsButton.test.tsx @@ -1,6 +1,7 @@ import React from 'react' import {shallow} from 'enzyme' -import FuncsButton from 'src/ifql/components/FuncsButton' +import {FuncsButton} from 'src/ifql/components/FuncsButton' +import DropdownInput from 'src/shared/components/DropdownInput' const setup = (override = {}) => { const props = { @@ -39,14 +40,67 @@ describe('IFQL.Components.FuncsButton', () => { it('displays the list of functions', () => { const {wrapper} = setup() - wrapper.simulate('click') + const dropdownButton = wrapper.find('button') + dropdownButton.simulate('click') - const list = wrapper.find({'data-test': 'func-item'}) + const list = wrapper.find('.func') expect(list.length).toBe(2) expect(list.first().text()).toBe('f1') expect(list.last().text()).toBe('f2') }) }) + + describe('filtering the list', () => { + it('displays the filtered funcs', () => { + const {wrapper} = setup() + + const dropdownButton = wrapper.find('button') + dropdownButton.simulate('click') + + let list = wrapper.find('.func') + + expect(list.length).toBe(2) + expect(list.first().text()).toBe('f1') + expect(list.last().text()).toBe('f2') + + const input = wrapper + .find(DropdownInput) + .dive() + .find('input') + + input.simulate('change', {target: {value: '2'}}) + wrapper.update() + + list = wrapper.find('.func') + + expect(list.length).toBe(1) + expect(list.first().text()).toBe('f2') + }) + }) + + describe('exiting the list', () => { + it('closes when ESC is pressed', () => { + const {wrapper} = setup() + + const dropdownButton = wrapper.find('button') + dropdownButton.simulate('click') + let list = wrapper.find('.func') + + expect(list.exists()).toBe(true) + + const input = wrapper + .find(DropdownInput) + .dive() + .find('input') + + input.simulate('keyDown', {key: 'Escape'}) + wrapper.update() + + list = wrapper.find('.func') + + expect(list.exists()).toBe(false) + }) + }) }) })