phanpy/src/components/icon.jsx

128 wiersze
3.0 KiB
React

2024-01-03 01:49:48 +00:00
import moize from 'moize';
2023-12-23 10:05:30 +00:00
import { useEffect, useRef, useState } from 'preact/hooks';
2025-08-24 02:28:14 +00:00
import escapeHTML from '../utils/escape-html';
2024-01-20 02:25:47 +00:00
import { ICONS } from './ICONS';
2022-12-10 09:14:48 +00:00
const SIZES = {
xs: 8,
2022-12-10 09:14:48 +00:00
s: 12,
m: 16,
l: 20,
xl: 24,
xxl: 32,
};
2023-12-23 04:14:11 +00:00
const ICONDATA = {};
2024-01-03 01:49:48 +00:00
// Memoize the dangerouslySetInnerHTML of the SVGs
2025-08-24 02:28:14 +00:00
const INVALID_ID_CHARS_REGEX = /[^a-zA-Z0-9]/g;
2024-01-03 01:49:48 +00:00
const SVGICon = moize(
2025-08-24 02:28:14 +00:00
function ({ icon, title, width, height, body, rotate, flip }) {
const titleID = title?.replace(INVALID_ID_CHARS_REGEX, '-') || '';
const id = `icon-${icon}-${titleID}`;
const html = title
? `<title id="${id}">${escapeHTML(title)}</title>${body}`
: body;
2024-01-03 01:49:48 +00:00
return (
<svg
2025-08-24 02:28:14 +00:00
role={title ? 'img' : 'presentation'}
aria-labelledby={id}
2024-01-03 01:49:48 +00:00
viewBox={`0 0 ${width} ${height}`}
2025-08-24 02:28:14 +00:00
dangerouslySetInnerHTML={{ __html: html }}
2024-01-03 01:49:48 +00:00
style={{
transform: `${rotate ? `rotate(${rotate})` : ''} ${
flip ? `scaleX(-1)` : ''
}`,
}}
/>
);
},
{
isShallowEqual: true,
maxSize: Object.keys(ICONS).length,
2024-01-25 16:28:03 +00:00
matchesArg: (cacheKeyArg, keyArg) =>
2025-08-24 02:28:14 +00:00
cacheKeyArg.icon === keyArg.icon &&
cacheKeyArg.title === keyArg.title &&
cacheKeyArg.body === keyArg.body,
2024-01-03 01:49:48 +00:00
},
);
2023-03-09 13:51:50 +00:00
function Icon({
icon,
size = 'm',
alt,
title,
class: className = '',
style = {},
}) {
2022-12-14 13:48:17 +00:00
if (!icon) return null;
2022-12-10 09:14:48 +00:00
const iconSize = SIZES[size];
let iconBlock = ICONS[icon];
2024-01-04 10:55:21 +00:00
if (!iconBlock) {
console.warn(`Icon ${icon} not found`);
return null;
}
2024-08-04 05:32:30 +00:00
let rotate,
flip,
rtl = false;
if (Array.isArray(iconBlock)) {
[iconBlock, rotate, flip] = iconBlock;
2024-08-04 05:32:30 +00:00
} else if (typeof iconBlock === 'object') {
({ rotate, flip, rtl } = iconBlock);
iconBlock = iconBlock.module;
}
2023-12-23 04:14:11 +00:00
const [iconData, setIconData] = useState(ICONDATA[icon]);
2023-12-23 10:05:30 +00:00
const currentIcon = useRef(icon);
2023-12-23 04:14:11 +00:00
useEffect(() => {
2023-12-23 10:05:30 +00:00
if (iconData && currentIcon.current === icon) return;
2023-12-23 04:14:11 +00:00
(async () => {
const iconB = await iconBlock();
setIconData(iconB.default);
ICONDATA[icon] = iconB.default;
})();
2023-12-23 10:05:30 +00:00
currentIcon.current = icon;
}, [icon]);
2022-12-10 09:14:48 +00:00
return (
2023-09-29 13:02:09 +00:00
<span
2024-08-04 05:32:30 +00:00
class={`icon ${className} ${rtl ? 'rtl-flip' : ''}`}
2022-12-10 09:14:48 +00:00
style={{
width: `${iconSize}px`,
height: `${iconSize}px`,
2023-03-09 13:51:50 +00:00
...style,
2022-12-10 09:14:48 +00:00
}}
2024-08-04 05:32:30 +00:00
data-icon={icon}
2022-12-10 09:14:48 +00:00
>
{iconData && (
2024-01-03 01:49:48 +00:00
// <svg
// width={iconSize}
// height={iconSize}
// viewBox={`0 0 ${iconData.width} ${iconData.height}`}
// dangerouslySetInnerHTML={{ __html: iconData.body }}
// style={{
// transform: `${rotate ? `rotate(${rotate})` : ''} ${
// flip ? `scaleX(-1)` : ''
// }`,
// }}
// />
<SVGICon
2024-01-25 13:28:41 +00:00
icon={icon}
2025-08-24 02:28:14 +00:00
title={title || alt}
2024-01-03 01:49:48 +00:00
width={iconData.width}
height={iconData.height}
body={iconData.body}
rotate={rotate}
flip={flip}
/>
)}
2023-09-29 13:02:09 +00:00
</span>
2022-12-10 09:14:48 +00:00
);
2022-12-16 05:27:04 +00:00
}
export default Icon;