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