@huyooo/file-explorer-frontend-vue 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 (76) hide show
  1. package/dist/components/Breadcrumb.vue.d.ts +11 -0
  2. package/dist/components/Breadcrumb.vue.d.ts.map +1 -0
  3. package/dist/components/CompressDialog.vue.d.ts +16 -0
  4. package/dist/components/CompressDialog.vue.d.ts.map +1 -0
  5. package/dist/components/ContextMenu.vue.d.ts +18 -0
  6. package/dist/components/ContextMenu.vue.d.ts.map +1 -0
  7. package/dist/components/FileGrid.vue.d.ts +40 -0
  8. package/dist/components/FileGrid.vue.d.ts.map +1 -0
  9. package/dist/components/FileIcon.vue.d.ts +13 -0
  10. package/dist/components/FileIcon.vue.d.ts.map +1 -0
  11. package/dist/components/FileInfoDialog.vue.d.ts +14 -0
  12. package/dist/components/FileInfoDialog.vue.d.ts.map +1 -0
  13. package/dist/components/FileList.vue.d.ts +37 -0
  14. package/dist/components/FileList.vue.d.ts.map +1 -0
  15. package/dist/components/FileListView.vue.d.ts +43 -0
  16. package/dist/components/FileListView.vue.d.ts.map +1 -0
  17. package/dist/components/FileSidebar.vue.d.ts +17 -0
  18. package/dist/components/FileSidebar.vue.d.ts.map +1 -0
  19. package/dist/components/ProgressDialog.vue.d.ts +28 -0
  20. package/dist/components/ProgressDialog.vue.d.ts.map +1 -0
  21. package/dist/components/SortIndicator.vue.d.ts +6 -0
  22. package/dist/components/SortIndicator.vue.d.ts.map +1 -0
  23. package/dist/components/StatusBar.vue.d.ts +27 -0
  24. package/dist/components/StatusBar.vue.d.ts.map +1 -0
  25. package/dist/components/Toolbar.vue.d.ts +60 -0
  26. package/dist/components/Toolbar.vue.d.ts.map +1 -0
  27. package/dist/components/Window.vue.d.ts +65 -0
  28. package/dist/components/Window.vue.d.ts.map +1 -0
  29. package/dist/composables/useApplicationIcon.d.ts +16 -0
  30. package/dist/composables/useApplicationIcon.d.ts.map +1 -0
  31. package/dist/composables/useDragAndDrop.d.ts +14 -0
  32. package/dist/composables/useDragAndDrop.d.ts.map +1 -0
  33. package/dist/composables/useMediaPlayer.d.ts +24 -0
  34. package/dist/composables/useMediaPlayer.d.ts.map +1 -0
  35. package/dist/composables/useSelection.d.ts +15 -0
  36. package/dist/composables/useSelection.d.ts.map +1 -0
  37. package/dist/composables/useWindowDrag.d.ts +18 -0
  38. package/dist/composables/useWindowDrag.d.ts.map +1 -0
  39. package/dist/composables/useWindowResize.d.ts +12 -0
  40. package/dist/composables/useWindowResize.d.ts.map +1 -0
  41. package/dist/index.css +1 -0
  42. package/dist/index.d.ts +22 -0
  43. package/dist/index.d.ts.map +1 -0
  44. package/dist/index.js +4051 -0
  45. package/dist/index.js.map +1 -0
  46. package/dist/types/index.d.ts +268 -0
  47. package/dist/types/index.d.ts.map +1 -0
  48. package/dist/utils/fileTypeIcon.d.ts +6 -0
  49. package/dist/utils/fileTypeIcon.d.ts.map +1 -0
  50. package/dist/utils/folderTypeIcon.d.ts +14 -0
  51. package/dist/utils/folderTypeIcon.d.ts.map +1 -0
  52. package/package.json +55 -0
  53. package/src/components/Breadcrumb.vue +111 -0
  54. package/src/components/CompressDialog.vue +478 -0
  55. package/src/components/ContextMenu.vue +550 -0
  56. package/src/components/FileGrid.vue +504 -0
  57. package/src/components/FileIcon.vue +132 -0
  58. package/src/components/FileInfoDialog.vue +465 -0
  59. package/src/components/FileList.vue +421 -0
  60. package/src/components/FileListView.vue +321 -0
  61. package/src/components/FileSidebar.vue +158 -0
  62. package/src/components/ProgressDialog.vue +368 -0
  63. package/src/components/SortIndicator.vue +22 -0
  64. package/src/components/StatusBar.vue +43 -0
  65. package/src/components/Toolbar.vue +271 -0
  66. package/src/components/Window.vue +561 -0
  67. package/src/composables/useApplicationIcon.ts +79 -0
  68. package/src/composables/useDragAndDrop.ts +103 -0
  69. package/src/composables/useMediaPlayer.ts +174 -0
  70. package/src/composables/useSelection.ts +107 -0
  71. package/src/composables/useWindowDrag.ts +66 -0
  72. package/src/composables/useWindowResize.ts +134 -0
  73. package/src/index.ts +32 -0
  74. package/src/types/index.ts +273 -0
  75. package/src/utils/fileTypeIcon.ts +309 -0
  76. package/src/utils/folderTypeIcon.ts +132 -0
