import { ChevronDownIcon, ChevronRightIcon, FileIcon, FolderIcon, } from 'lucide-react'; import { useMemo, useState } from 'react'; import { Link } from '#/components/atoms/link.jsx'; import { buildFileTree, filterFiles } from '#/lib/file-tree.js'; import { cn } from '#/lib/utils.js'; /** Whether a directory holds, or is, the path in view. */ const onPath = (dir, path) => dir === path || path.startsWith(`${dir}/`) || dir.startsWith(`${path}/`); /** * One row per node, indented by depth. A directory opens on its own state; * one on the way to the path in view opens by itself, so the branch a reader * is standing on is the one they see. A filter opens everything it matched. */ function Node({ node, depth, base, refName, path, selected, filter }) { const [manual, setManual] = useState(/** @type {boolean|null} */ (null)); const pad = { paddingLeft: `${depth * 12 + 10}px` }; if (node.type === 'file') { const current = node.path === selected; return ( {node.name} ); } const open = filter ? true : (manual ?? onPath(node.path, path)); const current = node.path === selected; const Chevron = open ? ChevronDownIcon : ChevronRightIcon; return (
{node.name}
{open && node.children.map((child) => ( ))}
); } /** * The whole repository as a tree, down the rail beside whatever is open: * every directory from the root, closed but for the ones on the way to the * path in view, the way a forge's file rail reads. * * `path` is the directory in view, which opens the branch leading to it. * `selected` is the row a reader is standing on, a file or the directory * itself. `filter` narrows the tree to the paths holding the text. * @param {{repo: string, refName: string, files: Array<{path: string}>, path: string, selected?: string, filter?: string}} props */ export function RepoTree({ repo, refName, files, path, selected = '', filter = '', }) { const base = `/${encodeURIComponent(repo)}`; const ref = encodeURI(refName); const roots = useMemo( () => buildFileTree(filterFiles(files, filter)), [files, filter], ); if (roots.length === 0) { return (

{filter ? 'Nothing matches.' : 'No files.'}

); } return (
{roots.map((node) => ( ))}
); }