Icon: Memoize rendered icons for situations where duplicate icons are rendered (#109883)

This commit is contained in:
Paul Marbach
2025-08-20 16:52:34 +00:00
committed by GitHub
parent 629cb1dee0
commit 24a3a4c7dc
@@ -42,65 +42,67 @@ const getIconStyles = (theme: GrafanaTheme2) => {
};
};
export const Icon = React.forwardRef<SVGElement, IconProps>(
({ size = 'md', type = 'default', name, className, style, title = '', ...rest }, ref) => {
const styles = useStyles2(getIconStyles);
export const Icon = React.memo(
React.forwardRef<SVGElement, IconProps>(
({ size = 'md', type = 'default', name, className, style, title = '', ...rest }, ref) => {
const styles = useStyles2(getIconStyles);
if (!isIconName(name)) {
console.warn('Icon component passed an invalid icon name', name);
}
// handle the deprecated 'fa fa-spinner'
const iconName: IconName = name === 'fa fa-spinner' ? 'spinner' : name;
const svgSize = getSvgSize(size);
const svgHgt = svgSize;
const svgWid = name.startsWith('gf-bar-align') ? 16 : name.startsWith('gf-interp') ? 30 : svgSize;
const svgPath = getIconPath(iconName, type);
const composedClassName = cx(
styles.icon,
className,
type === 'mono' ? { [styles.orange]: name === 'favorite' } : '',
{
[styles.spin]: iconName === 'spinner',
if (!isIconName(name)) {
console.warn('Icon component passed an invalid icon name', name);
}
);
return (
<SVG
aria-hidden={
rest.tabIndex === undefined &&
!title &&
!rest['aria-label'] &&
!rest['aria-labelledby'] &&
!rest['aria-describedby']
// handle the deprecated 'fa fa-spinner'
const iconName: IconName = name === 'fa fa-spinner' ? 'spinner' : name;
const svgSize = getSvgSize(size);
const svgHgt = svgSize;
const svgWid = name.startsWith('gf-bar-align') ? 16 : name.startsWith('gf-interp') ? 30 : svgSize;
const svgPath = getIconPath(iconName, type);
const composedClassName = cx(
styles.icon,
className,
type === 'mono' ? { [styles.orange]: name === 'favorite' } : '',
{
[styles.spin]: iconName === 'spinner',
}
innerRef={ref}
src={svgPath}
width={svgWid}
height={svgHgt}
title={title}
className={composedClassName}
style={style}
// render an empty div with the correct dimensions while loading
// this prevents content layout shift whilst the icon asynchronously loads
// which happens even if the icon is in the cache(!)
loader={
<div
className={cx(
css({
width: svgWid,
height: svgHgt,
}),
composedClassName
)}
/>
}
{...rest}
/>
);
}
);
return (
<SVG
aria-hidden={
rest.tabIndex === undefined &&
!title &&
!rest['aria-label'] &&
!rest['aria-labelledby'] &&
!rest['aria-describedby']
}
innerRef={ref}
src={svgPath}
width={svgWid}
height={svgHgt}
title={title}
className={composedClassName}
style={style}
// render an empty div with the correct dimensions while loading
// this prevents content layout shift whilst the icon asynchronously loads
// which happens even if the icon is in the cache(!)
loader={
<div
className={cx(
css({
width: svgWid,
height: svgHgt,
}),
composedClassName
)}
/>
}
{...rest}
/>
);
}
)
);
Icon.displayName = 'Icon';