ComponentsActions

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.

Moved Maya Chen to CV sent
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>
  );
}

At the end of a receipt line under Flo's reply, Undo is an underlined word in the brand colour.

Moved Maya Chen to CV sent ·
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.

Imported candidates.csv · 42 records
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.

Sent Maya Chen's CV to Halcyon Bank
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&apos;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 undone.Check your connection and try again.
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>
  );
}

On this page