Skip to content

Commit

Permalink
[Autocomplete] Expand virtualized example to have grouped items (#18763)
Browse files Browse the repository at this point in the history
  • Loading branch information
Janpot authored and oliviertassinari committed Dec 12, 2019
1 parent c4c5a03 commit a98c01c
Show file tree
Hide file tree
Showing 2 changed files with 127 additions and 61 deletions.
85 changes: 60 additions & 25 deletions docs/src/pages/components/autocomplete/Virtualize.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,50 +3,71 @@ import PropTypes from 'prop-types';
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 { useTheme, makeStyles } from '@material-ui/core/styles';
import { FixedSizeList } from 'react-window';
import { VariableSizeList } from 'react-window';
import { Typography } from '@material-ui/core';

const LISTBOX_PADDING = 8; // px

function renderRow(props) {
const { data, index, style } = props;

return React.cloneElement(data[index], {
style: {
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
display: 'block',
...style,
top: style.top + LISTBOX_PADDING,
},
});
}

const OuterElementContext = React.createContext({});

const OuterElementType = React.forwardRef((props, ref) => {
const outerProps = React.useContext(OuterElementContext);
return <div ref={ref} {...props} {...outerProps} />;
});

// Adapter for react-window
const ListboxComponent = React.forwardRef(function ListboxComponent(props, ref) {
const { children, ...other } = props;
const itemData = React.Children.toArray(children);
const theme = useTheme();
const smUp = useMediaQuery(theme.breakpoints.up('sm'));
const itemCount = Array.isArray(children) ? children.length : 0;
const smUp = useMediaQuery(theme.breakpoints.up('sm'), { noSsr: true });
const itemCount = itemData.length;
const itemSize = smUp ? 36 : 48;

const outerElementType = React.useMemo(() => {
return React.forwardRef((props2, ref2) => <div ref={ref2} {...props2} {...other} />);
}, []); // eslint-disable-line react-hooks/exhaustive-deps
const getChildSize = child => {
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);
};

return (
<div ref={ref}>
<FixedSizeList
style={{ padding: 0, height: Math.min(8, itemCount) * itemSize, maxHeight: 'auto' }}
itemData={children}
height={250}
width="100%"
outerElementType={outerElementType}
innerElementType="ul"
itemSize={itemSize}
overscanCount={5}
itemCount={itemCount}
>
{renderRow}
</FixedSizeList>
<OuterElementContext.Provider value={other}>
<VariableSizeList
itemData={itemData}
height={getHeight() + 2 * LISTBOX_PADDING}
width="100%"
key={itemCount}
outerElementType={OuterElementType}
innerElementType="ul"
itemSize={index => getChildSize(itemData[index])}
overscanCount={5}
itemCount={itemCount}
>
{renderRow}
</VariableSizeList>
</OuterElementContext.Provider>
</div>
);
});
Expand Down Expand Up @@ -75,6 +96,17 @@ const useStyles = makeStyles({
},
});

const OPTIONS = Array.from(new Array(10000))
.map(() => random(10 + Math.ceil(Math.random() * 20)))
.sort((a, b) => a.toUpperCase().localeCompare(b.toUpperCase()));

const renderGroup = params => [
<ListSubheader key={params.key} component="div">
{params.key}
</ListSubheader>,
params.children,
];

export default function Virtualize() {
const classes = useStyles();

Expand All @@ -85,10 +117,13 @@ export default function Virtualize() {
disableListWrap
classes={classes}
ListboxComponent={ListboxComponent}
options={Array.from(new Array(10000)).map(() => random(Math.ceil(Math.random() * 18)))}
renderGroup={renderGroup}
options={OPTIONS}
groupBy={option => option[0].toUpperCase()}
renderInput={params => (
<TextField {...params} variant="outlined" label="10,000 options" fullWidth />
)}
renderOption={option => <Typography noWrap>{option}</Typography>}
/>
);
}
103 changes: 67 additions & 36 deletions docs/src/pages/components/autocomplete/Virtualize.tsx
Original file line number Diff line number Diff line change
@@ -1,58 +1,75 @@
import React from 'react';
import TextField from '@material-ui/core/TextField';
import Autocomplete from '@material-ui/lab/Autocomplete';
import Autocomplete, { RenderGroupParams } from '@material-ui/lab/Autocomplete';
import useMediaQuery from '@material-ui/core/useMediaQuery';
import ListSubheader from '@material-ui/core/ListSubheader';
import { useTheme, makeStyles } from '@material-ui/core/styles';
import { FixedSizeList, ListChildComponentProps } from 'react-window';
import { VariableSizeList, ListChildComponentProps } from 'react-window';
import { Typography } from '@material-ui/core';

const LISTBOX_PADDING = 8; // px

function renderRow(props: ListChildComponentProps) {
const { data, index, style } = props;

return React.cloneElement(data[index], {
style: {
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
display: 'block',
...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} />;
});

// Adapter for react-window
const ListboxComponent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLElement>>(
function ListboxComponent(props, ref) {
const { children, ...other } = props;
const theme = useTheme();
const smUp = useMediaQuery(theme.breakpoints.up('sm'));
const itemCount = Array.isArray(children) ? children.length : 0;
const itemSize = smUp ? 36 : 48;

const outerElementType = React.useMemo(() => {
return React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
(props2, ref2) => <div ref={ref2} {...props2} {...other} />,
);
}, []); // eslint-disable-line react-hooks/exhaustive-deps

return (
<div ref={ref}>
<FixedSizeList
style={{ padding: 0, height: Math.min(8, itemCount) * itemSize, maxHeight: 'auto' }}
itemData={children}
height={250}
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);
};

return (
<div ref={ref}>
<OuterElementContext.Provider value={other}>
<VariableSizeList
itemData={itemData}
height={getHeight() + 2 * LISTBOX_PADDING}
width="100%"
outerElementType={outerElementType}
key={itemCount}
outerElementType={OuterElementType}
innerElementType="ul"
itemSize={itemSize}
itemSize={index => getChildSize(itemData[index])}
overscanCount={5}
itemCount={itemCount}
>
{renderRow}
</FixedSizeList>
</div>
);
},
);
</VariableSizeList>
</OuterElementContext.Provider>
</div>
);
});

function random(length: number) {
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
Expand All @@ -74,6 +91,17 @@ const useStyles = makeStyles({
},
});

const OPTIONS = Array.from(new Array(10000))
.map(() => random(10 + Math.ceil(Math.random() * 20)))
.sort((a: string, b: string) => a.toUpperCase().localeCompare(b.toUpperCase()));

const renderGroup = (params: RenderGroupParams) => [
<ListSubheader key={params.key} component="div">
{params.key}
</ListSubheader>,
params.children,
];

export default function Virtualize() {
const classes = useStyles();

Expand All @@ -83,11 +111,14 @@ export default function Virtualize() {
style={{ width: 300 }}
disableListWrap
classes={classes}
ListboxComponent={ListboxComponent}
options={Array.from(new Array(10000)).map(() => random(Math.ceil(Math.random() * 18)))}
ListboxComponent={ListboxComponent as React.ComponentType<React.HTMLAttributes<HTMLElement>>}
renderGroup={renderGroup}
options={OPTIONS}
groupBy={option => option[0].toUpperCase()}
renderInput={params => (
<TextField {...params} variant="outlined" label="10,000 options" fullWidth />
)}
renderOption={option => <Typography noWrap>{option}</Typography>}
/>
);
}

0 comments on commit a98c01c

Please sign in to comment.