UNPKG

@deviniti/jql-autocomplete-input

Version:

Jira JQL autocomplete component based on the Atlaskit

302 lines (265 loc) 9.99 kB
import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; import FieldBase, { Label } from '@atlaskit/field-base'; import debounce from 'lodash.debounce'; import { akColorG400, akColorR400 } from '@atlaskit/util-shared-styles'; import Input from '@atlaskit/input'; import DropList, { Group, Item } from '@atlaskit/droplist'; import CheckCircleIcon from '@atlaskit/icon/glyph/check-circle'; import CrossCircleIcon from '@atlaskit/icon/glyph/cross-circle'; import JQLAutocomplete from '@deviniti/jql-autocomplete'; import { FieldBaseWrapper } from './styled/styled'; import jQuery from 'jquery'; function JqlInvalidIcon() { return ( <CrossCircleIcon size="medium" primaryColor={akColorR400} label="JQL valid"/> ); } function JqlValidIcon() { return ( <CheckCircleIcon size="medium" primaryColor={akColorG400} label="JQL invalid"/> ); } class JQLAutocompleteInput extends PureComponent { state = { jql: this.props.initialValue, isOpen: false, suggestions: [] }; componentDidMount() { this.constructorData = { //API requires jquery... TODO change jql-autocomplete API input: jQuery('#' + this.props.inputId), render: this.setSuggestions, getSuggestions: debounce( (fieldName, onSuccess, fieldValue = '') => { this.props.getSuggestionsRequest(fieldName, fieldValue) .then((response) => { onSuccess(response.data.results); }); } , 400) }; this.jql = new JQLAutocomplete(this.constructorData); this.props.getAutocompleteDataRequest() .then((response) => { this.jql.passAutocompleteData(response.data); }); this.validateWhenComponentDidMount(this.props.initialValue); } setSuggestions = (suggestions) => { this.setState({ suggestions: [...suggestions] }); }; render() { const { isOpen, suggestions } = this.state; const { maxHeight, shouldFlip } = this.props; const { jqlError } = this.state; const Icon = !jqlError ? <JqlValidIcon/> : <JqlInvalidIcon/>; return ( <div style={{ width: '100%', cursor: 'default' }} onKeyDown={this.handleKeyboardInteractions} ref={(element) => { this.containerNode = element; }} > <Label label={this.props.label}/> <DropList isKeyboardInteractionDisabled shouldFitContainer isOpen={isOpen && suggestions.length > 0} onOpenChange={this.onOpenChange} maxHeight={maxHeight} shouldFlip={shouldFlip} trigger={ <FieldBaseWrapper> <FieldBase isPaddingDisabled={true} isFitContainerWidthEnabled invalidMessage={jqlError} > {Icon} <Input isEditing defaultValue={this.props.initialValue} onInput={this.handleInputChange} autocomplete={'off'} id={this.props.inputId} style={{ paddingLeft: 8, cursor: 'auto', marginTop: 8 }} ref={(input) => { this.textInput = input; }} /> </FieldBase> </FieldBaseWrapper> } > <Group> {this.renderItems()} </Group> </DropList> </div> ); } renderItems = () => { return this.state.suggestions.map((item, index) => { const createMarkup = function () { return { __html: item.text }; }; const ItemText = function () { return <span dangerouslySetInnerHTML={createMarkup()} />; }; return ( <Item onActivate={() => this.handleItemSelect(item)} isFocused={index === this.state.focusedItemIndex} key={item.text} type="option" > <ItemText/> </Item> ); }); }; handleKeyboardInteractions = (event) => { const isSelectOpen = this.state.isOpen; switch (event.key) { case 'ArrowDown': event.preventDefault(); if (!isSelectOpen) { this.onOpenChange({ event, isOpen: true }); } this.focusNextItem(); break; case 'ArrowUp': event.preventDefault(); if (isSelectOpen) { this.focusPreviousItem(); } break; case 'Enter': if (isSelectOpen) { event.preventDefault(); if (this.state.focusedItemIndex !== undefined) { this.handleItemSelect( this.state.suggestions[this.state.focusedItemIndex] ); } else { this.onOpenChange({ event, isOpen: false }); } } break; case 'Tab': this.onOpenChange({ event, isOpen: false }); break; default: return false; } }; onOpenChange = (attrs) => { this.setState({ focusedItemIndex: undefined, isOpen: attrs.isOpen }); }; focusNextItem = () => { const { focusedItemIndex, suggestions } = this.state; const nextItemIndex = this.getNextFocusable(focusedItemIndex, suggestions.length); this.setState({ focusedItemIndex: nextItemIndex }); this.scrollToFocused(nextItemIndex); }; focusPreviousItem = () => { const { focusedItemIndex, suggestions } = this.state; const nextItemIndex = this.getPrevFocusable(focusedItemIndex, suggestions.length); this.setState({ focusedItemIndex: nextItemIndex }); this.scrollToFocused(nextItemIndex); }; getNextFocusable = (indexItem, length) => { let currentItem = indexItem; if (currentItem === undefined) { currentItem = 0; } else if (currentItem < length) { currentItem += 1; } else { currentItem = 0; } return currentItem; }; handleItemSelect = (item) => { this.setState({ focusedItemIndex: undefined, isOpen: false }); if (item) { item.onClick(); } }; getPrevFocusable = (indexItem, length) => { let currentItem = indexItem; if (currentItem > 0) { currentItem -= 1; } else { currentItem = length; } return currentItem; }; scrollToFocused = (index) => { const scrollable = this.containerNode.querySelector('[data-role="droplistContent"]'); let item; if (scrollable && index !== undefined) { item = scrollable.querySelectorAll('[data-role="droplistItem"]')[index]; } if (item && scrollable) { scrollable.scrollTop = (item.offsetTop - scrollable.clientHeight) + item.clientHeight; } }; handleInputChange = (event) => { const { value } = event.target; if (value.trim() !== this.props.inputValue) { this.validateInput(event); this.onOpenChange({ event, isOpen: true }); this.props.onChange(event); } }; validateRequest = (value) => { if (value) { this.jqlTimer = setTimeout(() => { this.props.validationRequest(value) .then(() => { this.setState({ jqlError: null }); }) .catch((error) => { this.setState({ jqlError: JSON.stringify(error.response.data.errorMessages) }); }); }, 500); } else { this.setState({ jqlError: null }); } }; validateInput = (event) => { if (this.jqlTimer) { clearTimeout(this.jqlTimer); } const jql = event.currentTarget.value; this.validateRequest(jql); }; validateWhenComponentDidMount = (value) => { this.validateRequest(value); }; } JQLAutocomplete.propTypes = { getSuggestionsRequest: PropTypes.func.isRequired, getAutocompleteDataRequest: PropTypes.func.isRequired, /** Set the max height of the dropdown list in pixels. */ maxHeight: PropTypes.number, initialValue: PropTypes.string, inputId: PropTypes.string.isRequired, inputValue: PropTypes.string, onChange: PropTypes.func.isRequired, label: PropTypes.string, shouldFlip: PropTypes.bool, validationRequest: PropTypes.func.isRequired, }; JQLAutocomplete.defaultProps = { maxHeight: 400, shouldFlip: false, initialValue: '', inputValue: '', label: '' }; export default JQLAutocompleteInput;