@huyooo/file-explorer-frontend-react 0.4.2

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 (46) hide show
  1. package/dist/index.css +1740 -0
  2. package/dist/index.css.map +1 -0
  3. package/dist/index.d.ts +562 -0
  4. package/dist/index.js +3453 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/style.css +3 -0
  7. package/package.json +58 -0
  8. package/src/components/Breadcrumb.css +61 -0
  9. package/src/components/Breadcrumb.tsx +38 -0
  10. package/src/components/CompressDialog.css +264 -0
  11. package/src/components/CompressDialog.tsx +222 -0
  12. package/src/components/ContextMenu.css +155 -0
  13. package/src/components/ContextMenu.tsx +375 -0
  14. package/src/components/FileGrid.css +267 -0
  15. package/src/components/FileGrid.tsx +277 -0
  16. package/src/components/FileIcon.css +41 -0
  17. package/src/components/FileIcon.tsx +86 -0
  18. package/src/components/FileInfoDialog.css +252 -0
  19. package/src/components/FileInfoDialog.tsx +202 -0
  20. package/src/components/FileList.css +226 -0
  21. package/src/components/FileList.tsx +228 -0
  22. package/src/components/FileListView.css +36 -0
  23. package/src/components/FileListView.tsx +355 -0
  24. package/src/components/FileSidebar.css +94 -0
  25. package/src/components/FileSidebar.tsx +66 -0
  26. package/src/components/ProgressDialog.css +211 -0
  27. package/src/components/ProgressDialog.tsx +183 -0
  28. package/src/components/SortIndicator.css +7 -0
  29. package/src/components/SortIndicator.tsx +19 -0
  30. package/src/components/StatusBar.css +20 -0
  31. package/src/components/StatusBar.tsx +21 -0
  32. package/src/components/Toolbar.css +150 -0
  33. package/src/components/Toolbar.tsx +127 -0
  34. package/src/components/Window.css +246 -0
  35. package/src/components/Window.tsx +335 -0
  36. package/src/hooks/useApplicationIcon.ts +80 -0
  37. package/src/hooks/useDragAndDrop.ts +104 -0
  38. package/src/hooks/useMediaPlayer.ts +164 -0
  39. package/src/hooks/useSelection.ts +112 -0
  40. package/src/hooks/useWindowDrag.ts +60 -0
  41. package/src/hooks/useWindowResize.ts +126 -0
  42. package/src/index.css +3 -0
  43. package/src/index.ts +34 -0
  44. package/src/types/index.ts +274 -0
  45. package/src/utils/fileTypeIcon.ts +309 -0
  46. package/src/utils/folderTypeIcon.ts +132 -0
