Skip to content

Commit

Permalink
[docs][material-ui][Autocomplete] Fix virtualization demo (mui#44382)
Browse files Browse the repository at this point in the history
  • Loading branch information
DiegoAndai authored Nov 15, 2024
1 parent e8e4b79 commit 4e26c0b
Show file tree
Hide file tree
Showing 7 changed files with 186 additions and 17 deletions.
6 changes: 5 additions & 1 deletion docs/data/material/components/autocomplete/Virtualize.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,11 @@ export default function Virtualize() {
renderGroup={(params) => params}
slots={{
popper: StyledPopper,
listbox: ListboxComponent,
}}
slotProps={{
listbox: {
component: ListboxComponent,
},
}}
/>
);
Expand Down
6 changes: 5 additions & 1 deletion docs/data/material/components/autocomplete/Virtualize.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,11 @@ export default function Virtualize() {
renderGroup={(params) => params as any}
slots={{
popper: StyledPopper,
listbox: ListboxComponent,
}}
slotProps={{
listbox: {
component: ListboxComponent,
},
}}
/>
);
Expand Down
15 changes: 0 additions & 15 deletions docs/data/material/components/autocomplete/Virtualize.tsx.preview

This file was deleted.

3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions test/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"react-dom": "^18.3.1",
"react-is": "^18.3.1",
"react-router-dom": "^6.28.0",
"react-window": "^1.8.10",
"sinon": "^19.0.2",
"styled-components": "^6.1.13",
"stylis": "4.2.0",
Expand Down
162 changes: 162 additions & 0 deletions test/regressions/fixtures/Autocomplete/Virtualize.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import TextField from '@mui/material/TextField';
import Autocomplete, { autocompleteClasses } from '@mui/material/Autocomplete';
import useMediaQuery from '@mui/material/useMediaQuery';
import ListSubheader from '@mui/material/ListSubheader';
import Popper from '@mui/material/Popper';
import { useTheme, styled } from '@mui/material/styles';
import { VariableSizeList } from 'react-window';
import Typography from '@mui/material/Typography';

const LISTBOX_PADDING = 8; // px

function renderRow(props) {
const { data, index, style } = props;
const dataSet = data[index];
const inlineStyle = {
...style,
top: style.top + LISTBOX_PADDING,
};

if (dataSet.hasOwnProperty('group')) {
return (
<ListSubheader key={dataSet.key} component="div" style={inlineStyle}>
{dataSet.group}
</ListSubheader>
);
}

const { key, ...optionProps } = dataSet[0];

return (
<Typography key={key} component="li" {...optionProps} noWrap style={inlineStyle}>
{`#${dataSet[2] + 1} - ${dataSet[1]}`}
</Typography>
);
}

const OuterElementContext = React.createContext({});

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

function useResetCache(data) {
const ref = React.useRef(null);
React.useEffect(() => {
if (ref.current != null) {
ref.current.resetAfterIndex(0, true);
}
}, [data]);
return ref;
}

// Adapter for react-window
const ListboxComponent = React.forwardRef(function ListboxComponent(props, ref) {
const { children, ...other } = props;
const itemData = [];
children.forEach((item) => {
itemData.push(item);
itemData.push(...(item.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) => {
if (child.hasOwnProperty('group')) {
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>
);
});

ListboxComponent.propTypes = {
children: PropTypes.node,
};

function random(length) {
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let result = '';

for (let i = 0; i < length; i += 1) {
result += characters.charAt(Math.floor(Math.random() * characters.length));
}

return result;
}

const StyledPopper = styled(Popper)({
[`& .${autocompleteClasses.listbox}`]: {
boxSizing: 'border-box',
'& ul': {
padding: 0,
margin: 0,
},
},
});

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

export default function Virtualize() {
return (
<div style={{ height: 400 }}>
<Autocomplete
disableCloseOnSelect
sx={{ width: 300 }}
disableListWrap
options={OPTIONS}
groupBy={(option) => option[0].toUpperCase()}
renderInput={(params) => <TextField {...params} label="10,000 options" />}
renderOption={(props, option, state) => [props, option, state.index]}
renderGroup={(params) => params}
slots={{
popper: StyledPopper,
}}
slotProps={{
listbox: {
component: ListboxComponent,
},
}}
/>
</div>
);
}
10 changes: 10 additions & 0 deletions test/regressions/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,16 @@ async function main() {
route: '/regression-Autocomplete/TextboxExpandsOnListboxOpen2',
});
});

it('should style virtualized listbox correctly', async () => {
const testcase = await renderFixture('/regression-Autocomplete/Virtualize');
await page.getByRole('combobox').click();
await takeScreenshot({ testcase, route: '/regression-Autocomplete/Virtualize2' });
await page.hover('[role="option"]');
await takeScreenshot({ testcase, route: '/regression-Autocomplete/Virtualize3' });
await page.click('[role="option"]');
await takeScreenshot({ testcase, route: '/regression-Autocomplete/Virtualize4' });
});
});
});

Expand Down

0 comments on commit 4e26c0b

Please sign in to comment.