Improve scrolling experience
This commit is contained in:
parent
c33102b472
commit
ae14076ceb
5 changed files with 58 additions and 30 deletions
3
assets/images/ChevronDown.svg
Normal file
3
assets/images/ChevronDown.svg
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<svg width="15" height="10" viewBox="0 0 15 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.23278 6.21084L13 -6.40628e-08L14.4656 1.3609L7.23278 9.15006L-1.15036e-05 1.3609L1.46558 -5.68248e-07L7.23278 6.21084Z" fill="#06847B"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 292 B |
|
@ -2,7 +2,6 @@ import { storiesOf } from "@storybook/react";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import Form from "@saleor/components/Form";
|
import Form from "@saleor/components/Form";
|
||||||
import { maybe } from "@saleor/misc";
|
|
||||||
import CardDecorator from "@saleor/storybook/CardDecorator";
|
import CardDecorator from "@saleor/storybook/CardDecorator";
|
||||||
import Decorator from "@saleor/storybook/Decorator";
|
import Decorator from "@saleor/storybook/Decorator";
|
||||||
import { ChoiceProvider } from "@saleor/storybook/mock";
|
import { ChoiceProvider } from "@saleor/storybook/mock";
|
||||||
|
|
|
@ -7,9 +7,13 @@ import { makeStyles } from "@material-ui/styles";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { GetItemPropsOptions } from "downshift";
|
import { GetItemPropsOptions } from "downshift";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import SVG from "react-inlinesvg";
|
||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
|
|
||||||
import useElementScroll from "@saleor/hooks/useElementScroll";
|
import chevronDown from "@assets/images/ChevronDown.svg";
|
||||||
|
import useElementScroll, {
|
||||||
|
isScrolledToBottom
|
||||||
|
} from "@saleor/hooks/useElementScroll";
|
||||||
import { FetchMoreProps } from "@saleor/types";
|
import { FetchMoreProps } from "@saleor/types";
|
||||||
import Hr from "../Hr";
|
import Hr from "../Hr";
|
||||||
|
|
||||||
|
@ -35,11 +39,29 @@ export interface SingleAutocompleteSelectFieldContentProps
|
||||||
|
|
||||||
const useStyles = makeStyles(
|
const useStyles = makeStyles(
|
||||||
(theme: Theme) => ({
|
(theme: Theme) => ({
|
||||||
|
arrowContainer: {
|
||||||
|
position: "relative"
|
||||||
|
},
|
||||||
|
arrowInnerContainer: {
|
||||||
|
alignItems: "center",
|
||||||
|
background: theme.palette.grey[50],
|
||||||
|
bottom: 0,
|
||||||
|
display: "flex",
|
||||||
|
height: 30,
|
||||||
|
justifyContent: "center",
|
||||||
|
opacity: 1,
|
||||||
|
position: "absolute",
|
||||||
|
transition: theme.transitions.duration.short + "ms",
|
||||||
|
width: "100%"
|
||||||
|
},
|
||||||
content: {
|
content: {
|
||||||
maxHeight: menuItemHeight * maxMenuItems + theme.spacing.unit * 2,
|
maxHeight: menuItemHeight * maxMenuItems + theme.spacing.unit * 2,
|
||||||
overflow: "scroll",
|
overflow: "scroll",
|
||||||
padding: 8
|
padding: 8
|
||||||
},
|
},
|
||||||
|
hide: {
|
||||||
|
opacity: 0
|
||||||
|
},
|
||||||
hr: {
|
hr: {
|
||||||
margin: `${theme.spacing.unit}px 0`
|
margin: `${theme.spacing.unit}px 0`
|
||||||
},
|
},
|
||||||
|
@ -53,22 +75,14 @@ const useStyles = makeStyles(
|
||||||
justifyContent: "center"
|
justifyContent: "center"
|
||||||
},
|
},
|
||||||
root: {
|
root: {
|
||||||
borderRadius: 4,
|
borderBottomLeftRadius: 8,
|
||||||
|
borderBottomRightRadius: 8,
|
||||||
left: 0,
|
left: 0,
|
||||||
marginTop: theme.spacing.unit,
|
marginTop: theme.spacing.unit,
|
||||||
|
overflow: "hidden",
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
right: 0,
|
right: 0,
|
||||||
zIndex: 22
|
zIndex: 22
|
||||||
},
|
|
||||||
shadow: {
|
|
||||||
"&$shadowLine": {
|
|
||||||
boxShadow: `0px -5px 10px 0px ${theme.palette.grey[800]}`
|
|
||||||
}
|
|
||||||
},
|
|
||||||
shadowLine: {
|
|
||||||
boxShadow: `0px 0px 0px 0px ${theme.palette.grey[50]}`,
|
|
||||||
height: 1,
|
|
||||||
transition: theme.transitions.duration.short + "ms"
|
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
|
@ -114,10 +128,7 @@ const SingleAutocompleteSelectFieldContent: React.FC<
|
||||||
const scrollPosition = useElementScroll(anchor);
|
const scrollPosition = useElementScroll(anchor);
|
||||||
const [calledForMore, setCalledForMore] = React.useState(false);
|
const [calledForMore, setCalledForMore] = React.useState(false);
|
||||||
|
|
||||||
const scrolledToBottom = anchor.current
|
const scrolledToBottom = isScrolledToBottom(anchor, scrollPosition, 50);
|
||||||
? scrollPosition.y + anchor.current.clientHeight + offset >=
|
|
||||||
anchor.current.scrollHeight
|
|
||||||
: false;
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!calledForMore && onFetchMore && scrolledToBottom) {
|
if (!calledForMore && onFetchMore && scrolledToBottom) {
|
||||||
|
@ -133,7 +144,7 @@ const SingleAutocompleteSelectFieldContent: React.FC<
|
||||||
}, [loading]);
|
}, [loading]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper className={classes.root} square>
|
<Paper className={classes.root}>
|
||||||
<div className={classes.content} ref={anchor}>
|
<div className={classes.content} ref={anchor}>
|
||||||
{choices.length > 0 || displayCustomValue ? (
|
{choices.length > 0 || displayCustomValue ? (
|
||||||
<>
|
<>
|
||||||
|
@ -219,11 +230,15 @@ const SingleAutocompleteSelectFieldContent: React.FC<
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
<div className={classes.arrowContainer}>
|
||||||
<div
|
<div
|
||||||
className={classNames(classes.shadowLine, {
|
className={classNames(classes.arrowInnerContainer, {
|
||||||
[classes.shadow]: !scrolledToBottom && choices.length > 0
|
[classes.hide]: scrolledToBottom && choices.length > 0
|
||||||
})}
|
})}
|
||||||
/>
|
>
|
||||||
|
<SVG src={chevronDown} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</Paper>
|
</Paper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,20 +1,30 @@
|
||||||
import throttle from "lodash-es/throttle";
|
import throttle from "lodash-es/throttle";
|
||||||
import { MutableRefObject, useEffect, useState } from "react";
|
import { MutableRefObject, useEffect, useState } from "react";
|
||||||
|
|
||||||
function getPosition(anchor?: HTMLElement) {
|
export type Position = Record<"x" | "y", number>;
|
||||||
|
|
||||||
|
function getPosition(anchor?: HTMLElement): Position {
|
||||||
if (!!anchor) {
|
if (!!anchor) {
|
||||||
return {
|
return {
|
||||||
x: anchor.scrollLeft,
|
x: anchor.scrollLeft,
|
||||||
y: anchor.scrollTop
|
y: anchor.scrollTop
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return {
|
return undefined;
|
||||||
x: 0,
|
|
||||||
y: 0
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function useElementScroll(anchor: MutableRefObject<HTMLElement>) {
|
export function isScrolledToBottom(
|
||||||
|
anchor: MutableRefObject<HTMLElement>,
|
||||||
|
position: Position,
|
||||||
|
offset: number = 0
|
||||||
|
) {
|
||||||
|
return !!anchor.current && position
|
||||||
|
? position.y + anchor.current.clientHeight + offset >=
|
||||||
|
anchor.current.scrollHeight
|
||||||
|
: false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function useElementScroll(anchor: MutableRefObject<HTMLElement>): Position {
|
||||||
const [scroll, setScroll] = useState(getPosition(anchor.current));
|
const [scroll, setScroll] = useState(getPosition(anchor.current));
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
@ -10,7 +10,8 @@ function createSingleAutocompleteSelectHandler(
|
||||||
change(event);
|
change(event);
|
||||||
|
|
||||||
const value = event.target.value;
|
const value = event.target.value;
|
||||||
setSelected(choices.find(category => category.value === value).label);
|
const choice = choices.find(category => category.value === value)
|
||||||
|
setSelected(choice ? choice.label : value);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue