File size: 3,617 Bytes
0842d68 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 | import { useState, useRef, useEffect } from "react";
import { ChevronDown } from "lucide-react";
import {
LANGUAGES,
type LanguageCode,
LANGUAGES_WITH_AUTO,
} from "../constants";
interface LanguageSelectorProps {
value: LanguageCode;
onChange: (language: LanguageCode) => void;
includeAuto?: boolean;
}
export default function LanguageSelector({
value,
onChange,
includeAuto = false,
}: LanguageSelectorProps) {
const [isOpen, setIsOpen] = useState(false);
const [search, setSearch] = useState("");
const containerRef = useRef<HTMLDivElement>(null);
const searchInputRef = useRef<HTMLInputElement>(null);
const languages = includeAuto ? LANGUAGES_WITH_AUTO : LANGUAGES;
const getLanguageDisplay = (code: LanguageCode) => {
return languages.find((lang) => lang.code === code)?.name || code;
};
const filterLanguages = (searchTerm: string) => {
const searchLower = searchTerm.toLowerCase();
return languages.filter(
(lang) =>
lang.name.toLowerCase().includes(searchLower) ||
lang.code.toLowerCase().includes(searchLower)
);
};
const filteredLanguages = search ? filterLanguages(search) : languages;
const handleSelect = (language: LanguageCode) => {
onChange(language);
setIsOpen(false);
setSearch("");
};
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (
containerRef.current &&
!containerRef.current.contains(event.target as Node)
) {
setIsOpen(false);
setSearch("");
}
};
if (isOpen) {
document.addEventListener("mousedown", handleClickOutside);
setTimeout(() => searchInputRef.current?.focus(), 0);
}
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, [isOpen]);
return (
<div className="relative" ref={containerRef}>
<button
onClick={() => setIsOpen(!isOpen)}
className="inline-flex items-center gap-2 px-3 py-2 text-sm font-medium text-primary hover:bg-muted rounded-md transition-colors"
>
<span>{getLanguageDisplay(value)}</span>
<ChevronDown
className={`w-4 h-4 transition-transform ${isOpen ? "rotate-180" : ""}`}
/>
</button>
{isOpen && (
<div className="absolute top-full left-0 mt-2 w-64 bg-white border border-border rounded-md shadow-lg z-50">
<div className="p-2 border-b border-border">
<input
ref={searchInputRef}
type="text"
placeholder="Search languages..."
value={search}
onChange={(e) => setSearch(e.target.value)}
className="w-full px-3 py-2 text-sm border border-border rounded-md focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary"
/>
</div>
<div className="max-h-60 overflow-auto">
{filteredLanguages.map((lang) => (
<button
key={lang.code}
onClick={() => handleSelect(lang.code)}
className={`w-full px-3 py-2 text-left text-sm hover:bg-muted transition-colors ${
value === lang.code ? "bg-primary-50 text-primary-800" : ""
}`}
>
{lang.name}
</button>
))}
{filteredLanguages.length === 0 && (
<div className="px-3 py-4 text-sm text-muted-foreground text-center">
No languages found
</div>
)}
</div>
</div>
)}
</div>
);
}
|