{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "icon-picker-virtua",
  "title": "Icon Picker Virtualized",
  "description": "A simple icon picker component with virtualization.",
  "dependencies": [
    "lucide-react",
    "virtua"
  ],
  "registryDependencies": [
    "input"
  ],
  "files": [
    {
      "path": "registry/icon-picker/icon-picker-virtua.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { icons } from \"lucide-react\";\n\nimport { experimental_VGrid as VGrid } from \"virtua\";\n\nimport { cn } from \"@/lib/utils\";\nimport { Input } from \"@/components/ui/input\";\nimport { useDebounce } from \"@/hooks/use-debounce\";\n\nconst IconItem = React.memo(\n  ({\n    iconName,\n    setSelectedIcon,\n    selectedIcon,\n  }: {\n    iconName: string;\n    setSelectedIcon?: (iconName: string) => void;\n    selectedIcon?: string | null;\n  }) => {\n    const Icon = icons[iconName as keyof typeof icons];\n    const handleClick = React.useCallback(() => {\n      setSelectedIcon?.(iconName);\n    }, [iconName, setSelectedIcon]);\n    return (\n      <div className=\"flex items-center justify-center size-full\">\n        <button\n          className={cn(\n            \"flex items-center justify-center size-7 rounded-md cursor-pointer text-popover-foreground/70 hover:bg-muted hover:text-popover-foreground transition-colors\",\n            selectedIcon === iconName &&\n              \"bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground\",\n          )}\n          onClick={handleClick}\n          title={iconName}\n          aria-label={`Select ${iconName} icon`}\n          aria-pressed={selectedIcon === iconName}\n        >\n          <Icon size={16} />\n        </button>\n      </div>\n    );\n  },\n);\nIconItem.displayName = \"IconItem\";\n\nconst IconPicker = React.memo(\n  ({\n    setSelectedIcon,\n    selectedIcon,\n    className,\n    ...props\n  }: {\n    setSelectedIcon?: (iconName: string) => void;\n    selectedIcon?: string | null;\n  } & React.ComponentProps<\"div\">) => {\n    const [searchQuery, setSearchQuery] = React.useState(\"\");\n    const debouncedSearchQuery = useDebounce(searchQuery, 200);\n\n    const iconsMap = React.useMemo(() => Object.keys(icons), []);\n\n    const filteredIcons = React.useMemo(() => {\n      if (!debouncedSearchQuery.trim()) {\n        return iconsMap;\n      }\n      return iconsMap.filter((iconName) =>\n        iconName.toLowerCase().includes(debouncedSearchQuery.toLowerCase()),\n      );\n    }, [debouncedSearchQuery, iconsMap]);\n\n    const handleSearchChange = React.useCallback(\n      (e: React.ChangeEvent<HTMLInputElement>) => {\n        setSearchQuery(e.target.value);\n      },\n      [],\n    );\n\n    const ITEMS_PER_ROW = 9;\n    const ITEM_SIZE = 28; // size-7 corresponds to 28px\n\n    const gridKey = React.useMemo(\n      () => `icon-grid-${filteredIcons.length}`,\n      [filteredIcons.length],\n    );\n\n    const totalRows = Math.ceil(filteredIcons.length / ITEMS_PER_ROW);\n\n    return (\n      <div\n        className={cn(\"w-72 bg-popover rounded-lg border shadow-md\", className)}\n        aria-label=\"Icon Picker\"\n        {...props}\n      >\n        <div className=\"relative px-2 pt-2\">\n          <Input\n            autoComplete=\"off\"\n            autoCorrect=\"off\"\n            autoCapitalize=\"off\"\n            spellCheck={false}\n            className=\"border-none focus-visible:ring-2 font-semibold px-2.5 py-2\"\n            placeholder=\"Search...\"\n            type=\"text\"\n            value={searchQuery}\n            onChange={handleSearchChange}\n            aria-label=\"Search icons\"\n          />\n        </div>\n        <div className=\"h-80 p-1.5\" aria-label=\"Available icons\">\n          <VGrid\n            key={gridKey}\n            row={totalRows}\n            col={ITEMS_PER_ROW}\n            cellWidth={ITEM_SIZE}\n            cellHeight={ITEM_SIZE}\n          >\n            {({ rowIndex, colIndex }) => {\n              const index = rowIndex * ITEMS_PER_ROW + colIndex;\n              if (index >= filteredIcons.length) return null;\n\n              return (\n                <IconItem\n                  key={filteredIcons[index]}\n                  iconName={filteredIcons[index]}\n                  setSelectedIcon={setSelectedIcon}\n                  selectedIcon={selectedIcon}\n                />\n              );\n            }}\n          </VGrid>\n        </div>\n      </div>\n    );\n  },\n);\nIconPicker.displayName = \"IconPicker\";\n\nexport default IconPicker;\n",
      "type": "registry:component"
    },
    {
      "path": "hooks/use-debounce.ts",
      "content": "import * as React from \"react\";\n\nexport function useDebounce<T>(value: T, delay?: number): T {\n  const [debouncedValue, setDebouncedValue] = React.useState<T>(value);\n\n  React.useEffect(() => {\n    const timer = setTimeout(() => {\n      setDebouncedValue(value);\n    }, delay || 500);\n\n    return () => {\n      clearTimeout(timer);\n    };\n  }, [value, delay]);\n\n  return debouncedValue;\n}\n",
      "type": "registry:hook"
    }
  ],
  "type": "registry:block"
}