Как настраивал кастомный select?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Архитектура кастомного select
Кастомный select требует управления состоянием, видимостью выпадающего списка, обработки клавиатурных событий и фильтрации опций. Самый надёжный подход — разделить на три компонента: Select (контейнер), SelectTrigger (кнопка открытия) и SelectContent (список опций).
Реализация с React Hooks
import { useState, useRef, useEffect } from 'react';
interface SelectProps {
value: string;
onChange: (value: string) => void;
options: Array<{ value: string; label: string }>;
placeholder?: string;
}
export function Select({ value, onChange, options, placeholder = 'Выберите...' }: SelectProps) {
const [isOpen, setIsOpen] = useState(false);
const [searchTerm, setSearchTerm] = useState('');
const containerRef = useRef<HTMLDivElement>(null);
const filteredOptions = options.filter(opt =>
opt.label.toLowerCase().includes(searchTerm.toLowerCase())
);
// Закрытие при клике вне компонента
useEffect(() => {
function handleClickOutside(e: MouseEvent) {
if (containerRef.current && !containerRef.current.contains(e.target as Node)) {
setIsOpen(false);
}
}
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}, []);
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Escape') setIsOpen(false);
if (e.key === 'Enter' && filteredOptions.length > 0) {
onChange(filteredOptions[0].value);
setIsOpen(false);
}
};
return (
<div ref={containerRef} className="relative w-full">
<button
onClick={() => setIsOpen(!isOpen)}
className="w-full px-4 py-2 border rounded-lg text-left"
>
{value ? options.find(o => o.value === value)?.label : placeholder}
</button>
{isOpen && (
<div className="absolute top-full mt-1 w-full border rounded-lg bg-white shadow-lg z-10">
<input
type="text"
placeholder="Поиск..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
onKeyDown={handleKeyDown}
className="w-full px-4 py-2 border-b"
/>
<ul className="max-h-48 overflow-y-auto">
{filteredOptions.map(opt => (
<li
key={opt.value}
onClick={() => {
onChange(opt.value);
setIsOpen(false);
setSearchTerm('');
}}
className="px-4 py-2 hover:bg-gray-100 cursor-pointer"
>
{opt.label}
</li>
))}
</ul>
</div>
)}
</div>
);
}
Ключевые особенности
Управление фокусом: Автоматическое закрытие при клике вне элемента через useEffect и useRef. Это стандартная практика для всех выпадающих меню.
Фильтрация опций: Поле поиска внутри select значительно улучшает UX при большом количестве опций. Фильтрация работает независимо от основного значения.
Доступность клавиатуры: Клавиша Escape закрывает список, Enter выбирает первую опцию. Это критично для пользователей, работающих только с клавиатурой.
Performance: Не переиспользуется состояние для каждой опции — список рендерится один раз при изменении filteredOptions.
Расширение функционала
Для более сложных случаев добавляешь:
- Virtual scrolling (если 1000+ опций)
- Группировку опций по категориям
- Иконки рядом с опциями
- Асинхронную загрузку опций
Главное — держать компонент простым и композируемым. Это позволяет легко адаптировать его под конкретные требования проекта.