StatusTransitionButton
A menu of the states the current record may move to next, with optional guards and confirmation.
Installation
pnpm dlx shadcn@latest add @chelone/status-transition-buttonA menu of the states the current record may move to next. Picking one patches the record's status field.
Usage
import {
Edit,
SaveButton,
StatusTransitionButton,
TextInput,
Toolbar,
} from "@chelone/react";
export const PostEdit = () => (
<Edit>
<TextInput source="title" />
<Toolbar>
<SaveButton />
<StatusTransitionButton
transitions={{ draft: ["review"], review: ["published", "draft"] }}
guards={{ "review->published": (record) => Boolean(record.approved) }}
labels={{ review: "In review" }}
confirm
onTransition={(from, to) => console.log(`${from} -> ${to}`)}
/>
</Toolbar>
</Edit>
);transitionsmaps each state to the states it may move to. A state missing from the map has no moves.guardsare keyed"<from>-><to>". A transition with no guard is allowed.confirmasks for confirmation before the update.sourcenames the status field. Defaultstatus.
Vue
onTransition is the transition event in Vue. It is called with
(from, to, record).
<script setup lang="ts">
import {
Edit,
SaveButton,
StatusTransitionButton,
TextInput,
Toolbar,
} from "@chelone/vue";
const transitions = { draft: ["review"], review: ["published", "draft"] };
const guards = {
"review->published": (record: Record<string, unknown>) =>
Boolean(record.approved),
};
const labels = { review: "In review" };
</script>
<template>
<Edit resource="posts">
<TextInput source="title" />
<template #toolbar>
<Toolbar>
<SaveButton />
<StatusTransitionButton
:transitions="transitions"
:guards="guards"
:labels="labels"
confirm
@transition="(from, to) => console.log(`${from} -> ${to}`)"
/>
</Toolbar>
</template>
</Edit>
</template>Behaviour
- The trigger is disabled in a state with no allowed move.
- A guard that returns
falseor throws blocks its move. The other moves from that state stay available. onTransitionruns after the update resolves. It does not run when the update fails.mutationModedefaults topessimistic. Withundoablethe move shows the Undo notification, andonTransitionruns once that notification is queued.- The button renders nothing without a record, a resource or
editaccess.
The guards only hide moves in the UI. The server must reject a move it does not allow.
In a custom button
@chelone/core exports getAllowedTransitions(record, { source, transitions, guards }). It returns { current, allowed }, the same list the menu shows.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | — | |
| confirm | boolean | — | Ask for confirmation before the update. |
| guards | Record<string, (record: Record<string, unknown>) => boolean> | — | Predicates keyed `"<from>-><to>"`. A transition with no guard is allowed. |
| labels | Record<string, string> | — | Text for a state. A state with no entry shows as it is. |
| mutationMode | MutationMode | — | Default `"pessimistic"`; `"undoable"` shows an Undo notification. |
| onTransition | (from: string, to: string, record: LooseRecord) => void | — | Runs once the update has resolved, or, in undoable mode, once the Undo notification is queued. Not called when the update fails. |
| record | LooseRecord | — | |
| resource | string | — | |
| source | string | — | Record field holding the status. Default `"status"`. |
| transitions | Record<string, readonly string[]> | — | The states each state may move to. |