import { createElement, type CSSProperties } from 'react'; import * as AntIcons from '@ant-design/icons'; import { resolveAntdIconName } from './iconMap'; type AntIconLike = React.ComponentType<{ className?: string; style?: CSSProperties; onClick?: React.MouseEventHandler; }>; const ANT_ICONS = AntIcons as unknown as Record; export interface IconProps { /** * Icon name. Accepts either: * - A backend / Iconify-style name (e.g. `"ep:tools"`, `"fa:edit"`, * `"tabler:ai"`) — resolved via `iconMap`. * - An Ant Design icon component name (e.g. `"SettingOutlined"`, * `"WarningFilled"`) — used directly. */ icon?: string | null; /** CSS color, applied as `style.color`. */ color?: string; /** Size in px when given a number, or any CSS length when given a string. Defaults to 16. */ size?: number | string; className?: string; style?: CSSProperties; onClick?: React.MouseEventHandler; } /** * Generic icon renderer that mirrors the Vue project's `Icon.vue` API * (`icon` / `color` / `size` / `class`) on top of Ant Design 6 icon * components. * * Backend menu icons (Iconify-style: `ep:*` / `fa:*` / `simple-icons:*`) are * translated through {@link iconMap}, so feature pages can write * `` directly. Returns `null` when the name can't be * resolved — callers don't need to pre-check, and the layout doesn't break. */ const Icon: React.FC = ({ icon, color, size = 16, className, style, onClick, }) => { const antdName = resolveAntdIconName(icon); if (!antdName) return null; const Component = ANT_ICONS[antdName]; if (!Component) return null; const fontSize = typeof size === 'number' ? `${size}px` : size; return createElement(Component, { className, style: { fontSize, color, ...style }, onClick, }); }; export default Icon;