← back to Letsbegin
components/DirectoryTree.tsx
121 lines
'use client'
import { useState } from 'react'
interface DirectoryNode {
name: string
path: string
type: 'file' | 'directory'
children?: DirectoryNode[]
}
interface Props {
node: DirectoryNode
depth: number
selectedPath: string
onSelect?: (path: string) => void
}
export default function DirectoryTree({ node, depth, selectedPath, onSelect }: Props) {
const [isExpanded, setIsExpanded] = useState(depth < 2)
const isDirectory = node.type === 'directory'
const hasChildren = isDirectory && node.children && node.children.length > 0
const isSelected = node.path === selectedPath
const fileIcon = getFileIcon(node.name, isDirectory)
return (
<div>
<div
className={`tree-item ${isSelected ? 'selected' : ''}`}
style={{ paddingLeft: `${depth * 16 + 8}px` }}
onClick={() => {
if (isDirectory) {
setIsExpanded(!isExpanded)
}
onSelect?.(node.path)
}}
>
{/* Chevron */}
<div className={`tree-chevron ${isExpanded && hasChildren ? 'expanded' : ''}`}>
{hasChildren ? (
<svg className="w-4 h-4 text-[var(--foreground-tertiary)]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
</svg>
) : (
<span className="w-4" />
)}
</div>
{/* Icon */}
<span className="text-sm">{fileIcon}</span>
{/* Name */}
<span className="text-sm truncate">{node.name}</span>
</div>
{/* Children */}
{isExpanded && hasChildren && (
<div>
{node.children!
.sort((a, b) => {
// Directories first, then files
if (a.type !== b.type) {
return a.type === 'directory' ? -1 : 1
}
return a.name.localeCompare(b.name)
})
.map((child) => (
<DirectoryTree
key={child.path}
node={child}
depth={depth + 1}
selectedPath={selectedPath}
onSelect={onSelect}
/>
))}
</div>
)}
</div>
)
}
function getFileIcon(name: string, isDirectory: boolean): string {
if (isDirectory) {
const specialFolders: Record<string, string> = {
'node_modules': '📦',
'.git': '🔧',
'app': '📱',
'components': '🧩',
'lib': '📚',
'api': '🔌',
'public': '🌐',
'styles': '🎨',
'tests': '🧪',
'__tests__': '🧪',
}
return specialFolders[name] || '📁'
}
const ext = name.split('.').pop()?.toLowerCase() || ''
const icons: Record<string, string> = {
'ts': '📘',
'tsx': '⚛️',
'js': '📒',
'jsx': '⚛️',
'json': '📋',
'md': '📝',
'css': '🎨',
'scss': '🎨',
'html': '🌐',
'env': '🔐',
'gitignore': '🔧',
'lock': '🔒',
'png': '🖼️',
'jpg': '🖼️',
'svg': '🎭',
}
return icons[ext] || '📄'
}