# File tree

A project's files in a panel, with the line it shows, a count of changes and when it was read.

A block: a ready-made arrangement of components, to copy into a project. Its own classes carry the `site-` prefix; rename it to your project's own. Status: experimental. Figma: the Figma file, https://www.figma.com/community/file/1688324308358870630. Built from: Card, Button, Icon, Tree view, Divider.

## Examples

### Usage

The files of one line, open on the way to the file being read, which is chosen; the header counts them and the footer says when they were read.

```html
<article
  class="smbk-card smbk-card--surface-raised smbk-card--layout-panel site-file-tree"
  aria-label="Files"
>
  <div class="smbk-card__content">
    <header class="smbk-card__header smbk-card__header--type-title smbk-card__header--size-sm">
      <div class="smbk-card__header-text">
        <div class="smbk-card__title-row">
          <h3 class="smbk-card__title">Files</h3>
        </div>
        <p class="smbk-card__meta">main · 128 files · 3 changed</p>
      </div>
      <div class="smbk-card__trailing">
        <button
          type="button"
          class="smbk-btn smbk-btn--tone-ghost smbk-btn--size-sm smbk-btn--icon-only"
          aria-label="More file actions"
        >
          <i
            class="ph ph-dots-three smbk-icon smbk-icon--size-md smbk-btn__icon"
            aria-hidden="true"
          ></i>
        </button>
      </div>
    </header>
    <div class="smbk-card__body smbk-card__body--type-slot">
      <ul aria-label="Files on main" role="tree" class="smbk-tree-view site-file-tree__tree">
        <li
          role="treeitem"
          data-key="simbolik"
          aria-level="1"
          aria-expanded="true"
          aria-selected="false"
          aria-labelledby="file-tree-1-label"
          tabindex="-1"
          class="smbk-tree-view__item"
          style="--smbk-tree-level:0"
        ><span class="smbk-tree-view__row"><span class="smbk-tree-view__toggle" aria-hidden="true"><i class="ph ph-caret-right smbk-icon smbk-icon--size-md smbk-tree-view__caret" aria-hidden="true"></i></span><i class="ph ph-folder-simple smbk-icon smbk-icon--size-md smbk-tree-view__icon" aria-hidden="true"></i><span id="file-tree-1-label" class="smbk-tree-view__label" data-label="simbolik">simbolik</span></span><ul role="group" class="smbk-tree-view__group"><li role="treeitem" data-key="components" aria-level="2" aria-expanded="true" aria-selected="false" aria-labelledby="file-tree-2-label" tabindex="-1" class="smbk-tree-view__item" style="--smbk-tree-level:1"><span class="smbk-tree-view__row"><span class="smbk-tree-view__toggle" aria-hidden="true"><i class="ph ph-caret-right smbk-icon smbk-icon--size-md smbk-tree-view__caret" aria-hidden="true"></i></span><i class="ph ph-folder-simple smbk-icon smbk-icon--size-md smbk-tree-view__icon" aria-hidden="true"></i><span id="file-tree-2-label" class="smbk-tree-view__label" data-label="components">components</span></span><ul role="group" class="smbk-tree-view__group"><li role="treeitem" data-key="button" aria-level="3" aria-expanded="false" aria-selected="false" aria-labelledby="file-tree-3-label" tabindex="-1" class="smbk-tree-view__item" style="--smbk-tree-level:2"><span class="smbk-tree-view__row"><span class="smbk-tree-view__toggle" aria-hidden="true"><i class="ph ph-caret-right smbk-icon smbk-icon--size-md smbk-tree-view__caret" aria-hidden="true"></i></span><i class="ph ph-folder-simple smbk-icon smbk-icon--size-md smbk-tree-view__icon" aria-hidden="true"></i><span id="file-tree-3-label" class="smbk-tree-view__label" data-label="button">button</span></span><ul role="group" class="smbk-tree-view__group" hidden=""><li role="treeitem" data-key="button-css" aria-level="4" aria-selected="false" aria-labelledby="file-tree-4-label" tabindex="-1" class="smbk-tree-view__item" style="--smbk-tree-level:3"><span class="smbk-tree-view__row"><span class="smbk-tree-view__toggle" aria-hidden="true"></span><i class="ph ph-note smbk-icon smbk-icon--size-md smbk-tree-view__icon" aria-hidden="true"></i><span id="file-tree-4-label" class="smbk-tree-view__label" data-label="button.css">button.css</span></span></li><li role="treeitem" data-key="button-manifest" aria-level="4" aria-selected="false" aria-labelledby="file-tree-5-label" tabindex="-1" class="smbk-tree-view__item" style="--smbk-tree-level:3"><span class="smbk-tree-view__row"><span class="smbk-tree-view__toggle" aria-hidden="true"></span><i class="ph ph-note smbk-icon smbk-icon--size-md smbk-tree-view__icon" aria-hidden="true"></i><span id="file-tree-5-label" class="smbk-tree-view__label" data-label="manifest.ts">manifest.ts</span></span></li></ul></li><li role="treeitem" data-key="card" aria-level="3" aria-expanded="true" aria-selected="false" aria-labelledby="file-tree-6-label" tabindex="-1" class="smbk-tree-view__item" style="--smbk-tree-level:2"><span class="smbk-tree-view__row"><span class="smbk-tree-view__toggle" aria-hidden="true"><i class="ph ph-caret-right smbk-icon smbk-icon--size-md smbk-tree-view__caret" aria-hidden="true"></i></span><i class="ph ph-folder-simple smbk-icon smbk-icon--size-md smbk-tree-view__icon" aria-hidden="true"></i><span id="file-tree-6-label" class="smbk-tree-view__label" data-label="card">card</span></span><ul role="group" class="smbk-tree-view__group"><li role="treeitem" data-key="card-css" aria-level="4" aria-selected="true" aria-labelledby="file-tree-7-label" tabindex="0" class="smbk-tree-view__item" style="--smbk-tree-level:3"><span class="smbk-tree-view__row"><span class="smbk-tree-view__toggle" aria-hidden="true"></span><i class="ph ph-note smbk-icon smbk-icon--size-md smbk-tree-view__icon" aria-hidden="true"></i><span id="file-tree-7-label" class="smbk-tree-view__label" data-label="card.css">card.css</span></span></li><li role="treeitem" data-key="card-manifest" aria-level="4" aria-selected="false" aria-labelledby="file-tree-8-label" tabindex="-1" class="smbk-tree-view__item" style="--smbk-tree-level:3"><span class="smbk-tree-view__row"><span class="smbk-tree-view__toggle" aria-hidden="true"></span><i class="ph ph-note smbk-icon smbk-icon--size-md smbk-tree-view__icon" aria-hidden="true"></i><span id="file-tree-8-label" class="smbk-tree-view__label" data-label="manifest.ts">manifest.ts</span></span></li></ul></li></ul></li><li role="treeitem" data-key="tokens" aria-level="2" aria-expanded="false" aria-selected="false" aria-labelledby="file-tree-9-label" tabindex="-1" class="smbk-tree-view__item" style="--smbk-tree-level:1"><span class="smbk-tree-view__row"><span class="smbk-tree-view__toggle" aria-hidden="true"><i class="ph ph-caret-right smbk-icon smbk-icon--size-md smbk-tree-view__caret" aria-hidden="true"></i></span><i class="ph ph-folder-simple smbk-icon smbk-icon--size-md smbk-tree-view__icon" aria-hidden="true"></i><span id="file-tree-9-label" class="smbk-tree-view__label" data-label="tokens">tokens</span></span><ul role="group" class="smbk-tree-view__group" hidden=""><li role="treeitem" data-key="color" aria-level="3" aria-selected="false" aria-labelledby="file-tree-10-label" tabindex="-1" class="smbk-tree-view__item" style="--smbk-tree-level:2"><span class="smbk-tree-view__row"><span class="smbk-tree-view__toggle" aria-hidden="true"></span><i class="ph ph-note smbk-icon smbk-icon--size-md smbk-tree-view__icon" aria-hidden="true"></i><span id="file-tree-10-label" class="smbk-tree-view__label" data-label="color.tokens.json">color.tokens.json</span></span></li><li role="treeitem" data-key="space" aria-level="3" aria-selected="false" aria-labelledby="file-tree-11-label" tabindex="-1" class="smbk-tree-view__item" style="--smbk-tree-level:2"><span class="smbk-tree-view__row"><span class="smbk-tree-view__toggle" aria-hidden="true"></span><i class="ph ph-note smbk-icon smbk-icon--size-md smbk-tree-view__icon" aria-hidden="true"></i><span id="file-tree-11-label" class="smbk-tree-view__label" data-label="space.tokens.json">space.tokens.json</span></span></li></ul></li></ul></li>
        <li
          role="treeitem"
          data-key="readme"
          aria-level="1"
          aria-selected="false"
          aria-labelledby="file-tree-12-label"
          tabindex="-1"
          class="smbk-tree-view__item"
          style="--smbk-tree-level:0"
        ><span class="smbk-tree-view__row"><span class="smbk-tree-view__toggle" aria-hidden="true"></span><i class="ph ph-note smbk-icon smbk-icon--size-md smbk-tree-view__icon" aria-hidden="true"></i><span id="file-tree-12-label" class="smbk-tree-view__label" data-label="README.md">README.md</span></span></li>
      </ul>
    </div>
    <hr class="smbk-divider smbk-divider--orientation-horizontal smbk-card__divider"/>
    <footer class="smbk-card__footer smbk-card__footer--type-meta">
      <div class="smbk-card__footer-row">
        <div class="smbk-card__footer-start">Updated 2 min ago</div>
        <div class="smbk-card__footer-end">from main</div>
      </div>
    </footer>
  </div>
</article>
```

