diff --git a/docs/src/pages/components/autocomplete/Virtualize.js b/docs/src/pages/components/autocomplete/Virtualize.js index 42e36e3e999cf3..8a46534e5b4079 100644 --- a/docs/src/pages/components/autocomplete/Virtualize.js +++ b/docs/src/pages/components/autocomplete/Virtualize.js @@ -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
; +}); + // 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) =>
); - }, []); // 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 (
- - {renderRow} - + + getChildSize(itemData[index])} + overscanCount={5} + itemCount={itemCount} + > + {renderRow} + +
); }); @@ -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 => [ + + {params.key} + , + params.children, +]; + export default function Virtualize() { const classes = useStyles(); @@ -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 => ( )} + renderOption={option => {option}} /> ); } diff --git a/docs/src/pages/components/autocomplete/Virtualize.tsx b/docs/src/pages/components/autocomplete/Virtualize.tsx index b676d3849a5adc..17cd5e086d46a8 100644 --- a/docs/src/pages/components/autocomplete/Virtualize.tsx +++ b/docs/src/pages/components/autocomplete/Virtualize.tsx @@ -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((props, ref) => { + const outerProps = React.useContext(OuterElementContext); + return
; +}); + // Adapter for react-window -const ListboxComponent = React.forwardRef>( - 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>( - (props2, ref2) =>
, - ); - }, []); // eslint-disable-line react-hooks/exhaustive-deps - - return ( -
- (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 ( +
+ + getChildSize(itemData[index])} overscanCount={5} itemCount={itemCount} > {renderRow} - -
- ); - }, -); + + +
+ ); +}); function random(length: number) { const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; @@ -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) => [ + + {params.key} + , + params.children, +]; + export default function Virtualize() { const classes = useStyles(); @@ -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>} + renderGroup={renderGroup} + options={OPTIONS} + groupBy={option => option[0].toUpperCase()} renderInput={params => ( )} + renderOption={option => {option}} /> ); }