@availity/mui-file-selector 1.6.5 → 1.7.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/CHANGELOG.md +20 -0
- package/README.md +84 -1
- package/dist/index.d.mts +81 -1
- package/dist/index.d.ts +81 -1
- package/dist/index.js +1684 -137
- package/dist/index.mjs +1699 -135
- package/package.json +6 -7
- package/src/index.ts +3 -0
- package/src/lib/Dropzone.tsx +4 -4
- package/src/lib/Dropzone2.test.tsx +28 -0
- package/src/lib/Dropzone2.tsx +244 -0
- package/src/lib/FileList2.test.tsx +73 -0
- package/src/lib/FileList2.tsx +118 -0
- package/src/lib/FileSelector.tsx +1 -1
- package/src/lib/FileSelector2.stories.tsx +253 -0
- package/src/lib/FileSelector2.test.tsx +23 -0
- package/src/lib/FileSelector2.tsx +209 -0
- package/src/lib/util.test.ts +16 -0
- package/src/lib/util.ts +15 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@availity/mui-file-selector",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "Availity MUI file-selector Component - part of the @availity/element design system",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -40,21 +40,20 @@
|
|
|
40
40
|
"publish:canary": "yarn npm publish --access public --tag canary"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@availity/api-axios": "^
|
|
43
|
+
"@availity/api-axios": "^11.0.0",
|
|
44
44
|
"@availity/mui-alert": "^1.0.6",
|
|
45
45
|
"@availity/mui-button": "^1.1.4",
|
|
46
46
|
"@availity/mui-divider": "^1.0.2",
|
|
47
|
-
"@availity/mui-form-utils": "^1.3.
|
|
47
|
+
"@availity/mui-form-utils": "^1.3.2",
|
|
48
48
|
"@availity/mui-icon": "^1.1.0",
|
|
49
49
|
"@availity/mui-layout": "^1.0.2",
|
|
50
|
-
"@availity/mui-list": "^1.0.
|
|
50
|
+
"@availity/mui-list": "^1.0.7",
|
|
51
51
|
"@availity/mui-progress": "^1.0.3",
|
|
52
52
|
"@availity/mui-typography": "^1.0.2",
|
|
53
|
-
"@availity/upload-core": "^
|
|
54
|
-
"@tanstack/react-query": "^4.36.1",
|
|
53
|
+
"@availity/upload-core": "^8.0.0",
|
|
55
54
|
"react-dropzone": "^11.7.1",
|
|
56
55
|
"react-hook-form": "^7.55.0",
|
|
57
|
-
"tus-js-client": "4.
|
|
56
|
+
"tus-js-client": "4.3.1",
|
|
58
57
|
"uuid": "^9.0.1"
|
|
59
58
|
},
|
|
60
59
|
"devDependencies": {
|
package/src/index.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
export * from './lib/Dropzone';
|
|
2
|
+
export * from './lib/Dropzone2';
|
|
2
3
|
export * from './lib/ErrorAlert';
|
|
3
4
|
export * from './lib/FileList';
|
|
5
|
+
export * from './lib/FileList2';
|
|
4
6
|
export * from './lib/FilePickerBtn';
|
|
5
7
|
export * from './lib/FileSelector';
|
|
8
|
+
export * from './lib/FileSelector2';
|
|
6
9
|
export * from './lib/FileTypesMessage';
|
|
7
10
|
export * from './lib/HeaderMessage';
|
|
8
11
|
export * from './lib/UploadProgressBar';
|
package/src/lib/Dropzone.tsx
CHANGED
|
@@ -12,7 +12,7 @@ import { Typography } from '@availity/mui-typography';
|
|
|
12
12
|
|
|
13
13
|
import { FilePickerBtn } from './FilePickerBtn';
|
|
14
14
|
|
|
15
|
-
const outerBoxStyles = {
|
|
15
|
+
export const outerBoxStyles = {
|
|
16
16
|
backgroundColor: 'background.secondary',
|
|
17
17
|
border: '1px dotted',
|
|
18
18
|
borderColor: 'secondary.light',
|
|
@@ -24,13 +24,13 @@ const outerBoxStyles = {
|
|
|
24
24
|
},
|
|
25
25
|
};
|
|
26
26
|
|
|
27
|
-
const innerBoxStyles = {
|
|
27
|
+
export const innerBoxStyles = {
|
|
28
28
|
width: '100%',
|
|
29
29
|
height: '100%',
|
|
30
30
|
};
|
|
31
31
|
|
|
32
32
|
/** Counter for creating unique id */
|
|
33
|
-
const createCounter = () => {
|
|
33
|
+
export const createCounter = () => {
|
|
34
34
|
let id = 0;
|
|
35
35
|
const increment = () => (id += 1);
|
|
36
36
|
return {
|
|
@@ -96,7 +96,7 @@ export type DropzoneProps = {
|
|
|
96
96
|
validator?: (file: File) => FileError | FileError[] | null;
|
|
97
97
|
};
|
|
98
98
|
|
|
99
|
-
const DropzoneContainer = styled(Box, { name: 'AvDropzoneContainer', slot: 'root' })({
|
|
99
|
+
export const DropzoneContainer = styled(Box, { name: 'AvDropzoneContainer', slot: 'root' })({
|
|
100
100
|
'.MuiDivider-root': {
|
|
101
101
|
width: '196px',
|
|
102
102
|
marginLeft: 'auto',
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import { useForm, FormProvider } from 'react-hook-form';
|
|
4
|
+
|
|
5
|
+
import { Dropzone2 } from './Dropzone2';
|
|
6
|
+
|
|
7
|
+
const TestForm = ({ children }: { children: ReactNode }) => {
|
|
8
|
+
const methods = useForm();
|
|
9
|
+
|
|
10
|
+
return <FormProvider {...methods}>{children}</FormProvider>;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
describe('Dropzone', () => {
|
|
14
|
+
test('should render successfully', () => {
|
|
15
|
+
render(
|
|
16
|
+
<TestForm>
|
|
17
|
+
<Dropzone2
|
|
18
|
+
name="test"
|
|
19
|
+
maxSize={1000}
|
|
20
|
+
setTotalSize={jest.fn()}
|
|
21
|
+
uploadOptions={{ customerId: '123', clientId: 'test', bucketId: 'abc' }}
|
|
22
|
+
/>
|
|
23
|
+
</TestForm>
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
expect(screen.getByText('Drag and Drop Files Here')).toBeTruthy();
|
|
27
|
+
});
|
|
28
|
+
});
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
import type { MouseEvent } from 'react';
|
|
3
|
+
import { useDropzone } from 'react-dropzone';
|
|
4
|
+
import type { DropEvent, FileError, FileRejection } from 'react-dropzone';
|
|
5
|
+
import { useFormContext } from 'react-hook-form';
|
|
6
|
+
import { Divider } from '@availity/mui-divider';
|
|
7
|
+
import { CloudUploadIcon, PlusIcon } from '@availity/mui-icon';
|
|
8
|
+
import { Box, Stack } from '@availity/mui-layout';
|
|
9
|
+
import { Typography } from '@availity/mui-typography';
|
|
10
|
+
import Upload from '@availity/upload-core';
|
|
11
|
+
import type { UploadOptions } from '@availity/upload-core';
|
|
12
|
+
import type { OnSuccessPayload } from 'tus-js-client';
|
|
13
|
+
|
|
14
|
+
import { FilePickerBtn } from './FilePickerBtn';
|
|
15
|
+
import { dedupeErrors } from './util';
|
|
16
|
+
import { createCounter, DropzoneContainer, innerBoxStyles, outerBoxStyles } from './Dropzone';
|
|
17
|
+
import type { DropzoneProps } from './Dropzone';
|
|
18
|
+
|
|
19
|
+
const counter = createCounter();
|
|
20
|
+
|
|
21
|
+
export type Dropzone2Props = DropzoneProps & {
|
|
22
|
+
uploadOptions: UploadOptions;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
type Options = {
|
|
26
|
+
onError?: (error: Error) => void;
|
|
27
|
+
onSuccess?: (response: OnSuccessPayload) => void;
|
|
28
|
+
onProgress?: () => void;
|
|
29
|
+
onChunkComplete?: (chunkSize: number, bytesAccepted: number, bytesTotal: number | null) => void;
|
|
30
|
+
} & UploadOptions;
|
|
31
|
+
|
|
32
|
+
async function startUpload(file: File, options: Options) {
|
|
33
|
+
const { onSuccess, onError, onProgress, onChunkComplete, ...uploadOptions } = options;
|
|
34
|
+
const upload = new Upload(file, uploadOptions);
|
|
35
|
+
|
|
36
|
+
await upload.generateId();
|
|
37
|
+
|
|
38
|
+
if (onSuccess) upload.onSuccess.push(onSuccess);
|
|
39
|
+
if (onError) upload.onError.push(onError);
|
|
40
|
+
if (onProgress) upload.onProgress.push(onProgress);
|
|
41
|
+
if (onChunkComplete) upload.onChunkComplete.push(onChunkComplete);
|
|
42
|
+
|
|
43
|
+
upload.start();
|
|
44
|
+
|
|
45
|
+
return upload;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* `<Dropzone2 />` is the future of the the `<Dropzone />` component. In a
|
|
50
|
+
* future release, the `<Dropzone />` and `<Dropzone2 />` components will be
|
|
51
|
+
* consolidated into a single component.
|
|
52
|
+
*
|
|
53
|
+
* `<Dropzone2 />` adds the `uploadOptions` prop that previously existed on
|
|
54
|
+
* `<FileSelector />`.
|
|
55
|
+
*/
|
|
56
|
+
export const Dropzone2 = ({
|
|
57
|
+
allowedFileTypes = [],
|
|
58
|
+
disabled,
|
|
59
|
+
enableDropArea = true,
|
|
60
|
+
maxFiles,
|
|
61
|
+
maxSize,
|
|
62
|
+
multiple,
|
|
63
|
+
name,
|
|
64
|
+
onChange,
|
|
65
|
+
onClick,
|
|
66
|
+
onDrop,
|
|
67
|
+
setFileRejections,
|
|
68
|
+
setTotalSize,
|
|
69
|
+
uploadOptions,
|
|
70
|
+
validator,
|
|
71
|
+
}: Dropzone2Props) => {
|
|
72
|
+
const { getValues, setValue, watch } = useFormContext();
|
|
73
|
+
|
|
74
|
+
const accept = allowedFileTypes.join(',');
|
|
75
|
+
|
|
76
|
+
const handleValidation = useCallback(
|
|
77
|
+
(file: File) => {
|
|
78
|
+
const previous: Upload[] = watch(name) ?? [];
|
|
79
|
+
const errors: FileError[] = [];
|
|
80
|
+
|
|
81
|
+
const isDuplicate = previous.some((prev) => prev.file.name === file.name);
|
|
82
|
+
if (isDuplicate) {
|
|
83
|
+
errors.push({
|
|
84
|
+
code: 'duplicate-name',
|
|
85
|
+
message: 'A file with this name already exists',
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const hasMaxFiles = maxFiles && previous.length >= maxFiles;
|
|
90
|
+
if (hasMaxFiles) {
|
|
91
|
+
errors.push({
|
|
92
|
+
code: 'too-many-files',
|
|
93
|
+
message: `Too many files. You may only upload ${maxFiles} file(s).`,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (validator) {
|
|
98
|
+
const validatorErrors = validator(file);
|
|
99
|
+
if (validatorErrors) {
|
|
100
|
+
if (Array.isArray(validatorErrors)) {
|
|
101
|
+
errors.push(...validatorErrors);
|
|
102
|
+
} else {
|
|
103
|
+
errors.push(validatorErrors);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return errors.length > 0 ? dedupeErrors(errors) : null;
|
|
109
|
+
},
|
|
110
|
+
[maxFiles, validator]
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
const handleOnDrop = useCallback(
|
|
114
|
+
async (acceptedFiles: File[], fileRejections: (FileRejection & { id: number })[], event: DropEvent) => {
|
|
115
|
+
let newSize = 0;
|
|
116
|
+
for (const file of acceptedFiles) {
|
|
117
|
+
newSize += file.size;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
setTotalSize((prev) => prev + newSize);
|
|
121
|
+
|
|
122
|
+
const previous = watch(name) ?? [];
|
|
123
|
+
|
|
124
|
+
// Set accepted files to form context
|
|
125
|
+
const uploads = acceptedFiles.map((file) => startUpload(file, uploadOptions));
|
|
126
|
+
setValue(name, previous.concat(await Promise.all(uploads)));
|
|
127
|
+
|
|
128
|
+
if (fileRejections.length > 0) {
|
|
129
|
+
const TOO_MANY_FILES_CODE = 'too-many-files';
|
|
130
|
+
let hasTooManyFiles = false;
|
|
131
|
+
|
|
132
|
+
fileRejections = fileRejections.reduce(
|
|
133
|
+
(acc, rejection) => {
|
|
134
|
+
const isTooManyFiles = rejection.errors.some((error) => error.code === TOO_MANY_FILES_CODE);
|
|
135
|
+
|
|
136
|
+
if (isTooManyFiles) {
|
|
137
|
+
// Only add the first too-many-files rejection
|
|
138
|
+
if (!hasTooManyFiles) {
|
|
139
|
+
hasTooManyFiles = true;
|
|
140
|
+
acc.push({
|
|
141
|
+
...rejection,
|
|
142
|
+
id: counter.increment(),
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
} else {
|
|
146
|
+
// Add all other rejection types normally
|
|
147
|
+
acc.push({
|
|
148
|
+
...rejection,
|
|
149
|
+
id: counter.increment(),
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return acc;
|
|
154
|
+
},
|
|
155
|
+
[] as Array<(typeof fileRejections)[0] & { id: number }>
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (setFileRejections) setFileRejections(fileRejections);
|
|
160
|
+
if (onDrop) onDrop(acceptedFiles, fileRejections, event);
|
|
161
|
+
},
|
|
162
|
+
[setFileRejections]
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
const { getRootProps, getInputProps } = useDropzone({
|
|
166
|
+
onDrop: handleOnDrop,
|
|
167
|
+
maxSize,
|
|
168
|
+
maxFiles,
|
|
169
|
+
disabled,
|
|
170
|
+
multiple,
|
|
171
|
+
accept,
|
|
172
|
+
validator: handleValidation,
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
const inputProps = getInputProps({
|
|
176
|
+
multiple,
|
|
177
|
+
accept,
|
|
178
|
+
onChange,
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// Remove role and tabIndex for accessibility
|
|
182
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
183
|
+
const { role, tabIndex, ...rootProps } = getRootProps();
|
|
184
|
+
|
|
185
|
+
const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
186
|
+
if (inputProps.onChange) {
|
|
187
|
+
inputProps.onChange(event);
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const handleOnClick = (event: MouseEvent<HTMLButtonElement>) => {
|
|
192
|
+
if (!enableDropArea && rootProps.onClick) rootProps.onClick(event);
|
|
193
|
+
if (onClick) onClick;
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
const getFieldValue = () => {
|
|
197
|
+
const field = getValues();
|
|
198
|
+
return field[name] || [];
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
const hasFiles = getFieldValue().length > 0;
|
|
202
|
+
|
|
203
|
+
return enableDropArea ? (
|
|
204
|
+
<DropzoneContainer sx={outerBoxStyles} {...rootProps}>
|
|
205
|
+
<Box sx={innerBoxStyles}>
|
|
206
|
+
<Stack spacing={2} alignItems="center" justifyContent="center">
|
|
207
|
+
<>
|
|
208
|
+
<CloudUploadIcon fontSize="xlarge" color="secondary" />
|
|
209
|
+
<Typography variant="subtitle2" fontWeight="700">
|
|
210
|
+
Drag and Drop Files Here
|
|
211
|
+
</Typography>
|
|
212
|
+
<Divider flexItem>
|
|
213
|
+
<Typography variant="subtitle2">OR</Typography>
|
|
214
|
+
</Divider>
|
|
215
|
+
<FilePickerBtn
|
|
216
|
+
name={name}
|
|
217
|
+
color="primary"
|
|
218
|
+
disabled={disabled}
|
|
219
|
+
maxSize={maxSize}
|
|
220
|
+
onClick={onClick}
|
|
221
|
+
inputProps={inputProps}
|
|
222
|
+
onChange={handleOnChange}
|
|
223
|
+
>
|
|
224
|
+
Browse Files
|
|
225
|
+
</FilePickerBtn>
|
|
226
|
+
</>
|
|
227
|
+
</Stack>
|
|
228
|
+
</Box>
|
|
229
|
+
</DropzoneContainer>
|
|
230
|
+
) : (
|
|
231
|
+
<FilePickerBtn
|
|
232
|
+
name={name}
|
|
233
|
+
color="tertiary"
|
|
234
|
+
disabled={disabled}
|
|
235
|
+
maxSize={maxSize}
|
|
236
|
+
onClick={handleOnClick}
|
|
237
|
+
inputProps={inputProps}
|
|
238
|
+
onChange={handleOnChange}
|
|
239
|
+
startIcon={<PlusIcon />}
|
|
240
|
+
>
|
|
241
|
+
{hasFiles ? 'Add More Files' : 'Add File(s)'}
|
|
242
|
+
</FilePickerBtn>
|
|
243
|
+
);
|
|
244
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { screen, render, fireEvent, waitFor } from '@testing-library/react';
|
|
2
|
+
|
|
3
|
+
import { FileList2 } from './FileList2';
|
|
4
|
+
import Upload from '@availity/upload-core';
|
|
5
|
+
|
|
6
|
+
const options = { bucketId: '123', customerId: '123', clientId: '123' };
|
|
7
|
+
|
|
8
|
+
describe('FileList2', () => {
|
|
9
|
+
test('should render successfully', async () => {
|
|
10
|
+
const mockFile = new File(['file content'], 'mock.txt', { type: 'text/plain' });
|
|
11
|
+
const upload = new Upload(mockFile, options);
|
|
12
|
+
|
|
13
|
+
render(
|
|
14
|
+
<FileList2
|
|
15
|
+
uploads={[upload]}
|
|
16
|
+
options={options}
|
|
17
|
+
onRemoveFile={() => {
|
|
18
|
+
// noop
|
|
19
|
+
}}
|
|
20
|
+
/>
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
await waitFor(() => {
|
|
24
|
+
expect(screen.getByText('mock.txt')).toBeDefined();
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('should call onRemoveFile', async () => {
|
|
29
|
+
const mockRemove = jest.fn();
|
|
30
|
+
const mockFile = new File(['file content'], 'mock.txt', { type: 'text/plain' });
|
|
31
|
+
const upload = new Upload(mockFile, options);
|
|
32
|
+
|
|
33
|
+
render(
|
|
34
|
+
<FileList2
|
|
35
|
+
uploads={[upload]}
|
|
36
|
+
options={options}
|
|
37
|
+
onRemoveFile={(id) => {
|
|
38
|
+
mockRemove(id);
|
|
39
|
+
}}
|
|
40
|
+
/>
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
await waitFor(() => {
|
|
44
|
+
expect(screen.getByText('mock.txt')).toBeDefined();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
fireEvent.click(screen.getByLabelText('remove file'));
|
|
48
|
+
expect(mockRemove).toHaveBeenCalled();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('should not display remove button when disableRemove is set to true', async () => {
|
|
52
|
+
const mockRemove = jest.fn();
|
|
53
|
+
const mockFile = new File(['file content'], 'mock.txt', { type: 'text/plain' });
|
|
54
|
+
const upload = new Upload(mockFile, options);
|
|
55
|
+
|
|
56
|
+
render(
|
|
57
|
+
<FileList2
|
|
58
|
+
uploads={[upload]}
|
|
59
|
+
options={options}
|
|
60
|
+
onRemoveFile={(id) => {
|
|
61
|
+
mockRemove(id);
|
|
62
|
+
}}
|
|
63
|
+
disableRemove
|
|
64
|
+
/>
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
await waitFor(() => {
|
|
68
|
+
expect(screen.getByText('mock.txt')).toBeDefined();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
expect(() => screen.getByLabelText('remove file')).toThrow();
|
|
72
|
+
});
|
|
73
|
+
});
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import type { default as Upload } from '@availity/upload-core';
|
|
2
|
+
import { List, ListItem, ListItemText, ListItemIcon } from '@availity/mui-list';
|
|
3
|
+
import { IconButton } from '@availity/mui-button';
|
|
4
|
+
import { DeleteIcon } from '@availity/mui-icon';
|
|
5
|
+
import { Grid } from '@availity/mui-layout';
|
|
6
|
+
import { Divider } from '@availity/mui-divider';
|
|
7
|
+
|
|
8
|
+
import { UploadProgressBar } from './UploadProgressBar';
|
|
9
|
+
import { formatBytes, getFileExtIcon } from './util';
|
|
10
|
+
|
|
11
|
+
import type { FileListProps, FileRowProps } from './FileList';
|
|
12
|
+
|
|
13
|
+
export type FileRow2Props = Omit<FileRowProps, 'file' | 'queryOptions'> & {
|
|
14
|
+
/**
|
|
15
|
+
* The File object containing information about the uploaded file
|
|
16
|
+
* */
|
|
17
|
+
upload: Upload;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* `<FileRow2 />` is the future of the the `<FileRow />` component. In a
|
|
22
|
+
* future release, the `<FileRow />` and `<FileRow2 />` components will be
|
|
23
|
+
* consolidated into a single component.
|
|
24
|
+
*
|
|
25
|
+
* `<FileRow2 />` replaces the `file` prop with the `upload` prop and
|
|
26
|
+
* removes the `queryOptions` prop.
|
|
27
|
+
*/
|
|
28
|
+
export const FileRow2 = ({
|
|
29
|
+
upload,
|
|
30
|
+
options,
|
|
31
|
+
onRemoveFile,
|
|
32
|
+
customFileRow: CustomRow,
|
|
33
|
+
disableRemove = false,
|
|
34
|
+
}: FileRow2Props) => {
|
|
35
|
+
const Icon = getFileExtIcon(upload.file.name);
|
|
36
|
+
|
|
37
|
+
if (!upload) return null;
|
|
38
|
+
|
|
39
|
+
if (CustomRow) return <CustomRow upload={upload} options={options} onRemoveFile={onRemoveFile} />;
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<ListItem
|
|
43
|
+
disableGutters
|
|
44
|
+
secondaryAction={
|
|
45
|
+
!disableRemove && (
|
|
46
|
+
<IconButton
|
|
47
|
+
title="remove file"
|
|
48
|
+
edge="end"
|
|
49
|
+
onClick={() => {
|
|
50
|
+
onRemoveFile(upload.id, upload);
|
|
51
|
+
}}
|
|
52
|
+
>
|
|
53
|
+
<DeleteIcon />
|
|
54
|
+
</IconButton>
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
>
|
|
58
|
+
<Grid container spacing={2} alignItems="center" justifyContent="space-between" width="100%" flexWrap="wrap">
|
|
59
|
+
<Grid size={{ xs: 'auto' }}>
|
|
60
|
+
<ListItemIcon sx={{ minWidth: '1.5rem' }}>
|
|
61
|
+
<Icon />
|
|
62
|
+
</ListItemIcon>
|
|
63
|
+
</Grid>
|
|
64
|
+
<Grid size={{ xs: 4 }} sx={{ minWidth: '8rem' }}>
|
|
65
|
+
<ListItemText sx={{ wordBreak: 'break-all' }}>{upload.trimFileName(upload.file.name)}</ListItemText>
|
|
66
|
+
</Grid>
|
|
67
|
+
<Grid size={{ xs: 2 }} sx={{ minWidth: '3rem' }}>
|
|
68
|
+
<ListItemText sx={{ textAlign: 'end' }}>{formatBytes(upload.file.size)}</ListItemText>
|
|
69
|
+
</Grid>
|
|
70
|
+
<Grid size={{ xs: 'grow' }} sx={{ minWidth: '6rem' }}>
|
|
71
|
+
<UploadProgressBar upload={upload} />
|
|
72
|
+
</Grid>
|
|
73
|
+
</Grid>
|
|
74
|
+
<Divider />
|
|
75
|
+
</ListItem>
|
|
76
|
+
);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export type FileList2Props = Omit<FileListProps, 'files'> & {
|
|
80
|
+
/**
|
|
81
|
+
* Array of File objects to be displayed in the list
|
|
82
|
+
*/
|
|
83
|
+
uploads: Upload[];
|
|
84
|
+
} & Omit<FileRow2Props, 'upload'>;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* `<FileList2 />` is the future of the the `<FileList />` component. In a
|
|
88
|
+
* future release, the `<FileList />` and `<FileList2 />` components will
|
|
89
|
+
* be consolidated into a single component.
|
|
90
|
+
*
|
|
91
|
+
* `<FileList2 />` replaces the `files` prop with the `uploads` prop.
|
|
92
|
+
*/
|
|
93
|
+
export const FileList2 = ({
|
|
94
|
+
uploads,
|
|
95
|
+
options,
|
|
96
|
+
onRemoveFile,
|
|
97
|
+
customFileRow,
|
|
98
|
+
disableRemove,
|
|
99
|
+
}: FileList2Props): JSX.Element | null => {
|
|
100
|
+
if (uploads.length === 0) return null;
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<List>
|
|
104
|
+
{uploads.map((upload) => {
|
|
105
|
+
return (
|
|
106
|
+
<FileRow2
|
|
107
|
+
key={upload.id}
|
|
108
|
+
upload={upload}
|
|
109
|
+
options={options}
|
|
110
|
+
onRemoveFile={onRemoveFile}
|
|
111
|
+
customFileRow={customFileRow}
|
|
112
|
+
disableRemove={disableRemove}
|
|
113
|
+
/>
|
|
114
|
+
);
|
|
115
|
+
})}
|
|
116
|
+
</List>
|
|
117
|
+
);
|
|
118
|
+
};
|
package/src/lib/FileSelector.tsx
CHANGED
|
@@ -15,7 +15,7 @@ import { FileTypesMessage } from './FileTypesMessage';
|
|
|
15
15
|
import { HeaderMessage } from './HeaderMessage';
|
|
16
16
|
import type { Options, UploadQueryOptions } from './useUploadCore';
|
|
17
17
|
|
|
18
|
-
const CLOUD_URL = '/cloud/web/appl/vault/upload/v1/resumable';
|
|
18
|
+
export const CLOUD_URL = '/cloud/web/appl/vault/upload/v1/resumable';
|
|
19
19
|
|
|
20
20
|
export type FileSelectorProps = {
|
|
21
21
|
/**
|