```tsx
import { Card, CardHeader, CardFooter, CardBody } from './design-system/components/card/card.js';
import { Button } from './design-system/components/button/button.js';
import { TreeView, TreeItem } from './design-system/components/tree-view/tree-view.js';

<Card surface="raised" layout="panel" className="site-file-tree" aria-label="Files" header={<CardHeader size="sm" title="Files" meta="main · 128 files · 3 changed" menu={<Button tone="ghost" size="sm" icon-only icon="DotsThree" aria-label="More file actions" />} />} footer={<CardFooter type="meta" start="Updated 2 min ago" end="from main" />}>
  <CardBody type="slot">
    <TreeView className="site-file-tree__tree" aria-label="Files on main" defaultExpandedKeys={["simbolik", "components", "card"]} defaultSelectedKey="card-css">
      <TreeItem id="simbolik" label="simbolik">
        <TreeItem id="components" label="components">
          <TreeItem id="button" label="button">
            <TreeItem id="button-css" label="button.css" />
            <TreeItem id="button-manifest" label="manifest.ts" />
          </TreeItem>
          <TreeItem id="card" label="card">
            <TreeItem id="card-css" label="card.css" />
            <TreeItem id="card-manifest" label="manifest.ts" />
          </TreeItem>
        </TreeItem>
        <TreeItem id="tokens" label="tokens">
          <TreeItem id="color" label="color.tokens.json" />
          <TreeItem id="space" label="space.tokens.json" />
        </TreeItem>
      </TreeItem>
      <TreeItem id="readme" label="README.md" />
    </TreeView>
  </CardBody>
</Card>
```

