Menu
Na tej stronie
Menu to <details> z ostylowanym panelem. To świadomy wybór zamiast API popover: popover żyje w warstwie wierzchniej i nie da się go ustawić względem wyzwalacza bez anchor positioning, którego jeszcze nie ma wszędzie. <details> pozycjonuje się poprawnie już dziś i nie wymaga niczego doładowanego.
Wyzwalaczem jest to <summary>, ostylowane jak przycisk — więc etykietę i propsy przycisku podajesz do menu(), a nie przekazujesz gotowego button(). Summary jest już interaktywne, a przycisk w jego wnętrzu zagnieżdża dwie kontrolki tam, gdzie jest jedna akcja: niepoprawne znaczniki i dwa przystanki tabulacji na jedną rzecz.
Zamykanie kliknięciem poza i Escape pochodzą z handlera ontoggle, który importuje je przy pierwszym otwarciu menu — i tylko wtedy. Jeśli ten moduł nigdy nie dotrze, menu i tak otwiera się i zamyka własnym summary.
Podstawowe menu
menu({ trigger: 'Akcje' },
menuItem({ href: '#edit' }, 'Edytuj'),
menuItem({ href: '#duplicate' }, 'Duplikuj'),
menuSeparator(),
menuItem({ href: '#delete' }, 'Usuń'),
)Wyrównanie
Menu otwiera się od początkowej krawędzi swojego wyzwalacza. align: 'end' je odwraca, czego potrzebuje menu przy prawej krawędzi paska.
stack({ direction: 'row', gap: 'xl', justify: 'space-between', style: 'width: 100%' },
menu({ trigger: 'Do początku', variant: 'soft' },
menuItem({ href: '#a' }, 'Pierwszy'),
menuItem({ href: '#b' }, 'Drugi'),
),
menu({ trigger: 'Do końca', variant: 'soft', align: 'end' },
menuItem({ href: '#c' }, 'Pierwszy'),
menuItem({ href: '#d' }, 'Drugi'),
),
)Wyzwalacze z ikoną
Ikona bez tekstu w trigger potrzebuje label — staje się dostępną nazwą, której ikona sama nie potrafi dać.
stack({ direction: 'row', gap: 'md' },
menu({
align: 'end',
label: 'Więcej akcji',
variant: 'ghost',
icon: icon('more-horizontal'),
},
menuItem({ href: '#rename' }, 'Zmień nazwę'),
menuItem({ href: '#move' }, 'Przenieś'),
menuSeparator(),
menuItem({ href: '#archive' }, 'Archiwizuj'),
),
)Elementy z ikonami
menu({ trigger: 'Plik' },
menuItem({
href: '#new',
icon: icon('plus'),
}, 'Nowa strona'),
menuItem({
href: '#open',
icon: icon('folder'),
}, 'Otwórz…'),
menuSeparator(),
menuItem({
href: '#build',
icon: icon('zap'),
}, 'Zbuduj witrynę'),
)Przyciski zamiast odnośników
Element bez href renderuje <button> — na akcję, która dzieje się na stronie, a nie na nawigację.
menu({ trigger: 'Eksport', variant: 'soft', color: 'primary' },
menuItem({ onclick: "import('/su/toast.js').then(m=>m.toast('Wyeksportowano jako JSON.',{color:'success'}))" }, 'Jako JSON'),
menuItem({ onclick: "import('/su/toast.js').then(m=>m.toast('Wyeksportowano jako CSV.',{color:'success'}))" }, 'Jako CSV'),
)W pasku aplikacji
appBar({ brand: 'sitelo' },
appBarSpacer(),
appBarActions(
themeToggle(),
menu({
align: 'end',
label: 'Więcej',
variant: 'ghost',
icon: icon('more-horizontal'),
},
menuItem({ href: '/docs' }, 'Dokumentacja'),
menuItem({ href: '/examples' }, 'Przykłady'),
menuSeparator(),
menuItem({ href: 'https://github.com/paul-browne/sitelo' }, 'GitHub'),
),
),
)Dostępność
Panel ma role="menu", którego elementy mają role="menuitem", a summary niesie aria-haspopup. <details> nie jest natywnym widżetem menu, więc to rozsądne przybliżenie, a nie rzecz doskonała — dla zwykłej listy odnośników nav wewnątrz details jest równie poprawny i obiecuje mniej.
Propsy
menu() — propsy wyzwalacza to propsy przycisku:
| Prop | Typ | Domyślnie | Opis |
|---|---|---|---|
trigger | Child | — | Widoczna etykieta. Podaj tekst, a nie gotowy button(). |
icon | Child | — | Znaczniki przed etykietą albo same, dla wyzwalacza z samą ikoną. |
label | string | — | Dostępna nazwa. Wymagana, gdy jest ikona i nie ma tekstu w trigger. |
variant | 'solid' | 'soft' | 'outline' | 'ghost' | 'link' | 'outline' | Styl wyzwalacza. |
color | 'primary' | 'neutral' | 'success' | 'warning' | 'danger' | 'neutral' | Z której palety czerpie wyzwalacz. |
size | 'sm' | 'md' | 'lg' | 'md' | Rozmiar wyzwalacza. |
align | 'start' | 'end' | 'start' | Do której krawędzi wyzwalacza ustawia się panel. |
triggerClass | string | — | Dodatkowe klasy dla wyzwalacza, a nie dla opakowującego details. |
menuItem():
| Prop | Typ | Domyślnie | Opis |
|---|---|---|---|
href | string | — | Renderuje kotwicę; bez tego przycisk. |
icon | Child | — | Znaczniki przed etykietą. |
as | string | 'button' | Element do wyrenderowania, gdy nie ma href. |
menuSeparator() nie przyjmuje propsów — to włoskowa linia między grupami elementów.