atomsystem 0.4.3 → 0.5.1

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.
@@ -1,27 +1,18 @@
1
1
  'use client';
2
2
 
3
3
  import React, { useState } from 'react';
4
+ import { Card, Button, Badge } from '@/components/ui';
4
5
  import {
5
6
  Folder,
6
7
  FolderOpen,
7
8
  FileCode,
8
9
  FileText,
9
10
  FileJson,
10
- Image as ImageIcon,
11
11
  ChevronRight,
12
12
  ChevronDown,
13
- Search,
14
- LayoutGrid,
15
- List as ListIcon,
16
- Download,
17
13
  Copy,
18
14
  Check,
19
- Plus,
20
- Trash2,
21
- HardDrive,
22
15
  File,
23
- Eye,
24
- ExternalLink,
25
16
  } from 'lucide-react';
26
17
 
27
18
  interface FileNode {
@@ -37,133 +28,68 @@ interface FileNode {
37
28
 
38
29
  const FILE_TREE: FileNode[] = [
39
30
  {
40
- id: 'f-src',
41
- name: 'src',
31
+ id: 'f-tokens',
32
+ name: 'tokens',
42
33
  type: 'folder',
43
- updatedAt: '2026.11.10',
34
+ updatedAt: '2026.11.11',
44
35
  children: [
45
36
  {
46
- id: 'f-components',
47
- name: 'components',
48
- type: 'folder',
49
- updatedAt: '2026.11.11',
50
- children: [
51
- {
52
- id: 'f-btn',
53
- name: 'Button.tsx',
54
- type: 'file',
55
- extension: 'tsx',
56
- size: '3.4 KB',
57
- updatedAt: '2026.11.10 14:20',
58
- content: `import React from 'react';\n\nexport interface ButtonProps {\n variant?: 'primary' | 'secondary';\n children: React.ReactNode;\n}\n\nexport const Button = ({ variant = 'primary', children }: ButtonProps) => {\n return <button className={\`atom-btn-\${variant}\`}>{children}</button>;\n};`,
59
- },
60
- {
61
- id: 'f-card',
62
- name: 'Card.tsx',
63
- type: 'file',
64
- extension: 'tsx',
65
- size: '2.1 KB',
66
- updatedAt: '2026.11.09 11:15',
67
- content: `export const Card = ({ title, children }) => (\n <div className="atom-card">\n <h3>{title}</h3>\n {children}\n </div>\n);`,
68
- },
69
- {
70
- id: 'f-input',
71
- name: 'Input.tsx',
72
- type: 'file',
73
- extension: 'tsx',
74
- size: '1.8 KB',
75
- updatedAt: '2026.11.08 17:40',
76
- content: `export const Input = (props) => <input className="atom-input" {...props} />;`,
77
- },
78
- ],
37
+ id: 'f-color',
38
+ name: 'color.json',
39
+ type: 'file',
40
+ extension: 'json',
41
+ size: '1.8 KB',
42
+ updatedAt: '2026.11.11 10:20',
43
+ content: `{\n "color": {\n "primary": { "value": "var(--accent-primary)" },\n "neutral": { "value": "var(--bg-primary)" }\n }\n}`,
79
44
  },
80
45
  {
81
- id: 'f-tokens',
82
- name: 'tokens',
83
- type: 'folder',
84
- updatedAt: '2026.11.11',
85
- children: [
86
- {
87
- id: 'f-tokens-json',
88
- name: 'tokens.json',
89
- type: 'file',
90
- extension: 'json',
91
- size: '18.6 KB',
92
- updatedAt: '2026.11.11 09:30',
93
- content: `{\n "color": {\n "primary": { "value": "#3182f6", "type": "color" },\n "bg": { "value": "#ffffff", "type": "color" }\n }\n}`,
94
- },
95
- {
96
- id: 'f-token-types',
97
- name: 'token.types.ts',
98
- type: 'file',
99
- extension: 'ts',
100
- size: '4.2 KB',
101
- updatedAt: '2026.11.10 16:55',
102
- content: `export type ColorToken = 'primary' | 'secondary' | 'accent';`,
103
- },
104
- ],
46
+ id: 'f-space',
47
+ name: 'spacing.json',
48
+ type: 'file',
49
+ extension: 'json',
50
+ size: '1.2 KB',
51
+ updatedAt: '2026.11.11 09:15',
52
+ content: `{\n "spacing": {\n "1": { "value": "4px" },\n "2": { "value": "8px" },\n "4": { "value": "16px" }\n }\n}`,
105
53
  },
106
54
  ],
107
55
  },
108
56
  {
109
- id: 'f-public',
110
- name: 'public',
57
+ id: 'f-components',
58
+ name: 'components',
111
59
  type: 'folder',
112
- updatedAt: '2026.11.05',
60
+ updatedAt: '2026.11.11',
113
61
  children: [
114
62
  {
115
- id: 'f-logo',
116
- name: 'atom-logo.svg',
63
+ id: 'f-btn',
64
+ name: 'Button.tsx',
117
65
  type: 'file',
118
- extension: 'svg',
119
- size: '5.2 KB',
120
- updatedAt: '2026.11.05 10:00',
121
- content: `<svg viewBox="0 0 100 100" fill="none">\n <circle cx="50" cy="50" r="40" stroke="#3182f6" strokeWidth="8"/>\n</svg>`,
66
+ extension: 'tsx',
67
+ size: '3.4 KB',
68
+ updatedAt: '2026.11.11 09:30',
69
+ content: `export function Button({ children, variant = 'primary' }) {\n return (\n <button className="px-4 py-2 rounded-(--radius) bg-(--accent-primary) text-white font-medium">\n {children}\n </button>\n );\n}`,
70
+ },
71
+ {
72
+ id: 'f-card',
73
+ name: 'Card.tsx',
74
+ type: 'file',
75
+ extension: 'tsx',
76
+ size: '4.2 KB',
77
+ updatedAt: '2026.11.10 16:20',
78
+ content: `export function Card({ title, children }) {\n return (\n <div className="p-4 rounded-(--radius) border border-(--border-subtle) bg-(--bg-primary)">\n <h3>{title}</h3>\n {children}\n </div>\n );\n}`,
122
79
  },
123
80
  ],
124
81
  },
125
- {
126
- id: 'f-readme',
127
- name: 'README.md',
128
- type: 'file',
129
- extension: 'md',
130
- size: '6.8 KB',
131
- updatedAt: '2026.11.10 18:00',
132
- content: `# AtomDesignToken\n\nFramework-Agnostic W3C DTCG Token Architecture with 0KB Runtime.`,
133
- },
134
- {
135
- id: 'f-package',
136
- name: 'package.json',
137
- type: 'file',
138
- extension: 'json',
139
- size: '2.5 KB',
140
- updatedAt: '2026.11.11 08:45',
141
- content: `{\n "name": "atomsystem",\n "version": "0.4.0",\n "type": "module"\n}`,
142
- },
143
82
  ];
144
83
 
145
84
  export function FileExplorerBlock() {
146
85
  const [openFolders, setOpenFolders] = useState<Record<string, boolean>>({
147
- 'f-src': true,
86
+ 'f-tokens': true,
148
87
  'f-components': true,
149
- 'f-tokens': false,
150
- 'f-public': false,
151
- });
152
-
153
- const [currentFolder, setCurrentFolder] = useState<FileNode>({
154
- id: 'f-components',
155
- name: 'components',
156
- type: 'folder',
157
- updatedAt: '2026.11.11',
158
- children: FILE_TREE[0].children![0].children,
159
88
  });
160
89
 
161
90
  const [selectedFile, setSelectedFile] = useState<FileNode>(
162
- FILE_TREE[0].children![0].children![0]
91
+ FILE_TREE[0].children![0]
163
92
  );
164
-
165
- const [viewMode, setViewMode] = useState<'grid' | 'table'>('grid');
166
- const [searchQuery, setSearchQuery] = useState('');
167
93
  const [copied, setCopied] = useState(false);
168
94
 
169
95
  const toggleFolder = (folderId: string) => {
@@ -174,362 +100,104 @@ export function FileExplorerBlock() {
174
100
  switch (ext) {
175
101
  case 'tsx':
176
102
  case 'ts':
177
- return <FileCode className="w-4 h-4 text-blue-500" />;
103
+ return <FileCode className="w-3.5 h-3.5 text-blue-500" />;
178
104
  case 'json':
179
- return <FileJson className="w-4 h-4 text-amber-500" />;
180
- case 'svg':
181
- return <ImageIcon className="w-4 h-4 text-emerald-500" />;
105
+ return <FileJson className="w-3.5 h-3.5 text-amber-500" />;
182
106
  case 'md':
183
- return <FileText className="w-4 h-4 text-purple-500" />;
107
+ return <FileText className="w-3.5 h-3.5 text-purple-500" />;
184
108
  default:
185
- return <File className="w-4 h-4 text-(--text-muted)" />;
109
+ return <File className="w-3.5 h-3.5 text-(--text-secondary)" />;
186
110
  }
187
111
  };
188
112
 
189
- const copyContent = (text?: string) => {
190
- if (!text) return;
191
- navigator.clipboard.writeText(text);
192
- setCopied(true);
193
- setTimeout(() => setCopied(false), 2000);
113
+ const handleCopy = () => {
114
+ if (selectedFile.content) {
115
+ navigator.clipboard.writeText(selectedFile.content);
116
+ setCopied(true);
117
+ setTimeout(() => setCopied(false), 2000);
118
+ }
194
119
  };
195
120
 
196
- const currentFiles = (currentFolder.children || []).filter((item) =>
197
- item.name.toLowerCase().includes(searchQuery.toLowerCase())
198
- );
199
-
200
121
  return (
201
- <div className="w-full bg-(--bg-primary) rounded-2xl border border-(--border-subtle) shadow-sm overflow-hidden flex flex-col h-[520px] transition-colors">
202
- {/* Top Header & Breadcrumb & Action Toolbar */}
203
- <div className="px-4 py-3 border-b border-(--border-subtle) bg-(--bg-secondary)/40 flex flex-wrap items-center justify-between gap-3">
204
- {/* Breadcrumb Path */}
205
- <div className="flex items-center gap-1.5 text-xs font-medium text-(--text-secondary)">
206
- <HardDrive className="w-4 h-4 text-(--accent-primary)" />
207
- <span>project-root</span>
208
- <ChevronRight className="w-3.5 h-3.5 text-(--text-muted)" />
209
- <span>src</span>
210
- <ChevronRight className="w-3.5 h-3.5 text-(--text-muted)" />
211
- <span className="font-bold text-(--text-primary) px-1.5 py-0.5 rounded bg-(--bg-secondary)">
212
- {currentFolder.name}
213
- </span>
122
+ <Card className="flex flex-col md:flex-row h-[380px] overflow-hidden transition-colors">
123
+ {/* 1. Left Tree Nav */}
124
+ <aside className="w-full md:w-56 border-r border-(--border-subtle) bg-(--bg-secondary)/20 p-3 overflow-y-auto shrink-0 space-y-1 text-xs">
125
+ <div className="text-[11px] font-mono text-(--text-secondary) uppercase tracking-wider px-2 py-1 mb-1">
126
+ Project Files
214
127
  </div>
128
+ {FILE_TREE.map((node) => {
129
+ const isOpen = openFolders[node.id];
130
+ return (
131
+ <div key={node.id} className="space-y-0.5">
132
+ <button
133
+ onClick={() => toggleFolder(node.id)}
134
+ className="w-full flex items-center gap-1.5 px-2 py-1 rounded-(--radius-sm) text-(--text-secondary) hover:text-(--text-primary) hover:bg-(--bg-secondary) text-left transition-colors"
135
+ >
136
+ {isOpen ? (
137
+ <ChevronDown className="w-3 h-3 text-(--text-secondary)" />
138
+ ) : (
139
+ <ChevronRight className="w-3 h-3 text-(--text-secondary)" />
140
+ )}
141
+ {isOpen ? (
142
+ <FolderOpen className="w-3.5 h-3.5 text-(--accent-primary)" />
143
+ ) : (
144
+ <Folder className="w-3.5 h-3.5 text-(--text-secondary)" />
145
+ )}
146
+ <span className="font-medium truncate">{node.name}</span>
147
+ </button>
215
148
 
216
- {/* Search, View Mode & New File */}
217
- <div className="flex items-center gap-2">
218
- <div className="relative">
219
- <Search className="w-3.5 h-3.5 absolute left-2.5 top-1/2 -translate-y-1/2 text-(--text-muted)" />
220
- <input
221
- type="text"
222
- value={searchQuery}
223
- onChange={(e) => setSearchQuery(e.target.value)}
224
- placeholder="파일 검색..."
225
- className="text-xs pl-8 pr-3 py-1 rounded-lg bg-(--bg-primary) text-(--text-primary) border border-(--border-subtle) focus:outline-none focus:border-(--accent-primary) w-32 sm:w-44 transition-colors"
226
- />
227
- </div>
228
-
229
- <div className="flex items-center p-0.5 rounded-lg bg-(--bg-primary) border border-(--border-subtle)">
230
- <button
231
- onClick={() => setViewMode('grid')}
232
- aria-label="그리드 뷰"
233
- className={`p-1 rounded ${
234
- viewMode === 'grid'
235
- ? 'bg-(--bg-secondary) text-(--text-primary)'
236
- : 'text-(--text-muted) hover:text-(--text-primary)'
237
- }`}
238
- >
239
- <LayoutGrid className="w-3.5 h-3.5" />
240
- </button>
241
- <button
242
- onClick={() => setViewMode('table')}
243
- aria-label="테이블 뷰"
244
- className={`p-1 rounded ${
245
- viewMode === 'table'
246
- ? 'bg-(--bg-secondary) text-(--text-primary)'
247
- : 'text-(--text-muted) hover:text-(--text-primary)'
248
- }`}
249
- >
250
- <ListIcon className="w-3.5 h-3.5" />
251
- </button>
252
- </div>
253
-
254
- <button className="hidden sm:flex items-center gap-1 px-2.5 py-1 rounded-lg text-xs font-semibold bg-(--accent-primary) text-white shadow-xs hover:bg-(--accent-hover) transition-colors">
255
- <Plus className="w-3 h-3" />
256
- <span>추가</span>
257
- </button>
258
- </div>
259
- </div>
260
-
261
- {/* Main 3-Column Body: Tree Sidebar | File List Viewport | File Inspector */}
262
- <div className="flex-1 flex overflow-hidden">
263
- {/* 1. Left Tree Sidebar */}
264
- <aside className="w-48 sm:w-56 border-r border-(--border-subtle) bg-(--bg-secondary)/20 p-3 overflow-y-auto shrink-0 select-none">
265
- <div className="text-[10px] font-bold text-(--text-muted) uppercase tracking-wider mb-2 px-1">
266
- EXPLORER TREE
267
- </div>
268
-
269
- <div className="space-y-0.5 text-xs">
270
- {FILE_TREE.map((node) => {
271
- if (node.type === 'folder') {
272
- const isOpen = openFolders[node.id];
273
- return (
274
- <div key={node.id}>
275
- <button
276
- onClick={() => toggleFolder(node.id)}
277
- className="w-full flex items-center gap-1.5 px-2 py-1.5 rounded-lg text-(--text-secondary) hover:text-(--text-primary) hover:bg-(--bg-secondary) transition-colors text-left"
278
- >
279
- {isOpen ? (
280
- <ChevronDown className="w-3.5 h-3.5 text-(--text-muted)" />
281
- ) : (
282
- <ChevronRight className="w-3.5 h-3.5 text-(--text-muted)" />
283
- )}
284
- {isOpen ? (
285
- <FolderOpen className="w-4 h-4 text-amber-500 fill-amber-500/20" />
286
- ) : (
287
- <Folder className="w-4 h-4 text-amber-500 fill-amber-500/20" />
288
- )}
289
- <span className="font-semibold truncate">{node.name}</span>
290
- </button>
291
-
292
- {/* Subfolders & Subfiles */}
293
- {isOpen && node.children && (
294
- <div className="pl-4 space-y-0.5 border-l border-(--border-subtle) ml-3.5 my-0.5">
295
- {node.children.map((sub) => {
296
- if (sub.type === 'folder') {
297
- const isSubOpen = openFolders[sub.id];
298
- const isCurrent = currentFolder.id === sub.id;
299
- return (
300
- <button
301
- key={sub.id}
302
- onClick={() => {
303
- toggleFolder(sub.id);
304
- setCurrentFolder(sub);
305
- }}
306
- className={`w-full flex items-center gap-1.5 px-2 py-1 rounded-md text-left transition-colors ${
307
- isCurrent
308
- ? 'bg-(--accent-primary)/10 text-(--accent-primary) font-bold'
309
- : 'text-(--text-secondary) hover:text-(--text-primary) hover:bg-(--bg-secondary)'
310
- }`}
311
- >
312
- {isSubOpen ? (
313
- <FolderOpen className="w-3.5 h-3.5 text-amber-500" />
314
- ) : (
315
- <Folder className="w-3.5 h-3.5 text-amber-500" />
316
- )}
317
- <span className="truncate">{sub.name}</span>
318
- </button>
319
- );
320
- } else {
321
- const isSelected = selectedFile?.id === sub.id;
322
- return (
323
- <button
324
- key={sub.id}
325
- onClick={() => setSelectedFile(sub)}
326
- className={`w-full flex items-center gap-1.5 px-2 py-1 rounded-md text-left transition-colors ${
327
- isSelected
328
- ? 'bg-(--accent-primary)/10 text-(--accent-primary) font-semibold'
329
- : 'text-(--text-secondary) hover:text-(--text-primary) hover:bg-(--bg-secondary)'
330
- }`}
331
- >
332
- {getFileIcon(sub.extension)}
333
- <span className="truncate">{sub.name}</span>
334
- </button>
335
- );
336
- }
337
- })}
338
- </div>
339
- )}
340
- </div>
341
- );
342
- } else {
343
- return (
344
- <button
345
- key={node.id}
346
- onClick={() => setSelectedFile(node)}
347
- className="w-full flex items-center gap-1.5 px-2 py-1.5 rounded-lg text-(--text-secondary) hover:text-(--text-primary) hover:bg-(--bg-secondary) text-left"
348
- >
349
- {getFileIcon(node.extension)}
350
- <span className="truncate">{node.name}</span>
351
- </button>
352
- );
353
- }
354
- })}
355
- </div>
356
- </aside>
357
-
358
- {/* 2. Middle Files Viewport */}
359
- <main className="flex-1 p-4 overflow-y-auto bg-(--bg-primary)">
360
- <div className="flex items-center justify-between text-xs text-(--text-muted) mb-3 pb-2 border-b border-(--border-subtle)">
361
- <span>{currentFiles.length}개 파일 항목</span>
362
- <span>최종 갱신: {currentFolder.updatedAt}</span>
363
- </div>
364
-
365
- {currentFiles.length === 0 ? (
366
- <div className="py-16 text-center text-xs text-(--text-muted)">
367
- 검색된 파일이 없습니다.
368
- </div>
369
- ) : viewMode === 'grid' ? (
370
- /* Grid Mode */
371
- <div className="grid grid-cols-2 sm:grid-cols-3 gap-3">
372
- {currentFiles.map((file) => {
373
- const isSelected = selectedFile?.id === file.id;
374
- return (
375
- <div
376
- key={file.id}
377
- onClick={() => setSelectedFile(file)}
378
- className={`group p-3.5 rounded-xl border transition-all cursor-pointer flex flex-col justify-between ${
379
- isSelected
380
- ? 'border-(--accent-primary) bg-(--accent-primary)/5 shadow-xs ring-2 ring-(--accent-primary)/10'
381
- : 'border-(--border-subtle) hover:border-(--border-strong) bg-(--bg-secondary)/30 hover:bg-(--bg-secondary)'
382
- }`}
383
- >
384
- <div className="flex items-center justify-between mb-3">
385
- <div className="p-2 rounded-lg bg-(--bg-primary) border border-(--border-subtle)">
386
- {getFileIcon(file.extension)}
387
- </div>
388
- <span className="text-[10px] font-mono text-(--text-muted) uppercase">
389
- {file.extension}
390
- </span>
391
- </div>
392
- <div>
393
- <h4 className="text-xs font-bold text-(--text-primary) truncate mb-1">
394
- {file.name}
395
- </h4>
396
- <div className="flex items-center justify-between text-[10px] text-(--text-muted)">
397
- <span>{file.size}</span>
398
- <span>{file.updatedAt.split(' ')[0]}</span>
399
- </div>
400
- </div>
401
- </div>
402
- );
403
- })}
404
- </div>
405
- ) : (
406
- /* Table Mode */
407
- <div className="border border-(--border-subtle) rounded-xl overflow-hidden text-xs">
408
- <table className="w-full text-left border-collapse">
409
- <thead className="bg-(--bg-secondary)/60 text-(--text-muted) text-[11px] font-semibold border-b border-(--border-subtle)">
410
- <tr>
411
- <th className="p-2.5 pl-3">이름</th>
412
- <th className="p-2.5">유형</th>
413
- <th className="p-2.5">크기</th>
414
- <th className="p-2.5">수정 일시</th>
415
- </tr>
416
- </thead>
417
- <tbody className="divide-y divide-(--border-subtle)">
418
- {currentFiles.map((file) => {
149
+ {isOpen && node.children && (
150
+ <div className="pl-4 space-y-0.5 border-l border-(--border-subtle) ml-3">
151
+ {node.children.map((file) => {
419
152
  const isSelected = selectedFile?.id === file.id;
420
153
  return (
421
- <tr
154
+ <button
422
155
  key={file.id}
423
156
  onClick={() => setSelectedFile(file)}
424
- className={`cursor-pointer transition-colors ${
157
+ className={`w-full flex items-center gap-1.5 px-2 py-1 rounded-(--radius-sm) text-left transition-colors ${
425
158
  isSelected
426
- ? 'bg-(--accent-primary)/10 font-semibold text-(--accent-primary)'
427
- : 'hover:bg-(--bg-secondary)/50 text-(--text-primary)'
159
+ ? 'bg-(--accent-primary)/10 text-(--accent-primary) font-semibold'
160
+ : 'text-(--text-secondary) hover:text-(--text-primary) hover:bg-(--bg-secondary)'
428
161
  }`}
429
162
  >
430
- <td className="p-2.5 pl-3 flex items-center gap-2">
431
- {getFileIcon(file.extension)}
432
- <span>{file.name}</span>
433
- </td>
434
- <td className="p-2.5 text-(--text-muted) uppercase font-mono text-[10px]">
435
- {file.extension}
436
- </td>
437
- <td className="p-2.5 text-(--text-secondary)">{file.size}</td>
438
- <td className="p-2.5 text-(--text-muted)">{file.updatedAt}</td>
439
- </tr>
163
+ {getFileIcon(file.extension)}
164
+ <span className="truncate">{file.name}</span>
165
+ </button>
440
166
  );
441
167
  })}
442
- </tbody>
443
- </table>
444
- </div>
445
- )}
446
- </main>
447
-
448
- {/* 3. Right File Preview & Inspector Panel */}
449
- <aside className="w-60 sm:w-72 border-l border-(--border-subtle) bg-(--bg-secondary)/30 p-4 flex flex-col justify-between overflow-y-auto shrink-0">
450
- <div>
451
- <div className="flex items-center justify-between mb-3 pb-2 border-b border-(--border-subtle)">
452
- <span className="text-[11px] font-bold text-(--text-muted) uppercase tracking-wider flex items-center gap-1">
453
- <Eye className="w-3.5 h-3.5" />
454
- 파일 인스펙터
455
- </span>
456
- <button
457
- onClick={() => copyContent(selectedFile?.content)}
458
- className="text-[10px] text-(--accent-primary) font-semibold flex items-center gap-1 hover:underline"
459
- >
460
- {copied ? <Check className="w-3 h-3 text-emerald-500" /> : <Copy className="w-3 h-3" />}
461
- <span>{copied ? '복사됨' : '내용 복사'}</span>
462
- </button>
463
- </div>
464
-
465
- {selectedFile ? (
466
- <div className="space-y-3">
467
- <div className="flex items-center gap-2.5 p-2 rounded-xl bg-(--bg-primary) border border-(--border-subtle)">
468
- <div className="p-2 rounded-lg bg-(--bg-secondary)">
469
- {getFileIcon(selectedFile.extension)}
470
- </div>
471
- <div className="truncate">
472
- <h5 className="text-xs font-bold text-(--text-primary) truncate">
473
- {selectedFile.name}
474
- </h5>
475
- <span className="text-[10px] text-(--text-muted)">{selectedFile.size}</span>
476
- </div>
477
- </div>
478
-
479
- {/* Details Meta */}
480
- <div className="text-[11px] space-y-1.5 p-2.5 rounded-lg bg-(--bg-primary)/60 border border-(--border-subtle)">
481
- <div className="flex justify-between text-(--text-secondary)">
482
- <span>확장자</span>
483
- <span className="font-mono uppercase font-bold text-(--text-primary)">
484
- {selectedFile.extension}
485
- </span>
486
- </div>
487
- <div className="flex justify-between text-(--text-secondary)">
488
- <span>수정 일시</span>
489
- <span className="text-(--text-primary)">{selectedFile.updatedAt}</span>
490
- </div>
491
- <div className="flex justify-between text-(--text-secondary)">
492
- <span>보안 검증</span>
493
- <span className="text-emerald-500 font-semibold">100% Verified</span>
494
- </div>
495
168
  </div>
496
-
497
- {/* File Preview Snippet */}
498
- <div>
499
- <span className="text-[10px] font-bold text-(--text-muted) mb-1 block">
500
- 코드/데이터 미리보기
501
- </span>
502
- <div className="rounded-lg bg-(--bg-primary) border border-(--border-subtle) p-2 text-[10px] font-mono text-(--text-secondary) overflow-x-auto max-h-36 leading-relaxed">
503
- <pre>
504
- <code>{selectedFile.content || '// 파일 내용이 없습니다.'}</code>
505
- </pre>
506
- </div>
507
- </div>
508
- </div>
509
- ) : (
510
- <div className="py-10 text-center text-xs text-(--text-muted)">
511
- 선택된 파일이 없습니다.
512
- </div>
513
- )}
169
+ )}
170
+ </div>
171
+ );
172
+ })}
173
+ </aside>
174
+
175
+ {/* 2. File Preview & Code Area composed with Button & Card */}
176
+ <main className="flex-1 flex flex-col bg-(--bg-primary) overflow-hidden">
177
+ <div className="px-4 py-2.5 border-b border-(--border-subtle) bg-(--bg-secondary)/30 flex items-center justify-between text-xs">
178
+ <div className="flex items-center gap-2">
179
+ {getFileIcon(selectedFile.extension)}
180
+ <span className="font-mono font-medium text-(--text-primary)">{selectedFile.name}</span>
181
+ <Badge variant="neutral" className="text-xs">
182
+ {selectedFile.size}
183
+ </Badge>
514
184
  </div>
515
185
 
516
- <div className="pt-3 border-t border-(--border-subtle) flex gap-2">
517
- <button
518
- onClick={() => copyContent(selectedFile?.content)}
519
- className="flex-1 py-1.5 rounded-lg bg-(--accent-primary) hover:bg-(--accent-hover) text-white font-semibold text-xs flex items-center justify-center gap-1.5 shadow-xs transition-colors"
520
- >
521
- <Download className="w-3.5 h-3.5" />
522
- <span>다운로드</span>
523
- </button>
524
- <button
525
- className="p-1.5 rounded-lg bg-(--bg-primary) hover:bg-rose-500/10 hover:text-rose-500 text-(--text-muted) border border-(--border-subtle) transition-colors"
526
- title="삭제"
527
- >
528
- <Trash2 className="w-3.5 h-3.5" />
529
- </button>
530
- </div>
531
- </aside>
532
- </div>
533
- </div>
186
+ <Button
187
+ variant="outline"
188
+ size="sm"
189
+ onClick={handleCopy}
190
+ className="text-xs py-1 px-2.5 flex items-center gap-1"
191
+ >
192
+ {copied ? <Check className="w-3 h-3 text-emerald-600" /> : <Copy className="w-3 h-3" />}
193
+ <span>{copied ? '복사됨' : '코드 복사'}</span>
194
+ </Button>
195
+ </div>
196
+
197
+ <div className="flex-1 p-4 overflow-auto font-mono text-xs leading-relaxed bg-(--bg-secondary)/10">
198
+ <pre className="text-(--text-primary)">{selectedFile.content}</pre>
199
+ </div>
200
+ </main>
201
+ </Card>
534
202
  );
535
203
  }