Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[icons][docs] Virtualize icons svg #43939

Merged
merged 16 commits into from
Oct 3, 2024
100 changes: 74 additions & 26 deletions docs/data/material/components/material-icons/SearchIcons.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import * as mui from '@mui/icons-material';
import { Link } from '@mui/docs/Link';
import { useTranslate } from '@mui/docs/i18n';
import useQueryParameterState from 'docs/src/modules/utils/useQueryParameterState';

// For Debugging
// import Menu from '@mui/icons-material/Menu';
// import MenuOutlined from '@mui/icons-material/MenuOutlined';
Expand Down Expand Up @@ -95,6 +96,8 @@ function selectNode(node) {

const iconWidth = 35;

const SVG_ICON_CLASS = 'svg-icon';

const StyledIcon = styled('span')(({ theme }) => ({
display: 'inline-flex',
flexDirection: 'column',
Expand All @@ -108,23 +111,24 @@ const StyledIcon = styled('span')(({ theme }) => ({
textAlign: 'center',
width: `calc(${iconWidth}px + ${theme.spacing(2)} * 2 + 2px)`,
},
}));

const StyledSvgIcon = styled(SvgIcon)(({ theme }) => ({
boxSizing: 'content-box',
cursor: 'pointer',
color: theme.palette.text.primary,
border: '1px solid transparent',
fontSize: iconWidth,
borderRadius: '12px',
transition: theme.transitions.create(['background-color', 'box-shadow'], {
duration: theme.transitions.duration.shortest,
}),
padding: theme.spacing(2),
margin: theme.spacing(0.5, 0),
'&:hover': {
backgroundColor: theme.palette.background.default,
borderColor: theme.palette.primary.light,
[`& .${SVG_ICON_CLASS}`]: {
width: iconWidth,
height: iconWidth,
boxSizing: 'content-box',
cursor: 'pointer',
color: theme.palette.text.primary,
border: '1px solid transparent',
fontSize: iconWidth,
borderRadius: '12px',
transition: theme.transitions.create(['background-color', 'box-shadow'], {
duration: theme.transitions.duration.shortest,
}),
padding: theme.spacing(2),
margin: theme.spacing(0.5, 0),
'&:hover': {
backgroundColor: theme.palette.background.default,
borderColor: theme.palette.primary.light,
},
},
}));

Expand All @@ -143,20 +147,58 @@ function handleLabelClick(event) {
selectNode(event.currentTarget);
}

function isElmVisible(elm, margin = 0) {
const rect = elm.getBoundingClientRect();
return rect.bottom >= -margin && rect.top <= window.innerHeight + margin;
}

function Icon(props) {
const { icon, onOpenClick } = props;
const { icon, onOpenClick, initiallyVisible = false } = props;

const rootRef = React.useRef(null);
const [isVisible, setIsVisible] = React.useState(initiallyVisible);

// Virtualize the icons to reduce page size and React rendering time.
// Only render the icons after they become visible in the viewport.
React.useEffect(() => {
const margin = 200;
Copy link
Member

@oliviertassinari oliviertassinari Sep 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

margin to hoist?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kept it here to prevent it from being used for anything other than virtualization. Separation of concerns.

const root = /** @type {SVGElement} */ (rootRef.current);
if (initiallyVisible || isElmVisible(root, margin)) {
setIsVisible(true);
return () => {};
}
const observer = new IntersectionObserver(
(entries) => {
if (isElmVisible(entries[0].target, margin)) {
setIsVisible(true);
}
},
{ rootMargin: `${margin}px 0px` },
);
observer.observe(root);
return () => {
observer.disconnect();
};
}, [initiallyVisible]);

/* eslint-disable jsx-a11y/click-events-have-key-events */
return (
<StyledIcon
key={icon.importName}
ref={rootRef}
onClick={Math.random() < 0.1 ? handleIconClick(icon) : null}
>
<StyledSvgIcon
component={icon.Component}
tabIndex={-1}
onClick={onOpenClick}
title={icon.importName}
/>
{isVisible ? (
oliviertassinari marked this conversation as resolved.
Show resolved Hide resolved
<SvgIcon
component={icon.Component}
className={SVG_ICON_CLASS}
tabIndex={-1}
onClick={onOpenClick}
title={icon.importName}
/>
) : (
<div className={SVG_ICON_CLASS} />
)}
{/* eslint-disable-next-line jsx-a11y/no-static-element-interactions -- TODO: a11y */}
<div onClick={handleLabelClick}>{icon.importName}</div>
{/* eslint-enable jsx-a11y/click-events-have-key-events */}
Expand All @@ -169,8 +211,14 @@ const Icons = React.memo(function Icons(props) {

return (
<div>
{icons.map((icon) => (
<Icon key={icon.importName} icon={icon} onOpenClick={handleOpenClick} />
{icons.map((icon, i) => (
<Icon
key={icon.importName}
icon={icon}
onOpenClick={handleOpenClick}
// Render the first 50 icons immediately as they would be visible on page load
initiallyVisible={i < 50}
/>
))}
</div>
);
Expand Down