@wavv/ui 2.3.5 → 2.3.7-alpha.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,12 +1,33 @@
|
|
|
1
1
|
import { type DropZoneProps, type FileTriggerProps } from 'react-aria-components';
|
|
2
2
|
import type { MarginPadding, WidthHeight } from './types';
|
|
3
|
+
/** Represents a file managed by the DropZone */
|
|
4
|
+
export type DropZoneFile = {
|
|
5
|
+
/** Unique identifier for the file */
|
|
6
|
+
id: string;
|
|
7
|
+
/** Name of the file */
|
|
8
|
+
name: string;
|
|
9
|
+
/** The File object */
|
|
10
|
+
file: File;
|
|
11
|
+
};
|
|
3
12
|
type Props = {
|
|
4
13
|
/** The label to display inside the drop zone */
|
|
5
14
|
label?: string;
|
|
6
|
-
/** Whether to show the file
|
|
7
|
-
|
|
15
|
+
/** Whether to show the file list below the drop zone with remove buttons */
|
|
16
|
+
showFileList?: boolean;
|
|
8
17
|
/** Whether the drop target is disabled. If true, the drop target will not accept any drops. */
|
|
9
18
|
disabled?: DropZoneProps['isDisabled'];
|
|
19
|
+
/** Whether the drop zone is in an invalid state */
|
|
20
|
+
invalid?: boolean;
|
|
21
|
+
/** Error message to display below the file names */
|
|
22
|
+
errorMessage?: string;
|
|
23
|
+
/** Array of supported file format names to display (e.g., ['PNG', 'JPG', 'PDF']) */
|
|
24
|
+
supportedFormats?: string[];
|
|
25
|
+
/** Maximum file size string to display (e.g., '25MB') */
|
|
26
|
+
maxFileSize?: string;
|
|
27
|
+
/** Callback when files are added. */
|
|
28
|
+
onFilesAdded?: (files: DropZoneFile[]) => void;
|
|
29
|
+
/** Callback when files are removed. */
|
|
30
|
+
onFilesRemoved?: (files: DropZoneFile[]) => void;
|
|
10
31
|
className?: DropZoneProps['className'];
|
|
11
32
|
style?: DropZoneProps['style'];
|
|
12
33
|
onDrop?: DropZoneProps['onDrop'];
|
|
@@ -22,7 +43,6 @@ type Props = {
|
|
|
22
43
|
allowsMultiple?: FileTriggerProps['allowsMultiple'];
|
|
23
44
|
defaultCamera?: FileTriggerProps['defaultCamera'];
|
|
24
45
|
acceptDirectory?: FileTriggerProps['acceptDirectory'];
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
declare const DropZone: ({ label, showFileNames, disabled, acceptedFileTypes, allowsMultiple, defaultCamera, acceptDirectory, onDrop, onSelect, ...rest }: Props) => import("react/jsx-runtime").JSX.Element;
|
|
46
|
+
} & MarginPadding & WidthHeight & Omit<DropZoneProps, 'isDisabled' | 'children'> & Omit<FileTriggerProps, 'children' | 'onSelect'>;
|
|
47
|
+
declare const DropZone: ({ label, showFileList, disabled, invalid, errorMessage, supportedFormats, maxFileSize, acceptedFileTypes, allowsMultiple, defaultCamera, acceptDirectory, onDrop, onFilesAdded, onFilesRemoved, width, margin, marginTop, marginRight, marginBottom, marginLeft, ...rest }: Props) => import("react/jsx-runtime").JSX.Element;
|
|
28
48
|
export default DropZone;
|
|
@@ -1,65 +1,194 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import styled from "@emotion/styled";
|
|
3
|
-
import { useState } from "react";
|
|
3
|
+
import { useId, useState } from "react";
|
|
4
4
|
import { DropZone, Text } from "react-aria-components";
|
|
5
5
|
import matchesFileTypes from "../utils/matchesFileTypes.js";
|
|
6
|
-
import
|
|
6
|
+
import Button from "./Button/index.js";
|
|
7
7
|
import FileTrigger from "./FileTrigger.js";
|
|
8
8
|
import { marginProps, paddingProps, widthHeightProps } from "./helpers/styledProps.js";
|
|
9
|
-
const DropZone_DropZone = ({ label = 'Drop here',
|
|
10
|
-
const [
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
9
|
+
const DropZone_DropZone = ({ label = 'Drop here', showFileList = true, disabled, invalid, errorMessage, supportedFormats, maxFileSize, acceptedFileTypes, allowsMultiple, defaultCamera, acceptDirectory, onDrop, onFilesAdded, onFilesRemoved, width, margin, marginTop, marginRight, marginBottom, marginLeft, ...rest })=>{
|
|
10
|
+
const [files, setFiles] = useState([]);
|
|
11
|
+
const [sizeValidationError, setSizeValidationError] = useState(null);
|
|
12
|
+
const instanceId = useId();
|
|
13
|
+
const hasFileCallbacks = !!onFilesAdded || !!onFilesRemoved;
|
|
14
|
+
const shouldManageFiles = showFileList || hasFileCallbacks;
|
|
15
|
+
const showFileTrigger = !!acceptedFileTypes || !!allowsMultiple || !!defaultCamera || !!acceptDirectory || hasFileCallbacks;
|
|
16
|
+
const generateFileId = (name, index)=>`${instanceId}-${name}-${Date.now()}-${index}`;
|
|
17
|
+
const parseFileSize = (sizeString)=>{
|
|
18
|
+
const match = sizeString.trim().match(/^(\d+(?:\.\d+)?)\s*(KB|MB|GB|TB|B)$/i);
|
|
19
|
+
if (!match) return null;
|
|
20
|
+
const value = parseFloat(match[1]);
|
|
21
|
+
const unit = match[2].toUpperCase();
|
|
22
|
+
const multipliers = {
|
|
23
|
+
B: 1,
|
|
24
|
+
KB: 1024,
|
|
25
|
+
MB: 1048576,
|
|
26
|
+
GB: 1073741824,
|
|
27
|
+
TB: 1099511627776
|
|
28
|
+
};
|
|
29
|
+
return value * (multipliers[unit] || 1);
|
|
30
|
+
};
|
|
31
|
+
const validateFileSizes = (fileList)=>{
|
|
32
|
+
if (!maxFileSize) return {
|
|
33
|
+
valid: fileList,
|
|
34
|
+
oversized: []
|
|
35
|
+
};
|
|
36
|
+
const maxBytes = parseFileSize(maxFileSize);
|
|
37
|
+
if (null === maxBytes) return {
|
|
38
|
+
valid: fileList,
|
|
39
|
+
oversized: []
|
|
40
|
+
};
|
|
41
|
+
const valid = [];
|
|
42
|
+
const oversized = [];
|
|
43
|
+
for (const file of fileList)if (file.size <= maxBytes) valid.push(file);
|
|
44
|
+
else oversized.push(file);
|
|
45
|
+
return {
|
|
46
|
+
valid,
|
|
47
|
+
oversized
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
const addFiles = async (newFileList)=>{
|
|
51
|
+
const { valid, oversized } = validateFileSizes(newFileList);
|
|
52
|
+
if (oversized.length > 0) {
|
|
53
|
+
const oversizedNames = oversized.map((file)=>file.name).join(', ');
|
|
54
|
+
setSizeValidationError(`Error: ${oversizedNames} ${1 === oversized.length ? 'exceeds' : 'exceed'} maximum file size of ${maxFileSize}`);
|
|
55
|
+
} else setSizeValidationError(null);
|
|
56
|
+
if (0 === valid.length) return;
|
|
57
|
+
const newEntries = valid.map((file, index)=>({
|
|
58
|
+
id: generateFileId(file.name, index),
|
|
59
|
+
name: file.name,
|
|
60
|
+
file
|
|
61
|
+
}));
|
|
62
|
+
const updatedFiles = allowsMultiple ? [
|
|
63
|
+
...files,
|
|
64
|
+
...newEntries
|
|
65
|
+
] : newEntries;
|
|
66
|
+
setFiles(updatedFiles);
|
|
67
|
+
onFilesAdded?.(newEntries);
|
|
68
|
+
};
|
|
69
|
+
const handleFileDrop = async (event)=>{
|
|
70
|
+
let fileItems = event.items.filter((file)=>'file' === file.kind);
|
|
71
|
+
if (acceptedFileTypes && acceptedFileTypes.length > 0) fileItems = fileItems.filter((file)=>matchesFileTypes(file, acceptedFileTypes));
|
|
72
|
+
if (!allowsMultiple && fileItems.length > 1) fileItems = fileItems.slice(0, 1);
|
|
73
|
+
if (shouldManageFiles) {
|
|
74
|
+
const filePromises = fileItems.map((item)=>{
|
|
75
|
+
if ('getFile' in item && 'function' == typeof item.getFile) return item.getFile();
|
|
76
|
+
return null;
|
|
77
|
+
});
|
|
78
|
+
const resolvedFiles = (await Promise.all(filePromises)).filter((f)=>null !== f);
|
|
79
|
+
await addFiles(resolvedFiles);
|
|
80
|
+
}
|
|
18
81
|
if (onDrop) onDrop(event);
|
|
19
82
|
};
|
|
20
|
-
const handleFileSelect = (selected)=>{
|
|
83
|
+
const handleFileSelect = async (selected)=>{
|
|
21
84
|
if (!selected) return;
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
85
|
+
const selectedFiles = Array.from(selected);
|
|
86
|
+
if (shouldManageFiles) await addFiles(selectedFiles);
|
|
87
|
+
};
|
|
88
|
+
const handleRemoveFile = (id)=>{
|
|
89
|
+
const removedFile = files.find((file)=>file.id === id);
|
|
90
|
+
const updatedFiles = files.filter((file)=>file.id !== id);
|
|
91
|
+
setFiles(updatedFiles);
|
|
92
|
+
if (removedFile) onFilesRemoved?.([
|
|
93
|
+
removedFile
|
|
94
|
+
]);
|
|
26
95
|
};
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
96
|
+
const containerProps = {
|
|
97
|
+
width,
|
|
98
|
+
margin,
|
|
99
|
+
marginTop,
|
|
100
|
+
marginRight,
|
|
101
|
+
marginBottom,
|
|
102
|
+
marginLeft
|
|
103
|
+
};
|
|
104
|
+
const showManagedFileList = showFileList && files.length > 0;
|
|
105
|
+
const showFileInfo = !!supportedFormats || !!maxFileSize;
|
|
106
|
+
return /*#__PURE__*/ jsxs(Container, {
|
|
107
|
+
...containerProps,
|
|
31
108
|
children: [
|
|
32
|
-
/*#__PURE__*/
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
children:
|
|
109
|
+
/*#__PURE__*/ jsxs(StyledDropZone, {
|
|
110
|
+
isDisabled: disabled,
|
|
111
|
+
invalid: invalid,
|
|
112
|
+
onDrop: handleFileDrop,
|
|
113
|
+
...rest,
|
|
114
|
+
children: [
|
|
115
|
+
/*#__PURE__*/ jsxs(TriggerContainer, {
|
|
116
|
+
children: [
|
|
117
|
+
showFileTrigger && /*#__PURE__*/ jsx(FileTrigger, {
|
|
118
|
+
acceptedFileTypes: acceptedFileTypes,
|
|
119
|
+
allowsMultiple: allowsMultiple,
|
|
120
|
+
defaultCamera: defaultCamera,
|
|
121
|
+
acceptDirectory: acceptDirectory,
|
|
122
|
+
onSelect: handleFileSelect
|
|
123
|
+
}),
|
|
124
|
+
/*#__PURE__*/ jsxs(StyledText, {
|
|
125
|
+
slot: "label",
|
|
126
|
+
children: [
|
|
127
|
+
showFileTrigger ? 'or ' : '',
|
|
128
|
+
" ",
|
|
129
|
+
showFileTrigger ? label.toLowerCase() : label
|
|
130
|
+
]
|
|
131
|
+
})
|
|
132
|
+
]
|
|
133
|
+
}),
|
|
134
|
+
(errorMessage || sizeValidationError) && /*#__PURE__*/ jsx(ErrorMessageText, {
|
|
135
|
+
children: errorMessage || sizeValidationError
|
|
136
|
+
})
|
|
137
|
+
]
|
|
38
138
|
}),
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
139
|
+
showFileInfo && /*#__PURE__*/ jsxs(FileInfo, {
|
|
140
|
+
children: [
|
|
141
|
+
/*#__PURE__*/ jsx(FileInfoLeft, {
|
|
142
|
+
children: supportedFormats && /*#__PURE__*/ jsxs("div", {
|
|
143
|
+
children: [
|
|
144
|
+
"Supported formats: ",
|
|
145
|
+
supportedFormats.join(', ')
|
|
146
|
+
]
|
|
147
|
+
})
|
|
148
|
+
}),
|
|
149
|
+
/*#__PURE__*/ jsx(FileInfoRight, {
|
|
150
|
+
children: maxFileSize && /*#__PURE__*/ jsxs("div", {
|
|
151
|
+
children: [
|
|
152
|
+
"Maximum file size: ",
|
|
153
|
+
maxFileSize
|
|
154
|
+
]
|
|
155
|
+
})
|
|
156
|
+
})
|
|
157
|
+
]
|
|
45
158
|
}),
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
159
|
+
showManagedFileList && /*#__PURE__*/ jsx(FileList, {
|
|
160
|
+
children: files.map((file)=>/*#__PURE__*/ jsxs(FileItem, {
|
|
161
|
+
children: [
|
|
162
|
+
/*#__PURE__*/ jsx(FileName, {
|
|
163
|
+
title: file.name,
|
|
164
|
+
children: file.name
|
|
165
|
+
}),
|
|
166
|
+
!disabled && /*#__PURE__*/ jsx(Button, {
|
|
167
|
+
icon: "close",
|
|
168
|
+
size: "small",
|
|
169
|
+
subtle: true,
|
|
170
|
+
secondary: true,
|
|
171
|
+
onClick: ()=>handleRemoveFile(file.id)
|
|
172
|
+
})
|
|
173
|
+
]
|
|
174
|
+
}, file.id))
|
|
51
175
|
})
|
|
52
176
|
]
|
|
53
177
|
});
|
|
54
178
|
};
|
|
55
|
-
const
|
|
179
|
+
const Container = styled.div({
|
|
180
|
+
display: 'flex',
|
|
181
|
+
flexDirection: 'column',
|
|
182
|
+
gap: 4
|
|
183
|
+
}, widthHeightProps, marginProps);
|
|
184
|
+
const StyledDropZone = styled(DropZone)(({ theme, invalid })=>({
|
|
56
185
|
display: 'flex',
|
|
57
186
|
flexDirection: 'column',
|
|
58
187
|
alignItems: 'center',
|
|
59
188
|
justifyContent: 'center',
|
|
60
189
|
gap: theme.size.sm,
|
|
61
190
|
padding: theme.size.md,
|
|
62
|
-
border: `1px dashed ${theme.scale4}`,
|
|
191
|
+
border: `1px dashed ${invalid ? theme.color.error : theme.scale4}`,
|
|
63
192
|
borderRadius: 8,
|
|
64
193
|
backgroundColor: 'transparent',
|
|
65
194
|
outline: 'none',
|
|
@@ -84,24 +213,55 @@ const StyledDropZone = styled(DropZone)(({ theme })=>({
|
|
|
84
213
|
}
|
|
85
214
|
}
|
|
86
215
|
}), widthHeightProps, marginProps, paddingProps);
|
|
216
|
+
const TriggerContainer = styled.div({
|
|
217
|
+
display: 'flex',
|
|
218
|
+
alignItems: 'center',
|
|
219
|
+
justifyContent: 'center',
|
|
220
|
+
gap: 8
|
|
221
|
+
});
|
|
87
222
|
const StyledText = styled(Text)(({ theme })=>({
|
|
88
223
|
fontSize: theme.font.size.md,
|
|
89
224
|
fontWeight: theme.font.weight.medium,
|
|
90
225
|
lineHeight: '14px',
|
|
91
226
|
textAlign: 'center'
|
|
92
227
|
}));
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
228
|
+
const ErrorMessageText = styled(StyledText)(({ theme })=>({
|
|
229
|
+
color: theme.color.error
|
|
230
|
+
}));
|
|
231
|
+
const FileList = styled.div(({ theme })=>({
|
|
232
|
+
display: 'flex',
|
|
233
|
+
flexDirection: 'column',
|
|
234
|
+
gap: theme.size.xs
|
|
97
235
|
}));
|
|
98
|
-
const
|
|
236
|
+
const FileItem = styled.div(({ theme })=>({
|
|
99
237
|
display: 'flex',
|
|
100
238
|
alignItems: 'center',
|
|
101
|
-
justifyContent: '
|
|
239
|
+
justifyContent: 'space-between',
|
|
240
|
+
gap: theme.size.xs,
|
|
241
|
+
padding: '4px 8px',
|
|
242
|
+
paddingRight: 4,
|
|
243
|
+
backgroundColor: theme.scale0,
|
|
244
|
+
borderRadius: 4
|
|
245
|
+
}));
|
|
246
|
+
const FileName = styled.div(({ theme })=>({
|
|
247
|
+
fontSize: theme.font.size.sm,
|
|
248
|
+
lineHeight: '14px',
|
|
249
|
+
flex: 1,
|
|
250
|
+
overflow: 'hidden',
|
|
251
|
+
textOverflow: 'ellipsis',
|
|
252
|
+
whiteSpace: 'nowrap'
|
|
253
|
+
}));
|
|
254
|
+
const FileInfo = styled.div(({ theme })=>({
|
|
255
|
+
display: 'flex',
|
|
256
|
+
alignItems: 'center',
|
|
257
|
+
justifyContent: 'space-between',
|
|
258
|
+
gap: 16,
|
|
102
259
|
fontSize: theme.font.size.sm,
|
|
103
|
-
color: theme.scale6
|
|
104
|
-
width: '100%'
|
|
260
|
+
color: theme.scale6
|
|
105
261
|
}));
|
|
262
|
+
const FileInfoLeft = styled.div({});
|
|
263
|
+
const FileInfoRight = styled.div({
|
|
264
|
+
textAlign: 'right'
|
|
265
|
+
});
|
|
106
266
|
const components_DropZone = DropZone_DropZone;
|
|
107
267
|
export { components_DropZone as default };
|
|
@@ -4,9 +4,9 @@ import Button from "./Button/index.js";
|
|
|
4
4
|
const FileTrigger_FileTrigger = (props)=>/*#__PURE__*/ jsx(FileTrigger, {
|
|
5
5
|
...props,
|
|
6
6
|
children: /*#__PURE__*/ jsx(Button, {
|
|
7
|
-
small: true,
|
|
8
7
|
outline: true,
|
|
9
|
-
|
|
8
|
+
icon: "upload",
|
|
9
|
+
children: "Upload"
|
|
10
10
|
})
|
|
11
11
|
});
|
|
12
12
|
const components_FileTrigger = FileTrigger_FileTrigger;
|
package/build/index.d.ts
CHANGED
|
@@ -76,6 +76,7 @@ export type { ThemeOption } from './theme';
|
|
|
76
76
|
export type { EditorValue } from './components/Editor';
|
|
77
77
|
export type { Selection } from 'react-aria-components';
|
|
78
78
|
export type { DropEvent, DropItem } from 'react-aria';
|
|
79
|
+
export type { DropZoneFile } from './components/DropZone';
|
|
79
80
|
export type { DotType } from './components/Dot';
|
|
80
81
|
export { default as useConfirm } from './hooks/useConfirm';
|
|
81
82
|
export { default as useElementObserver } from './hooks/useElementObserver';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wavv/ui",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.7-alpha.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
"@babel/core": "^7.28.5",
|
|
62
62
|
"@babel/preset-env": "^7.28.5",
|
|
63
63
|
"@babel/preset-typescript": "^7.28.5",
|
|
64
|
-
"@biomejs/biome": "2.3.
|
|
64
|
+
"@biomejs/biome": "2.3.8",
|
|
65
65
|
"@chromatic-com/storybook": "^4.1.3",
|
|
66
66
|
"@emotion/babel-plugin": "^11.13.5",
|
|
67
67
|
"@emotion/react": "^11.14.0",
|
|
@@ -69,10 +69,10 @@
|
|
|
69
69
|
"@rsbuild/plugin-react": "^1.4.2",
|
|
70
70
|
"@rsbuild/plugin-svgr": "^1.2.2",
|
|
71
71
|
"@rslib/core": "^0.18.2",
|
|
72
|
-
"@storybook/addon-docs": "^
|
|
73
|
-
"@storybook/addon-links": "^
|
|
74
|
-
"@storybook/addon-themes": "^
|
|
75
|
-
"@storybook/test-runner": "^0.
|
|
72
|
+
"@storybook/addon-docs": "^10.1.2",
|
|
73
|
+
"@storybook/addon-links": "^10.1.2",
|
|
74
|
+
"@storybook/addon-themes": "^10.1.2",
|
|
75
|
+
"@storybook/test-runner": "^0.24.2",
|
|
76
76
|
"@svgr/core": "^8.1.0",
|
|
77
77
|
"@svgr/plugin-jsx": "^8.1.0",
|
|
78
78
|
"@svgr/plugin-prettier": "^8.1.0",
|
|
@@ -104,8 +104,8 @@
|
|
|
104
104
|
"react-dom": "^19.2.0",
|
|
105
105
|
"replace": "^1.2.2",
|
|
106
106
|
"signale": "^1.4.0",
|
|
107
|
-
"storybook": "^
|
|
108
|
-
"storybook-react-rsbuild": "^
|
|
107
|
+
"storybook": "^10.1.2",
|
|
108
|
+
"storybook-react-rsbuild": "^3.0.0",
|
|
109
109
|
"tsc-files": "^1.1.4",
|
|
110
110
|
"tslib": "^2.8.1",
|
|
111
111
|
"tsx": "^4.21.0",
|