|
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 |
|
|
|
|
|
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() === '') { |
|
|
|
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 (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 |
|
|