@groupeactual/ui-kit 1.7.9 → 2.0.0-beta.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.
Files changed (144) hide show
  1. package/dist/cjs/index.js +15097 -66
  2. package/dist/es/{src/DesignSystemProvider.d.ts → DesignSystemProvider.d.ts} +6 -5
  3. package/dist/es/{src/components → components}/Accordion/Accordion.d.ts +1 -1
  4. package/dist/es/{src/components → components}/BannerNotification/BannerNotification.d.ts +1 -1
  5. package/dist/es/{src/components → components}/Breadcrumbs/Breadcrumbs.d.ts +2 -2
  6. package/dist/es/{src/components → components}/Button/Button.d.ts +2 -2
  7. package/dist/es/{src/components → components}/Chip/Chip.d.ts +1 -1
  8. package/dist/es/components/Datatable/Datatable.d.ts +4 -0
  9. package/dist/es/{src/components → components}/EmbbededNotification/EmbeddedNotification.d.ts +1 -1
  10. package/dist/es/{src/components/UploadDocument → components/FileUploader}/FileUploader.d.ts +1 -1
  11. package/dist/es/components/Form/AutoCompleteMulti/AutoCompleteMulti.d.ts +23 -0
  12. package/dist/es/{src/components → components}/Form/AutoCompleteSingle/AutoCompleteSingle.d.ts +8 -5
  13. package/dist/es/components/Form/Checkbox/Checkbox.d.ts +12 -0
  14. package/dist/es/components/Form/CheckboxGroup/CheckboxGroup.d.ts +12 -0
  15. package/dist/es/components/Form/CheckboxGroup/checkboxgroup.interface.d.ts +6 -0
  16. package/dist/es/{src/components → components}/Form/DatePicker/DatePicker.d.ts +6 -4
  17. package/dist/es/{src/components → components}/Form/MultiSelect/MultiSelect.d.ts +3 -3
  18. package/dist/es/components/Form/RadioGroup/RadioGroup.d.ts +16 -0
  19. package/dist/es/{src/components → components}/Form/Select/Select.d.ts +3 -3
  20. package/dist/es/components/Form/Switch/Switch.d.ts +11 -0
  21. package/dist/es/{src/components → components}/Form/TextField/TextField.d.ts +6 -5
  22. package/dist/es/{src/components → components}/Form/TimePicker/TimePicker.d.ts +3 -3
  23. package/dist/es/{src/components → components}/IconButton/IconButton.d.ts +3 -3
  24. package/dist/es/components/IconProvider/IconProvider.d.ts +24 -0
  25. package/dist/es/{src/components → components}/Link/Link.d.ts +1 -2
  26. package/dist/es/components/MenuItem/MenuItem.d.ts +9 -0
  27. package/dist/es/components/Modal/Dialog/Dialog.d.ts +4 -0
  28. package/dist/es/components/Modal/Drawer/Drawer.d.ts +4 -0
  29. package/dist/es/{src/components → components}/Modal/modal.interface.d.ts +4 -1
  30. package/dist/es/{src/components → components}/Navigation/Stepper/Stepper.d.ts +1 -1
  31. package/dist/es/{src/components → components}/Pagination/Pagination.d.ts +2 -1
  32. package/dist/es/{src/components → components}/Snackbar/Snackbar.d.ts +1 -1
  33. package/dist/es/components/TabsPanel/TabsPanel.d.ts +4 -0
  34. package/dist/es/components/TabsPanel/index.d.ts +1 -0
  35. package/dist/es/components/TabsPanel/tab.interface.d.ts +17 -0
  36. package/dist/es/components/Text/Text.d.ts +8 -0
  37. package/dist/es/{src/components → components}/Tooltip/Tooltip.d.ts +1 -1
  38. package/dist/es/{src/components → components}/index.d.ts +3 -2
  39. package/dist/es/index.d.ts +108 -153
  40. package/dist/es/index.js +37 -79
  41. package/package.json +20 -17
  42. package/src/DesignSystemProvider.tsx +21 -36
  43. package/src/components/Accordion/Accordion.tsx +41 -59
  44. package/src/components/BannerNotification/BannerNotification.tsx +19 -20
  45. package/src/components/Breadcrumbs/Breadcrumbs.tsx +32 -28
  46. package/src/components/Button/Button.tsx +70 -17
  47. package/src/components/Chip/Chip.tsx +88 -117
  48. package/src/components/Datatable/Datatable.tsx +49 -37
  49. package/src/components/Datatable/DatatableCellRender.tsx +1 -1
  50. package/src/components/EmbbededNotification/EmbeddedNotification.tsx +78 -69
  51. package/src/components/FileUploader/FileUploader.tsx +767 -0
  52. package/src/components/Form/AutoCompleteMulti/AutoCompleteMulti.tsx +289 -172
  53. package/src/components/Form/AutoCompleteSingle/AutoCompleteSingle.tsx +228 -126
  54. package/src/components/Form/Checkbox/Checkbox.tsx +38 -96
  55. package/src/components/Form/CheckboxGroup/CheckboxGroup.tsx +86 -60
  56. package/src/components/Form/CheckboxGroup/checkboxgroup.interface.ts +0 -15
  57. package/src/components/Form/DatePicker/DatePicker.tsx +88 -40
  58. package/src/components/Form/MultiSelect/MultiSelect.tsx +196 -171
  59. package/src/components/Form/RadioGroup/RadioGroup.tsx +76 -82
  60. package/src/components/Form/Select/Select.tsx +156 -136
  61. package/src/components/Form/Switch/Switch.tsx +87 -47
  62. package/src/components/Form/TextField/TextField.tsx +125 -76
  63. package/src/components/Form/TimePicker/TimePicker.tsx +26 -7
  64. package/src/components/IconButton/IconButton.tsx +64 -39
  65. package/src/components/IconProvider/IconProvider.tsx +90 -69
  66. package/src/components/Link/Link.tsx +6 -10
  67. package/src/components/MenuItem/MenuItem.tsx +35 -23
  68. package/src/components/Modal/Dialog/Dialog.tsx +17 -14
  69. package/src/components/Modal/Drawer/Drawer.tsx +97 -69
  70. package/src/components/Modal/modal.interface.ts +4 -1
  71. package/src/components/Navigation/Stepper/Step.tsx +7 -6
  72. package/src/components/Navigation/Stepper/Stepper.tsx +24 -23
  73. package/src/components/NotistackAdapter/NotistackAdapter.tsx +1 -1
  74. package/src/components/Pagination/Pagination.tsx +131 -118
  75. package/src/components/Snackbar/Snackbar.tsx +29 -29
  76. package/src/components/TabsPanel/TabsPanel.tsx +151 -0
  77. package/src/components/TabsPanel/index.ts +1 -0
  78. package/src/components/TabsPanel/tab.interface.ts +20 -0
  79. package/src/components/Text/Text.tsx +25 -12
  80. package/src/components/Tooltip/Tooltip.tsx +54 -51
  81. package/src/components/index.ts +3 -2
  82. package/src/index.ts +0 -1
  83. package/dist/es/src/components/Datatable/Datatable.d.ts +0 -4
  84. package/dist/es/src/components/Form/AutoCompleteMulti/AutoCompleteMulti.d.ts +0 -19
  85. package/dist/es/src/components/Form/Checkbox/Checkbox.d.ts +0 -14
  86. package/dist/es/src/components/Form/CheckboxGroup/CheckboxGroup.d.ts +0 -12
  87. package/dist/es/src/components/Form/CheckboxGroup/checkboxgroup.interface.d.ts +0 -8
  88. package/dist/es/src/components/Form/RadioGroup/RadioGroup.d.ts +0 -18
  89. package/dist/es/src/components/Form/Switch/Switch.d.ts +0 -10
  90. package/dist/es/src/components/IconProvider/IconProvider.d.ts +0 -19
  91. package/dist/es/src/components/MenuItem/MenuItem.d.ts +0 -10
  92. package/dist/es/src/components/Modal/Dialog/Dialog.d.ts +0 -4
  93. package/dist/es/src/components/Modal/Drawer/Drawer.d.ts +0 -4
  94. package/dist/es/src/components/Text/Text.d.ts +0 -8
  95. package/dist/es/src/index.d.ts +0 -5
  96. package/dist/es/src/interfaces/theme.d.ts +0 -51
  97. package/src/components/UploadDocument/FileUploader.tsx +0 -728
  98. package/src/interfaces/theme.ts +0 -51
  99. /package/dist/es/{src/components → components}/Accordion/index.d.ts +0 -0
  100. /package/dist/es/{src/components → components}/BannerNotification/index.d.ts +0 -0
  101. /package/dist/es/{src/components → components}/Breadcrumbs/index.d.ts +0 -0
  102. /package/dist/es/{src/components → components}/Button/index.d.ts +0 -0
  103. /package/dist/es/{src/components → components}/Chip/index.d.ts +0 -0
  104. /package/dist/es/{src/components → components}/Datatable/DatatableCellRender.d.ts +0 -0
  105. /package/dist/es/{src/components → components}/Datatable/datatable.interface.d.ts +0 -0
  106. /package/dist/es/{src/components → components}/Datatable/index.d.ts +0 -0
  107. /package/dist/es/{src/components → components}/Datatable/use-pagination-props.hook.d.ts +0 -0
  108. /package/dist/es/{src/components → components}/EmbbededNotification/index.d.ts +0 -0
  109. /package/dist/es/{src/components/UploadDocument → components/FileUploader}/fileuploader.interface.d.ts +0 -0
  110. /package/dist/es/{src/components/UploadDocument → components/FileUploader}/index.d.ts +0 -0
  111. /package/dist/es/{src/components → components}/Form/AutoCompleteMulti/index.d.ts +0 -0
  112. /package/dist/es/{src/components → components}/Form/AutoCompleteSingle/index.d.ts +0 -0
  113. /package/dist/es/{src/components → components}/Form/Checkbox/index.d.ts +0 -0
  114. /package/dist/es/{src/components → components}/Form/CheckboxGroup/index.d.ts +0 -0
  115. /package/dist/es/{src/components → components}/Form/DatePicker/index.d.ts +0 -0
  116. /package/dist/es/{src/components → components}/Form/MultiSelect/index.d.ts +0 -0
  117. /package/dist/es/{src/components → components}/Form/RadioGroup/index.d.ts +0 -0
  118. /package/dist/es/{src/components → components}/Form/Select/index.d.ts +0 -0
  119. /package/dist/es/{src/components → components}/Form/Switch/index.d.ts +0 -0
  120. /package/dist/es/{src/components → components}/Form/TextField/index.d.ts +0 -0
  121. /package/dist/es/{src/components → components}/Form/TimePicker/index.d.ts +0 -0
  122. /package/dist/es/{src/components → components}/IconButton/index.d.ts +0 -0
  123. /package/dist/es/{src/components → components}/IconProvider/index.d.ts +0 -0
  124. /package/dist/es/{src/components → components}/Link/index.d.ts +0 -0
  125. /package/dist/es/{src/components → components}/MenuItem/index.d.ts +0 -0
  126. /package/dist/es/{src/components → components}/Modal/Dialog/index.d.ts +0 -0
  127. /package/dist/es/{src/components → components}/Modal/Drawer/index.d.ts +0 -0
  128. /package/dist/es/{src/components → components}/Navigation/Stepper/Step.d.ts +0 -0
  129. /package/dist/es/{src/components → components}/Navigation/Stepper/index.d.ts +0 -0
  130. /package/dist/es/{src/components → components}/Navigation/Stepper/stepper.helper.d.ts +0 -0
  131. /package/dist/es/{src/components → components}/Navigation/Stepper/stepper.interface.d.ts +0 -0
  132. /package/dist/es/{src/components → components}/NotistackAdapter/NotistackAdapter.d.ts +0 -0
  133. /package/dist/es/{src/components → components}/NotistackAdapter/index.d.ts +0 -0
  134. /package/dist/es/{src/components → components}/Pagination/index.d.ts +0 -0
  135. /package/dist/es/{src/components → components}/Pagination/pagination.helper.d.ts +0 -0
  136. /package/dist/es/{src/components → components}/Snackbar/index.d.ts +0 -0
  137. /package/dist/es/{src/components → components}/Text/index.d.ts +0 -0
  138. /package/dist/es/{src/components → components}/Tooltip/index.d.ts +0 -0
  139. /package/dist/es/{src/components → components}/Tooltip/tooltip.interface.d.ts +0 -0
  140. /package/dist/es/{src/helpers → helpers}/GooglePickerWrapper.d.ts +0 -0
  141. /package/dist/es/{src/hooks → hooks}/useGooleDrivePicker.d.ts +0 -0
  142. /package/dist/es/{src/types → types}/googleDrive.d.ts +0 -0
  143. /package/src/components/{UploadDocument → FileUploader}/fileuploader.interface.ts +0 -0
  144. /package/src/components/{UploadDocument → FileUploader}/index.ts +0 -0
