import { InputBase, TextField } from "@material-ui/core"; import { InputProps } from "@material-ui/core/Input"; import { ExtendedFormHelperTextProps } from "@saleor/channels/components/ChannelForm/types"; import { makeStyles } from "@saleor/macaw-ui"; import { FetchMoreProps } from "@saleor/types"; import classNames from "classnames"; import Downshift from "downshift"; import { filter } from "fuzzaldrin"; import React from "react"; import ArrowDropdownIcon from "../../icons/ArrowDropdown"; import Debounce, { DebounceProps } from "../Debounce"; import SingleAutocompleteSelectFieldContent, { SingleAutocompleteActionType, SingleAutocompleteChoiceType } from "./SingleAutocompleteSelectFieldContent"; const useStyles = makeStyles( theme => ({ container: { flexGrow: 1, position: "relative" }, nakedInput: { padding: theme.spacing(2, 3) }, adornment: { cursor: "pointer", "&:active": { pointerEvents: "none" } } }), { name: "SingleAutocompleteSelectField" } ); export interface SingleAutocompleteSelectFieldProps extends Partial { add?: SingleAutocompleteActionType; className?: string; error?: boolean; name: string; displayValue: string; emptyOption?: boolean; choices: Array>; value: string; disabled?: boolean; placeholder?: string; allowCustomValues?: boolean; helperText?: string; label?: string; InputProps?: InputProps; fetchChoices?: (value: string) => void; onChange: (event: React.ChangeEvent) => void; fetchOnFocus?: boolean; FormHelperTextProps?: ExtendedFormHelperTextProps; nakedInput?: boolean; onBlur?: () => void; } const DebounceAutocomplete: React.ComponentType> = Debounce; const SingleAutocompleteSelectFieldComponent: React.FC = props => { const { add, allowCustomValues, className, choices, disabled, displayValue, emptyOption, error, hasMore, helperText, label, loading, name, placeholder, value, InputProps, fetchChoices, onChange, onFetchMore, fetchOnFocus, FormHelperTextProps, nakedInput = false, onBlur, ...rest } = props; const classes = useStyles(props); const handleChange = (item: string) => { onChange({ target: { name, value: item } } as any); }; return ( {debounceFn => ( displayValue || ""} onInputValueChange={value => debounceFn(value)} onSelect={handleChange} selectedItem={value || ""} // this is to prevent unwanted state updates when the dropdown is closed with an empty value, // which downshift interprets as the value being updated with an empty string, causing side-effects stateReducer={(_, changes) => { if (changes.isOpen === false) { delete changes.inputValue; } return changes; }} > {({ getInputProps, getItemProps, isOpen, inputValue, selectedItem, toggleMenu, closeMenu, highlightedIndex, reset }) => { const isCustomValueSelected = choices && selectedItem ? choices.filter(c => c.value === selectedItem).length === 0 : false; const choiceFromInputValue = choices.find( ({ value: choiceId }) => choiceId === inputValue ); const isValueInValues = !!choiceFromInputValue; const isValueInLabels = !!choices.find( choice => choice.label === inputValue ); const ensureProperValues = (alwaysCheck: boolean = false) => { if ((allowCustomValues || isValueInLabels) && !alwaysCheck) { return; } if (isValueInValues && !isValueInLabels) { reset({ inputValue: choiceFromInputValue.value }); return; } reset({ inputValue: displayValue }); }; const displayCustomValue = !!( inputValue && inputValue.length > 0 && allowCustomValues && !isValueInLabels ); const handleBlur = () => { ensureProperValues(true); if (onBlur) { onBlur(); } closeMenu(); }; const TextFieldComponent = nakedInput ? InputBase : TextField; const commonInputProps = { ...InputProps, ...getInputProps({ placeholder }), endAdornment: (
), error, id: undefined, onBlur: handleBlur, onClick: !disabled && toggleMenu, onFocus: () => { if (fetchOnFocus) { fetchChoices(inputValue); } } }; const nakedInputProps = nakedInput ? { "aria-label": "naked", ...commonInputProps, autoFocus: true, className: classes.nakedInput } : {}; return (
{isOpen && (!!inputValue || !!choices.length) && ( { add.onClick(); closeMenu(); } } } choices={choices} displayCustomValue={displayCustomValue} emptyOption={emptyOption} getItemProps={getItemProps} hasMore={hasMore} highlightedIndex={highlightedIndex} loading={loading} inputValue={inputValue} isCustomValueSelected={isCustomValueSelected} selectedItem={selectedItem} onFetchMore={onFetchMore} /> )}
); }}
)}
); }; const SingleAutocompleteSelectField: React.FC = ({ choices, fetchChoices, ...rest }) => { const [query, setQuery] = React.useState(""); if (fetchChoices) { return ( ); } return ( setQuery(q || "")} choices={filter(choices, query, { key: "label" })} {...rest} /> ); }; export default SingleAutocompleteSelectField;