11
22import React from 'react' ;
3+ import { motion , AnimatePresence } from 'framer-motion' ;
34import { FileNode } from '../types' ;
45
56interface FileTreeProps {
@@ -8,6 +9,7 @@ interface FileTreeProps {
89 onDeleteFile : ( path : string ) => void ;
910 onCopyPath : ( path : string ) => void ;
1011 onToggleExclude : ( path : string ) => void ;
12+ onDirDoubleClick ?: ( ) => void ;
1113 selectedFilePath : string | null ;
1214 showCharCount : boolean ;
1315}
@@ -138,16 +140,17 @@ const getFileIcon = (fileName: string): IconEntry => {
138140export { getFileIcon } ;
139141export type { IconEntry } ;
140142
141- const FileTreeNode : React . FC < {
142- node : FileNode ;
143- onFileSelect : ( path : string ) => void ;
144- onDeleteFile : ( path : string ) => void ;
145- onCopyPath : ( path : string ) => void ;
143+ const FileTreeNode : React . FC < {
144+ node : FileNode ;
145+ onFileSelect : ( path : string ) => void ;
146+ onDeleteFile : ( path : string ) => void ;
147+ onCopyPath : ( path : string ) => void ;
146148 onToggleExclude : ( path : string ) => void ;
147- level : number ;
148- selectedFilePath : string | null ;
149- showCharCount : boolean ;
150- } > = React . memo ( ( { node, onFileSelect, onDeleteFile, onCopyPath, onToggleExclude, level, selectedFilePath, showCharCount } ) => {
149+ onDirDoubleClick ?: ( ) => void ;
150+ level : number ;
151+ selectedFilePath : string | null ;
152+ showCharCount : boolean ;
153+ } > = React . memo ( ( { node, onFileSelect, onDeleteFile, onCopyPath, onToggleExclude, onDirDoubleClick, level, selectedFilePath, showCharCount } ) => {
151154 const [ isOpen , setIsOpen ] = React . useState ( true ) ;
152155
153156 const handleToggle = ( ) => {
@@ -163,6 +166,13 @@ const FileTreeNode: React.FC<{
163166 handleToggle ( ) ;
164167 }
165168 } ;
169+
170+ const handleDoubleClick = ( ) => {
171+ if ( node . isDirectory && onDirDoubleClick ) {
172+ setIsOpen ( ! isOpen ) ;
173+ onDirDoubleClick ( ) ;
174+ }
175+ } ;
166176
167177 const handleDelete = ( e : React . MouseEvent ) => {
168178 e . stopPropagation ( ) ;
@@ -214,6 +224,7 @@ const FileTreeNode: React.FC<{
214224 < div
215225 className = { `group flex flex-col py-1 px-2 rounded-md cursor-pointer hover:bg-light-border dark:hover:bg-dark-border/50 transition-colors duration-150 ${ statusClass } ${ isSelected ? 'bg-primary/10 dark:bg-primary/20' : '' } ` }
216226 onClick = { handleSelect }
227+ onDoubleClick = { handleDoubleClick }
217228 title = { title }
218229 >
219230 { /* Top Row: Icon, Name, Stats */ }
@@ -275,18 +286,28 @@ const FileTreeNode: React.FC<{
275286 </ div >
276287 ) }
277288 </ div >
278- { node . isDirectory && isOpen && (
279- < ul className = "pl-0" >
280- { node . children . map ( child => (
281- < FileTreeNode key = { child . path } node = { child } onFileSelect = { onFileSelect } onDeleteFile = { onDeleteFile } onCopyPath = { onCopyPath } onToggleExclude = { onToggleExclude } level = { level + 1 } selectedFilePath = { selectedFilePath } showCharCount = { showCharCount } />
282- ) ) }
283- </ ul >
289+ { node . isDirectory && (
290+ < AnimatePresence initial = { false } >
291+ { isOpen && (
292+ < motion . ul
293+ className = "pl-0 overflow-hidden"
294+ initial = { { height : 0 , opacity : 0 } }
295+ animate = { { height : 'auto' , opacity : 1 } }
296+ exit = { { height : 0 , opacity : 0 } }
297+ transition = { { duration : 0.15 , ease : 'easeInOut' } }
298+ >
299+ { node . children . map ( child => (
300+ < FileTreeNode key = { child . path } node = { child } onFileSelect = { onFileSelect } onDeleteFile = { onDeleteFile } onCopyPath = { onCopyPath } onToggleExclude = { onToggleExclude } onDirDoubleClick = { onDirDoubleClick } level = { level + 1 } selectedFilePath = { selectedFilePath } showCharCount = { showCharCount } />
301+ ) ) }
302+ </ motion . ul >
303+ ) }
304+ </ AnimatePresence >
284305 ) }
285306 </ li >
286307 ) ;
287308} ) ;
288309
289- const FileTree : React . FC < FileTreeProps > = ( { nodes, onFileSelect, onDeleteFile, onCopyPath, onToggleExclude, selectedFilePath, showCharCount } ) => {
310+ const FileTree : React . FC < FileTreeProps > = ( { nodes, onFileSelect, onDeleteFile, onCopyPath, onToggleExclude, onDirDoubleClick , selectedFilePath, showCharCount } ) => {
290311 if ( ! nodes || nodes . length === 0 ) {
291312 return < div className = "p-4 text-center text-sm text-light-subtle-text dark:text-dark-subtle-text" > 未加载文件。</ div > ;
292313 }
@@ -295,7 +316,7 @@ const FileTree: React.FC<FileTreeProps> = ({ nodes, onFileSelect, onDeleteFile,
295316 < h3 className = "text-xs font-semibold px-2 mb-2 text-light-subtle-text dark:text-dark-subtle-text uppercase tracking-wider" > 资源管理器</ h3 >
296317 < ul className = "pl-0" >
297318 { nodes . map ( node => (
298- < FileTreeNode key = { node . path } node = { node } onFileSelect = { onFileSelect } onDeleteFile = { onDeleteFile } onCopyPath = { onCopyPath } onToggleExclude = { onToggleExclude } level = { 1 } selectedFilePath = { selectedFilePath } showCharCount = { showCharCount } />
319+ < FileTreeNode key = { node . path } node = { node } onFileSelect = { onFileSelect } onDeleteFile = { onDeleteFile } onCopyPath = { onCopyPath } onToggleExclude = { onToggleExclude } onDirDoubleClick = { onDirDoubleClick } level = { 1 } selectedFilePath = { selectedFilePath } showCharCount = { showCharCount } />
299320 ) ) }
300321 </ ul >
301322 </ div >
0 commit comments