xref: /expo/docs/ui/components/FileTree/index.tsx (revision 604792ab)
1import { FileCode01Icon, LayoutAlt01Icon, FolderIcon } from '@expo/styleguide-icons';
2import { HTMLAttributes, ReactNode } from 'react';
3
4type FileTreeProps = HTMLAttributes<HTMLDivElement> & {
5  files?: string[];
6};
7
8type FileObject = {
9  [key: string]: FileObject;
10};
11
12export function FileTree({ files = [], ...rest }: FileTreeProps) {
13  return (
14    <div className="text-xs border border-default rounded-md bg-default mb-4 p-2 pb-4" {...rest}>
15      {renderStructure(generateStructure(files))}
16    </div>
17  );
18}
19
20function generateStructure(files: string[]): FileObject {
21  const structure = {};
22  files.forEach(path =>
23    path.split('/').reduce((acc: FileObject, key) => acc[key] ?? (acc[key] = {}), structure)
24  );
25  return structure;
26}
27
28function renderStructure(structure: FileObject, level = 0): ReactNode {
29  return Object.entries(structure).map(([key, value]) => {
30    const FileIcon = getIconForFile(key);
31    return Object.keys(value).length ? (
32      <div className="mt-1 pt-1 px-2 rounded-sm flex flex-col">
33        <div className="flex items-center">
34          {' '.repeat(level)}
35          <FolderIcon className="text-icon-tertiary mr-2 opacity-60" />
36          <code className="text-secondary">{key}</code>
37        </div>
38        {renderStructure(value, level + 1)}
39      </div>
40    ) : (
41      <div className="mt-1 pl-3 pt-1 px-2 rounded-sm flex items-center">
42        {' '.repeat(Math.max(level - 1, 0))}
43        <FileIcon className="text-icon-tertiary mr-2" />
44        <code className="text-default">{key}</code>
45      </div>
46    );
47  });
48}
49
50function getIconForFile(filename: string) {
51  if (/_layout\.[jt]sx?/.test(filename)) {
52    return LayoutAlt01Icon;
53  }
54  return FileCode01Icon;
55}
56