@grafana/ui
Version:
Grafana Components Library
1 lines • 9.01 kB
Source Map (JSON)
{"version":3,"file":"Icon.cjs","sources":["../../../../src/components/Icon/Icon.tsx"],"sourcesContent":["import { css, cx } from '@emotion/css';\nimport { useCallback, useState, useRef, memo, forwardRef } from 'react';\nimport SVG from 'react-inlinesvg';\n\nimport { type GrafanaTheme2, isIconName } from '@grafana/data';\n\nimport { useStyles2 } from '../../themes/ThemeContext';\nimport { type IconName, type IconType, type IconSize } from '../../types/icon';\nimport { spin } from '../../utils/keyframes';\n\nimport { getIconPath, getSvgSize } from './utils';\n\nexport interface IconProps extends Omit<React.SVGProps<SVGElement>, 'onLoad' | 'onError' | 'ref'> {\n name: IconName;\n size?: IconSize;\n type?: IconType;\n /**\n * Give your icon a semantic meaning. The icon will be hidden from screen readers, unless this prop or an aria-label is provided.\n */\n title?: string;\n}\n\nconst getIconStyles = (theme: GrafanaTheme2) => {\n return {\n icon: css({\n display: 'inline-block',\n fill: 'currentColor',\n flexShrink: 0,\n label: 'Icon',\n // line-height: 0; is needed for correct icon alignment in Safari\n lineHeight: 0,\n verticalAlign: 'middle',\n }),\n orange: css({\n fill: theme.v1.palette.orange,\n }),\n spin: css({\n [theme.transitions.handleMotion('no-preference', 'reduce')]: {\n animation: `${spin} 2s infinite linear`,\n },\n }),\n };\n};\n\n// The SVG can become 'stuck' if it's changed quickly before the previous icon finished loading.\n// See https://github.com/gilbarbara/react-inlinesvg/issues/247\n// By using the svgPath as the key, we ensure that the component is re-mounted and the new icon is loaded correctly.\nfunction useIconWorkaround(name: IconName) {\n // Buffer name changes while the icon is loading until it stops loading, then apply the most recent name change.\n // We use refs for state and a forceUpdate state to avoid needing to use the buffered name in the happy path\n // of when the icon changes when its not currently loading.\n // We want to avoid needless state updates to keep track of the lifecycle.\n\n const [, setForceUpdate] = useState(0);\n const isLoadingRef = useRef(false);\n const currentNameRef = useRef(name);\n const bufferedNameRef = useRef<IconName | null>(null);\n\n // Decide which name to render THIS render\n let nameToUse = name;\n\n if (isLoadingRef.current && name !== currentNameRef.current) {\n // Currently loading and name changed - buffer it, keep using current\n nameToUse = currentNameRef.current;\n bufferedNameRef.current = name;\n } else if (!isLoadingRef.current && name !== currentNameRef.current) {\n // Not loading - use new name immediately (happy path)\n currentNameRef.current = name;\n bufferedNameRef.current = null;\n isLoadingRef.current = true; // Mark as loading when we accept a new name\n }\n\n const handleLoad = useCallback(() => {\n isLoadingRef.current = false;\n\n // Apply buffered name if one exists\n if (bufferedNameRef.current) {\n currentNameRef.current = bufferedNameRef.current;\n bufferedNameRef.current = null;\n setForceUpdate((n) => n + 1); // Trigger re-render with buffered name\n }\n }, []);\n\n return { nameToUse, handleLoad };\n}\n\n/**\n * Grafana's icon wrapper component.\n *\n * https://developers.grafana.com/ui/latest/index.html?path=/docs/iconography-icon--docs\n */\nexport const Icon = memo(\n forwardRef<SVGElement, IconProps>(\n ({ size = 'md', type = 'default', name: nameProp, className, style, title = '', ...rest }, ref) => {\n const styles = useStyles2(getIconStyles);\n const { nameToUse: name, handleLoad } = useIconWorkaround(nameProp);\n\n if (!isIconName(name)) {\n console.warn('Icon component passed an invalid icon name', name);\n }\n\n // handle the deprecated 'fa fa-spinner'\n const iconName: IconName = name === 'fa fa-spinner' ? 'spinner' : name;\n\n const svgSize = getSvgSize(size);\n const svgHgt = svgSize;\n const svgWid = name.startsWith('gf-bar-align') ? 16 : name.startsWith('gf-interp') ? 30 : svgSize;\n const svgPath = getIconPath(iconName, type);\n\n const composedClassName = cx(\n styles.icon,\n className,\n type === 'mono' ? { [styles.orange]: name === 'favorite' } : '',\n {\n [styles.spin]: iconName === 'spinner',\n }\n );\n\n return (\n // @ts-expect-error react-inlinesvg@4.3.0 return type includes bigint, which isn't in @types/react@18's ReactNode. Remove when we update @types/react.\n <SVG\n data-testid={`icon-${iconName}`}\n aria-hidden={\n rest.tabIndex === undefined &&\n !title &&\n !rest['aria-label'] &&\n !rest['aria-labelledby'] &&\n !rest['aria-describedby']\n }\n onLoad={handleLoad}\n onError={handleLoad}\n innerRef={ref}\n src={svgPath}\n width={svgWid}\n height={svgHgt}\n title={title}\n className={composedClassName}\n style={style}\n // render an empty element with the correct dimensions while loading\n // this prevents content layout shift whilst the icon asynchronously loads\n // which happens even if the icon is in the cache(!)\n loader={\n <svg\n className={cx(\n css({\n width: svgWid,\n height: svgHgt,\n }),\n composedClassName\n )}\n />\n }\n {...rest}\n />\n );\n }\n )\n);\n\nIcon.displayName = 'Icon';\n"],"names":["css","spin","useState","useRef","useCallback","memo","forwardRef","useStyles2","isIconName","getSvgSize","getIconPath","cx","jsx","SVG"],"mappings":"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"}