UNPKG

wix-style-react

Version:
264 lines (220 loc) 7.11 kB
import React from 'react'; import PropTypes from 'prop-types'; import InputWithOptions from '../InputWithOptions'; import SearchIcon from 'wix-ui-icons-common/Search'; import Input from '../Input'; import Box from '../Box'; import Loader from '../Loader'; import Text from '../Text'; import { dataHooks } from './constants'; /** AddressInput */ class AddressInput extends React.PureComponent { constructor(props) { super(props); this.state = { inputValue: props.initialValue || '', isDropdownOpen: false, }; } innerRef = React.createRef(); _onChange = event => { const { onChange } = this.props; this.setState({ inputValue: event.target.value, }); if (onChange) { onChange(event); } }; _onSelect = option => { const { onSelect } = this.props; this.setState({ inputValue: option.label, }); onSelect && onSelect(option); }; _onClear = () => { const { onClear } = this.props; this.setState( { inputValue: '', }, () => { onClear && onClear(); }, ); }; _getInputValue = () => { const { value: controlledValue } = this.props; const { inputValue } = this.state; const value = controlledValue !== undefined ? controlledValue : inputValue; return value; }; _getIsLoading = () => { const { status } = this.props; return status === 'loading'; }; _getStatus = () => { const { status } = this.props; const { isDropdownOpen } = this.state; const isLoading = this._getIsLoading(); /** If addresses are loading and dropdown is open, * displays loader in dropdown instead of in input. */ if (isLoading && isDropdownOpen) { return undefined; } return status; }; _setDropdownOpen = () => this.setState({ isDropdownOpen: true }); _setDropdownClosed = () => this.setState({ isDropdownOpen: false }); _renderLoadingOption = () => { return { id: '', disabled: true, value: () => ( <Box flex={1} align="center" padding="3px"> <Loader size="tiny" dataHook={dataHooks.loader} /> </Box> ), }; }; _renderNoResultsOption = () => { const { noResultsText } = this.props; const isString = typeof noResultsText === 'string'; const value = isString ? ( <Text light secondary dataHook={dataHooks.noResultsText}> {noResultsText} </Text> ) : ( noResultsText ); return { id: '', disabled: true, overrideOptionStyle: !isString, value, }; }; _renderOptions = () => { const { options, noResultsText } = this.props; const value = this._getInputValue(); const isLoading = this._getIsLoading(); const noResultsFound = !options || options.length === 0; if (isLoading) { return [this._renderLoadingOption()]; } /** show `noResultsText` option * if the input is not empty and there are no results */ if (value && noResultsFound && noResultsText) { return [this._renderNoResultsOption()]; } return options; }; render() { const { dataHook, className, size, clearButton, placeholder, disabled, onBlur, statusMessage, border, onManuallyInput, } = this.props; const value = this._getInputValue(); const status = this._getStatus(); return ( <InputWithOptions dataHook={dataHook} className={className} clearButton={clearButton} onChange={this._onChange} size={size} options={this._renderOptions()} onSelect={this._onSelect} onManuallyInput={onManuallyInput} value={value} disabled={disabled} border={border} /** <Input /> always shows clear button when `onClear` prop is passed, so we only pass handler when clearButton is `true` */ onClear={clearButton ? this._onClear : undefined} onBlur={onBlur} status={status} statusMessage={statusMessage} menuArrow={false} highlight prefix={ <Input.IconAffix> <SearchIcon /> </Input.IconAffix> } placeholder={placeholder} onOptionsShow={this._setDropdownOpen} onOptionsHide={this._setDropdownClosed} // disable browser autofill (including in chrome) autocomplete="off,chrome-off" ref={this.innerRef} /> ); } /** * Sets focus on the input element * @param {FocusOptions} options */ focus() { this.innerRef.current && this.innerRef.current.focus(); } } AddressInput.displayName = 'AddressInput'; AddressInput.propTypes = { /** Applies a data-hook HTML attribute that can be used in the tests */ dataHook: PropTypes.string, /** Specifies a CSS class name to be appended to the component’s root element */ className: PropTypes.string, /** Displays a clear button (X) on a non-empty input */ clearButton: PropTypes.bool, /** Sets the initial input value */ initialValue: PropTypes.string, /** Sets a value to display (controlled mode). */ value: PropTypes.string, /** Specifies whether input is disabled */ disabled: PropTypes.bool, /** Fetch predictions debounce in milliseconds */ debounceDuration: PropTypes.number, /** Defines a callback function which is called whenever a user selects a different option in the list */ onSelect: PropTypes.func, /** Defines a callback function which is called every time input value is changed */ onChange: PropTypes.func, /** Specify an array of options to render. When the option is an {optionProps}, the <AddressInput.Option/> component will be rendered on behalf of the user. */ options: PropTypes.array, /** Defines a handler for getting notified upon a clear event. When passed, it displays a clear button in the input. */ onClear: PropTypes.func, /** Defines a standard input onBlur callback */ onBlur: PropTypes.func, /** Defines a callback function which is called when user performs a submit action. * Submit-Action triggers are: "Enter", "Tab" */ onManuallyInput: PropTypes.func, /** Specify the status of a field. Mostly used for “loading” indication upon async request calls. */ status: PropTypes.oneOf(['loading', 'error', 'warning']), /** Defines the message to display on status icon hover. If not given or empty there will be no tooltip. */ statusMessage: PropTypes.node, /** Control the border style of an input */ border: PropTypes.oneOf(['standard', 'round', 'bottomLine']), /** Controls the size of the input */ size: PropTypes.oneOf(['small', 'medium', 'large']), /** Sets a placeholder message to display */ placeholder: PropTypes.string, /** Sets the message to show in a dropdown when no results are found */ noResultsText: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), }; AddressInput.defaultProps = { clearButton: true, debounceDuration: 200, border: 'round', size: 'medium', }; export default AddressInput;