14 / 37 · Tool use
Code diff
A unified diff with tinted additions and removals, sized for chat.
composer.tsx+2 −1
export function Composer() {
const threadId = useThreadId();
const composer = useComposer();
− const [draft, setDraft] = useState("");
+ const draft = useDraft(threadId);
+ useEffect(() => hydrate(draft), [threadId]);
return (
<form onSubmit={composer.send}>
{/* … */}
Installation
npx shadcn@latest add "@assistant-ui/elements-code-diff"
Usage
import { CodeDiff } from "@/components/elements/code-diff";
<CodeDiff
filename="thread.tsx"
additions={2}
deletions={1}
lines={[
{ kind: "removed", text: "const x = 1" },
{ kind: "added", text: "const x = 2" },
]}
cycle={0}
/>Props
filename*stringFile path shown in the diff header.
additions*numberAddition count rendered in green next to the filename.
deletions*numberDeletion count rendered in red next to the filename.
lines*readonly DiffLine[]Unified diff lines with context, added, or removed kind.
cycle*numberIdentity key that remounts the body so entrance animation can replay.
classNamestringExtra classes merged onto the root.
Source
code-diff.tsx"use client";
import { cn } from "@/lib/utils";
import { mono, paper } from "./surfaces";
export type DiffKind = "context" | "added" | "removed";
export interface DiffLine {
kind: DiffKind;
text: string;
}
const GUTTER: Record<DiffKind, string> = {
context: "",
added: "+",
removed: "−",
};
export function CodeDiff({
filename,
additions,
deletions,
lines,
cycle,
className,
}: {
filename: string;
additions: number;
deletions: number;
lines: readonly DiffLine[];
cycle: number;
className?: string;
}) {
return (
<div
className={cn(
paper,
"w-full max-w-md overflow-hidden rounded-2xl font-mono text-xs",
className,
)}
>
<div className="flex items-center justify-between px-4 pt-3 pb-2">
<span className="text-foreground/90">{filename}</span>
<span className={cn(mono, "tabular-nums")}>
<span className="text-emerald-600 dark:text-emerald-400">
+{additions}
</span>{" "}
<span className="text-red-600 dark:text-red-400">−{deletions}</span>
</span>
</div>
<div>
{lines.map((line, i) => (
<div
key={`${cycle}-${i}-${line.text}`}
className={cn(
"fade-in animate-in fill-mode-both flex px-4 py-0.5 leading-relaxed whitespace-pre duration-300",
line.kind === "context" && "text-foreground/45",
line.kind === "added" &&
"bg-emerald-500/10 text-emerald-700 dark:bg-emerald-500/10 dark:text-emerald-300",
line.kind === "removed" &&
"bg-red-500/10 text-red-700 dark:text-red-300",
)}
style={{ animationDelay: `${i * 60}ms` }}
>
<span className="w-4 shrink-0 select-none">
{GUTTER[line.kind]}
</span>
<span>{line.text}</span>
</div>
))}
</div>
</div>
);
}