allaw-ui 3.0.1 → 3.0.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.
@@ -1,8 +1,11 @@
1
1
  import React from "react";
2
+ import { CropMetadata } from "./ImageCropperModal";
2
3
  export interface FileUploaderProps {
3
4
  acceptedExtensions: string[];
4
5
  maxFileSizeMB: number;
5
6
  enableDragAndDrop?: boolean;
7
+ enableCropping?: boolean;
8
+ cropShape?: "circle" | "square";
6
9
  iconUrl?: string;
7
10
  descriptionParts?: {
8
11
  beforeLink: string;
@@ -10,7 +13,7 @@ export interface FileUploaderProps {
10
13
  linkUrl: string;
11
14
  afterLink: string;
12
15
  };
13
- onFileRead?: (file: File, fileContent: ArrayBuffer) => void;
16
+ onFileRead?: (file: File, fileContent: ArrayBuffer, cropMetadata?: CropMetadata) => void;
14
17
  onFileRemove?: () => void;
15
18
  uploadProgress?: number;
16
19
  isLoading?: boolean;
@@ -20,6 +23,7 @@ export interface FileUploaderProps {
20
23
  maxSizeLabel?: string;
21
24
  fileName?: string;
22
25
  fileSize?: number;
26
+ filePresentationLabel?: string;
23
27
  }
24
28
  declare const FileUploader: React.FC<FileUploaderProps>;
25
29
  export default FileUploader;
@@ -35,11 +35,19 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
35
35
  }
36
36
  };
37
37
  import React, { useState, useRef } from "react";
38
+ import Image from "next/image";
38
39
  import styles from "./FileUploader.module.css";