### Changed files

Only what changed on a line: the tree view marks each changed file and folder and counts what each folder holds.

```html
<article
  class="smbk-card smbk-card--surface-raised smbk-card--layout-panel site-file-tree"
  aria-label="Changes"
>
  <div class="smbk-card__content">
    <header class="smbk-card__header smbk-card__header--type-title smbk-card__header--size-sm">
      <div class="smbk-card__header-text">
        <div class="smbk-card__title-row">
          <h3 class="smbk-card__title">Changes</h3>
        </div>
        <p class="smbk-card__meta">new-checkout · 3 changed</p>
      </div>
      <div class="smbk-card__trailing">
        <button
          type="button"
          class="smbk-btn smbk-btn--tone-ghost smbk-btn--size-sm smbk-btn--icon-only"
          aria-label="More change actions"
        >
          <i
            class="ph ph-dots-three smbk-icon smbk-icon--size-md smbk-btn__icon"
            aria-hidden="true"
          ></i>
        </button>
      </div>
    </header>
    <div class="smbk-card__body smbk-card__body--type-slot">
      <ul
        aria-label="Changed files on new-checkout"
        role="tree"
        class="smbk-tree-view site-file-tree__tree"
      >
        <li
          role="treeitem"
          data-key="src"
          aria-level="1"
          aria-expanded="true"
          aria-selected="false"
          aria-labelledby="file-tree-1-label file-tree-1-meta"
          tabindex="-1"
          class="smbk-tree-view__item"
          style="--smbk-tree-level:0"
        ><span class="smbk-tree-view__row"><span class="smbk-tree-view__toggle" aria-hidden="true"><i class="ph ph-caret-right smbk-icon smbk-icon--size-md smbk-tree-view__caret" aria-hidden="true"></i></span><i class="ph ph-folder-simple smbk-icon smbk-icon--size-md smbk-tree-view__icon" aria-hidden="true"></i><span id="file-tree-1-label" class="smbk-tree-view__label" data-label="src">src</span><span id="file-tree-1-meta" class="smbk-tree-view__meta">3</span></span><ul role="group" class="smbk-tree-view__group"><li role="treeitem" data-key="pages" aria-level="2" aria-expanded="true" aria-selected="false" aria-labelledby="file-tree-2-label file-tree-2-meta file-tree-2-changed" tabindex="-1" class="smbk-tree-view__item" style="--smbk-tree-level:1"><span class="smbk-tree-view__row"><span class="smbk-tree-view__toggle" aria-hidden="true"><i class="ph ph-caret-right smbk-icon smbk-icon--size-md smbk-tree-view__caret" aria-hidden="true"></i></span><i class="ph ph-folder-simple smbk-icon smbk-icon--size-md smbk-tree-view__icon" aria-hidden="true"></i><span id="file-tree-2-label" class="smbk-tree-view__label" data-label="pages">pages</span><span id="file-tree-2-meta" class="smbk-tree-view__meta">2</span><span class="smbk-tree-view__changed"><span id="file-tree-2-changed" class="smbk-tree-view__hidden">changed</span></span></span><ul role="group" class="smbk-tree-view__group"><li role="treeitem" data-key="checkout" aria-level="3" aria-selected="true" aria-labelledby="file-tree-3-label file-tree-3-changed" tabindex="0" class="smbk-tree-view__item" style="--smbk-tree-level:2"><span class="smbk-tree-view__row"><span class="smbk-tree-view__toggle" aria-hidden="true"></span><i class="ph ph-note smbk-icon smbk-icon--size-md smbk-tree-view__icon" aria-hidden="true"></i><span id="file-tree-3-label" class="smbk-tree-view__label" data-label="checkout.tsx">checkout.tsx</span><span class="smbk-tree-view__changed"><span id="file-tree-3-changed" class="smbk-tree-view__hidden">changed</span></span></span></li><li role="treeitem" data-key="basket" aria-level="3" aria-selected="false" aria-labelledby="file-tree-4-label file-tree-4-changed" tabindex="-1" class="smbk-tree-view__item" style="--smbk-tree-level:2"><span class="smbk-tree-view__row"><span class="smbk-tree-view__toggle" aria-hidden="true"></span><i class="ph ph-note smbk-icon smbk-icon--size-md smbk-tree-view__icon" aria-hidden="true"></i><span id="file-tree-4-label" class="smbk-tree-view__label" data-label="basket.tsx">basket.tsx</span><span class="smbk-tree-view__changed"><span id="file-tree-4-changed" class="smbk-tree-view__hidden">changed</span></span></span></li><li role="treeitem" data-key="home" aria-level="3" aria-selected="false" aria-labelledby="file-tree-5-label" tabindex="-1" class="smbk-tree-view__item" style="--smbk-tree-level:2"><span class="smbk-tree-view__row"><span class="smbk-tree-view__toggle" aria-hidden="true"></span><i class="ph ph-note smbk-icon smbk-icon--size-md smbk-tree-view__icon" aria-hidden="true"></i><span id="file-tree-5-label" class="smbk-tree-view__label" data-label="home.tsx">home.tsx</span></span></li></ul></li><li role="treeitem" data-key="styles" aria-level="2" aria-expanded="true" aria-selected="false" aria-labelledby="file-tree-6-label file-tree-6-meta file-tree-6-changed" tabindex="-1" class="smbk-tree-view__item" style="--smbk-tree-level:1"><span class="smbk-tree-view__row"><span class="smbk-tree-view__toggle" aria-hidden="true"><i class="ph ph-caret-right smbk-icon smbk-icon--size-md smbk-tree-view__caret" aria-hidden="true"></i></span><i class="ph ph-folder-simple smbk-icon smbk-icon--size-md smbk-tree-view__icon" aria-hidden="true"></i><span id="file-tree-6-label" class="smbk-tree-view__label" data-label="styles">styles</span><span id="file-tree-6-meta" class="smbk-tree-view__meta">1</span><span class="smbk-tree-view__changed"><span id="file-tree-6-changed" class="smbk-tree-view__hidden">changed</span></span></span><ul role="group" class="smbk-tree-view__group"><li role="treeitem" data-key="checkout-css" aria-level="3" aria-selected="false" aria-labelledby="file-tree-7-label file-tree-7-changed" tabindex="-1" class="smbk-tree-view__item" style="--smbk-tree-level:2"><span class="smbk-tree-view__row"><span class="smbk-tree-view__toggle" aria-hidden="true"></span><i class="ph ph-note smbk-icon smbk-icon--size-md smbk-tree-view__icon" aria-hidden="true"></i><span id="file-tree-7-label" class="smbk-tree-view__label" data-label="checkout.css">checkout.css</span><span class="smbk-tree-view__changed"><span id="file-tree-7-changed" class="smbk-tree-view__hidden">changed</span></span></span></li></ul></li></ul></li>
        <li
          role="treeitem"
          data-key="package"
          aria-level="1"
          aria-selected="false"
          aria-labelledby="file-tree-8-label"
          tabindex="-1"
          class="smbk-tree-view__item"
          style="--smbk-tree-level:0"
        ><span class="smbk-tree-view__row"><span class="smbk-tree-view__toggle" aria-hidden="true"></span><i class="ph ph-note smbk-icon smbk-icon--size-md smbk-tree-view__icon" aria-hidden="true"></i><span id="file-tree-8-label" class="smbk-tree-view__label" data-label="package.json">package.json</span></span></li>
      </ul>
    </div>
    <hr class="smbk-divider smbk-divider--orientation-horizontal smbk-card__divider"/>
    <footer class="smbk-card__footer smbk-card__footer--type-meta">
      <div class="smbk-card__footer-row">
        <div class="smbk-card__footer-start">Updated just now</div>
        <div class="smbk-card__footer-end">from new-checkout</div>
      </div>
    </footer>
  </div>
</article>
```

