@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,127 @@
1
+ import { Icon } from '@iconify/react';
2
+ import { Breadcrumb } from './Breadcrumb';
3
+ import type { BreadcrumbItem } from '../types';
4
+ import './Toolbar.css';
5
+
6
+ interface ToolbarProps {
7
+ canGoBack?: boolean;
8
+ canGoForward?: boolean;
9
+ breadcrumbs?: BreadcrumbItem[];
10
+ viewMode?: 'grid' | 'list';
11
+ searchQuery?: string;
12
+ showSearch?: boolean;
13
+ showViewToggle?: boolean;
14
+ draggable?: boolean;
15
+ onBack?: () => void;
16
+ onForward?: () => void;
17
+ onBreadcrumbNavigate?: (item: BreadcrumbItem) => void;
18
+ onViewModeChange?: (mode: 'grid' | 'list') => void;
19
+ onSearchQueryChange?: (query: string) => void;
20
+ children?: React.ReactNode;
21
+ breadcrumbSlot?: React.ReactNode;
22
+ actionsSlot?: React.ReactNode;
23
+ }
24
+
25
+ export function Toolbar({
26
+ canGoBack = false,
27
+ canGoForward = false,
28
+ breadcrumbs = [],
29
+ viewMode = 'grid',
30
+ searchQuery = '',
31
+ showSearch = false,
32
+ showViewToggle = true,
33
+ draggable = false,
34
+ onBack,
35
+ onForward,
36
+ onBreadcrumbNavigate,
37
+ onViewModeChange,
38
+ onSearchQueryChange,
39
+ children,
40
+ breadcrumbSlot,
41
+ actionsSlot,
42
+ }: ToolbarProps) {
43
+ return (
44
+ <div
45
+ className={`file-toolbar ${draggable ? 'file-toolbar--draggable' : ''}`}
46
+ >
47
+ {/* 导航按钮 */}
48
+ <div className="file-toolbar-nav">
49
+ <button
50
+ className="file-toolbar-button"
51
+ onClick={onBack}
52
+ disabled={!canGoBack}
53
+ title="后退"
54
+ >
55
+ <Icon icon="lucide:chevron-left" width={18} height={18} />
56
+ </button>
57
+ <button
58
+ className="file-toolbar-button"
59
+ onClick={onForward}
60
+ disabled={!canGoForward}
61
+ title="前进"
62
+ >
63
+ <Icon icon="lucide:chevron-right" width={18} height={18} />
64
+ </button>
65
+ </div>
66
+
67
+ {/* 面包屑插槽 */}
68
+ <div className="file-toolbar-breadcrumb">
69
+ {breadcrumbSlot || (
70
+ breadcrumbs.length > 0 && (
71
+ <Breadcrumb
72
+ items={breadcrumbs}
73
+ onNavigate={onBreadcrumbNavigate}
74
+ />
75
+ )
76
+ )}
77
+ </div>
78
+
79
+ {/* 自定义内容插槽 */}
80
+ {children && <div className="file-toolbar-custom">{children}</div>}
81
+
82
+ {/* 操作区 */}
83
+ <div className="file-toolbar-actions">
84
+ {/* 搜索框 */}
85
+ {showSearch && (
86
+ <div className="file-toolbar-search">
87
+ <Icon icon="lucide:search" width={16} height={16} className="file-toolbar-search-icon" />
88
+ <input
89
+ type="text"
90
+ value={searchQuery}
91
+ onChange={(e) => onSearchQueryChange?.(e.target.value)}
92
+ placeholder="搜索"
93
+ className="file-toolbar-search-input"
94
+ />
95
+ </div>
96
+ )}
97
+
98
+ {/* 视图切换 */}
99
+ {showViewToggle && (
100
+ <div className="file-toolbar-view-toggle">
101
+ <button
102
+ onClick={() => onViewModeChange?.('grid')}
103
+ className={`file-toolbar-button ${
104
+ viewMode === 'grid' ? 'file-toolbar-button--active' : ''
105
+ }`}
106
+ title="网格视图"
107
+ >
108
+ <Icon icon="lucide:layout-grid" width={18} height={18} />
109
+ </button>
110
+ <button
111
+ onClick={() => onViewModeChange?.('list')}
112
+ className={`file-toolbar-button ${
113
+ viewMode === 'list' ? 'file-toolbar-button--active' : ''
114
+ }`}
115
+ title="列表视图"
116
+ >
117
+ <Icon icon="lucide:list" width={18} height={18} />
118
+ </button>
119
+ </div>
120
+ )}
121
+
122
+ {/* 额外操作插槽 */}
123
+ {actionsSlot}
124
+ </div>
125
+ </div>
126
+ );
127
+ }
@@ -0,0 +1,246 @@
1
+ .window-overlay {
2
+ position: fixed;
3
+ inset: 0;
4
+ z-index: 100;
5
+ background: rgba(0, 0, 0, 0.3);
6
+ animation: fade-in 200ms ease-out;
7
+ display: flex;
8
+ align-items: center;
9
+ justify-content: center;
10
+ }
11
+
12
+ .window-container {
13
+ position: absolute;
14
+ display: flex;
15
+ flex-direction: column;
16
+ background: rgba(255, 255, 255, 0.95);
17
+ backdrop-filter: blur(24px);
18
+ border: 1px solid rgb(229, 231, 233);
19
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
20
+ border-radius: 0.5rem;
21
+ overflow: hidden;
22
+ animation: window-fade-in 200ms ease-out;
23
+ }
24
+
25
+ .window-title-bar {
26
+ height: 2.5rem;
27
+ display: flex;
28
+ align-items: center;
29
+ justify-content: space-between;
30
+ padding: 0 0.75rem;
31
+ border-bottom: 1px solid rgb(229, 231, 233);
32
+ flex-shrink: 0;
33
+ user-select: none;
34
+ background: rgba(249, 250, 251, 0.8);
35
+ z-index: 20;
36
+ cursor: grab;
37
+ }
38
+
39
+ .window-title-bar:active {
40
+ cursor: grabbing;
41
+ }
42
+
43
+ .window-controls {
44
+ display: flex;
45
+ align-items: center;
46
+ gap: 0.5rem;
47
+ }
48
+
49
+ .window-control-button {
50
+ width: 0.75rem;
51
+ height: 0.75rem;
52
+ border-radius: 50%;
53
+ display: flex;
54
+ align-items: center;
55
+ justify-content: center;
56
+ border: 1px solid;
57
+ box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
58
+ outline: none;
59
+ cursor: pointer;
60
+ padding: 0;
61
+ background: transparent;
62
+ flex-shrink: 0;
63
+ position: relative;
64
+ }
65
+
66
+ .window-control-button svg {
67
+ width: 7px !important;
68
+ height: 7px !important;
69
+ display: block;
70
+ flex-shrink: 0;
71
+ }
72
+
73
+ .window-control-button .window-control-icon {
74
+ width: 7px !important;
75
+ height: 7px !important;
76
+ min-width: 7px;
77
+ min-height: 7px;
78
+ }
79
+
80
+ .window-control-button--close {
81
+ background-color: rgb(255, 95, 87);
82
+ border-color: rgb(224, 68, 62);
83
+ }
84
+
85
+ .window-control-button--minimize {
86
+ background-color: rgb(254, 188, 46);
87
+ border-color: rgb(216, 158, 36);
88
+ }
89
+
90
+ .window-control-button--maximize {
91
+ background-color: rgb(40, 200, 64);
92
+ border-color: rgb(26, 171, 41);
93
+ }
94
+
95
+ .window-control-icon {
96
+ color: rgba(0, 0, 0, 0.5);
97
+ opacity: 0;
98
+ transition: opacity 200ms;
99
+ width: 7px;
100
+ height: 7px;
101
+ flex-shrink: 0;
102
+ }
103
+
104
+ .window-controls:hover .window-control-icon {
105
+ opacity: 1;
106
+ }
107
+
108
+ .window-control-button--close:hover {
109
+ background-color: rgba(255, 95, 87, 0.8);
110
+ }
111
+
112
+ .window-control-button--minimize:hover {
113
+ background-color: rgba(254, 188, 46, 0.8);
114
+ }
115
+
116
+ .window-control-button--maximize:hover {
117
+ background-color: rgba(40, 200, 64, 0.8);
118
+ }
119
+
120
+ .window-title-info {
121
+ display: flex;
122
+ flex-direction: column;
123
+ align-items: center;
124
+ justify-content: center;
125
+ flex: 1;
126
+ margin: 0 1rem;
127
+ overflow: hidden;
128
+ pointer-events: none;
129
+ }
130
+
131
+ .window-title-text {
132
+ font-weight: 500;
133
+ font-size: 0.75rem;
134
+ color: rgb(75, 85, 99);
135
+ overflow: hidden;
136
+ text-overflow: ellipsis;
137
+ white-space: nowrap;
138
+ width: 100%;
139
+ text-align: center;
140
+ }
141
+
142
+ .window-title-actions {
143
+ display: flex;
144
+ align-items: center;
145
+ justify-content: flex-end;
146
+ min-width: 4rem;
147
+ flex-shrink: 0;
148
+ }
149
+
150
+ .window-content {
151
+ flex: 1;
152
+ display: flex;
153
+ flex-direction: column;
154
+ overflow: hidden;
155
+ position: relative;
156
+ width: 100%;
157
+ height: 100%;
158
+ }
159
+
160
+ /* Resize Handles */
161
+ .window-resize-handle {
162
+ position: absolute;
163
+ background: transparent;
164
+ z-index: 30;
165
+ }
166
+
167
+ .window-resize-handle--n {
168
+ top: 0;
169
+ left: 0;
170
+ right: 0;
171
+ height: 4px;
172
+ cursor: n-resize;
173
+ }
174
+
175
+ .window-resize-handle--s {
176
+ bottom: 0;
177
+ left: 0;
178
+ right: 0;
179
+ height: 4px;
180
+ cursor: s-resize;
181
+ }
182
+
183
+ .window-resize-handle--e {
184
+ top: 0;
185
+ right: 0;
186
+ bottom: 0;
187
+ width: 4px;
188
+ cursor: e-resize;
189
+ }
190
+
191
+ .window-resize-handle--w {
192
+ top: 0;
193
+ left: 0;
194
+ bottom: 0;
195
+ width: 4px;
196
+ cursor: w-resize;
197
+ }
198
+
199
+ .window-resize-handle--ne {
200
+ top: 0;
201
+ right: 0;
202
+ width: 8px;
203
+ height: 8px;
204
+ cursor: ne-resize;
205
+ }
206
+
207
+ .window-resize-handle--nw {
208
+ top: 0;
209
+ left: 0;
210
+ width: 8px;
211
+ height: 8px;
212
+ cursor: nw-resize;
213
+ }
214
+
215
+ .window-resize-handle--se {
216
+ bottom: 0;
217
+ right: 0;
218
+ width: 8px;
219
+ height: 8px;
220
+ cursor: se-resize;
221
+ }
222
+
223
+ .window-resize-handle--sw {
224
+ bottom: 0;
225
+ left: 0;
226
+ width: 8px;
227
+ height: 8px;
228
+ cursor: sw-resize;
229
+ }
230
+
231
+ @keyframes fade-in {
232
+ from { opacity: 0; }
233
+ to { opacity: 1; }
234
+ }
235
+
236
+ @keyframes window-fade-in {
237
+ from {
238
+ opacity: 0;
239
+ transform: translate(-50%, -50%) scale(0.95);
240
+ }
241
+ to {
242
+ opacity: 1;
243
+ transform: translate(-50%, -50%) scale(1);
244
+ }
245
+ }
246
+
@@ -0,0 +1,335 @@
1
+ import { createPortal } from 'react-dom';
2
+ import { useMemo, useRef, useEffect, useState } from 'react';
3
+ import { Icon } from '@iconify/react';
4
+ import { useWindowDrag } from '../hooks/useWindowDrag';
5
+ import { useWindowResize } from '../hooks/useWindowResize';
6
+ import './Window.css';
7
+
8
+ interface WindowProps {
9
+ title?: string;
10
+ showTitleBar?: boolean;
11
+ showMinimize?: boolean;
12
+ showMaximize?: boolean;
13
+ draggable?: boolean;
14
+ resizable?: boolean;
15
+ width?: string | number;
16
+ height?: string | number;
17
+ minWidth?: string | number;
18
+ minHeight?: string | number;
19
+ maxWidth?: string | number;
20
+ maxHeight?: string | number;
21
+ closeOnBackdrop?: boolean;
22
+ fitContent?: boolean;
23
+ onClose?: () => void;
24
+ onMinimize?: () => void;
25
+ onMaximize?: () => void;
26
+ children?: React.ReactNode;
27
+ titleSlot?: React.ReactNode;
28
+ actionsSlot?: React.ReactNode;
29
+ }
30
+
31
+ export function Window({
32
+ title,
33
+ showTitleBar = true,
34
+ showMinimize = false,
35
+ showMaximize = false,
36
+ draggable = true,
37
+ resizable = true,
38
+ closeOnBackdrop = true,
39
+ width = 'auto',
40
+ height = 'auto',
41
+ minWidth = 300,
42
+ minHeight = 200,
43
+ maxWidth = '80vw',
44
+ maxHeight = '80vh',
45
+ fitContent = false,
46
+ onClose,
47
+ onMinimize,
48
+ onMaximize,
49
+ children,
50
+ titleSlot,
51
+ actionsSlot,
52
+ }: WindowProps) {
53
+ const windowContainerRef = useRef<HTMLDivElement>(null);
54
+ const windowDrag = draggable ? useWindowDrag() : null;
55
+
56
+ /**
57
+ * 是否使用自适应内容模式
58
+ */
59
+ const isAutoSize = useMemo(() => {
60
+ return (
61
+ fitContent ||
62
+ width === 'auto' ||
63
+ width === 'fit-content' ||
64
+ height === 'auto' ||
65
+ height === 'fit-content'
66
+ );
67
+ }, [fitContent, width, height]);
68
+
69
+ /**
70
+ * 计算初始尺寸
71
+ */
72
+ const getInitialSize = () => {
73
+ if (isAutoSize) {
74
+ return { initialWidth: 0, initialHeight: 0 };
75
+ }
76
+
77
+ const defaultWidth = 600;
78
+ const defaultHeight = 500;
79
+
80
+ let initialWidth = defaultWidth;
81
+ let initialHeight = defaultHeight;
82
+
83
+ if (width !== 'auto' && width !== 'fit-content') {
84
+ initialWidth =
85
+ typeof width === 'number' ? width : parseInt(width as string);
86
+ }
87
+ if (height !== 'auto' && height !== 'fit-content') {
88
+ initialHeight =
89
+ typeof height === 'number' ? height : parseInt(height as string);
90
+ }
91
+
92
+ return { initialWidth, initialHeight };
93
+ };
94
+
95
+ /**
96
+ * 解析尺寸限制
97
+ */
98
+ const parseSize = (size: string | number, defaultPx: number): number => {
99
+ if (typeof size === 'number') return size;
100
+ if (typeof size === 'string') {
101
+ if (size.endsWith('px')) return parseInt(size);
102
+ if (size.endsWith('vw'))
103
+ return (parseInt(size) / 100) * window.innerWidth;
104
+ if (size.endsWith('vh'))
105
+ return (parseInt(size) / 100) * window.innerHeight;
106
+ }
107
+ return defaultPx;
108
+ };
109
+
110
+ const { initialWidth, initialHeight } = getInitialSize();
111
+ const minW = parseSize(minWidth, 500);
112
+ const minH = parseSize(minHeight, 350);
113
+ const maxW = parseSize(maxWidth, window.innerWidth * 0.8);
114
+ const maxH = parseSize(maxHeight, window.innerHeight * 0.8);
115
+
116
+ const windowResize = resizable
117
+ ? useWindowResize(initialWidth, initialHeight, minW, minH, maxW, maxH)
118
+ : null;
119
+
120
+ const windowStyle = useMemo(() => {
121
+ const baseStyle: React.CSSProperties = {
122
+ left: '50%',
123
+ top: '50%',
124
+ };
125
+
126
+ let translateX = '-50%';
127
+ let translateY = '-50%';
128
+
129
+ if (draggable && windowDrag) {
130
+ translateX = `calc(-50% + ${windowDrag.position.x}px)`;
131
+ translateY = `calc(-50% + ${windowDrag.position.y}px)`;
132
+ }
133
+
134
+ baseStyle.transform = `translate(${translateX}, ${translateY})`;
135
+ baseStyle.transformOrigin = 'center center';
136
+
137
+ if (isAutoSize) {
138
+ if (resizable && windowResize && windowResize.width > 0) {
139
+ baseStyle.width = `${windowResize.width}px`;
140
+ baseStyle.height = `${windowResize.height}px`;
141
+ }
142
+ } else {
143
+ if (resizable && windowResize) {
144
+ baseStyle.width = `${windowResize.width}px`;
145
+ baseStyle.height = `${windowResize.height}px`;
146
+ } else {
147
+ if (width !== 'auto' && width !== 'fit-content') {
148
+ baseStyle.width =
149
+ typeof width === 'number' ? `${width}px` : (width as string);
150
+ }
151
+ if (height !== 'auto' && height !== 'fit-content') {
152
+ baseStyle.height =
153
+ typeof height === 'number' ? `${height}px` : (height as string);
154
+ }
155
+ }
156
+ }
157
+
158
+ if (minWidth) {
159
+ baseStyle.minWidth =
160
+ typeof minWidth === 'number' ? `${minWidth}px` : (minWidth as string);
161
+ }
162
+ if (minHeight) {
163
+ baseStyle.minHeight =
164
+ typeof minHeight === 'number'
165
+ ? `${minHeight}px`
166
+ : (minHeight as string);
167
+ }
168
+ if (maxWidth) {
169
+ baseStyle.maxWidth =
170
+ typeof maxWidth === 'number' ? `${maxWidth}px` : (maxWidth as string);
171
+ }
172
+ if (maxHeight) {
173
+ baseStyle.maxHeight =
174
+ typeof maxHeight === 'number'
175
+ ? `${maxHeight}px`
176
+ : (maxHeight as string);
177
+ }
178
+
179
+ return baseStyle;
180
+ }, [
181
+ draggable,
182
+ windowDrag,
183
+ isAutoSize,
184
+ resizable,
185
+ windowResize,
186
+ width,
187
+ height,
188
+ minWidth,
189
+ minHeight,
190
+ maxWidth,
191
+ maxHeight,
192
+ ]);
193
+
194
+ const handleBackdropClick = (e: React.MouseEvent) => {
195
+ if (closeOnBackdrop && e.target === e.currentTarget) {
196
+ onClose?.();
197
+ }
198
+ };
199
+
200
+ const handleDragStart = (e: React.MouseEvent) => {
201
+ if (draggable && windowDrag) {
202
+ windowDrag.startDrag(e);
203
+ }
204
+ };
205
+
206
+ const handleResizeStart = (
207
+ e: React.MouseEvent,
208
+ direction: 'n' | 's' | 'e' | 'w' | 'ne' | 'nw' | 'se' | 'sw'
209
+ ) => {
210
+ if (!resizable || !windowResize || !windowContainerRef.current) return;
211
+
212
+ const rect = windowContainerRef.current.getBoundingClientRect();
213
+ const currentWidth = rect.width;
214
+ const currentHeight = rect.height;
215
+
216
+ windowResize.startResize(e, direction, currentWidth, currentHeight);
217
+ };
218
+
219
+ /**
220
+ * ESC 键关闭
221
+ */
222
+ useEffect(() => {
223
+ const handleEsc = (e: KeyboardEvent) => {
224
+ if (e.key === 'Escape') {
225
+ e.preventDefault();
226
+ onClose?.();
227
+ }
228
+ };
229
+ window.addEventListener('keydown', handleEsc);
230
+ return () => {
231
+ window.removeEventListener('keydown', handleEsc);
232
+ };
233
+ }, [onClose]);
234
+
235
+ return createPortal(
236
+ <div className="window-overlay" onClick={handleBackdropClick}>
237
+ <div
238
+ ref={windowContainerRef}
239
+ className="window-container"
240
+ style={windowStyle}
241
+ onClick={(e) => e.stopPropagation()}
242
+ >
243
+ {/* Title Bar */}
244
+ {showTitleBar && (
245
+ <div
246
+ className="window-title-bar draggable-area"
247
+ onMouseDown={handleDragStart}
248
+ >
249
+ <div className="window-controls">
250
+ <button
251
+ onClick={(e) => {
252
+ e.stopPropagation();
253
+ onClose?.();
254
+ }}
255
+ className="window-control-button window-control-button--close"
256
+ >
257
+ <Icon icon="lucide:x" width={7} height={7} className="window-control-icon" />
258
+ </button>
259
+ {showMinimize && (
260
+ <button
261
+ onClick={(e) => {
262
+ e.stopPropagation();
263
+ onMinimize?.();
264
+ }}
265
+ className="window-control-button window-control-button--minimize"
266
+ >
267
+ <Icon icon="lucide:minus" width={7} height={7} className="window-control-icon" />
268
+ </button>
269
+ )}
270
+ {showMaximize && (
271
+ <button
272
+ onClick={(e) => {
273
+ e.stopPropagation();
274
+ onMaximize?.();
275
+ }}
276
+ className="window-control-button window-control-button--maximize"
277
+ >
278
+ <Icon icon="lucide:maximize-2" width={7} height={7} className="window-control-icon" />
279
+ </button>
280
+ )}
281
+ </div>
282
+
283
+ <div className="window-title-info">
284
+ {titleSlot || <span className="window-title-text">{title}</span>}
285
+ </div>
286
+
287
+ <div className="window-title-actions">{actionsSlot}</div>
288
+ </div>
289
+ )}
290
+
291
+ {/* Content */}
292
+ <div className="window-content">{children}</div>
293
+
294
+ {/* Resize Handles */}
295
+ {resizable && (
296
+ <>
297
+ <div
298
+ className="window-resize-handle window-resize-handle--n"
299
+ onMouseDown={(e) => handleResizeStart(e, 'n')}
300
+ ></div>
301
+ <div
302
+ className="window-resize-handle window-resize-handle--s"
303
+ onMouseDown={(e) => handleResizeStart(e, 's')}
304
+ ></div>
305
+ <div
306
+ className="window-resize-handle window-resize-handle--e"
307
+ onMouseDown={(e) => handleResizeStart(e, 'e')}
308
+ ></div>
309
+ <div
310
+ className="window-resize-handle window-resize-handle--w"
311
+ onMouseDown={(e) => handleResizeStart(e, 'w')}
312
+ ></div>
313
+ <div
314
+ className="window-resize-handle window-resize-handle--ne"
315
+ onMouseDown={(e) => handleResizeStart(e, 'ne')}
316
+ ></div>
317
+ <div
318
+ className="window-resize-handle window-resize-handle--nw"
319
+ onMouseDown={(e) => handleResizeStart(e, 'nw')}
320
+ ></div>
321
+ <div
322
+ className="window-resize-handle window-resize-handle--se"
323
+ onMouseDown={(e) => handleResizeStart(e, 'se')}
324
+ ></div>
325
+ <div
326
+ className="window-resize-handle window-resize-handle--sw"
327
+ onMouseDown={(e) => handleResizeStart(e, 'sw')}
328
+ ></div>
329
+ </>
330
+ )}
331
+ </div>
332
+ </div>,
333
+ document.body
334
+ );
335
+ }