allaw-ui 3.0.0 → 3.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/allaw-icon-pdf.svg +5 -0
- package/dist/components/molecules/fileUploader/FileUploader.d.ts +28 -0
- package/dist/components/molecules/fileUploader/FileUploader.js +217 -0
- package/dist/components/molecules/fileUploader/FileUploader.module.css +299 -0
- package/dist/components/molecules/fileUploader/FileUploader.stories.d.ts +106 -0
- package/dist/components/molecules/fileUploader/FileUploader.stories.js +226 -0
- package/dist/components/molecules/fileUploader/ImageCropperModal.d.ts +15 -0
- package/dist/components/molecules/fileUploader/ImageCropperModal.js +211 -0
- package/dist/components/molecules/fileUploader/ImageCropperModal.module.css +232 -0
- package/dist/components/molecules/fileUploader/index.d.ts +2 -0
- package/dist/components/molecules/fileUploader/index.js +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/package.json +1 -1
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
var __assign = (this && this.__assign) || function () {
|
|
2
|
+
__assign = Object.assign || function(t) {
|
|
3
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
4
|
+
s = arguments[i];
|
|
5
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
6
|
+
t[p] = s[p];
|
|
7
|
+
}
|
|
8
|
+
return t;
|
|
9
|
+
};
|
|
10
|
+
return __assign.apply(this, arguments);
|
|
11
|
+
};
|
|
12
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
13
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
14
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
15
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
16
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
17
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
18
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
22
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
|
|
23
|
+
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
24
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
25
|
+
function step(op) {
|
|
26
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
27
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
28
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
29
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
30
|
+
switch (op[0]) {
|
|
31
|
+
case 0: case 1: t = op; break;
|
|
32
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
33
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
34
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
35
|
+
default:
|
|
36
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
37
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
38
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
39
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
40
|
+
if (t[2]) _.ops.pop();
|
|
41
|
+
_.trys.pop(); continue;
|
|
42
|
+
}
|
|
43
|
+
op = body.call(thisArg, _);
|
|
44
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
45
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
import React, { useState } from "react";
|
|
49
|
+
import FileUploader from "./FileUploader";
|
|
50
|
+
import "../../../styles/global.css";
|
|
51
|
+
import pdfIcon from "../../../assets/allaw-icon-pdf.svg";
|
|
52
|
+
export default {
|
|
53
|
+
title: "Components/Molecules/FileUploader/FileUploader",
|
|
54
|
+
component: FileUploader,
|
|
55
|
+
tags: ["autodocs"],
|
|
56
|
+
parameters: {
|
|
57
|
+
backgrounds: {
|
|
58
|
+
default: "light",
|
|
59
|
+
values: [
|
|
60
|
+
{ name: "light", value: "#ffffff" },
|
|
61
|
+
{ name: "grey", value: "#728ea7" },
|
|
62
|
+
{ name: "figma", value: "#404040" },
|
|
63
|
+
{ name: "dark", value: "#171e25" },
|
|
64
|
+
],
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
argTypes: {
|
|
68
|
+
acceptedExtensions: {
|
|
69
|
+
control: "array",
|
|
70
|
+
description: "Liste des extensions de fichiers acceptées",
|
|
71
|
+
},
|
|
72
|
+
maxFileSizeMB: {
|
|
73
|
+
control: "number",
|
|
74
|
+
description: "Taille maximale du fichier en MB",
|
|
75
|
+
},
|
|
76
|
+
enableDragAndDrop: {
|
|
77
|
+
control: "boolean",
|
|
78
|
+
description: "Active/désactive la zone de drag & drop",
|
|
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
|
+
},
|
|
88
|
+
iconUrl: {
|
|
89
|
+
control: "text",
|
|
90
|
+
description: "URL de l'icône du type de fichier",
|
|
91
|
+
},
|
|
92
|
+
buttonLabel: {
|
|
93
|
+
control: "text",
|
|
94
|
+
description: "Texte du bouton de sélection manuelle",
|
|
95
|
+
},
|
|
96
|
+
acceptedLabel: {
|
|
97
|
+
control: "text",
|
|
98
|
+
description: "Texte sur le format accepté",
|
|
99
|
+
},
|
|
100
|
+
maxSizeLabel: {
|
|
101
|
+
control: "text",
|
|
102
|
+
description: "Texte sur la taille maximale",
|
|
103
|
+
},
|
|
104
|
+
uploadProgress: {
|
|
105
|
+
control: { type: "range", min: 0, max: 10, step: 1 },
|
|
106
|
+
description: "Progression de l'upload (0-10)",
|
|
107
|
+
},
|
|
108
|
+
isLoading: {
|
|
109
|
+
control: "boolean",
|
|
110
|
+
description: "Affiche un loader pendant lecture ou traitement",
|
|
111
|
+
},
|
|
112
|
+
errorMessage: {
|
|
113
|
+
control: "text",
|
|
114
|
+
description: "Message d'erreur à afficher",
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
// Template de base avec gestion d'état intégré pour la démo
|
|
119
|
+
var Template = function (args) {
|
|
120
|
+
var _a = useState(null), file = _a[0], setFile = _a[1];
|
|
121
|
+
var _b = useState(null), fileContent = _b[0], setFileContent = _b[1];
|
|
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 () {
|
|
127
|
+
return __generator(this, function (_a) {
|
|
128
|
+
switch (_a.label) {
|
|
129
|
+
case 0:
|
|
130
|
+
setLoading(true);
|
|
131
|
+
setFile(file);
|
|
132
|
+
setFileContent(content);
|
|
133
|
+
if (cropMeta) {
|
|
134
|
+
setCropMetadata(cropMeta);
|
|
135
|
+
console.log("Métadonnées de cadrage:", cropMeta);
|
|
136
|
+
}
|
|
137
|
+
// Simuler une progression
|
|
138
|
+
setProgress(2);
|
|
139
|
+
return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 300); })];
|
|
140
|
+
case 1:
|
|
141
|
+
_a.sent();
|
|
142
|
+
setProgress(5);
|
|
143
|
+
return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 300); })];
|
|
144
|
+
case 2:
|
|
145
|
+
_a.sent();
|
|
146
|
+
setProgress(8);
|
|
147
|
+
return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 300); })];
|
|
148
|
+
case 3:
|
|
149
|
+
_a.sent();
|
|
150
|
+
setProgress(10);
|
|
151
|
+
setLoading(false);
|
|
152
|
+
console.log("Fichier lu:", file.name, "Taille:", file.size);
|
|
153
|
+
return [2 /*return*/];
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
}); };
|
|
157
|
+
var handleFileRemove = function () {
|
|
158
|
+
setFile(null);
|
|
159
|
+
setFileContent(null);
|
|
160
|
+
setCropMetadata(null);
|
|
161
|
+
setProgress(0);
|
|
162
|
+
setError(null);
|
|
163
|
+
console.log("Fichier supprimé");
|
|
164
|
+
};
|
|
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))))));
|
|
175
|
+
};
|
|
176
|
+
export var Default = Template.bind({});
|
|
177
|
+
Default.args = {
|
|
178
|
+
acceptedExtensions: [".jpg", ".jpeg", ".png", ".webp"],
|
|
179
|
+
maxFileSizeMB: 5,
|
|
180
|
+
enableDragAndDrop: true,
|
|
181
|
+
enableCropping: true,
|
|
182
|
+
cropShape: "circle",
|
|
183
|
+
buttonLabel: "Choisir une image",
|
|
184
|
+
acceptedLabel: "Formats acceptés :",
|
|
185
|
+
maxSizeLabel: "Taille maximale :",
|
|
186
|
+
};
|
|
187
|
+
export var WithDescription = Template.bind({});
|
|
188
|
+
WithDescription.args = __assign(__assign({}, Default.args), { descriptionParts: {
|
|
189
|
+
beforeLink: "Une",
|
|
190
|
+
linkLabel: "page sera ajoutée",
|
|
191
|
+
linkUrl: "https://example.com",
|
|
192
|
+
afterLink: "à la fin de votre convention d'honoraire précisant les détails du rendez-vous.",
|
|
193
|
+
} });
|
|
194
|
+
export var WithoutDragAndDrop = Template.bind({});
|
|
195
|
+
WithoutDragAndDrop.args = __assign(__assign({}, Default.args), { enableDragAndDrop: false });
|
|
196
|
+
export var WithError = Template.bind({});
|
|
197
|
+
WithError.args = __assign(__assign({}, Default.args), { errorMessage: "Le format du fichier choisi n'est pas supporté ou sa taille dépasse 5 Mo." });
|
|
198
|
+
export var WithProgress = Template.bind({});
|
|
199
|
+
WithProgress.args = __assign(__assign({}, Default.args), { uploadProgress: 6, isLoading: true, fileName: "document.pdf", fileSize: 2 * 1024 * 1024 });
|
|
200
|
+
// Exemple avec des extensions multiples
|
|
201
|
+
export var MultipleExtensions = Template.bind({});
|
|
202
|
+
MultipleExtensions.args = __assign(__assign({}, Default.args), { acceptedExtensions: [".pdf", ".doc", ".docx"] });
|
|
203
|
+
// Exemple avec le cadrage d'image en carré
|
|
204
|
+
export var WithSquareCropping = Template.bind({});
|
|
205
|
+
WithSquareCropping.args = {
|
|
206
|
+
acceptedExtensions: [".jpg", ".jpeg", ".png", ".webp"],
|
|
207
|
+
maxFileSizeMB: 5,
|
|
208
|
+
enableDragAndDrop: true,
|
|
209
|
+
enableCropping: true,
|
|
210
|
+
cropShape: "square",
|
|
211
|
+
buttonLabel: "Choisir une image",
|
|
212
|
+
acceptedLabel: "Formats acceptés :",
|
|
213
|
+
maxSizeLabel: "Taille maximale :",
|
|
214
|
+
};
|
|
215
|
+
// Exemple avec le cadrage d'image en cercle
|
|
216
|
+
export var WithCircleCropping = Template.bind({});
|
|
217
|
+
WithCircleCropping.args = {
|
|
218
|
+
acceptedExtensions: [".jpg", ".jpeg", ".png", ".webp"],
|
|
219
|
+
maxFileSizeMB: 5,
|
|
220
|
+
enableDragAndDrop: true,
|
|
221
|
+
enableCropping: true,
|
|
222
|
+
cropShape: "circle",
|
|
223
|
+
buttonLabel: "Choisir une image",
|
|
224
|
+
acceptedLabel: "Formats acceptés :",
|
|
225
|
+
maxSizeLabel: "Taille maximale :",
|
|
226
|
+
};
|
|
@@ -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,211 @@
|
|
|
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
|
+
return onConfirm({
|
|
204
|
+
zoom: zoom,
|
|
205
|
+
offsetX: position.x,
|
|
206
|
+
offsetY: position.y,
|
|
207
|
+
shape: shape,
|
|
208
|
+
});
|
|
209
|
+
} }, "Valider"))))));
|
|
210
|
+
};
|
|
211
|
+
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
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as FileUploader } from "./FileUploader";
|