Components › Forms & input › Button › Menu button

Menu button

canon

The 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 …

Import

import { MenuButton, MenuItems, MenuPanel } from '@acrm/ui'

Props

MenuButton packages/ui/src/components/MenuButton.tsx

NameTypeDefaultDescription
label *string——
iconstring—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).
iconOnlyboolean—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ů).
forceMenuboolean—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).
headerReactNode—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).
disabledboolean——
loadingboolean——
classNamestring——
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

NameTypeDefaultDescription
items *MenuButtonItem[]——
onDone *() => void——
keepFocusboolean—Řá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

NameTypeDefaultDescription
anchorEl *HTMLElement | null——
open *boolean——
onClose *() => void——
label *string—Jméno nabídky pro čtečku; na telefonu nadpis šuplíku.
items *MenuButtonItem[]——
headerReactNode——
align'start' | 'end''start'—
keepFocusboolean—Řá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.

CSS classes

.acrm-popover-item.acrm-popover-item.danger.acrm-popover-item.strong.acrm-popover-sep.acrm-popover-empty.acrm-kbdacrm-popover-reason

Files