{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "slash-command-dropdown",
  "type": "registry:ui",
  "title": "Slash Command Dropdown",
  "description": "A slash command dropdown for selecting tools and actions with categorization and search.",
  "registryDependencies": [
    "icons"
  ],
  "files": [
    {
      "path": "registry/new-york/ui/slash-command-dropdown.tsx",
      "content": "\"use client\";\n\nimport type React from \"react\";\nimport { useEffect, useMemo, useRef } from \"react\";\nimport {\n\tCancel01Icon,\n\tHugeiconsIcon,\n\tTag01Icon,\n\tToolsIcon,\n} from \"@/components/icons\";\n\nimport { cn } from \"@/lib/utils\";\nimport { formatToolName, getToolCategoryIcon } from \"@/lib/utils/tool-icons\";\n\nexport interface Tool {\n\t/** Unique tool identifier */\n\tname: string;\n\t/** Category for grouping tools */\n\tcategory: string;\n\t/** Description shown below tool name */\n\tdescription?: string;\n\t/** Custom icon (defaults to category icon) */\n\ticon?: React.ReactNode;\n}\n\nexport interface SlashCommandMatch {\n\ttool: Tool;\n\tscore: number;\n}\n\ninterface SlashCommandDropdownProps {\n\t/** List of tools to display */\n\tmatches: SlashCommandMatch[];\n\t/** Currently selected tool index */\n\tselectedIndex: number;\n\t/** Callback when a tool is selected */\n\tonSelect: (tool: SlashCommandMatch) => void;\n\t/** Callback when dropdown is closed */\n\tonClose: () => void;\n\t/** Position config for the dropdown */\n\tposition: { top?: number; bottom?: number; left: number; width?: number };\n\t/** Whether the dropdown is visible */\n\tisVisible: boolean;\n\t/** If opened via button (shows header) */\n\topenedViaButton?: boolean;\n\t/** Currently selected category filter */\n\tselectedCategory?: string;\n\t/** Available categories */\n\tcategories?: string[];\n\t/** Callback when category changes */\n\tonCategoryChange?: (category: string) => void;\n\t/** Additional CSS classes */\n\tclassName?: string;\n\t/** Additional inline styles */\n\tstyle?: React.CSSProperties;\n}\n\n// Default icon size for consistency\nconst ICON_SIZE = 20;\nconst CATEGORY_ICON_SIZE = 16;\n\n/**\n * Get a default icon for a tool based on its category\n * Always returns an icon - no tool should be without one\n */\nconst getDefaultToolIcon = (\n\ttool: Tool,\n\tsize: number = ICON_SIZE,\n): React.ReactNode => {\n\t// If tool has custom icon, use it\n\tif (tool.icon) return tool.icon;\n\n\t// Try to get category icon\n\tconst categoryIcon = getToolCategoryIcon(tool.category, {\n\t\tshowBackground: false,\n\t\twidth: size,\n\t\theight: size,\n\t});\n\n\t// If category icon exists, use it\n\tif (categoryIcon) return categoryIcon;\n\n\treturn (\n\t\t<HugeiconsIcon icon={ToolsIcon} size={size} className=\"text-zinc-400\" />\n\t);\n};\n\n/**\n * Get icon for a category tab\n */\nconst getCategoryTabIcon = (category: string): React.ReactNode => {\n\tif (category === \"all\") {\n\t\treturn (\n\t\t\t<HugeiconsIcon\n\t\t\t\ticon={Tag01Icon}\n\t\t\t\tsize={CATEGORY_ICON_SIZE}\n\t\t\t\tclassName=\"text-current\"\n\t\t\t/>\n\t\t);\n\t}\n\n\tconst icon = getToolCategoryIcon(category, {\n\t\tshowBackground: false,\n\t\twidth: CATEGORY_ICON_SIZE,\n\t\theight: CATEGORY_ICON_SIZE,\n\t});\n\n\t// Fallback for categories without icons\n\treturn (\n\t\ticon || (\n\t\t\t<HugeiconsIcon\n\t\t\t\ticon={ToolsIcon}\n\t\t\t\tsize={CATEGORY_ICON_SIZE}\n\t\t\t\tclassName=\"text-current\"\n\t\t\t/>\n\t\t)\n\t);\n};\n\nexport const SlashCommandDropdown: React.FC<SlashCommandDropdownProps> = ({\n\tmatches,\n\tselectedIndex,\n\tonSelect,\n\tonClose,\n\tposition,\n\tisVisible,\n\topenedViaButton = false,\n\tselectedCategory = \"all\",\n\tcategories = [],\n\tonCategoryChange,\n\tclassName,\n\tstyle,\n}) => {\n\tconst dropdownRef = useRef<HTMLDivElement>(null);\n\tconst scrollContainerRef = useRef<HTMLDivElement>(null);\n\n\t// Focus the dropdown when it becomes visible (only when opened via button)\n\tuseEffect(() => {\n\t\tif (isVisible && openedViaButton && dropdownRef.current) {\n\t\t\trequestAnimationFrame(() => {\n\t\t\t\tdropdownRef.current?.focus();\n\t\t\t});\n\t\t}\n\t}, [isVisible, openedViaButton]);\n\n\t// Get unique categories from matches if not provided\n\tconst computedCategories = useMemo(() => {\n\t\tif (categories && categories.length > 0) {\n\t\t\treturn categories;\n\t\t}\n\t\tconst uniqueCategories = Array.from(\n\t\t\tnew Set(matches.map((match) => match.tool.category)),\n\t\t);\n\t\treturn [\"all\", ...uniqueCategories.sort()];\n\t}, [matches, categories]);\n\n\t// Filter matches based on selected category\n\tconst filteredMatches = useMemo(() => {\n\t\tif (selectedCategory === \"all\") {\n\t\t\treturn matches;\n\t\t}\n\t\treturn matches.filter((match) => match.tool.category === selectedCategory);\n\t}, [matches, selectedCategory]);\n\n\t// Scroll to selected item when selectedIndex changes\n\tuseEffect(() => {\n\t\tif (selectedIndex >= 0 && selectedIndex < filteredMatches.length) {\n\t\t\tconst selectedElement = scrollContainerRef.current?.querySelector(\n\t\t\t\t`[data-index=\"${selectedIndex}\"]`,\n\t\t\t);\n\t\t\tif (selectedElement) {\n\t\t\t\tselectedElement.scrollIntoView({\n\t\t\t\t\tbehavior: \"smooth\",\n\t\t\t\t\tblock: \"nearest\",\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}, [selectedIndex, filteredMatches.length]);\n\n\tif (!isVisible || matches.length === 0) return null;\n\n\treturn (\n\t\t<div\n\t\t\tref={dropdownRef}\n\t\t\tclassName={cn(\n\t\t\t\t// Base styles\n\t\t\t\t\"fixed z-[200] overflow-hidden rounded-2xl\",\n\t\t\t\t// Light mode support\n\t\t\t\t\"border border-zinc-200 bg-white/95 dark:border-zinc-800 dark:bg-zinc-900/95\",\n\t\t\t\t\"backdrop-blur-xl shadow-2xl\",\n\t\t\t\t// Animation\n\t\t\t\t\"animate-in fade-in-0 slide-in-from-bottom-2 duration-200\",\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t\tstyle={{\n\t\t\t\t...(position.top !== undefined && { top: 0, height: position.top }),\n\t\t\t\t...(position.bottom !== undefined && {\n\t\t\t\t\tbottom: `calc(100vh - ${position.bottom - 8}px)`,\n\t\t\t\t\tmaxHeight: Math.min(position.bottom - 16, 400),\n\t\t\t\t}),\n\t\t\t\tleft: position.left,\n\t\t\t\twidth: position.width,\n\t\t\t\t...style,\n\t\t\t}}\n\t\t\ttabIndex={-1}\n\t\t>\n\t\t\t{/* Header section - Only show when opened via button */}\n\t\t\t{openedViaButton && (\n\t\t\t\t<div className=\"flex items-center justify-between pl-5 pr-2 py-1\">\n\t\t\t\t\t<div className=\"text-xs font-semibold text-zinc-900 dark:text-zinc-100\">\n\t\t\t\t\t\tBrowse Tools\n\t\t\t\t\t</div>\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tonClick={onClose}\n\t\t\t\t\t\tclassName=\"cursor-pointer rounded-full p-1.5 hover:bg-zinc-100 dark:hover:bg-zinc-800 text-zinc-500 hover:text-zinc-700 dark:text-zinc-400 dark:hover:text-zinc-200 transition-colors\"\n\t\t\t\t\t\taria-label=\"Close\"\n\t\t\t\t\t>\n\t\t\t\t\t\t<HugeiconsIcon icon={Cancel01Icon} size={16} />\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t)}\n\n\t\t\t{/* Category Tabs */}\n\t\t\t{computedCategories.length > 1 && (\n\t\t\t\t<div>\n\t\t\t\t\t<div className=\"flex overflow-x-auto px-3 py-1 gap-1.5 [&::-webkit-scrollbar]:hidden [-ms-overflow-style:none] [scrollbar-width:none]\">\n\t\t\t\t\t\t{computedCategories.map((category) => (\n\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\tkey={category}\n\t\t\t\t\t\t\t\tonClick={() => onCategoryChange?.(category)}\n\t\t\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\t\t// Base styles\n\t\t\t\t\t\t\t\t\t\"flex items-center gap-2 rounded-lg px-3 py-2 text-xs font-medium whitespace-nowrap cursor-pointer transition-all\",\n\t\t\t\t\t\t\t\t\t// Selected state\n\t\t\t\t\t\t\t\t\tselectedCategory === category\n\t\t\t\t\t\t\t\t\t\t? \"bg-zinc-100 text-zinc-900 dark:bg-zinc-700/50 dark:text-white\"\n\t\t\t\t\t\t\t\t\t\t: \"text-zinc-500 hover:bg-zinc-50 hover:text-zinc-700 dark:text-zinc-400 dark:hover:bg-zinc-800/50 dark:hover:text-zinc-300\",\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{getCategoryTabIcon(category)}\n\t\t\t\t\t\t\t\t<span>\n\t\t\t\t\t\t\t\t\t{category === \"all\" ? \"All\" : formatToolName(category)}\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t))}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t)}\n\n\t\t\t{/* Tool List */}\n\t\t\t<div\n\t\t\t\tref={scrollContainerRef}\n\t\t\t\tclassName=\"max-h-[200px] overflow-y-auto py-1.5\"\n\t\t\t>\n\t\t\t\t{filteredMatches.length === 0 ? (\n\t\t\t\t\t<div className=\"px-4 py-8 text-center text-sm text-zinc-500 dark:text-zinc-400\">\n\t\t\t\t\t\tNo tools found\n\t\t\t\t\t</div>\n\t\t\t\t) : (\n\t\t\t\t\tfilteredMatches.map((match, index) => {\n\t\t\t\t\t\tconst isSelected = index === selectedIndex;\n\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\tkey={`${match.tool.category}-${match.tool.name}`}\n\t\t\t\t\t\t\t\tdata-index={index}\n\t\t\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\t\t// Base styles - full width with consistent padding\n\t\t\t\t\t\t\t\t\t\"w-full text-left mx-0 px-3 py-0 cursor-pointer transition-all duration-150\",\n\t\t\t\t\t\t\t\t\t// Selected/hover states with light mode support\n\t\t\t\t\t\t\t\t\tisSelected\n\t\t\t\t\t\t\t\t\t\t? \"bg-zinc-100 dark:bg-zinc-700/40\"\n\t\t\t\t\t\t\t\t\t\t: \"hover:bg-zinc-50 dark:hover:bg-zinc-800/40\",\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\tonClick={() => onSelect(match)}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<div className=\"flex items-center gap-3 py-2.5 px-1\">\n\t\t\t\t\t\t\t\t\t{/* Icon container - fixed size for consistency */}\n\t\t\t\t\t\t\t\t\t<div className=\"flex-shrink-0 flex items-center justify-center w-8 h-8 rounded-lg bg-zinc-100 dark:bg-zinc-800\">\n\t\t\t\t\t\t\t\t\t\t{getDefaultToolIcon(match.tool, ICON_SIZE)}\n\t\t\t\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t\t\t\t{/* Content - fills remaining space */}\n\t\t\t\t\t\t\t\t\t<div className=\"min-w-0 flex-1\">\n\t\t\t\t\t\t\t\t\t\t<div className=\"flex items-center justify-between gap-3\">\n\t\t\t\t\t\t\t\t\t\t\t<span className=\"truncate text-sm font-medium text-zinc-900 dark:text-zinc-100\">\n\t\t\t\t\t\t\t\t\t\t\t\t{formatToolName(match.tool.name)}\n\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t\t{selectedCategory === \"all\" && (\n\t\t\t\t\t\t\t\t\t\t\t\t<span className=\"flex-shrink-0 rounded-md bg-zinc-100 dark:bg-zinc-800 px-2 py-0.5 text-xs text-zinc-500 dark:text-zinc-400\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t{formatToolName(match.tool.category)}\n\t\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t{match.tool.description && (\n\t\t\t\t\t\t\t\t\t\t\t<p className=\"text-xs text-zinc-500 dark:text-zinc-400 mt-0.5 truncate\">\n\t\t\t\t\t\t\t\t\t\t\t\t{match.tool.description}\n\t\t\t\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t);\n\t\t\t\t\t})\n\t\t\t\t)}\n\t\t\t</div>\n\t\t</div>\n\t);\n};\n",
      "type": "registry:ui"
    },
    {
      "path": "lib/utils/tool-icons.tsx",
      "content": "import type { IconSvgElement } from \"@hugeicons/react\";\nimport Image from \"next/image\";\nimport {\n\tBrain02Icon,\n\tCheckListIcon,\n\tAlarmClockIcon,\n\tComputerProgramming01Icon,\n\tFile02Icon,\n\tHugeiconsIcon,\n\tImage02Icon,\n\tInformationCircleIcon,\n\tLink01Icon,\n\tNotification03Icon,\n\tPackageOpenIcon,\n\tSourceCodeCircleIcon,\n\tSquareArrowUpRight02Icon,\n\tTarget02Icon,\n\tToolsIcon,\n} from \"@/components/icons\";\n\nexport interface IconProps {\n\tsize?: number;\n\twidth?: number;\n\theight?: number;\n\tstrokeWidth?: number;\n\tclassName?: string;\n\tcolor?: string;\n}\n\n// Category-specific icons with colors\nexport interface IconConfig {\n\ticon: IconSvgElement | string;\n\tbgColor: string;\n\tbgColorLight?: string; // Light mode background\n\ticonColor: string;\n\tisImage?: boolean;\n}\n\n/**\n * Normalize a category/integration name for icon lookup\n */\nconst normalizeCategoryName = (name: string): string => {\n\tif (!name) return \"general\";\n\treturn name\n\t\t.toLowerCase()\n\t\t.trim()\n\t\t.replace(/[\\s-]+/g, \"_\")\n\t\t.replace(/_+/g, \"_\")\n\t\t.replace(/^_|_$/g, \"\");\n};\n\n// Alias mapping for backwards compatibility\nconst iconAliases: Record<string, string> = {\n\tcalendar: \"google_calendar\",\n};\n\n// Tool category icon configs - matches gaia repo pattern\nconst iconConfigs: Record<string, IconConfig> = {\n\t// Integration icons (use images)\n\tgmail: {\n\t\ticon: \"/images/icons/gmail.svg\",\n\t\tbgColor: \"bg-zinc-700\",\n\t\tbgColorLight: \"bg-zinc-200\",\n\t\ticonColor: \"text-zinc-200\",\n\t\tisImage: true,\n\t},\n\tgoogle_calendar: {\n\t\ticon: \"/images/icons/googlecalendar.webp\",\n\t\tbgColor: \"bg-zinc-700\",\n\t\tbgColorLight: \"bg-zinc-200\",\n\t\ticonColor: \"text-zinc-200\",\n\t\tisImage: true,\n\t},\n\tgithub: {\n\t\ticon: \"/images/icons/github.png\",\n\t\tbgColor: \"bg-zinc-700\",\n\t\tbgColorLight: \"bg-zinc-200\",\n\t\ticonColor: \"text-zinc-200\",\n\t\tisImage: true,\n\t},\n\tlinear: {\n\t\ticon: \"/images/icons/linear.svg\",\n\t\tbgColor: \"bg-zinc-700\",\n\t\tbgColorLight: \"bg-zinc-200\",\n\t\ticonColor: \"text-zinc-200\",\n\t\tisImage: true,\n\t},\n\tslack: {\n\t\ticon: \"/images/icons/slack.svg\",\n\t\tbgColor: \"bg-zinc-700\",\n\t\tbgColorLight: \"bg-zinc-200\",\n\t\ticonColor: \"text-zinc-200\",\n\t\tisImage: true,\n\t},\n\tgoogle_docs: {\n\t\ticon: \"/images/icons/google_docs.webp\",\n\t\tbgColor: \"bg-zinc-700\",\n\t\tbgColorLight: \"bg-zinc-200\",\n\t\ticonColor: \"text-zinc-200\",\n\t\tisImage: true,\n\t},\n\tgooglesheets: {\n\t\ticon: \"/images/icons/googlesheets.webp\",\n\t\tbgColor: \"bg-zinc-700\",\n\t\tbgColorLight: \"bg-zinc-200\",\n\t\ticonColor: \"text-zinc-200\",\n\t\tisImage: true,\n\t},\n\tsearch: {\n\t\ticon: \"/images/icons/google.svg\",\n\t\tbgColor: \"bg-zinc-700\",\n\t\tbgColorLight: \"bg-zinc-200\",\n\t\ticonColor: \"text-zinc-200\",\n\t\tisImage: true,\n\t},\n\tweather: {\n\t\ticon: \"/images/icons/weather.webp\",\n\t\tbgColor: \"bg-zinc-700\",\n\t\tbgColorLight: \"bg-zinc-200\",\n\t\ticonColor: \"text-zinc-200\",\n\t\tisImage: true,\n\t},\n\tnotion: {\n\t\ticon: \"/images/icons/notion.webp\",\n\t\tbgColor: \"bg-zinc-700\",\n\t\tbgColorLight: \"bg-zinc-200\",\n\t\ticonColor: \"text-zinc-200\",\n\t\tisImage: true,\n\t},\n\ttwitter: {\n\t\ticon: \"/images/icons/twitter.webp\",\n\t\tbgColor: \"bg-zinc-700\",\n\t\tbgColorLight: \"bg-zinc-200\",\n\t\ticonColor: \"text-zinc-200\",\n\t\tisImage: true,\n\t},\n\tlinkedin: {\n\t\ticon: \"/images/icons/linkedin.svg\",\n\t\tbgColor: \"bg-zinc-700\",\n\t\tbgColorLight: \"bg-zinc-200\",\n\t\ticonColor: \"text-zinc-200\",\n\t\tisImage: true,\n\t},\n\treddit: {\n\t\ticon: \"/images/icons/reddit.svg\",\n\t\tbgColor: \"bg-zinc-700\",\n\t\tbgColorLight: \"bg-zinc-200\",\n\t\ticonColor: \"text-zinc-200\",\n\t\tisImage: true,\n\t},\n\tfigma: {\n\t\ticon: \"/images/icons/figma.svg\",\n\t\tbgColor: \"bg-zinc-800\",\n\t\tbgColorLight: \"bg-zinc-200\",\n\t\ticonColor: \"text-white\",\n\t\tisImage: true,\n\t},\n\t\n\t// Category icons (use HugeIcons components)\n\ttodos: {\n\t\ticon: CheckListIcon,\n\t\tbgColor: \"bg-emerald-500/20 backdrop-blur\",\n\t\tbgColorLight: \"bg-emerald-500/20\",\n\t\ticonColor: \"text-emerald-400\",\n\t},\n\treminders: {\n\t\ticon: AlarmClockIcon,\n\t\tbgColor: \"bg-sky-500/20 backdrop-blur\",\n\t\tbgColorLight: \"bg-sky-500/20\",\n\t\ticonColor: \"text-blue-400\",\n\t},\n\tdocuments: {\n\t\ticon: File02Icon,\n\t\tbgColor: \"bg-orange-500/20 backdrop-blur\",\n\t\tbgColorLight: \"bg-orange-500/20\",\n\t\ticonColor: \"text-orange-400\",\n\t},\n\tdevelopment: {\n\t\ticon: SourceCodeCircleIcon,\n\t\tbgColor: \"bg-sky-500/20 backdrop-blur\",\n\t\tbgColorLight: \"bg-sky-500/20\",\n\t\ticonColor: \"text-cyan-400\",\n\t},\n\tmemory: {\n\t\ticon: Brain02Icon,\n\t\tbgColor: \"bg-indigo-500/20 backdrop-blur\",\n\t\tbgColorLight: \"bg-indigo-500/20\",\n\t\ticonColor: \"text-indigo-400\",\n\t},\n\tcreative: {\n\t\ticon: Image02Icon,\n\t\tbgColor: \"bg-pink-500/20 backdrop-blur\",\n\t\tbgColorLight: \"bg-pink-500/20\",\n\t\ticonColor: \"text-pink-400\",\n\t},\n\tgoal_tracking: {\n\t\ticon: Target02Icon,\n\t\tbgColor: \"bg-emerald-500/20 backdrop-blur\",\n\t\tbgColorLight: \"bg-emerald-500/20\",\n\t\ticonColor: \"text-emerald-400\",\n\t},\n\tnotifications: {\n\t\ticon: Notification03Icon,\n\t\tbgColor: \"bg-yellow-500/20 backdrop-blur\",\n\t\tbgColorLight: \"bg-yellow-500/20\",\n\t\ticonColor: \"text-yellow-400\",\n\t},\n\tsupport: {\n\t\ticon: InformationCircleIcon,\n\t\tbgColor: \"bg-sky-500/20 backdrop-blur\",\n\t\tbgColorLight: \"bg-sky-500/20\",\n\t\ticonColor: \"text-blue-400\",\n\t},\n\tgeneral: {\n\t\ticon: InformationCircleIcon,\n\t\tbgColor: \"bg-gray-500/20 backdrop-blur\",\n\t\tbgColorLight: \"bg-gray-500/20\",\n\t\ticonColor: \"text-gray-400\",\n\t},\n\tintegrations: {\n\t\ticon: Link01Icon,\n\t\tbgColor: \"bg-zinc-700\",\n\t\tbgColorLight: \"bg-zinc-200\",\n\t\ticonColor: \"text-zinc-200\",\n\t},\n\t\n\t// Agent tool call categories\n\thandoff: {\n\t\ticon: SquareArrowUpRight02Icon,\n\t\tbgColor: \"bg-sky-500/20 backdrop-blur\",\n\t\tbgColorLight: \"bg-sky-500/20\",\n\t\ticonColor: \"text-sky-400\",\n\t},\n\tretrieve_tools: {\n\t\ticon: PackageOpenIcon,\n\t\tbgColor: \"bg-indigo-500/20 backdrop-blur\",\n\t\tbgColorLight: \"bg-indigo-500/20\",\n\t\ticonColor: \"text-indigo-400\",\n\t},\n\texecutor: {\n\t\ticon: ComputerProgramming01Icon,\n\t\tbgColor: \"bg-teal-500/20 backdrop-blur\",\n\t\tbgColorLight: \"bg-teal-500/20\",\n\t\ticonColor: \"text-teal-400\",\n\t},\n\tunknown: {\n\t\ticon: ToolsIcon,\n\t\tbgColor: \"bg-zinc-500/20 backdrop-blur\",\n\t\tbgColorLight: \"bg-zinc-500/20\",\n\t\ticonColor: \"text-zinc-400\",\n\t},\n};\n\n/**\n * Get icon for a tool category with optional URL-based icon fallback.\n * Supports built-in categories and custom integration icons via iconUrl.\n */\nexport const getToolCategoryIcon = (\n\tcategory: string,\n\ticonProps: Partial<IconProps> & { showBackground?: boolean } = {},\n\ticonUrl?: string | null,\n) => {\n\tconst { showBackground = true, ...restProps } = iconProps;\n\n\tconst defaultProps = {\n\t\tsize: restProps.size || 16,\n\t\twidth: restProps.width || 20,\n\t\theight: restProps.height || 20,\n\t\tstrokeWidth: restProps.strokeWidth || 2,\n\t\tclassName: restProps.className,\n\t};\n\n\t// Normalize\n\tconst normalizedCategory = normalizeCategoryName(category);\n\n\t// Resolve aliases\n\tconst aliasedCategory =\n\t\ticonAliases[normalizedCategory] ||\n\t\ticonAliases[category] ||\n\t\tnormalizedCategory;\n\n\tconst finalCategory = normalizeCategoryName(aliasedCategory);\n\n\tlet config = iconConfigs[finalCategory];\n\n\t// Fallback search\n\tif (!config) {\n\t\tconst normalizedConfigs = Object.entries(iconConfigs);\n\t\tconst matchingConfig = normalizedConfigs.find(\n\t\t\t([key]) => normalizeCategoryName(key) === finalCategory,\n\t\t);\n\t\tif (matchingConfig) {\n\t\t\tconfig = matchingConfig[1];\n\t\t}\n\t}\n\n\t// If no predefined config found, try iconUrl fallback for custom integrations\n\tif (!config) {\n\t\tif (iconUrl) {\n\t\t\tconst iconElement = (\n\t\t\t\t<Image\n\t\t\t\t\talt={`${category} Icon`}\n\t\t\t\t\twidth={defaultProps.width}\n\t\t\t\t\theight={defaultProps.height}\n\t\t\t\t\tclassName={`${restProps.className || \"\"} aspect-square object-contain`}\n\t\t\t\t\tsrc={iconUrl}\n\t\t\t\t/>\n\t\t\t);\n\t\t\treturn showBackground ? (\n\t\t\t\t<div className=\"rounded-lg p-1 bg-zinc-700 dark:bg-zinc-700\">{iconElement}</div>\n\t\t\t) : (\n\t\t\t\ticonElement\n\t\t\t);\n\t\t}\n\t\treturn null;\n\t}\n\n\t// Render image or component icon\n\tconst iconElement = config.isImage ? (\n\t\t<Image\n\t\t\talt={`${category} Icon`}\n\t\t\twidth={defaultProps.width}\n\t\t\theight={defaultProps.height}\n\t\t\tclassName={`${restProps.className || \"\"} aspect-square object-contain`}\n\t\t\tsrc={config.icon as string}\n\t\t/>\n\t) : (\n\t\t<HugeiconsIcon\n\t\t\ticon={config.icon as IconSvgElement}\n\t\t\tsize={defaultProps.size}\n\t\t\tclassName={restProps.className || config.iconColor}\n\t\t/>\n\t);\n\n\t// Return with or without background based on showBackground prop\n\t// Using dark: prefix for proper light/dark mode support\n\treturn showBackground ? (\n\t\t<div className={`rounded-lg p-1 ${config.bgColorLight || config.bgColor} dark:${config.bgColor}`}>\n\t\t\t{iconElement}\n\t\t</div>\n\t) : (\n\t\ticonElement\n\t);\n};\n\n// Format tool names from snake_case to Title Case\nexport const formatToolName = (name: string): string => {\n\treturn name\n\t\t.split(\"_\")\n\t\t.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())\n\t\t.join(\" \");\n};\n",
      "type": "registry:lib"
    }
  ]
}