• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

agentic-dev-library / thumbcode / 21118624864

18 Jan 2026 09:07PM UTC coverage: 28.754% (-14.4%) from 43.195%
21118624864

Pull #50

github

web-flow
Merge dfbe59181 into c7c91f5ce
Pull Request #50: feat(components): expand component library to production-ready set

181 of 1095 branches covered (16.53%)

Branch coverage included in aggregate %.

0 of 307 new or added lines in 21 files covered. (0.0%)

495 of 1256 relevant lines covered (39.41%)

1.29 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/components/code/FileTree.tsx
1
/**
2
 * FileTree Component
3
 *
4
 * Displays a hierarchical file/folder structure.
5
 * Supports expandable folders and file type icons.
6
 */
7

8
import { useMemo, useState } from 'react';
9
import { Pressable, Text, View } from 'react-native';
10

11
interface FileNode {
12
  name: string;
13
  type: 'file' | 'folder';
14
  path: string;
15
  children?: FileNode[];
16
  modified?: boolean;
17
  added?: boolean;
18
  deleted?: boolean;
19
}
20

21
interface FileTreeProps {
22
  /** Root nodes of the tree */
23
  data: FileNode[];
24
  /** Callback when a file is selected */
25
  onSelectFile?: (path: string) => void;
26
  /** Currently selected file path */
27
  selectedPath?: string;
28
  /** Initially expanded folders */
29
  defaultExpanded?: string[];
30
  /** Show file status indicators */
31
  showStatus?: boolean;
32
}
33

34
interface FileTreeNodeProps {
35
  node: FileNode;
36
  depth: number;
37
  onSelectFile?: (path: string) => void;
38
  selectedPath?: string;
39
  expandedPaths: Set<string>;
40
  toggleExpanded: (path: string) => void;
41
  showStatus?: boolean;
42
}
43

44
function getFileIcon(name: string): string {
NEW
45
  const ext = name.split('.').pop()?.toLowerCase();
×
NEW
46
  const iconMap: Record<string, string> = {
×
47
    ts: '📘',
48
    tsx: '⚛️',
49
    js: '📙',
50
    jsx: '⚛️',
51
    json: '📋',
52
    md: '📝',
53
    css: '🎨',
54
    scss: '🎨',
55
    html: '🌐',
56
    png: '🖼️',
57
    jpg: '🖼️',
58
    svg: '📐',
59
    git: '🔧',
60
    env: '⚙️',
61
    lock: '🔒',
62
  };
NEW
63
  return iconMap[ext || ''] || '📄';
×
64
}
65

