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 +3SourceStoryDefault
export const Default = () => <MenuButton label="Přidat aktivitu" items={ACTIVITY} />;Variants
export const Variants = () => (
<div style={grid}>
<Labeled label="default — label + trailing chevron">
<MenuButton label="Přidat aktivitu" items={ACTIVITY} />
</Labeled>
<Labeled label="with a leading icon">
<MenuButton label="Přidat aktivitu" icon="plus" items={ACTIVITY} />
</Labeled>
<Labeled label="primary — at most once per container">
<MenuButton label="Přidat aktivitu" variant="primary" items={ACTIVITY} />
</Labeled>
<Labeled label="ghost — quiet, where the action is secondary">
<MenuButton label="Přidat aktivitu" variant="ghost" items={ACTIVITY} />
</Labeled>
<Labeled label="icon only — kebab „⋯“ (IconButton sm), label = aria-label + sheet title">
<MenuButton label="Další akce" iconOnly items={ANATOMY} />
</Labeled>
<Labeled label="icon only, framed — window / toolbar">
<MenuButton label="Další akce" iconOnly iconVariant="framed" items={ANATOMY} />
</Labeled>
<Labeled label="one item — no menu, a plain Button that runs it">
<MenuButton label="Přidat aktivitu" items={ACTIVITY.slice(0, 1)} />
</Labeled>
</div>
);export const RowActionsCount = () => (
<div style={row}>
<Labeled label="row action + trailing ⋮, count, checked, muted (archived)">
<StaticPopover width={300}>
<MenuItems items={THREADS} onDone={noop} onEnter={noop} />
</StaticPopover>
</Labeled>
<Labeled label="⋮ — the same panel drills into the row's actions">
<StaticPopover width={300}>
<button type="button" className="acrm-popover-item" role="menuitem">
<span className="ic">
<Icon name="arrow-left" />
</span>
Nabídka pro Kovotech
</button>
<div className="acrm-popover-sep" />
<MenuItems
items={[
{ key: 'r', label: 'Přejmenovat…', icon: 'pencil', onClick: noop },
{ key: 'a', label: 'Archivovat', icon: 'archive', onClick: noop },
]}
onDone={noop}
/>
</StaticPopover>
</Labeled>
</div>
);States
export const States = () => (
<div style={row}>
<Labeled label="open — items with the .ic glyph, groups split by .acrm-popover-sep">
<div style={{ display: 'grid', gap: 'var(--sp-2)', justifyItems: 'end' }}>
<MenuButton label="Přidat aktivitu" items={ACTIVITY} />
<StaticPopover width={220}>
<MenuItem icon="calendar" role="menuitem" check={false}>
Schůzka
</MenuItem>
<MenuItem icon="circle-check" role="menuitem" check={false}>
Úkol
</MenuItem>
<div className="acrm-popover-sep" />
<MenuItem icon="phone" role="menuitem" check={false}>
Zaznamenat kontakt
</MenuItem>
</StaticPopover>
</div>
</Labeled>
<Labeled label="disabled">
<MenuButton label="Přidat aktivitu" items={ACTIVITY} disabled />
</Labeled>
<Labeled label="loading">
<MenuButton label="Přidat aktivitu" items={ACTIVITY} loading />
</Labeled>
<Labeled label="phone sheet — one line per item; a disabled item's reason is its title, never a second line">
{/* Statická replika šuplíku (pod 720 px je nabídka BottomSheet). Položky v šuplíku stojí na
jeho hraně (mobilní vrstva, max-width: 720px) — tady to drží inline pravidlo repliky. */}
<div className="acrm-sheet story-sheet" style={SHEET}>
<style>{`.story-sheet .acrm-popover-item { margin-inline: calc(-1 * var(--sp-3)); width: calc(100% + 2 * var(--sp-3)); }`}</style>
<div className="phe-sheethead">Akce milníku</div>
<MenuItems
onDone={noop}
items={[
{
key: 'up',
label: 'Posunout nahoru',
icon: 'arrow-up',
disabled: true,
title: 'Milník je první',
onClick: noop,
},
{ key: 'down', label: 'Posunout dolů', icon: 'arrow-down', onClick: noop },
{ key: 'del', label: 'Smazat milník', icon: 'trash', danger: true, onClick: noop },
]}
/>
</div>
</Labeled>
</div>
);Composition
export const Composition = () => (
<div
style={{
width: 520,
border: '1px solid var(--border)',
borderRadius: 'var(--radius-md)',
background: 'var(--bg-canvas)',
}}
>
{/* FA204 — the empty state offers the action through `action.menu`, never a menu composed
under it by hand. */}
<EmptyState
icon="activity"
title="Zatím žádná aktivita"
subtitle="Naplánuj schůzku nebo úkol, nebo zapiš kontakt."
action={{ label: 'Přidat aktivitu', menu: ACTIVITY }}
/>
</div>
);