Menu button
canonThe one action menu of the app: a labelled button with a chevron, or the kebab „⋯“ (iconOnly), opening a menu of actions; no action is the default one.
AD + owner 28 Sep 2026 („sjednotit popover do menu-button“): Menu button is THE action menu — the row anatomy moved here from Popover. Row: .ic (icon | iconNode) · label (.tx > .nm when something is on the right) · .tag · .acrm-kbd (aria-hidden) · .chk (checked rows); variants danger (auto separator before the first danger row), strong, disabled + title (tooltip + aria-describedby); empty/loading row {empty} = .acrm-popover-empty. Kebab = iconOnly → canonical IconButton (sm / framed), label = aria-label + phone sheet title, aria-haspopup/expanded + stopPropagation by the component. „Dimmed but clickable“ rows do not exist. Phone = BottomSheet. Mechanical refactor moved 18 hand-built Popover menus onto it (acrm MR, 28 Sep 2026); sub-menu (mail „Přesunout do složky ›“, reservation „Předat…“), menu header (account menu) and insert menus wait for an approved shape (MR-B). History: FA204, 26 Sep 2026 (acrm !3890) 1:1 port of „Přidat aktivitu“ (P144, TL-12); custom trigger({open, menu, onClick}) acrm !3899 (dialog chip row, rail link !4014). forceMenu (menu even with one item) and danger items (2026-09-28, acrm !4088). Touch (AD 28 Sep 2026, acrm !4111): in the phone sheet a disabled item shows its title as a visible second line .acrm-popover-reason (--hint, --ink-600, aligned with the label, outside the button) — MenuItems sheet; desktop keeps the tooltip. 3 Oct 2026: MenuButtonItem count / countTone / muted / actions / actionsLabel (acrm !4527). Registry repaired 17 Oct 2026 (review F6): added exported MenuPanel to react[].
Used in 63 files · 33 areas · 69× — components/AppShell, components/FilterBar, components/NavItemMenu, components/ReservationControl, components/accounting, components/assistant …
@acrm/ui <MenuButton> <MenuItems> <MenuPanel>.acrm-popover-item .acrm-popover-item.danger .acrm-popover-item.strong .acrm-popover-sep +3SourceStoryImport
import { MenuButton, MenuItems, MenuPanel } from '@acrm/ui'
Props
MenuButton packages/ui/src/components/MenuButton.tsx
| Name | Type | Default | Description |
|---|---|---|---|
label * | string | — | — |
icon | string | — | Glyf před popiskem; chevron na konci kreslí komponenta sama. U iconOnly je to glyf ikonového spouštěče (výchozí dots-vertical = kebab „⋮"; jeden symbol pro „více" v celé appce — AD 5. 10. 2026). |
iconOnly | boolean | — | Spouštěč jen z ikony — kanonický IconButton (kebab „⋯" v řádku, v hlavičce karty, v okně). label se pak NEKRESLÍ, ale je povinným jménem pro čtečku (aria-label) i titulkem šuplíku na telefonu; aria-haspopup/aria-expanded řeší komponenta, ne host. |
iconVariant | 'framed' | 'secondary' | — | Tvar ikonového spouštěče: výchozí tichý sm, framed = rámovaný (okno, lišta nástrojů). |
forceMenu | boolean | — | Nabídka i s JEDINOU položkou (⋮ „Další akce" v liště sekce, acrm !4088): spouštěč s obecným glyfem nesmí akci spustit naslepo — uživatel ji nejdřív uvidí v nabídce. U iconOnly platí vždy, tady jde zapnout i pro vlastní spouštěč (trigger). |
header | ReactNode | — | Blok NAD položkami (NÁVRH MR-B): identita v účtovém menu (avatar, jméno, e-mail). Není to nadpis nabídky (ten nabídka nemá nikdy) — je to informace, pod kým jsi přihlášený. Jen v kořeni, v podnabídce ne. |
items * | MenuButtonItem[] | — | — |
variant | 'default' | 'primary' | 'ghost' | 'default' | Jen tvary Buttonu, které nabídka unese — destruktivní akce do spouštěče nepatří. |
align | 'start' | 'end' | 'start' | Zarovnání nabídky ke kotvě: výchozí levá hrana tlačítka, k pravé se překlopí sama, jen když se vpravo nevejde (place.ts; zadavatel 26. 9. 2026). |
disabled | boolean | — | — |
loading | boolean | — | — |
className | string | — | — |
trigger | (p: { open: boolean; menu: boolean; disabled: boolean; onClick: () => void; }) => ReactNode | — | Vlastní spouštěč (FA204 kolo 2, 26. 9. 2026): v řadě chipů dialogu je kanonem MetaChip (.tk-chip), ne <Button> — „Přidat audio" v řadě poznámky nesmí z řady vypadnout. Komponenta dál řídí nabídku, položky i pravidlo „jedna položka = žádná nabídka"; host jen nakreslí spouštěč a zavolá onClick (ten buď nabídku přepne, nebo rovnou spustí jedinou položku). menu říká, jestli nabídka existuje (pro aria-haspopup), open její stav. Chevron si host kreslí jen tehdy, když ho jeho tvar má — chip řady ho nemá. disabled platí i tady: onClick pak nic neudělá a host dostane disabled pro vlastní vzhled (vč. zakázané jediné položky). label (jen jako název nabídky), icon, variant a loading se s vlastním spouštěčem nekreslí. |
MenuItems packages/ui/src/components/MenuButton.tsx
| Name | Type | Default | Description |
|---|---|---|---|
items * | MenuButtonItem[] | — | — |
onDone * | () => void | — | — |
keepFocus | boolean | — | Řádky neberou fokus při stisku — viz MenuPanel.keepFocus. |
onEnter | (it: { key: string; label: string; icon?: string | undefined; onClick?: (() => void) | undefined; href?: string | undefined; target?: '_blank' | undefined; download?: string | boolean | undefined; disabled?: boolean | undefined; checked?: boolean | undefined; danger?: boolean | undefined; strong?: boolean | undefined; kbd?: string | undefined; title?: string | undefined; iconNode?: ReactNode; ariaLabel?: string | undefined; submenu?: MenuButtonItem[] | undefined; switchOn?: boolean | undefined; count?: ReactNode; muted?: boolean | undefined; countTone?: 'accent' | undefined; actions?: MenuButtonItem[] | undefined; actionsLabel?: string | undefined; }) => void | — | Otevření podnabídky (drill-in) — řídí MenuButton; bez něj se submenu nekreslí jako vstup. |
MenuPanel packages/ui/src/components/MenuButton.tsx
| Name | Type | Default | Description |
|---|---|---|---|
anchorEl * | HTMLElement | null | — | — |
open * | boolean | — | — |
onClose * | () => void | — | — |
label * | string | — | Jméno nabídky pro čtečku; na telefonu nadpis šuplíku. |
items * | MenuButtonItem[] | — | — |
header | ReactNode | — | — |
align | 'start' | 'end' | 'start' | — |
keepFocus | boolean | — | Řádky neberou fokus při stisku (mousedown bez výchozí akce): nabídka otevřená z EDITORU („+ blok" v poznámce) musí nechat kurzor a výběr v textu, jinak by vložení mířilo jinam. |