import { useState, useRef, useMemo } from 'react'; import { Search, Play, Square, AudioLines } from 'lucide-react'; import { Widget } from 'widgets/Widget'; import { Input } from '@/components/ui/input'; import { categories, allSounds } from './catalog-data'; import type { SoundAsset } from './types'; type ActiveCategory = 'all' | string; const formatName = (name: string) => name .split('-') .map((w) => w.charAt(0).toUpperCase() + w.slice(1)) .join(' '); const getCategoryLabel = (sound: SoundAsset): string => { const cat = categories.find((c) => c.sounds.includes(sound)); return cat?.label ?? ''; }; const SoundCard = ({ sound, isPlaying, onPlay, onStop, }: { sound: SoundAsset; isPlaying: boolean; onPlay: () => void; onStop: () => void; }) => ( {isPlaying ? : } {formatName(sound.name)} {getCategoryLabel(sound)} · {sound.duration.toFixed(2)}s ); export const Catalog = () => { const [search, setSearch] = useState(''); const [activeCategory, setActiveCategory] = useState('all'); const [playingId, setPlayingId] = useState(null); const audioRef = useRef(null); const filtered = useMemo(() => { const source = activeCategory === 'all' ? allSounds : (categories.find((c) => c.id === activeCategory)?.sounds ?? []); if (!search) return source; const q = search.toLowerCase(); return source.filter((s) => s.name.toLowerCase().includes(q)); }, [search, activeCategory]); const play = (sound: SoundAsset) => { if (audioRef.current) { audioRef.current.pause(); audioRef.current = null; } const audio = new Audio(sound.dataUri); audio.addEventListener('ended', () => setPlayingId(null)); audio.play(); audioRef.current = audio; setPlayingId(sound.name); }; const stop = () => { if (audioRef.current) { audioRef.current.pause(); audioRef.current = null; } setPlayingId(null); }; const tabs = [ { id: 'all' as const, label: 'All', count: allSounds.length }, ...categories.map((c) => ({ id: c.id, label: c.label, count: c.sounds.length })), ]; return ( setSearch(ev.target.value)} className="pl-9" /> {tabs.map((tab) => ( setActiveCategory(tab.id)} className={`px-2.5 py-1 rounded-full text-xs font-medium cursor-pointer transition-colors border ${ activeCategory === tab.id ? 'border-duck-teal bg-duck-teal/10 text-duck-teal' : 'border-duck-dark/15 text-duck-dark/50 hover:border-duck-dark/30' }`} > {tab.label} {tab.count} ))} {filtered.length} sounds {filtered.length === 0 && No sounds found.} {filtered.map((sound) => ( play(sound)} onStop={stop} /> ))} CC0 licensed · Original audio by{' '} Kenney powered by soundcn ); };
No sounds found.