@@ -0,0 +1,368 @@
1
+ <template>
2
+ <Teleport to="body">
3
+ <div v-if="visible" class="progress-dialog-overlay">
4
+ <div class="progress-dialog">
5
+ <!-- 头部 -->
6
+ <div class="progress-dialog-header">
7
+ <div class="progress-dialog-title">
8
+ <component :is="statusIcon" :size="24" :class="statusIconClass" />
9
+ <span>{{ title }}</span>
10
+ </div>
11
+ <button v-if="isCompleted" class="progress-dialog-close" @click="handleClose">
12
+ <X :size="18" />
13
+ </button>
14
+ </div>
15
+
16
+ <!-- 内容 -->
17
+ <div class="progress-dialog-content">
18
+ <!-- 状态文本 -->
19
+ <div class="progress-dialog-status">{{ statusText }}</div>
20
+
21
+ <!-- 进度条 -->
22
+ <div v-if="progress.status === 'processing'" class="progress-dialog-bar-container">
23
+ <div class="progress-dialog-bar">
24
+ <div
25
+ class="progress-dialog-bar-fill"
26
+ :style="{ width: `${progress.percent}%` }"
27
+ />
28
+ </div>
29
+ <span class="progress-dialog-percent">{{ progress.percent }}%</span>
30
+ </div>
31
+
32
+ <!-- 当前文件 -->
33
+ <div v-if="progress.currentFile && progress.status === 'processing'" class="progress-dialog-current-file">
34
+ {{ progress.currentFile }}
35
+ </div>
36
+
37
+ <!-- 文件计数 -->
38
+ <div v-if="progress.totalCount && progress.totalCount > 0 && progress.status === 'processing'" class="progress-dialog-count">
39
+ {{ progress.processedCount || 0 }} / {{ progress.totalCount }} 个文件
40
+ </div>
41
+
42
+ <!-- 错误信息 -->
43
+ <div v-if="progress.error" class="progress-dialog-error">
44
+ {{ progress.error }}
45
+ </div>
46
+
47
+ <!-- 成功后显示输出路径 -->
48
+ <div v-if="progress.status === 'success' && progress.outputPath" class="progress-dialog-output">
49
+ <span class="progress-dialog-output-label">输出位置:</span>
50
+ <span class="progress-dialog-output-path">{{ progress.outputPath }}</span>
51
+ </div>
52
+ </div>
53
+
54
+ <!-- 底部按钮 -->
55
+ <div class="progress-dialog-footer">
56
+ <button
57
+ v-if="progress.status === 'processing'"
58
+ class="progress-dialog-btn progress-dialog-btn-cancel"
59
+ @click="emit('cancel')"
60
+ >
61
+ 取消
62
+ </button>
63
+ <button
64
+ v-if="progress.status === 'success' && progress.outputPath"
65
+ class="progress-dialog-btn progress-dialog-btn-folder"
66
+ @click="emit('openFolder', progress.outputPath!)"
67
+ >
68
+ <FolderOpen :size="16" />
69
+ 打开文件夹
70
+ </button>
71
+ <button
72
+ v-if="isCompleted"
73
+ class="progress-dialog-btn progress-dialog-btn-close"
74
+ @click="handleClose"
75
+ >
76
+ 关闭
77
+ </button>
78
+ </div>
79
+ </div>
80
+ </div>
81
+ </Teleport>
82
+ </template>
83
+
84
+ <script setup lang="ts">
85
+ import { computed } from 'vue';
86
+ import { X, Loader2, CheckCircle, XCircle, Archive, FolderOpen } from 'lucide-vue-next';
87
+
88
+ /** 进度状态 */
89
+ export type ProgressStatus = 'pending' | 'processing' | 'success' | 'error';
90
+
91
+ /** 进度信息 */
92
+ export interface ProgressInfo {
93
+ type: 'compress' | 'extract';
94
+ status: ProgressStatus;
95
+ percent: number;
96
+ currentFile?: string;
97
+ processedCount?: number;
98
+ totalCount?: number;
99
+ error?: string;
100
+ outputPath?: string;
101
+ }
102
+
103
+ interface Props {
104
+ visible: boolean;
105
+ progress: ProgressInfo;
106
+ }
107
+
108
+ const props = defineProps<Props>();
109
+
110
+ const emit = defineEmits<{
111
+ cancel: [];
112
+ close: [];
113
+ openFolder: [path: string];
114
+ }>();
115
+
116
+ const title = computed(() => props.progress.type === 'compress' ? '压缩文件' : '解压文件');
117
+ const isCompleted = computed(() => props.progress.status === 'success' || props.progress.status === 'error');
118
+
119
+ const statusIcon = computed(() => {
120
+ switch (props.progress.status) {
121
+ case 'processing': return Loader2;
122
+ case 'success': return CheckCircle;
123
+ case 'error': return XCircle;
124
+ default: return Archive;
125
+ }
126
+ });
127
+
128
+ const statusIconClass = computed(() => {
129
+ switch (props.progress.status) {
130
+ case 'processing': return 'progress-dialog-icon-spin';
131
+ case 'success': return 'progress-dialog-icon-success';
132
+ case 'error': return 'progress-dialog-icon-error';
133
+ default: return '';
134
+ }
135
+ });
136
+
137
+ const statusText = computed(() => {
138
+ switch (props.progress.status) {
139
+ case 'pending': return '准备中...';
140
+ case 'processing': return props.progress.type === 'compress' ? '正在压缩...' : '正在解压...';
141
+ case 'success': return props.progress.type === 'compress' ? '压缩完成' : '解压完成';
142
+ case 'error': return '操作失败';
143
+ default: return '';
144
+ }
145
+ });
146
+
147
+ const handleClose = () => {
148
+ if (isCompleted.value) {
149
+ emit('close');
150
+ } else {
151
+ emit('cancel');
152
+ }
153
+ };
154
+ </script>
155
+
156
+ <style scoped>
157
+ .progress-dialog-overlay {
158
+ position: fixed;
159
+ inset: 0;
160
+ background: rgba(0, 0, 0, 0.5);
161
+ display: flex;
162
+ align-items: center;
163
+ justify-content: center;
164
+ z-index: 10001;
165
+ }
166
+
167
+ .progress-dialog {
168
+ background: white;
169
+ border-radius: 12px;
170
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.2);
171
+ width: 380px;
172
+ max-width: 90vw;
173
+ overflow: hidden;
174
+ display: flex;
175
+ flex-direction: column;
176
+ }
177
+
178
+ .progress-dialog-header {
179
+ display: flex;
180
+ align-items: center;
181
+ justify-content: space-between;
182
+ padding: 16px 20px;
183
+ border-bottom: 1px solid rgb(229, 231, 233);
184
+ }
185
+
186
+ .progress-dialog-title {
187
+ display: flex;
188
+ align-items: center;
189
+ gap: 10px;
190
+ font-weight: 600;
191
+ font-size: 16px;
192
+ color: rgb(17, 24, 39);
193
+ }
194
+
195
+ .progress-dialog-close {
196
+ background: none;
197
+ border: none;
198
+ padding: 4px;
199
+ cursor: pointer;
200
+ color: rgb(107, 114, 128);
201
+ border-radius: 4px;
202
+ display: flex;
203
+ align-items: center;
204
+ justify-content: center;
205
+ }
206
+
207
+ .progress-dialog-close:hover {
208
+ background: rgb(243, 244, 246);
209
+ color: rgb(55, 65, 81);
210
+ }
211
+
212
+ .progress-dialog-content {
213
+ padding: 24px 20px;
214
+ display: flex;
215
+ flex-direction: column;
216
+ gap: 16px;
217
+ }
218
+
219
+ .progress-dialog-status {
220
+ font-size: 14px;
221
+ color: rgb(55, 65, 81);
222
+ text-align: center;
223
+ }
224
+
225
+ .progress-dialog-bar-container {
226
+ display: flex;
227
+ align-items: center;
228
+ gap: 12px;
229
+ }
230
+
231
+ .progress-dialog-bar {
232
+ flex: 1;
233
+ height: 8px;
234
+ background: rgb(229, 231, 233);
235
+ border-radius: 4px;
236
+ overflow: hidden;
237
+ }
238
+
239
+ .progress-dialog-bar-fill {
240
+ height: 100%;
241
+ background: rgb(59, 130, 246);
242
+ border-radius: 4px;
243
+ transition: width 0.3s ease;
244
+ }
245
+
246
+ .progress-dialog-percent {
247
+ font-size: 13px;
248
+ font-weight: 500;
249
+ color: rgb(55, 65, 81);
250
+ min-width: 40px;
251
+ text-align: right;
252
+ }
253
+
254
+ .progress-dialog-current-file {
255
+ font-size: 12px;
256
+ color: rgb(107, 114, 128);
257
+ text-align: center;
258
+ white-space: nowrap;
259
+ overflow: hidden;
260
+ text-overflow: ellipsis;
261
+ }
262
+
263
+ .progress-dialog-count {
264
+ font-size: 12px;
265
+ color: rgb(107, 114, 128);
266
+ text-align: center;
267
+ }
268
+
269
+ .progress-dialog-error {
270
+ padding: 12px;
271
+ background: rgb(254, 242, 242);
272
+ border: 1px solid rgb(254, 202, 202);
273
+ border-radius: 6px;
274
+ color: rgb(185, 28, 28);
275
+ font-size: 13px;
276
+ }
277
+
278
+ .progress-dialog-output {
279
+ padding: 12px;
280
+ background: rgb(240, 253, 244);
281
+ border: 1px solid rgb(187, 247, 208);
282
+ border-radius: 6px;
283
+ font-size: 12px;
284
+ display: flex;
285
+ flex-direction: column;
286
+ gap: 4px;
287
+ }
288
+
289
+ .progress-dialog-output-label {
290
+ color: rgb(22, 101, 52);
291
+ font-weight: 500;
292
+ }
293
+
294
+ .progress-dialog-output-path {
295
+ color: rgb(21, 128, 61);
296
+ word-break: break-all;
297
+ }
298
+
299
+ .progress-dialog-footer {
300
+ display: flex;
301
+ justify-content: flex-end;
302
+ gap: 12px;
303
+ padding: 16px 20px;
304
+ border-top: 1px solid rgb(229, 231, 233);
305
+ }
306
+
307
+ .progress-dialog-btn {
308
+ padding: 8px 16px;
309
+ border-radius: 6px;
310
+ font-size: 14px;
311
+ font-weight: 500;
312
+ cursor: pointer;
313
+ transition: all 0.2s;
314
+ display: flex;
315
+ align-items: center;
316
+ gap: 6px;
317
+ }
318
+
319
+ .progress-dialog-btn-cancel {
320
+ background: white;
321
+ border: 1px solid rgb(209, 213, 219);
322
+ color: rgb(55, 65, 81);
323
+ }
324
+
325
+ .progress-dialog-btn-cancel:hover {
326
+ background: rgb(249, 250, 251);
327
+ }
328
+
329
+ .progress-dialog-btn-folder {
330
+ background: rgb(240, 253, 244);
331
+ border: 1px solid rgb(187, 247, 208);
332
+ color: rgb(22, 101, 52);
333
+ }
334
+
335
+ .progress-dialog-btn-folder:hover {
336
+ background: rgb(220, 252, 231);
337
+ }
338
+
339
+ .progress-dialog-btn-close {
340
+ background: rgb(59, 130, 246);
341
+ border: 1px solid rgb(59, 130, 246);
342
+ color: white;
343
+ }
344
+
345
+ .progress-dialog-btn-close:hover {
346
+ background: rgb(37, 99, 235);
347
+ }
348
+
349
+ /* 旋转动画 */
350
+ .progress-dialog-icon-spin {
351
+ animation: spin 1s linear infinite;
352
+ color: rgb(59, 130, 246);
353
+ }
354
+
355
+ @keyframes spin {
356
+ from { transform: rotate(0deg); }
357
+ to { transform: rotate(360deg); }
358
+ }
359
+
360
+ .progress-dialog-icon-success {
361
+ color: rgb(34, 197, 94);
362
+ }
363
+
364
+ .progress-dialog-icon-error {
365
+ color: rgb(239, 68, 68);
366
+ }
367
+ </style>
368
+
@@ -0,0 +1,22 @@
1
+ <template>
2
+ <span class="sort-indicator">
3
+ <Icon :icon="direction === 'asc' ? 'lucide:chevron-up' : 'lucide:chevron-down'" :width="12" :height="12" />
4
+ </span>
5
+ </template>
6
+
7
+ <script setup lang="ts">
8
+ import { Icon } from '@iconify/vue';
9
+
10
+ defineProps<{
11
+ direction: 'asc' | 'desc';
12
+ }>();
13
+ </script>
14
+
15
+ <style scoped>
16
+ .sort-indicator {
17
+ margin-left: 4px;
18
+ display: inline-flex;
19
+ align-items: center;
20
+ color: rgb(59, 130, 246);
21
+ }
22
+ </style>
@@ -0,0 +1,43 @@
1
+ <template>
2
+ <div class="file-status-bar">
3
+ <slot>
4
+ <span>{{ itemCount }} 个项目</span>
5
+ <span v-if="selectedCount > 0"> • 已选择 {{ selectedCount }} 个</span>
6
+ </slot>
7
+ </div>
8
+ </template>
9
+
10
+ <script setup lang="ts">
11
+ // Props
12
+ withDefaults(defineProps<{
13
+ /** 项目总数 */
14
+ itemCount?: number;
15
+ /** 选中数量 */
16
+ selectedCount?: number;
17
+ }>(), {
18
+ itemCount: 0,
19
+ selectedCount: 0,
20
+ });
21
+ </script>
22
+
23
+ <style scoped>
24
+ .file-status-bar {
25
+ height: 1.5rem;
26
+ background-color: rgb(249, 250, 251);
27
+ border-top: 1px solid rgb(229, 231, 233);
28
+ display: flex;
29
+ align-items: center;
30
+ justify-content: center;
31
+ padding: 0 8px;
32
+ font-size: 10px;
33
+ color: rgb(107, 114, 128);
34
+ user-select: none;
35
+ flex-shrink: 0;
36
+ z-index: 20;
37
+ gap: 4px;
38
+ }
39
+
40
+ .file-status-bar > * {
41
+ flex-shrink: 0;
42
+ }
43
+ </style>
@@ -0,0 +1,271 @@
1
+ <template>
2
+ <div class="file-toolbar" :class="{ 'file-toolbar--draggable': draggable }">
3
+ <!-- 导航按钮 -->
4
+ <div class="file-toolbar-nav">
5
+ <button
6
+ class="file-toolbar-button"
7
+ @click="emit('back')"
8
+ :disabled="!canGoBack"
9
+ title="后退"
10
+ >
11
+ <Icon icon="lucide:chevron-left" :width="18" :height="18" />
12
+ </button>
13
+ <button
14
+ class="file-toolbar-button"
15
+ @click="emit('forward')"
16
+ :disabled="!canGoForward"
17
+ title="前进"
18
+ >
19
+ <Icon icon="lucide:chevron-right" :width="18" :height="18" />
20
+ </button>
21
+ </div>
22
+
23
+ <!-- 面包屑插槽 -->
24
+ <div class="file-toolbar-breadcrumb">
25
+ <slot name="breadcrumb">
26
+ <Breadcrumb
27
+ v-if="breadcrumbs.length > 0"
28
+ :items="breadcrumbs"
29
+ @navigate="(item) => emit('breadcrumb-navigate', item)"
30
+ />
31
+ </slot>
32
+ </div>
33
+
34
+ <!-- 自定义内容插槽 -->
35
+ <div v-if="$slots.default" class="file-toolbar-custom">
36
+ <slot></slot>
37
+ </div>
38
+
39
+ <!-- 操作区 -->
40
+ <div class="file-toolbar-actions">
41
+ <!-- 搜索框 -->
42
+ <div v-if="showSearch" class="file-toolbar-search">
43
+ <Icon icon="lucide:search" :width="16" :height="16" class="file-toolbar-search-icon" />
44
+ <input
45
+ type="text"
46
+ :value="searchQuery"
47
+ @input="emit('update:searchQuery', ($event.target as HTMLInputElement).value)"
48
+ placeholder="搜索"
49
+ class="file-toolbar-search-input"
50
+ />
51
+ </div>
52
+
53
+ <!-- 视图切换 -->
54
+ <div v-if="showViewToggle" class="file-toolbar-view-toggle">
55
+ <button
56
+ @click="emit('update:viewMode', 'grid')"
57
+ :class="['file-toolbar-button', viewMode === 'grid' ? 'file-toolbar-button--active' : '']"
58
+ title="网格视图"
59
+ >
60
+ <Icon icon="lucide:layout-grid" :width="18" :height="18" />
61
+ </button>
62
+ <button
63
+ @click="emit('update:viewMode', 'list')"
64
+ :class="['file-toolbar-button', viewMode === 'list' ? 'file-toolbar-button--active' : '']"
65
+ title="列表视图"
66
+ >
67
+ <Icon icon="lucide:list" :width="18" :height="18" />
68
+ </button>
69
+ </div>
70
+
71
+ <!-- 额外操作插槽 -->
72
+ <slot name="actions"></slot>
73
+ </div>
74
+ </div>
75
+ </template>
76
+
77
+ <script setup lang="ts">
78
+ import { Icon } from '@iconify/vue';
79
+ import Breadcrumb from './Breadcrumb.vue';
80
+ import type { BreadcrumbItem } from '../types';
81
+
82
+ // Props
83
+ withDefaults(defineProps<{
84
+ /** 是否可后退 */
85
+ canGoBack?: boolean;
86
+ /** 是否可前进 */
87
+ canGoForward?: boolean;
88
+ /** 面包屑数据 */
89
+ breadcrumbs?: BreadcrumbItem[];
90
+ /** 视图模式 */
91
+ viewMode?: 'grid' | 'list';
92
+ /** 搜索关键词 */
93
+ searchQuery?: string;
94
+ /** 是否显示搜索框 */
95
+ showSearch?: boolean;
96
+ /** 是否显示视图切换 */
97
+ showViewToggle?: boolean;
98
+ /** 是否可拖拽(用于窗口拖动) */
99
+ draggable?: boolean;
100
+ }>(), {
101
+ canGoBack: false,
102
+ canGoForward: false,
103
+ breadcrumbs: () => [],
104
+ viewMode: 'grid',
105
+ searchQuery: '',
106
+ showSearch: false,
107
+ showViewToggle: true,
108
+ draggable: false,
109
+ });
110
+
111
+ // Events
112
+ const emit = defineEmits<{
113
+ (e: 'back'): void;
114
+ (e: 'forward'): void;
115
+ (e: 'breadcrumb-navigate', item: BreadcrumbItem): void;
116
+ (e: 'update:viewMode', mode: 'grid' | 'list'): void;
117
+ (e: 'update:searchQuery', query: string): void;
118
+ }>();
119
+ </script>
120
+
121
+ <style scoped>
122
+ .file-toolbar {
123
+ height: 3rem;
124
+ background: rgba(249, 250, 251, 0.9);
125
+ backdrop-filter: blur(12px);
126
+ border-bottom: 1px solid rgb(229, 231, 233);
127
+ display: flex;
128
+ align-items: center;
129
+ padding: 0 1rem;
130
+ user-select: none;
131
+ flex-shrink: 0;
132
+ z-index: 30;
133
+ position: relative;
134
+ }
135
+
136
+ .file-toolbar--draggable {
137
+ -webkit-app-region: drag;
138
+ }
139
+
140
+ .file-toolbar-nav {
141
+ display: flex;
142
+ align-items: center;
143
+ color: rgb(75, 85, 99);
144
+ background: rgba(229, 231, 233, 0.5);
145
+ border-radius: 0.5rem;
146
+ padding: 0.125rem;
147
+ flex-shrink: 0;
148
+ -webkit-app-region: no-drag;
149
+ }
150
+
151
+ .file-toolbar-button {
152
+ padding: 0.25rem;
153
+ border-radius: 0.375rem;
154
+ transition: all 200ms;
155
+ border: none;
156
+ background: transparent;
157
+ cursor: pointer;
158
+ display: flex;
159
+ align-items: center;
160
+ justify-content: center;
161
+ color: rgb(31, 41, 55);
162
+ }
163
+
164
+ .file-toolbar-button:hover:not(:disabled) {
165
+ background: rgb(209, 213, 219);
166
+ }
167
+
168
+ .file-toolbar-button:disabled {
169
+ color: rgb(156, 163, 175);
170
+ cursor: default;
171
+ }
172
+
173
+ .file-toolbar-button--active {
174
+ background: white;
175
+ box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
176
+ color: rgb(31, 41, 55);
177
+ }
178
+
179
+ .file-toolbar-breadcrumb {
180
+ flex: 1;
181
+ min-width: 0;
182
+ overflow: hidden;
183
+ margin-left: 1rem;
184
+ /* 不设置 no-drag,让空白区域可拖拽 */
185
+ }
186
+
187
+ .file-toolbar-custom {
188
+ display: flex;
189
+ align-items: center;
190
+ gap: 0.5rem;
191
+ -webkit-app-region: no-drag;
192
+ }
193
+
194
+ .file-toolbar-actions {
195
+ display: flex;
196
+ align-items: center;
197
+ gap: 0.75rem;
198
+ flex-shrink: 0;
199
+ margin-left: auto;
200
+ -webkit-app-region: no-drag;
201
+ }
202
+
203
+ .file-toolbar-search {
204
+ position: relative;
205
+ display: none;
206
+ }
207
+
208
+ @media (min-width: 768px) {
209
+ .file-toolbar-search {
210
+ display: block;
211
+ }
212
+ }
213
+
214
+ .file-toolbar-search-icon {
215
+ position: absolute;
216
+ left: 0.625rem;
217
+ top: 50%;
218
+ transform: translateY(-50%);
219
+ color: rgb(156, 163, 175);
220
+ transition: color 200ms;
221
+ }
222
+
223
+ .file-toolbar-search:focus-within .file-toolbar-search-icon {
224
+ color: rgb(59, 130, 246);
225
+ }
226
+
227
+ .file-toolbar-search-input {
228
+ background: rgb(243, 244, 246);
229
+ border: 1px solid transparent;
230
+ border-radius: 0.375rem;
231
+ padding-left: 2rem;
232
+ padding-right: 0.75rem;
233
+ padding-top: 0.25rem;
234
+ padding-bottom: 0.25rem;
235
+ font-size: 0.875rem;
236
+ outline: none;
237
+ transition: all 200ms;
238
+ width: 8rem;
239
+ color: rgb(55, 65, 81);
240
+ }
241
+
242
+ .file-toolbar-search-input::placeholder {
243
+ color: rgb(156, 163, 175);
244
+ }
245
+
246
+ .file-toolbar-search-input:focus {
247
+ background: white;
248
+ border-color: rgb(96, 165, 250);
249
+ box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.1);
250
+ width: 12rem;
251
+ }
252
+
253
+ .file-toolbar-view-toggle {
254
+ display: flex;
255
+ align-items: center;
256
+ background: rgba(229, 231, 233, 0.5);
257
+ border-radius: 0.5rem;
258
+ padding: 0.125rem;
259
+ color: rgb(75, 85, 99);
260
+ }
261
+
262
+ .file-toolbar-view-toggle .file-toolbar-button--active {
263
+ background: white;
264
+ box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
265
+ color: rgb(31, 41, 55);
266
+ }
267
+
268
+ .file-toolbar-view-toggle .file-toolbar-button:not(.file-toolbar-button--active):hover {
269
+ background: rgba(209, 213, 219, 0.5);
270
+ }
271
+ </style>