wix-style-react
Version:
264 lines (220 loc) • 7.11 kB
JavaScript
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;