```tsx
import { Card, CardHeader, CardFooter, CardBody } from './design-system/components/card/card.js';
import { Button } from './design-system/components/button/button.js';
import { TreeView, TreeItem } from './design-system/components/tree-view/tree-view.js';

<Card surface="raised" layout="panel" className="site-file-tree" aria-label="Changes" header={<CardHeader size="sm" title="Changes" meta="new-checkout · 3 changed" menu={<Button tone="ghost" size="sm" icon-only icon="DotsThree" aria-label="More change actions" />} />} footer={<CardFooter type="meta" start="Updated just now" end="from new-checkout" />}>
  <CardBody type="slot">
    <TreeView className="site-file-tree__tree" aria-label="Changed files on new-checkout" defaultExpandedKeys={["src", "pages", "styles"]} defaultSelectedKey="checkout">
      <TreeItem id="src" label="src" meta="3">
        <TreeItem id="pages" label="pages" meta="2" changed>
          <TreeItem id="checkout" label="checkout.tsx" changed />
          <TreeItem id="basket" label="basket.tsx" changed />
          <TreeItem id="home" label="home.tsx" />
        </TreeItem>
        <TreeItem id="styles" label="styles" meta="1" changed>
          <TreeItem id="checkout-css" label="checkout.css" changed />
        </TreeItem>
      </TreeItem>
      <TreeItem id="package" label="package.json" />
    </TreeView>
  </CardBody>
</Card>
```

