Template
fix
This commit is contained in:
1 parent
61df5e3dcc
commit
c0a94d3b6d
25 files changed
+2143
-348
No files matched your search
@@ -0,0 +1,233 @@
|
||||
import { useState, useMemo } from 'react'
|
||||
import {
|
||||
Search,
|
||||
Sun,
|
||||
Moon,
|
||||
Copy,
|
||||
Check,
|
||||
Eye,
|
||||
ArrowRight,
|
||||
ArrowLeft,
|
||||
Menu,
|
||||
Layers,
|
||||
Sparkles,
|
||||
Terminal,
|
||||
AlertTriangle,
|
||||
AlertCircle,
|
||||
Info,
|
||||
CheckCircle2,
|
||||
X,
|
||||
Plus,
|
||||
Minus,
|
||||
Trash2,
|
||||
ExternalLink,
|
||||
ChevronDown,
|
||||
ChevronRight,
|
||||
ChevronLeft,
|
||||
ChevronUp,
|
||||
Filter,
|
||||
SlidersHorizontal,
|
||||
Code2,
|
||||
Cpu,
|
||||
Database,
|
||||
User,
|
||||
Shield,
|
||||
Folder,
|
||||
FileText,
|
||||
Calendar,
|
||||
Clock,
|
||||
RefreshCw,
|
||||
Download,
|
||||
Upload,
|
||||
Settings,
|
||||
HelpCircle,
|
||||
type LucideIcon,
|
||||
} from 'lucide-react'
|
||||
import { Icon } from './Icon'
|
||||
import { Kbd } from './ui/Kbd'
|
||||
import { Badge } from './ui/Badge'
|
||||
|
||||
type IconItem = {
|
||||
name: string
|
||||
pascalName: string
|
||||
category: 'Navigation' | 'Interface' | 'System' | 'Status'
|
||||
component: LucideIcon
|
||||
}
|
||||
|
||||
const ICONS: IconItem[] = [
|
||||
// Navigation
|
||||
{ name: 'arrow-right', pascalName: 'ArrowRight', category: 'Navigation', component: ArrowRight },
|
||||
{ name: 'arrow-left', pascalName: 'ArrowLeft', category: 'Navigation', component: ArrowLeft },
|
||||
{ name: 'chevron-right', pascalName: 'ChevronRight', category: 'Navigation', component: ChevronRight },
|
||||
{ name: 'chevron-left', pascalName: 'ChevronLeft', category: 'Navigation', component: ChevronLeft },
|
||||
{ name: 'chevron-down', pascalName: 'ChevronDown', category: 'Navigation', component: ChevronDown },
|
||||
{ name: 'chevron-up', pascalName: 'ChevronUp', category: 'Navigation', component: ChevronUp },
|
||||
{ name: 'external-link', pascalName: 'ExternalLink', category: 'Navigation', component: ExternalLink },
|
||||
{ name: 'menu', pascalName: 'Menu', category: 'Navigation', component: Menu },
|
||||
|
||||
// System & AI
|
||||
{ name: 'sparkles', pascalName: 'Sparkles', category: 'System', component: Sparkles },
|
||||
{ name: 'terminal', pascalName: 'Terminal', category: 'System', component: Terminal },
|
||||
{ name: 'code', pascalName: 'Code2', category: 'System', component: Code2 },
|
||||
{ name: 'cpu', pascalName: 'Cpu', category: 'System', component: Cpu },
|
||||
{ name: 'layers', pascalName: 'Layers', category: 'System', component: Layers },
|
||||
{ name: 'sun', pascalName: 'Sun', category: 'System', component: Sun },
|
||||
{ name: 'moon', pascalName: 'Moon', category: 'System', component: Moon },
|
||||
{ name: 'database', pascalName: 'Database', category: 'System', component: Database },
|
||||
|
||||
// Interface & Actions
|
||||
{ name: 'search', pascalName: 'Search', category: 'Interface', component: Search },
|
||||
{ name: 'copy', pascalName: 'Copy', category: 'Interface', component: Copy },
|
||||
{ name: 'settings', pascalName: 'Settings', category: 'Interface', component: Settings },
|
||||
{ name: 'sliders', pascalName: 'SlidersHorizontal', category: 'Interface', component: SlidersHorizontal },
|
||||
{ name: 'filter', pascalName: 'Filter', category: 'Interface', component: Filter },
|
||||
{ name: 'plus', pascalName: 'Plus', category: 'Interface', component: Plus },
|
||||
{ name: 'minus', pascalName: 'Minus', category: 'Interface', component: Minus },
|
||||
{ name: 'trash', pascalName: 'Trash2', category: 'Interface', component: Trash2 },
|
||||
{ name: 'refresh', pascalName: 'RefreshCw', category: 'Interface', component: RefreshCw },
|
||||
{ name: 'download', pascalName: 'Download', category: 'Interface', component: Download },
|
||||
{ name: 'upload', pascalName: 'Upload', category: 'Interface', component: Upload },
|
||||
{ name: 'folder', pascalName: 'Folder', category: 'Interface', component: Folder },
|
||||
{ name: 'file', pascalName: 'FileText', category: 'Interface', component: FileText },
|
||||
{ name: 'user', pascalName: 'User', category: 'Interface', component: User },
|
||||
{ name: 'calendar', pascalName: 'Calendar', category: 'Interface', component: Calendar },
|
||||
{ name: 'clock', pascalName: 'Clock', category: 'Interface', component: Clock },
|
||||
|
||||
// Status & Feedback
|
||||
{ name: 'check', pascalName: 'Check', category: 'Status', component: Check },
|
||||
{ name: 'check-circle', pascalName: 'CheckCircle2', category: 'Status', component: CheckCircle2 },
|
||||
{ name: 'warning', pascalName: 'AlertTriangle', category: 'Status', component: AlertTriangle },
|
||||
{ name: 'alert', pascalName: 'AlertCircle', category: 'Status', component: AlertCircle },
|
||||
{ name: 'info', pascalName: 'Info', category: 'Status', component: Info },
|
||||
{ name: 'close', pascalName: 'X', category: 'Status', component: X },
|
||||
{ name: 'shield', pascalName: 'Shield', category: 'Status', component: Shield },
|
||||
{ name: 'help', pascalName: 'HelpCircle', category: 'Status', component: HelpCircle },
|
||||
{ name: 'eye', pascalName: 'Eye', category: 'Status', component: Eye },
|
||||
]
|
||||
|
||||
const CATEGORIES = ['All', 'Navigation', 'Interface', 'System', 'Status'] as const
|
||||
type Category = (typeof CATEGORIES)[number]
|
||||
|
||||
export function IconMatrix() {
|
||||
const [activeCategory, setActiveCategory] = useState<Category>('All')
|
||||
const [search, setSearch] = useState('')
|
||||
const [size, setSize] = useState<number>(20)
|
||||
const [copiedName, setCopiedName] = useState<string | null>(null)
|
||||
|
||||
const filteredIcons = useMemo(() => {
|
||||
return ICONS.filter(icon => {
|
||||
const matchesCategory = activeCategory === 'All' || icon.category === activeCategory
|
||||
const matchesSearch =
|
||||
icon.name.toLowerCase().includes(search.toLowerCase()) ||
|
||||
icon.pascalName.toLowerCase().includes(search.toLowerCase())
|
||||
return matchesCategory && matchesSearch
|
||||
})
|
||||
}, [activeCategory, search])
|
||||
|
||||
const copyUsage = (icon: IconItem) => {
|
||||
const code = `<${icon.pascalName} size={${size}} />`
|
||||
navigator.clipboard?.writeText(code)
|
||||
setCopiedName(icon.name)
|
||||
setTimeout(() => setCopiedName(null), 2000)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="icon-matrix-container">
|
||||
{/* Header controls */}
|
||||
<div className="icon-matrix-header">
|
||||
<div className="icon-search-bar">
|
||||
<Icon name="search" size={16} />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search icons (e.g. arrow, check, sparkles)..."
|
||||
value={search}
|
||||
onChange={e => setSearch(e.target.value)}
|
||||
className="icon-search-input"
|
||||
/>
|
||||
{search && (
|
||||
<button className="icon-clear-btn" onClick={() => setSearch('')}>
|
||||
<Icon name="close" size={14} />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="icon-size-picker">
|
||||
<span className="mono muted">Size:</span>
|
||||
{[16, 20, 24].map(s => (
|
||||
<button
|
||||
key={s}
|
||||
className={`icon-size-btn ${size === s ? 'active' : ''}`}
|
||||
onClick={() => setSize(s)}
|
||||
>
|
||||
{s}px
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Category tabs */}
|
||||
<div className="icon-category-tabs">
|
||||
{CATEGORIES.map(cat => (
|
||||
<button
|
||||
key={cat}
|
||||
className={`icon-cat-btn ${activeCategory === cat ? 'active' : ''}`}
|
||||
onClick={() => setActiveCategory(cat)}
|
||||
>
|
||||
{cat}
|
||||
<span className="icon-cat-count">
|
||||
{cat === 'All' ? ICONS.length : ICONS.filter(i => i.category === cat).length}
|
||||
</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Grid of icons */}
|
||||
<div className="icon-grid">
|
||||
{filteredIcons.map(icon => {
|
||||
const isCopied = copiedName === icon.name
|
||||
return (
|
||||
<button
|
||||
key={icon.name}
|
||||
className={`icon-tile ${isCopied ? 'copied' : ''}`}
|
||||
onClick={() => copyUsage(icon)}
|
||||
title={`Click to copy <${icon.pascalName} />`}
|
||||
>
|
||||
<div className="icon-tile-glyph">
|
||||
{isCopied ? (
|
||||
<Icon name="check" size={size} className="accent" />
|
||||
) : (
|
||||
<icon.component size={size} strokeWidth={1.8} />
|
||||
)}
|
||||
</div>
|
||||
<span className="icon-tile-name">{icon.name}</span>
|
||||
<span className="icon-tile-action">
|
||||
{isCopied ? 'Copied!' : <Kbd>Copy</Kbd>}
|
||||
</span>
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
{filteredIcons.length === 0 && (
|
||||
<div className="empty-state">
|
||||
<div className="empty-state-icon">
|
||||
<Icon name="search" size={24} />
|
||||
</div>
|
||||
<h4 style={{ margin: '0 0 var(--space-2)' }}>No icons found</h4>
|
||||
<p className="muted" style={{ margin: 0, fontSize: 'var(--text-xs)' }}>
|
||||
No icon matches "{search}". You can use any of Lucide's 1,400+ icons directly via{' '}
|
||||
<code>lucide-react</code>.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Footer usage tip */}
|
||||
<div className="icon-matrix-footer">
|
||||
<div className="icon-footer-pill">
|
||||
<Badge variant="outline">lucide-react</Badge>
|
||||
<span>Over 1,400+ SVG icons available. Fully tree-shaken and styled via <code>currentColor</code>.</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in new issue
Block a user