Skip to content

Commit

Permalink
[docs][material-ui] Fix Material Icon search lag and other improvemen…
Browse files Browse the repository at this point in the history
…ts (#41330)

Co-authored-by: MUI bot <2109932+Janpot@users.noreply.github.com>
Co-authored-by: ZeeshanTamboli <zeeshan.tamboli@gmail.com>
  • Loading branch information
3 people authored Aug 9, 2024
1 parent 0137be7 commit 5ecb9cc
Showing 1 changed file with 104 additions and 109 deletions.
213 changes: 104 additions & 109 deletions docs/data/material/components/material-icons/SearchIcons.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
import * as React from 'react';
import { VirtuosoGrid } from 'react-virtuoso';
import { styled } from '@mui/material/styles';
import MuiPaper from '@mui/material/Paper';
import copy from 'clipboard-copy';
import InputBase from '@mui/material/InputBase';
import Typography from '@mui/material/Typography';
import PropTypes from 'prop-types';
import { debounce } from '@mui/material/utils';
import Grid from '@mui/material/Grid';
import Dialog from '@mui/material/Dialog';
import DialogActions from '@mui/material/DialogActions';
import DialogContent from '@mui/material/DialogContent';
import DialogTitle from '@mui/material/DialogTitle';
import CircularProgress from '@mui/material/CircularProgress';
import InputAdornment from '@mui/material/InputAdornment';
import IconButton from '@mui/material/IconButton';
import Tooltip from '@mui/material/Tooltip';
import Button from '@mui/material/Button';
import * as flexsearch from 'flexsearch';
import flexsearch from 'flexsearch';
import SearchIcon from '@mui/icons-material/Search';
import FormControlLabel from '@mui/material/FormControlLabel';
import RadioGroup from '@mui/material/RadioGroup';
Expand Down Expand Up @@ -48,9 +50,7 @@ import useQueryParameterState from 'docs/src/modules/utils/useQueryParameterStat
import { HighlightedCode } from '@mui/docs/HighlightedCode';
import synonyms from './synonyms';

const FlexSearchIndex = flexsearch.default.Index;

const UPDATE_SEARCH_INDEX_WAIT_MS = 220;
const FlexSearchIndex = flexsearch.Index;

// const mui = {
// ExitToApp,
Expand Down Expand Up @@ -129,55 +129,56 @@ const StyledSvgIcon = styled(SvgIcon)(({ theme }) => ({
},
}));

