@availity/mui-file-selector 0.1.3 → 0.2.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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,30 @@
2
2
 
3
3
  This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
4
4
 
5
+ ## [0.2.1](https://github.com/Availity/element/compare/@availity/mui-file-selector@0.2.0...@availity/mui-file-selector@0.2.1) (2024-12-23)
6
+
7
+ ### Dependency Updates
8
+
9
+ * `mui-form-utils` updated to version `0.2.0`
10
+ ## [0.2.0](https://github.com/Availity/element/compare/@availity/mui-file-selector@0.1.3...@availity/mui-file-selector@0.2.0) (2024-12-16)
11
+
12
+
13
+ ### Features
14
+
15
+ * **mui-file-selector:** add support for dropping files and fix removing ([66af280](https://github.com/Availity/element/commit/66af28076ed7149bc47c6b9758bb9a2e5461f201))
16
+ * **mui-file-selector:** update upload-core ([0da6ec7](https://github.com/Availity/element/commit/0da6ec7672f1d7a884f42d03772ec20249a3309b))
17
+ * pass uploads to onSubmit ([e894489](https://github.com/Availity/element/commit/e8944899a6a6ed4b4bb192a51b707a9aa88b6833))
18
+
19
+
20
+ ### Bug Fixes
21
+
22
+ * add better error handling ([c2db0c0](https://github.com/Availity/element/commit/c2db0c0a1d06bebbb03bc38309d65829487717fb))
23
+ * fixes for file selector ([add8801](https://github.com/Availity/element/commit/add88013c18816ccdaeeb9366768089d68aded7f))
24
+ * fixes for tests ([e112880](https://github.com/Availity/element/commit/e11288079f203464f55f35cbaed47f86f4db3755))
25
+ * **mui-file-selector:** update upload-core ([e24b673](https://github.com/Availity/element/commit/e24b67337a8b1a71441c6954e84a6bd3a1b8e323))
26
+ * update upload-core ([e11a6ad](https://github.com/Availity/element/commit/e11a6ad155743afb221739686b608e581099c37a))
27
+ * update upload-core ([5d820db](https://github.com/Availity/element/commit/5d820db3f146e9e0015daa0f5e9a9d9316cd6807))
28
+
5
29
  ## [0.1.3](https://github.com/Availity/element/compare/@availity/mui-file-selector@0.1.2...@availity/mui-file-selector@0.1.3) (2024-12-09)
6
30
 
7
31
  ### Dependency Updates
package/dist/index.d.mts CHANGED
@@ -2,13 +2,21 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import Upload from '@availity/upload-core';
3
3
 
4
4
  type UploadProgressBarProps = {
5
- /** The upload instance returned by creating a new Upload via @availity/upload-core. */
5
+ /**
6
+ * The upload instance returned by creating a new Upload via @availity/upload-core.
7
+ */
6
8
  upload: Upload;
7
- /** Callback function to hook into the onProgress within the Upload instance provided in the upload prop. */
9
+ /**
10
+ * Callback function to hook into the onProgress within the Upload instance provided in the upload prop.
11
+ */
8
12
  onProgress?: (upload: Upload) => void;
9
- /** Callback function to hook into the onSuccess within the Upload instance provided in the upload prop. */
13
+ /**
14
+ * Callback function to hook into the onSuccess within the Upload instance provided in the upload prop.
15
+ */
10
16
  onSuccess?: (upload: Upload) => void;
11
- /** Callback function to hook into the onError within the Upload instance provided in the upload prop. */
17
+ /**
18
+ * Callback function to hook into the onError within the Upload instance provided in the upload prop.
19
+ */
12
20
  onError?: (upload: Upload) => void;
13
21
  };
14
22
  declare const UploadProgressBar: ({ upload, onProgress, onError, onSuccess }: UploadProgressBarProps) => react_jsx_runtime.JSX.Element;
package/dist/index.d.ts CHANGED
@@ -2,13 +2,21 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import Upload from '@availity/upload-core';
3
3
 
4
4
  type UploadProgressBarProps = {
5
- /** The upload instance returned by creating a new Upload via @availity/upload-core. */
5
+ /**
6
+ * The upload instance returned by creating a new Upload via @availity/upload-core.
7
+ */
6
8
  upload: Upload;
7
- /** Callback function to hook into the onProgress within the Upload instance provided in the upload prop. */
9
+ /**
10
+ * Callback function to hook into the onProgress within the Upload instance provided in the upload prop.
11
+ */
8
12
  onProgress?: (upload: Upload) => void;
9
- /** Callback function to hook into the onSuccess within the Upload instance provided in the upload prop. */
13
+ /**
14
+ * Callback function to hook into the onSuccess within the Upload instance provided in the upload prop.
15
+ */
10
16
  onSuccess?: (upload: Upload) => void;
11
- /** Callback function to hook into the onError within the Upload instance provided in the upload prop. */
17
+ /**
18
+ * Callback function to hook into the onError within the Upload instance provided in the upload prop.
19
+ */
12
20
  onError?: (upload: Upload) => void;
13
21
  };
14
22
  declare const UploadProgressBar: ({ upload, onProgress, onError, onSuccess }: UploadProgressBarProps) => react_jsx_runtime.JSX.Element;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@availity/mui-file-selector",
3
- "version": "0.1.3",
3
+ "version": "0.2.1",
4
4
  "description": "Availity MUI file-selector Component - part of the @availity/element design system",
5
5
  "keywords": [
6
6
  "react",
@@ -33,23 +33,24 @@
33
33
  },
34
34
  "dependencies": {
35
35
  "@availity/api-axios": "^9.0.4",
36
+ "@availity/mui-alert": "^0.7.0",
36
37
  "@availity/mui-button": "^0.6.12",
37
38
  "@availity/mui-divider": "^0.4.0",
38
- "@availity/mui-form-utils": "^0.15.1",
39
+ "@availity/mui-form-utils": "^0.16.0",
39
40
  "@availity/mui-icon": "^0.11.1",
40
41
  "@availity/mui-layout": "^0.2.0",
41
42
  "@availity/mui-list": "^0.2.2",
42
43
  "@availity/mui-progress": "^0.4.1",
43
44
  "@availity/mui-typography": "^0.2.1",
44
- "@availity/upload-core": "^6.1.1",
45
+ "@availity/upload-core": "7.0.0-alpha.5",
45
46
  "@tanstack/react-query": "^4.36.1",
46
47
  "react-dropzone": "^11.7.1",
47
48
  "react-hook-form": "^7.51.3",
49
+ "tus-js-client": "4.2.3",
48
50
  "uuid": "^9.0.1"
49
51
  },
50
52
  "devDependencies": {
51
53
  "@mui/material": "^5.15.15",
52
- "@types/tus-js-client": "^1.8.0",
53
54
  "react": "18.2.0",
54
55
  "react-dom": "18.2.0",
55
56
  "tsup": "^8.0.2",
@@ -18,7 +18,7 @@ describe('Dropzone', () => {
18
18
  render(
19
19
  <QueryClientProvider client={client}>
20
20
  <TestForm>
21
- <Dropzone name="test" bucketId="test" customerId="123" clientId="test" maxSize={1000} />
21
+ <Dropzone name="test" maxSize={1000} setTotalSize={jest.fn()} />
22
22
  </TestForm>
23
23
  </QueryClientProvider>
24
24
  );
@@ -1,13 +1,12 @@
1
- import { ChangeEvent, MouseEvent, useCallback, useState } from 'react';
2
- import { useDropzone, FileRejection, DropEvent } from 'react-dropzone';
3
- import { v4 as uuid } from 'uuid';
1
+ import { Dispatch, MouseEvent, useCallback, ChangeEvent } from 'react';
2
+ import { useDropzone, FileRejection } from 'react-dropzone';
4
3
  import { Divider } from '@availity/mui-divider';
5
4
  import { CloudDownloadIcon } from '@availity/mui-icon';
6
5
  import { Box, Stack } from '@availity/mui-layout';
7
6
  import { Typography } from '@availity/mui-typography';
8
- import Upload, { Options } from '@availity/upload-core';
9
7
 
10
8
  import { FilePickerBtn } from './FilePickerBtn';
9
+ import { useFormContext } from 'react-hook-form';
11
10
 
12
11
  const outerBoxStyles = {
13
12
  backgroundColor: 'background.canvas',
@@ -21,141 +20,158 @@ const innerBoxStyles = {
21
20
  height: '100%',
22
21
  };
23
22
 
24
- const CLOUD_URL = '/cloud/web/appl/vault/upload/v1/resumable';
23
+ /** Counter for creating unique id */
24
+ const createCounter = () => {
25
+ let id = 0;
26
+ const increment = () => (id += 1);
27
+ return {
28
+ id,
29
+ increment,
30
+ };
31
+ };
32
+
33
+ const counter = createCounter();
25
34
 
26
35
  export type DropzoneProps = {
36
+ /**
37
+ * Name given to the input field. Used by react-hook-form
38
+ */
27
39
  name: string;
28
- bucketId: string;
29
- clientId: string;
30
- customerId: string;
31
- allowedFileNameCharacters?: string;
40
+ /**
41
+ * List of allowed file extensions (e.g. ['.pdf', '.doc']). Each extension must start with a dot
42
+ */
32
43
  allowedFileTypes?: `.${string}`[];
33
- deliverFileOnSubmit?: boolean;
34
- deliveryChannel?: string;
44
+ /**
45
+ * Whether the dropzone is disabled
46
+ */
35
47
  disabled?: boolean;
36
- endpoint?: string;
37
- fileDeliveryMetadata?: Record<string, unknown> | ((file: Upload) => Record<string, unknown>);
38
- getDropRejectionMessages?: (fileRejectsions: FileRejection[]) => void;
39
- isCloud?: boolean;
48
+ /**
49
+ * Maximum number of files that can be uploaded
50
+ */
40
51
  maxFiles?: number;
52
+ /**
53
+ * Maximum size of each file in bytes
54
+ */
41
55
  maxSize?: number;
56
+ /**
57
+ * Whether multiple file selection is allowed
58
+ */
42
59
  multiple?: boolean;
60
+ /**
61
+ * Handler called when the file input's value changes
62
+ */
43
63
  onChange?: (event: ChangeEvent<HTMLInputElement>) => void;
64
+ /**
65
+ * Handler called when the file picker button is clicked
66
+ */
44
67
  onClick?: (event: MouseEvent<HTMLButtonElement>) => void;
45
- // onDeliveryError?: (responses: unknown[]) => void;
46
- // onDeliverySuccess?: (responses: unknown[]) => void;
47
- onFileDelivery?: (upload: Upload) => void;
48
- onFilePreUpload?: ((upload: Upload) => boolean)[];
68
+ /**
69
+ * Callback to handle rejected files that don't meet validation criteria
70
+ */
71
+ setFileRejections?: (fileRejections: (FileRejection & { id: number })[]) => void;
72
+ /**
73
+ * Callback to update the total size of all uploaded files
74
+ */
75
+ setTotalSize: Dispatch<React.SetStateAction<number>>;
49
76
  };
50
77
 
78
+ // The types below were props used in the availity-react implementation.
79
+ // Perserving this here in case it needs to be added back
80
+ // deliverFileOnSubmit?: boolean;
81
+ // deliveryChannel?: string;
82
+ // fileDeliveryMetadata?: Record<string, unknown> | ((file: Upload) => Record<string, unknown>);
83
+ // onDeliveryError?: (responses: unknown[]) => void;
84
+ // onDeliverySuccess?: (responses: unknown[]) => void;
85
+ // onFileDelivery?: (upload: Upload) => void;
86
+
51
87
  export const Dropzone = ({
52
- allowedFileNameCharacters,
53
88
  allowedFileTypes = [],
54
- bucketId,
55
- clientId,
56
- customerId,
57
- deliveryChannel,
58
- // deliverFileOnSubmit,
59
- fileDeliveryMetadata,
60
89
  disabled,
61
- endpoint,
62
- getDropRejectionMessages,
63
- isCloud,
64
90
  maxFiles,
65
91
  maxSize,
66
92
  multiple,
67
93
  name,
68
94
  onChange,
69
95
  onClick,
70
- onFilePreUpload,
71
- onFileDelivery,
96
+ setFileRejections,
97
+ setTotalSize,
72
98
  }: DropzoneProps) => {
73
- const [totalSize, setTotalSize] = useState(0);
74
- const [files, setFiles] = useState<Upload[]>([]);
99
+ const { setValue, watch } = useFormContext();
75
100
 
76
- const onDrop = useCallback(
77
- (acceptedFiles: File[], fileRejections: FileRejection[], dropEvent: DropEvent) => {
78
- // Do something with the files
79
- console.log('Dropzone acceptedFiles:', acceptedFiles);
80
- console.log('Dropzone fileRejections:', fileRejections);
81
- console.log('Dropzone dropEvent:', dropEvent);
82
-
83
- // Verify we have not exceeded max number of files
84
- if (maxFiles && acceptedFiles.length > maxFiles) {
85
- acceptedFiles.slice(0, Math.max(9, maxFiles));
101
+ const validator = useCallback(
102
+ (file: File) => {
103
+ const previous: File[] = watch(name) ?? [];
104
+
105
+ const isDuplicate = previous.some((prev) => prev.name === file.name);
106
+ if (isDuplicate) {
107
+ return {
108
+ code: 'duplicate-name',
109
+ message: 'A file with this name already exists',
110
+ };
86
111
  }
87
112
 
88
- const uploads = acceptedFiles.map((file) => {
89
- const options: Options = {
90
- bucketId,
91
- customerId,
92
- clientId,
93
- fileTypes: allowedFileTypes,
94
- maxSize,
95
- allowedFileNameCharacters,
113
+ const hasMaxFiles = maxFiles && previous.length >= maxFiles;
114
+ if (hasMaxFiles) {
115
+ return {
116
+ code: 'too-many-files',
117
+ message: `Too many files. You may only upload ${maxFiles} file(s).`,
96
118
  };
119
+ }
97
120
 
98
- if (onFilePreUpload) options.onPreStart = onFilePreUpload;
99
- if (endpoint) options.endpoint = endpoint;
100
- if (isCloud) options.endpoint = CLOUD_URL;
101
-
102
- const upload = new Upload(file, options);
103
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
104
- // @ts-ignore
105
- upload.id = `${upload.id}-${uuid()}`;
106
-
107
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
108
- // @ts-ignore
109
- if (file.dropRejectionMessage) {
110
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
111
- // @ts-ignore
112
- upload.errorMessage = file.dropRejectionMessage;
113
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
114
- // @ts-ignore
115
- } else if (maxSize && totalSize + newFilesTotalSize + upload.file.size > maxSize) {
116
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
117
- // @ts-ignore
118
- upload.errorMessage = 'Total documents size is too large';
119
- } else {
120
- upload.start();
121
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
122
- // @ts-ignore
123
- newFilesTotalSize += upload.file.size;
124
- }
125
- if (onFileDelivery) {
126
- onFileDelivery(upload);
127
- } else if (deliveryChannel && fileDeliveryMetadata) {
128
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
129
- // @ts-ignore
130
- // upload.onSuccess.push(() => {
131
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
132
- // @ts-ignore
133
- // if (upload?.references?.[0]) {
134
- // allow form to revalidate when upload is complete
135
- // setFieldTouched(name, true);
136
- // deliver upon upload complete, not form submit
137
- // if (!deliverFileOnSubmit) {
138
- // callFileDelivery(upload);
139
- // }
140
- // }
141
- // });
142
- }
121
+ return null;
122
+ },
123
+ [maxFiles]
124
+ );
125
+
126
+ const onDrop = useCallback(
127
+ (acceptedFiles: File[], fileRejections: (FileRejection & { id: number })[]) => {
128
+ let newSize = 0;
129
+ for (const file of acceptedFiles) {
130
+ newSize += file.size;
131
+ }
132
+
133
+ setTotalSize((prev) => prev + newSize);
143
134
 
144
- return upload;
145
- });
135
+ const previous = watch(name) ?? [];
146
136
 
147
- // Set uploads somewhere. state?
148
- setFiles(files);
137
+ // Set accepted files to form context
138
+ setValue(name, previous.concat(acceptedFiles));
149
139
 
150
- if (getDropRejectionMessages) getDropRejectionMessages(fileRejections);
140
+ if (fileRejections.length > 0) {
141
+ for (const rejection of fileRejections) {
142
+ rejection.id = counter.increment();
143
+ }
144
+ }
145
+
146
+ if (setFileRejections) setFileRejections(fileRejections);
151
147
  },
152
- [getDropRejectionMessages]
148
+ [setFileRejections]
153
149
  );
154
150
 
155
- const { getRootProps, getInputProps } = useDropzone({ onDrop });
156
-
157
151
  const accept = allowedFileTypes.join(',');
158
152
 
153
+ const { getRootProps, getInputProps } = useDropzone({
154
+ onDrop,
155
+ maxSize,
156
+ maxFiles,
157
+ disabled,
158
+ multiple,
159
+ accept,
160
+ validator,
161
+ });
162
+
163
+ const inputProps = getInputProps({
164
+ multiple,
165
+ accept,
166
+ onChange,
167
+ });
168
+
169
+ const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>) => {
170
+ if (inputProps.onChange) {
171
+ inputProps.onChange(event);
172
+ }
173
+ };
174
+
159
175
  return (
160
176
  <Box sx={outerBoxStyles} {...getRootProps()}>
161
177
  <Box sx={innerBoxStyles}>
@@ -171,11 +187,8 @@ export const Dropzone = ({
171
187
  disabled={disabled}
172
188
  maxSize={maxSize}
173
189
  onClick={onClick}
174
- inputProps={getInputProps({
175
- multiple,
176
- accept,
177
- onChange,
178
- })}
190
+ inputProps={inputProps}
191
+ onChange={handleOnChange}
179
192
  />
180
193
  </>
181
194
  </Stack>
@@ -0,0 +1,11 @@
1
+ import { render, screen } from '@testing-library/react';
2
+
3
+ import { ErrorAlert } from './ErrorAlert';
4
+
5
+ describe('ErrorAlert', () => {
6
+ test('should render error message', () => {
7
+ render(<ErrorAlert id={0} errors={[{ code: 'test', message: 'example' }]} fileName="file" onClose={jest.fn()} />);
8
+
9
+ expect(screen.getByText('Error: file')).toBeDefined();
10
+ });
11
+ });
@@ -0,0 +1,46 @@
1
+ import { Alert, AlertTitle } from '@availity/mui-alert';
2
+ import type { FileRejection } from 'react-dropzone';
3
+
4
+ const codes: Record<string, string> = {
5
+ 'file-too-large': 'File exceeds maximum size',
6
+ 'file-invalid-type': 'File has an invalid type',
7
+ 'file-too-small': 'File is smaller than minimum size',
8
+ 'too-many-file': 'Too many files',
9
+ 'duplicate-name': 'Duplicate file selected',
10
+ };
11
+
12
+ export type ErrorAlertProps = {
13
+ /**
14
+ * Array of file rejection errors
15
+ */
16
+ errors: FileRejection['errors'];
17
+ /**
18
+ * Name of the file that encountered errors
19
+ */
20
+ fileName: string;
21
+ /**
22
+ * Unique identifier for the error alert
23
+ */
24
+ id: number;
25
+
26
+ onClose: () => void;
27
+ };
28
+
29
+ export const ErrorAlert = ({ errors, fileName, id, onClose }: ErrorAlertProps) => {
30
+ if (errors.length === 0) return null;
31
+
32
+ return (
33
+ <>
34
+ {errors.map((error) => {
35
+ return (
36
+ <Alert severity="error" onClose={onClose} key={`${id}-${error.code}`}>
37
+ <AlertTitle>
38
+ {codes[error.code] || 'Error'}: {fileName}
39
+ </AlertTitle>
40
+ {error.message}
41
+ </Alert>
42
+ );
43
+ })}
44
+ </>
45
+ );
46
+ };
@@ -1,16 +1,50 @@
1
- import { render } from '@testing-library/react';
1
+ import { screen, render, fireEvent, waitFor } from '@testing-library/react';
2
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
2
3
 
3
4
  import { FileList } from './FileList';
4
5
 
5
6
  describe('FileList', () => {
6
- test('should render successfully', () => {
7
+ test('should render successfully', async () => {
8
+ const mockFile = new File(['file content'], 'mock.txt', { type: 'text/plain' });
9
+
7
10
  render(
8
- <FileList
9
- uploads={[]}
10
- onRemoveFile={() => {
11
- // noop
12
- }}
13
- />
11
+ <QueryClientProvider client={new QueryClient()}>
12
+ <FileList
13
+ files={[mockFile]}
14
+ options={{ bucketId: '123', customerId: '123', clientId: '123' }}
15
+ onRemoveFile={() => {
16
+ // noop
17
+ }}
18
+ />
19
+ </QueryClientProvider>
14
20
  );
21
+
22
+ await waitFor(() => {
23
+ expect(screen.getByText('mock.txt')).toBeDefined();
24
+ });
25
+ });
26
+
27
+ test('should call onRemoveFile', async () => {
28
+ const mockRemove = jest.fn();
29
+ const mockFile = new File(['file content'], 'mock.txt', { type: 'text/plain' });
30
+
31
+ render(
32
+ <QueryClientProvider client={new QueryClient()}>
33
+ <FileList
34
+ files={[mockFile]}
35
+ options={{ bucketId: '123', customerId: '123', clientId: '123' }}
36
+ onRemoveFile={(id) => {
37
+ mockRemove(id);
38
+ }}
39
+ />
40
+ </QueryClientProvider>
41
+ );
42
+
43
+ await waitFor(() => {
44
+ expect(screen.getByText('mock.txt')).toBeDefined();
45
+ });
46
+
47
+ fireEvent.click(screen.getByRole('button'));
48
+ expect(mockRemove).toHaveBeenCalled();
15
49
  });
16
50
  });
@@ -1,24 +1,35 @@
1
- import type Upload from '@availity/upload-core';
1
+ import type { default as Upload, UploadOptions } from '@availity/upload-core';
2
2
  import { List, ListItem, ListItemText, ListItemIcon, ListItemButton } from '@availity/mui-list';
3
3
  import { DeleteIcon, FileIcon } from '@availity/mui-icon';
4
4
  import { Grid } from '@availity/mui-layout';
5
5
 
6
6
  import { UploadProgressBar } from './UploadProgressBar';
7
7
  import { formatBytes, getFileExtIcon } from './util';
8
+ import { useUploadCore } from './useUploadCore';
8
9
 
9
10
  type FileRowProps = {
10
- /** The upload instance returned by creating a new Upload via @availity/upload-core. */
11
- upload: Upload;
12
- /** Callback called when file is removed. The callback is passed the id of the file that was removed. */
13
- onRemoveFile: (id: string) => void;
11
+ /** The File object containing information about the uploaded file */
12
+ file: File;
13
+ /**
14
+ * Callback function called when a file is removed
15
+ * @param id - The unique identifier of the file being removed
16
+ * @param upload - The Upload instance associated with the file
17
+ */
18
+ onRemoveFile: (id: string, upload: Upload) => void;
19
+ /** Configuration options for the upload process */
20
+ options: UploadOptions;
14
21
  };
15
22
 
16
- const FileRow = ({ upload, onRemoveFile }: FileRowProps) => {
17
- const { ext, icon } = getFileExtIcon(upload.file.name);
23
+ const FileRow = ({ file, options, onRemoveFile }: FileRowProps) => {
24
+ const { ext, icon } = getFileExtIcon(file.name);
18
25
  console.log('ext, icon:', ext, icon);
19
26
 
27
+ const { data: upload } = useUploadCore(file, options);
28
+
29
+ if (!upload) return null;
30
+
20
31
  return (
21
- <ListItem>
32
+ <>
22
33
  <Grid container spacing={2} alignItems="center" justifyContent="space-between" width="100%">
23
34
  <Grid xs={1}>
24
35
  <ListItemIcon>
@@ -35,35 +46,45 @@ const FileRow = ({ upload, onRemoveFile }: FileRowProps) => {
35
46
  <UploadProgressBar upload={upload} />
36
47
  </Grid>
37
48
  <Grid xs={1}>
38
- <ListItemButton>
39
- <ListItemIcon
40
- onClick={() => {
41
- onRemoveFile(upload.id);
42
- }}
43
- >
49
+ <ListItemButton
50
+ onClick={() => {
51
+ onRemoveFile(upload.id, upload);
52
+ }}
53
+ >
54
+ <ListItemIcon>
44
55
  <DeleteIcon />
45
56
  </ListItemIcon>
46
57
  </ListItemButton>
47
58
  </Grid>
48
59
  </Grid>
49
- </ListItem>
60
+ </>
50
61
  );
51
62
  };
52
63
 
53
64
  export type FileListProps = {
54
- /** List of Upload objects */
55
- uploads: Upload[];
56
- /** Callback called when file is removed. The callback is passed the id of the file that was removed. */
57
- onRemoveFile: (id: string) => void;
65
+ /**
66
+ * Array of File objects to be displayed in the list
67
+ */
68
+ files: File[];
69
+ /**
70
+ * Callback function called when a file is removed from the list
71
+ * @param id - The unique identifier of the file being removed
72
+ * @param upload - The Upload instance associated with the file
73
+ */
74
+ onRemoveFile: (id: string, upload: Upload) => void;
75
+ /**
76
+ * Configuration options applied to all file uploads in the list
77
+ */
78
+ options: UploadOptions;
58
79
  };
59
80
 
60
- export const FileList = ({ uploads, onRemoveFile }: FileListProps) => {
61
- if (uploads.length === 0) return null;
81
+ export const FileList = ({ files, options, onRemoveFile }: FileListProps) => {
82
+ if (files.length === 0) return null;
62
83
 
63
84
  return (
64
85
  <List>
65
- {uploads.map((upload) => {
66
- return <FileRow key={upload.id} upload={upload} onRemoveFile={onRemoveFile} />;
86
+ {files.map((file) => {
87
+ return <FileRow key={file.name} file={file} options={options} onRemoveFile={onRemoveFile} />;
67
88
  })}
68
89
  </List>
69
90
  );
@@ -10,7 +10,7 @@ const TestForm = () => {
10
10
 
11
11
  return (
12
12
  <FormProvider {...methods}>
13
- <FilePickerBtn name="test" inputProps={{ ref }} />
13
+ <FilePickerBtn name="test" inputProps={{ ref }} onChange={jest.fn()} />
14
14
  </FormProvider>
15
15
  );
16
16
  };