import React, { useState, useMemo, useRef, useEffect } from 'react'; import { X, Search, Globe, Plus } from 'lucide-react'; import { POPULAR_LANGS } from '../utils/constants'; import { LANG_CODES } from '../utils/languages'; import './MultiLangPicker.css'; /** * MultiLangPicker — chip-based multi-language selector for batch dubbing. * * Shows selected languages as removable badges. Click "+" to open a * searchable dropdown with Popular + All Languages sections. */ export default function MultiLangPicker({ selected = [], // array of { lang: string, code: string } onChange, // (newSelected) => void disabled = false, }) { const [dropOpen, setDropOpen] = useState(false); const [query, setQuery] = useState(''); const dropRef = useRef(null); const inputRef = useRef(null); // Close dropdown on outside click useEffect(() => { if (!dropOpen) return; const handler = (e) => { if (dropRef.current && !dropRef.current.contains(e.target)) setDropOpen(false); }; document.addEventListener('mousedown', handler); return () => document.removeEventListener('mousedown', handler); }, [dropOpen]); // Focus search when dropdown opens useEffect(() => { if (dropOpen && inputRef.current) inputRef.current.focus(); }, [dropOpen]); const selectedCodes = useMemo(() => new Set(selected.map(s => s.code)), [selected]); const addLang = (lang, code) => { if (selectedCodes.has(code)) return; onChange([...selected, { lang, code }]); setQuery(''); }; const removeLang = (code) => { onChange(selected.filter(s => s.code !== code)); }; const filteredLangs = useMemo(() => { const q = query.toLowerCase().trim(); return LANG_CODES.filter(lc => !selectedCodes.has(lc.code) && (!q || lc.label.toLowerCase().includes(q) || lc.code.toLowerCase().includes(q)) ); }, [query, selectedCodes]); const popularFiltered = useMemo(() => { const q = query.toLowerCase().trim(); return POPULAR_LANGS .map(lang => { const match = LANG_CODES.find(lc => lc.label.toLowerCase() === lang.toLowerCase()); return match ? { lang, code: match.code } : null; }) .filter(item => item && !selectedCodes.has(item.code) && (!q || item.lang.toLowerCase().includes(q) || item.code.includes(q))); }, [query, selectedCodes]); return (
{selected.map(s => ( {s.code} {!disabled && ( )} ))} {!disabled && ( )}
{selected.length > 0 && (
{selected.length} language{selected.length > 1 ? 's' : ''} selected
)} {dropOpen && (
setQuery(e.target.value)} placeholder="Search languages…" spellCheck={false} />
{popularFiltered.length > 0 && ( <>
Popular
{popularFiltered.map(item => ( ))} )}
All Languages
{filteredLangs.slice(0, 50).map(lc => ( ))} {filteredLangs.length > 50 && (
+{filteredLangs.length - 50} more — type to narrow
)} {filteredLangs.length === 0 && popularFiltered.length === 0 && (
No matches
)}
)}
); }