2019-06-19 14:40:52 +00:00
|
|
|
import { InputProps } from "@material-ui/core/Input";
|
2019-10-30 14:34:24 +00:00
|
|
|
import { makeStyles } from "@material-ui/core/styles";
|
2019-06-19 14:40:52 +00:00
|
|
|
import TextField from "@material-ui/core/TextField";
|
|
|
|
import Downshift from "downshift";
|
2019-10-16 15:51:53 +00:00
|
|
|
import { filter } from "fuzzaldrin";
|
2019-08-09 10:26:22 +00:00
|
|
|
import React from "react";
|
2019-06-19 14:40:52 +00:00
|
|
|
|
2019-08-09 11:14:35 +00:00
|
|
|
import useStateFromProps from "@saleor/hooks/useStateFromProps";
|
2019-10-14 14:17:03 +00:00
|
|
|
import { FetchMoreProps } from "@saleor/types";
|
2019-06-19 14:40:52 +00:00
|
|
|
import ArrowDropdownIcon from "../../icons/ArrowDropdown";
|
|
|
|
import Debounce, { DebounceProps } from "../Debounce";
|
2019-12-02 10:49:14 +00:00
|
|
|
import SingleAutocompleteSelectFieldContent, {
|
|
|
|
SingleAutocompleteChoiceType
|
|
|
|
} from "./SingleAutocompleteSelectFieldContent";
|
2019-06-19 14:40:52 +00:00
|
|
|
|
2019-12-03 15:28:40 +00:00
|
|
|
const useStyles = makeStyles(
|
|
|
|
{
|
|
|
|
container: {
|
|
|
|
flexGrow: 1,
|
|
|
|
position: "relative"
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{ name: "SingleAutocompleteSelectField" }
|
|
|
|
);
|
2019-06-19 14:40:52 +00:00
|
|
|
|
2019-10-14 14:17:03 +00:00
|
|
|
export interface SingleAutocompleteSelectFieldProps
|
|
|
|
extends Partial<FetchMoreProps> {
|
2019-06-19 14:40:52 +00:00
|
|
|
error?: boolean;
|
|
|
|
name: string;
|
2019-08-09 11:14:35 +00:00
|
|
|
displayValue: string;
|
|
|
|
emptyOption?: boolean;
|
|
|
|
choices: SingleAutocompleteChoiceType[];
|
2019-10-14 14:17:03 +00:00
|
|
|
value: string;
|
2019-06-19 14:40:52 +00:00
|
|
|
disabled?: boolean;
|
|
|
|
placeholder?: string;
|
2019-08-09 11:14:35 +00:00
|
|
|
allowCustomValues?: boolean;
|
2019-06-19 14:40:52 +00:00
|
|
|
helperText?: string;
|
|
|
|
label?: string;
|
|
|
|
InputProps?: InputProps;
|
2019-08-09 11:14:35 +00:00
|
|
|
fetchChoices?: (value: string) => void;
|
|
|
|
onChange: (event: React.ChangeEvent<any>) => void;
|
2019-06-19 14:40:52 +00:00
|
|
|
}
|
|
|
|
|
2020-01-15 15:36:45 +00:00
|
|
|
const DebounceAutocomplete: React.ComponentType<DebounceProps<
|
|
|
|
string
|
|
|
|
>> = Debounce;
|
2019-06-19 14:40:52 +00:00
|
|
|
|
2020-01-15 15:36:45 +00:00
|
|
|
const SingleAutocompleteSelectFieldComponent: React.FC<SingleAutocompleteSelectFieldProps> = props => {
|
2019-10-30 14:34:24 +00:00
|
|
|
const {
|
2019-08-09 11:14:35 +00:00
|
|
|
allowCustomValues,
|
2020-01-15 15:36:45 +00:00
|
|
|
choices,
|
2019-06-19 14:40:52 +00:00
|
|
|
disabled,
|
2019-08-09 11:14:35 +00:00
|
|
|
displayValue,
|
|
|
|
emptyOption,
|
2019-06-19 14:40:52 +00:00
|
|
|
error,
|
2019-10-14 14:17:03 +00:00
|
|
|
hasMore,
|
2019-06-19 14:40:52 +00:00
|
|
|
helperText,
|
|
|
|
label,
|
|
|
|
loading,
|
|
|
|
name,
|
|
|
|
placeholder,
|
|
|
|
value,
|
|
|
|
InputProps,
|
|
|
|
fetchChoices,
|
2019-08-27 13:29:00 +00:00
|
|
|
onChange,
|
2019-10-14 14:17:03 +00:00
|
|
|
onFetchMore,
|
2019-10-30 14:34:24 +00:00
|
|
|
...rest
|
|
|
|
} = props;
|
|
|
|
const classes = useStyles(props);
|
2019-10-14 11:57:08 +00:00
|
|
|
|
2019-10-30 14:34:24 +00:00
|
|
|
const [prevDisplayValue] = useStateFromProps(displayValue);
|
2019-06-19 14:40:52 +00:00
|
|
|
|
2019-10-30 14:34:24 +00:00
|
|
|
const handleChange = item =>
|
|
|
|
onChange({
|
|
|
|
target: {
|
|
|
|
name,
|
|
|
|
value: item
|
|
|
|
}
|
|
|
|
} as any);
|
2019-08-09 11:14:35 +00:00
|
|
|
|
2019-10-30 14:34:24 +00:00
|
|
|
return (
|
|
|
|
<DebounceAutocomplete debounceFn={fetchChoices}>
|
|
|
|
{debounceFn => (
|
|
|
|
<Downshift
|
|
|
|
defaultInputValue={displayValue}
|
|
|
|
itemToString={() => displayValue}
|
|
|
|
onInputValueChange={value => debounceFn(value)}
|
|
|
|
onSelect={handleChange}
|
|
|
|
selectedItem={value}
|
|
|
|
>
|
|
|
|
{({
|
|
|
|
getInputProps,
|
|
|
|
getItemProps,
|
|
|
|
isOpen,
|
|
|
|
inputValue,
|
|
|
|
selectedItem,
|
|
|
|
toggleMenu,
|
|
|
|
closeMenu,
|
|
|
|
highlightedIndex,
|
|
|
|
reset
|
|
|
|
}) => {
|
|
|
|
const isCustomValueSelected =
|
|
|
|
choices && selectedItem
|
|
|
|
? choices.filter(c => c.value === selectedItem).length === 0
|
|
|
|
: false;
|
|
|
|
const hasInputValueChanged = prevDisplayValue !== displayValue;
|
2019-08-09 11:14:35 +00:00
|
|
|
|
2019-10-30 14:34:24 +00:00
|
|
|
if (hasInputValueChanged) {
|
|
|
|
reset({ inputValue: displayValue });
|
|
|
|
}
|
2019-10-14 11:57:08 +00:00
|
|
|
|
2019-10-30 14:34:24 +00:00
|
|
|
const displayCustomValue =
|
|
|
|
inputValue &&
|
|
|
|
inputValue.length > 0 &&
|
|
|
|
allowCustomValues &&
|
|
|
|
!choices.find(
|
|
|
|
choice =>
|
|
|
|
choice.label.toLowerCase() === inputValue.toLowerCase()
|
2019-06-19 14:40:52 +00:00
|
|
|
);
|
2019-10-30 14:34:24 +00:00
|
|
|
|
|
|
|
return (
|
|
|
|
<div className={classes.container} {...rest}>
|
|
|
|
<TextField
|
|
|
|
InputProps={{
|
|
|
|
...InputProps,
|
|
|
|
...getInputProps({
|
|
|
|
placeholder
|
|
|
|
}),
|
|
|
|
endAdornment: (
|
|
|
|
<div>
|
|
|
|
<ArrowDropdownIcon />
|
|
|
|
</div>
|
|
|
|
),
|
|
|
|
error,
|
|
|
|
id: undefined,
|
|
|
|
onBlur: closeMenu,
|
|
|
|
onClick: toggleMenu
|
|
|
|
}}
|
|
|
|
error={error}
|
|
|
|
disabled={disabled}
|
|
|
|
helperText={helperText}
|
|
|
|
label={label}
|
|
|
|
fullWidth={true}
|
|
|
|
/>
|
|
|
|
{isOpen && (!!inputValue || !!choices.length) && (
|
|
|
|
<SingleAutocompleteSelectFieldContent
|
|
|
|
choices={choices}
|
|
|
|
displayCustomValue={displayCustomValue}
|
|
|
|
emptyOption={emptyOption}
|
|
|
|
getItemProps={getItemProps}
|
|
|
|
hasMore={hasMore}
|
|
|
|
highlightedIndex={highlightedIndex}
|
|
|
|
loading={loading}
|
|
|
|
inputValue={inputValue}
|
|
|
|
isCustomValueSelected={isCustomValueSelected}
|
|
|
|
selectedItem={selectedItem}
|
|
|
|
onFetchMore={onFetchMore}
|
|
|
|
/>
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}}
|
|
|
|
</Downshift>
|
|
|
|
)}
|
|
|
|
</DebounceAutocomplete>
|
|
|
|
);
|
|
|
|
};
|
2019-06-19 14:40:52 +00:00
|
|
|
|
2020-01-15 15:36:45 +00:00
|
|
|
const SingleAutocompleteSelectField: React.FC<SingleAutocompleteSelectFieldProps> = ({
|
|
|
|
choices,
|
|
|
|
fetchChoices,
|
|
|
|
...rest
|
|
|
|
}) => {
|
2019-10-16 15:51:53 +00:00
|
|
|
const [query, setQuery] = React.useState("");
|
|
|
|
if (fetchChoices) {
|
2019-06-19 14:40:52 +00:00
|
|
|
return (
|
2019-10-16 15:51:53 +00:00
|
|
|
<DebounceAutocomplete debounceFn={fetchChoices}>
|
|
|
|
{debounceFn => (
|
|
|
|
<SingleAutocompleteSelectFieldComponent
|
|
|
|
choices={choices}
|
2019-10-30 14:34:24 +00:00
|
|
|
{...rest}
|
2019-10-16 15:51:53 +00:00
|
|
|
fetchChoices={debounceFn}
|
|
|
|
/>
|
|
|
|
)}
|
|
|
|
</DebounceAutocomplete>
|
2019-06-19 14:40:52 +00:00
|
|
|
);
|
|
|
|
}
|
2019-10-14 14:17:03 +00:00
|
|
|
|
2019-10-16 15:51:53 +00:00
|
|
|
return (
|
|
|
|
<SingleAutocompleteSelectFieldComponent
|
|
|
|
fetchChoices={q => setQuery(q || "")}
|
|
|
|
choices={filter(choices, query, {
|
|
|
|
key: "label"
|
|
|
|
})}
|
2019-10-30 14:34:24 +00:00
|
|
|
{...rest}
|
2019-10-16 15:51:53 +00:00
|
|
|
/>
|
|
|
|
);
|
|
|
|
};
|
2019-06-19 14:40:52 +00:00
|
|
|
export default SingleAutocompleteSelectField;
|