## Purpose

Shows a project's files in a panel beside what a reader is working on: which line of the project it shows, how many files there are and how many changed, the files as a tree to open and choose from, and when the tree was read.

## Anatomy

- The panel, the Card component in its raised surface and panel layout: the header on a band, the body padded, the divider over the footer.
- The header: the title in the small header size, a line under it with the project's line and the counts, and a menu button at its end.
- The tree, the Tree view component, in the slot body. Past the list height it scrolls inside the panel.
- The footer, the card's meta kind: when the tree was read, and from which line.

## When to use

- Beside an editor, a preview or a review, where a reader moves between a project's files.

## When not to use

- For a list of a few files with no folders. Use a list.
- For a site's pages. Use the Tree view on its own, or the site's navigation.

## Behavior

The tree behaves as the Tree view does: folders open and close, one file is chosen at a time, and the arrow keys move through it. The panel takes no focus of its own.

## Accessibility

### What it does for you

- The tree is one stop for the Tab key; inside it the arrow keys move, open and close, and Home and End reach its ends.
- The chosen file is shown by its fill and its weight, never by color alone.

### What you must do

- Name the panel by its title, and the tree for what it holds, such as "Files on main".
- Name the menu button for what it holds, such as "More file actions".

### How to test

- Tab into the tree, move through it with the arrow keys, open and close a folder, and reach its ends with Home and End; Tab again leaves it.
- With a screen reader, the tree is read by its name, and the menu button by what it holds.
- In a forced-color mode, the chosen file still stands out by its weight.