66
function FileTreeNode({
67
  node,
68
  depth,
69
  onSelectFile,
70
  selectedPath,
71
  expandedPaths,
72
  toggleExpanded,
73
  showStatus,
74
}: FileTreeNodeProps) {
NEW
75
  const isExpanded = expandedPaths.has(node.path);
×
NEW
76
  const isSelected = selectedPath === node.path;
×
NEW
77
  const isFolder = node.type === 'folder';
×
NEW
78
  const hasChildren = node.children && node.children.length > 0;
×
79

NEW
80
  const icon = isFolder ? (isExpanded ? '📂' : '📁') : getFileIcon(node.name);
×
81

NEW
82
  const statusColor = node.added
×
83
    ? 'text-teal-400'
84
    : node.modified
×
85
      ? 'text-gold-400'
86
      : node.deleted
×
87
        ? 'text-coral-400'
88
        : '';
89

NEW
90
  return (
×
91
    <View>
92
      <Pressable
93
        onPress={() => {
NEW
94
          if (isFolder && hasChildren) {
×
NEW
95
            toggleExpanded(node.path);
×
NEW
96
          } else if (!isFolder) {
×
NEW
97
            onSelectFile?.(node.path);
×
98
          }
99
        }}
100
        className={`flex-row items-center py-1.5 px-2 ${
101
          isSelected ? 'bg-teal-600/20' : 'active:bg-neutral-700'
×
102
        }`}
103
        style={{ paddingLeft: 8 + depth * 16 }}
104
      >
105
        {isFolder && hasChildren && (
×
106
          <Text className="text-xs text-neutral-500 w-4 mr-1">{isExpanded ? '▼' : '▶'}</Text>
×
107
        )}
108
        {(!isFolder || !hasChildren) && <View className="w-4 mr-1" />}
×
109

110
        <Text className="mr-2">{icon}</Text>
111

112
        <Text
113
          className={`font-mono text-sm flex-1 ${
114
            isSelected ? 'text-teal-300' : 'text-neutral-200'
×
115
          } ${statusColor}`}
116
          numberOfLines={1}
117
        >
118
          {node.name}
119
        </Text>
120

121
        {showStatus && (node.added || node.modified || node.deleted) && (
×
122
          <Text className={`text-xs ${statusColor} ml-2`}>
123
            {node.added ? 'A' : node.modified ? 'M' : 'D'}
×
124
          </Text>
125
        )}
126
      </Pressable>
127

128
      {isFolder && isExpanded && hasChildren && (
×
129
        <View>
130
          {node.children?.map((child) => (
NEW
131
            <FileTreeNode
×
132
              key={child.path}
133
              node={child}
134
              depth={depth + 1}
135
              onSelectFile={onSelectFile}
136
              selectedPath={selectedPath}
137
              expandedPaths={expandedPaths}
138
              toggleExpanded={toggleExpanded}
139
              showStatus={showStatus}
140
            />
141
          ))}
142
        </View>
143
      )}
144
    </View>
145
  );
146
}
147

148
export function FileTree({
149
  data,
150
  onSelectFile,
151
  selectedPath,
152
  defaultExpanded = [],
×
153
  showStatus = true,
×
154
}: FileTreeProps) {
NEW
155
  const [expandedPaths, setExpandedPaths] = useState<Set<string>>(new Set(defaultExpanded));
×
156

NEW
157
  const toggleExpanded = (path: string) => {
×
NEW
158
    setExpandedPaths((prev) => {
×
NEW
159
      const next = new Set(prev);
×
NEW
160
      if (next.has(path)) {
×
NEW
161
        next.delete(path);
×
162
      } else {
NEW
163
        next.add(path);
×
164
      }
NEW
165
      return next;
×
166
    });
167
  };
168

169
  // Sort nodes: folders first, then alphabetically
NEW
170
  const sortedData = useMemo(() => {
×
NEW
171
    const sortNodes = (nodes: FileNode[]): FileNode[] => {
×
NEW
172
      return [...nodes]
×
173
        .sort((a, b) => {
NEW
174
          if (a.type !== b.type) {
×
NEW
175
            return a.type === 'folder' ? -1 : 1;
×
176
          }
NEW
177
          return a.name.localeCompare(b.name);
×
178
        })
NEW
179
        .map((node) => ({
×
180
          ...node,
181
          children: node.children ? sortNodes(node.children) : undefined,
×
182
        }));
183
    };
NEW
184
    return sortNodes(data);
×
185
  }, [data]);
186

NEW
187
  return (
×
188
    <View
189
      className="bg-surface overflow-hidden"
190
      style={{
191
        borderTopLeftRadius: 12,
192
        borderTopRightRadius: 10,
193
        borderBottomRightRadius: 14,
194
        borderBottomLeftRadius: 8,
195
      }}
196
    >
197
      {sortedData.map((node) => (
NEW
198
        <FileTreeNode
×
199
          key={node.path}
200
          node={node}
201
          depth={0}
202
          onSelectFile={onSelectFile}
203
          selectedPath={selectedPath}
204
          expandedPaths={expandedPaths}
205
          toggleExpanded={toggleExpanded}
206
          showStatus={showStatus}
207
        />
208
      ))}
209
    </View>
210
  );
211
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc