allaw-ui 5.1.1 → 5.1.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.
@@ -14,6 +14,7 @@ export interface TextAreaProps {
14
14
  error?: string;
15
15
  maxHeight?: number;
16
16
  minHeight?: number;
17
+ maxLength?: number;
17
18
  variant?: "bold" | "semiBold" | "medium";
18
19
  color?: "bleu-allaw" | "mid-grey" | "dark-grey" | "noir" | "pure-white" | "grey-venom" | "venom-grey-dark";
19
20
  dataTestId?: string;
@@ -6,7 +6,7 @@ import Paragraph from "../typography/Paragraph";
6
6
  import TinyInfo from "../typography/TinyInfo";
7
7
  import "./TextArea.css";
8
8
  var TextArea = forwardRef(function (_a, ref) {
9
- var title = _a.title, _b = _a.style, style = _b === void 0 ? "default" : _b, placeholder = _a.placeholder, _c = _a.isRequired, isRequired = _c === void 0 ? false : _c, validate = _a.validate, onError = _a.onError, onChange = _a.onChange, propValue = _a.value, propError = _a.error, _d = _a.maxHeight, maxHeight = _d === void 0 ? 400 : _d, minHeight = _a.minHeight, _e = _a.variant, variant = _e === void 0 ? "medium" : _e, _f = _a.color, color = _f === void 0 ? "noir" : _f, dataTestId = _a.dataTestId, _g = _a.isPrefilled, isPrefilled = _g === void 0 ? false : _g;
9
+ var title = _a.title, _b = _a.style, style = _b === void 0 ? "default" : _b, placeholder = _a.placeholder, _c = _a.isRequired, isRequired = _c === void 0 ? false : _c, validate = _a.validate, onError = _a.onError, onChange = _a.onChange, propValue = _a.value, propError = _a.error, _d = _a.maxHeight, maxHeight = _d === void 0 ? 400 : _d, minHeight = _a.minHeight, maxLength = _a.maxLength, _e = _a.variant, variant = _e === void 0 ? "medium" : _e, _f = _a.color, color = _f === void 0 ? "noir" : _f, dataTestId = _a.dataTestId, _g = _a.isPrefilled, isPrefilled = _g === void 0 ? false : _g;
10
10
  var _h = useState(propValue || ""), value = _h[0], setValue = _h[1];
11
11
  var _j = useState(propError || ""), error = _j[0], setError = _j[1];
12
12
  var _k = useState(false), isTouched = _k[0], setIsTouched = _k[1];
@@ -86,7 +86,7 @@ var TextArea = forwardRef(function (_a, ref) {
86
86
  : ""), "data-testid": dataTestId || "text-area" },
87
87
  React.createElement("textarea", { ref: textareaRef, placeholder: placeholder, className: minHeight !== undefined
88
88
  ? "text-area-placeholder no-transition"
89
- : "text-area-placeholder", value: value, onChange: handleChange, onBlur: handleBlur, style: textareaStyle, "data-testid": dataTestId || "text-area" })),
89
+ : "text-area-placeholder", value: value, onChange: handleChange, onBlur: handleBlur, style: textareaStyle, maxLength: maxLength, "data-testid": dataTestId || "text-area" })),
90
90
  error && isTouched && (React.createElement("div", { className: "error-message" },
91
91
  React.createElement(TinyInfo, { variant: "medium12", color: "actions-error", text: error }))))));
92
92
  });