40
+ import ImageCropperModal from "./ImageCropperModal";
41
+ import IconButton from "../../atoms/buttons/IconButton";
42
+ import { TertiaryButton } from "src/components/atoms/buttons";
39
43
  var FileUploader = function (_a) {
40
- var acceptedExtensions = _a.acceptedExtensions, maxFileSizeMB = _a.maxFileSizeMB, _b = _a.enableDragAndDrop, enableDragAndDrop = _b === void 0 ? true : _b, iconUrl = _a.iconUrl, descriptionParts = _a.descriptionParts, onFileRead = _a.onFileRead, onFileRemove = _a.onFileRemove, _c = _a.uploadProgress, uploadProgress = _c === void 0 ? 0 : _c, _d = _a.isLoading, isLoading = _d === void 0 ? false : _d, _e = _a.errorMessage, errorMessage = _e === void 0 ? null : _e, _f = _a.buttonLabel, buttonLabel = _f === void 0 ? "Choisir un fichier" : _f, _g = _a.acceptedLabel, acceptedLabel = _g === void 0 ? "Format accepté :" : _g, _h = _a.maxSizeLabel, maxSizeLabel = _h === void 0 ? "Taille maximale :" : _h, fileName = _a.fileName, fileSize = _a.fileSize;
41
- var _j = useState(null), selectedFile = _j[0], setSelectedFile = _j[1];
42
- var _k = useState(false), isHovering = _k[0], setIsHovering = _k[1];
44
+ var acceptedExtensions = _a.acceptedExtensions, maxFileSizeMB = _a.maxFileSizeMB, _b = _a.enableDragAndDrop, enableDragAndDrop = _b === void 0 ? true : _b, _c = _a.enableCropping, enableCropping = _c === void 0 ? false : _c, _d = _a.cropShape, cropShape = _d === void 0 ? "square" : _d, iconUrl = _a.iconUrl, descriptionParts = _a.descriptionParts, onFileRead = _a.onFileRead, onFileRemove = _a.onFileRemove, _e = _a.uploadProgress, uploadProgress = _e === void 0 ? 0 : _e, _f = _a.isLoading, isLoading = _f === void 0 ? false : _f, _g = _a.errorMessage, errorMessage = _g === void 0 ? null : _g, _h = _a.buttonLabel, buttonLabel = _h === void 0 ? "Choisir un fichier" : _h, _j = _a.acceptedLabel, acceptedLabel = _j === void 0 ? "Format accepté :" : _j, _k = _a.maxSizeLabel, maxSizeLabel = _k === void 0 ? "Taille maximale :" : _k, fileName = _a.fileName, fileSize = _a.fileSize, _l = _a.filePresentationLabel, filePresentationLabel = _l === void 0 ? "Voici votre fichier." : _l;
45
+ var _m = useState(null), selectedFile = _m[0], setSelectedFile = _m[1];
46
+ var _o = useState(null), fileContent = _o[0], setFileContent = _o[1];
47
+ var _p = useState(false), isHovering = _p[0], setIsHovering = _p[1];
48
+ var _q = useState(false), showCropper = _q[0], setShowCropper = _q[1];
49
+ var _r = useState(null), previewUrl = _r[0], setPreviewUrl = _r[1];
50
+ var _s = useState(null), cropMetadata = _s[0], setCropMetadata = _s[1];
43
51
  var fileInputRef = useRef(null);
44
52
  var resetFileInput = function () {
45
53
  if (fileInputRef.current) {
@@ -55,6 +63,12 @@ var FileUploader = function (_a) {
55
63
  var isValidSize = file.size <= maxFileSizeMB * 1024 * 1024;
56
64
  return isValidExtension && isValidSize;
57
65
  };
66
+ var isImageFile = function (file) {
67
+ var _a;
68
+ var fileExtension = ".".concat((_a = file.name.split(".").pop()) === null || _a === void 0 ? void 0 : _a.toLowerCase());
69
+ var imageExtensions = [".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp"];
70
+ return imageExtensions.includes(fileExtension);
71
+ };
58
72
  var handleFileSelect = function (event) { return __awaiter(void 0, void 0, void 0, function () {
59
73
  var file;
60
74
  return __generator(this, function (_a) {
@@ -85,37 +99,93 @@ var FileUploader = function (_a) {
85
99
  });
86
100
  }); };
87
101
  var processFile = function (file) { return __awaiter(void 0, void 0, void 0, function () {
88
- var fileContent, error_1;
102
+ var content, url, error_1;
89
103
  return __generator(this, function (_a) {
90
104
  switch (_a.label) {
91
105
  case 0:
92
106
  if (!validateFile(file)) {
93
107
  setSelectedFile(null);
108
+ setPreviewUrl(null);
109
+ setCropMetadata(null);
94
110
  return [2 /*return*/];
95
111
  }
96
112
  _a.label = 1;
97
113
  case 1:
98
114
  _a.trys.push([1, 3, , 4]);
99
- setSelectedFile(file);
100
115
  return [4 /*yield*/, file.arrayBuffer()];
101
116
  case 2:
102
- fileContent = _a.sent();
103
- onFileRead === null || onFileRead === void 0 ? void 0 : onFileRead(file, fileContent);
117
+ content = _a.sent();
118
+ setFileContent(content);
119
+ // Créer un aperçu si c'est une image
120
+ if (isImageFile(file)) {
121
+ url = URL.createObjectURL(file);
122
+ setPreviewUrl(url);
123
+ // Si le cadrage n'est pas activé, on utilise une métadonnée par défaut
124
+ if (!enableCropping) {
125
+ setCropMetadata({
126
+ zoom: 1,
127
+ offsetX: 0,
128
+ offsetY: 0,
129
+ shape: cropShape,
130
+ });
131
+ }
132
+ }
133
+ else {
134
+ setPreviewUrl(null);
135
+ setCropMetadata(null);
136
+ }
137
+ // Si c'est une image et que le cadrage est activé, afficher le composant de cadrage
138
+ if (enableCropping && isImageFile(file)) {
139
+ setSelectedFile(file);
140
+ setShowCropper(true);
141
+ }
142
+ else {
143
+ // Sinon, traiter normalement
144
+ setSelectedFile(file);
145
+ onFileRead === null || onFileRead === void 0 ? void 0 : onFileRead(file, content);
146
+ }
104
147
  return [3 /*break*/, 4];
105
148
  case 3:
106
149
  error_1 = _a.sent();
107
150
  console.error("Error reading file:", error_1);
108
151
  setSelectedFile(null);
152
+ setPreviewUrl(null);
153
+ setCropMetadata(null);
109
154
  return [3 /*break*/, 4];
110
155
  case 4: return [2 /*return*/];
111
156
  }
112
157
  });
113
158
  }); };
159
+ var openCropModal = function () {
160
+ if (selectedFile && isImageFile(selectedFile)) {
161
+ setShowCropper(true);
162
+ }
163
+ };
164
+ var handleCropCancel = function () {
165
+ setShowCropper(false);
166
+ };
167
+ var handleCropConfirm = function (cropMetadata) {
168
+ setShowCropper(false);
169
+ setCropMetadata(cropMetadata);
170
+ if (selectedFile && fileContent) {
171
+ onFileRead === null || onFileRead === void 0 ? void 0 : onFileRead(selectedFile, fileContent, cropMetadata);
172
+ }
173
+ };
114
174
  var handleFileDelete = function () {
175
+ if (previewUrl) {
176
+ URL.revokeObjectURL(previewUrl);
177
+ }
115
178
  setSelectedFile(null);
179
+ setFileContent(null);
180
+ setPreviewUrl(null);
181
+ setCropMetadata(null);
116
182
  resetFileInput();
117
183
  onFileRemove === null || onFileRemove === void 0 ? void 0 : onFileRemove();
118
184
  };
185
+ var handleNewFileClick = function () {
186
+ var _a;
187
+ (_a = fileInputRef.current) === null || _a === void 0 ? void 0 : _a.click();
188
+ };
119
189
  var formatFileSize = function (size) {
120
190
  if (size < 1024) {
121
191
  return "".concat(size, " octets");
@@ -129,9 +199,28 @@ var FileUploader = function (_a) {
129
199
  };
130
200
  var displayFileSize = fileSize !== undefined ? fileSize : selectedFile ? selectedFile.size : 0;
131
201
  var displayFileName = fileName || (selectedFile ? selectedFile.name : "");
202
+ var isSelectedImage = selectedFile && isImageFile(selectedFile);
132
203
  return (React.createElement("div", { className: styles.upload_main_container },
133
- enableDragAndDrop ? (React.createElement("div", { className: "".concat(styles.upload_container, " ").concat(isHovering ? styles.drag_over : ""), onDragOver: handleDragOver, onDragLeave: handleDragLeave, onDrop: handleDrop },
134
- React.createElement("div", { className: styles.upload_content },
204
+ enableDragAndDrop ? (React.createElement("div", { className: "".concat(styles.upload_container, " ").concat(isHovering ? styles.drag_over : "", " ").concat(previewUrl ? styles.upload_container_with_preview : ""), onDragOver: handleDragOver, onDragLeave: handleDragLeave, onDrop: handleDrop, onClick: function () { var _a; return !previewUrl && ((_a = fileInputRef.current) === null || _a === void 0 ? void 0 : _a.click()); } },
205
+ previewUrl ? (React.createElement("div", { className: styles.preview_container, onClick: function (e) {
206
+ var _a;
207
+ // Ne pas déclencher si on clique sur le conteneur d'image
208
+ if (!e.target.closest(".".concat(styles.preview_image_container))) {
209
+ (_a = fileInputRef.current) === null || _a === void 0 ? void 0 : _a.click();
210
+ }
211
+ } },
212
+ React.createElement("div", { className: "".concat(styles.preview_image_container, " ").concat(styles["preview_image_".concat((cropMetadata === null || cropMetadata === void 0 ? void 0 : cropMetadata.shape) || cropShape)]), onClick: function () { return enableCropping && openCropModal(); } },
213
+ React.createElement("div", { className: styles.preview_image_wrapper, style: {
214
+ transform: cropMetadata
215
+ ? "translate(".concat(cropMetadata.offsetX, "px, ").concat(cropMetadata.offsetY, "px) scale(").concat(cropMetadata.zoom, ")")
216
+ : "scale(1)",
217
+ } },
218
+ React.createElement("img", { src: previewUrl, alt: displayFileName, className: styles.preview_image, draggable: false }))),
219
+ enableCropping && (React.createElement("div", { className: styles.edit_button_container, onClick: function (e) {
220
+ e.stopPropagation();
221
+ openCropModal();
222
+ } },
223
+ React.createElement(IconButton, { style: "largeFilled", iconName: "allaw-icon-edit-2" }))))) : (React.createElement("div", { className: styles.upload_content },
135
224
  React.createElement("div", { className: styles.upload_icons },
136
225
  React.createElement("div", { className: styles.upload_file_icon },
137
226
  React.createElement("i", { className: "allaw-icon-file" })),
@@ -142,13 +231,14 @@ var FileUploader = function (_a) {
142
231
  React.createElement("span", null,
143
232
  "Glissez-d\u00E9posez votre fichier, ou",
144
233
  " ",
145
- React.createElement("label", { htmlFor: "file-upload", className: styles.file_upload_label },
146
- React.createElement("span", { className: styles.btn_tertiary }, buttonLabel),
147
- React.createElement("input", { id: "file-upload", ref: fileInputRef, type: "file", accept: acceptedExtensions.join(","), style: { display: "none" }, onChange: handleFileSelect }))))))) : (React.createElement("div", { className: styles.manual_upload_container },
148
- React.createElement("label", { htmlFor: "file-upload", className: styles.file_upload_label },
234
+ React.createElement("span", { className: styles.btn_tertiary }, buttonLabel))))),
235
+ React.createElement("input", { id: "file-upload", ref: fileInputRef, type: "file", accept: acceptedExtensions.join(","), style: { display: "none" }, onChange: handleFileSelect }))) : (React.createElement("div", { className: styles.manual_upload_container },
236
+ React.createElement("button", { className: styles.file_upload_button, onClick: function () { var _a; return (_a = fileInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); } },
149
237
  React.createElement("span", { className: styles.btn_tertiary }, buttonLabel),
150
238
  React.createElement("input", { id: "file-upload", ref: fileInputRef, type: "file", accept: acceptedExtensions.join(","), style: { display: "none" }, onChange: handleFileSelect })))),
151
- React.createElement("div", { className: styles.limits },
239
+ previewUrl ? (React.createElement("div", { className: styles.file_info_container },
240
+ filePresentationLabel && (React.createElement("span", { className: styles.file_presentation_label }, filePresentationLabel)),
241
+ React.createElement(TertiaryButton, { label: "Charger ".concat(isSelectedImage ? "une autre image" : "un autre fichier"), color: "bleu-allaw", variant: true, onClick: handleNewFileClick }))) : (React.createElement("div", { className: styles.limits },
152
242
  React.createElement("div", { className: styles.format },
153
243
  React.createElement("span", { className: styles.limit_text },
154
244
  acceptedLabel,
@@ -159,7 +249,7 @@ var FileUploader = function (_a) {
159
249
  maxSizeLabel,
160
250
  " ",
161
251
  maxFileSizeMB,
162
- " Mo"))),
252
+ " Mo")))),
163
253
  descriptionParts && (React.createElement("p", { className: styles.description },
164
254
  descriptionParts.beforeLink,
165
255
  " ",
@@ -171,16 +261,18 @@ var FileUploader = function (_a) {
171
261
  React.createElement("span", { className: styles.error_message }, errorMessage)))),
172
262
  (isLoading || selectedFile || fileName) && (React.createElement("div", { className: styles.uploaded_file_container },
173
263
  React.createElement("div", { className: styles.uploaded_file_content },
174
- React.createElement("div", { className: styles.uploaded_file_icon }, iconUrl ? (React.createElement("img", { src: iconUrl, alt: "File icon", className: styles.file_icon })) : (React.createElement("i", { className: "allaw-icon-file" }))),
264
+ React.createElement("div", { className: styles.uploaded_file_icon }, iconUrl ? (React.createElement(Image, { src: iconUrl, alt: "File icon", className: styles.file_icon, width: 20, height: 20 })) : (React.createElement("i", { className: "allaw-icon-file" }))),
175
265
  React.createElement("div", { className: styles.uploaded_file_name_size },
176
266
  React.createElement("div", { className: styles.uploaded_file_name },
177
- React.createElement("span", { className: styles.file_name }, displayFileName || "Analyse en cours...")),
267
+ React.createElement("span", { className: styles.file_name }, displayFileName || "Analyse en cours..."),
268
+ selectedFile && (React.createElement("i", { className: "allaw-icon-check ".concat(styles.success_icon) }))),
178
269
  React.createElement("div", { className: styles.uploaded_file_size },
179
270
  React.createElement("span", { className: styles.file_size }, displayFileSize > 0 ? formatFileSize(displayFileSize) : ""))),
180
271
  selectedFile && (React.createElement("div", { className: styles.uploaded_file_delete_container },
181
272
  React.createElement("div", { className: styles.uploaded_file_delete, onClick: handleFileDelete },
182
273
  React.createElement("i", { className: "allaw-icon-close" }))))),
183
274
  React.createElement("div", { className: styles.uploaded_file_progress_bar }, uploadProgress > 0 && (React.createElement("div", { className: styles.progress_bar_container },
184
- React.createElement("div", { className: styles.progress_bar_fill, style: { width: "".concat((uploadProgress / 10) * 100, "%") } }))))))));
275
+ React.createElement("div", { className: styles.progress_bar_fill, style: { width: "".concat((uploadProgress / 10) * 100, "%") } })))))),
276
+ showCropper && selectedFile && (React.createElement(ImageCropperModal, { file: selectedFile, shape: cropShape, onCancel: handleCropCancel, onConfirm: handleCropConfirm }))));
185
277
  };
