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

agentic-dev-library / thumbcode / 21120323096

18 Jan 2026 11:16PM UTC coverage: 25.507% (-0.02%) from 25.529%
21120323096

Pull #57

github

web-flow
Merge cc03ec2b2 into 79c7ce934
Pull Request #57: Implement Comprehensive Accessibility Features

260 of 1595 branches covered (16.3%)

Branch coverage included in aggregate %.

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

2 existing lines in 2 files now uncovered.

621 of 1859 relevant lines covered (33.41%)

1.48 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 getStatusColor(node: FileNode): string {
NEW
67
  if (node.added) return 'text-teal-400';
×
NEW
68
  if (node.modified) return 'text-gold-400';
×
NEW
69
  if (node.deleted) return 'text-coral-400';
×
NEW
70
  return '';
×
71
}
72

73
function getStatusText(node: FileNode): string {
NEW
74
  if (node.added) return 'added';
×
NEW
75
  if (node.modified) return 'modified';
×
NEW
76
  if (node.deleted) return 'deleted';
×
NEW
77
  return '';
×
78
}
79

80
function getStatusLabel(node: FileNode): string {
NEW
81
  if (node.added) return 'A';
×
NEW
82
  if (node.modified) return 'M';
×
NEW
83
  if (node.deleted) return 'D';
×
NEW
84
  return '';
×
85
}
86

87
function getAccessibilityHint(
88
  isFolder: boolean,
89
  hasChildren: boolean,
90
  isExpanded: boolean
91
): string {
NEW
92
  if (!isFolder) return 'Open file';
×
NEW
93
  if (!hasChildren) return 'Empty folder';
×
NEW
94
  return isExpanded ? 'Collapse folder' : 'Expand folder';
×
95
}
96

97
function FileTreeNode({
98
  node,
99
  depth,
100
  onSelectFile,
101
  selectedPath,
102
  expandedPaths,
103
  toggleExpanded,
104
  showStatus,
105
}: FileTreeNodeProps) {
106
  const isExpanded = expandedPaths.has(node.path);
×
107
  const isSelected = selectedPath === node.path;
×
108
  const isFolder = node.type === 'folder';
×
NEW
109
  const hasChildren = Boolean(node.children?.length);
×
NEW
110
  const statusColor = getStatusColor(node);
×
UNCOV
111
  const icon = isFolder ? (isExpanded ? '📂' : '📁') : getFileIcon(node.name);
×
112

NEW
113
  const accessibilityLabel = [node.name, isFolder ? 'folder' : 'file', getStatusText(node)]
×
114
    .filter(Boolean)
115
    .join(', ');
116

NEW
117
  const handlePress = () => {
×
NEW
118
    if (isFolder && hasChildren) {
×
NEW
119
      toggleExpanded(node.path);
×
NEW
120
    } else if (!isFolder) {
×
NEW
121
      onSelectFile?.(node.path);
×
122
    }
123
  };
124

NEW
125
  const hasStatus = Boolean(node.added || node.modified || node.deleted);
×
126

127
  return (
×
128
    <View>
129
      <Pressable
130
        accessibilityRole="button"
131
        accessibilityLabel={accessibilityLabel}
132
        accessibilityHint={getAccessibilityHint(isFolder, hasChildren, isExpanded)}
133
        onPress={handlePress}
134
        className={`flex-row items-center py-1.5 px-2 ${isSelected ? 'bg-teal-600/20' : 'active:bg-neutral-700'}`}
×
135
        style={{ paddingLeft: 8 + depth * 16 }}
136
      >
137
        {isFolder && hasChildren ? (
×
138
          <Text className="text-xs text-neutral-500 w-4 mr-1">{isExpanded ? '▼' : '▶'}</Text>
×
139
        ) : (
140
          <View className="w-4 mr-1" />
141
        )}
142

143
        <Text className="mr-2">{icon}</Text>
144

145
        <Text
146
          className={`font-mono text-sm flex-1 ${isSelected ? 'text-teal-300' : 'text-neutral-200'} ${statusColor}`}
×
147
          numberOfLines={1}
148
        >
149
          {node.name}
150
        </Text>
151

152
        {showStatus && hasStatus && (
×
153
          <Text className={`text-xs ${statusColor} ml-2`}>{getStatusLabel(node)}</Text>
154
        )}
155
      </Pressable>
156

157
      {isFolder && isExpanded && hasChildren && (
×
158
        <View>
159
          {node.children?.map((child) => (
160
            <FileTreeNode
×
161
              key={child.path}
162
              node={child}
163
              depth={depth + 1}
164
              onSelectFile={onSelectFile}
165
              selectedPath={selectedPath}
166
              expandedPaths={expandedPaths}
167
              toggleExpanded={toggleExpanded}
168
              showStatus={showStatus}
169
            />
170
          ))}
171
        </View>
172
      )}
173
    </View>
174
  );
175
}
176

177
export function FileTree({
178
  data,
179
  onSelectFile,
180
  selectedPath,
181
  defaultExpanded = [],
×
182
  showStatus = true,
×
183
}: FileTreeProps) {
184
  const [expandedPaths, setExpandedPaths] = useState<Set<string>>(new Set(defaultExpanded));
×
185

186
  const toggleExpanded = (path: string) => {
×
187
    setExpandedPaths((prev) => {
×
188
      const next = new Set(prev);
×
189
      if (next.has(path)) {
×
190
        next.delete(path);
×
191
      } else {
192
        next.add(path);
×
193
      }
194
      return next;
×
195
    });
196
  };
197

198
  // Sort nodes: folders first, then alphabetically
199
  const sortedData = useMemo(() => {
×
200
    const sortNodes = (nodes: FileNode[]): FileNode[] => {
×
201
      return [...nodes]
×
202
        .sort((a, b) => {
203
          if (a.type !== b.type) {
×
204
            return a.type === 'folder' ? -1 : 1;
×
205
          }
206
          return a.name.localeCompare(b.name);
×
207
        })
208
        .map((node) => ({
×
209
          ...node,
210
          children: node.children ? sortNodes(node.children) : undefined,
×
211
        }));
212
    };
213
    return sortNodes(data);
×
214
  }, [data]);
215

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