Elements

81 / 122 · Composer

Draft restore

Come back to a thread and the sentence you never sent is still waiting.

Add a regression test for draft restore across thread switchesunsent draft · 2 minutes ago

Installation

npx shadcn@latest add "@assistant-ui/elements-draft-restore"

Usage

import { DraftRestore } from "@/components/elements/draft-restore";

<DraftRestore
  draft="Add a regression test for draft restore"
  savedAt="2 minutes ago"
  onRestore={restore}
  onDiscard={discard}
/>

Props

draft*stringThe unsent text, truncated to one line.
savedAt*stringWhen it was last touched, pre-formatted.
onRestore() => voidCalled to put the draft back in the composer.
onDiscard() => voidCalled to throw the draft away.
classNamestringExtra classes merged onto the root.

Source

draft-restore.tsx
"use client";

import { PencilLineIcon, XIcon } from "lucide-react";
import { cn } from "@/lib/utils";
import { ghostButton, mono, paper } from "./surfaces";

export function DraftRestore({
  draft,
  savedAt,
  onRestore,
  onDiscard,
  className,
}: {
  draft: string;
  savedAt: string;
  onRestore?: () => void;
  onDiscard?: () => void;
  className?: string;
}) {
  return (
    <div
      className={cn(
        paper,
        "fade-in slide-in-from-bottom-1 animate-in flex w-full max-w-sm items-center gap-2.5 rounded-2xl py-2 pr-2 pl-3.5 duration-300",
        className,
      )}
    >
      <PencilLineIcon className="text-foreground/30 size-3.5 shrink-0" />
      <div className="flex min-w-0 flex-1 flex-col">
        <span className="text-foreground/70 truncate text-[13px]">{draft}</span>
        <span className={cn(mono, "text-foreground/30")}>
          unsent draft · {savedAt}
        </span>
      </div>
      <button
        type="button"
        onClick={onRestore}
        className="text-foreground/70 hover:bg-foreground/[0.06] hover:text-foreground/95 shrink-0 rounded-full px-2.5 py-1 text-xs font-medium transition-[background-color,color,scale] duration-150 active:scale-[0.96]"
      >
        Restore
      </button>
      <button
        type="button"
        aria-label="Discard the draft"
        onClick={onDiscard}
        className={cn(ghostButton, "size-7 shrink-0")}
      >
        <XIcon className="size-3.5" />
      </button>
    </div>
  );
}