Undo control
Undo is how Ascern recovers from a write: an Undo on the toast that confirms it, ⌘Z for the newest one, and an inline Undo on a receipt or activity line for changes Flo made.
Playground
Change the controls to try each prop. The code under the preview updates as you go and lists only the props you changed; copy it into your page.
import { UndoControl } from '@/ui/undo-control';<UndoControl label="Undo" doneLabel="Undone" />Inline Undo
The secondary button after what happened. Press it: it holds a spinner and keeps focus while the undo runs, then "Undone" takes its place as a status line. The toast's Undo and ⌘Z are part of Toast.
import { UndoControl } from '@/ui/undo-control';
const moveBack = () => new Promise<void>((resolve) => setTimeout(resolve, 800));
export default function UndoControlDefault() {
return (
<span className="flex items-center gap-150 text-body leading-body">
Moved Maya Chen to CV sent
<UndoControl onUndo={moveBack} />
</span>
);
}As a link
At the end of a receipt line under Flo's reply, Undo is an underlined word in the brand colour.
import { UndoControl } from '@/ui/undo-control';
const moveBack = () => new Promise<void>((resolve) => setTimeout(resolve, 800));
export default function UndoControlLink() {
return (
<span className="flex items-center gap-050 text-body leading-body text-text-subtle">
Moved Maya Chen to CV sent ·
<UndoControl appearance="link" onUndo={moveBack} />
</span>
);
}A bigger reversal
Name an undo that takes back more than one change: "Undo import". The status line says what happened.
import { UndoControl } from '@/ui/undo-control';
const undoImport = () => new Promise<void>((resolve) => setTimeout(resolve, 800));
export default function UndoControlBigger() {
return (
<span className="flex items-center gap-150 text-body leading-body">
Imported candidates.csv · 42 records
<UndoControl label="Undo import" doneLabel="Import undone. 3 values changed since were kept." onUndo={undoImport} />
</span>
);
}When it can't go back
When onUndo returns a reason, it shows in danger and the button stays to try again.
import { UndoControl } from '@/ui/undo-control';
export default function UndoControlFailed() {
return (
<span className="flex items-center gap-150 text-body leading-body">
Sent Maya Chen's CV to Halcyon Bank
<UndoControl onUndo={() => 'Maya moved to Interview since. Move her back by hand.'} />
</span>
);
}States
Undoing, undone and failed, set by the app with status.
import { UndoControl } from '@/ui/undo-control';
export default function UndoControlStates() {
return (
<div className="flex flex-col items-start gap-150 text-body leading-body">
<UndoControl status="undoing" label="Undo import" onUndo={() => {}} />
<UndoControl status="undone" doneLabel="Import undone." onUndo={() => {}} />
<UndoControl status="failed" error="Check your connection and try again." onUndo={() => {}} />
</div>
);
}Split button
A primary action with a caret beside it. The label runs the usual choice in one click; the caret opens the other ways to do the same thing.
Code input
Six boxes for a one-time code: two-step sign-in, and the step-up check before a sensitive change. It reads as one field; typing or pasting fills the boxes in order.