@siteboon/claude-code-ui 1.8.2 → 1.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/dist/assets/index-BNGSzSdr.css +32 -0
  2. package/dist/assets/index-BZctMHnE.js +900 -0
  3. package/{index.html → dist/index.html} +4 -3
  4. package/package.json +6 -1
  5. package/server/database/auth.db +0 -0
  6. package/.env.example +0 -12
  7. package/.nvmrc +0 -1
  8. package/postcss.config.js +0 -6
  9. package/src/App.jsx +0 -751
  10. package/src/components/ChatInterface.jsx +0 -3485
  11. package/src/components/ClaudeLogo.jsx +0 -11
  12. package/src/components/ClaudeStatus.jsx +0 -107
  13. package/src/components/CodeEditor.jsx +0 -422
  14. package/src/components/CreateTaskModal.jsx +0 -88
  15. package/src/components/CursorLogo.jsx +0 -9
  16. package/src/components/DarkModeToggle.jsx +0 -35
  17. package/src/components/DiffViewer.jsx +0 -41
  18. package/src/components/ErrorBoundary.jsx +0 -73
  19. package/src/components/FileTree.jsx +0 -480
  20. package/src/components/GitPanel.jsx +0 -1283
  21. package/src/components/ImageViewer.jsx +0 -54
  22. package/src/components/LoginForm.jsx +0 -110
  23. package/src/components/MainContent.jsx +0 -577
  24. package/src/components/MicButton.jsx +0 -272
  25. package/src/components/MobileNav.jsx +0 -88
  26. package/src/components/NextTaskBanner.jsx +0 -695
  27. package/src/components/PRDEditor.jsx +0 -871
  28. package/src/components/ProtectedRoute.jsx +0 -44
  29. package/src/components/QuickSettingsPanel.jsx +0 -262
  30. package/src/components/Settings.jsx +0 -2023
  31. package/src/components/SetupForm.jsx +0 -135
  32. package/src/components/Shell.jsx +0 -663
  33. package/src/components/Sidebar.jsx +0 -1665
  34. package/src/components/StandaloneShell.jsx +0 -106
  35. package/src/components/TaskCard.jsx +0 -210
  36. package/src/components/TaskDetail.jsx +0 -406
  37. package/src/components/TaskIndicator.jsx +0 -108
  38. package/src/components/TaskList.jsx +0 -1054
  39. package/src/components/TaskMasterSetupWizard.jsx +0 -603
  40. package/src/components/TaskMasterStatus.jsx +0 -86
  41. package/src/components/TodoList.jsx +0 -91
  42. package/src/components/Tooltip.jsx +0 -91
  43. package/src/components/ui/badge.jsx +0 -31
  44. package/src/components/ui/button.jsx +0 -46
  45. package/src/components/ui/input.jsx +0 -19
  46. package/src/components/ui/scroll-area.jsx +0 -23
  47. package/src/contexts/AuthContext.jsx +0 -158
  48. package/src/contexts/TaskMasterContext.jsx +0 -324
  49. package/src/contexts/TasksSettingsContext.jsx +0 -95
  50. package/src/contexts/ThemeContext.jsx +0 -94
  51. package/src/contexts/WebSocketContext.jsx +0 -29
  52. package/src/hooks/useAudioRecorder.js +0 -109
  53. package/src/hooks/useVersionCheck.js +0 -39
  54. package/src/index.css +0 -822
  55. package/src/lib/utils.js +0 -6
  56. package/src/main.jsx +0 -10
  57. package/src/utils/api.js +0 -141
  58. package/src/utils/websocket.js +0 -109
  59. package/src/utils/whisper.js +0 -37
  60. package/tailwind.config.js +0 -63
  61. package/vite.config.js +0 -29
  62. /package/{public → dist}/convert-icons.md +0 -0
  63. /package/{public → dist}/favicon.png +0 -0
  64. /package/{public → dist}/favicon.svg +0 -0
  65. /package/{public → dist}/generate-icons.js +0 -0
  66. /package/{public → dist}/icons/claude-ai-icon.svg +0 -0
  67. /package/{public → dist}/icons/cursor.svg +0 -0
  68. /package/{public → dist}/icons/generate-icons.md +0 -0
  69. /package/{public → dist}/icons/icon-128x128.png +0 -0
  70. /package/{public → dist}/icons/icon-128x128.svg +0 -0
  71. /package/{public → dist}/icons/icon-144x144.png +0 -0
  72. /package/{public → dist}/icons/icon-144x144.svg +0 -0
  73. /package/{public → dist}/icons/icon-152x152.png +0 -0
  74. /package/{public → dist}/icons/icon-152x152.svg +0 -0
  75. /package/{public → dist}/icons/icon-192x192.png +0 -0
  76. /package/{public → dist}/icons/icon-192x192.svg +0 -0
  77. /package/{public → dist}/icons/icon-384x384.png +0 -0
  78. /package/{public → dist}/icons/icon-384x384.svg +0 -0
  79. /package/{public → dist}/icons/icon-512x512.png +0 -0
  80. /package/{public → dist}/icons/icon-512x512.svg +0 -0
  81. /package/{public → dist}/icons/icon-72x72.png +0 -0
  82. /package/{public → dist}/icons/icon-72x72.svg +0 -0
  83. /package/{public → dist}/icons/icon-96x96.png +0 -0
  84. /package/{public → dist}/icons/icon-96x96.svg +0 -0
  85. /package/{public → dist}/icons/icon-template.svg +0 -0
  86. /package/{public → dist}/logo.svg +0 -0
  87. /package/{public → dist}/manifest.json +0 -0
  88. /package/{public → dist}/screenshots/cli-selection.png +0 -0
  89. /package/{public → dist}/screenshots/desktop-main.png +0 -0
  90. /package/{public → dist}/screenshots/mobile-chat.png +0 -0
  91. /package/{public → dist}/screenshots/tools-modal.png +0 -0
  92. /package/{public → dist}/sw.js +0 -0