@@ -0,0 +1,252 @@
1
+ /* FileInfoDialog 样式 */
2
+
3
+ .file-info-dialog-overlay {
4
+ position: fixed;
5
+ inset: 0;
6
+ background: rgba(0, 0, 0, 0.4);
7
+ display: flex;
8
+ align-items: center;
9
+ justify-content: center;
10
+ z-index: 10000;
11
+ animation: file-info-fadeIn 0.15s ease-out;
12
+ }
13
+
14
+ @keyframes file-info-fadeIn {
15
+ from { opacity: 0; }
16
+ to { opacity: 1; }
17
+ }
18
+
19
+ .file-info-dialog {
20
+ background: #ffffff;
21
+ border: 1px solid #e0e0e0;
22
+ border-radius: 12px;
23
+ width: 420px;
24
+ max-width: 90vw;
25
+ max-height: 80vh;
26
+ display: flex;
27
+ flex-direction: column;
28
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15), 0 2px 8px rgba(0, 0, 0, 0.1);
29
+ animation: file-info-slideIn 0.2s ease-out;
30
+ }
31
+
32
+ @keyframes file-info-slideIn {
33
+ from {
34
+ opacity: 0;
35
+ transform: scale(0.95) translateY(-10px);
36
+ }
37
+ to {
38
+ opacity: 1;
39
+ transform: scale(1) translateY(0);
40
+ }
41
+ }
42
+
43
+ /* 深色模式 */
44
+ @media (prefers-color-scheme: dark) {
45
+ .file-info-dialog {
46
+ background: #2d2d2d;
47
+ border-color: #404040;
48
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
49
+ }
50
+ }
51
+
52
+ /* 头部 */
53
+ .file-info-dialog-header {
54
+ display: flex;
55
+ align-items: center;
56
+ justify-content: space-between;
57
+ padding: 16px 20px;
58
+ border-bottom: 1px solid #e0e0e0;
59
+ gap: 12px;
60
+ }
61
+
62
+ @media (prefers-color-scheme: dark) {
63
+ .file-info-dialog-header {
64
+ border-bottom-color: #404040;
65
+ }
66
+ }
67
+
68
+ .file-info-dialog-title {
69
+ display: flex;
70
+ align-items: center;
71
+ gap: 10px;
72
+ min-width: 0;
73
+ flex: 1;
74
+ }
75
+
76
+ .file-info-dialog-name {
77
+ font-size: 16px;
78
+ font-weight: 600;
79
+ color: #1a1a1a;
80
+ white-space: nowrap;
81
+ overflow: hidden;
82
+ text-overflow: ellipsis;
83
+ }
84
+
85
+ @media (prefers-color-scheme: dark) {
86
+ .file-info-dialog-name {
87
+ color: #e0e0e0;
88
+ }
89
+ }
90
+
91
+ .file-info-dialog-close {
92
+ display: flex;
93
+ align-items: center;
94
+ justify-content: center;
95
+ width: 28px;
96
+ height: 28px;
97
+ border: none;
98
+ background: transparent;
99
+ color: #666;
100
+ border-radius: 6px;
101
+ cursor: pointer;
102
+ flex-shrink: 0;
103
+ transition: all 0.15s ease;
104
+ }
105
+
106
+ .file-info-dialog-close:hover {
107
+ background: #f0f0f0;
108
+ color: #333;
109
+ }
110
+
111
+ @media (prefers-color-scheme: dark) {
112
+ .file-info-dialog-close {
113
+ color: #888;
114
+ }
115
+ .file-info-dialog-close:hover {
116
+ background: #404040;
117
+ color: #e0e0e0;
118
+ }
119
+ }
120
+
121
+ /* 内容 */
122
+ .file-info-dialog-content {
123
+ padding: 16px 20px;
124
+ display: flex;
125
+ flex-direction: column;
126
+ gap: 12px;
127
+ overflow-y: auto;
128
+ }
129
+
130
+ .file-info-row {
131
+ display: flex;
132
+ align-items: flex-start;
133
+ gap: 16px;
134
+ }
135
+
136
+ .file-info-label {
137
+ display: flex;
138
+ align-items: center;
139
+ gap: 6px;
140
+ width: 90px;
141
+ flex-shrink: 0;
142
+ color: #666;
143
+ font-size: 13px;
144
+ }
145
+
146
+ @media (prefers-color-scheme: dark) {
147
+ .file-info-label {
148
+ color: #888;
149
+ }
150
+ }
151
+
152
+ .file-info-label svg {
153
+ flex-shrink: 0;
154
+ }
155
+
156
+ .file-info-value {
157
+ flex: 1;
158
+ color: #1a1a1a;
159
+ font-size: 13px;
160
+ word-break: break-all;
161
+ line-height: 1.4;
162
+ }
163
+
164
+ @media (prefers-color-scheme: dark) {
165
+ .file-info-value {
166
+ color: #e0e0e0;
167
+ }
168
+ }
169
+
170
+ .file-info-value--path {
171
+ font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', monospace;
172
+ font-size: 12px;
173
+ background: #f5f5f5;
174
+ padding: 4px 8px;
175
+ border-radius: 4px;
176
+ user-select: all;
177
+ }
178
+
179
+ @media (prefers-color-scheme: dark) {
180
+ .file-info-value--path {
181
+ background: #383838;
182
+ }
183
+ }
184
+
185
+ /* 图标样式 */
186
+ .file-info-icon {
187
+ width: 32px;
188
+ height: 32px;
189
+ flex-shrink: 0;
190
+ }
191
+
192
+ .file-info-icon--folder {
193
+ color: #dcb67a;
194
+ }
195
+
196
+ .file-info-icon--image {
197
+ color: #7ec699;
198
+ }
199
+
200
+ .file-info-icon--video {
201
+ color: #c678dd;
202
+ }
203
+
204
+ .file-info-icon--music {
205
+ color: #e06c75;
206
+ }
207
+
208
+ .file-info-icon--document {
209
+ color: #61afef;
210
+ }
211
+
212
+ .file-info-icon--code {
213
+ color: #98c379;
214
+ }
215
+
216
+ .file-info-icon--archive {
217
+ color: #d19a66;
218
+ }
219
+
220
+ .file-info-icon--file {
221
+ color: #abb2bf;
222
+ }
223
+
224
+ /* 底部 */
225
+ .file-info-dialog-footer {
226
+ display: flex;
227
+ justify-content: flex-end;
228
+ padding: 12px 20px;
229
+ border-top: 1px solid #e0e0e0;
230
+ }
231
+
232
+ @media (prefers-color-scheme: dark) {
233
+ .file-info-dialog-footer {
234
+ border-top-color: #404040;
235
+ }
236
+ }
237
+
238
+ .file-info-dialog-btn {
239
+ padding: 6px 16px;
240
+ border: none;
241
+ border-radius: 6px;
242
+ font-size: 13px;
243
+ cursor: pointer;
244
+ transition: all 0.15s ease;
245
+ background: #007aff;
246
+ color: white;
247
+ }
248
+
249
+ .file-info-dialog-btn:hover {
250
+ background: #0066d6;
251
+ }
252
+
@@ -0,0 +1,202 @@
1
+ /**
2
+ * FileInfoDialog - 文件信息弹窗组件
3
+ *
4
+ * 显示文件/文件夹的详细信息
5
+ */
6
+
7
+ import React from 'react';
8
+ import { Icon } from '@iconify/react';
9
+ import { FileItem, FileType } from '../types';
10
+ import { getFileTypeIcon } from '../utils/fileTypeIcon';
11
+ import './FileInfoDialog.css';
12
+
13
+ export interface FileInfoDialogProps {
14
+ /** 是否显示 */
15
+ visible: boolean;
16
+ /** 文件项 */
17
+ item: FileItem | null;
18
+ /** 关闭回调 */
19
+ onClose: () => void;
20
+ }
21
+
22
+ /** 获取文件类型图标 */
23
+ function getFileIcon(type: FileType, name?: string) {
24
+ // 文件夹使用 material-icon-theme:folder
25
+ if (type === FileType.FOLDER) {
26
+ return <Icon icon="flat-color-icons:folder" width={48} height={48} className="file-info-icon" />;
27
+ }
28
+
29
+ // 如果有文件名,使用 material-icon-theme 图标映射(传递 type 作为兜底)
30
+ if (name) {
31
+ const iconName = getFileTypeIcon(name, type);
32
+ return <Icon icon={iconName} width={48} height={48} className="file-info-icon" />;
33
+ }
34
+
35
+ // 回退逻辑(没有文件名时)
36
+ const iconMap: Partial<Record<FileType, string>> = {
37
+ [FileType.IMAGE]: 'material-icon-theme:image',
38
+ [FileType.VIDEO]: 'material-icon-theme:video',
39
+ [FileType.MUSIC]: 'material-icon-theme:audio',
40
+ [FileType.DOCUMENT]: 'material-icon-theme:word',
41
+ [FileType.CODE]: 'material-icon-theme:javascript',
42
+ [FileType.ARCHIVE]: 'material-icon-theme:zip',
43
+ [FileType.PDF]: 'material-icon-theme:pdf',
44
+ [FileType.TEXT]: 'material-icon-theme:document',
45
+ [FileType.APPLICATION]: 'material-icon-theme:exe',
46
+ };
47
+
48
+ const iconName = iconMap[type] || 'material-icon-theme:document';
49
+ return <Icon icon={iconName} width={48} height={48} className="file-info-icon" />;
50
+ }
51
+
52
+ /** 获取文件类型名称 */
53
+ function getFileTypeName(type: FileType, extension?: string): string {
54
+ switch (type) {
55
+ case FileType.FOLDER:
56
+ return '文件夹';
57
+ case FileType.IMAGE:
58
+ return `图片${extension ? ` (${extension.toUpperCase()})` : ''}`;
59
+ case FileType.VIDEO:
60
+ return `视频${extension ? ` (${extension.toUpperCase()})` : ''}`;
61
+ case FileType.MUSIC:
62
+ return `音频${extension ? ` (${extension.toUpperCase()})` : ''}`;
63
+ case FileType.DOCUMENT:
64
+ return `文档${extension ? ` (${extension.toUpperCase()})` : ''}`;
65
+ case FileType.CODE:
66
+ return `代码文件${extension ? ` (${extension.toUpperCase()})` : ''}`;
67
+ case FileType.ARCHIVE:
68
+ return `压缩包${extension ? ` (${extension.toUpperCase()})` : ''}`;
69
+ default:
70
+ return extension ? `${extension.toUpperCase()} 文件` : '文件';
71
+ }
72
+ }
73
+
74
+ /** 获取文件扩展名 */
75
+ function getExtension(filename: string): string | undefined {
76
+ const lastDot = filename.lastIndexOf('.');
77
+ if (lastDot === -1 || lastDot === 0) return undefined;
78
+ return filename.substring(lastDot + 1).toLowerCase();
79
+ }
80
+
81
+ /** 获取文件所在目录 */
82
+ function getDirectory(path: string): string {
83
+ const lastSlash = path.lastIndexOf('/');
84
+ if (lastSlash === -1) return path;
85
+ return path.substring(0, lastSlash) || '/';
86
+ }
87
+
88
+ export function FileInfoDialog({ visible, item, onClose }: FileInfoDialogProps) {
89
+ if (!visible || !item) return null;
90
+
91
+ const extension = getExtension(item.name);
92
+ const directory = getDirectory(item.id);
93
+
94
+ // 点击背景关闭
95
+ const handleBackdropClick = (e: React.MouseEvent) => {
96
+ if (e.target === e.currentTarget) {
97
+ onClose();
98
+ }
99
+ };
100
+
101
+ // ESC 关闭
102
+ React.useEffect(() => {
103
+ const handleKeyDown = (e: KeyboardEvent) => {
104
+ if (e.key === 'Escape') {
105
+ onClose();
106
+ }
107
+ };
108
+ if (visible) {
109
+ document.addEventListener('keydown', handleKeyDown);
110
+ }
111
+ return () => document.removeEventListener('keydown', handleKeyDown);
112
+ }, [visible, onClose]);
113
+
114
+ return (
115
+ <div className="file-info-dialog-overlay" onClick={handleBackdropClick}>
116
+ <div className="file-info-dialog">
117
+ {/* 头部 */}
118
+ <div className="file-info-dialog-header">
119
+ <div className="file-info-dialog-title">
120
+ {getFileIcon(item.type, item.name)}
121
+ <span className="file-info-dialog-name" title={item.name}>
122
+ {item.name}
123
+ </span>
124
+ </div>
125
+ <button className="file-info-dialog-close" onClick={onClose}>
126
+ <Icon icon="lucide:x" width={18} height={18} />
127
+ </button>
128
+ </div>
129
+
130
+ {/* 内容 */}
131
+ <div className="file-info-dialog-content">
132
+ {/* 类型 */}
133
+ <div className="file-info-row">
134
+ <div className="file-info-label">
135
+ <Icon icon="lucide:file" width={14} height={14} />
136
+ <span>类型</span>
137
+ </div>
138
+ <div className="file-info-value">
139
+ {getFileTypeName(item.type, extension)}
140
+ </div>
141
+ </div>
142
+
143
+ {/* 大小 */}
144
+ {item.type !== FileType.FOLDER && item.size && (
145
+ <div className="file-info-row">
146
+ <div className="file-info-label">
147
+ <Icon icon="lucide:hard-drive" width={14} height={14} />
148
+ <span>大小</span>
149
+ </div>
150
+ <div className="file-info-value">
151
+ {item.size}
152
+ </div>
153
+ </div>
154
+ )}
155
+
156
+ {/* 位置 */}
157
+ <div className="file-info-row">
158
+ <div className="file-info-label">
159
+ <Icon icon="lucide:map-pin" width={14} height={14} />
160
+ <span>位置</span>
161
+ </div>
162
+ <div className="file-info-value file-info-value--path" title={directory}>
163
+ {directory}
164
+ </div>
165
+ </div>
166
+
167
+ {/* 完整路径 */}
168
+ <div className="file-info-row">
169
+ <div className="file-info-label">
170
+ <Icon icon="lucide:map-pin" width={14} height={14} />
171
+ <span>完整路径</span>
172
+ </div>
173
+ <div className="file-info-value file-info-value--path" title={item.id}>
174
+ {item.id}
175
+ </div>
176
+ </div>
177
+
178
+ {/* 修改时间 */}
179
+ {item.dateModified && (
180
+ <div className="file-info-row">
181
+ <div className="file-info-label">
182
+ <Icon icon="lucide:clock" width={14} height={14} />
183
+ <span>修改时间</span>
184
+ </div>
185
+ <div className="file-info-value">
186
+ {item.dateModified}
187
+ </div>
188
+ </div>
189
+ )}
190
+ </div>
191
+
192
+ {/* 底部按钮 */}
193
+ <div className="file-info-dialog-footer">
194
+ <button className="file-info-dialog-btn" onClick={onClose}>
195
+ 关闭
196
+ </button>
197
+ </div>
198
+ </div>
199
+ </div>
200
+ );
201
+ }
202
+
@@ -0,0 +1,226 @@
1
+ /**
2
+ * FileList - 列表视图样式
3
+ * 参考 macOS Finder 和 Windows Explorer 的设计
4
+ */
5
+
6
+ .file-list {
7
+ width: 100%;
8
+ overflow-y: auto;
9
+ overflow-x: hidden;
10
+ }
11
+
12
+ .file-list-table {
13
+ width: 100%;
14
+ font-size: 13px;
15
+ text-align: left;
16
+ border-collapse: collapse;
17
+ }
18
+
19
+ /* 表头 - 更紧凑 */
20
+ .file-list-header {
21
+ font-size: 11px;
22
+ color: #86868b;
23
+ font-weight: 500;
24
+ text-transform: uppercase;
25
+ letter-spacing: 0.3px;
26
+ position: sticky;
27
+ top: 0;
28
+ background: rgba(255, 255, 255, 0.92);
29
+ backdrop-filter: blur(8px);
30
+ -webkit-backdrop-filter: blur(8px);
31
+ z-index: 10;
32
+ user-select: none;
33
+ }
34
+
35
+ .file-list-header-cell {
36
+ padding: 6px 12px;
37
+ border-bottom: 1px solid rgba(0, 0, 0, 0.08);
38
+ cursor: pointer;
39
+ text-align: left;
40
+ font-weight: 500;
41
+ transition: background-color 100ms;
42
+ }
43
+
44
+ .file-list-header-cell:hover {
45
+ background: rgba(0, 0, 0, 0.04);
46
+ }
47
+
48
+ .file-list-header-cell--name {
49
+ padding-left: 12px;
50
+ padding-right: 12px;
51
+ width: 45%;
52
+ }
53
+
54
+ .file-list-body {
55
+ /* 无需额外边框 */
56
+ }
57
+
58
+ /* 行样式 - 更紧凑 */
59
+ .file-list-row {
60
+ cursor: pointer;
61
+ user-select: none;
62
+ transition: background-color 80ms ease;
63
+ border-bottom: 1px solid transparent;
64
+ }
65
+
66
+ /* 可拖拽时的鼠标样式 */
67
+ .file-list-row[draggable="true"] {
68
+ cursor: grab;
69
+ }
70
+
71
+ .file-list-row:active {
72
+ cursor: grabbing;
73
+ }
74
+
75
+ /* 偶数行 */
76
+ .file-list-row--even {
77
+ background: transparent;
78
+ }
79
+
80
+ /* 奇数行 - 轻微斑马纹 */
81
+ .file-list-row--odd {
82
+ background: rgba(0, 0, 0, 0.015);
83
+ }
84
+
85
+ /* 悬停效果 */
86
+ .file-list-row--even:hover,
87
+ .file-list-row--odd:hover {
88
+ background: rgba(0, 0, 0, 0.04);
89
+ }
90
+
91
+ /* 选中状态 - macOS 风格整行高亮 */
92
+ .file-list-row--selected {
93
+ background: #007aff;
94
+ color: white;
95
+ }
96
+
97
+ .file-list-row--selected:hover {
98
+ background: #0066d6;
99
+ }
100
+
101
+ /* 拖拽悬停 */
102
+ .file-list-row--drag-over {
103
+ background: rgba(0, 122, 255, 0.15);
104
+ border-bottom-color: rgba(0, 122, 255, 0.3);
105
+ }
106
+
107
+ /* 单元格 - 更紧凑的行高 */
108
+ .file-list-cell {
109
+ padding: 4px 12px;
110
+ white-space: nowrap;
111
+ color: #6e6e73;
112
+ vertical-align: middle;
113
+ height: 28px;
114
+ }
115
+
116
+ /* 名称列 - 图标和文字对齐 */
117
+ .file-list-cell--name {
118
+ padding-left: 12px;
119
+ padding-right: 12px;
120
+ display: flex;
121
+ align-items: center;
122
+ gap: 8px;
123
+ }
124
+
125
+ /* 选中时单元格颜色 */
126
+ .file-list-cell--selected {
127
+ color: rgba(255, 255, 255, 0.75);
128
+ }
129
+
130
+ /* 大小列 - 等宽字体 */
131
+ .file-list-cell--size {
132
+ font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
133
+ font-size: 11px;
134
+ color: #8e8e93;
135
+ font-variant-numeric: tabular-nums;
136
+ }
137
+
138
+ /* 文件名 */
139
+ .file-list-name {
140
+ overflow: hidden;
141
+ text-overflow: ellipsis;
142
+ flex: 1;
143
+ color: #1d1d1f;
144
+ cursor: default;
145
+ font-weight: 400;
146
+ }
147
+
148
+ /* 选中后点击文件名可编辑 */
149
+ .file-list-name--selected {
150
+ color: white;
151
+ font-weight: 500;
152
+ cursor: text;
153
+ }
154
+
155
+ /* 重命名输入框 */
156
+ .file-list-rename-input {
157
+ flex: 1;
158
+ font-size: 13px;
159
+ padding: 2px 6px;
160
+ border: 1px solid #007aff;
161
+ border-radius: 4px;
162
+ outline: none;
163
+ box-shadow: 0 0 0 3px rgba(0, 122, 255, 0.2);
164
+ background: white;
165
+ cursor: text;
166
+ }
167
+
168
+ /* 深色模式支持 */
169
+ @media (prefers-color-scheme: dark) {
170
+ .file-list-header {
171
+ background: rgba(28, 28, 30, 0.92);
172
+ color: #98989d;
173
+ }
174
+
175
+ .file-list-header-cell {
176
+ border-bottom-color: rgba(255, 255, 255, 0.08);
177
+ }
178
+
179
+ .file-list-header-cell:hover {
180
+ background: rgba(255, 255, 255, 0.04);
181
+ }
182
+
183
+ .file-list-row--odd {
184
+ background: rgba(255, 255, 255, 0.02);
185
+ }
186
+
187
+ .file-list-row--even:hover,
188
+ .file-list-row--odd:hover {
189
+ background: rgba(255, 255, 255, 0.06);
190
+ }
191
+
192
+ .file-list-row--selected {
193
+ background: #0a84ff;
194
+ }
195
+
196
+ .file-list-row--selected:hover {
197
+ background: #0077ed;
198
+ }
199
+
200
+ .file-list-row--drag-over {
201
+ background: rgba(10, 132, 255, 0.2);
202
+ border-bottom-color: rgba(10, 132, 255, 0.4);
203
+ }
204
+
205
+ .file-list-cell {
206
+ color: #98989d;
207
+ }
208
+
209
+ .file-list-cell--size {
210
+ color: #6e6e73;
211
+ }
212
+
213
+ .file-list-name {
214
+ color: #f5f5f7;
215
+ }
216
+
217
+ .file-list-name--selected {
218
+ color: white;
219
+ }
220
+
221
+ .file-list-rename-input {
222
+ background: #1c1c1e;
223
+ border-color: #0a84ff;
224
+ color: #f5f5f7;
225
+ }
226
+ }