Project: Icon Search Directory
In this project, we will construct an interactive icon search directory. The interface allows users to filter icons dynamically in real-time and click any icon card to copy its import code.
1. Directory Specifications
- Search Input: Triggers keyup events to filter matching icon cards.
- Dynamic Grid: Displays filtered card components showing the icon name and SVG shape.
- Clipboard Copy: Copies JSX code (e.g.,
<Home />) to the clipboard on click.
2. Implementing the Directory Interface
Here is the React code. Save this inside src/components/IconSearch.tsx:
import React, { useState } from "react";
import {
Search,
Home,
Settings,
User,
Trash2,
Edit,
Copy,
Check
} from "lucide-react";
interface IconItem {
name: string;
component: React.ComponentType<{ className?: string }>;
}
export function IconSearch() {
const [query, setQuery] = useState("");
const [copiedIndex, setCopiedIndex] = useState<number | null>(null);
const iconsList: IconItem[] = [
{ name: "Home", component: Home },
{ name: "Settings", component: Settings },
{ name: "User", component: User },
{ name: "Trash", component: Trash2 },
{ name: "Edit", component: Edit },
];
// Filter list based on search query string
const filteredIcons = iconsList.filter((icon) =>
icon.name.toLowerCase().includes(query.toLowerCase())
);
// Copy code snippet to clipboard
const handleCopyCode = (name: string, index: number) => {
const codeText = `<${name} className="w-5 h-5" />`;
navigator.clipboard.writeText(codeText);
setCopiedIndex(index);
setTimeout(() => setCopiedIndex(null), 1500);
};
return (
<div className="max-w-4xl mx-auto p-8">
<h1 className="text-3xl font-bold">Icon Search Directory</h1>
{/* Search Input bar */}
<div className="relative mt-6">
<span className="absolute inset-y-0 left-0 pl-3 flex items-center">
<Search className="w-5 h-5 text-gray-400" />
</span>
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search icons by name..."
className="w-full pl-10 pr-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
/>
</div>
{/* Grid displays icon cards */}
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-6 mt-8">
{filteredIcons.map((icon, index) => {
const IconComponent = icon.component;
const isCopied = copiedIndex === index;
return (
<button
key={index}
onClick={() => handleCopyCode(icon.name, index)}
className="flex flex-col items-center justify-center p-6 bg-white border border-gray-200 rounded-xl hover:border-blue-500 hover:shadow-md transition relative group"
>
<IconComponent className="w-8 h-8 text-gray-700 group-hover:text-blue-600 transition" />
<span className="text-sm text-gray-500 mt-3">{icon.name}</span>
{/* Copy indicator pill overlay */}
<span className="absolute top-2 right-2 p-1.5 rounded-lg bg-gray-50 text-gray-400 group-hover:bg-blue-50 group-hover:text-blue-600 transition">
{isCopied ? <Check className="w-4 h-4 text-green-600" /> : <Copy className="w-4 h-4" />}
</span>
</button>
);
})}
</div>
</div>
);
}Published on Last updated: