import { useState, useMemo, useCallback, useRef, useEffect } from 'react' import { MagnifyingGlass } from '@phosphor-icons/react' import { ICON_OPTIONS, type IconEntry } from '../utils/iconRegistry' import { ACCENT_COLORS } from '../utils/typeColors' import { cn } from '@/lib/utils' function filterIcons(icons: IconEntry[], query: string): IconEntry[] { if (!query) return icons const lower = query.toLowerCase() return icons.filter((o) => o.name.includes(lower)) } interface TypeCustomizePopoverProps { currentIcon: string | null currentColor: string | null currentTemplate: string | null onChangeIcon: (icon: string) => void onChangeColor: (color: string) => void onChangeTemplate: (template: string) => void onClose: () => void } /** Debounce a callback by `delay` ms. Returns a stable ref-based wrapper. */ function useDebouncedCallback(fn: (v: string) => void, delay: number): (v: string) => void { const timerRef = useRef | undefined>(undefined) const fnRef = useRef(fn) useEffect(() => { fnRef.current = fn }) useEffect(() => () => { clearTimeout(timerRef.current) }, []) return useCallback((v: string) => { clearTimeout(timerRef.current) timerRef.current = setTimeout(() => fnRef.current(v), delay) }, [delay]) } export function TypeCustomizePopover({ currentIcon, currentColor, currentTemplate, onChangeIcon, onChangeColor, onChangeTemplate, onClose, }: TypeCustomizePopoverProps) { const [selectedColor, setSelectedColor] = useState(currentColor) const [selectedIcon, setSelectedIcon] = useState(currentIcon) const [search, setSearch] = useState('') const [templateText, setTemplateText] = useState(currentTemplate ?? '') const filteredIcons = useMemo(() => filterIcons(ICON_OPTIONS, search), [search]) const handleColorClick = (key: string) => { setSelectedColor(key) onChangeColor(key) } const handleIconClick = (name: string) => { setSelectedIcon(name) onChangeIcon(name) } const debouncedSaveTemplate = useDebouncedCallback(onChangeTemplate, 500) const handleTemplateChange = (value: string) => { setTemplateText(value) debouncedSaveTemplate(value) } return (
e.stopPropagation()} onContextMenu={(e) => e.stopPropagation()} > {/* Color section */}
Color
{ACCENT_COLORS.map((c) => (
{/* Icon section */}
Icon
{/* Search input */}
setSearch(e.target.value)} placeholder="Search icons…" className="w-full rounded border border-border bg-background pl-7 pr-2 py-1 text-[12px] text-foreground placeholder:text-muted-foreground outline-none focus:border-primary" />
{/* Icon grid */}
{filteredIcons.length === 0 ? (
No icons found
) : ( filteredIcons.map(({ name, Icon }) => ( )) )}
{/* Template section */}
Template