Diff
A change to read and review.
Modifiedsrc/lexer/identifier.ts9 additions, 4 deletions
Addedsrc/lexer/unicode.ts17 additions, 0 deletions
View code
import { diffLines } from "@sdxc/diff";
let { hunks } = diffLines(before, after);
<Diff labels={{ copied: "Copied" }}>
<Diff.Toolbar
view="unified"
unifiedHref="/acme/delta/pull/412/files?diff=unified"
splitHref="/acme/delta/pull/412/files?diff=split"
viewed={0}
total={2}
labels={toolbarLabels}
/>
<Diff.File
path="src/lexer/identifier.ts"
status="modified"
additions={9}
deletions={4}
hunks={hunks}
language="ts"
commentHref={(path, side, line) => `/acme/delta/pull/412/files?comment=${path}:${side}${line}`}
threads={{ R10: <ReviewThread comments={comments} replyAction={replyAction} resolveAction={resolveAction} labels={threadLabels} /> }}
menu={<FileMenu />}
viewedControl={<OptimisticToggle action="/acme/delta/pull/412/files/viewed" name="viewed" pressed={false} … />}
labels={fileLabels}
/>
<Diff.File path="src/lexer/unicode.ts" status="added" additions={17} deletions={0} hunks={added} language="ts" labels={fileLabels} />
</Diff>Demo apps
Whole pages built only from @sdxc/ui, each for an invented product with its own palette and type. They open in a new tab.
- Issue trackerCairnCairn, an invented issue tracker: grouped issue lists with filters in the address, a drag-and-drop board, projects on a roadmap, cycles with burn-up charts and an inbox./demo/apps/cairn
- Social networkChorusChorus, an invented social network: a live timeline with pictures, link cards, polls and threads, profiles, grouped notifications, search and messages./demo/apps/chorus
- Code hostDeltaDelta, a code host whose one repository is its own source: a file tree beside the code, line links, blame and the commits list, every file and commit read from git when the site is built./demo/apps/delta
- CMSInkwellInkwell, a demo CMS built from @sdxc/ui/app: posts with count tabs and quick edit, an editor with a quiet save status and a loud Publish, media and settings./demo/apps/inkwell
- Ops dashboardPulsePulse, an invented ops dashboard: Northwind's services with live filters, a peek sheet and a bulk bar, metrics that switch one chart, incidents, deploys and settings./demo/apps/pulse
- Support inboxRelayRelay, an invented support inbox: mailboxes, conversations and the open conversation on one screen, with contacts, reports and settings./demo/apps/relay
- Chat assistantTernTern, an invented chat assistant: conversations with streamed replies, code and tables, projects, a canvas, sharing and settings./demo/apps/tern
Every demo, landings and docs sites alike, is listed in the demo gallery.
Installation
An ordinary dependency: install it, import the theme once, and import the component where it is used.
npm add @sdxc/ui remiximport "@sdxc/ui/theme.css";Usage
import { Diff } from "@sdxc/ui/app";Without script each + links to commentHref; mapping @sdxc/ui/app/diff runs one
DiffInteractions for every file, turning +, drags and copy into DIFF_COMMENT_EVENT.
Set --diff-font-size, --diff-line-height and the --diff-add-*/--diff-del-* tints.
Composition
The parts the component publishes, each a static property of the host.
DiffDiff.ToolbarRenders the bar above the files: the unified/split switch as two GET links carried in the address, the app's own controls, then "n / N files viewed" with a bar that fills as the review burns down.Diff.FileRenders one file's card: a<details>whose summary carries the chevron, status, path, counts and five-block bar, with copy,⋯and "Viewed" beside it, then the lines or the note standing in for them.Diff.HunksRenders hunks as one table without a card, for Diff.File and for a thread's excerpt or suggestion.
Examples
Split, old and new side by side
Modifiedsrc/lexer/identifier.ts9 additions, 4 deletions
View code
<Diff labels={{ copied: "Copied" }}>
<Diff.Toolbar
view="split"
unifiedHref="/acme/delta/pull/412/files?diff=unified"
splitHref="/acme/delta/pull/412/files?diff=split"
labels={toolbarLabels}
/>
<Diff.File path="src/lexer/identifier.ts" status="modified" additions={9} deletions={4} hunks={hunks} language="ts" view="split" labels={fileLabels} />
</Diff>Binary and too large to show
Modifieddocs/assets/identifiers.png0 additions, 0 deletions
Binary file not shown.
Modifiedpackage-lock.json1834 additions, 1210 deletions
Large diff not shown.
View code
<Diff labels={{ copied: "Copied" }}>
<Diff.File path="docs/assets/identifiers.png" status="modified" additions={0} deletions={0} binary labels={fileLabels} />
<Diff.File path="package-lock.json" status="modified" additions={1834} deletions={1210} tooLarge labels={fileLabels} />
</Diff>A viewed file, collapsed
Modifiedsrc/lexer/identifier.ts9 additions, 4 deletions
Modifiedsrc/lexer/index.ts1 additions, 1 deletions
View code
<Diff labels={{ copied: "Copied" }}>
<Diff.Toolbar view="unified" unifiedHref={unifiedHref} splitHref={splitHref} viewed={1} total={2} labels={toolbarLabels} />
<Diff.File
path="src/lexer/identifier.ts"
status="modified"
additions={9}
deletions={4}
hunks={hunks}
viewed
viewedControl={<OptimisticToggle action="/acme/delta/pull/412/files/viewed" name="viewed" pressed … />}
labels={fileLabels}
/>
<Diff.File path="src/lexer/index.ts" status="modified" additions={1} deletions={1} hunks={indexHunks} language="ts" labels={fileLabels} />
</Diff>Props
Read from the component's own types, so every prop, every default and every allowed value is listed.
| Prop | Type | Required | Description |
|---|---|---|---|
labels | Labels | Yes | The strings the page's island announces, so it reads in any language. |
children | RemixNode | No | A Diff.Toolbar and the page's Diff.File cards. |
Also accepts everything in TagProps<"div">.
Diff.Toolbar
| Prop | Type | Required | Description |
|---|---|---|---|
view | "unified" | "split" | Yes | The view the page renders, which the switch marks current. |
unifiedHref | string | Yes | This page's address with ?diff=unified. |
splitHref | string | Yes | This page's address with ?diff=split. |
target | string | No | The frame the view links swap, such as "main". |
viewed | number | No | Files marked viewed; with total, draws "n / N files viewed" and its bar. |
total | number | No | The files on the page; with viewed, draws the progress. |
labels | ToolbarLabels | Yes | The toolbar's strings. |
children | RemixNode | No | Controls between the switch and the progress: "Jump to", a file filter. |
Also accepts everything in TagProps<"div">.
Diff.File
| Prop | Type | Required | Description |
|---|---|---|---|
oldPath | string | No | The path on the old side, drawn as old → new when it differs from path. |
status | FileStatus | Yes | Drawn as a toned letter (A, M, D, R, C) and read out through labels.statuses. |
additions | number | Yes | Lines added, counted in the header and its five-block bar. |
deletions | number | Yes | Lines deleted, counted in the header and its five-block bar. |
hunks | readonly Hunk[] | No | The file's hunks; omitted for a binary file, a dropped patch or a lazy one. |
binary | boolean | No | Git declared the file binary. |
tooLarge | boolean | No | The patch was dropped for size, so the file draws "Large diff not shown". |
collapsed | boolean | No | Renders closed, as a reader's collapse would. |
viewed | boolean | No | The reader marked the file viewed, which renders it closed. |
viewedControl | RemixNode | No | The "Viewed" checkbox, as a form that posts the mark (an OptimisticToggle). |
menu | RemixNode | No | A ⋯ menu trigger at the header's end. |
load | RemixNode | No | What a large file shows in place of its lines, such as a lazy frame's "Load diff"; given, a file past largeThreshold renders collapsed and draws no row. |
largeThreshold | number | No | Changed lines past which the file counts as large. @default DEFAULT_LARGE_THRESHOLD |
labels | FileLabels | Yes | The card's strings, so it reads in any language. |
Also accepts everything in Omit<TagProps<"section">, "children">.
Also accepts everything in Omit<HunksProps, keyof TagProps<"div"> | "hunks" | "labels" | "fileId" | "anchored">.
Diff.Hunks
| Prop | Type | Required | Description |
|---|---|---|---|
path | string | Yes | The file's path, which names the table and keys the comment links. |
hunks | readonly Hunk[] | Yes | The parsed hunks to draw, with their word-level ranges. |
language | string | No | Language the code highlights as (ts, css); omitted, lines draw plain. |
view | "unified" | "split" | No | One column, or old and new side by side above 48rem. @default "unified" |
fileId | string | No | Prefix of every line anchor, <fileId>R12. @default "diff-" plus a hash of the path |
threads | Readonly<Record<string, RemixNode>> | No | Review threads drawn as a full-width row after their line, keyed R12 (new side) or L12 (old side). |
lineHref | (side: DiffSide, line: number) => string | No | The address a line's number links to. @default #<fileId><side><line> |
commentHref | (path: string, side: DiffSide, line: number) => string | No | Where a line's + goes without script; set, every line draws a +. |
expandHref | (hunk: Hunk, index: number) => string | No | Where a hunk's expand link goes, which the app can answer as a frame. |
chunkSize | number | No | Lines per chunk. @default DEFAULT_CHUNK_SIZE |
anchored | boolean | No | Gives each number an id and a link. Turn it off for a copy of lines shown beside the file itself (a thread's excerpt or suggestion), so anchors stay unique. @default true |
labels | HunksLabels | Yes | The table's strings. |
Also accepts everything in TagProps<"div">.
Types
| Name | Values | What it decides |
|---|---|---|
View | "unified" | "split" | How hunks draw: one column of lines, or the old and new sides beside each other. |