Use virtualized list

This commit is contained in:
Ken Hibino 2022-03-25 06:43:44 -07:00
parent 99f147df66
commit f31f248937
3 changed files with 98 additions and 1 deletions

View File

@ -56,6 +56,7 @@
]
},
"devDependencies": {
"@types/react-window": "1.8.5",
"redux-devtools": "3.7.0"
},
"homepage": "/[[.RootPath]]"

View File

@ -1,7 +1,10 @@
import React from "react";
import { makeStyles } from "@material-ui/core/styles";
import { makeStyles, useTheme } from "@material-ui/core/styles";
import TextField from "@material-ui/core/TextField";
import Autocomplete from "@material-ui/lab/Autocomplete";
import useMediaQuery from "@material-ui/core/useMediaQuery";
import ListSubheader from "@material-ui/core/ListSubheader";
import { VariableSizeList, ListChildComponentProps } from "react-window";
import { GroupInfo } from "../api";
import { isDarkTheme } from "../theme";
@ -34,6 +37,12 @@ export default function GroupSelect(props: Props) {
return (
<Autocomplete
id="task-group-selector"
disableListWrap
ListboxComponent={
ListboxComponent as React.ComponentType<
React.HTMLAttributes<HTMLElement>
>
}
options={props.groups}
getOptionLabel={(option: GroupInfo) => option.group}
style={{ width: 300 }}
@ -49,3 +58,83 @@ export default function GroupSelect(props: Props) {
/>
);
}
// Virtualized list.
// Reference: https://v4.mui.com/components/autocomplete/#virtualization
const LISTBOX_PADDING = 8; // px
function renderRow(props: ListChildComponentProps) {
const { data, index, style } = props;
return React.cloneElement(data[index], {
style: {
...style,
top: (style.top as number) + LISTBOX_PADDING,
},
});
}
const OuterElementContext = React.createContext({});
const OuterElementType = React.forwardRef<HTMLDivElement>((props, ref) => {
const outerProps = React.useContext(OuterElementContext);
return <div ref={ref} {...props} {...outerProps} />;
});
function useResetCache(data: any) {
const ref = React.useRef<VariableSizeList>(null);
React.useEffect(() => {
if (ref.current != null) {
ref.current.resetAfterIndex(0, true);
}
}, [data]);
return ref;
}
// Adapter for react-window
const ListboxComponent = React.forwardRef<HTMLDivElement>(
function ListboxComponent(props, ref) {
const { children, ...other } = props;
const itemData = React.Children.toArray(children);
const theme = useTheme();
const smUp = useMediaQuery(theme.breakpoints.up("sm"), { noSsr: true });
const itemCount = itemData.length;
const itemSize = smUp ? 36 : 48;
const getChildSize = (child: React.ReactNode) => {
if (React.isValidElement(child) && child.type === ListSubheader) {
return 48;
}
return itemSize;
};
const getHeight = () => {
if (itemCount > 8) {
return 8 * itemSize;
}
return itemData.map(getChildSize).reduce((a, b) => a + b, 0);
};
const gridRef = useResetCache(itemCount);
return (
<div ref={ref}>
<OuterElementContext.Provider value={other}>
<VariableSizeList
itemData={itemData}
height={getHeight() + 2 * LISTBOX_PADDING}
width="100%"
ref={gridRef}
outerElementType={OuterElementType}
innerElementType="ul"
itemSize={(index) => getChildSize(itemData[index])}
overscanCount={5}
itemCount={itemCount}
>
{renderRow}
</VariableSizeList>
</OuterElementContext.Provider>
</div>
);
}
);

View File

@ -2036,6 +2036,13 @@
dependencies:
"@types/react" "*"
"@types/react-window@1.8.5":
version "1.8.5"
resolved "https://registry.npmjs.org/@types/react-window/-/react-window-1.8.5.tgz#285fcc5cea703eef78d90f499e1457e9b5c02fc1"
integrity sha512-V9q3CvhC9Jk9bWBOysPGaWy/Z0lxYcTXLtLipkt2cnRj1JOSFNF7wqGpkScSXMgBwC+fnVRg/7shwgddBG5ICw==
dependencies:
"@types/react" "*"
"@types/react@*", "@types/react@^17.0.29":
version "17.0.29"
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.29.tgz#9535f3fc01a4981ce9cadcf0daa2593c0c2f2251"