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

agentic-dev-library / thumbcode / 21119992399

18 Jan 2026 10:51PM UTC coverage: 21.625% (-3.9%) from 25.529%
21119992399

Pull #57

github

web-flow
Merge b48a4f692 into 1c5d35a49
Pull Request #57: Implement Comprehensive Accessibility Features

181 of 1502 branches covered (12.05%)

Branch coverage included in aggregate %.

0 of 5 new or added lines in 3 files covered. (0.0%)

2 existing lines in 2 files now uncovered.

495 of 1624 relevant lines covered (30.48%)

1.0 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 {
45
  const ext = name.split('.').pop()?.toLowerCase();
×
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
  };
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) {
75
  const isExpanded = expandedPaths.has(node.path);
×
76
  const isSelected = selectedPath === node.path;
×
77
  const isFolder = node.type === 'folder';
×
78
  const hasChildren = node.children && node.children.length > 0;
×
79

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

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
  const status = node.added
×
91
    ? 'added'
92
    : node.modified
×
93
      ? 'modified'
94
      : node.deleted
×
95
        ? 'deleted'
96
        : '';
NEW
97
  const accessibilityLabel = [node.name, isFolder ? 'folder' : 'file', status]
×
98
    .filter(Boolean)
99
    .join(', ');
NEW
100
  const accessibilityHint = isFolder
×
101
    ? hasChildren
×
102
      ? isExpanded
×
103
        ? 'Collapse folder'
104
        : 'Expand folder'
105
      : 'Empty folder'
106
    : 'Open file';
107

UNCOV
108
  return (
×
109
    <View>
110
      <Pressable
111
        accessibilityRole="button"
112
        accessibilityLabel={accessibilityLabel}
113
        accessibilityHint={accessibilityHint}
114
        onPress={() => {
115
          if (isFolder && hasChildren) {
×
116
            toggleExpanded(node.path);
×
117
          } else if (!isFolder) {
×
118
            onSelectFile?.(node.path);
×
119
          }
120
        }}
121
        className={`flex-row items-center py-1.5 px-2 ${
122
          isSelected ? 'bg-teal-600/20' : 'active:bg-neutral-700'
×
123
        }`}
124
        style={{ paddingLeft: 8 + depth * 16 }}
125
      >
126
        {isFolder && hasChildren && (
×
127
          <Text className="text-xs text-neutral-500 w-4 mr-1">{isExpanded ? '▼' : '▶'}</Text>
×
128
        )}
129
        {(!isFolder || !hasChildren) && <View className="w-4 mr-1" />}
×
130

131
        <Text className="mr-2">{icon}</Text>
132

133
        <Text
134
          className={`font-mono text-sm flex-1 ${
135
            isSelected ? 'text-teal-300' : 'text-neutral-200'
×
136
          } ${statusColor}`}
137
          numberOfLines={1}
138
        >
139
          {node.name}
140
        </Text>
141

142
        {showStatus && (node.added || node.modified || node.deleted) && (
×
143
          <Text className={`text-xs ${statusColor} ml-2`}>
144
            {node.added ? 'A' : node.modified ? 'M' : 'D'}
×
145
          </Text>
146
        )}
147
      </Pressable>
148

149
      {isFolder && isExpanded && hasChildren && (
×
150
        <View>
151
          {node.children?.map((child) => (
152
            <FileTreeNode
×
153
              key={child.path}
154
              node={child}
155
              depth={depth + 1}
156
              onSelectFile={onSelectFile}
157
              selectedPath={selectedPath}
158
              expandedPaths={expandedPaths}
159
              toggleExpanded={toggleExpanded}
160
              showStatus={showStatus}
161
            />
162
          ))}
163
        </View>
164
      )}
165
    </View>
166
  );
167
}
168

169
export function FileTree({
170
  data,
171
  onSelectFile,
172
  selectedPath,
173
  defaultExpanded = [],
×
174
  showStatus = true,
×
175
}: FileTreeProps) {
176
  const [expandedPaths, setExpandedPaths] = useState<Set<string>>(new Set(defaultExpanded));
×
177

178
  const toggleExpanded = (path: string) => {
×
179
    setExpandedPaths((prev) => {
×
180
      const next = new Set(prev);
×
181
      if (next.has(path)) {
×
182
        next.delete(path);
×
183
      } else {
184
        next.add(path);
×
185
      }
186
      return next;
×
187
    });
188
  };
189

190
  // Sort nodes: folders first, then alphabetically
191
  const sortedData = useMemo(() => {
×
192
    const sortNodes = (nodes: FileNode[]): FileNode[] => {
×
193
      return [...nodes]
×
194
        .sort((a, b) => {
195
          if (a.type !== b.type) {
×
196
            return a.type === 'folder' ? -1 : 1;
×
197
          }
198
          return a.name.localeCompare(b.name);
×
199
        })
200
        .map((node) => ({
×
201
          ...node,
202
          children: node.children ? sortNodes(node.children) : undefined,
×
203
        }));
204
    };
205
    return sortNodes(data);
×
206
  }, [data]);
207

208
  return (
×
209
    <View
210
      className="bg-surface overflow-hidden"
211
      style={{
212
        borderTopLeftRadius: 12,
213
        borderTopRightRadius: 10,
214
        borderBottomRightRadius: 14,
215
        borderBottomLeftRadius: 8,
216
      }}
217
    >
218
      {sortedData.map((node) => (
219
        <FileTreeNode
×
220
          key={node.path}
221
          node={node}
222
          depth={0}
223
          onSelectFile={onSelectFile}
224
          selectedPath={selectedPath}
225
          expandedPaths={expandedPaths}
226
          toggleExpanded={toggleExpanded}
227
          showStatus={showStatus}
228
        />
229
      ))}
230
    </View>
231
  );
232
}
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