Actions
WxAction is the square icon button at the end of a row — edit, duplicate, delete. WxActions is the row they sit in, and it can fold itself into a dropdown when the column gets too narrow.
Usage
<template>
<wx-actions aria-label="Row actions">
<wx-action type="edit" :href="`/admin/pages/${page.id}/edit`" />
<wx-action type="copy" @click="duplicate(page)" />
<wx-action type="remove" @click="confirmRemove(page)" />
</wx-actions>
</template>type is what the action does, and it picks three things at once: the icon, the colour and the accessible name. That is what keeps a delete red and an edit blue on every screen without anyone having to remember.
type | Icon | Colour | type | Icon | Colour |
|---|---|---|---|---|---|
add | plus | primary | upload | upload | primary |
edit | edit | primary | download | download | primary |
remove | trash | danger | sort | drag | neutral |
copy | copy | primary | search | search | primary |
link | link | primary | restore | refresh | success |
goto | external-link | primary | settings | settings | neutral |
details | info | primary | send | primary | |
view | eye | primary | more | more-horizontal | neutral |
hide | eye-off | neutral |
Any of the three can be overridden — icon takes any name from the icon set, including one you registered yourself:
<template>
<wx-action type="add" icon="folder" title="New folder" />
<wx-action type="edit" tone="warning" />
</template>Links and routes
An action with href renders an <a>; as renders it through another component, which is how it becomes a router link:
<template>
<wx-action type="edit" :as="RouterLink" :to="{ name: 'pages.edit', params: { id: page.id } }" />
</template>Keeping the row aligned
hidden draws nothing but keeps the square. A list where one record may not be deleted still lines up with the rows where it may — which is the whole point of a column of actions:
<template>
<wx-actions>
<wx-action type="edit" />
<wx-action type="remove" :hidden="!page.can_delete" />
</wx-actions>
</template>disabled is the other half of that choice: the action is visible, greyed and inert, which says "not now" rather than "not for this record".
Asking first
confirm is the question to ask before the action goes ahead. The button opens a small Popconfirm beside itself, and click is reported only on a yes — so a row that was removed on click asks first by gaining one prop, and its handler stays as it was.
<template>
<wx-action
type="remove"
title="Take the label off"
confirm="Delete?"
confirm-text="Yes"
cancel-text="Cancel"
@click="remove(label)"
/>
</template>The focus lands on Cancel; Enter goes ahead from anywhere in the popover, Escape and a click outside put it away. A danger action — remove is one — gets a red yes.
This is for one row of a list: a label off a product, a choice out of a field, a recipient out of a channel. Deleting a whole record — something with a page, a history, a place in other lists — is worth a dialog instead.
In the admin panel the words come from useRemoveConfirm() of @webx-ui/module-admin, in the panel's language: <wx-action type="remove" v-bind="askRemove" @click="…" />.
The tooltip
title is drawn by WxTooltip, not by the browser: one shape across a panel, a delay of its own and a side that can be turned away from the edge of a dialog with tooltipSide. The attribute is gone from the markup, so an action is found by its name rather than by [title=…].
A greyed action keeps its tooltip, which is when an icon needs it most. That is why disabled puts aria-disabled on the control instead of the attribute: a disabled button receives no pointer events at all, so nothing would ever open. Clicks and keys are turned away all the same, and the control stays out of the tab order.
Accessibility
Every action has an accessible name — label if given, otherwise title, otherwise the English name of the type. In a localised admin panel, pass title: it is both the tooltip and the name a screen reader reads.
The tooltip comes with a portal beside the control, so an action is not a single root node: in a test, reach for the button inside the wrapper rather than for the wrapper itself.
Collapsing
collapse measures the row against its container and folds it into a dropdown as soon as it no longer fits. The collapsed slot is what the menu shows:
<template>
<wx-actions collapse align="end" aria-label="Page actions">
<wx-action type="edit" title="Edit" />
<wx-action type="copy" title="Duplicate" />
<wx-action type="remove" title="Delete" />
<template #collapsed>
<wx-dropdown-item icon="edit">Edit</wx-dropdown-item>
<wx-dropdown-item icon="copy">Duplicate</wx-dropdown-item>
<wx-dropdown-item icon="trash" tone="danger">Delete</wx-dropdown-item>
</template>
</wx-actions>
</template>Leave #collapsed out and the menu shows the same actions the row holds. #trigger replaces the … button.
The row is never unmounted, only taken out of the flow — that is how the component knows there is room for it again. The container has to have a width of its own (a table cell, a card, a grid column); a container that shrinks to fit its content has nothing to measure against.
collapse="always" skips the measuring: the actions are a menu at every width, and the row is never built. That is what a list wants when every record in a panel has to open the same way — one place to look, whether a row offers one action or seven — and it is why the container's width stops mattering.
<wx-actions collapse="always" align="end" aria-label="Page actions">
<template #collapsed>
<wx-dropdown-item icon="edit">Open</wx-dropdown-item>
<hr class="wx-dropdown__divider" />
<wx-dropdown-item icon="trash" tone="danger">Delete</wx-dropdown-item>
</template>
</wx-actions>WxAction props
| Prop | Type | Default | Description |
|---|---|---|---|
type | ActionType | 'edit' | What the action does — see the table above |
icon | string | by type | Icon to draw instead |
tone | 'primary' | 'danger' | 'success' | 'warning' | 'neutral' | by type | Colour to use instead |
title | string | — | Tooltip, and the accessible name |
label | string | by type | Accessible name on its own |
href | string | — | Renders an <a> |
target | string | — | Target of that link |
as | string | Component | — | Render through another component |
disabled | boolean | false | Visible, greyed and inert |
hidden | boolean | false | Draws nothing, keeps the square |
size | 'sm' | 'md' | 'lg' | group's | 30, 36 or 42 pixels |
tooltipSide | 'top' | 'right' | 'bottom' | 'left' | 'top' | Where the tooltip prefers to sit |
confirm | string | — | Asks this in a popover first; click only on a yes |
confirmText | string | 'Yes' | The button of that question that goes ahead |
cancelText | string | 'Cancel' | The one that does not |
Events: click (MouseEvent) — with confirm, after the yes. Slot: default — replaces the icon.
WxActions props
| Prop | Type | Default | Description |
|---|---|---|---|
align | 'start' | 'center' | 'end' | 'start' | Where the row sits in its space |
size | 'sm' | 'md' | 'lg' | 'md' | Size for actions that set none |
collapse | boolean | 'always' | false | Fold into a dropdown when it does not fit; 'always' never draws the row |
ariaLabel | string | — | Accessible name of the group |
moreLabel | string | 'More' | Name and tip of the button the folded-up menu opens from |
Models: v-model:menuOpen — whether the folded-up menu is showing.
Events: collapse (boolean) — the row folded, or came back out.
Slots: default — the actions; collapsed — what the menu shows; trigger — the button that opens it.
menuOpen is there because the menu's panel is teleported. A row that hides itself until the pointer is over it — the buttons on a card, say — loses that pointer the moment the menu opens, and would fade out from under the panel hanging off it. Reading the model is how it knows to stay:
<wx-actions v-model:menu-open="menuOpen" collapse :class="{ 'is-busy': menuOpen }">…</wx-actions>The gap between actions is a variable:
<template>
<wx-actions style="--wx-actions-gap: 2px">…</wx-actions>
</template>