@groupeactual/ui-kit 1.7.1 → 1.7.3

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.
@@ -32,6 +32,7 @@ type WithoutPaginationProps = {
32
32
  };
33
33
  export type PaginationProps = {
34
34
  withPagination: true;
35
+ hideTotal: false;
35
36
  setPage: (_page: number) => void;
36
37
  setLimit: (_limit: number) => void;
37
38
  trans: PaginationTrans;
@@ -3,11 +3,12 @@ interface Props {
3
3
  totalString: string;
4
4
  totalPerPageString: string;
5
5
  totalRows: number;
6
+ hideTotal?: boolean;
6
7
  limitsPerPage?: number[];
7
8
  page?: number;
8
9
  limit?: number;
9
10
  setPage?: (_page: number) => void;
10
11
  setLimit?: (_limit: number) => void;
11
12
  }
12
- declare const Pagination: ({ totalString, totalPerPageString, limitsPerPage, setLimit, setPage, page, totalRows, limit, }: Props) => React.JSX.Element;
13
+ declare const Pagination: ({ totalString, totalPerPageString, hideTotal, limitsPerPage, page, totalRows, limit, setLimit, setPage, }: Props) => React.JSX.Element;
13
14
  export default Pagination;
@@ -11,6 +11,8 @@ interface Props {
11
11
  accept?: string[];
12
12
  acceptText?: AcceptTextType;
13
13
  orText?: string;
14
+ fromLocalText?: string;
15
+ fromGoogleDriveText?: string;
14
16
  disabled?: boolean;
15
17
  error?: string;
16
18
  enableGoogleDrive?: boolean;
@@ -19,7 +21,7 @@ interface Props {
19
21
  _isDroppingFile?: boolean;
20
22
  validateFile?: (_size: number, _type: string, _accept: string[], _setUploadFileError: React.Dispatch<React.SetStateAction<boolean | string>>) => boolean;
21
23
  onTouched?: () => void;
22
- onFilesDataChange?: (_fileData: FileDataType[] | undefined, _filesInput?: File[]) => void;
24
+ onFilesDataChange?: (_fileData: FileDataType[] | undefined) => void;
23
25
  }
24
- declare const FileUploader: ({ title, subTitle, titleAddButton, helperText, titleTooltip, files, isMulti, accept, acceptText, orText, disabled, error, enableGoogleDrive, googleAuthClientId, googleApiKey, _isDroppingFile, validateFile, onTouched, onFilesDataChange, }: Props) => React.JSX.Element;
26
+ declare const FileUploader: ({ title, subTitle, titleAddButton, helperText, titleTooltip, files, isMulti, accept, acceptText, orText, fromLocalText, fromGoogleDriveText, disabled, error, enableGoogleDrive, googleAuthClientId, googleApiKey, _isDroppingFile, validateFile, onTouched, onFilesDataChange, }: Props) => React.JSX.Element;
25
27
  export default FileUploader;
@@ -8,6 +8,7 @@ export interface FileDataType {
8
8
  size: number;
9
9
  type: string;
10
10
  url: string;
11
+ file?: File;
11
12
  driveFileId?: string;
12
13
  driveAccessToken?: string;
13
14
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@groupeactual/ui-kit",
3
- "version": "1.7.1",
3
+ "version": "1.7.3",
4
4
  "type": "module",
5
5
  "description": "A simple template for a custom React component library",
6
6
  "main": "dist/cjs/index.js",
@@ -14,7 +14,7 @@
14
14
  "@types/google.picker": "^0.0.46",
15
15
  "@types/styled-components": "^5.1.34",
16
16
  "framer-motion": "^11.12.0",
17
- "eslint": "^9.15.0",
17
+ "eslint": "^9.16.0",
18
18
  "eslint-config-prettier": "^9.1.0",
19
19
  "eslint-plugin-import": "^2.31.0",
20
20
  "eslint-plugin-jest": "^28.9.0",
@@ -33,13 +33,13 @@
33
33
  "@mui/x-date-pickers": "7.15.0",
34
34
  "@mui/x-date-pickers-pro": "7.15.0",
35
35
  "styled-components": "^6.1.13",
36
- "@groupeactual/design-tokens": "1.7.1"
36
+ "@groupeactual/design-tokens": "1.7.3"
37
37
  },
38
38
  "scripts": {
39
39
  "build": "rollup -c",
40
40
  "build:watch": "rollup --bundleConfigAsCjs -c -w",
41
41
  "dev": "npm run build:watch",
42
- "eslint": "eslint --config \"eslint.config.js\" --color \".\"",
42
+ "eslint": "eslint --config \"eslint.config.js\" --color \".\" --max-warnings=0",
43
43
  "lint": "npm run eslint"
44
44
  }
45
45
  }
@@ -35,7 +35,7 @@ const Datatable = <T extends object>({
35
35
  isTableLayoutFixed = false,
36
36
  ...props
37
37
  }: Props<T>) => {
38
- const { trans, limits, withTopPagination, setPage, setLimit } =
38
+ const { trans, limits, withTopPagination, hideTotal, setPage, setLimit } =
39
39
  usePaginationProps<T>(props);
40
40
  const theme = useTheme();
41
41
 
@@ -75,6 +75,7 @@ const Datatable = <T extends object>({
75
75
  page={currentPage}
76
76
  limit={itemsPerPage}
77
77
  setPage={setPage}
78
+ hideTotal={hideTotal}
78
79
  setLimit={setLimit}
79
80
  limitsPerPage={limits}
80
81
  totalPerPageString={trans.totalPerPage}
@@ -40,6 +40,7 @@ type WithoutPaginationProps = {
40
40
 
41
41
  export type PaginationProps = {
42
42
  withPagination: true;
43
+ hideTotal: false;
43
44
  setPage: (_page: number) => void;
44
45
  setLimit: (_limit: number) => void;
45
46
  trans: PaginationTrans;
@@ -1,4 +1,3 @@
1
- /* eslint-disable @typescript-eslint/no-empty-function */
2
1
  import { PaginationProps, PaginationTrans, Props } from './datatable.interface';
3
2
 
4
3
  const usePaginationProps = <T extends object>(
@@ -9,6 +8,7 @@ const usePaginationProps = <T extends object>(
9
8
  let withTopPagination = false;
10
9
  let setPage: (_page: number) => void = () => {};
11
10
  let setLimit: (_limit: number) => void = () => {};
11
+ let hideTotal = false;
12
12
 
13
13
  if ('trans' in props && props.trans) {
14
14
  trans = props.trans;
@@ -30,10 +30,15 @@ const usePaginationProps = <T extends object>(
30
30
  setLimit = props.setLimit;
31
31
  }
32
32
 
33
+ if ('hideTotal' in props) {
34
+ hideTotal = props.hideTotal;
35
+ }
36
+
33
37
  return {
34
38
  trans,
35
39
  limits,
36
40
  withTopPagination,
41
+ hideTotal,
37
42
  setPage,
38
43
  setLimit,
39
44
  };
@@ -19,6 +19,7 @@ interface Props {
19
19
  totalString: string;
20
20
  totalPerPageString: string;
21
21
  totalRows: number;
22
+ hideTotal?: boolean;
22
23
  limitsPerPage?: number[];
23
24
  page?: number;
24
25
  limit?: number;
@@ -29,12 +30,13 @@ interface Props {
29
30
  const Pagination = ({
30
31
  totalString,
31
32
  totalPerPageString,
33
+ hideTotal = false,
32
34
  limitsPerPage = [5, 10, 20],
33
- setLimit,
34
- setPage,
35
35
  page = 1,
36
36
  totalRows,
37
37
  limit,
38
+ setLimit,
39
+ setPage,
38
40
  }: Props) => {
39
41
  const theme = useTheme();
40
42
  const StyledPagination = useMemo(
@@ -88,18 +90,22 @@ const Pagination = ({
88
90
  }}
89
91
  >
90
92
  <Box display="flex" alignItems="center">
91
- <Text variant="body1Medium">
92
- {totalRows} {totalString}
93
- </Text>
94
- <Divider
95
- orientation="vertical"
96
- sx={{
97
- color: 'greyXLight',
98
- width: '1px',
99
- height: '33px',
100
- marginX: '16px',
101
- }}
102
- />
93
+ {!hideTotal && (
94
+ <>
95
+ <Text variant="body1Medium">
96
+ {totalRows} {totalString}
97
+ </Text>
98
+ <Divider
99
+ orientation="vertical"
100
+ sx={{
101
+ color: 'greyXLight',
102
+ width: '1px',
103
+ height: '33px',
104
+ marginX: '16px',
105
+ }}
106
+ />
107
+ </>
108
+ )}
103
109
  <Select
104
110
  className="dac-select-label"
105
111
  labelId="select-label"
@@ -22,7 +22,6 @@ import {
22
22
  } from '@fortawesome/pro-solid-svg-icons';
23
23
  import { Box, IconButton } from '@mui/material';
24
24
  import Menu from '@mui/material/Menu';
25
- import { set } from 'lodash';
26
25
 
27
26
  import Button from '@/components/Button';
28
27
  import IconProvider from '@/components/IconProvider';
@@ -48,6 +47,8 @@ interface Props {
48
47
  accept?: string[];
49
48
  acceptText?: AcceptTextType;
50
49
  orText?: string;
50
+ fromLocalText?: string;
51
+ fromGoogleDriveText?: string;
51
52
  disabled?: boolean;
52
53
  error?: string;
53
54
  enableGoogleDrive?: boolean;
@@ -61,10 +62,7 @@ interface Props {
61
62
  _setUploadFileError: React.Dispatch<React.SetStateAction<boolean | string>>,
62
63
  ) => boolean;
63
64
  onTouched?: () => void;
64
- onFilesDataChange?: (
65
- _fileData: FileDataType[] | undefined,
66
- _filesInput?: File[],
67
- ) => void;
65
+ onFilesDataChange?: (_fileData: FileDataType[] | undefined) => void;
68
66
  }
69
67
 
70
68
  const FileUploader = ({
@@ -78,6 +76,8 @@ const FileUploader = ({
78
76
  accept = [],
79
77
  acceptText = { fileFormat: '', maxSize: '', subText: '' },
80
78
  orText = 'ou',
79
+ fromLocalText = 'Depuis votre PC',
80
+ fromGoogleDriveText = 'Depuis Google Drive',
81
81
  disabled = false,
82
82
  error,
83
83
  enableGoogleDrive = false,
@@ -94,8 +94,6 @@ const FileUploader = ({
94
94
 
95
95
  // * States
96
96
  const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
97
- // * currentFiles is used only to revoke the object URL when the component is unmounted for performance reasons
98
- const [currentFiles, setCurrentFiles] = useState<File[]>([]);
99
97
  // * filesData is used to display the files in the component
100
98
  const [filesData, setFilesData] = useState<FileDataType[] | undefined>([]);
101
99
  const [isDroppingFile, setIsDroppingFile] =
@@ -175,7 +173,7 @@ const FileUploader = ({
175
173
 
176
174
  // * Handle file change and file deletion
177
175
  const handleFileChange = useCallback(
178
- (
176
+ async (
179
177
  _event: ChangeEvent<HTMLInputElement> | null,
180
178
  data?: {
181
179
  docs: GoogleDriveFile[];
@@ -192,12 +190,64 @@ const FileUploader = ({
192
190
  handleClose();
193
191
  onTouched?.();
194
192
 
195
- // * Local Files
196
- if (
193
+ if (token && data && data.docs && data.docs.length > 0) {
194
+ // * Google Drive
195
+ const validDocs = data.docs.filter((doc) =>
196
+ validateFile
197
+ ? validateFile(
198
+ doc.sizeBytes,
199
+ doc.mimeType,
200
+ accept,
201
+ setUploadFileError,
202
+ )
203
+ : true,
204
+ );
205
+
206
+ const googleFileData = await Promise.all(
207
+ validDocs.map(async (doc) => {
208
+ const baseUrl = `https://www.googleapis.com/drive/v3/files/${doc.id}`;
209
+ const params = new URLSearchParams({
210
+ alt: 'media',
211
+ supportsAllDrives: 'true',
212
+ });
213
+
214
+ const url = `${baseUrl}?${params}`;
215
+
216
+ const response = await fetch(url, {
217
+ method: 'GET',
218
+ headers: {
219
+ Authorization: `Bearer ${token}`,
220
+ },
221
+ });
222
+
223
+ const blob = await response.blob();
224
+
225
+ let file: File | undefined = undefined;
226
+
227
+ if (response.status === 200) {
228
+ file = new File([blob], doc.name, {
229
+ type: doc.mimeType,
230
+ });
231
+ }
232
+
233
+ return {
234
+ name: doc.name,
235
+ size: Math.round(doc.sizeBytes / 1024), // * Convert size to KB
236
+ type: doc.mimeType,
237
+ url: `https://drive.google.com/file/d/${doc.id}/view?usp=drive_we`,
238
+ file: file,
239
+ driveFileId: doc.id,
240
+ driveAccessToken: token,
241
+ };
242
+ }),
243
+ );
244
+
245
+ setFilesData([...(filesData || []), ...googleFileData]);
246
+ } else if (
197
247
  fileInputRef.current?.files &&
198
248
  fileInputRef.current?.files?.length > 0
199
249
  ) {
200
- const validInputFiles: File[] = [];
250
+ // * Local Files
201
251
  const fileList = Array.from(fileInputRef.current?.files);
202
252
 
203
253
  const newFileData: FileDataType[] = fileList
@@ -210,54 +260,27 @@ const FileUploader = ({
210
260
  return null;
211
261
  }
212
262
 
213
- validInputFiles.push(file);
214
-
215
263
  return {
216
264
  name: file?.name ?? '',
217
265
  size: Math.round(file.size / 1024),
218
266
  type: file.type,
219
267
  url: URL.createObjectURL(file),
268
+ file: file,
220
269
  };
221
270
  })
222
- .filter((file): file is FileDataType => file !== null);
271
+ .filter((file) => file !== null);
223
272
 
224
273
  if (newFileData && newFileData?.length > 0) {
225
- setCurrentFiles([...(currentFiles || []), ...validInputFiles]);
226
274
  setFilesData([...(filesData || []), ...newFileData]);
227
275
  }
228
- } else if (token && data && data.docs && data.docs.length > 0) {
229
- // * Google Drive
230
- const validDocs = data.docs.filter((doc) =>
231
- validateFile
232
- ? validateFile(
233
- doc.sizeBytes,
234
- doc.mimeType,
235
- accept,
236
- setUploadFileError,
237
- )
238
- : true,
239
- );
240
-
241
- const googleFileData = validDocs.map((doc) => ({
242
- name: doc.name,
243
- size: Math.round(doc.sizeBytes / 1024), // * Convert size to KB
244
- type: doc.mimeType,
245
- url: `https://drive.google.com/file/d/${doc.id}/view?usp=drive_we`,
246
- driveFileId: doc.id,
247
- driveAccessToken: token,
248
- }));
249
-
250
- setFilesData([...(filesData || []), ...googleFileData]);
251
276
  }
252
277
 
253
278
  setIsDroppingFile(false);
254
279
  },
255
280
  [
256
281
  fileInputRef,
257
- currentFiles,
258
282
  filesData,
259
283
  setIsDroppingFile,
260
- setCurrentFiles,
261
284
  setFilesData,
262
285
  validateFile,
263
286
  onTouched,
@@ -268,8 +291,8 @@ const FileUploader = ({
268
291
  const handleDelete = useCallback(
269
292
  (fileIndex: number) => {
270
293
  //* Remove the file from the currentFiles array
271
- if (currentFiles && currentFiles.length > 0) {
272
- const fileToDelete = currentFiles[fileIndex];
294
+ if (filesData && filesData.length > 0) {
295
+ const fileToDelete = filesData[fileIndex]?.file;
273
296
  if (fileToDelete) {
274
297
  // * 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
275
298
  try {
@@ -279,14 +302,6 @@ const FileUploader = ({
279
302
  // * Do nothing
280
303
  }
281
304
  }
282
-
283
- if (currentFiles.length === 1) {
284
- setCurrentFiles([]);
285
- } else {
286
- setCurrentFiles(
287
- currentFiles.filter((_, index) => index !== fileIndex),
288
- );
289
- }
290
305
  }
291
306
 
292
307
  //* Remove the file from the filesData array
@@ -302,7 +317,7 @@ const FileUploader = ({
302
317
 
303
318
  setIsDroppingFile(false);
304
319
  },
305
- [currentFiles, filesData, setIsDroppingFile, setCurrentFiles, setFilesData],
320
+ [filesData, setIsDroppingFile, setFilesData],
306
321
  );
307
322
 
308
323
  // * Utils
@@ -318,54 +333,54 @@ const FileUploader = ({
318
333
  // * Initialize the component with the files passed as props
319
334
  useEffect(() => {
320
335
  if (!isInitialized && files.length > 0) {
321
- const FilesDataDto: FileDataType[] = files.map((file) => ({
322
- name: file.name,
323
- size: Math.round(file.size / 1024), // Convert size to KB
324
- type: file.type,
325
- url: file.url,
326
- }));
327
-
328
336
  const initializeFiles = async () => {
329
337
  const initialFiles = await Promise.all(
330
- FilesDataDto.map(async (fileData) => {
331
- const response = await fetch(fileData.url);
338
+ files.map(async (file) => {
339
+ const response = await fetch(file.url);
332
340
  const blob = await response.blob();
333
341
 
334
- return new File([blob], fileData.name, {
335
- type: fileData.type,
336
- });
342
+ return {
343
+ name: file.name,
344
+ size: Math.round(file.size / 1024), // Convert size to KB
345
+ type: file.type,
346
+ url: file.url,
347
+ file: new File([blob], file.name, {
348
+ type: file.type,
349
+ }),
350
+ };
337
351
  }),
338
352
  );
339
353
 
340
- setCurrentFiles(initialFiles); // Now files is a File[] array
354
+ setFilesData(initialFiles);
341
355
  };
342
356
 
343
357
  initializeFiles();
344
- setFilesData(FilesDataDto);
345
358
  setIsInitialized(true);
346
359
  }
347
360
  }, [files]);
348
361
 
349
362
  useEffect(() => {
350
363
  // * Notify parent component
351
- onFilesDataChange?.(filesData, currentFiles);
352
- }, [filesData, currentFiles]);
364
+ onFilesDataChange?.(filesData);
365
+ }, [filesData]);
353
366
 
354
367
  useEffect(() => {
355
368
  return () => {
356
- if (!currentFiles?.length) return;
369
+ if (!filesData?.length) return;
357
370
 
358
371
  try {
359
372
  // * Only for file imported with the file input, use try and catch to avoid errors on the other files
360
- currentFiles.forEach((file) => {
361
- const fileURL = URL.createObjectURL(file);
362
- URL.revokeObjectURL(fileURL);
373
+ filesData.forEach((_fileData) => {
374
+ if (_fileData?.file) {
375
+ const fileURL = URL.createObjectURL(_fileData?.file);
376
+ URL.revokeObjectURL(fileURL);
377
+ }
363
378
  });
364
379
  } catch {
365
380
  // * Do nothing
366
381
  }
367
382
  };
368
- }, [currentFiles]);
383
+ }, [filesData]);
369
384
 
370
385
  return (
371
386
  <Box
@@ -517,9 +532,9 @@ const FileUploader = ({
517
532
  testId="pc-add"
518
533
  onClick={() => handleFileChange(null, null, null, [], true)}
519
534
  >
520
- <Box gap={1} display="flex">
535
+ <Box gap={1} display="inline-flex" alignItems="center">
521
536
  <IconProvider size="sm" icon={faFolderOpen} />
522
- <Text variant="body2">Depuis votre PC</Text>
537
+ <Text variant="body2">{fromLocalText}</Text>
523
538
  </Box>
524
539
  </MenuItem>
525
540
  <GooglePickerWrapper
@@ -534,9 +549,9 @@ const FileUploader = ({
534
549
  viewId="FOLDERS"
535
550
  >
536
551
  <MenuItem testId="drive-add" onClick={handleClose}>
537
- <Box gap={1} display="flex">
552
+ <Box gap={1} display="inline-flex" alignItems="center">
538
553
  <IconProvider size="sm" icon={faGoogleDrive} />
539
- <Text variant="body2">Depuis Google Drive</Text>
554
+ <Text variant="body2">{fromGoogleDriveText}</Text>
540
555
  </Box>
541
556
  </MenuItem>
542
557
  </GooglePickerWrapper>
@@ -9,6 +9,7 @@ export interface FileDataType {
9
9
  size: number;
10
10
  type: string;
11
11
  url: string;
12
+ file?: File;
12
13
  driveFileId?: string;
13
14
  driveAccessToken?: string;
14
15
  }