### Keyboard

| Key | What it does |
|---|---|
| Tab | Moves focus into the tree, onto the row focused last, else the chosen one, else the first; pressed again, leaves the tree. |
| Down Arrow or Up Arrow | Moves to the next or previous row the reader can see. |
| Right Arrow | Opens a closed folder, or moves into an open one's first row. |
| Left Arrow | Closes an open folder, or moves out to the folder a row is in. In a right-to-left page Left and Right swap. |
| Home or End | Moves to the first or the last row. |
| Enter | Acts on the row, and opens or closes it if it is a folder. |
| Space | Chooses the row without opening it. |
| * | Opens every folder beside the row. |
| Any character | Typing a row's first letters moves to it. |

## Usage rules

### Do

- Open the folders on the way to the file being read.
- Say in the header which line of the project the tree shows.

### Don't

- Put two trees in one panel.
- Let a long tree push the footer off the screen.

## Stylesheet

```css
/* site-file-tree — File tree, a block: a project's files in a panel, its title saying which line it shows and how
 * many files have changed, and a footer saying when it was read. Copy it into your project and rename site- to
 * your project's own prefix. The frame is the Card component, raised surface, in its panel layout, and the tree is
 * the Tree view component; Figma draws nothing else. The block's one rule keeps a long tree inside the panel: past
 * the list height it scrolls. */

.site-file-tree__tree {
  max-block-size: var(--smbk-size-list-max-height);
  overflow-y: auto;
}
```
