2019-10-14 14:17:03 +00:00
|
|
|
import CircularProgress from "@material-ui/core/CircularProgress";
|
2019-10-14 11:57:08 +00:00
|
|
|
import MenuItem from "@material-ui/core/MenuItem";
|
|
|
|
import Paper from "@material-ui/core/Paper";
|
|
|
|
import { Theme } from "@material-ui/core/styles";
|
|
|
|
import Typography from "@material-ui/core/Typography";
|
|
|
|
import { makeStyles } from "@material-ui/styles";
|
|
|
|
import classNames from "classnames";
|
|
|
|
import { GetItemPropsOptions } from "downshift";
|
|
|
|
import React from "react";
|
|
|
|
import { FormattedMessage } from "react-intl";
|
|
|
|
|
|
|
|
import useElementScroll from "@saleor/hooks/useElementScroll";
|
2019-10-14 14:17:03 +00:00
|
|
|
import { FetchMoreProps } from "@saleor/types";
|
2019-10-14 11:57:08 +00:00
|
|
|
import Hr from "../Hr";
|
|
|
|
|
|
|
|
const menuItemHeight = 46;
|
|
|
|
const maxMenuItems = 5;
|
2019-10-14 14:17:03 +00:00
|
|
|
const offset = 24;
|
2019-10-14 11:57:08 +00:00
|
|
|
|
|
|
|
export interface SingleAutocompleteChoiceType {
|
|
|
|
label: string;
|
|
|
|
value: any;
|
|
|
|
}
|
2019-10-14 14:17:03 +00:00
|
|
|
export interface SingleAutocompleteSelectFieldContentProps
|
|
|
|
extends Partial<FetchMoreProps> {
|
2019-10-14 11:57:08 +00:00
|
|
|
choices: SingleAutocompleteChoiceType[];
|
|
|
|
displayCustomValue: boolean;
|
|
|
|
emptyOption: boolean;
|
|
|
|
getItemProps: (options: GetItemPropsOptions) => void;
|
|
|
|
highlightedIndex: number;
|
|
|
|
inputValue: string;
|
|
|
|
isCustomValueSelected: boolean;
|
|
|
|
selectedItem: any;
|
|
|
|
}
|
|
|
|
|
|
|
|
const useStyles = makeStyles(
|
|
|
|
(theme: Theme) => ({
|
|
|
|
content: {
|
|
|
|
maxHeight: menuItemHeight * maxMenuItems + theme.spacing.unit * 2,
|
|
|
|
overflow: "scroll",
|
|
|
|
padding: 8
|
|
|
|
},
|
|
|
|
hr: {
|
|
|
|
margin: `${theme.spacing.unit}px 0`
|
|
|
|
},
|
|
|
|
menuItem: {
|
|
|
|
height: "auto",
|
|
|
|
whiteSpace: "normal"
|
|
|
|
},
|
2019-10-14 14:17:03 +00:00
|
|
|
progress: {},
|
|
|
|
progressContainer: {
|
|
|
|
display: "flex",
|
|
|
|
justifyContent: "center"
|
|
|
|
},
|
2019-10-14 11:57:08 +00:00
|
|
|
root: {
|
|
|
|
borderRadius: 4,
|
|
|
|
left: 0,
|
|
|
|
marginTop: theme.spacing.unit,
|
|
|
|
position: "absolute",
|
|
|
|
right: 0,
|
|
|
|
zIndex: 22
|
|
|
|
},
|
|
|
|
shadow: {
|
2019-10-14 14:17:03 +00:00
|
|
|
"&$shadowLine": {
|
|
|
|
boxShadow: `0px -5px 10px 0px ${theme.palette.grey[800]}`
|
|
|
|
}
|
2019-10-14 11:57:08 +00:00
|
|
|
},
|
|
|
|
shadowLine: {
|
|
|
|
boxShadow: `0px 0px 0px 0px ${theme.palette.grey[50]}`,
|
|
|
|
height: 1,
|
|
|
|
transition: theme.transitions.duration.short + "ms"
|
|
|
|
}
|
|
|
|
}),
|
|
|
|
{
|
|
|
|
name: "SingleAutocompleteSelectFieldContent"
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
function getChoiceIndex(
|
|
|
|
index: number,
|
|
|
|
emptyValue: boolean,
|
|
|
|
customValue: boolean
|
|
|
|
) {
|
|
|
|
let choiceIndex = index;
|
|
|
|
if (emptyValue) {
|
|
|
|
choiceIndex += 1;
|
|
|
|
}
|
|
|
|
if (customValue) {
|
|
|
|
choiceIndex += 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
return choiceIndex;
|
|
|
|
}
|
|
|
|
|
|
|
|
const SingleAutocompleteSelectFieldContent: React.FC<
|
|
|
|
SingleAutocompleteSelectFieldContentProps
|
|
|
|
> = props => {
|
|
|
|
const {
|
|
|
|
choices,
|
|
|
|
displayCustomValue,
|
|
|
|
emptyOption,
|
|
|
|
getItemProps,
|
2019-10-14 14:17:03 +00:00
|
|
|
hasMore,
|
2019-10-14 11:57:08 +00:00
|
|
|
highlightedIndex,
|
2019-10-14 14:17:03 +00:00
|
|
|
loading,
|
2019-10-14 11:57:08 +00:00
|
|
|
inputValue,
|
|
|
|
isCustomValueSelected,
|
2019-10-14 14:17:03 +00:00
|
|
|
selectedItem,
|
|
|
|
onFetchMore
|
2019-10-14 11:57:08 +00:00
|
|
|
} = props;
|
|
|
|
|
|
|
|
const classes = useStyles(props);
|
|
|
|
const anchor = React.useRef<HTMLDivElement>();
|
|
|
|
const scrollPosition = useElementScroll(anchor);
|
2019-10-14 14:17:03 +00:00
|
|
|
const [calledForMore, setCalledForMore] = React.useState(false);
|
2019-10-14 11:57:08 +00:00
|
|
|
|
2019-10-14 14:17:03 +00:00
|
|
|
const scrolledToBottom = anchor.current
|
|
|
|
? scrollPosition.y + anchor.current.clientHeight + offset >=
|
2019-10-14 11:57:08 +00:00
|
|
|
anchor.current.scrollHeight
|
|
|
|
: false;
|
|
|
|
|
2019-10-14 14:17:03 +00:00
|
|
|
React.useEffect(() => {
|
|
|
|
if (!calledForMore && onFetchMore && scrolledToBottom) {
|
|
|
|
onFetchMore();
|
|
|
|
setCalledForMore(true);
|
|
|
|
}
|
|
|
|
}, [scrolledToBottom]);
|
|
|
|
|
|
|
|
React.useEffect(() => {
|
|
|
|
if (calledForMore && !loading) {
|
|
|
|
setCalledForMore(false);
|
|
|
|
}
|
|
|
|
}, [loading]);
|
|
|
|
|
2019-10-14 11:57:08 +00:00
|
|
|
return (
|
|
|
|
<Paper className={classes.root} square>
|
|
|
|
<div className={classes.content} ref={anchor}>
|
|
|
|
{choices.length > 0 || displayCustomValue ? (
|
|
|
|
<>
|
|
|
|
{emptyOption && (
|
|
|
|
<MenuItem
|
|
|
|
className={classes.menuItem}
|
|
|
|
component="div"
|
|
|
|
{...getItemProps({
|
|
|
|
item: ""
|
|
|
|
})}
|
|
|
|
data-tc="singleautocomplete-select-option"
|
|
|
|
>
|
|
|
|
<Typography color="textSecondary">
|
|
|
|
<FormattedMessage defaultMessage="None" />
|
|
|
|
</Typography>
|
|
|
|
</MenuItem>
|
|
|
|
)}
|
|
|
|
{displayCustomValue && (
|
|
|
|
<MenuItem
|
|
|
|
className={classes.menuItem}
|
|
|
|
key={"customValue"}
|
|
|
|
selected={isCustomValueSelected}
|
|
|
|
component="div"
|
|
|
|
{...getItemProps({
|
|
|
|
item: inputValue
|
|
|
|
})}
|
|
|
|
data-tc="singleautocomplete-select-option"
|
|
|
|
>
|
|
|
|
<FormattedMessage
|
|
|
|
defaultMessage="Add new value: {value}"
|
|
|
|
description="add custom select input option"
|
|
|
|
values={{
|
|
|
|
value: inputValue
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
</MenuItem>
|
|
|
|
)}
|
|
|
|
{choices.length > 0 && displayCustomValue && (
|
|
|
|
<Hr className={classes.hr} />
|
|
|
|
)}
|
|
|
|
{choices.map((suggestion, index) => {
|
|
|
|
const choiceIndex = getChoiceIndex(
|
|
|
|
index,
|
|
|
|
emptyOption,
|
|
|
|
displayCustomValue
|
|
|
|
);
|
|
|
|
|
|
|
|
return (
|
|
|
|
<MenuItem
|
|
|
|
className={classes.menuItem}
|
|
|
|
key={JSON.stringify(suggestion)}
|
|
|
|
selected={
|
|
|
|
highlightedIndex === choiceIndex ||
|
|
|
|
selectedItem === suggestion.value
|
|
|
|
}
|
|
|
|
component="div"
|
|
|
|
{...getItemProps({
|
|
|
|
index: choiceIndex,
|
|
|
|
item: suggestion.value
|
|
|
|
})}
|
|
|
|
data-tc="singleautocomplete-select-option"
|
|
|
|
>
|
|
|
|
{suggestion.label}
|
|
|
|
</MenuItem>
|
|
|
|
);
|
|
|
|
})}
|
2019-10-14 14:17:03 +00:00
|
|
|
{hasMore && (
|
|
|
|
<>
|
|
|
|
<Hr className={classes.hr} />
|
|
|
|
<div className={classes.progressContainer}>
|
|
|
|
<CircularProgress className={classes.progress} size={24} />
|
|
|
|
</div>
|
|
|
|
</>
|
|
|
|
)}
|
2019-10-14 11:57:08 +00:00
|
|
|
</>
|
|
|
|
) : (
|
|
|
|
<MenuItem
|
|
|
|
disabled={true}
|
|
|
|
component="div"
|
|
|
|
data-tc="singleautocomplete-select-no-options"
|
|
|
|
>
|
|
|
|
<FormattedMessage defaultMessage="No results found" />
|
|
|
|
</MenuItem>
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
<div
|
|
|
|
className={classNames(classes.shadowLine, {
|
2019-10-14 14:17:03 +00:00
|
|
|
[classes.shadow]: !scrolledToBottom
|
2019-10-14 11:57:08 +00:00
|
|
|
})}
|
|
|
|
/>
|
|
|
|
</Paper>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
SingleAutocompleteSelectFieldContent.displayName =
|
|
|
|
"SingleAutocompleteSelectFieldContent";
|
|
|
|
export default SingleAutocompleteSelectFieldContent;
|