import React, { createContext, useCallback, useContext, useMemo, useState, type ReactNode, } from 'react'; import { View, Text, TouchableOpacity } from 'react-native'; import { ChevronRight, File as FileIcon, Folder, FolderOpen, } from 'lucide-react-native'; import Animated, { useAnimatedStyle, withTiming, useSharedValue, } from 'react-native-reanimated'; // ====================== Custom Collapsible ====================== type CollapsibleProps = { open: boolean; children: ReactNode; }; const Collapsible = ({ open, children }: CollapsibleProps) => { const progress = useSharedValue(open ? 1 : 0); const animatedStyle = useAnimatedStyle(() => ({ opacity: progress.value, maxHeight: progress.value * 9999, }),[progress.value]); React.useEffect(() => { progress.value = withTiming(open ? 1 : 0, { duration: 180 }); }, [open]); return {children}; }; // ====================== Contexts ====================== interface FileTreeContextType { expandedPaths: Set; togglePath: (path: string) => void; selectedPath?: string; onSelect?: (path: string) => void; } const FileTreeContext = createContext({ expandedPaths: new Set(), togglePath: () => {}, }); interface FileTreeFolderContextType { path: string; name: string; isExpanded: boolean; } const FileTreeFolderContext = createContext({ path: '', name: '', isExpanded: false, }); // ====================== Main FileTree ====================== export type FileTreeProps = { expanded?: Set; defaultExpanded?: Set; selectedPath?: string; onSelect?: (path: string) => void; onExpandedChange?: (expanded: Set) => void; className?: string; children: ReactNode; }; export const FileTree = ({ expanded: controlledExpanded, defaultExpanded = new Set(), selectedPath, onSelect, onExpandedChange, className, children, }: FileTreeProps) => { const [internalExpanded, setInternalExpanded] = useState(defaultExpanded); const expandedPaths = controlledExpanded ?? internalExpanded; const togglePath = useCallback( (path: string) => { const newExpanded = new Set(expandedPaths); if (newExpanded.has(path)) { newExpanded.delete(path); } else { newExpanded.add(path); } setInternalExpanded(newExpanded); onExpandedChange?.(newExpanded); }, [expandedPaths, onExpandedChange] ); const contextValue = useMemo( () => ({ expandedPaths, togglePath, selectedPath, onSelect }), [expandedPaths, togglePath, selectedPath, onSelect] ); return ( {children} ); }; // ====================== Folder ====================== export type FileTreeFolderProps = { path: string; name: string; children?: ReactNode; className?: string; }; export const FileTreeFolder = ({ path, name, children, className, }: FileTreeFolderProps) => { const { expandedPaths, togglePath, selectedPath, onSelect } = useContext(FileTreeContext); const isExpanded = expandedPaths.has(path); const isSelected = selectedPath === path; const handleToggle = useCallback(() => togglePath(path), [togglePath, path]); const handleSelect = useCallback(() => onSelect?.(path), [onSelect, path]); const folderContextValue = useMemo( () => ({ isExpanded, name, path }), [isExpanded, name, path] ); return ( {isExpanded ? ( ) : ( )} {name} {children} ); }; // ====================== File ====================== export type FileTreeFileProps = { path: string; name: string; icon?: ReactNode; className?: string; }; export const FileTreeFile = ({ path, name, icon, className, }: FileTreeFileProps) => { const { selectedPath, onSelect } = useContext(FileTreeContext); const isSelected = selectedPath === path; const handlePress = useCallback(() => onSelect?.(path), [onSelect, path]); return ( {icon ?? } {name} ); }; // ====================== Actions ====================== export const FileTreeActions = ({ children, className, }: { children: ReactNode; className?: string; }) => { return ( {children} ); };