@stralos-co-nz/ui exports RowMenu, the kebab that carries a row’s actions in DataTable and the scheduler’s resource grid. Every row action surface in the suite — rowActions, sectionActions, and the scheduler’s per-resource menu — is built from it.
Props
Each
RowMenuItem accepts:
Use instead of the seven row-action vocabularies in the estate: a View link, an Edit button, Open, Open plus a separate kebab, two inline buttons, or nothing at all.
How activation works (from 0.34.0)
Each item’sonSelect handler is wired to the item’s own onClick event. This replaces the previous wiring through Ark’s Menu.Root onSelect, which dropped activations silently when the menu machine had no highlighted item.
The old path failed in three cases that look unrelated in tests and in production:
- Assistive technology — an activate gesture (
Space,Enter, or a pointer click) with no prior hover over the item left the machine’shighlightedValueasnull. The menu closed; the handler was not called. - Programmatic activation — any
click()orfireEvent.click()that arrived without a precedingpointermoveat distinct coordinates hit the same gap. - Later selections in a test file —
@zag-js/focus-visiblestoreslastPointerPositionat module scope. In happy-dom, where every pointer event reports(0, 0), only the firstpointermovein a module updated the interaction modality. Every later pointer event was treated as stationary, so the modality reverted to"virtual"when the menu opened and highlighted nothing.
RowMenu and the scheduler’s resource menu wire each handler directly to the item’s onClick. The keyboard path is unchanged — Ark activates the highlighted item by calling element.click() on it, which fires onClick. A disabled item is now stopped by RowMenu’s own guard rather than relying on Ark refusing to emit, so the disabled prop is load-bearing.
Testing a RowMenu (from 0.34.0)
Open the trigger, then click the item.fireEvent is sufficient — @testing-library/user-event is not required, and the pattern works any number of times in one test file.
userEvent.click also works. The keyboard path is {ArrowDown}{Enter} after opening the trigger.
Assert toHaveBeenCalledTimes(1), not toHaveBeenCalled(). The failure mode this replaced was a silent zero; a future rewiring could produce a two. Both are invisible to toHaveBeenCalled.
Add a positive control alongside any “handler did not fire” assertion. A click that reaches nothing satisfies every negative assertion. Without a positive control you cannot distinguish a correctly blocked action from a broken gesture.
Migrating older tests
Tests written against 0.33.0 and earlier used workarounds: keyboard-only activation, a single selection per test file, or a fullpointerDown/pointerUp/click sequence. Those workarounds continue to pass — the fix does not change what the workaround gestures do. Remove them when you are already editing the test for an unrelated reason; do not leave them as the sole form of coverage.
Accessible name
DataTable passes a label derived from the row. The label comes from getRowLabel when supplied. When getRowLabel is not set, the label comes from the first column’s rendered text if that column renders a plain string, and falls back to getRowId otherwise.
Pass
getRowLabel whenever the first column renders a link, a badge, or anything that is not plain text.
Use in DataTable
DataTable creates the kebab column automatically when rowActions is set. The column is shared with the columnPicker slot; DataTable creates it even when only one of the two is present.
sectionActions. The section kebab is named after the section label, falling back to section <id>.
