Skip to content

FileTree ​

A tree of file paths, drawn in the library's theme on the @pierre/trees engine (pinned at 1.0.0-beta.6). useFileTree creates the model, and FileTree.Root draws it with 25 pixel rows, 12 pixel chevrons, names truncated at the end and a sideways shift for names that do not fit.

tsx
import { FileTree, useFileTree } from '@adecore/ui';
tsx
import { useState } from 'react';
import { FileTree, useFileTree } from '@adecore/ui';

export default function FileTreeDemo() {
    const [viewed, setViewed] = useState<ReadonlySet<string>>(new Set());
    const [opened, setOpened] = useState<string | null>(null);
    const { model } = useFileTree({
        paths: ['src/components/Button.tsx', 'src/components/Checkbox.tsx', 'src/utilities/path.ts', 'README.md'],
        initialExpansion: 'open',
        flattenEmptyDirectories: true
    });
    return (
        <div className="w-80">
            <FileTree.Root
                model={model}
                label="Project files"
                onActivate={setOpened}
                className="h-48"
                renderControl={(row) =>
                    row.kind === 'file' ? (
                        <FileTree.Checkbox
                            label={`Viewed ${row.path}`}
                            checked={viewed.has(row.path)}
                            onCheckedChange={(checked) =>
                                setViewed((current) => {
                                    const next = new Set(current);
                                    if (checked) {
                                        next.add(row.path);
                                    } else {
                                        next.delete(row.path);
                                    }
                                    return next;
                                })
                            }
                        />
                    ) : null
                }
                renderDecoration={(row) => (row.kind === 'file' ? <span className="font-mono text-xs text-text-faint">+2</span> : null)}
            />
            {opened !== null && <p className="mt-2 truncate text-xs text-text-muted">{opened}</p>}
        </div>
    );
}

For nodes that are not file paths, such as connections or database objects, use Tree, which shares the look and leaves the model to you.

A file browser ​

tsx
import { useState } from 'react';
import { FileTree, useFileTree } from '@adecore/ui';

export function ProjectFiles() {
    const [opened, setOpened] = useState<string | null>(null);
    const { model } = useFileTree({
        paths: ['src/index.ts', 'src/utilities/path.ts', 'README.md'],
        initialExpansion: 'open',
        flattenEmptyDirectories: true
    });
    return (
        <div>
            <FileTree.Root model={model} label="Project files" className="h-64" onActivate={setOpened} />
            {opened !== null && <p>{opened}</p>}
        </div>
    );
}

Paths are POSIX and relative to a root you choose. A directory path ends in /, a file path does not, and the model derives the parent directories from the paths. Keep absolute filesystem paths and permission checks in your own code.

The view virtualizes, so it needs a height to scroll in. Without label the tree is named "Files" (tree.label); give it a name that says which files it holds.

Defaults and ownership ​

useFileTree starts with closed directories, directories before files in numeric order (FileTree.compareRows), the FileIcon set (FILE_TREE_ICONS), and the engine's search, sticky folders and context menu turned off. You can override any of these. Density and row height are fixed. unsafeCSS is appended to the wrapper's own styles inside the tree's shadow root.

The options only create the model. A new paths array or initialExpansion on a later render does not rebuild it; use the model's methods instead (resetPaths, add, remove, move, batch, setGitStatus, setIcons). Only onSelectionChange is kept current by the hook.

tsx
import { useEffect } from 'react';
import { FileTree, useFileTree } from '@adecore/ui';

export function Files({ paths }: { paths: readonly string[] }) {
    const { model } = useFileTree({ paths });
    useEffect(() => {
        model.resetPaths(paths);
    }, [model, paths]);
    return <FileTree.Root model={model} label="Files" className="h-64" />;
}

resetPaths replaces the whole listing and its expansion; Models, loading and expansion shows how to keep folds, and how to apply smaller changes. Changing the model changes what is drawn, never the filesystem.

Your code owns filesystem changes, listings, search requests, caches and permissions; the model owns the displayed paths, selection and expansion. The hook cleans up its model when the component unmounts, so do not call cleanUp() on it while it is mounted. Unsubscribe your own model listeners and cancel your own requests when the root changes.

Guides ​

Limits ​

The row controls sit in slots beside the engine's own row buttons, in its shadow root. Pointer and keyboard behavior there is tested; screen reader behavior across those slots is not verified yet. The wrapper relies on internals of the pinned engine version, so a dependency update needs the checks listed in the reference.

Licenses and third-party notices are listed in each package.