import * as React from 'react' import * as LucideIcons from 'lucide-react' import type { SAILSizeExtended, SAILColorInput } from '../../types/sail' import { resolveColorClass } from '../../utils/colorResolver' type LinkStyle = "INLINE" | "STANDALONE" export interface IconProps { /** The key of the icon to display */ icon: string /** Alternative text for accessibility */ altText?: string /** Text to display on mouseover */ caption?: string /** Icon size */ size?: SAILSizeExtended /** Icon color - semantic color, palette token (e.g. TEAL_700), or hex value */ color?: SAILColorInput /** Link behavior when icon is clicked */ link?: () => void /** How the link is underlined */ linkStyle?: LinkStyle /** Whether to show this component */ showWhen?: boolean } /** * Icon component for use within Rich Text Display Field * Displays icons with various formatting options */ export const Icon: React.FC = ({ icon, altText, caption, size = "STANDARD", color = "STANDARD", link, linkStyle = "INLINE", // Note: linkStyle not implemented yet for icons showWhen = true }) => { // Suppress unused variable warning - linkStyle will be implemented later void linkStyle if (!showWhen || !icon) return null // Size mappings - icons use similar sizing to text const sizeMap: Record = { SMALL: 12, STANDARD: 16, MEDIUM: 20, MEDIUM_PLUS: 24, LARGE: 28, LARGE_PLUS: 32, EXTRA_LARGE: 40 } // Color mappings const colorClass = color.startsWith('#') ? '' : (resolveColorClass(color, 'text') || 'text-gray-900') // Build classes array const classes = [ 'inline-block', colorClass, // Link styles link && 'cursor-pointer', link && 'hover:opacity-75' ].filter(Boolean).join(' ') // Inline styles for hex colors const inlineStyles = color.startsWith('#') ? { color } : undefined // Map any Lucide icon name directly, with SAIL compatibility fallbacks const getIconComponent = (iconName: string) => { // First try direct Lucide icon name (kebab-case or PascalCase) const kebabToPascal = (str: string) => str.split('-').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join('') const pascalIconName = kebabToPascal(iconName) if (pascalIconName in LucideIcons) { return LucideIcons[pascalIconName as keyof typeof LucideIcons] as React.ComponentType<{ size?: number; className?: string; style?: React.CSSProperties }> } // Also try direct case-insensitive lookup const directIconName = iconName.charAt(0).toUpperCase() + iconName.slice(1).toLowerCase() if (directIconName in LucideIcons) { return LucideIcons[directIconName as keyof typeof LucideIcons] as React.ComponentType<{ size?: number; className?: string; style?: React.CSSProperties }> } // Fallback to SAIL compatibility mapping const sailIconMap: Record = { 'USER': 'User', 'PHONE': 'Phone', 'BUILDING-O': 'Building', 'HOME': 'Home', 'CHECK-SQUARE-O': 'SquareCheck', 'FILE-TEXT-O': 'FileText', 'PICTURE-O': 'Image', 'HEADPHONES': 'Headphones', 'VIDEO-CAMERA': 'Video', 'WRENCH': 'Wrench', 'EXCLAMATION': 'AlertTriangle', 'TICKET': 'Ticket', 'ARROW-RIGHT': 'ArrowRight', 'CHECK': 'Check' } const lucideIconName = sailIconMap[iconName.toUpperCase()] if (lucideIconName && lucideIconName in LucideIcons) { return LucideIcons[lucideIconName] as React.ComponentType<{ size?: number; className?: string; style?: React.CSSProperties }> } // Fallback to a generic icon return LucideIcons.Circle as React.ComponentType<{ size?: number; className?: string; style?: React.CSSProperties }> } const IconComponent = getIconComponent(icon) const iconElement = ( ) // If there's a link, wrap in clickable element if (link) { return ( { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault() link() } }} > {iconElement} ) } return iconElement }