@@ -1,41 +0,0 @@
1
- import React from 'react';
2
-
3
- function DiffViewer({ diff, fileName, isMobile, wrapText }) {
4
- if (!diff) {
5
- return (
6
- <div className="p-4 text-center text-gray-500 dark:text-gray-400 text-sm">
7
- No diff available
8
- </div>
9
- );
10
- }
11
-
12
- const renderDiffLine = (line, index) => {
13
- const isAddition = line.startsWith('+') && !line.startsWith('+++');
14
- const isDeletion = line.startsWith('-') && !line.startsWith('---');
15
- const isHeader = line.startsWith('@@');
16
-
17
- return (
18
- <div
19
- key={index}
20
- className={`font-mono text-xs p-2 ${
21
- isMobile && wrapText ? 'whitespace-pre-wrap break-all' : 'whitespace-pre overflow-x-auto'
22
- } ${
23
- isAddition ? 'bg-green-50 dark:bg-green-950 text-green-700 dark:text-green-300' :
24
- isDeletion ? 'bg-red-50 dark:bg-red-950 text-red-700 dark:text-red-300' :
25
- isHeader ? 'bg-blue-50 dark:bg-blue-950 text-blue-700 dark:text-blue-300' :
26
- 'text-gray-600 dark:text-gray-400'
27
- }`}
28
- >
29
- {line}
30
- </div>
31
- );
32
- };
33
-
34
- return (
35
- <div className="diff-viewer">
36
- {diff.split('\n').map((line, index) => renderDiffLine(line, index))}
37
- </div>
38
- );
39
- }
40
-
41
- export default DiffViewer;
@@ -1,73 +0,0 @@
1
- import React from 'react';
2
-
3
- class ErrorBoundary extends React.Component {
4
- constructor(props) {
5
- super(props);
6
- this.state = { hasError: false, error: null, errorInfo: null };
7
- }
8
-
9
- static getDerivedStateFromError(error) {
10
- // Update state so the next render will show the fallback UI
11
- return { hasError: true };
12
- }
13
-
14
- componentDidCatch(error, errorInfo) {
15
- // Log the error details
16
- console.error('ErrorBoundary caught an error:', error, errorInfo);
17
-
18
- // You can also log the error to an error reporting service here
19
- this.setState({
20
- error: error,
21
- errorInfo: errorInfo
22
- });
23
- }
24
-
25
- render() {
26
- if (this.state.hasError) {
27
- // Fallback UI
28
- return (
29
- <div className="flex flex-col items-center justify-center p-8 text-center">
30
- <div className="bg-red-50 border border-red-200 rounded-lg p-6 max-w-md">
31
- <div className="flex items-center mb-4">
32
- <div className="flex-shrink-0">
33
- <svg className="h-5 w-5 text-red-400" viewBox="0 0 20 20" fill="currentColor">
34
- <path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clipRule="evenodd" />
35
- </svg>
36
- </div>
37
- <h3 className="ml-3 text-sm font-medium text-red-800">
38
- Something went wrong
39
- </h3>
40
- </div>
41
- <div className="text-sm text-red-700">
42
- <p className="mb-2">An error occurred while loading the chat interface.</p>
43
- {this.props.showDetails && this.state.error && (
44
- <details className="mt-4">
45
- <summary className="cursor-pointer text-xs font-mono">Error Details</summary>
46
- <pre className="mt-2 text-xs bg-red-100 p-2 rounded overflow-auto max-h-40">
47
- {this.state.error.toString()}
48
- {this.state.errorInfo && this.state.errorInfo.componentStack}
49
- </pre>
50
- </details>
51
- )}
52
- </div>
53
- <div className="mt-4">
54
- <button
55
- onClick={() => {
56
- this.setState({ hasError: false, error: null, errorInfo: null });
57
- if (this.props.onRetry) this.props.onRetry();
58
- }}
59
- className="bg-red-600 text-white px-4 py-2 rounded text-sm hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500"
60
- >
61
- Try Again
62
- </button>
63
- </div>
64
- </div>
65
- </div>
66
- );
67
- }
68
-
69
- return this.props.children;
70
- }
71
- }
72
-
73
- export default ErrorBoundary;
@@ -1,480 +0,0 @@
1
- import React, { useState, useEffect } from 'react';
2
- import { ScrollArea } from './ui/scroll-area';
3
- import { Button } from './ui/button';
4
- import { Input } from './ui/input';
5
- import { Folder, FolderOpen, File, FileText, FileCode, List, TableProperties, Eye, Search, X } from 'lucide-react';
6
- import { cn } from '../lib/utils';
7
- import CodeEditor from './CodeEditor';
8
- import ImageViewer from './ImageViewer';
9
- import { api } from '../utils/api';
10
-
11
- function FileTree({ selectedProject }) {
12
- const [files, setFiles] = useState([]);
13
- const [loading, setLoading] = useState(false);
14
- const [expandedDirs, setExpandedDirs] = useState(new Set());
15
- const [selectedFile, setSelectedFile] = useState(null);
16
- const [selectedImage, setSelectedImage] = useState(null);
17
- const [viewMode, setViewMode] = useState('detailed'); // 'simple', 'detailed', 'compact'
18
- const [searchQuery, setSearchQuery] = useState('');
19
- const [filteredFiles, setFilteredFiles] = useState([]);
20
-
21
- useEffect(() => {
22
- if (selectedProject) {
23
- fetchFiles();
24
- }
25
- }, [selectedProject]);
26
-
27
- // Load view mode preference from localStorage
28
- useEffect(() => {
29
- const savedViewMode = localStorage.getItem('file-tree-view-mode');
30
- if (savedViewMode && ['simple', 'detailed', 'compact'].includes(savedViewMode)) {
31
- setViewMode(savedViewMode);
32
- }
33
- }, []);
34
-
35
- // Filter files based on search query
36
- useEffect(() => {
37
- if (!searchQuery.trim()) {
38
- setFilteredFiles(files);
39
- } else {
40
- const filtered = filterFiles(files, searchQuery.toLowerCase());
41
- setFilteredFiles(filtered);
42
-
43
- // Auto-expand directories that contain matches
44
- const expandMatches = (items) => {
45
- items.forEach(item => {
46
- if (item.type === 'directory' && item.children && item.children.length > 0) {
47
- setExpandedDirs(prev => new Set(prev.add(item.path)));
48
- expandMatches(item.children);
49
- }
50
- });
51
- };
52
- expandMatches(filtered);
53
- }
54
- }, [files, searchQuery]);
55
-
56
- // Recursively filter files and directories based on search query
57
- const filterFiles = (items, query) => {
58
- return items.reduce((filtered, item) => {
59
- const matchesName = item.name.toLowerCase().includes(query);
60
- let filteredChildren = [];
61
-
62
- if (item.type === 'directory' && item.children) {
63
- filteredChildren = filterFiles(item.children, query);
64
- }
65
-
66
- // Include item if:
67
- // 1. It matches the search query, or
68
- // 2. It's a directory with matching children
69
- if (matchesName || filteredChildren.length > 0) {
70
- filtered.push({
71
- ...item,
72
- children: filteredChildren
73
- });
74
- }
75
-
76
- return filtered;
77
- }, []);
78
- };
79
-
80
- const fetchFiles = async () => {
81
- setLoading(true);
82
- try {
83
- const response = await api.getFiles(selectedProject.name);
84
-
85
- if (!response.ok) {
86
- const errorText = await response.text();
87
- console.error('❌ File fetch failed:', response.status, errorText);
88
- setFiles([]);
89
- return;
90
- }
91
-
92
- const data = await response.json();
93
- setFiles(data);
94
- } catch (error) {
95
- console.error('❌ Error fetching files:', error);
96
- setFiles([]);
97
- } finally {
98
- setLoading(false);
99
- }
100
- };
101
-
102
- const toggleDirectory = (path) => {
103
- const newExpanded = new Set(expandedDirs);
104
- if (newExpanded.has(path)) {
105
- newExpanded.delete(path);
106
- } else {
107
- newExpanded.add(path);
108
- }
109
- setExpandedDirs(newExpanded);
110
- };
111
-
112
- // Change view mode and save preference
113
- const changeViewMode = (mode) => {
114
- setViewMode(mode);
115
- localStorage.setItem('file-tree-view-mode', mode);
116
- };
117
-
118
- // Format file size
119
- const formatFileSize = (bytes) => {
120
- if (!bytes || bytes === 0) return '0 B';
121
- const k = 1024;
122
- const sizes = ['B', 'KB', 'MB', 'GB'];
123
- const i = Math.floor(Math.log(bytes) / Math.log(k));
124
- return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
125
- };
126
-
127
- // Format date as relative time
128
- const formatRelativeTime = (date) => {
129
- if (!date) return '-';
130
- const now = new Date();
131
- const past = new Date(date);
132
- const diffInSeconds = Math.floor((now - past) / 1000);
133
-
134
- if (diffInSeconds < 60) return 'just now';
135
- if (diffInSeconds < 3600) return `${Math.floor(diffInSeconds / 60)} min ago`;
136
- if (diffInSeconds < 86400) return `${Math.floor(diffInSeconds / 3600)} hours ago`;
137
- if (diffInSeconds < 2592000) return `${Math.floor(diffInSeconds / 86400)} days ago`;
138
- return past.toLocaleDateString();
139
- };
140
-
141
- const renderFileTree = (items, level = 0) => {
142
- return items.map((item) => (
143
- <div key={item.path} className="select-none">
144
- <Button
145
- variant="ghost"
146
- className={cn(
147
- "w-full justify-start p-2 h-auto font-normal text-left hover:bg-accent",
148
- )}
149
- style={{ paddingLeft: `${level * 16 + 12}px` }}
150
- onClick={() => {
151
- if (item.type === 'directory') {
152
- toggleDirectory(item.path);
153
- } else if (isImageFile(item.name)) {
154
- // Open image in viewer
155
- setSelectedImage({
156
- name: item.name,
157
- path: item.path,
158
- projectPath: selectedProject.path,
159
- projectName: selectedProject.name
160
- });
161
- } else {
162
- // Open file in editor
163
- setSelectedFile({
164
- name: item.name,
165
- path: item.path,
166
- projectPath: selectedProject.path,
167
- projectName: selectedProject.name
168
- });
169
- }
170
- }}
171
- >
172
- <div className="flex items-center gap-2 min-w-0 w-full">
173
- {item.type === 'directory' ? (
174
- expandedDirs.has(item.path) ? (
175
- <FolderOpen className="w-4 h-4 text-blue-500 flex-shrink-0" />
176
- ) : (
177
- <Folder className="w-4 h-4 text-muted-foreground flex-shrink-0" />
178
- )
179
- ) : (
180
- getFileIcon(item.name)
181
- )}
182
- <span className="text-sm truncate text-foreground">
183
- {item.name}
184
- </span>
185
- </div>
186
- </Button>
187
-
188
- {item.type === 'directory' &&
189
- expandedDirs.has(item.path) &&
190
- item.children &&
191
- item.children.length > 0 && (
192
- <div>
193
- {renderFileTree(item.children, level + 1)}
194
- </div>
195
- )}
196
- </div>
197
- ));
198
- };
199
-
200
- const isImageFile = (filename) => {
201
- const ext = filename.split('.').pop()?.toLowerCase();
202
- const imageExtensions = ['png', 'jpg', 'jpeg', 'gif', 'svg', 'webp', 'ico', 'bmp'];
203
- return imageExtensions.includes(ext);
204
- };
205
-
206
- const getFileIcon = (filename) => {
207
- const ext = filename.split('.').pop()?.toLowerCase();
208
-
209
- const codeExtensions = ['js', 'jsx', 'ts', 'tsx', 'py', 'java', 'cpp', 'c', 'php', 'rb', 'go', 'rs'];
210
- const docExtensions = ['md', 'txt', 'doc', 'pdf'];
211
- const imageExtensions = ['png', 'jpg', 'jpeg', 'gif', 'svg', 'webp', 'ico', 'bmp'];
212
-
213
- if (codeExtensions.includes(ext)) {
214
- return <FileCode className="w-4 h-4 text-green-500 flex-shrink-0" />;
215
- } else if (docExtensions.includes(ext)) {
216
- return <FileText className="w-4 h-4 text-blue-500 flex-shrink-0" />;
217
- } else if (imageExtensions.includes(ext)) {
218
- return <File className="w-4 h-4 text-purple-500 flex-shrink-0" />;
219
- } else {
220
- return <File className="w-4 h-4 text-muted-foreground flex-shrink-0" />;
221
- }
222
- };
223
-
224
- // Render detailed view with table-like layout
225
- const renderDetailedView = (items, level = 0) => {
226
- return items.map((item) => (
227
- <div key={item.path} className="select-none">
228
- <div
229
- className={cn(
230
- "grid grid-cols-12 gap-2 p-2 hover:bg-accent cursor-pointer items-center",
231
- )}
232
- style={{ paddingLeft: `${level * 16 + 12}px` }}
233
- onClick={() => {
234
- if (item.type === 'directory') {
235
- toggleDirectory(item.path);
236
- } else if (isImageFile(item.name)) {
237
- setSelectedImage({
238
- name: item.name,
239
- path: item.path,
240
- projectPath: selectedProject.path,
241
- projectName: selectedProject.name
242
- });
243
- } else {
244
- setSelectedFile({
245
- name: item.name,
246
- path: item.path,
247
- projectPath: selectedProject.path,
248
- projectName: selectedProject.name
249
- });
250
- }
251
- }}
252
- >
253
- <div className="col-span-5 flex items-center gap-2 min-w-0">
254
- {item.type === 'directory' ? (
255
- expandedDirs.has(item.path) ? (
256
- <FolderOpen className="w-4 h-4 text-blue-500 flex-shrink-0" />
257
- ) : (
258
- <Folder className="w-4 h-4 text-muted-foreground flex-shrink-0" />
259
- )
260
- ) : (
261
- getFileIcon(item.name)
262
- )}
263
- <span className="text-sm truncate text-foreground">
264
- {item.name}
265
- </span>
266
- </div>
267
- <div className="col-span-2 text-sm text-muted-foreground">
268
- {item.type === 'file' ? formatFileSize(item.size) : '-'}
269
- </div>
270
- <div className="col-span-3 text-sm text-muted-foreground">
271
- {formatRelativeTime(item.modified)}
272
- </div>
273
- <div className="col-span-2 text-sm text-muted-foreground font-mono">
274
- {item.permissionsRwx || '-'}
275
- </div>
276
- </div>
277
-
278
- {item.type === 'directory' &&
279
- expandedDirs.has(item.path) &&
280
- item.children &&
281
- renderDetailedView(item.children, level + 1)}
282
- </div>
283
- ));
284
- };
285
-
286
- // Render compact view with inline details
287
- const renderCompactView = (items, level = 0) => {
288
- return items.map((item) => (
289
- <div key={item.path} className="select-none">
290
- <div
291
- className={cn(
292
- "flex items-center justify-between p-2 hover:bg-accent cursor-pointer",
293
- )}
294
- style={{ paddingLeft: `${level * 16 + 12}px` }}
295
- onClick={() => {
296
- if (item.type === 'directory') {
297
- toggleDirectory(item.path);
298
- } else if (isImageFile(item.name)) {
299
- setSelectedImage({
300
- name: item.name,
301
- path: item.path,
302
- projectPath: selectedProject.path,
303
- projectName: selectedProject.name
304
- });
305
- } else {
306
- setSelectedFile({
307
- name: item.name,
308
- path: item.path,
309
- projectPath: selectedProject.path,
310
- projectName: selectedProject.name
311
- });
312
- }
313
- }}
314
- >
315
- <div className="flex items-center gap-2 min-w-0">
316
- {item.type === 'directory' ? (
317
- expandedDirs.has(item.path) ? (
318
- <FolderOpen className="w-4 h-4 text-blue-500 flex-shrink-0" />
319
- ) : (
320
- <Folder className="w-4 h-4 text-muted-foreground flex-shrink-0" />
321
- )
322
- ) : (
323
- getFileIcon(item.name)
324
- )}
325
- <span className="text-sm truncate text-foreground">
326
- {item.name}
327
- </span>
328
- </div>
329
- <div className="flex items-center gap-3 text-xs text-muted-foreground">
330
- {item.type === 'file' && (
331
- <>
332
- <span>{formatFileSize(item.size)}</span>
333
- <span className="font-mono">{item.permissionsRwx}</span>
334
- </>
335
- )}
336
- </div>
337
- </div>
338
-
339
- {item.type === 'directory' &&
340
- expandedDirs.has(item.path) &&
341
- item.children &&
342
- renderCompactView(item.children, level + 1)}
343
- </div>
344
- ));
345
- };
346
-
347
- if (loading) {
348
- return (
349
- <div className="h-full flex items-center justify-center">
350
- <div className="text-gray-500 dark:text-gray-400">
351
- Loading files...
352
- </div>
353
- </div>
354
- );
355
- }
356
-
357
- return (
358
- <div className="h-full flex flex-col bg-card">
359
- {/* Header with Search and View Mode Toggle */}
360
- <div className="p-4 border-b border-border space-y-3">
361
- <div className="flex items-center justify-between">
362
- <h3 className="text-sm font-medium text-foreground">Files</h3>
363
- <div className="flex gap-1">
364
- <Button
365
- variant={viewMode === 'simple' ? 'default' : 'ghost'}
366
- size="sm"
367
- className="h-8 w-8 p-0"
368
- onClick={() => changeViewMode('simple')}
369
- title="Simple view"
370
- >
371
- <List className="w-4 h-4" />
372
- </Button>
373
- <Button
374
- variant={viewMode === 'compact' ? 'default' : 'ghost'}
375
- size="sm"
376
- className="h-8 w-8 p-0"
377
- onClick={() => changeViewMode('compact')}
378
- title="Compact view"
379
- >
380
- <Eye className="w-4 h-4" />
381
- </Button>
382
- <Button
383
- variant={viewMode === 'detailed' ? 'default' : 'ghost'}
384
- size="sm"
385
- className="h-8 w-8 p-0"
386
- onClick={() => changeViewMode('detailed')}
387
- title="Detailed view"
388
- >
389
- <TableProperties className="w-4 h-4" />
390
- </Button>
391
- </div>
392
- </div>
393
-
394
- {/* Search Bar */}
395
- <div className="relative">
396
- <Search className="absolute left-2 top-1/2 transform -translate-y-1/2 w-4 h-4 text-muted-foreground" />
397
- <Input
398
- type="text"
399
- placeholder="Search files and folders..."
400
- value={searchQuery}
401
- onChange={(e) => setSearchQuery(e.target.value)}
402
- className="pl-8 pr-8 h-8 text-sm"
403
- />
404
- {searchQuery && (
405
- <Button
406
- variant="ghost"
407
- size="sm"
408
- className="absolute right-1 top-1/2 transform -translate-y-1/2 h-6 w-6 p-0 hover:bg-accent"
409
- onClick={() => setSearchQuery('')}
410
- title="Clear search"
411
- >
412
- <X className="w-3 h-3" />
413
- </Button>
414
- )}
415
- </div>
416
- </div>
417
-
418
- {/* Column Headers for Detailed View */}
419
- {viewMode === 'detailed' && filteredFiles.length > 0 && (
420
- <div className="px-4 pt-2 pb-1 border-b border-border">
421
- <div className="grid grid-cols-12 gap-2 px-2 text-xs font-medium text-muted-foreground">
422
- <div className="col-span-5">Name</div>
423
- <div className="col-span-2">Size</div>
424
- <div className="col-span-3">Modified</div>
425
- <div className="col-span-2">Permissions</div>
426
- </div>
427
- </div>
428
- )}
429
-
430
- <ScrollArea className="flex-1 p-4">
431
- {files.length === 0 ? (
432
- <div className="text-center py-8">
433
- <div className="w-12 h-12 bg-muted rounded-lg flex items-center justify-center mx-auto mb-3">
434
- <Folder className="w-6 h-6 text-muted-foreground" />
435
- </div>
436
- <h4 className="font-medium text-foreground mb-1">No files found</h4>
437
- <p className="text-sm text-muted-foreground">
438
- Check if the project path is accessible
439
- </p>
440
- </div>
441
- ) : filteredFiles.length === 0 && searchQuery ? (
442
- <div className="text-center py-8">
443
- <div className="w-12 h-12 bg-muted rounded-lg flex items-center justify-center mx-auto mb-3">
444
- <Search className="w-6 h-6 text-muted-foreground" />
445
- </div>
446
- <h4 className="font-medium text-foreground mb-1">No matches found</h4>
447
- <p className="text-sm text-muted-foreground">
448
- Try a different search term or clear the search
449
- </p>
450
- </div>
451
- ) : (
452
- <div className={viewMode === 'detailed' ? '' : 'space-y-1'}>
453
- {viewMode === 'simple' && renderFileTree(filteredFiles)}
454
- {viewMode === 'compact' && renderCompactView(filteredFiles)}
455
- {viewMode === 'detailed' && renderDetailedView(filteredFiles)}
456
- </div>
457
- )}
458
- </ScrollArea>
459
-
460
- {/* Code Editor Modal */}
461
- {selectedFile && (
462
- <CodeEditor
463
- file={selectedFile}
464
- onClose={() => setSelectedFile(null)}
465
- projectPath={selectedFile.projectPath}
466
- />
467
- )}
468
-
469
- {/* Image Viewer Modal */}
470
- {selectedImage && (
471
- <ImageViewer
472
- file={selectedImage}
473
- onClose={() => setSelectedImage(null)}
474
- />
475
- )}
476
- </div>
477
- );
478
- }
479
-
480
- export default FileTree;