rem-notepad / src /components /Sidebar /IconPicker.tsx
algorembrant's picture
Upload 31 files
4af09f9 verified
import { useState } from 'react';
import * as HugeIcons from 'hugeicons-react';
import iconNames from '../../utils/icon_names.json';
interface IconPickerProps {
onSelect: (iconName: string, color?: string) => void;
onClose: () => void;
}
export default function IconPicker({ onSelect, onClose }: IconPickerProps) {
const [search, setSearch] = useState('');
const [selectedColor, setSelectedColor] = useState<string | undefined>(undefined);
const COLORS = [
'#2e6ff2', '#e11d48', '#d97706', '#16a34a',
'#9333ea', '#db2777', '#0891b2', '#475569',
'#111111'
];
const filteredIcons = iconNames.filter(name =>
name.toLowerCase().includes(search.toLowerCase())
).slice(0, 100); // Limit results for performance
return (
<div className="icon-picker-overlay" onClick={onClose} style={{
position: 'fixed', top: 0, left: 0, right: 0, bottom: 0,
zIndex: 2000, display: 'flex', alignItems: 'center', justifyContent: 'center',
backgroundColor: 'rgba(0,0,0,0.2)'
}}>
<div className="icon-picker" onClick={e => e.stopPropagation()} style={{
background: 'var(--bg-panel)', padding: '16px', borderRadius: '8px',
boxShadow: '0 8px 32px rgba(0,0,0,0.15)', width: '400px', maxHeight: '500px',
display: 'flex', flexDirection: 'column', border: '1px solid var(--border-color)'
}}>
<input
autoFocus
placeholder="Search icons..."
value={search}
onChange={e => setSearch(e.target.value)}
style={{
width: '100%', padding: '10px', marginBottom: '12px',
border: '1px solid var(--border-color)', borderRadius: '4px',
outline: 'none', fontSize: '14px'
}}
/>
<div className="icon-grid" style={{
display: 'grid', gridTemplateColumns: 'repeat(5, 1fr)',
gap: '8px', overflowY: 'auto', flex: 1, padding: '4px'
}}>
{filteredIcons.map(name => {
const Icon = (HugeIcons as any)[name];
if (!Icon) return null;
return (
<button
key={name}
onClick={() => { onSelect(name, selectedColor); onClose(); }}
title={name}
style={{
display: 'flex', alignItems: 'center', justifyContent: 'center',
padding: '8px', borderRadius: '4px', background: 'transparent',
border: '1px solid transparent', cursor: 'pointer', transition: 'var(--transition-smooth)'
}}
onMouseEnter={e => e.currentTarget.style.background = 'rgba(0,0,0,0.05)'}
onMouseLeave={e => e.currentTarget.style.background = 'transparent'}
>
<Icon size={20} color={selectedColor || "var(--text-primary)"} />
</button>
);
})}
</div>
<div style={{ marginTop: '12px', borderTop: '1px solid var(--border-color)', paddingTop: '12px', display: 'flex', gap: '8px', justifyContent: 'center' }}>
{COLORS.map(c => (
<div
key={c}
onClick={() => setSelectedColor(c)}
style={{
width: '20px', height: '20px', borderRadius: '50%', backgroundColor: c,
cursor: 'pointer', border: selectedColor === c ? '2px solid var(--text-primary)' : '2px solid transparent',
boxShadow: '0 2px 4px rgba(0,0,0,0.1)'
}}
/>
))}
<div
onClick={() => setSelectedColor(undefined)}
style={{
width: '20px', height: '20px', borderRadius: '50%', border: '1px dashed var(--text-secondary)',
cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '10px'
}}
>
x
</div>
</div>
{filteredIcons.length === 0 && (
<div style={{ textAlign: 'center', padding: '20px', color: 'var(--text-secondary)' }}>
No icons found
</div>
)}
</div>
</div>
);
}