import { useState, useMemo, useCallback, useRef, useEffect } from 'react' import { MagnifyingGlass } from '@phosphor-icons/react' import { ICON_OPTIONS, type IconEntry } from '../utils/iconRegistry' import { cn } from '@/lib/utils' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Textarea } from '@/components/ui/textarea' import { translate, type AppLocale } from '../lib/i18n' import { AccentColorPicker } from './AccentColorPicker' 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 showTemplate?: boolean showDone?: boolean surface?: 'popover' | 'inline' locale?: AppLocale } interface ColorSectionProps { selectedColor: string | null locale: AppLocale onSelectColor: (key: string) => void } interface IconSectionProps { selectedIcon: string | null search: string filteredIcons: IconEntry[] locale: AppLocale onSearchChange: (query: string) => void onSelectIcon: (name: string) => void } interface TemplateSectionProps { templateText: string locale: AppLocale onTemplateChange: (value: string) => void } const ICON_PICKER_ICON_SIZE = 18 const ICON_PICKER_ICON_CLASS_NAME = 'size-[18px]' interface DebouncedCallback { flush: () => void run: (value: string) => void } /** Debounce a callback by `delay` ms. Pending work is flushed on unmount. */ function useDebouncedCallback(fn: (v: string) => void, delay: number): DebouncedCallback { const timerRef = useRef | undefined>(undefined) const pendingValueRef = useRef(null) const fnRef = useRef(fn) useEffect(() => { fnRef.current = fn }) const flush = useCallback(() => { clearTimeout(timerRef.current) timerRef.current = undefined if (pendingValueRef.current === null) return const value = pendingValueRef.current pendingValueRef.current = null fnRef.current(value) }, []) const run = useCallback((value: string) => { clearTimeout(timerRef.current) pendingValueRef.current = value timerRef.current = setTimeout(flush, delay) }, [delay, flush]) useEffect(() => () => { flush() }, [flush]) return useMemo(() => ({ flush, run }), [flush, run]) } function ColorSection({ selectedColor, locale, onSelectColor }: ColorSectionProps) { return ( <>
{translate(locale, 'customize.color')}
) } function IconSection({ selectedIcon, search, filteredIcons, locale, onSearchChange, onSelectIcon, }: IconSectionProps) { return ( <>
{translate(locale, 'customize.icon')}
onSearchChange(event.target.value)} placeholder={translate(locale, 'customize.searchIcons')} className="h-7 pl-7 pr-2 py-1 text-[12px]" />
{filteredIcons.length === 0 ? (
{translate(locale, 'customize.noIconsFound')}
) : ( filteredIcons.map(({ name, Icon }) => ( )) )}
) } function TemplateSection({ templateText, locale, onTemplateChange }: TemplateSectionProps) { return ( <>
{translate(locale, 'customize.template')}