const Icons = React.memo(function Icons(props) {
const { icons, handleOpenClick } = props;

const handleIconClick = (icon) => () => {
if (Math.random() < 0.1) {
window.gtag('event', 'material-icons', {
eventAction: 'click',
eventLabel: icon.name,
});
window.gtag('event', 'material-icons-theme', {
eventAction: 'click',
eventLabel: icon.theme,
});
}
};

const handleLabelClick = (event) => {
selectNode(event.currentTarget);
};

const ListWrapper = React.forwardRef(({ style, children, ...props }, ref) => {
return (
<div>
{icons.map((icon) => {
/* eslint-disable jsx-a11y/click-events-have-key-events */
return (
<StyledIcon key={icon.importName} onClick={handleIconClick(icon)}>
<StyledSvgIcon
component={icon.Component}
fontSize="large"
tabIndex={-1}
onClick={handleOpenClick}
title={icon.importName}
/>
<div>
{/* eslint-disable-next-line jsx-a11y/no-static-element-interactions -- TODO: a11y */}
<div onClick={handleLabelClick}>{icon.importName}</div>
</div>
{/* eslint-enable jsx-a11y/click-events-have-key-events */}
</StyledIcon>
);
})}
<div
ref={ref}
{...props}
style={{ display: 'flex', flexWrap: 'wrap', ...style }}
>
{children}
</div>
);
});

Icons.propTypes = {
handleOpenClick: PropTypes.func.isRequired,
icons: PropTypes.array.isRequired,
};
function Icon(handleOpenClick) {
return function itemContent(_, icon) {
const handleIconClick = () => {
if (Math.random() < 0.1) {
window.gtag('event', 'material-icons', {
eventAction: 'click',
eventLabel: icon.name,
});
window.gtag('event', 'material-icons-theme', {
eventAction: 'click',
eventLabel: icon.theme,
});
}
};

const handleLabelClick = (event) => {
selectNode(event.currentTarget);
};

return (
/* eslint-disable jsx-a11y/click-events-have-key-events */
<StyledIcon key={icon.importName} onClick={handleIconClick}>
<StyledSvgIcon
component={icon.Component}
fontSize="large"
tabIndex={-1}
onClick={handleOpenClick}
title={icon.importName}
/>
<div>
{/* eslint-disable-next-line jsx-a11y/no-static-element-interactions -- TODO: a11y */}
<div onClick={handleLabelClick}>{icon.importName}</div>
</div>
{/* eslint-enable jsx-a11y/click-events-have-key-events */}
</StyledIcon>
);
};
}

const ImportLink = styled(Link)(({ theme }) => ({
textAlign: 'right',
Expand Down Expand Up @@ -438,14 +439,7 @@ DialogDetails.propTypes = {
selectedIcon: PropTypes.object,
};

const Form = styled('form')({
position: 'sticky',
top: 80,
});

const Paper = styled(MuiPaper)(({ theme }) => ({
position: 'sticky',
top: 80,
display: 'flex',
alignItems: 'center',
marginBottom: theme.spacing(2),
Expand Down Expand Up @@ -516,7 +510,6 @@ function useLatest(value) {
}

export default function SearchIcons() {
const [keys, setKeys] = React.useState(null);
const [theme, setTheme] = useQueryParameterState('theme', 'Filled');
const [selectedIcon, setSelectedIcon] = useQueryParameterState('selected', '');
const [query, setQuery] = useQueryParameterState('query', '');
Expand All @@ -532,42 +525,30 @@ export default function SearchIcons() {
setSelectedIcon('');
}, [setSelectedIcon]);

const updateSearchResults = React.useMemo(
() =>
debounce((value) => {
if (value === '') {
setKeys(null);
} else {
searchIndex.searchAsync(value, { limit: 3000 }).then((results) => {
setKeys(results);

// Keep track of the no results so we can add synonyms in the future.
if (value.length >= 4 && results.length === 0) {
window.gtag('event', 'material-icons', {
eventAction: 'no-results',
eventLabel: value,
});
}
});
}
}, UPDATE_SEARCH_INDEX_WAIT_MS),
[],
);
const deferredQuery = React.useDeferredValue(query);
const deferredTheme = React.useDeferredValue(theme);

const isPending = query !== deferredQuery || theme !== deferredTheme;

const icons = React.useMemo(() => {
const keys =
deferredQuery === ''
? null
: searchIndex.search(deferredQuery, { limit: 3000 });
return (keys === null ? allIcons : keys.map((key) => allIconsMap[key])).filter(
(icon) => deferredTheme === icon.theme,
);
}, [deferredQuery, deferredTheme]);

React.useEffect(() => {
updateSearchResults(query);
return () => {
updateSearchResults.clear();
};
}, [query, updateSearchResults]);

const icons = React.useMemo(
() =>
(keys === null ? allIcons : keys.map((key) => allIconsMap[key])).filter(
(icon) => theme === icon.theme,
),
[theme, keys],
);
// Keep track of the no results so we can add synonyms in the future.
if (deferredQuery.length >= 4 && icons.length === 0) {
window.gtag('event', 'material-icons', {
eventAction: 'no-results',
eventLabel: deferredQuery,
});
}
}, [deferredQuery, icons.length]);

const dialogSelectedIcon = useLatest(
selectedIcon ? allIconsMap[selectedIcon] : null,
Expand All @@ -576,29 +557,28 @@ export default function SearchIcons() {
return (
<Grid container sx={{ minHeight: 500 }}>
<Grid item xs={12} sm={3}>
<Form>
<Typography sx={{ fontWeight: 500, mb: 1 }}>Filter the style</Typography>
<RadioGroup>
<form>
<Typography fontWeight={500} sx={{ mb: 1 }}>
Filter the style
</Typography>
<RadioGroup
value={theme}
onChange={(event) => setTheme(event.target.value)}
>
{['Filled', 'Outlined', 'Rounded', 'Two tone', 'Sharp'].map(
(currentTheme) => {
return (
<FormControlLabel
key={currentTheme}
control={
<Radio
size="small"
checked={theme === currentTheme}
onChange={() => setTheme(currentTheme)}
value={currentTheme}
/>
}
value={currentTheme}
control={<Radio size="small" />}
label={currentTheme}
/>
);
},
)}
</RadioGroup>
</Form>
</form>
</Grid>
<Grid item xs={12} sm={9}>
<Paper>
Expand All @@ -611,18 +591,33 @@ export default function SearchIcons() {
onChange={(event) => setQuery(event.target.value)}
placeholder="Search icons…"
inputProps={{ 'aria-label': 'search icons' }}
endAdornment={
isPending ? (
<InputAdornment position="end">
<CircularProgress size={16} sx={{ mr: 2 }} />
</InputAdornment>
) : null
}
/>
</Paper>
<Typography sx={{ mb: 1 }}>{`${formatNumber(
icons.length,
)} matching results`}</Typography>
<Icons icons={icons} handleOpenClick={handleOpenClick} />
<VirtuosoGrid
style={{ height: 500 }}
data={icons}
components={{ List: ListWrapper }}
itemContent={Icon(handleOpenClick)}
/>
</Grid>
<DialogDetails
open={!!selectedIcon}
selectedIcon={dialogSelectedIcon}
handleClose={handleClose}
/>
{/* Temporary fix for Dialog not closing sometimes and Backdrop stuck at opacity 0 (see issue https://github.com/mui/material-ui/issues/32286). One disadvantage is that the closing animation is not applied. */}
{selectedIcon ? (
<DialogDetails
open={!!selectedIcon}
selectedIcon={dialogSelectedIcon}
handleClose={handleClose}
/>
) : null}
</Grid>
);
}

0 comments on commit 5ecb9cc

Please sign in to comment.