@@ -0,0 +1,767 @@
1
+ import React, {
2
+ ChangeEvent,
3
+ DragEvent,
4
+ useCallback,
5
+ useEffect,
6
+ useMemo,
7
+ useRef,
8
+ useState,
9
+ } from 'react';
10
+
11
+ import { faGoogleDrive } from '@fortawesome/free-brands-svg-icons';
12
+ import {
13
+ faEye,
14
+ faFolderOpen,
15
+ faTrash,
16
+ faUpload,
17
+ } from '@fortawesome/pro-regular-svg-icons';
18
+ import {
19
+ faCaretDown,
20
+ faFile,
21
+ faInfoCircle,
22
+ } from '@fortawesome/pro-solid-svg-icons';
23
+ import { Box, IconButton } from '@mui/material';
24
+ import Menu from '@mui/material/Menu';
25
+
26
+ import Button from '@/components/Button';
27
+ import IconProvider from '@/components/IconProvider';
28
+ import MenuItem from '@/components/MenuItem';
29
+ import Text from '@/components/Text';
30
+ import Tooltip from '@/components/Tooltip';
31
+ import GooglePickerWrapper from '@/helpers/GooglePickerWrapper';
32
+
33
+ import {
34
+ AcceptTextType,
35
+ FileDataType,
36
+ GoogleDriveFile,
37
+ } from './fileuploader.interface';
38
+
39
+ interface Props {
40
+ title?: string;
41
+ subTitle?: string;
42
+ titleAddButton?: string;
43
+ helperText?: string;
44
+ titleTooltip?: string;
45
+ files?: FileDataType[];
46
+ isMulti?: boolean;
47
+ accept?: string[];
48
+ acceptText?: AcceptTextType;
49
+ orText?: string;
50
+ fromLocalText?: string;
51
+ fromGoogleDriveText?: string;
52
+ disabled?: boolean;
53
+ error?: string;
54
+ enableGoogleDrive?: boolean;
55
+ googleAuthClientId?: string;
56
+ googleApiKey?: string;
57
+ _isDroppingFile?: boolean; // * Only used for storybook
58
+ validateFile?: (
59
+ _size: number,
60
+ _type: string,
61
+ _accept: string[],
62
+ _setUploadFileError: React.Dispatch<React.SetStateAction<boolean | string>>,
63
+ ) => boolean;
64
+ onTouched?: () => void;
65
+ onFilesDataChange?: (_fileData: FileDataType[] | undefined) => void;
66
+ }
67
+
68
+ const FileUploader = React.forwardRef<HTMLDivElement, Props>(
69
+ (
70
+ {
71
+ title = '',
72
+ subTitle = 'Déposer un fichier ici',
73
+ titleAddButton = 'Ajouter un fichier',
74
+ helperText = '',
75
+ titleTooltip,
76
+ files = [],
77
+ isMulti = false,
78
+ accept = [],
79
+ acceptText = { fileFormat: '', maxSize: '', subText: '' },
80
+ orText = 'ou',
81
+ fromLocalText = 'Depuis votre PC',
82
+ fromGoogleDriveText = 'Depuis Google Drive',
83
+ disabled = false,
84
+ error,
85
+ enableGoogleDrive = false,
86
+ googleAuthClientId,
87
+ googleApiKey,
88
+ _isDroppingFile = false,
89
+ validateFile,
90
+ onTouched,
91
+ onFilesDataChange,
92
+ },
93
+ ref,
94
+ ) => {
95
+ const DEFAULT_COLOR = '%23CBCBCB';
96
+ const ERROR_COLOR = '%23e20e28';
97
+ const DROPPING_COLOR = '%23004F88';
98
+
99
+ // * States
100
+ const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
101
+ // * filesData is used to display the files in the component
102
+ const [filesData, setFilesData] = useState<FileDataType[] | undefined>(
103
+ undefined,
104
+ );
105
+ const [isDroppingFile, setIsDroppingFile] =
106
+ useState<boolean>(_isDroppingFile);
107
+ // * uploadFileError is used to display the error message when the file is not valid from the validateFile function
108
+ const [uploadFileError, setUploadFileError] = useState<string | boolean>(
109
+ false,
110
+ );
111
+ const [isInitialized, setIsInitialized] = useState(false);
112
+
113
+ // * Refs
114
+ const fileInputRef = useRef<HTMLInputElement | null>(null);
115
+
116
+ // * Colors
117
+ const dashedColor = useMemo(() => {
118
+ if ((!isMulti && filesData?.[0]?.name) || disabled) return DEFAULT_COLOR;
119
+ if (isDroppingFile) return DROPPING_COLOR;
120
+ if (error || uploadFileError !== false) return ERROR_COLOR;
121
+ return DEFAULT_COLOR;
122
+ }, [isDroppingFile, error, uploadFileError]);
123
+
124
+ const bgColor = useMemo(() => {
125
+ if ((!isMulti && filesData?.[0]?.name) || disabled) return 'greyXLight';
126
+ if (isDroppingFile) return 'blueHoverEquivalence';
127
+ return 'white';
128
+ }, [filesData?.length, disabled, isDroppingFile]);
129
+
130
+ const titleColor = useMemo(() => {
131
+ if (!isMulti && filesData?.[0]?.name) return 'greyDark';
132
+ if (error || uploadFileError !== false) return 'redError';
133
+ return 'greyXDark';
134
+ }, [filesData?.length, error, uploadFileError]);
135
+
136
+ // * Styles
137
+ const inputCss = useMemo(
138
+ () => ({
139
+ height: '87px',
140
+ backgroundImage: `url("data:image/svg+xml,%3csvg width='100%25' height='100%25' xmlns='http://www.w3.org/2000/svg'%3e%3crect width='100%25' height='100%25' fill='none' rx='4' ry='4' stroke='${dashedColor}' stroke-width='2' stroke-dasharray='8%2c 8' stroke-dashoffset='0' stroke-linecap='round'/%3e%3c/svg%3e")`,
141
+ borderRadius: '4px',
142
+ py: '24px',
143
+ px: '22px',
144
+ display: 'flex',
145
+ justifyContent: 'space-between',
146
+ position: 'relative',
147
+ alignItems: 'center',
148
+ overflow: 'hidden',
149
+ backgroundColor: bgColor,
150
+ }),
151
+ [dashedColor, bgColor],
152
+ );
153
+
154
+ // * Events handlers
155
+ const handleClick = (event: React.MouseEvent<HTMLElement>) => {
156
+ setAnchorEl(event.currentTarget);
157
+ };
158
+ const handleClose = () => {
159
+ setAnchorEl(null);
160
+ };
161
+
162
+ const handleDragOver = (event: DragEvent<HTMLDivElement>) => {
163
+ event.preventDefault();
164
+ event.stopPropagation();
165
+
166
+ if ((!isMulti && filesData?.[0]?.name) || disabled) return;
167
+
168
+ setIsDroppingFile(true);
169
+ };
170
+
171
+ const handleDragLeave = (event: DragEvent<HTMLDivElement>) => {
172
+ event.preventDefault();
173
+ event.stopPropagation();
174
+
175
+ if ((!isMulti && filesData?.[0]?.name) || disabled) return;
176
+
177
+ setIsDroppingFile(false);
178
+ };
179
+
180
+ // * Handle file change and file deletion
181
+ const handleFileChange = useCallback(
182
+ async (
183
+ _event: ChangeEvent<HTMLInputElement> | null,
184
+ data?: {
185
+ docs: GoogleDriveFile[];
186
+ } | null,
187
+ token: string | null = null,
188
+ accept: string[] = [],
189
+ fakeClick?: boolean,
190
+ ) => {
191
+ if (fakeClick) {
192
+ fileInputRef?.current?.click();
193
+ return;
194
+ }
195
+
196
+ handleClose();
197
+ onTouched?.();
198
+
199
+ if (token && data && data.docs && data.docs.length > 0) {
200
+ // * Google Drive
201
+ const validDocs = data.docs.filter((doc) =>
202
+ validateFile
203
+ ? validateFile(
204
+ doc.sizeBytes,
205
+ doc.mimeType,
206
+ accept,
207
+ setUploadFileError,
208
+ )
209
+ : true,
210
+ );
211
+
212
+ const googleFileData = await Promise.all(
213
+ validDocs.map(async (doc) => {
214
+ const baseUrl = `https://www.googleapis.com/drive/v3/files/${doc.id}`;
215
+ const params = new URLSearchParams({
216
+ alt: 'media',
217
+ supportsAllDrives: 'true',
218
+ });
219
+
220
+ const url = `${baseUrl}?${params}`;
221
+
222
+ const response = await fetch(url, {
223
+ method: 'GET',
224
+ headers: {
225
+ Authorization: `Bearer ${token}`,
226
+ },
227
+ });
228
+
229
+ const blob = await response.blob();
230
+
231
+ let file: File | undefined = undefined;
232
+
233
+ if (response.status === 200) {
234
+ file = new File([blob], doc.name, {
235
+ type: doc.mimeType,
236
+ });
237
+ }
238
+
239
+ return {
240
+ name: doc.name,
241
+ size: Math.round(doc.sizeBytes / 1024), // * Convert size to KB
242
+ type: doc.mimeType,
243
+ url: `https://drive.google.com/file/d/${doc.id}/view?usp=drive_we`,
244
+ file: file,
245
+ driveFileId: doc.id,
246
+ driveAccessToken: token,
247
+ };
248
+ }),
249
+ );
250
+
251
+ setFilesData([...(filesData || []), ...googleFileData]);
252
+ } else if (
253
+ fileInputRef.current?.files &&
254
+ fileInputRef.current?.files?.length > 0
255
+ ) {
256
+ // * Local Files
257
+ const fileList = Array.from(fileInputRef.current?.files);
258
+
259
+ const newFileData: FileDataType[] = fileList
260
+ .map((file) => {
261
+ if (
262
+ validateFile &&
263
+ !validateFile(file.size, file.type, accept, setUploadFileError)
264
+ ) {
265
+ setIsDroppingFile(false);
266
+ return null;
267
+ }
268
+
269
+ return {
270
+ name: file?.name ?? '',
271
+ size: Math.round(file.size / 1024),
272
+ type: file.type,
273
+ url: URL.createObjectURL(file),
274
+ file: file,
275
+ };
276
+ })
277
+ .filter((file) => file !== null);
278
+
279
+ if (newFileData && newFileData?.length > 0) {
280
+ setFilesData([...(filesData || []), ...newFileData]);
281
+ }
282
+ }
283
+
284
+ setIsDroppingFile(false);
285
+ },
286
+ [
287
+ fileInputRef,
288
+ filesData,
289
+ setIsDroppingFile,
290
+ setFilesData,
291
+ validateFile,
292
+ onTouched,
293
+ handleClose,
294
+ ],
295
+ );
296
+
297
+ const handleDelete = useCallback(
298
+ (fileIndex: number) => {
299
+ //* Remove the file from the currentFiles array
300
+ if (filesData && filesData.length > 0) {
301
+ const fileToDelete = filesData[fileIndex]?.file;
302
+ if (fileToDelete) {
303
+ // * Revoke the object URL to free up memory, use try and catch to avoid errors on the other files not imported with the file input
304
+ try {
305
+ const fileURL = URL.createObjectURL(fileToDelete);
306
+ URL.revokeObjectURL(fileURL);
307
+ } catch {
308
+ // * Do nothing
309
+ }
310
+ }
311
+ }
312
+
313
+ //* Remove the file from the filesData array
314
+ if (filesData && filesData.length === 1) {
315
+ setFilesData([]);
316
+ } else {
317
+ setFilesData(
318
+ Object.values(filesData || []).filter(
319
+ (_, index) => index !== fileIndex,
320
+ ),
321
+ );
322
+ }
323
+
324
+ setIsDroppingFile(false);
325
+ },
326
+ [filesData, setIsDroppingFile, setFilesData],
327
+ );
328
+
329
+ // * Utils
330
+ const extractExtensions = (types: string[]): string[] => {
331
+ return types.map((type) => {
332
+ const extension = type.split('/')[1].toUpperCase();
333
+ return extension;
334
+ });
335
+ };
336
+
337
+ const initializeFiles = async () => {
338
+ const initialFiles = await Promise.all(
339
+ files.map(async (file) => {
340
+ let fileTmp: File | undefined = undefined;
341
+
342
+ if (isMulti) {
343
+ const response = await fetch(file.url);
344
+ const blob = await response.blob();
345
+ fileTmp = new File([blob], file.name, {
346
+ type: file.type,
347
+ });
348
+ }
349
+
350
+ return {
351
+ name: file.name,
352
+ size: Math.round(file.size / 1024), // Convert size to KB
353
+ type: file.type,
354
+ url: file.url,
355
+ file: fileTmp,
356
+ };
357
+ }),
358
+ );
359
+
360
+ setFilesData(initialFiles);
361
+ };
362
+
363
+ // * UseEffects
364
+
365
+ // * Manage isInitialized state
366
+ useEffect(() => {
367
+ if (!isInitialized && filesData && filesData.length >= 0) {
368
+ setIsInitialized(true);
369
+ }
370
+ }, [isInitialized, filesData]);
371
+
372
+ // * Manage filesData state
373
+ useEffect(() => {
374
+ if (isInitialized && typeof filesData !== 'undefined') {
375
+ onFilesDataChange?.(filesData);
376
+ } else if (!isInitialized && typeof filesData === 'undefined') {
377
+ initializeFiles();
378
+ }
379
+ }, [filesData]);
380
+
381
+ // * Revoke the object URL to free up memory
382
+ useEffect(() => {
383
+ return () => {
384
+ if (!filesData?.length) return;
385
+
386
+ try {
387
+ // * Only for file imported with the file input, use try and catch to avoid errors on the other files
388
+ filesData.forEach((_fileData) => {
389
+ if (_fileData?.file) {
390
+ const fileURL = URL.createObjectURL(_fileData?.file);
391
+ URL.revokeObjectURL(fileURL);
392
+ }
393
+ });
394
+ } catch {
395
+ // * Do nothing
396
+ }
397
+ };
398
+ }, [filesData]);
399
+
400
+ return (
401
+ <Box
402
+ ref={ref}
403
+ data-testid="ds-uploader-document"
404
+ sx={{
405
+ pb: 3,
406
+ }}
407
+ >
408
+ <Box sx={{ display: 'inline-flex', alignItems: 'center' }}>
409
+ <Text variant="body2Medium" color={titleColor} pl={1}>
410
+ {title}
411
+ </Text>
412
+ {titleTooltip && (
413
+ <Box sx={{ ml: 1 }}>
414
+ <Tooltip
415
+ title={titleTooltip}
416
+ sx={{
417
+ display: 'flex',
418
+ }}
419
+ >
420
+ <IconProvider icon={faInfoCircle} size="sm" color="blueInfo" />
421
+ </Tooltip>
422
+ </Box>
423
+ )}
424
+ </Box>
425
+
426
+ <Box
427
+ mt={1}
428
+ p={2}
429
+ sx={inputCss}
430
+ onDragOver={handleDragOver}
431
+ onDragEnter={handleDragOver}
432
+ onDragLeave={handleDragLeave}
433
+ onDragEnd={handleDragLeave}
434
+ data-testid="ds-drag-drop-area"
435
+ >
436
+ <Box
437
+ sx={{
438
+ opacity: 0,
439
+ position: 'absolute',
440
+ top: '0px',
441
+ right: '0px',
442
+ width: '100%',
443
+ height: '100%',
444
+ 'input[type="file"]': {
445
+ cursor:
446
+ disabled || (filesData?.[0]?.name && !isMulti)
447
+ ? 'not-allowed'
448
+ : 'pointer',
449
+ color: 'transparent',
450
+ width: '100%',
451
+ height: '100%',
452
+ },
453
+ }}
454
+ >
455
+ <input
456
+ title=""
457
+ data-testid="ds-document-input"
458
+ disabled={!!(!isMulti && filesData?.[0]?.name) || disabled}
459
+ type="file"
460
+ ref={fileInputRef}
461
+ onChange={(event) => handleFileChange(event, null, null, accept)}
462
+ onBlur={onTouched}
463
+ multiple={isMulti}
464
+ accept={accept.join(',')}
465
+ />
466
+ </Box>
467
+ <Box sx={{ display: 'flex', flexDirection: 'column' }}>
468
+ <Text
469
+ variant="body2Medium"
470
+ color={
471
+ (!isMulti && filesData?.[0]?.name && 'greyDark') || 'greyXDark'
472
+ }
473
+ data-testid="ds-sub-title"
474
+ >
475
+ {subTitle}
476
+ </Text>
477
+ <Box sx={{ maxWidth: '240px' }}>
478
+ <Text
479
+ variant="caption"
480
+ sx={{
481
+ color: 'greyDark',
482
+ mt: 0.5,
483
+ }}
484
+ data-testid="ds-accept-text"
485
+ >
486
+ Format{' '}
487
+ {(acceptText?.fileFormat &&
488
+ acceptText?.fileFormat?.trim() !== ''
489
+ ? acceptText.fileFormat
490
+ : extractExtensions(accept).join(', ')) + ' - '}
491
+ {(acceptText.maxSize && acceptText.maxSize) || 'Max. 10 Mo'}
492
+ {acceptText.subText && (
493
+ <>
494
+ <br />
495
+ {acceptText.subText}
496
+ </>
497
+ )}
498
+ </Text>
499
+ </Box>
500
+ </Box>
501
+ <Box>
502
+ <Text
503
+ variant="body2Medium"
504
+ color={
505
+ (!isMulti && filesData?.[0]?.name && 'greyDark') || 'greyXDark'
506
+ }
507
+ data-testid="ds-or-text"
508
+ >
509
+ {orText}
510
+ </Text>
511
+ </Box>
512
+ <Box>
513
+ {(enableGoogleDrive && (
514
+ <>
515
+ <Button
516
+ variant="secondary"
517
+ onClick={handleClick}
518
+ disabled={!!(!isMulti && filesData?.[0]?.name) || disabled}
519
+ startIcon={
520
+ <IconProvider
521
+ ml={1}
522
+ color="inherit"
523
+ icon={faUpload}
524
+ size="md"
525
+ />
526
+ }
527
+ endIcon={
528
+ <IconProvider
529
+ color="inherit"
530
+ icon={faCaretDown}
531
+ size="md"
532
+ />
533
+ }
534
+ data-testid="ds-add-button"
535
+ >
536
+ {titleAddButton}
537
+ </Button>
538
+ <Menu
539
+ data-testid="ds-seizure-card-menu"
540
+ anchorEl={anchorEl}
541
+ open={Boolean(anchorEl)}
542
+ onClose={handleClose}
543
+ anchorOrigin={{
544
+ vertical: 'bottom',
545
+ horizontal: 'right',
546
+ }}
547
+ transformOrigin={{
548
+ vertical: 'top',
549
+ horizontal: 'right',
550
+ }}
551
+ slotProps={{
552
+ paper: {
553
+ style: {
554
+ marginTop: '8px',
555
+ marginLeft: '0',
556
+ },
557
+ },
558
+ }}
559
+ >
560
+ <MenuItem
561
+ onClick={() => handleFileChange(null, null, null, [], true)}
562
+ data-testid="ds-menu-item-local"
563
+ >
564
+ <Box gap={1} display="inline-flex" alignItems="center">
565
+ <IconProvider
566
+ size="sm"
567
+ icon={faFolderOpen}
568
+ color="inherit"
569
+ />
570
+ <Text variant="body2">{fromLocalText}</Text>
571
+ </Box>
572
+ </MenuItem>
573
+ <GooglePickerWrapper
574
+ callback={(data, token) =>
575
+ handleFileChange(null, data, token, accept)
576
+ }
577
+ multiselect={true}
578
+ navHidden={false}
579
+ googleAuthClientId={googleAuthClientId ?? ''}
580
+ googleApiKey={googleApiKey ?? ''}
581
+ scopes="https://www.googleapis.com/auth/drive.file"
582
+ viewId="FOLDERS"
583
+ >
584
+ <MenuItem
585
+ onClick={handleClose}
586
+ data-testid="ds-menu-item-google-drive"
587
+ >
588
+ <Box
589
+ gap={1}
590
+ display="inline-flex"
591
+ alignItems="center"
592
+ color="inherit"
593
+ >
594
+ <IconProvider size="sm" icon={faGoogleDrive} />
595
+ <Text variant="body2">{fromGoogleDriveText}</Text>
596
+ </Box>
597
+ </MenuItem>
598
+ </GooglePickerWrapper>
599
+ </Menu>
600
+ </>
601
+ )) || (
602
+ <Button
603
+ variant="secondary"
604
+ onClick={() => handleFileChange(null, null, null, [], true)}
605
+ disabled={!!(!isMulti && filesData?.[0]?.name) || disabled}
606
+ startIcon={
607
+ <IconProvider
608
+ icon={faUpload}
609
+ size="md"
610
+ ml={1}
611
+ color="inherit"
612
+ />
613
+ }
614
+ data-testid="ds-add-button"
615
+ >
616
+ {titleAddButton}
617
+ </Button>
618
+ )}
619
+ </Box>
620
+ </Box>
621
+ {(error || helperText || uploadFileError !== false) && (
622
+ <Box pl={1} pt={1}>
623
+ <Text
624
+ variant="caption"
625
+ color={
626
+ ((error || uploadFileError !== false) && 'error.main') ||
627
+ 'greyDark'
628
+ }
629
+ data-testid="ds-helper-text"
630
+ >
631
+ {uploadFileError !== false
632
+ ? uploadFileError
633
+ : (error ?? helperText)}
634
+ </Text>
635
+ </Box>
636
+ )}
637
+ <Box sx={{ mt: '16px' }}>
638
+ {(isMulti
639
+ ? Object.values(filesData ?? [])
640
+ : Object.values(filesData ?? [])?.slice(0, 1)
641
+ )?.map((fileData, index) => (
642
+ <Box
643
+ key={index}
644
+ sx={{
645
+ display: 'flex',
646
+ alignItems: 'center',
647
+ border: '0.5px solid',
648
+ borderColor: error ? 'error.main' : 'greyLightDefaultBorder',
649
+ borderRadius: '0',
650
+ justifyContent: 'space-between',
651
+ maxHeight: '50px',
652
+ p: 4,
653
+ mb: 2,
654
+ }}
655
+ data-testid={`ds-file-item-${index}`}
656
+ >
657
+ <Box
658
+ sx={{
659
+ display: 'flex',
660
+ alignItems: 'center',
661
+ flexShrink: 1,
662
+ minWidth: 0,
663
+ }}
664
+ >
665
+ <IconProvider
666
+ icon={faFile}
667
+ color="greyMediumInactive"
668
+ size="sm"
669
+ mr={1}
670
+ />
671
+ <Text
672
+ variant="body2Medium"
673
+ color="greyXDark"
674
+ sx={{
675
+ overflow: 'hidden',
676
+ whiteSpace: 'nowrap',
677
+ textAlign: 'left',
678
+ textOverflow: 'ellipsis',
679
+ flexShrink: 1,
680
+ }}
681
+ data-testid={`ds-file-name-${index}`}
682
+ >
683
+ {fileData?.name}
684
+ </Text>
685
+ {fileData?.size && fileData.size !== 0 ? (
686
+ <Text
687
+ component="span"
688
+ variant="body2"
689
+ color="greyDark"
690
+ sx={{ minWidth: '41px', marginLeft: '8px' }}
691
+ data-testid={`ds-file-size-${index}`}
692
+ >
693
+ ({fileData?.size} ko)
694
+ </Text>
695
+ ) : null}
696
+ </Box>
697
+ <Box
698
+ sx={{
699
+ display: 'flex',
700
+ alignItems: 'center',
701
+ flexShrink: 0,
702
+ }}
703
+ >
704
+ <IconButton
705
+ size="medium"
706
+ color="primary"
707
+ sx={{
708
+ height: '42px',
709
+ width: '42px',
710
+ mx: 1 / 2,
711
+ outline: 'none !important',
712
+ borderRadius: '4px',
713
+ '&:hover': {
714
+ backgroundColor: 'blueHoverOpacity12',
715
+ },
716
+ }}
717
+ data-testid={`ds-view-btn-${index}`}
718
+ onClick={() => {
719
+ if (fileData?.url) {
720
+ window.open(fileData.url, '_blank');
721
+ }
722
+ }}
723
+ >
724
+ <IconProvider
725
+ icon={faEye}
726
+ color="grey"
727
+ size="md"
728
+ height="16px"
729
+ width="16px"
730
+ />
731
+ </IconButton>
732
+ <IconButton
733
+ size="medium"
734
+ color="primary"
735
+ sx={{
736
+ mx: 1 / 2,
737
+ height: '42px',
738
+ width: '42px',
739
+ outline: 'none !important',
740
+ borderRadius: '4px',
741
+ '&:hover': {
742
+ backgroundColor: 'blueHoverOpacity12',
743
+ },
744
+ }}
745
+ data-testid={`ds-delete-btn-${index}`}
746
+ onClick={() => handleDelete(index)}
747
+ >
748
+ <IconProvider
749
+ icon={faTrash}
750
+ color="grey"
751
+ size="md"
752
+ height="16px"
753
+ width="16px"
754
+ />
755
+ </IconButton>
756
+ </Box>
757
+ </Box>
758
+ ))}
759
+ </Box>
760
+ </Box>
761
+ );
762
+ },
763
+ );
764
+
765
+ FileUploader.displayName = 'FileUploader';
766
+
767
+ export default FileUploader;