Skip to content
Tier

Change tree

Pro

Changed files as a tree of folders, on Tree: each file with a chip for its change in ChangeDiff's words (N new, C changed, D deleted, and a dot for unchanged, which is no letter; the letters and words are strings, so a translation has its own), the word read before its name ("Deleted: billing.ts"), deleted files struck through and still listed, each folder with how many of its files changed, and a folder that holds only one folder joined to it in one row (src/billing). The colour is never alone: the letter, the word and the strike say the change. Paths read left to right in a right-to-left page. A file's name draws in --cd-font-name when the app sets it (the token ChangeDiff's file names read, so the two match); unset, it keeps the tree's own font.

Selecting a file calls onSelect with its path, so one list drives the tree and a ChangeDiff beside it: pass the same selected to both. A FileDiff[] can be passed as files as it is. The keyboard is Tree's: ↑↓ move, → opens, ← closes, Enter selects.

Its strings are overridden under changeTree in LocaleProvider's strings.

Markdown
Version 5.4.0· other versions

This page is for 5.4.0, the current release. An earlier release’s page is the Markdown that release published, so the docs match the version you have installed (candor-lock.json records it).

Not in the docs of 1.3.0, 1.4.0, 1.5.0, 1.5.1, 1.6.0, 1.7.0, 1.8.0, 1.8.1, 1.9.0, 1.9.1, 2.0.0, 2.1.0, 2.2.0, 2.3.0, 2.4.0, 2.5.0, 2.5.1, 2.6.0, 2.6.1, 2.6.2, 2.7.0, 2.8.0, 2.9.0, 2.10.0, 3.0.0, 4.0.0, 4.0.1, 4.0.2, 4.1.0, 4.2.0, 4.3.0, 4.4.0, 4.5.0, 5.0.0, 5.1.0 and 5.2.0: the page came later, or had another name then.

Docs pages weren’t kept for 1.0.0, 1.0.1, 1.1.0, 1.1.1, 1.2.0 and 1.2.1: they are kept from 1.3.0 on, and before that only llms.txt was published.

Preview

Loading the preview

Install

With the Candor CLI

First time? Get started sets up the registry and theme. Or with shadcn directly: npx shadcn@latest add @candor/change-tree

Brings in: bidi, data, locale, tokens, tree, utils · npm: lucide-react@^1.67.0

Usage

TSX · The example above
"use client"

import * as React from "react"
import type { FileDiff } from "@/lib/candor/work"
import { ChangeDiff } from "@/components/candor/change-diff"
import { ChangeTree } from "@/components/candor/change-tree"

const line = (text: string, newNo: number) => ({ kind: "add" as const, text, newNo })

const files: FileDiff[] = [
  { path: "web/src/customers/CustomerHeader.tsx", change: "update", language: "tsx", hunks: [{ lines: [{ kind: "remove", text: '<Fact label="Partner tier" />', oldNo: 44 }, line('<Fact label="Region" />', 44)] }] },
  { path: "web/src/customers/RegionBadge.tsx", change: "create", language: "tsx", hunks: [{ lines: [line("export function RegionBadge() {", 1), line("  return null", 2), line("}", 3)] }] },
  { path: "web/src/customers/PartnerTier.tsx", change: "delete", language: "tsx", hunks: [{ lines: [{ kind: "remove", text: "export function PartnerTier() {}", oldNo: 1 }] }] },
  { path: "db/migrations/0142_add_customer_region.sql", change: "create", language: "sql", hunks: [{ lines: [line("ALTER TABLE customers ADD COLUMN region text;", 1)] }] },
]

export default function Example() {
  // One selection drives both: the tree marks the file the diff shows, and picking either moves the other.
  const [path, setPath] = React.useState<string | null>(files[0]!.path)
  return (
    <div className="flex flex-wrap items-start gap-3">
      <div className="w-72 rounded-xl border bg-card p-1.5">
        <ChangeTree files={files} selected={path} onSelect={setPath} />
      </div>
      <ChangeDiff className="min-w-0 flex-1 basis-96" files={files} selected={path} onSelect={setPath} />
    </div>
  )
}

<ChangeTree>

PropTypeDefaultDescription
filesrequiredreadonly ChangeTreeFile[]—The files, each with its path and how it changed. A ChangeDiff's `files` can be passed as they are.
selectedstring | null | undefined—The path of the selected file, when the app holds the selection: marked in the tree, and changed only by the app (`onSelect` says what the person picked). `null` marks none.
defaultSelectedstring | null | undefined—The path of the file selected at first, when the tree holds the selection: given this and no `selected`, the tree marks the file the person picks, starting here, and still calls `onSelect`. `null` starts with none. With neither prop the tree holds no selection and marks none; `onSelect` is still called.
onSelect((path: string, file: ChangeTreeFile) => void) | undefined—Called with a file's path, and the file, when the person clicks it or presses Enter or Space on it. Folders aren't selected.
labelstring | undefined—The tree's accessible name. "Changed files" by default.
classNamestring | undefined—Classes for the tree, merged with Candor's.

Strings

The fixed words, in English by default. Change any of them under changeTree in <LocaleProvider strings={{ … }}>; the table is exported as changeTreeStrings.

KeyEnglish
labelChanged files
createNew
updateChanged
deleteDeleted
unchangedUnchanged
createLetterN
updateLetterC
deleteLetterD
unchangedMark·
changeName{change}:
folderChanges{ one: "{count} change", other: "{count} changes" }
emptyNo changed files.

Types

ChangeTreeFile
A file in a ChangeTree: its path, with `/` between folders, and how it changed. A `FileDiff` is one, so the list that drives ChangeDiff drives ChangeTree too; `"unchanged"` lists a file the change didn't touch.
{ path: string; change: "create" | "update" | "delete" | "unchanged" }