// src/layout/TabIcon.tsx import { useRef, useEffect } from 'react' import { getIcon, hasIcon } from '../plugin/ui/icon' interface TabIconProps { iconName: string fallbackText: string } /** * Renders a Lucide icon SVG from the icon registry via ref-based DOM mounting. * Falls back to the first letter of fallbackText when the icon name is unknown. * * Uses the same ref + useEffect pattern as RibbonIconElement (src/layout/Ribbon.tsx:9-17): * getIcon() is called inside useEffect with the iconName string as the dependency, * not the SVGElement return value (which is a new object on every call). * * The render-time branch decision uses hasIcon() — a pure registry lookup that * does NOT allocate an SVGElement — so no throwaway objects are created per render. */ export function TabIcon({ iconName, fallbackText }: TabIconProps) { const ref = useRef(null) const iconExists = hasIcon(iconName) useEffect(() => { const el = ref.current if (!el) return const svg = getIcon(iconName) if (svg) { el.replaceChildren(svg) } }, [iconName]) if (!iconExists) { return {fallbackText.charAt(0)} } return }