@@ -6,6 +6,21 @@ export interface FileUploaderProps {
6
6
  enableDragAndDrop?: boolean;
7
7
  enableCropping?: boolean;
8
8
  cropShape?: "circle" | "square" | "banner";
9
+ clientSideCrop?: boolean;
10
+ cropOutputDimensions?: {
11
+ square?: {
12
+ width: number;
13
+ height: number;
14
+ };
15
+ circle?: {
16
+ width: number;
17
+ height: number;
18
+ };
19
+ banner?: {
20
+ width: number;
21
+ height: number;
22
+ };
23
+ };
9
24
  iconUrl?: string;
10
25
  descriptionParts?: {
11
26
  beforeLink: string;
@@ -41,17 +41,21 @@ import ImageCropperModal from "./ImageCropperModal";
41
41
  import IconButton from "../../atoms/buttons/IconButton";
42
42
  import TertiaryButton from "../../atoms/buttons/TertiaryButton";
43
43
  var FileUploader = function (_a) {
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.autoManageProgress, autoManageProgress = _g === void 0 ? false : _g, _h = _a.progressSteps, progressSteps = _h === void 0 ? 10 : _h, _j = _a.progressInterval, progressInterval = _j === void 0 ? 500 : _j, _k = _a.errorMessage, errorMessage = _k === void 0 ? null : _k, _l = _a.buttonLabel, buttonLabel = _l === void 0 ? "Choisir un fichier" : _l, _m = _a.acceptedLabel, acceptedLabel = _m === void 0 ? "Format accepté :" : _m, _o = _a.maxSizeLabel, maxSizeLabel = _o === void 0 ? "Taille maximale :" : _o, fileName = _a.fileName, fileSize = _a.fileSize, _p = _a.filePresentationLabel, filePresentationLabel = _p === void 0 ? "Voici votre fichier." : _p, initialFile = _a.initialFile, initialPreviewUrl = _a.initialPreviewUrl, initialCropMetadata = _a.initialCropMetadata, originalFileName = _a.originalFileName;
45
- var _q = useState(initialFile || null), selectedFile = _q[0], setSelectedFile = _q[1];
46
- var _r = useState(null), fileContent = _r[0], setFileContent = _r[1];
47
- var _s = useState(false), isHovering = _s[0], setIsHovering = _s[1];
48
- var _t = useState(false), showCropper = _t[0], setShowCropper = _t[1];
49
- var _u = useState(initialPreviewUrl || null), previewUrl = _u[0], setPreviewUrl = _u[1];
50
- var _v = useState(initialCropMetadata || null), cropMetadata = _v[0], setCropMetadata = _v[1];
51
- var _w = useState(0), internalProgress = _w[0], setInternalProgress = _w[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, _e = _a.clientSideCrop, clientSideCrop = _e === void 0 ? false : _e, _f = _a.cropOutputDimensions, cropOutputDimensions = _f === void 0 ? {
45
+ square: { width: 200, height: 200 },
46
+ circle: { width: 200, height: 200 },
47
+ banner: { width: 1200, height: 400 },
48
+ } : _f, iconUrl = _a.iconUrl, descriptionParts = _a.descriptionParts, onFileRead = _a.onFileRead, onFileRemove = _a.onFileRemove, _g = _a.uploadProgress, uploadProgress = _g === void 0 ? 0 : _g, _h = _a.isLoading, isLoading = _h === void 0 ? false : _h, _j = _a.autoManageProgress, autoManageProgress = _j === void 0 ? false : _j, _k = _a.progressSteps, progressSteps = _k === void 0 ? 10 : _k, _l = _a.progressInterval, progressInterval = _l === void 0 ? 500 : _l, _m = _a.errorMessage, errorMessage = _m === void 0 ? null : _m, _o = _a.buttonLabel, buttonLabel = _o === void 0 ? "Choisir un fichier" : _o, _p = _a.acceptedLabel, acceptedLabel = _p === void 0 ? "Format accepté :" : _p, _q = _a.maxSizeLabel, maxSizeLabel = _q === void 0 ? "Taille maximale :" : _q, fileName = _a.fileName, fileSize = _a.fileSize, _r = _a.filePresentationLabel, filePresentationLabel = _r === void 0 ? "Voici votre fichier." : _r, initialFile = _a.initialFile, initialPreviewUrl = _a.initialPreviewUrl, initialCropMetadata = _a.initialCropMetadata, originalFileName = _a.originalFileName;
49
+ var _s = useState(initialFile || null), selectedFile = _s[0], setSelectedFile = _s[1];
50
+ var _t = useState(null), fileContent = _t[0], setFileContent = _t[1];
51
+ var _u = useState(false), isHovering = _u[0], setIsHovering = _u[1];
52
+ var _v = useState(false), showCropper = _v[0], setShowCropper = _v[1];
53
+ var _w = useState(initialPreviewUrl || null), previewUrl = _w[0], setPreviewUrl = _w[1];
54
+ var _x = useState(initialCropMetadata || null), cropMetadata = _x[0], setCropMetadata = _x[1];
55
+ var _y = useState(0), internalProgress = _y[0], setInternalProgress = _y[1];
52
56
  var progressIntervalRef = useRef(null);
53
57
  var fileInputRef = useRef(null);
54
- var _x = useState(originalFileName), currentFileName = _x[0], setCurrentFileName = _x[1];
58
+ var _z = useState(originalFileName), currentFileName = _z[0], setCurrentFileName = _z[1];
55
59
  useEffect(function () {
56
60
  if (initialFile) {
57
61
  setSelectedFile(initialFile);
@@ -207,19 +211,161 @@ var FileUploader = function (_a) {
207
211
  setShowCropper(true);
208
212
  }
209
213
  };
214
+ /**
215
+ * Crop l'image côté client et retourne un Blob
216
+ * Utilise la même logique que ImageCropperModal pour calculer le crop
217
+ */
218
+ var cropImageToBlob = function (imageUrl, cropMetadata, outputWidth, outputHeight) { return __awaiter(void 0, void 0, void 0, function () {
219
+ return __generator(this, function (_a) {
220
+ return [2 /*return*/, new Promise(function (resolve, reject) {
221
+ var img = new window.Image();
222
+ img.crossOrigin = "anonymous";
223
+ img.onload = function () {
224
+ try {
225
+ // Créer un canvas aux dimensions de sortie
226
+ var canvas = document.createElement("canvas");
227
+ canvas.width = outputWidth;
228
+ canvas.height = outputHeight;
229
+ var ctx = canvas.getContext("2d");
230
+ if (!ctx) {
231
+ reject(new Error("Impossible de créer le contexte canvas"));
232
+ return;
233
+ }
234
+ // Dimensions de la zone de crop dans le modal (selon le CSS)
235
+ // Ces dimensions correspondent exactement à celles utilisées dans ImageCropperModal.module.css
236
+ var cropAreaWidth = 280; // Dimensions réelles du cadre de crop pour square/circle
237
+ var cropAreaHeight = 280;
238
+ if (cropMetadata.shape === "banner") {
239
+ cropAreaWidth = 320;
240
+ cropAreaHeight = 60;
241
+ }
242
+ // Le zoom dans cropMetadata est déjà le zoom total (baseZoom * zoom relatif)
243
+ // Comme calculé dans ImageCropperModal ligne 318: zoom: baseZoom * zoom
244
+ var totalZoom = cropMetadata.zoom;
245
+ // Dimensions de l'image après zoom
246
+ var scaledImgWidth = img.width * totalZoom;
247
+ var scaledImgHeight = img.height * totalZoom;
248
+ // Dans le modal, l'image est centrée dans le container
249
+ // Le container a les mêmes dimensions que le viewport du modal
250
+ // La cropArea est centrée dans ce container
251
+ // Les offsets (offsetX, offsetY) représentent le déplacement de l'image depuis le centre
252
+ // Position du centre de l'image zoomée (qui correspond au centre du container)
253
+ var imgCenterX = scaledImgWidth / 2;
254
+ var imgCenterY = scaledImgHeight / 2;
255
+ // Position du centre de la cropArea dans l'image zoomée
256
+ // Les offsets sont la position de l'image par rapport au centre du container
257
+ // Donc le centre de la cropArea dans l'image zoomée est :
258
+ var cropCenterX = imgCenterX - cropMetadata.offsetX;
259
+ var cropCenterY = imgCenterY - cropMetadata.offsetY;
260
+ // Coordonnées du coin supérieur gauche de la zone à cropper dans l'image zoomée
261
+ var cropXInScaledImg = cropCenterX - cropAreaWidth / 2;
262
+ var cropYInScaledImg = cropCenterY - cropAreaHeight / 2;
263
+ // Convertir ces coordonnées en coordonnées de l'image originale (diviser par le zoom)
264
+ var sourceX = cropXInScaledImg / totalZoom;
265
+ var sourceY = cropYInScaledImg / totalZoom;
266
+ var sourceWidth = cropAreaWidth / totalZoom;
267
+ var sourceHeight = cropAreaHeight / totalZoom;
268
+ // S'assurer que les coordonnées sont valides
269
+ var clampedSourceX = Math.max(0, Math.min(sourceX, img.width - sourceWidth));
270
+ var clampedSourceY = Math.max(0, Math.min(sourceY, img.height - sourceHeight));
271
+ var clampedSourceWidth = Math.min(sourceWidth, img.width - clampedSourceX);
272
+ var clampedSourceHeight = Math.min(sourceHeight, img.height - clampedSourceY);
273
+ // Dessiner l'image croppée sur le canvas
274
+ ctx.drawImage(img, clampedSourceX, clampedSourceY, clampedSourceWidth, clampedSourceHeight, 0, 0, outputWidth, outputHeight);
275
+ // Si c'est un cercle, appliquer un masque
276
+ if (cropMetadata.shape === "circle") {
277
+ var imageData = ctx.getImageData(0, 0, outputWidth, outputHeight);
278
+ var pixels = imageData.data;
279
+ var centerX = outputWidth / 2;
280
+ var centerY = outputHeight / 2;
281
+ var radius = Math.min(outputWidth, outputHeight) / 2;
282
+ for (var y = 0; y < outputHeight; y++) {
283
+ for (var x = 0; x < outputWidth; x++) {
284
+ var distance = Math.sqrt(Math.pow(x - centerX, 2) + Math.pow(y - centerY, 2));
285
+ if (distance > radius) {
286
+ var index = (y * outputWidth + x) * 4;
287
+ pixels[index + 3] = 0; // Rendre transparent
288
+ }
289
+ }
290
+ }
291
+ ctx.putImageData(imageData, 0, 0);
292
+ }
293
+ // Convertir en Blob PNG
294
+ canvas.toBlob(function (blob) {
295
+ if (blob) {
296
+ resolve(blob);
297
+ }
298
+ else {
299
+ reject(new Error("Échec de la conversion canvas vers Blob"));
300
+ }
301
+ }, "image/png", 0.95);
302
+ }
303
+ catch (error) {
304
+ reject(error);
305
+ }
306
+ };
307
+ img.onerror = function () {
308
+ reject(new Error("Échec du chargement de l'image"));
309
+ };
310
+ img.src = imageUrl;
311
+ })];
312
+ });
313
+ }); };
210
314
  var handleCropCancel = function () {
211
315
  setShowCropper(false);
212
316
  };
213
- var handleCropConfirm = function (cropMetadata) {
214
- setShowCropper(false);
215
- setCropMetadata(cropMetadata);
216
- if (autoManageProgress) {
217
- startProgressSimulation();
218
- }
219
- if (selectedFile && fileContent) {
220
- onFileRead === null || onFileRead === void 0 ? void 0 : onFileRead(selectedFile, fileContent, cropMetadata);
221
- }
222
- };
317
+ var handleCropConfirm = function (cropMetadata) { return __awaiter(void 0, void 0, void 0, function () {
318
+ var dimensions, croppedBlob, croppedFile, croppedContent, croppedUrl, error_2;
319
+ return __generator(this, function (_a) {
320
+ switch (_a.label) {
321
+ case 0:
322
+ setShowCropper(false);
323
+ setCropMetadata(cropMetadata);
324
+ if (autoManageProgress) {
325
+ startProgressSimulation();
326
+ }
327
+ if (!(selectedFile && fileContent && previewUrl)) return [3 /*break*/, 7];
328
+ if (!clientSideCrop) return [3 /*break*/, 6];
329
+ _a.label = 1;
330
+ case 1:
331
+ _a.trys.push([1, 4, , 5]);
332
+ dimensions = cropOutputDimensions[cropMetadata.shape] ||
333
+ cropOutputDimensions.square ||
334
+ { width: 200, height: 200 };
335
+ return [4 /*yield*/, cropImageToBlob(previewUrl, cropMetadata, dimensions.width, dimensions.height)];
336
+ case 2:
337
+ croppedBlob = _a.sent();
338
+ croppedFile = new File([croppedBlob], selectedFile.name.replace(/\.[^/.]+$/, ".png"), // Remplacer l'extension par .png
339
+ { type: "image/png" });
340
+ return [4 /*yield*/, croppedFile.arrayBuffer()];
341
+ case 3:
342
+ croppedContent = _a.sent();
343
+ croppedUrl = URL.createObjectURL(croppedBlob);
344
+ if (previewUrl && !initialPreviewUrl) {
345
+ URL.revokeObjectURL(previewUrl);
346
+ }
347
+ setPreviewUrl(croppedUrl);
348
+ setSelectedFile(croppedFile);
349
+ setFileContent(croppedContent);
350
+ setCurrentFileName(croppedFile.name);
351
+ // Appeler onFileRead avec le fichier croppé (pas de cropMetadata car déjà croppé)
352
+ onFileRead === null || onFileRead === void 0 ? void 0 : onFileRead(croppedFile, croppedContent);
353
+ return [3 /*break*/, 5];
354
+ case 4:
355
+ error_2 = _a.sent();
356
+ console.error("Erreur lors du crop de l'image:", error_2);
357
+ // En cas d'erreur, fallback sur l'image originale avec métadonnées
358
+ onFileRead === null || onFileRead === void 0 ? void 0 : onFileRead(selectedFile, fileContent, cropMetadata);
359
+ return [3 /*break*/, 5];
360
+ case 5: return [3 /*break*/, 7];
361
+ case 6:
362
+ // Passer l'image originale avec les métadonnées au serveur
363
+ onFileRead === null || onFileRead === void 0 ? void 0 : onFileRead(selectedFile, fileContent, cropMetadata);
364
+ _a.label = 7;
365
+ case 7: return [2 /*return*/];
366
+ }
367
+ });
368
+ }); };
223
369
  var handleFileDelete = function () {
224
370
  if (previewUrl && !initialPreviewUrl) {
225
371
  URL.revokeObjectURL(previewUrl);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "allaw-ui",
3
- "version": "5.1.1",
3
+ "version": "5.1.3",
4
4
  "description": "Composants UI pour l'application Allaw",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.esm.js",