98 lines
2.5 KiB
TypeScript
98 lines
2.5 KiB
TypeScript
import React, { useState } from 'react'
|
|
import { ChevronRight, ChevronDown, Folder, FileText } from 'lucide-react'
|
|
import { clsx } from 'clsx'
|
|
import type { DocFile } from '@/lib/types'
|
|
import { getDisplayName } from '@/lib/parser'
|
|
|
|
interface DocTreeProps {
|
|
files: DocFile[]
|
|
selectedPath?: string
|
|
onSelect: (file: DocFile) => void
|
|
}
|
|
|
|
interface DocTreeNodeProps {
|
|
file: DocFile
|
|
level: number
|
|
selectedPath?: string
|
|
onSelect: (file: DocFile) => void
|
|
}
|
|
|
|
const DocTreeNode = React.memo(({ file, level, selectedPath, onSelect }: DocTreeNodeProps) => {
|
|
const [expanded, setExpanded] = useState(level === 0)
|
|
|
|
const isSelected = selectedPath === file.relativePath
|
|
const isDir = file.isDir
|
|
|
|
const handleClick = (e: React.MouseEvent) => {
|
|
e.stopPropagation()
|
|
if (isDir) {
|
|
setExpanded(!expanded)
|
|
} else {
|
|
onSelect(file)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div>
|
|
<div
|
|
className={clsx(
|
|
'flex items-center gap-2 py-1 px-2 rounded-md cursor-pointer transition-colors text-sm',
|
|
isSelected
|
|
? 'bg-gray-600 text-white'
|
|
: 'hover:bg-gray-700 text-gray-300'
|
|
)}
|
|
onClick={handleClick}
|
|
style={{ paddingLeft: `${level * 16 + 8}px` }}
|
|
>
|
|
<span className="text-gray-400 shrink-0">
|
|
{isDir ? (
|
|
expanded ? <ChevronDown size={16} /> : <ChevronRight size={16} />
|
|
) : (
|
|
<span className="w-4" />
|
|
)}
|
|
</span>
|
|
|
|
<span className="text-gray-400 shrink-0">
|
|
{isDir ? <Folder size={16} /> : <FileText size={16} />}
|
|
</span>
|
|
|
|
<span className="truncate">{getDisplayName(file.name)}</span>
|
|
</div>
|
|
|
|
{isDir && expanded && file.children && (
|
|
<div>
|
|
{file.children.map((child) => (
|
|
<DocTreeNode
|
|
key={child.relativePath}
|
|
file={child}
|
|
level={level + 1}
|
|
selectedPath={selectedPath}
|
|
onSelect={onSelect}
|
|
/>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
})
|
|
|
|
DocTreeNode.displayName = 'DocTreeNode'
|
|
|
|
export const DocTree = ({ files, selectedPath, onSelect }: DocTreeProps) => {
|
|
return (
|
|
<div className="select-none w-full h-full overflow-auto py-2">
|
|
<div className="space-y-1">
|
|
{files.map((file) => (
|
|
<DocTreeNode
|
|
key={file.relativePath}
|
|
file={file}
|
|
level={0}
|
|
selectedPath={selectedPath}
|
|
onSelect={onSelect}
|
|
/>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|