Menu

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.

Do początku
Do końca
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ę.

Eksport
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:

PropTypDomyślnieOpis
triggerChild—Widoczna etykieta. Podaj tekst, a nie gotowy button().
iconChild—Znaczniki przed etykietą albo same, dla wyzwalacza z samą ikoną.
labelstring—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.
triggerClassstring—Dodatkowe klasy dla wyzwalacza, a nie dla opakowującego details.

menuItem():

PropTypDomyślnieOpis
hrefstring—Renderuje kotwicę; bez tego przycisk.
iconChild—Znaczniki przed etykietą.
asstring'button'Element do wyrenderowania, gdy nie ma href.

menuSeparator() nie przyjmuje propsów — to włoskowa linia między grupami elementów.