124 lines
7.5 KiB
Markdown
124 lines
7.5 KiB
Markdown
# 🎨 Использование UI компонентов: Выпадающие списки (Dropdowns)
|
||
|
||
В проекте Magistr используется **премиальная кастомная дизайн-система** выпадающих списков. В связи с ограничениями браузеров на стилизацию стандартных элементов `<select>`, мы реализовали два типа компонентов, которые выглядят потрясающе (с эффектом glassmorphism, встроенными микро-анимациями и свечением), но интегрируются максимально просто.
|
||
|
||
---
|
||
|
||
## 1. Одинарные списки на React
|
||
|
||
В админке используется `frontend/react/shared/ui/CustomSelect.jsx`. Он сохраняет
|
||
стили `.custom-select-wrapper`, нативный `select` для формы и событие `change`,
|
||
но разметкой меню, выбранной подписью и состоянием `disabled` управляет React.
|
||
Глобальный `MutationObserver` из `admin/js/dropdown.js` больше не запускается.
|
||
Его прежнее применение к React-дереву оставляло устаревшие подписи и лишние DOM-узлы.
|
||
|
||
```jsx
|
||
import { CustomSelect } from '../../shared/ui/CustomSelect.jsx';
|
||
|
||
<div className="form-group">
|
||
<label htmlFor="my-new-select">Выберите опцию</label>
|
||
<CustomSelect
|
||
id="my-new-select"
|
||
value={selectedId}
|
||
disabled={loading}
|
||
onChange={event => setSelectedId(event.target.value)}
|
||
>
|
||
<option value="">Все значения</option>
|
||
{options.map(option => (
|
||
<option key={option.id} value={option.id}>{option.name}</option>
|
||
))}
|
||
</CustomSelect>
|
||
</div>
|
||
```
|
||
|
||
Список обновляется через `options` и `value`, без `innerHTML`. Пустое значение
|
||
можно выбрать для сброса фильтра; недоступные пункты помечаются `disabled`.
|
||
При количестве вариантов больше шести появляется поиск. Поддерживаются стрелки,
|
||
Home/End, Escape, закрытие снаружи и снятие обработчиков при размонтировании.
|
||
Для обязательного поля передайте `required`: пустое значение блокирует отправку,
|
||
показывает «Выберите значение» и переводит фокус на видимую кнопку селекта.
|
||
Ошибка связана с кнопкой через `aria-describedby` и снимается после выбора.
|
||
Для поиска по API используется отдельный `AsyncCombobox`.
|
||
|
||
---
|
||
|
||
## 2. Множественный выбор (Multi-Select с чекбоксами)
|
||
|
||
На React-страницах меню и закрытие снаружи управляются состоянием компонентов
|
||
(`EquipmentMultiSelect` в `ClassroomsTab.jsx`, `WorkloadMultiSelect` в
|
||
`AuditoriumWorkloadTab.jsx` и аналогичные компоненты остальных вкладок).
|
||
Глобальные `initMultiSelect` и `closeAllDropdownsOnOutsideClick` к React-дереву
|
||
не применяются. HTML-примеры ниже описывают прежний контракт CSS-классов;
|
||
новые элементы следует создавать в JSX и управлять ими через React-состояние.
|
||
|
||
Этот UI-компонент позволяет выбирать сразу несколько элементов из выпадающего списка. Он включает в себя кастомные красивые галочки (checkmarks) с неоновой подсветкой и кастомный скроллбар.
|
||
|
||
Этот компонент требует написания определённой HTML-структуры, так как нативного тега `select multiple` с похожей функциональностью не существует.
|
||
|
||
### Как добавить мульти-селект:
|
||
|
||
**1. HTML Структура:**
|
||
```html
|
||
<div class="form-group">
|
||
<label>Выберите оборудование</label>
|
||
<div class="custom-multi-select">
|
||
<!-- Кнопка-триггер (то, на что нажимаем) -->
|
||
<div class="select-box" id="my-multi-box">
|
||
<span class="select-text" id="my-multi-text">Выберите...</span>
|
||
<svg class="dropdown-icon" width="12" height="8" viewBox="0 0 12 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||
<path d="M1 1.5L6 6.5L11 1.5" stroke="#9ca3af" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||
</svg>
|
||
</div>
|
||
<!-- Само выпадающее меню -->
|
||
<div class="dropdown-menu" id="my-multi-menu">
|
||
<div id="my-multi-checkboxes" class="checkbox-group-vertical">
|
||
<!-- Сюда JS добавит чекбоксы -->
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
```
|
||
|
||
**2. Инициализация (в вашем JS-файле):**
|
||
Используйте готовую утилиту `initMultiSelect` из `utils.js` (она обрабатывает клики и открытие/закрытие):
|
||
```javascript
|
||
import { initMultiSelect } from '../utils.js';
|
||
|
||
// Передаем ID: box, menu, text, container
|
||
initMultiSelect('my-multi-box', 'my-multi-menu', 'my-multi-text', 'my-multi-checkboxes');
|
||
```
|
||
|
||
**3. Рендеринг элементов с кастомными галочками:**
|
||
Чтобы нарисовать сами чекбоксы, нужно использовать класс `.checkbox-item` и обязательный пустой `span.checkmark`. Пример генерации HTML:
|
||
|
||
```javascript
|
||
const container = document.getElementById('my-multi-checkboxes');
|
||
const items = [{id: 1, name: "Проектор"}, {id: 2, name: "Компьютер"}];
|
||
|
||
container.innerHTML = items.map(item => `
|
||
<label class="checkbox-item">
|
||
<input type="checkbox" value="${item.id}">
|
||
<!-- Обязательный элемент для красивой галочки: -->
|
||
<span class="checkmark"></span>
|
||
<span class="checkbox-label">${item.name}</span>
|
||
</label>
|
||
`).join('');
|
||
```
|
||
|
||
### Как прочитать выбранные значения:
|
||
Просто соберите массив value у выбранных чекбоксов внутри контейнера:
|
||
|
||
```javascript
|
||
const checkedBoxes = Array.from(document.querySelectorAll('#my-multi-checkboxes input:checked'));
|
||
const selectedIds = checkedBoxes.map(chk => parseInt(chk.value, 10));
|
||
console.log(selectedIds); // [1, 2]
|
||
```
|
||
|
||
---
|
||
|
||
## Итог и правила
|
||
|
||
1. **Никогда не пытайтесь "красить" нативные теги `<option>`.** Браузеры (особенно Safari и Chrome) не позволяют этого сделать.
|
||
2. Для **отдельного выбора (1 из N)** всегда используйте стандартный `select`. Наша обёртка сделает всю магию сама.
|
||
3. Для **множественного выбора (N из M)** используйте HTML-шаблон `.custom-multi-select` (с `span.checkmark`).
|