Initial commit: add project files and README
This commit is contained in:
97
src/components/DocTree.tsx
Normal file
97
src/components/DocTree.tsx
Normal file
@@ -0,0 +1,97 @@
|
||||
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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user