Dropdowny
View on GitHubDodaj dropdown
Przećwiczony przykład: wybór wariantu Dropdown i podpięcie każdego z nich. Nigdy nie buduj pływającego panelu ręcznie — nowy dropdown ma wyglądać i zachowywać się dokładnie jak istniejące.
Wybierz wariant
| Potrzebujesz… | Wariant |
|---|---|
| Wybrać dokładnie jedną wartość | select |
| Przełączać kilka wartości | multiselect |
| Listy akcji (bez stanu zaznaczenia) | menu |
| Własnej zawartości w tej samej powierzchni (formularze, podsumowania) | panel |
trigger to dowolny element — FilterButton, przycisk z avatarem, pigułka. Dropdown sprawia, że otwiera panel, i dodaje atrybuty ARIA; przekaż funkcję (({ isOpen }) => …), jeśli trigger zmienia się przy otwarciu.
Pojedynczy wybór
import Dropdown from '@/Components/Molecules/Dropdown/Dropdown';
import { useProjectPriorities } from '@/context/ProjectPrioritiesContext';
// A project's priorities are its own (see ../priorities/README.md), so the
// options come from the provider rather than a hardcoded list.
const { priorities } = useProjectPriorities();
<Dropdown
variant="select"
title="Priority"
options={priorities.map((priority) => ({
value: priority.name,
label: priority.name,
}))}
selectedValues={[priority]}
onSelect={(value) => setPriority(value)}
trigger={<button type="button">{priority}</button>}
/>
Pole wyszukiwania pojawia się samo, gdy opcji jest więcej niż sześć (searchable="auto"); wymuś je przez searchable albo ukryj przez searchable={false}. Dla bogatych etykiet (avatary, odznaki) ustaw searchLabel, żeby wyszukiwanie miało tekst do dopasowania. Dodaj onClear, aby pokazać akcję "Clear", gdy coś jest wybrane.
Wielokrotny wybór
<Dropdown
variant="multiselect"
title="Labels"
showCount
options={labels}
selectedValues={selectedLabels}
onSelect={toggleLabel}
onClear={() => setSelectedLabels([])}
onSelectAll={toggleAll}
trigger={<FilterButton label="Labels" hasMenu />}
/>
Zostaje otwarty między wyborami. onSelectAll dodaje stopkę "Select all"; showCount dodaje linię "N results", której używają filtry.
Menu
<Dropdown
variant="menu"
placement="top"
options={[
{ value: 'settings', label: 'Settings', icon: 'Settings' },
{ value: 'logout', label: 'Log out', icon: 'LogOut', tone: 'danger' },
]}
onSelect={(action) => handle(action)}
trigger={<button type="button">Account</button>}
/>
Wiersze nie mają wskaźnika zaznaczenia. Dla menu łączącego sekcje użyj wierszy kind: 'heading' / kind: 'separator' oraz role per opcja (menuitemradio, menuitemcheckbox), indicator: 'check' i closeOnSelect: false — zobacz MemberRoleDropdown.
Panel
<Dropdown variant="panel" ariaLabel="Saved views" width={288} trigger={…}>
{({ close }) => <MyForm onDone={close} />}
</Dropdown>
Dostajesz powierzchnię, pozycjonowanie oraz obsługę Escape / kliku poza; zawartość jest Twoja. Tab nie zamyka panelu, więc formularze działają.
Stan kontrolowany i specjalne kotwice
open+onOpenChangeoddają stan otwarcia rodzicowi (pasek filtrów zamyka jeden dropdown, gdy otwiera się drugi).anchorPoint={{ x, y }}otwiera go w punkcie zamiast przy triggerze —ListRowużywa tego dla menu kontekstowego pod prawym przyciskiem.align="end",placement="top"iwidth(liczba albo'trigger') dostrajają panel; odwraca się nad trigger i zostaje w obrębie viewportu.
Warto wiedzieć
- Kliknięcia i klawisze w panelu oraz kliknięcia na triggerze nie propagują się do przodków w drzewie komponentów (wiersz, karta), mimo że panel jest w portalu. Panel zamyka się, gdy trigger wyjdzie poza okno przy scrollu.
- ArrowUp/Down i Home/End przechodzą między wierszami, Escape zamyka i zwraca fokus do triggera, a Tab zamyka listę i idzie dalej.
- Panel renderuje się nad modalami (inline
zIndex: 9999).
Przetestuj
resources/js/Components/Molecules/Dropdown/Dropdown.test.tsxpokrywa każdy wariant; dodaj przypadek tam, gdy rozszerzasz samDropdown.- W testach nakładki szukaj wierszy po roli:
optiondlaselect/multiselect,menuitem/menuitemradio/menuitemcheckboxdlamenu.
