File Diff

Shows one file's changes as a diff, even while streaming.

@@ -12,9 +12,12 @@ export function BookingForm({ venue }: Props) {
const [guests, setGuests] = useState(2)
const [time, setTime] = useState("19:00")
const [time, setTime] = useState<string | null>(null)
const slots = useAvailableSlots(venue.id, guests)
​
async function submit() {
await book(venue.id, { guests, time })
if (!time) return
await book(venue.id, { guests, time })
toast.success(`Booked for ${guests} at ${time}`)
}
​
return (

Installation

pnpm dlx shadcn@latest add @paneui/file-diff

Usage

Examples

Split View

Two Files

Accept or Reject Changes

Collapse on Complete

Controlled

Without Line Numbers or Copy

Pre-parsed Lines

Renderer

The bundled diff renderer draws the rows inside a shadow root, themed with this library's tokens (--code surface, 12px font-mono, success / destructive for changes); light and dark syntax colours follow the html.dark class. It loads on demand in the browser (about 200 KB gzipped, with grammars per language). During static export and until it arrives, plain unhighlighted rows show.

Motion

While status is streaming, the newest row slides in with a primary flash and the view follows the end. Under reduced motion rows appear without movement and the chevron stops rotating.

Accessibility

The header is a collapsible trigger and reads out "N additions, N deletions". The rows sit in a region labelled "Changes to {filename}"; the Unified / Split switch is a radio group.

API Reference

Built on the Collapsible primitive. Only the props this library adds or changes are listed; everything else passes through.

FileDiff

Disclosure for one file change with live +/− counts, rendering a unified or split diff. Also accepts every prop of Collapsible.Root.

filename*
string
File path in the header; the directory part is dimmed. Also infers the language.
diff= ""
string
Unified diff text for one file. Ignored when lines, oldFile/newFile or fileDiff are given.
lines
FileDiffLine[]
Pre-parsed rows, e.g. appended one by one while an agent streams.
oldFile
string | null
Old contents for a two-file diff (null when the file is new). Unchanged regions become expandable.
newFile
string | null
New contents for a two-file diff (null when the file was deleted).
fileDiff
FileDiffMetadata
An already parsed diff (from parseDiffFromFile, parsePatchFiles or diffAcceptRejectHunk). Wins over every other source.
language
string
Syntax language id, inferred from filename when omitted.
status= "complete"
"streaming" | "complete"
streaming shows a spinner, flashes new rows in and follows the end.
open
boolean
Controlled open state.
defaultOpen= true
boolean
Initial open state when uncontrolled.
onOpenChange
(open: boolean) => void
Called when the disclosure opens or closes.
collapseOnComplete= false
boolean
Fold the diff away when status goes from streaming to complete; it reopens when streaming starts again.
diffStyle
"unified" | "split"
Controlled layout.
defaultDiffStyle= "unified"
"unified" | "split"
Initial layout when uncontrolled.
onDiffStyleChange
(style: "unified" | "split") => void
Called when the header switch changes the layout.
diffStyleToggle= false
boolean
Show the Unified / Split switch in the header.
inlineChanges= true
boolean | "word-alt" | "word" | "word-line" | "char"
Highlight the changed part of each modified line. true is "word-alt"; false turns it off.
indicators= "bars"
"bars" | "classic" | "none"
Mark changed lines with a coloured bar, with +/- signs, or by background only.
wrap= false
boolean
Wrap long lines instead of scrolling sideways.
maxHeight= 320
number
Max height of the rows area in px.
showLineNumbers= true
boolean
Show line numbers.
copyable= true
boolean
Show a button that copies the new file contents.
lineAnnotations
DiffLineAnnotation<A>[]
Rows of your own UI pinned under diff lines, each { side: "additions" | "deletions", lineNumber, metadata }.
renderAnnotation
(annotation: DiffLineAnnotation<A>) => ReactNode
Renders each annotation (comments, accept/reject buttons…).
options
FileDiffOptions
Extra renderer options merged over the defaults, e.g. hunkSeparators, expandUnchanged, enableLineSelection.
variant= "card"
"card" | "row"
row drops the card surface and moves the chevron to the end, for lists of files such as EditedFilesCard.
actions
ReactNode
Extra header controls before the copy button (reveal in folder, open in editor).
PropTypeDefaultDescription
filename*string—File path in the header; the directory part is dimmed. Also infers the language.
diffstring""Unified diff text for one file. Ignored when lines, oldFile/newFile or fileDiff are given.
linesFileDiffLine[]—Pre-parsed rows, e.g. appended one by one while an agent streams.
oldFilestring | null—Old contents for a two-file diff (null when the file is new). Unchanged regions become expandable.
newFilestring | null—New contents for a two-file diff (null when the file was deleted).
fileDiffFileDiffMetadata—An already parsed diff (from parseDiffFromFile, parsePatchFiles or diffAcceptRejectHunk). Wins over every other source.
languagestring—Syntax language id, inferred from filename when omitted.
status"streaming" | "complete""complete"streaming shows a spinner, flashes new rows in and follows the end.
openboolean—Controlled open state.
defaultOpenbooleantrueInitial open state when uncontrolled.
onOpenChange(open: boolean) => void—Called when the disclosure opens or closes.
collapseOnCompletebooleanfalseFold the diff away when status goes from streaming to complete; it reopens when streaming starts again.
diffStyle"unified" | "split"—Controlled layout.
defaultDiffStyle"unified" | "split""unified"Initial layout when uncontrolled.
onDiffStyleChange(style: "unified" | "split") => void—Called when the header switch changes the layout.
diffStyleTogglebooleanfalseShow the Unified / Split switch in the header.
inlineChangesboolean | "word-alt" | "word" | "word-line" | "char"trueHighlight the changed part of each modified line. true is "word-alt"; false turns it off.
indicators"bars" | "classic" | "none""bars"Mark changed lines with a coloured bar, with +/- signs, or by background only.
wrapbooleanfalseWrap long lines instead of scrolling sideways.
maxHeightnumber320Max height of the rows area in px.
showLineNumbersbooleantrueShow line numbers.
copyablebooleantrueShow a button that copies the new file contents.
lineAnnotationsDiffLineAnnotation<A>[]—Rows of your own UI pinned under diff lines, each { side: "additions" | "deletions", lineNumber, metadata }.
renderAnnotation(annotation: DiffLineAnnotation<A>) => ReactNode—Renders each annotation (comments, accept/reject buttons…).
optionsFileDiffOptions—Extra renderer options merged over the defaults, e.g. hunkSeparators, expandUnchanged, enableLineSelection.
variant"card" | "row""card"row drops the card surface and moves the chevron to the end, for lists of files such as EditedFilesCard.
actionsReactNode—Extra header controls before the copy button (reveal in folder, open in editor).
data-status
streaming or complete.
data-diff-style
unified or split.
Data attributeDescription
data-statusstreaming or complete.
data-diff-styleunified or split.

parseUnifiedDiff

(diff: string) => FileDiffLine[]. Parses one file's git diff output; file headers are dropped.

FileDiffLine

One row of a diff.

type*
"added" | "removed" | "context" | "hunk"
Row kind.
content*
string
Line text without the leading sign; the full @@ header for hunks.
oldLine
number
Line number in the old file.
newLine
number
Line number in the new file.
PropTypeDefaultDescription
type*"added" | "removed" | "context" | "hunk"—Row kind.
content*string—Line text without the leading sign; the full @@ header for hunks.
oldLinenumber—Line number in the old file.
newLinenumber—Line number in the new file.