
* Replace withStyleswith useStyles (#1100) * Replace withStyleswith useStyles * Update messages * Use rem as a spacing unit (#1101) * Use rems as spacing units * Fix visual bugs * Update stories * Use macaw-ui as theme provider (#1108) * Use macaw ui as a theme provider * Add react-dom to aliases * Fix jest module resolution * Update useTheme hook usage * Fix test wrapper * Use macaw from git repo * Fix CI * Update stories * Fix aliasing * Extract savebar to macaw ui (#1146) * wip * Use savebar from macaw * Use confirm button from macaw * Improve file structure * Use sidebar context from macaw * Update macaw * Update macaw version * Remove savebar from storybook * Update stories * Use alerts and notifications from macaw (#1166) * Use alerts from macaw * Add notifications from macaw * Update stories * Pin macaw version * Encapsulate limit reached in one component * Remove unused imports * Use backlinks from macaw (#1183) * Use backlink from macaw * Update macaw version * Use macaw sidebar (#1148) * Use sidebar from macaw * Use shipped logo * Use lowercase * Update stories * Use user chip from macaw (#1191) * Use user chip from macaw * Use dedicated components for menu items * Simplify code * Bump version and fix types (#1210) * Rename onBack to onClick * Rename UserChip to UserChipMenu * Rename IMenuItem to SidebarMenuItem * Update macaw version * Fix tables after changes in macaw (#1220) * Update macaw version * Update changelog * Update stories * Fix after rebase * Update to macaw 0.2.0 * Lint files * Update macaw to 0.2.2
264 lines
7.1 KiB
TypeScript
264 lines
7.1 KiB
TypeScript
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)
|
|
}
|
|
}),
|
|
{ name: "SingleAutocompleteSelectField" }
|
|
);
|
|
|
|
export interface SingleAutocompleteSelectFieldProps
|
|
extends Partial<FetchMoreProps> {
|
|
add?: SingleAutocompleteActionType;
|
|
className?: string;
|
|
error?: boolean;
|
|
name: string;
|
|
displayValue: string;
|
|
emptyOption?: boolean;
|
|
choices: SingleAutocompleteChoiceType[];
|
|
value: string;
|
|
disabled?: boolean;
|
|
placeholder?: string;
|
|
allowCustomValues?: boolean;
|
|
helperText?: string;
|
|
label?: string;
|
|
InputProps?: InputProps;
|
|
fetchChoices?: (value: string) => void;
|
|
onChange: (event: React.ChangeEvent<any>) => void;
|
|
fetchOnFocus?: boolean;
|
|
FormHelperTextProps?: ExtendedFormHelperTextProps;
|
|
nakedInput?: boolean;
|
|
}
|
|
|
|
const DebounceAutocomplete: React.ComponentType<DebounceProps<
|
|
string
|
|
>> = Debounce;
|
|
|
|
const SingleAutocompleteSelectFieldComponent: React.FC<SingleAutocompleteSelectFieldProps> = 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,
|
|
...rest
|
|
} = props;
|
|
const classes = useStyles(props);
|
|
|
|
const handleChange = (item: string) => {
|
|
onChange({
|
|
target: {
|
|
name,
|
|
value: item
|
|
}
|
|
} as any);
|
|
};
|
|
|
|
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 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.label });
|
|
return;
|
|
}
|
|
|
|
reset({ inputValue: displayValue });
|
|
};
|
|
|
|
const displayCustomValue = !!(
|
|
inputValue &&
|
|
inputValue.length > 0 &&
|
|
allowCustomValues &&
|
|
!isValueInLabels
|
|
);
|
|
|
|
const handleBlur = () => {
|
|
ensureProperValues(true);
|
|
closeMenu();
|
|
};
|
|
|
|
const TextFieldComponent = nakedInput ? InputBase : TextField;
|
|
|
|
const commonInputProps = {
|
|
...InputProps,
|
|
...getInputProps({
|
|
placeholder
|
|
}),
|
|
endAdornment: (
|
|
<div>
|
|
<ArrowDropdownIcon />
|
|
</div>
|
|
),
|
|
error,
|
|
id: undefined,
|
|
onBlur: handleBlur,
|
|
onClick: toggleMenu,
|
|
onFocus: () => {
|
|
if (fetchOnFocus) {
|
|
fetchChoices(inputValue);
|
|
}
|
|
}
|
|
};
|
|
|
|
const nakedInputProps = nakedInput
|
|
? {
|
|
"aria-label": "naked",
|
|
...commonInputProps,
|
|
autoFocus: true,
|
|
className: classes.nakedInput
|
|
}
|
|
: {};
|
|
|
|
return (
|
|
<div
|
|
className={classNames(classes.container, className)}
|
|
{...rest}
|
|
>
|
|
<TextFieldComponent
|
|
{...nakedInputProps}
|
|
InputProps={commonInputProps}
|
|
error={error}
|
|
disabled={disabled}
|
|
helperText={helperText}
|
|
FormHelperTextProps={FormHelperTextProps}
|
|
label={label}
|
|
fullWidth={true}
|
|
/>
|
|
{isOpen && (!!inputValue || !!choices.length) && (
|
|
<SingleAutocompleteSelectFieldContent
|
|
add={
|
|
!!add && {
|
|
...add,
|
|
onClick: () => {
|
|
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}
|
|
/>
|
|
)}
|
|
</div>
|
|
);
|
|
}}
|
|
</Downshift>
|
|
)}
|
|
</DebounceAutocomplete>
|
|
);
|
|
};
|
|
|
|
const SingleAutocompleteSelectField: React.FC<SingleAutocompleteSelectFieldProps> = ({
|
|
choices,
|
|
fetchChoices,
|
|
...rest
|
|
}) => {
|
|
const [query, setQuery] = React.useState("");
|
|
|
|
if (fetchChoices) {
|
|
return (
|
|
<SingleAutocompleteSelectFieldComponent
|
|
choices={choices}
|
|
{...rest}
|
|
fetchChoices={fetchChoices}
|
|
/>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<SingleAutocompleteSelectFieldComponent
|
|
fetchChoices={q => setQuery(q || "")}
|
|
choices={filter(choices, query, {
|
|
key: "label"
|
|
})}
|
|
{...rest}
|
|
/>
|
|
);
|
|
};
|
|
|
|
export default SingleAutocompleteSelectField;
|