import type { Component, JSX } from 'solid-js'; import { Plus, Paperclip, Github, Globe, Sparkles, Settings, FileText, Folder, Image, Monitor, MessageCircle, MessageSquare, Search, Mic, AudioLines, X, ChevronDown, ChevronLeft, Pencil, BookOpen, Code, Smile, Share, ArrowLeft, MoreHorizontal, House, Clock, Lock, Box, Briefcase, PanelLeft, Circle, SlidersHorizontal, Workflow, SquarePen, Archive, ArrowUp, Bookmark, Copy, Ellipsis, ExternalLink, Flag, GitBranch, GitPullRequest, ListFilter, RotateCw, } from 'lucide-solid'; type IconComponent = Component<{ class?: string }>; /** Curated name → lucide-solid component map for item icons in kai-menu / * kai-command. Extend here when new named icons are needed. */ const NAMED_ICONS: Record = { plus: Plus, paperclip: Paperclip, github: Github, globe: Globe, sparkles: Sparkles, settings: Settings, 'file-text': FileText, folder: Folder, image: Image, monitor: Monitor, 'message-circle': MessageCircle, 'message-square': MessageSquare, search: Search, mic: Mic, 'audio-lines': AudioLines, x: X, 'chevron-down': ChevronDown, pencil: Pencil, 'book-open': BookOpen, code: Code, smile: Smile, // Header / chrome glyphs. share: Share, 'arrow-left': ArrowLeft, 'more-horizontal': MoreHorizontal, 'chevron-left': ChevronLeft, // App-shell glyphs (sidebar nav, recents, filters). home: House, clock: Clock, lock: Lock, box: Box, briefcase: Briefcase, 'panel-left': PanelLeft, circle: Circle, 'sliders-horizontal': SlidersHorizontal, workflow: Workflow, 'square-pen': SquarePen, // Code-app / dev-tool glyphs (git, filters, list actions). archive: Archive, 'arrow-up': ArrowUp, bookmark: Bookmark, copy: Copy, ellipsis: Ellipsis, 'external-link': ExternalLink, flag: Flag, 'git-branch': GitBranch, 'git-pull-request': GitPullRequest, 'list-filter': ListFilter, 'rotate-cw': RotateCw, }; /** Render an item icon. * * Resolution order: * 1. Known icon name (e.g. `"paperclip"`) → lucide-solid component. * 2. URL / absolute path / data-URI → ``. * 3. Anything else → `` text fallback. * Returns `null` when `icon` is undefined/empty. * * The img and span branches render different markup, so they accept different * class options: `imgClass` for the ``, `spanClass` for the ``. * `class` is a shared fallback. Pass `ariaHidden` to mark the span decorative. */ export function renderIcon( icon: string | undefined, opts?: { class?: string; imgClass?: string; spanClass?: string; ariaHidden?: boolean }, ): JSX.Element { if (!icon) return null; const Named = NAMED_ICONS[icon]; if (Named) { return ; } const isUrl = /^(https?:|\/|data:)/.test(icon); // DEV footgun guard: a kebab/identifier-shaped string that isn't a URL and // isn't a known name is almost certainly a typo'd/unregistered icon (e.g. // `icon="share"` before it was added) — it would silently paint as literal // text. Warn in dev only; emoji/arbitrary text passes through untouched. if (import.meta.env.DEV && !isUrl && /^[a-z][a-z0-9-]*$/.test(icon)) { console.warn( `[kai-icon] unknown icon name "${icon}" — rendering as text. ` + 'Add it to NAMED_ICONS in src/ui/icon.tsx, or pass a URL / an inline SVG via slot="icon".', ); } return isUrl ? : ( {icon} ); }