186
278
  export default FileUploader;
@@ -19,6 +19,7 @@
19
19
  background-color: #ffffff;
20
20
  width: 100%;
21
21
  transition: all 0.2s ease-in-out;
22
+ cursor: pointer;
22
23
  }
23
24
 
24
25
  .manual_upload_container {
@@ -265,6 +266,20 @@
265
266
  font-weight: 600;
266
267
  }
267
268
 
269
+ .file_upload_button {
270
+ background: none;
271
+ border: none;
272
+ padding: 0;
273
+ margin: 0;
274
+ cursor: pointer;
275
+ font-family: inherit;
276
+ font-size: inherit;
277
+ }
278
+
279
+ .format {
280
+ width: 61.8%;
281
+ }
282
+
268
283
  @media (max-width: 768px) {
269
284
  .upload_container {
270
285
  padding: 2rem 1rem;
@@ -285,4 +300,116 @@
285
300
  align-items: flex-start;
286
301
  gap: 0.25rem;
287
302
  }
303
+
304
+ .format {
305
+ width: 100%;
306
+ }
307
+ }
308
+
309
+ .upload_container_with_preview {
310
+ padding: 0;
311
+ cursor: default;
312
+ }
313
+
314
+ .preview_container {
315
+ width: 100%;
316
+ height: 300px;
317
+ position: relative;
318
+ display: flex;
319
+ justify-content: center;
320
+ align-items: center;
321
+ background-color: rgba(0, 0, 0, 0.03);
322
+ overflow: hidden;
323
+ cursor: pointer;
324
+ }
325
+
326
+ .preview_image_container {
327
+ position: relative;
328
+ width: 280px;
329
+ height: 280px;
330
+ display: flex;
331
+ justify-content: center;
332
+ align-items: center;
333
+ overflow: hidden;
334
+ }
335
+
336
+ .preview_image_circle {
337
+ border-radius: 50%;
338
+ }
339
+
340
+ .preview_image_square {
341
+ border-radius: 8px;
342
+ }
343
+
344
+ .preview_image_wrapper {
345
+ position: absolute;
346
+ width: auto;
347
+ height: auto;
348
+ max-width: none;
349
+ max-height: none;
350
+ transform-origin: center;
351
+ display: flex;
352
+ justify-content: center;
353
+ align-items: center;
354
+ will-change: transform;
355
+ pointer-events: none;
356
+ }
357
+
358
+ .preview_image {
359
+ width: auto;
360
+ height: auto;
361
+ object-fit: cover;
362
+ display: block;
363
+ max-width: none;
364
+ max-height: none;
365
+ user-select: none;
366
+ -webkit-user-drag: none;
367
+ }
368
+
369
+ .edit_button_container {
370
+ position: absolute;
371
+ bottom: 12px;
372
+ right: 12px;
373
+ z-index: 20;
374
+ pointer-events: auto;
375
+ }
376
+
377
+ .edit_button_container button {
378
+ pointer-events: auto;
379
+ }
380
+
381
+ .file_info_container {
382
+ display: flex;
383
+ align-items: center;
384
+ justify-content: center;
385
+ margin-top: 12px;
386
+ flex-wrap: wrap;
387
+ gap: 8px;
388
+ }
389
+
390
+ .file_info_container span {
391
+ font-family: var(--font-open-sans, sans-serif);
392
+ font-size: 14px;
393
+ color: var(----noir, #171e25);
394
+ }
395
+
396
+ .file_presentation_label {
397
+ font-size: 14px;
398
+ color: var(--gris-03);
399
+ }
400
+
401
+ .change_file_link {
402
+ cursor: pointer;
403
+ text-decoration: none;
404
+ }
405
+
406
+ .success_icon {
407
+ margin-left: 8px;
408
+ color: var(--actions-valid, #29a36a);
409
+ font-size: 14px;
410
+ }
411
+
412
+ .uploaded_file_name {
413
+ display: flex;
414
+ align-items: center;
288
415
  }
@@ -29,53 +29,69 @@ declare namespace _default {
29
29
  let description_2: string;
30
30
  export { description_2 as description };
31
31
  }
32
- namespace iconUrl {
32
+ namespace enableCropping {
33
33
  let control_3: string;
34
34
  export { control_3 as control };
35
35
  let description_3: string;
36
36
  export { description_3 as description };
37
37
  }
38
- namespace buttonLabel {
39
- let control_4: string;
38
+ namespace cropShape {
39
+ export namespace control_4 {
40
+ let type: string;
41
+ let options: string[];
42
+ }
40
43
  export { control_4 as control };
41
44
  let description_4: string;
42
45
  export { description_4 as description };
43
46
  }
44
- namespace acceptedLabel {
47
+ namespace iconUrl {
45
48
  let control_5: string;
46
49
  export { control_5 as control };
47
50
  let description_5: string;
48
51
  export { description_5 as description };
49
52
  }
50
- namespace maxSizeLabel {
53
+ namespace buttonLabel {
51
54
  let control_6: string;
52
55
  export { control_6 as control };
53
56
  let description_6: string;
54
57
  export { description_6 as description };
55
58
  }
56
- namespace uploadProgress {
57
- export namespace control_7 {
58
- let type: string;
59
- let min: number;
60
- let max: number;
61
- let step: number;
62
- }
59
+ namespace acceptedLabel {
60
+ let control_7: string;
63
61
  export { control_7 as control };
64
62
  let description_7: string;
65
63
  export { description_7 as description };
66
64
  }
67
- namespace isLoading {
65
+ namespace maxSizeLabel {
68
66
  let control_8: string;
69
67
  export { control_8 as control };
70
68
  let description_8: string;
71
69
  export { description_8 as description };
72
70
  }
73
- namespace errorMessage {
74
- let control_9: string;
71
+ namespace uploadProgress {
72
+ export namespace control_9 {
73
+ let type_1: string;
74
+ export { type_1 as type };
75
+ export let min: number;
76
+ export let max: number;
77
+ export let step: number;
78
+ }
75
79
  export { control_9 as control };
76
80
  let description_9: string;
77
81
  export { description_9 as description };
78
82
  }
83
+ namespace isLoading {
84
+ let control_10: string;
85
+ export { control_10 as control };
86
+ let description_10: string;
87
+ export { description_10 as description };
88
+ }
89
+ namespace errorMessage {
90
+ let control_11: string;
91
+ export { control_11 as control };
92
+ let description_11: string;
93
+ export { description_11 as description };
94
+ }
79
95
  }
80
96
  }
81
97
  export default _default;
@@ -85,4 +101,7 @@ export const WithoutDragAndDrop: any;
85
101
  export const WithError: any;
86
102
  export const WithProgress: any;
87
103
  export const MultipleExtensions: any;
104
+ export const WithSquareCropping: any;
105
+ export const WithCircleCropping: any;
106
+ export const WithPreloadedImage: any;
88
107
  import FileUploader from "./FileUploader";
@@ -77,6 +77,14 @@ export default {
77
77
  control: "boolean",
78
78
  description: "Active/désactive la zone de drag & drop",
79
79
  },
80
+ enableCropping: {
81
+ control: "boolean",
82
+ description: "Active/désactive le cadrage pour les images",
83
+ },
84
+ cropShape: {
85
+ control: { type: "select", options: ["circle", "square"] },
86
+ description: "Forme du masque de cadrage (cercle ou carré)",
87
+ },
80
88
  iconUrl: {
81
89
  control: "text",
82
90
  description: "URL de l'icône du type de fichier",
@@ -111,16 +119,21 @@ export default {
111
119
  var Template = function (args) {
112
120
  var _a = useState(null), file = _a[0], setFile = _a[1];
113
121
  var _b = useState(null), fileContent = _b[0], setFileContent = _b[1];
114
- var _c = useState(0), progress = _c[0], setProgress = _c[1];
115
- var _d = useState(false), loading = _d[0], setLoading = _d[1];
116
- var _e = useState(null), error = _e[0], setError = _e[1];
117
- var handleFileRead = function (file, content) { return __awaiter(void 0, void 0, void 0, function () {
122
+ var _c = useState(null), cropMetadata = _c[0], setCropMetadata = _c[1];
123
+ var _d = useState(0), progress = _d[0], setProgress = _d[1];
124
+ var _e = useState(false), loading = _e[0], setLoading = _e[1];
125
+ var _f = useState(null), error = _f[0], setError = _f[1];
126
+ var handleFileRead = function (file, content, cropMeta) { return __awaiter(void 0, void 0, void 0, function () {
118
127
  return __generator(this, function (_a) {
119
128
  switch (_a.label) {
120
129
  case 0:
121
130
  setLoading(true);
122
131
  setFile(file);
123
132
  setFileContent(content);
133
+ if (cropMeta) {
134
+ setCropMetadata(cropMeta);
135
+ console.log("Métadonnées de cadrage:", cropMeta);
136
+ }
124
137
  // Simuler une progression
125
138
  setProgress(2);
126
139
  return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 300); })];
@@ -144,21 +157,33 @@ var Template = function (args) {
144
157
  var handleFileRemove = function () {
145
158
  setFile(null);
146
159
  setFileContent(null);
160
+ setCropMetadata(null);
147
161
  setProgress(0);
148
162
  setError(null);
149
163
  console.log("Fichier supprimé");
150
164
  };
151
- return (React.createElement(FileUploader, __assign({}, args, { onFileRead: handleFileRead, onFileRemove: handleFileRemove, uploadProgress: progress, isLoading: loading, errorMessage: error })));
165
+ return (React.createElement("div", null,
166
+ React.createElement(FileUploader, __assign({}, args, { onFileRead: handleFileRead, onFileRemove: handleFileRemove, uploadProgress: progress, isLoading: loading, errorMessage: error })),
167
+ cropMetadata && (React.createElement("div", { style: {
168
+ marginTop: "20px",
169
+ padding: "10px",
170
+ backgroundColor: "#f5f5f5",
171
+ borderRadius: "8px",
172
+ } },
173
+ React.createElement("h4", { style: { margin: "0 0 10px 0", fontSize: "14px", color: "#456073" } }, "M\u00E9tadonn\u00E9es de cadrage:"),
174
+ React.createElement("code", { style: { display: "block", fontSize: "12px", color: "#333" } }, JSON.stringify(cropMetadata, null, 2))))));
152
175
  };
153
176
  export var Default = Template.bind({});
154
177
  Default.args = {
155
- acceptedExtensions: [".pdf"],
178
+ acceptedExtensions: [".jpg", ".jpeg", ".png", ".webp"],
156
179
  maxFileSizeMB: 5,
157
180
  enableDragAndDrop: true,
158
- buttonLabel: "Choisir un fichier",
159
- acceptedLabel: "Format accepté :",
181
+ enableCropping: true,
182
+ cropShape: "circle",
183
+ buttonLabel: "Choisir une image",
184
+ acceptedLabel: "Formats acceptés :",
160
185
  maxSizeLabel: "Taille maximale :",
161
- iconUrl: pdfIcon,
186
+ filePresentationLabel: "Voici votre logo.",
162
187
  };
163
188
  export var WithDescription = Template.bind({});
164
189
  WithDescription.args = __assign(__assign({}, Default.args), { descriptionParts: {
@@ -176,3 +201,32 @@ WithProgress.args = __assign(__assign({}, Default.args), { uploadProgress: 6, is
176
201
  // Exemple avec des extensions multiples
177
202
  export var MultipleExtensions = Template.bind({});
178
203
  MultipleExtensions.args = __assign(__assign({}, Default.args), { acceptedExtensions: [".pdf", ".doc", ".docx"] });
204
+ // Exemple avec le cadrage d'image en carré
205
+ export var WithSquareCropping = Template.bind({});
206
+ WithSquareCropping.args = {
207
+ acceptedExtensions: [".jpg", ".jpeg", ".png", ".webp"],
208
+ maxFileSizeMB: 5,
209
+ enableDragAndDrop: true,
210
+ enableCropping: true,
211
+ cropShape: "square",
212
+ buttonLabel: "Choisir une image",
213
+ acceptedLabel: "Formats acceptés :",
214
+ maxSizeLabel: "Taille maximale :",
215
+ };
216
+ // Exemple avec le cadrage d'image en cercle
217
+ export var WithCircleCropping = Template.bind({});
218
+ WithCircleCropping.args = {
219
+ acceptedExtensions: [".jpg", ".jpeg", ".png", ".webp"],
220
+ maxFileSizeMB: 5,
221
+ enableDragAndDrop: true,
222
+ enableCropping: true,
223
+ cropShape: "circle",
224
+ buttonLabel: "Choisir une image",
225
+ acceptedLabel: "Formats acceptés :",
226
+ maxSizeLabel: "Taille maximale :",
227
+ };
228
+ // Exemple avec l'affichage d'une image préuploadée
229
+ export var WithPreloadedImage = Template.bind({});
230
+ WithPreloadedImage.args = __assign(__assign({}, Default.args), {
231
+ // On simule un fichier déjà chargé pour la story
232
+ fileName: "logo-entreprise.png", fileSize: 1.2 * 1024 * 1024, filePresentationLabel: "Voici votre logo." });
@@ -0,0 +1,15 @@
1
+ import React from "react";
2
+ export interface CropMetadata {
3
+ zoom: number;
4
+ offsetX: number;
5
+ offsetY: number;
6
+ shape: "circle" | "square";
7
+ }
8
+ interface ImageCropperModalProps {
9
+ file: File;
10
+ shape: "circle" | "square";
11
+ onCancel: () => void;
12
+ onConfirm: (cropMetadata: CropMetadata) => void;
13
+ }
14
+ declare const ImageCropperModal: React.FC<ImageCropperModalProps>;
15
+ export default ImageCropperModal;
@@ -0,0 +1,212 @@
1
+ import React, { useState, useRef, useEffect } from "react";
2
+ import Image from "next/image";
3
+ import styles from "./ImageCropperModal.module.css";
4
+ var ImageCropperModal = function (_a) {
5
+ var file = _a.file, shape = _a.shape, onCancel = _a.onCancel, onConfirm = _a.onConfirm;
6
+ var _b = useState(1), zoom = _b[0], setZoom = _b[1]; // Facteur de zoom relatif (1-3)
7
+ var _c = useState(1), baseZoom = _c[0], setBaseZoom = _c[1]; // Zoom de base pour fit l'image
8
+ var _d = useState({
9
+ x: 0,
10
+ y: 0,
11
+ }), position = _d[0], setPosition = _d[1];
12
+ var _e = useState(false), isDragging = _e[0], setIsDragging = _e[1];
13
+ var _f = useState(null), imageSrc = _f[0], setImageSrc = _f[1];
14
+ var _g = useState({
15
+ x: 0,
16
+ y: 0,
17
+ }), dragStart = _g[0], setDragStart = _g[1];
18
+ var _h = useState(null), imageSize = _h[0], setImageSize = _h[1];
19
+ var imageRef = useRef(null);
20
+ var cropAreaRef = useRef(null);
21
+ var imageWrapperRef = useRef(null);
22
+ // Calcule le zoom de base pour que l'image "fit" parfaitement dans le cadre
23
+ var calculateFitZoom = function (imgWidth, imgHeight) {
24
+ if (!cropAreaRef.current)
25
+ return 1;
26
+ var cropRect = cropAreaRef.current.getBoundingClientRect();
27
+ var cropWidth = cropRect.width;
28
+ var cropHeight = cropRect.height;
29
+ // Calcul du zoom pour que l'image couvre complètement le cadre
30
+ // On utilise max pour assurer que l'image couvre tout le cadre, sans espace vide
31
+ var fitZoom = Math.max(cropWidth / imgWidth, cropHeight / imgHeight);
32
+ return fitZoom;
33
+ };
34
+ // Calcule les limites de déplacement pour éviter les zones vides
35
+ var calculateBoundaries = function () {
36
+ if (!cropAreaRef.current || !imageSize) {
37
+ return { minX: 0, maxX: 0, minY: 0, maxY: 0 };
38
+ }
39
+ var cropRect = cropAreaRef.current.getBoundingClientRect();
40
+ var cropWidth = cropRect.width;
41
+ var cropHeight = cropRect.height;
42
+ // Taille de l'image après application des deux zooms
43
+ var totalZoom = baseZoom * zoom;
44
+ var scaledWidth = imageSize.width * totalZoom;
45
+ var scaledHeight = imageSize.height * totalZoom;
46
+ // Si l'image est plus petite que le crop à cause du zoom
47
+ // ce qui ne devrait pas arriver avec notre logique, mais au cas où
48
+ if (scaledWidth <= cropWidth || scaledHeight <= cropHeight) {
49
+ return { minX: 0, maxX: 0, minY: 0, maxY: 0 };
50
+ }
51
+ // Calcule combien l'image peut être déplacée sans créer d'espace vide
52
+ var xLimit = Math.max(0, (scaledWidth - cropWidth) / 2);
53
+ var yLimit = Math.max(0, (scaledHeight - cropHeight) / 2);
54
+ return {
55
+ minX: -xLimit,
56
+ maxX: xLimit,
57
+ minY: -yLimit,
58
+ maxY: yLimit,
59
+ };
60
+ };
61
+ // Centre l'image dans la zone de crop
62
+ var centerImage = function () {
63
+ setPosition({ x: 0, y: 0 });
64
+ };
65
+ useEffect(function () {
66
+ var reader = new FileReader();
67
+ reader.onload = function (e) {
68
+ if (e.target && typeof e.target.result === "string") {
69
+ setImageSrc(e.target.result);
70
+ // Charge l'image pour obtenir ses dimensions
71
+ var img_1 = new window.Image();
72
+ img_1.onload = function () {
73
+ setImageSize({ width: img_1.width, height: img_1.height });
74
+ // Attendre que les refs soient disponibles
75
+ setTimeout(function () {
76
+ var fitZoom = calculateFitZoom(img_1.width, img_1.height);
77
+ setBaseZoom(fitZoom);
78
+ setZoom(1); // Zoom initial toujours à 1 (relatif)
79
+ centerImage();
80
+ }, 50);
81
+ };
82
+ img_1.src = e.target.result;
83
+ }
84
+ };
85
+ reader.readAsDataURL(file);
86
+ return function () {
87
+ reader.abort();
88
+ };
89
+ }, [file]);
90
+ // Recalcule les limites quand le zoom change
91
+ useEffect(function () {
92
+ // Quand zoom change, recalculer les limites et ajuster la position si nécessaire
93
+ if (imageSize && cropAreaRef.current) {
94
+ var boundaries = calculateBoundaries();
95
+ // Ajuste la position pour rester dans les limites
96
+ setPosition({
97
+ x: Math.max(boundaries.minX, Math.min(boundaries.maxX, position.x)),
98
+ y: Math.max(boundaries.minY, Math.min(boundaries.maxY, position.y)),
99
+ });
100
+ }
101
+ }, [zoom, imageSize]);
102
+ var handleMouseDown = function (e) {
103
+ e.preventDefault();
104
+ setIsDragging(true);
105
+ setDragStart({
106
+ x: e.clientX - position.x,
107
+ y: e.clientY - position.y,
108
+ });
109
+ };
110
+ var handleTouchStart = function (e) {
111
+ if (e.touches.length !== 1)
112
+ return;
113
+ e.preventDefault();
114
+ setIsDragging(true);
115
+ setDragStart({
116
+ x: e.touches[0].clientX - position.x,
117
+ y: e.touches[0].clientY - position.y,
118
+ });
119
+ };
120
+ var updatePosition = function (clientX, clientY) {
121
+ if (!isDragging)
122
+ return;
123
+ var newX = clientX - dragStart.x;
124
+ var newY = clientY - dragStart.y;
125
+ var boundaries = calculateBoundaries();
126
+ setPosition({
127
+ x: Math.max(boundaries.minX, Math.min(boundaries.maxX, newX)),
128
+ y: Math.max(boundaries.minY, Math.min(boundaries.maxY, newY)),
129
+ });
130
+ };
131
+ var handleMouseMove = function (e) {
132
+ if (!isDragging)
133
+ return;
134
+ updatePosition(e.clientX, e.clientY);
135
+ };
136
+ var handleTouchMove = function (e) {
137
+ if (!isDragging || e.touches.length !== 1)
138
+ return;
139
+ e.preventDefault();
140
+ updatePosition(e.touches[0].clientX, e.touches[0].clientY);
141
+ };
142
+ var handleMouseUp = function () {
143
+ setIsDragging(false);
144
+ };
145
+ var handleTouchEnd = function () {
146
+ setIsDragging(false);
147
+ };
148
+ var handleZoomChange = function (e) {
149
+ var newZoom = parseFloat(e.target.value);
150
+ setZoom(Math.min(3, Math.max(1, newZoom)));
151
+ };
152
+ useEffect(function () {
153
+ if (isDragging) {
154
+ document.addEventListener("mousemove", handleDocumentMouseMove);
155
+ document.addEventListener("mouseup", handleDocumentMouseUp);
156
+ document.addEventListener("touchmove", handleDocumentTouchMove, {
157
+ passive: false,
158
+ });
159
+ document.addEventListener("touchend", handleDocumentTouchEnd);
160
+ }
161
+ return function () {
162
+ document.removeEventListener("mousemove", handleDocumentMouseMove);
163
+ document.removeEventListener("mouseup", handleDocumentMouseUp);
164
+ document.removeEventListener("touchmove", handleDocumentTouchMove);
165
+ document.removeEventListener("touchend", handleDocumentTouchEnd);
166
+ };
167
+ }, [isDragging, dragStart, zoom, imageSize]);
168
+ var handleDocumentMouseMove = function (e) {
169
+ updatePosition(e.clientX, e.clientY);
170
+ };
171
+ var handleDocumentTouchMove = function (e) {
172
+ if (e.touches.length !== 1)
173
+ return;
174
+ e.preventDefault();
175
+ updatePosition(e.touches[0].clientX, e.touches[0].clientY);
176
+ };
177
+ var handleDocumentMouseUp = function () {
178
+ setIsDragging(false);
179
+ };
180
+ var handleDocumentTouchEnd = function () {
181
+ setIsDragging(false);
182
+ };
183
+ return (React.createElement("div", { className: styles.modal_overlay },
184
+ React.createElement("div", { className: styles.modal_content },
185
+ React.createElement("div", { className: styles.modal_header },
186
+ React.createElement("h3", { className: styles.modal_title }, "Recadrer l'image")),
187
+ React.createElement("div", { className: styles.crop_container },
188
+ React.createElement("div", { className: styles.image_wrapper, ref: imageWrapperRef }, imageSrc && (React.createElement("div", { ref: imageRef, className: styles.image_container, style: {
189
+ transform: "translate(".concat(position.x, "px, ").concat(position.y, "px) scale(").concat(baseZoom * zoom, ")"),
190
+ cursor: isDragging ? "grabbing" : "grab",
191
+ } },
192
+ React.createElement(Image, { src: imageSrc, alt: "Aper\u00E7u", className: styles.image, draggable: false, width: 1000, height: 1000, priority: true, unoptimized: true })))),
193
+ React.createElement("div", { ref: cropAreaRef, className: "".concat(styles.crop_area, " ").concat(styles["crop_area_".concat(shape)]), onMouseDown: handleMouseDown, onMouseMove: handleMouseMove, onMouseUp: handleMouseUp, onTouchStart: handleTouchStart, onTouchMove: handleTouchMove, onTouchEnd: handleTouchEnd })),
194
+ React.createElement("div", { className: styles.controls },
195
+ React.createElement("div", { className: styles.zoom_control },
196
+ React.createElement("span", { className: styles.zoom_label }, "Zoom:"),
197
+ React.createElement("button", { className: styles.zoom_button, onClick: function () { return setZoom(Math.max(1, zoom - 0.1)); }, disabled: zoom <= 1 }, "\u2013"),
198
+ React.createElement("input", { type: "range", min: "1", max: "3", step: "0.1", value: zoom, onChange: handleZoomChange, className: styles.zoom_slider }),
199
+ React.createElement("button", { className: styles.zoom_button, onClick: function () { return setZoom(Math.min(3, zoom + 0.1)); }, disabled: zoom >= 3 }, "+")),
200
+ React.createElement("div", { className: styles.action_buttons },
201
+ React.createElement("button", { className: styles.cancel_button, onClick: onCancel }, "Annuler"),
202
+ React.createElement("button", { className: styles.confirm_button, onClick: function () {
203
+ // Retourner les métadonnées de cadrage avec le zoom total
204
+ onConfirm({
205
+ zoom: baseZoom * zoom,
206
+ offsetX: position.x,
207
+ offsetY: position.y,
208
+ shape: shape,
209
+ });
210
+ } }, "Valider"))))));
211
+ };
212
+ export default ImageCropperModal;
@@ -0,0 +1,232 @@
1
+ .modal_overlay {
2
+ position: fixed;
3
+ top: 0;
4
+ left: 0;
5
+ width: 100%;
6
+ height: 100%;
7
+ background-color: rgba(0, 0, 0, 0.7);
8
+ display: flex;
9
+ justify-content: center;
10
+ align-items: center;
11
+ z-index: 9999;
12
+ }
13
+
14
+ .modal_content {
15
+ display: flex;
16
+ flex-direction: column;
17
+ background-color: transparent;
18
+ border-radius: 12px;
19
+ width: 90%;
20
+ max-width: 500px;
21
+ height: 80vh;
22
+ max-height: 600px;
23
+ overflow: hidden;
24
+ }
25
+
26
+ .modal_header {
27
+ padding: 1rem;
28
+ background-color: white;
29
+ border-bottom: 1px solid var(--grey-venom, #e6edf5);
30
+ border-radius: 12px 12px 0 0;
31
+ }
32
+
33
+ .modal_title {
34
+ font-family: var(--font-open-sans, sans-serif);
35
+ font-size: 18px;
36
+ font-weight: 600;
37
+ color: var(--noir, #171e25);
38
+ margin: 0;
39
+ }
40
+
41
+ .crop_container {
42
+ position: relative;
43
+ width: 100%;
44
+ flex: 1;
45
+ display: flex;
46
+ justify-content: center;
47
+ align-items: center;
48
+ padding: 1rem;
49
+ overflow: hidden;
50
+ background-color: rgba(0, 0, 0, 0.5);
51
+ }
52
+
53
+ .crop_mask {
54
+ position: absolute;
55
+ top: 0;
56
+ left: 0;
57
+ right: 0;
58
+ bottom: 0;
59
+ background-color: rgba(0, 0, 0, 0.5);
60
+ pointer-events: none;
61
+ z-index: 10;
62
+ }
63
+
64
+ .crop_area {
65
+ position: relative;
66
+ width: 280px;
67
+ height: 280px;
68
+ background-color: transparent;
69
+ z-index: 20;
70
+ box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.5);
71
+ }
72
+
73
+ .crop_area_square {
74
+ border-radius: 8px;
75
+ }
76
+
77
+ .crop_area_circle {
78
+ border-radius: 50%;
79
+ }
80
+
81
+ .crop_area::before {
82
+ content: "";
83
+ position: absolute;
84
+ inset: 0;
85
+ border: 2px solid rgba(255, 255, 255, 0.8);
86
+ border-radius: inherit;
87
+ pointer-events: none;
88
+ z-index: 25;
89
+ }
90
+
91
+ .image_wrapper {
92
+ position: absolute;
93
+ top: 0;
94
+ left: 0;
95
+ width: 100%;
96
+ height: 100%;
97
+ display: flex;
98
+ justify-content: center;
99
+ align-items: center;
100
+ z-index: 5;
101
+ overflow: hidden;
102
+ pointer-events: none;
103
+ }
104
+
105
+ .image_container {
106
+ position: absolute;
107
+ transform-origin: center;
108
+ will-change: transform;
109
+ touch-action: none;
110
+ cursor: grab;
111
+ pointer-events: all;
112
+ }
113
+
114
+ .image {
115
+ width: auto !important;
116
+ height: auto !important;
117
+ max-width: none !important;
118
+ max-height: none !important;
119
+ user-select: none;
120
+ -webkit-user-drag: none;
121
+ display: block;
122
+ object-fit: cover !important;
123
+ }
124
+
125
+ .controls {
126
+ padding: 1rem;
127
+ background-color: white;
128
+ border-top: 1px solid var(--grey-venom, #e6edf5);
129
+ display: flex;
130
+ flex-direction: column;
131
+ gap: 1rem;
132
+ border-radius: 0 0 12px 12px;
133
+ }
134
+
135
+ .zoom_control {
136
+ display: flex;
137
+ align-items: center;
138
+ justify-content: space-between;
139
+ gap: 0.5rem;
140
+ }
141
+
142
+ .zoom_label {
143
+ font-family: var(--font-open-sans, sans-serif);
144
+ font-size: 14px;
145
+ color: var(--mid-grey, #728ea7);
146
+ white-space: nowrap;
147
+ }
148
+
149
+ .zoom_slider {
150
+ flex: 1;
151
+ -webkit-appearance: none;
152
+ appearance: none;
153
+ height: 4px;
154
+ background: var(--grey-venom, #e6edf5);
155
+ border-radius: 2px;
156
+ outline: none;
157
+ }
158
+
159
+ .zoom_slider::-webkit-slider-thumb {
160
+ -webkit-appearance: none;
161
+ appearance: none;
162
+ width: 18px;
163
+ height: 18px;
164
+ background: var(--bleu-allaw, #25beeb);
165
+ border-radius: 50%;
166
+ cursor: pointer;
167
+ }
168
+
169
+ .zoom_slider::-moz-range-thumb {
170
+ width: 18px;
171
+ height: 18px;
172
+ background: var(--bleu-allaw, #25beeb);
173
+ border-radius: 50%;
174
+ cursor: pointer;
175
+ border: none;
176
+ }
177
+
178
+ .zoom_button {
179
+ display: flex;
180
+ align-items: center;
181
+ justify-content: center;
182
+ width: 30px;
183
+ height: 30px;
184
+ border-radius: 50%;
185
+ border: 1px solid var(--grey-venom, #e6edf5);
186
+ background-color: white;
187
+ cursor: pointer;
188
+ font-size: 16px;
189
+ color: var(--noir, #171e25);
190
+ }
191
+
192
+ .zoom_button:disabled {
193
+ opacity: 0.5;
194
+ cursor: not-allowed;
195
+ }
196
+
197
+ .action_buttons {
198
+ display: flex;
199
+ justify-content: flex-end;
200
+ gap: 1rem;
201
+ }
202
+
203
+ .cancel_button,
204
+ .confirm_button {
205
+ padding: 0.5rem 1.5rem;
206
+ border-radius: 4px;
207
+ font-family: var(--font-open-sans, sans-serif);
208
+ font-size: 14px;
209
+ font-weight: 600;
210
+ cursor: pointer;
211
+ transition: all 0.2s ease;
212
+ }
213
+
214
+ .cancel_button {
215
+ background-color: transparent;
216
+ border: 1px solid var(--grey-venom, #e6edf5);
217
+ color: var(--mid-grey, #728ea7);
218
+ }
219
+
220
+ .confirm_button {
221
+ background-color: var(--bleu-allaw, #25beeb);
222
+ border: 1px solid var(--bleu-allaw, #25beeb);
223
+ color: white;
224
+ }
225
+
226
+ .cancel_button:hover {
227
+ background-color: #f8f9fb;
228
+ }
229
+
230
+ .confirm_button:hover {
231
+ background-color: #1da9d2;
232
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "allaw-ui",
3
- "version": "3.0.1",
3
+ "version": "3.0.3",
4
4
  "description": "Composants UI pour l'application Allaw",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.esm.js",