Icon: Memoize rendered icons for situations where duplicate icons are rendered (#109883)
This commit is contained in:
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user