From 4e26c0b003eaca35937e8e0df446ac89e37b57f0 Mon Sep 17 00:00:00 2001 From: Diego Andai Date: Fri, 15 Nov 2024 12:32:52 -0300 Subject: [PATCH] [docs][material-ui][Autocomplete] Fix virtualization demo (#44382) --- .../components/autocomplete/Virtualize.js | 6 +- .../components/autocomplete/Virtualize.tsx | 6 +- .../autocomplete/Virtualize.tsx.preview | 15 -- pnpm-lock.yaml | 3 + test/package.json | 1 + .../fixtures/Autocomplete/Virtualize.js | 162 ++++++++++++++++++ test/regressions/index.test.js | 10 ++ 7 files changed, 186 insertions(+), 17 deletions(-) delete mode 100644 docs/data/material/components/autocomplete/Virtualize.tsx.preview create mode 100644 test/regressions/fixtures/Autocomplete/Virtualize.js diff --git a/docs/data/material/components/autocomplete/Virtualize.js b/docs/data/material/components/autocomplete/Virtualize.js index 72c0cfa455bc3e..ca70916330891d 100644 --- a/docs/data/material/components/autocomplete/Virtualize.js +++ b/docs/data/material/components/autocomplete/Virtualize.js @@ -149,7 +149,11 @@ export default function Virtualize() { renderGroup={(params) => params} slots={{ popper: StyledPopper, - listbox: ListboxComponent, + }} + slotProps={{ + listbox: { + component: ListboxComponent, + }, }} /> ); diff --git a/docs/data/material/components/autocomplete/Virtualize.tsx b/docs/data/material/components/autocomplete/Virtualize.tsx index 11440cf0be8ec3..e2d8923bc3d116 100644 --- a/docs/data/material/components/autocomplete/Virtualize.tsx +++ b/docs/data/material/components/autocomplete/Virtualize.tsx @@ -155,7 +155,11 @@ export default function Virtualize() { renderGroup={(params) => params as any} slots={{ popper: StyledPopper, - listbox: ListboxComponent, + }} + slotProps={{ + listbox: { + component: ListboxComponent, + }, }} /> ); diff --git a/docs/data/material/components/autocomplete/Virtualize.tsx.preview b/docs/data/material/components/autocomplete/Virtualize.tsx.preview deleted file mode 100644 index d8b7f0e136c084..00000000000000 --- a/docs/data/material/components/autocomplete/Virtualize.tsx.preview +++ /dev/null @@ -1,15 +0,0 @@ - option[0].toUpperCase()} - renderInput={(params) => } - renderOption={(props, option, state) => - [props, option, state.index] as React.ReactNode - } - renderGroup={(params) => params as any} - slots={{ - popper: StyledPopper, - listbox: ListboxComponent, - }} -/> \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a1c623912a212f..5828da74b8d1d8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2349,6 +2349,9 @@ importers: react-router-dom: specifier: ^6.28.0 version: 6.28.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react-window: + specifier: ^1.8.10 + version: 1.8.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1) sinon: specifier: ^19.0.2 version: 19.0.2 diff --git a/test/package.json b/test/package.json index 7e0ca66fbfd11a..195ad9f99ef456 100644 --- a/test/package.json +++ b/test/package.json @@ -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", diff --git a/test/regressions/fixtures/Autocomplete/Virtualize.js b/test/regressions/fixtures/Autocomplete/Virtualize.js new file mode 100644 index 00000000000000..cc3f1a9064f218 --- /dev/null +++ b/test/regressions/fixtures/Autocomplete/Virtualize.js @@ -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 ( + + {dataSet.group} + + ); + } + + const { key, ...optionProps } = dataSet[0]; + + return ( + + {`#${dataSet[2] + 1} - ${dataSet[1]}`} + + ); +} + +const OuterElementContext = React.createContext({}); + +const OuterElementType = React.forwardRef((props, ref) => { + const outerProps = React.useContext(OuterElementContext); + return
; +}); + +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 ( +
+ + getChildSize(itemData[index])} + overscanCount={5} + itemCount={itemCount} + > + {renderRow} + + +
+ ); +}); + +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 ( +
+ option[0].toUpperCase()} + renderInput={(params) => } + renderOption={(props, option, state) => [props, option, state.index]} + renderGroup={(params) => params} + slots={{ + popper: StyledPopper, + }} + slotProps={{ + listbox: { + component: ListboxComponent, + }, + }} + /> +
+ ); +} diff --git a/test/regressions/index.test.js b/test/regressions/index.test.js index 290b89661556b1..7eb53f2887632d 100644 --- a/test/regressions/index.test.js +++ b/test/regressions/index.test.js @@ -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' }); + }); }); });