@kne/file-system-view 1.0.0

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.
package/README.md ADDED
@@ -0,0 +1,290 @@
1
+ # file-system-view
2
+
3
+ ### 描述
4
+
5
+ A React component for displaying file directory structure with tree view, file type icons and context menu
6
+
7
+ ### 安装
8
+
9
+ ```shell
10
+ npm i --save @kne/file-system-view
11
+ ```
12
+
13
+ ### 概述
14
+
15
+ 一个用于浏览文件目录结构的 React 组件,支持树形展示、文件夹展开收起、文件类型图标、hover 操作菜单等功能。
16
+
17
+ ### 特性
18
+
19
+ - 📁 **树形结构** - 支持多级目录展示,文件夹可展开/收起,带有层级连接线
20
+ - 🎨 **文件图标** - 自动识别 20+ 种文件类型,显示对应图标
21
+ - ⚡ **操作菜单** - hover 显示操作按钮,支持自定义菜单项
22
+ - 🌐 **国际化** - 内置中英文支持
23
+ - 📱 **美观易用** - 精心设计的交互和视觉样式
24
+
25
+
26
+ ### 示例
27
+
28
+ #### 示例代码
29
+
30
+ - 基础用法
31
+ - 展示完整的文件系统浏览功能,包含文件夹展开收起、文件类型图标、hover操作菜单等特性
32
+ - _FileSystemView(@kne/current-lib_file-system-view)[import * as _FileSystemView from "@kne/file-system-view"],(@kne/current-lib_file-system-view/dist/index.css)[import "@kne/file-system-view/dist/index.css"],antd(antd)[import * as antd from "antd"]
33
+
34
+ ```jsx
35
+ const { default: FileSystemView } = _FileSystemView;
36
+ const { message } = antd;
37
+
38
+ const fileData = [
39
+ {
40
+ name: 'src',
41
+ type: 'directory',
42
+ children: [
43
+ {
44
+ name: 'components',
45
+ type: 'directory',
46
+ children: [
47
+ { name: 'Button.tsx', type: 'file' },
48
+ { name: 'Input.tsx', type: 'file' },
49
+ { name: 'Modal.tsx', type: 'file' },
50
+ ],
51
+ },
52
+ {
53
+ name: 'utils',
54
+ type: 'directory',
55
+ children: [
56
+ { name: 'format.js', type: 'file' },
57
+ { name: 'request.js', type: 'file' },
58
+ ],
59
+ },
60
+ { name: 'index.ts', type: 'file' },
61
+ { name: 'App.tsx', type: 'file' },
62
+ { name: 'styles.scss', type: 'file' },
63
+ ],
64
+ },
65
+ {
66
+ name: 'public',
67
+ type: 'directory',
68
+ children: [
69
+ { name: 'index.html', type: 'file' },
70
+ { name: 'favicon.svg', type: 'file' },
71
+ { name: 'logo.png', type: 'file' },
72
+ ],
73
+ },
74
+ {
75
+ name: 'docs',
76
+ type: 'directory',
77
+ children: [
78
+ { name: 'README.md', type: 'file' },
79
+ { name: 'API.md', type: 'file' },
80
+ { name: 'guide.pdf', type: 'file' },
81
+ { name: 'very-long-file-name-that-demonstrates-text-overflow-handling-in-the-component-v2.0.0-final.pdf', type: 'file' },
82
+ ],
83
+ },
84
+ {
85
+ name: 'media',
86
+ type: 'directory',
87
+ children: [
88
+ {
89
+ name: 'videos',
90
+ type: 'directory',
91
+ children: [
92
+ { name: 'intro.mp4', type: 'file' },
93
+ { name: 'demo.mov', type: 'file' },
94
+ { name: 'tutorial.webm', type: 'file' },
95
+ ],
96
+ },
97
+ {
98
+ name: 'audio',
99
+ type: 'directory',
100
+ children: [
101
+ { name: 'bgm.mp3', type: 'file' },
102
+ { name: 'sound.wav', type: 'file' },
103
+ { name: 'voice.flac', type: 'file' },
104
+ ],
105
+ },
106
+ ],
107
+ },
108
+ { name: 'package.json', type: 'file' },
109
+ { name: 'tsconfig.json', type: 'file' },
110
+ { name: '.gitignore', type: 'file' },
111
+ ];
112
+
113
+ const menuItems = [
114
+ {
115
+ label: '打开',
116
+ onClick: (data, key) => message.info(`打开: ${key}`),
117
+ },
118
+ {
119
+ label: '复制路径',
120
+ onClick: (data, key) => {
121
+ navigator.clipboard.writeText(key);
122
+ message.success('路径已复制');
123
+ },
124
+ },
125
+ { type: 'divider' },
126
+ {
127
+ label: '重命名',
128
+ onClick: (data, key) => message.info(`重命名: ${data.name}`),
129
+ },
130
+ {
131
+ label: '删除',
132
+ danger: true,
133
+ onClick: (data, key) => message.warning(`删除: ${key}`),
134
+ },
135
+ ];
136
+
137
+ const BaseExample = () => {
138
+ return (
139
+ <div style={{ padding: 24, background: '#fafafa', borderRadius: 8 }}>
140
+ <FileSystemView
141
+ data={fileData}
142
+ menuItems={menuItems}
143
+ defaultExpandAll
144
+ onFileClick={(data, key) => message.info(&#96;点击文件: ${key}&#96;)}
145
+ />
146
+ </div>
147
+ );
148
+ };
149
+
150
+ render(<BaseExample />);
151
+
152
+ ```
153
+
154
+ - 简单列表
155
+ - 不包含操作菜单的简单文件列表示例
156
+ - _FileSystemView(@kne/current-lib_file-system-view)[import * as _FileSystemView from "@kne/file-system-view"],(@kne/current-lib_file-system-view/dist/index.css)[import "@kne/file-system-view/dist/index.css"]
157
+
158
+ ```jsx
159
+ const { default: FileSystemView } = _FileSystemView;
160
+
161
+ const fileData = [
162
+ { name: 'index.js', type: 'file' },
163
+ { name: 'utils.js', type: 'file' },
164
+ { name: 'styles.css', type: 'file' },
165
+ ];
166
+
167
+ const SimpleExample = () => {
168
+ return (
169
+ <div style={{ padding: 24, background: '#fafafa', borderRadius: 8 }}>
170
+ <FileSystemView
171
+ data={fileData}
172
+ onFileClick={(data, key) => console.log('点击文件:', key)}
173
+ />
174
+ </div>
175
+ );
176
+ };
177
+
178
+ render(<SimpleExample />);
179
+
180
+ ```
181
+
182
+ - 受控模式
183
+ - 通过 expandedKeys 和 selectedKey 控制展开和选中状态,可配合外部按钮实现展开全部/收起全部功能
184
+ - _FileSystemView(@kne/current-lib_file-system-view)[import * as _FileSystemView from "@kne/file-system-view"],(@kne/current-lib_file-system-view/dist/index.css)[import "@kne/file-system-view/dist/index.css"],antd(antd)[import * as antd from "antd"]
185
+
186
+ ```jsx
187
+ const { default: FileSystemView } = _FileSystemView;
188
+ const { useState } = React;
189
+ const { Button, Space, message } = antd;
190
+
191
+ const fileData = [
192
+ {
193
+ name: 'project',
194
+ type: 'directory',
195
+ children: [
196
+ {
197
+ name: 'src',
198
+ type: 'directory',
199
+ children: [
200
+ { name: 'main.js', type: 'file' },
201
+ { name: 'app.js', type: 'file' },
202
+ ],
203
+ },
204
+ {
205
+ name: 'lib',
206
+ type: 'directory',
207
+ children: [{ name: 'utils.js', type: 'file' }],
208
+ },
209
+ { name: 'config.json', type: 'file' },
210
+ ],
211
+ },
212
+ ];
213
+
214
+ const ControlledExample = () => {
215
+ const [expandedKeys, setExpandedKeys] = useState([]);
216
+
217
+ const expandAll = () => {
218
+ const allKeys = ['project', 'project/src', 'project/lib'];
219
+ setExpandedKeys(allKeys);
220
+ message.success('已展开所有目录');
221
+ };
222
+
223
+ const collapseAll = () => {
224
+ setExpandedKeys([]);
225
+ message.success('已收起所有目录');
226
+ };
227
+
228
+ return (
229
+ <div>
230
+ <Space style={{ marginBottom: 16 }}>
231
+ <Button onClick={expandAll}>展开全部</Button>
232
+ <Button onClick={collapseAll}>收起全部</Button>
233
+ </Space>
234
+ <div style={{ padding: 24, background: '#fafafa', borderRadius: 8 }}>
235
+ <FileSystemView
236
+ data={fileData}
237
+ expandedKeys={expandedKeys}
238
+ onExpand={(keys) => setExpandedKeys(keys)}
239
+ />
240
+ </div>
241
+ </div>
242
+ );
243
+ };
244
+
245
+ render(<ControlledExample />);
246
+
247
+ ```
248
+
249
+ ### API
250
+
251
+ ### Props
252
+
253
+ | 属性 | 类型 | 默认值 | 说明 |
254
+ |----|----|-----|----|
255
+ | data | `FileItem[]` | `[]` | 目录结构数据 |
256
+ | menuItems | `MenuItem[]` | - | 操作菜单项配置,不传则不显示操作按钮 |
257
+ | defaultExpandAll | `boolean` | `false` | 是否默认展开所有目录 |
258
+ | expandedKeys | `string[]` | - | (受控)展开的节点 key 数组 |
259
+ | onExpand | `(keys: string[], info: { node, expanded, nativeEvent }) => void` | - | 展开/收起节点时的回调 |
260
+ | onFileClick | `(data: FileItem, key: string) => void` | - | 点击文件时的回调 |
261
+
262
+ ### FileItem
263
+
264
+ | 属性 | 类型 | 说明 |
265
+ |----|----|----|
266
+ | name | `string` | 文件或文件夹名称 |
267
+ | type | `'file' \| 'directory'` | 类型,文件夹需要有 children 或 type 为 'directory' |
268
+ | children | `FileItem[]` | 子项列表(仅文件夹) |
269
+
270
+ ### MenuItem
271
+
272
+ | 属性 | 类型 | 说明 |
273
+ |----|----|----|
274
+ | label | `string` | 菜单项文本 |
275
+ | icon | `ReactNode` | 菜单项图标 |
276
+ | onClick | `(data: FileItem, key: string) => void` | 点击回调 |
277
+ | danger | `boolean` | 是否为危险操作(红色文本) |
278
+ | disabled | `(data: FileItem, key: string) => boolean` | 是否禁用的判断函数 |
279
+
280
+ ### 支持的文件类型图标
281
+
282
+ 组件内置以下文件类型的图标识别:
283
+
284
+ - **代码文件**: js, jsx, ts, tsx, json, css, scss, less, html, vue, py, java, go, rs
285
+ - **文档文件**: md, txt, pdf, doc, docx, xls, xlsx
286
+ - **图片文件**: png, jpg, jpeg, gif, svg, webp
287
+ - **视频文件**: mp4, avi, mov, wmv, mkv, webm, flv
288
+ - **音频文件**: mp3, wav, flac, aac, ogg, wma
289
+ - **压缩文件**: zip, rar, 7z, tar, gz
290
+ - **其他**: 默认文件图标
package/dist/index.css ADDED
@@ -0,0 +1,125 @@
1
+ ._qMhn9 {
2
+ font-size: 14px;
3
+ -webkit-user-select: none;
4
+ user-select: none;
5
+ }
6
+ ._qMhn9 .ant-tree {
7
+ background: transparent;
8
+ }
9
+ ._qMhn9 .ant-tree .ant-tree-treenode {
10
+ padding: 0;
11
+ width: 100%;
12
+ position: relative;
13
+ }
14
+ ._qMhn9 .ant-tree .ant-tree-treenode:before {
15
+ bottom: 0;
16
+ }
17
+ ._qMhn9 .ant-tree .ant-tree-treenode .ant-tree-node-content-wrapper {
18
+ padding: 0;
19
+ flex: 1;
20
+ min-width: 0;
21
+ }
22
+ ._qMhn9 .ant-tree .ant-tree-treenode .ant-tree-node-content-wrapper:hover {
23
+ background: transparent;
24
+ }
25
+ ._qMhn9 .ant-tree .ant-tree-treenode .ant-tree-switcher {
26
+ display: flex;
27
+ align-items: center;
28
+ justify-content: center;
29
+ width: 10px;
30
+ flex-shrink: 0;
31
+ }
32
+ ._qMhn9 .ant-tree .ant-tree-treenode .ant-tree-switcher:before {
33
+ display: none;
34
+ }
35
+ ._qMhn9 .ant-tree-list-holder-inner .ant-tree-treenode {
36
+ padding-left: 0;
37
+ }
38
+ ._qMhn9 .ant-tree-switcher-line-icon {
39
+ color: #d9d9d9;
40
+ }
41
+ ._qMhn9 .ant-tree-node-content-wrapper .tree-node {
42
+ border-left: 2px solid transparent;
43
+ transition: all 0.2s ease;
44
+ }
45
+ ._qMhn9 .ant-tree-treenode[style*="padding-left: 0px"] .tree-node, ._qMhn9 .ant-tree-treenode:first-child .tree-node {
46
+ background: rgba(0, 0, 0, 0.02);
47
+ }
48
+
49
+ ._t015K {
50
+ display: flex;
51
+ align-items: center;
52
+ padding: 4px 8px;
53
+ border-radius: 4px;
54
+ cursor: pointer;
55
+ transition: all 0.2s ease;
56
+ position: relative;
57
+ min-height: 28px;
58
+ width: 100%;
59
+ }
60
+ ._t015K:hover {
61
+ background: rgba(0, 0, 0, 0.04);
62
+ }
63
+ ._t015K:hover ._mLgqh {
64
+ margin-left: 4px;
65
+ opacity: 1;
66
+ visibility: visible;
67
+ }
68
+
69
+ ._dgomj {
70
+ display: flex;
71
+ align-items: center;
72
+ justify-content: center;
73
+ width: 16px;
74
+ height: 16px;
75
+ margin-right: 6px;
76
+ flex-shrink: 0;
77
+ }
78
+ ._dgomj .anticon {
79
+ font-size: 14px;
80
+ }
81
+
82
+ ._r6Ngq {
83
+ flex: 1;
84
+ overflow: hidden;
85
+ white-space: nowrap;
86
+ color: #333;
87
+ font-size: 13px;
88
+ display: flex;
89
+ min-width: 0;
90
+ }
91
+
92
+ ._st428 {
93
+ overflow: hidden;
94
+ text-overflow: ellipsis;
95
+ white-space: nowrap;
96
+ min-width: 0;
97
+ }
98
+
99
+ ._POSao {
100
+ flex-shrink: 0;
101
+ }
102
+
103
+ ._mLgqh {
104
+ opacity: 0;
105
+ visibility: hidden;
106
+ transition: all 0.2s ease;
107
+ margin-left: auto;
108
+ padding: 0 2px;
109
+ height: 20px;
110
+ width: 20px;
111
+ flex-shrink: 0;
112
+ color: #999;
113
+ }
114
+ ._mLgqh:hover {
115
+ color: #1677ff;
116
+ background: rgba(22, 119, 255, 0.1);
117
+ }
118
+
119
+ ._4zeDm {
120
+ padding: 40px 20px;
121
+ text-align: center;
122
+ color: #999;
123
+ font-size: 14px;
124
+ }
125
+ /*# sourceMappingURL=index.css.map */
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["style.module.scss"],"names":[],"mappings":"AAAA;EACE,eAAe;EACf,yBAAiB;UAAjB,iBAAiB;AACnB;AACA;EACE,uBAAuB;AACzB;AACA;EACE,UAAU;EACV,WAAW;EACX,kBAAkB;AACpB;AACA;EACE,SAAS;AACX;AACA;EACE,UAAU;EACV,OAAO;EACP,YAAY;AACd;AACA;EACE,uBAAuB;AACzB;AACA;EACE,aAAa;EACb,mBAAmB;EACnB,uBAAuB;EACvB,WAAW;EACX,cAAc;AAChB;AACA;EACE,aAAa;AACf;AACA;EACE,eAAe;AACjB;AACA;EACE,cAAc;AAChB;AACA;EACE,kCAAkC;EAClC,yBAAyB;AAC3B;AACA;EACE,+BAA+B;AACjC;;AAEA;EACE,aAAa;EACb,mBAAmB;EACnB,gBAAgB;EAChB,kBAAkB;EAClB,eAAe;EACf,yBAAyB;EACzB,kBAAkB;EAClB,gBAAgB;EAChB,WAAW;AACb;AACA;EACE,+BAA+B;AACjC;AACA;EACE,gBAAgB;EAChB,UAAU;EACV,mBAAmB;AACrB;;AAEA;EACE,aAAa;EACb,mBAAmB;EACnB,uBAAuB;EACvB,WAAW;EACX,YAAY;EACZ,iBAAiB;EACjB,cAAc;AAChB;AACA;EACE,eAAe;AACjB;;AAEA;EACE,OAAO;EACP,gBAAgB;EAChB,mBAAmB;EACnB,WAAW;EACX,eAAe;EACf,aAAa;EACb,YAAY;AACd;;AAEA;EACE,gBAAgB;EAChB,uBAAuB;EACvB,mBAAmB;EACnB,YAAY;AACd;;AAEA;EACE,cAAc;AAChB;;AAEA;EACE,UAAU;EACV,kBAAkB;EAClB,yBAAyB;EACzB,iBAAiB;EACjB,cAAc;EACd,YAAY;EACZ,WAAW;EACX,cAAc;EACd,WAAW;AACb;AACA;EACE,cAAc;EACd,mCAAmC;AACrC;;AAEA;EACE,kBAAkB;EAClB,kBAAkB;EAClB,WAAW;EACX,eAAe;AACjB","file":"index.css","sourcesContent":[".file-system-view {\n font-size: 14px;\n user-select: none;\n}\n.file-system-view :global .ant-tree {\n background: transparent;\n}\n.file-system-view :global .ant-tree .ant-tree-treenode {\n padding: 0;\n width: 100%;\n position: relative;\n}\n.file-system-view :global .ant-tree .ant-tree-treenode:before {\n bottom: 0;\n}\n.file-system-view :global .ant-tree .ant-tree-treenode .ant-tree-node-content-wrapper {\n padding: 0;\n flex: 1;\n min-width: 0;\n}\n.file-system-view :global .ant-tree .ant-tree-treenode .ant-tree-node-content-wrapper:hover {\n background: transparent;\n}\n.file-system-view :global .ant-tree .ant-tree-treenode .ant-tree-switcher {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 10px;\n flex-shrink: 0;\n}\n.file-system-view :global .ant-tree .ant-tree-treenode .ant-tree-switcher:before {\n display: none;\n}\n.file-system-view :global .ant-tree-list-holder-inner .ant-tree-treenode {\n padding-left: 0;\n}\n.file-system-view :global .ant-tree-switcher-line-icon {\n color: #d9d9d9;\n}\n.file-system-view :global .ant-tree-node-content-wrapper .tree-node {\n border-left: 2px solid transparent;\n transition: all 0.2s ease;\n}\n.file-system-view :global .ant-tree-treenode[style*=\"padding-left: 0px\"] .tree-node, .file-system-view :global .ant-tree-treenode:first-child .tree-node {\n background: rgba(0, 0, 0, 0.02);\n}\n\n.tree-node {\n display: flex;\n align-items: center;\n padding: 4px 8px;\n border-radius: 4px;\n cursor: pointer;\n transition: all 0.2s ease;\n position: relative;\n min-height: 28px;\n width: 100%;\n}\n.tree-node:hover {\n background: rgba(0, 0, 0, 0.04);\n}\n.tree-node:hover .action-btn {\n margin-left: 4px;\n opacity: 1;\n visibility: visible;\n}\n\n.node-icon {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 16px;\n height: 16px;\n margin-right: 6px;\n flex-shrink: 0;\n}\n.node-icon :global .anticon {\n font-size: 14px;\n}\n\n.node-title {\n flex: 1;\n overflow: hidden;\n white-space: nowrap;\n color: #333;\n font-size: 13px;\n display: flex;\n min-width: 0;\n}\n\n.node-name {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n min-width: 0;\n}\n\n.node-ext {\n flex-shrink: 0;\n}\n\n.action-btn {\n opacity: 0;\n visibility: hidden;\n transition: all 0.2s ease;\n margin-left: auto;\n padding: 0 2px;\n height: 20px;\n width: 20px;\n flex-shrink: 0;\n color: #999;\n}\n.action-btn:hover {\n color: #1677ff;\n background: rgba(22, 119, 255, 0.1);\n}\n\n.empty {\n padding: 40px 20px;\n text-align: center;\n color: #999;\n font-size: 14px;\n}"]}