Spaces:
Runtime error
Runtime error
import { useState, useEffect, useRef } from 'react' | |
const AutoComplete = ({ languages, onComplete }) => { | |
const [isOpen, setIsOpen] = useState(false) | |
const [searchTerm, setSearchTerm] = useState('') | |
const [selectedLanguage, setSelectedLanguage] = useState(null) | |
const [filteredLanguages, setFilteredLanguages] = useState([]) | |
const dropdownRef = useRef(null) | |
const inputRef = useRef(null) | |
useEffect(() => { | |
if (!languages) return | |
// Most spoken languages (by number of speakers) - you can adjust this list | |
const mostSpokenCodes = [ | |
'en', | |
'zh', | |
'hi', | |
'es', | |
'ar', | |
'bn', | |
'pt', | |
'ru', | |
'ja', | |
'pa', | |
'de', | |
'jv', | |
'ko', | |
'fr', | |
'te', | |
'mr', | |
'tr', | |
'ta', | |
'vi', | |
'ur' | |
] | |
if (searchTerm.trim() === '') { | |
// Show most spoken languages first, then others | |
const mostSpoken = mostSpokenCodes | |
.map(code => languages.find(lang => lang.bcp_47 === code)) | |
.filter(Boolean) | |
const others = languages | |
.filter(lang => !mostSpokenCodes.includes(lang.bcp_47)) | |
.sort((a, b) => a.language_name.localeCompare(b.language_name)) | |
setFilteredLanguages([...mostSpoken, ...others]) | |
} else { | |
const query = searchTerm.toLowerCase() | |
const matches = languages.filter( | |
language => | |
language.language_name.toLowerCase().includes(query) || | |
language.autonym.toLowerCase().includes(query) || | |
language.bcp_47.toLowerCase().includes(query) | |
) | |
setFilteredLanguages(matches) | |
} | |
}, [searchTerm, languages]) | |
useEffect(() => { | |
const handleClickOutside = event => { | |
if (dropdownRef.current && !dropdownRef.current.contains(event.target)) { | |
setIsOpen(false) | |
setSearchTerm('') | |
} | |
} | |
document.addEventListener('mousedown', handleClickOutside) | |
return () => document.removeEventListener('mousedown', handleClickOutside) | |
}, []) | |
const handleSelect = language => { | |
setSelectedLanguage(language) | |
setIsOpen(false) | |
setSearchTerm('') | |
onComplete([language]) | |
} | |
const handleClear = e => { | |
e.stopPropagation() | |
setSelectedLanguage(null) | |
onComplete([]) | |
} | |
const handleContainerClick = () => { | |
setIsOpen(true) | |
if (!selectedLanguage) { | |
setTimeout(() => inputRef.current?.focus(), 100) | |
} | |
} | |
const handleInputChange = e => { | |
// If user starts typing while a language is selected, clear the selection to enable search | |
if (selectedLanguage && e.target.value.length > 0) { | |
setSelectedLanguage(null) | |
onComplete([]) | |
} | |
setSearchTerm(e.target.value) | |
} | |
const handleKeyDown = e => { | |
if (e.key === 'Escape') { | |
setIsOpen(false) | |
setSearchTerm('') | |
} | |
} | |
const containerStyle = { | |
position: 'relative', | |
display: 'inline-block', | |
minWidth: '400px', | |
maxWidth: '600px' | |
} | |
const buttonStyle = { | |
color: selectedLanguage ? '#333' : '#666', | |
border: '1px solid #ddd', | |
padding: '0.75rem 1rem', | |
borderRadius: '4px', | |
fontSize: '0.95rem', | |
backgroundColor: '#fff', | |
cursor: 'pointer', | |
display: 'flex', | |
alignItems: 'center', | |
justifyContent: 'space-between', | |
width: '100%', | |
minHeight: '44px', | |
transition: 'border-color 0.2s ease, box-shadow 0.2s ease' | |
} | |
const inputStyle = { | |
border: 'none', | |
outline: 'none', | |
fontSize: '0.95rem', | |
width: '100%', | |
backgroundColor: 'transparent', | |
color: '#333' | |
} | |
const dropdownStyle = { | |
position: 'absolute', | |
top: '100%', | |
left: 0, | |
right: 0, | |
backgroundColor: '#fff', | |
border: '1px solid #ddd', | |
borderTop: 'none', | |
borderRadius: '0 0 4px 4px', | |
maxHeight: '300px', | |
overflowY: 'auto', | |
zIndex: 1000, | |
boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)' | |
} | |
const itemStyle = { | |
padding: '0.75rem 1rem', | |
cursor: 'pointer', | |
borderBottom: '1px solid #f0f0f0', | |
display: 'flex', | |
justifyContent: 'space-between', | |
alignItems: 'center', | |
transition: 'background-color 0.2s ease' | |
} | |
const clearButtonStyle = { | |
background: 'none', | |
border: 'none', | |
color: '#999', | |
cursor: 'pointer', | |
padding: '0.25rem', | |
borderRadius: '50%', | |
display: 'flex', | |
alignItems: 'center', | |
justifyContent: 'center', | |
width: '20px', | |
height: '20px', | |
fontSize: '14px', | |
marginLeft: '0.5rem' | |
} | |
return ( | |
<div style={containerStyle} ref={dropdownRef}> | |
<div | |
style={buttonStyle} | |
onClick={handleContainerClick} | |
onMouseEnter={e => { | |
if (!selectedLanguage) { | |
e.target.style.borderColor = '#bbb' | |
} | |
}} | |
onMouseLeave={e => { | |
e.target.style.borderColor = '#ddd' | |
}} | |
> | |
{selectedLanguage && !isOpen ? ( | |
<> | |
<span style={{ fontWeight: '500' }}> | |
{selectedLanguage.language_name} Leaderboard | |
</span> | |
<button | |
style={clearButtonStyle} | |
onClick={handleClear} | |
onMouseEnter={e => { | |
e.target.style.backgroundColor = '#f0f0f0' | |
}} | |
onMouseLeave={e => { | |
e.target.style.backgroundColor = 'transparent' | |
}} | |
title='View overall leaderboard' | |
> | |
× | |
</button> | |
</> | |
) : isOpen ? ( | |
<input | |
ref={inputRef} | |
style={inputStyle} | |
placeholder={ | |
selectedLanguage | |
? 'Type to search other languages...' | |
: 'Type to search languages...' | |
} | |
value={searchTerm} | |
onChange={handleInputChange} | |
onKeyDown={handleKeyDown} | |
/> | |
) : ( | |
<span>Go to leaderboard for specific language...</span> | |
)} | |
{(!selectedLanguage || isOpen) && ( | |
<span style={{ color: '#999', fontSize: '12px' }}> | |
{isOpen ? '▲' : '▼'} | |
</span> | |
)} | |
</div> | |
{isOpen && ( | |
<div style={dropdownStyle}> | |
{filteredLanguages.length === 0 ? ( | |
<div style={{ ...itemStyle, color: '#999', cursor: 'default' }}> | |
No languages found | |
</div> | |
) : ( | |
filteredLanguages.slice(0, 20).map((language, index) => ( | |
<div | |
key={language.bcp_47} | |
style={itemStyle} | |
onClick={() => handleSelect(language)} | |
onMouseEnter={e => { | |
e.target.style.backgroundColor = '#f8f9fa' | |
}} | |
onMouseLeave={e => { | |
e.target.style.backgroundColor = 'transparent' | |
}} | |
> | |
<div> | |
<div style={{ fontWeight: '500', marginBottom: '2px' }}> | |
{language.language_name} Leaderboard | |
</div> | |
<div style={{ fontSize: '0.85rem', color: '#666' }}> | |
{language.autonym} | |
</div> | |
</div> | |
<div style={{ color: '#999', fontSize: '0.8rem' }}> | |
{language.bcp_47} | |
</div> | |
</div> | |
)) | |
)} | |
{filteredLanguages.length > 20 && ( | |
<div | |
style={{ | |
...itemStyle, | |
color: '#999', | |
cursor: 'default', | |
fontStyle: 'italic' | |
}} | |
> | |
Type to search for more languages... | |
</div> | |
)} | |
</div> | |
)} | |
</div> | |
) | |
} | |
export default AutoComplete | |