@greatapps/common 1.1.453 → 1.1.455

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,5 +1,5 @@
1
1
  "use client";
2
- import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useRef, useState } from "react";
4
4
  import ReactCrop, {
5
5
  centerCrop,
@@ -14,14 +14,73 @@ import {
14
14
  DialogTitle
15
15
  } from "../../ui/overlay/Dialog";
16
16
  import { Button } from "../../ui/buttons/Button";
17
+ const baseCropStyles = `
18
+ .custom-crop-area .ReactCrop__crop-selection {
19
+ border: 2px solid white !important;
20
+ box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.5) !important;
21
+ }
22
+
23
+ /* Grid de 9 quadrados - linhas pontilhadas finas */
24
+ .custom-crop-area .ReactCrop__crop-selection .crop-grid {
25
+ position: absolute;
26
+ inset: 0;
27
+ pointer-events: none;
28
+ }
29
+
30
+ .custom-crop-area .ReactCrop__crop-selection .crop-grid-line {
31
+ position: absolute;
32
+ background: repeating-linear-gradient(
33
+ to right,
34
+ rgba(255, 255, 255, 0.5) 0,
35
+ rgba(255, 255, 255, 0.5) 4px,
36
+ transparent 4px,
37
+ transparent 8px
38
+ );
39
+ }
40
+
41
+ .custom-crop-area .ReactCrop__crop-selection .crop-grid-line.horizontal {
42
+ left: 0;
43
+ right: 0;
44
+ height: 1px;
45
+ }
46
+
47
+ .custom-crop-area .ReactCrop__crop-selection .crop-grid-line.vertical {
48
+ top: 0;
49
+ bottom: 0;
50
+ width: 1px;
51
+ background: repeating-linear-gradient(
52
+ to bottom,
53
+ rgba(255, 255, 255, 0.5) 0,
54
+ rgba(255, 255, 255, 0.5) 4px,
55
+ transparent 4px,
56
+ transparent 8px
57
+ );
58
+ }
59
+
60
+ .custom-crop-area .ReactCrop__crop-selection .crop-grid-line.h1 { top: 33.33%; }
61
+ .custom-crop-area .ReactCrop__crop-selection .crop-grid-line.h2 { top: 66.66%; }
62
+ .custom-crop-area .ReactCrop__crop-selection .crop-grid-line.v1 { left: 33.33%; }
63
+ .custom-crop-area .ReactCrop__crop-selection .crop-grid-line.v2 { left: 66.66%; }
64
+
65
+ /* C\xEDrculo inscrito */
66
+ .custom-crop-area.with-circle .ReactCrop__crop-selection .crop-circle {
67
+ position: absolute;
68
+ inset: 0;
69
+ border: 1px dashed rgba(255, 255, 255, 0.6);
70
+ border-radius: 50%;
71
+ pointer-events: none;
72
+ }
73
+ `;
17
74
  function ImageCropModal({
18
75
  open,
19
76
  onOpenChange,
20
77
  imageSrc,
21
78
  aspectRatio,
22
- onConfirm
79
+ onConfirm,
80
+ showCircleGuide
23
81
  }) {
24
82
  const imgRef = useRef(null);
83
+ const containerRef = useRef(null);
25
84
  const [crop, setCrop] = useState();
26
85
  const onImageLoad = (e) => {
27
86
  const { naturalWidth, naturalHeight } = e.currentTarget;
@@ -42,33 +101,48 @@ function ImageCropModal({
42
101
  onConfirm(imgRef.current, crop);
43
102
  }
44
103
  };
45
- return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs(DialogContent, { className: "max-w-2xl", children: [
46
- /* @__PURE__ */ jsx(DialogHeader, { children: /* @__PURE__ */ jsx(DialogTitle, { children: "Ajustar imagem" }) }),
47
- /* @__PURE__ */ jsx("div", { className: "flex justify-center py-4", children: /* @__PURE__ */ jsx(
48
- ReactCrop,
49
- {
50
- crop,
51
- onChange: (c) => setCrop(c),
52
- aspect: aspectRatio,
53
- className: "max-h-[60vh]",
54
- children: /* @__PURE__ */ jsx(
55
- "img",
56
- {
57
- ref: imgRef,
58
- src: imageSrc,
59
- alt: "Crop",
60
- onLoad: onImageLoad,
61
- className: "max-h-[60vh] object-contain"
62
- }
63
- )
64
- }
65
- ) }),
66
- /* @__PURE__ */ jsx("p", { className: "paragraph-small-regular text-gray-500 text-center", children: "Arraste para ajustar a \xE1rea de corte" }),
67
- /* @__PURE__ */ jsxs(DialogFooter, { children: [
68
- /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: () => onOpenChange(false), children: "Cancelar" }),
69
- /* @__PURE__ */ jsx(Button, { onClick: handleConfirm, children: "Confirmar" })
104
+ const shouldShowCircle = showCircleGuide ?? aspectRatio === 1;
105
+ const renderSelectionAddon = () => /* @__PURE__ */ jsxs(Fragment, { children: [
106
+ /* @__PURE__ */ jsxs("div", { className: "crop-grid", children: [
107
+ /* @__PURE__ */ jsx("div", { className: "crop-grid-line horizontal h1" }),
108
+ /* @__PURE__ */ jsx("div", { className: "crop-grid-line horizontal h2" }),
109
+ /* @__PURE__ */ jsx("div", { className: "crop-grid-line vertical v1" }),
110
+ /* @__PURE__ */ jsx("div", { className: "crop-grid-line vertical v2" })
111
+ ] }),
112
+ shouldShowCircle && /* @__PURE__ */ jsx("div", { className: "crop-circle" })
113
+ ] });
114
+ return /* @__PURE__ */ jsxs(Dialog, { open, onOpenChange, children: [
115
+ /* @__PURE__ */ jsx("style", { children: baseCropStyles }),
116
+ /* @__PURE__ */ jsxs(DialogContent, { className: "max-w-2xl", children: [
117
+ /* @__PURE__ */ jsx(DialogHeader, { children: /* @__PURE__ */ jsx(DialogTitle, { children: "Ajustar imagem" }) }),
118
+ /* @__PURE__ */ jsx("div", { ref: containerRef, className: "flex justify-center py-4 relative", children: /* @__PURE__ */ jsx(
119
+ ReactCrop,
120
+ {
121
+ crop,
122
+ onChange: (c) => setCrop(c),
123
+ aspect: aspectRatio,
124
+ className: `max-h-[60vh] custom-crop-area ${shouldShowCircle ? "with-circle" : ""}`,
125
+ keepSelection: true,
126
+ renderSelectionAddon,
127
+ children: /* @__PURE__ */ jsx(
128
+ "img",
129
+ {
130
+ ref: imgRef,
131
+ src: imageSrc,
132
+ alt: "Crop",
133
+ onLoad: onImageLoad,
134
+ className: "max-h-[60vh] object-contain"
135
+ }
136
+ )
137
+ }
138
+ ) }),
139
+ /* @__PURE__ */ jsx("p", { className: "paragraph-small-regular text-zinc-500 text-center", children: "Arraste para ajustar a \xE1rea de corte" }),
140
+ /* @__PURE__ */ jsxs(DialogFooter, { children: [
141
+ /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: () => onOpenChange(false), children: "Cancelar" }),
142
+ /* @__PURE__ */ jsx(Button, { onClick: handleConfirm, children: "Confirmar" })
143
+ ] })
70
144
  ] })
71
- ] }) });
145
+ ] });
72
146
  }
73
147
  export {
74
148
  ImageCropModal
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/components/widgets/ImageUpload/ImageCropModal.tsx"],"sourcesContent":["'use client'\n\nimport { useRef, useState } from 'react'\nimport ReactCrop, {\n type Crop,\n centerCrop,\n makeAspectCrop,\n} from 'react-image-crop'\nimport 'react-image-crop/dist/ReactCrop.css'\nimport {\n Dialog,\n DialogContent,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '../../ui/overlay/Dialog'\nimport { Button } from '../../ui/buttons/Button'\n\ninterface ImageCropModalProps {\n open: boolean\n onOpenChange: (open: boolean) => void\n imageSrc: string\n aspectRatio: number\n onConfirm: (image: HTMLImageElement, crop: Crop) => void\n}\n\nexport function ImageCropModal({\n open,\n onOpenChange,\n imageSrc,\n aspectRatio,\n onConfirm,\n}: ImageCropModalProps) {\n const imgRef = useRef<HTMLImageElement>(null)\n const [crop, setCrop] = useState<Crop>()\n\n const onImageLoad = (e: React.SyntheticEvent<HTMLImageElement>) => {\n const { naturalWidth, naturalHeight } = e.currentTarget\n\n const initialCrop = centerCrop(\n makeAspectCrop(\n { unit: '%', width: 90 },\n aspectRatio,\n naturalWidth,\n naturalHeight\n ),\n naturalWidth,\n naturalHeight\n )\n\n setCrop(initialCrop)\n }\n\n const handleConfirm = () => {\n if (imgRef.current && crop) {\n onConfirm(imgRef.current, crop)\n }\n }\n\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent className=\"max-w-2xl\">\n <DialogHeader>\n <DialogTitle>Ajustar imagem</DialogTitle>\n </DialogHeader>\n\n <div className=\"flex justify-center py-4\">\n <ReactCrop\n crop={crop}\n onChange={(c) => setCrop(c)}\n aspect={aspectRatio}\n className=\"max-h-[60vh]\"\n >\n <img\n ref={imgRef}\n src={imageSrc}\n alt=\"Crop\"\n onLoad={onImageLoad}\n className=\"max-h-[60vh] object-contain\"\n />\n </ReactCrop>\n </div>\n\n <p className=\"paragraph-small-regular text-gray-500 text-center\">\n Arraste para ajustar a área de corte\n </p>\n\n <DialogFooter>\n <Button variant=\"secondary\" onClick={() => onOpenChange(false)}>\n Cancelar\n </Button>\n <Button onClick={handleConfirm}>Confirmar</Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n )\n}\n"],"mappings":";AA+DU,cAwBF,YAxBE;AA7DV,SAAS,QAAQ,gBAAgB;AACjC,OAAO;AAAA,EAEL;AAAA,EACA;AAAA,OACK;AACP,OAAO;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,cAAc;AAUhB,SAAS,eAAe;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAwB;AACtB,QAAM,SAAS,OAAyB,IAAI;AAC5C,QAAM,CAAC,MAAM,OAAO,IAAI,SAAe;AAEvC,QAAM,cAAc,CAAC,MAA8C;AACjE,UAAM,EAAE,cAAc,cAAc,IAAI,EAAE;AAE1C,UAAM,cAAc;AAAA,MAClB;AAAA,QACE,EAAE,MAAM,KAAK,OAAO,GAAG;AAAA,QACvB;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAEA,YAAQ,WAAW;AAAA,EACrB;AAEA,QAAM,gBAAgB,MAAM;AAC1B,QAAI,OAAO,WAAW,MAAM;AAC1B,gBAAU,OAAO,SAAS,IAAI;AAAA,IAChC;AAAA,EACF;AAEA,SACE,oBAAC,UAAO,MAAY,cAClB,+BAAC,iBAAc,WAAU,aACvB;AAAA,wBAAC,gBACC,8BAAC,eAAY,4BAAc,GAC7B;AAAA,IAEA,oBAAC,SAAI,WAAU,4BACb;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA,UAAU,CAAC,MAAM,QAAQ,CAAC;AAAA,QAC1B,QAAQ;AAAA,QACR,WAAU;AAAA,QAEV;AAAA,UAAC;AAAA;AAAA,YACC,KAAK;AAAA,YACL,KAAK;AAAA,YACL,KAAI;AAAA,YACJ,QAAQ;AAAA,YACR,WAAU;AAAA;AAAA,QACZ;AAAA;AAAA,IACF,GACF;AAAA,IAEA,oBAAC,OAAE,WAAU,qDAAoD,qDAEjE;AAAA,IAEA,qBAAC,gBACC;AAAA,0BAAC,UAAO,SAAQ,aAAY,SAAS,MAAM,aAAa,KAAK,GAAG,sBAEhE;AAAA,MACA,oBAAC,UAAO,SAAS,eAAe,uBAAS;AAAA,OAC3C;AAAA,KACF,GACF;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../../src/components/widgets/ImageUpload/ImageCropModal.tsx"],"sourcesContent":["'use client'\n\nimport { useRef, useState } from 'react'\nimport ReactCrop, {\n type Crop,\n centerCrop,\n makeAspectCrop,\n} from 'react-image-crop'\nimport 'react-image-crop/dist/ReactCrop.css'\nimport {\n Dialog,\n DialogContent,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '../../ui/overlay/Dialog'\nimport { Button } from '../../ui/buttons/Button'\n\n// CSS customizado para grid pontilhado fino e círculo\nconst baseCropStyles = `\n .custom-crop-area .ReactCrop__crop-selection {\n border: 2px solid white !important;\n box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.5) !important;\n }\n\n /* Grid de 9 quadrados - linhas pontilhadas finas */\n .custom-crop-area .ReactCrop__crop-selection .crop-grid {\n position: absolute;\n inset: 0;\n pointer-events: none;\n }\n\n .custom-crop-area .ReactCrop__crop-selection .crop-grid-line {\n position: absolute;\n background: repeating-linear-gradient(\n to right,\n rgba(255, 255, 255, 0.5) 0,\n rgba(255, 255, 255, 0.5) 4px,\n transparent 4px,\n transparent 8px\n );\n }\n\n .custom-crop-area .ReactCrop__crop-selection .crop-grid-line.horizontal {\n left: 0;\n right: 0;\n height: 1px;\n }\n\n .custom-crop-area .ReactCrop__crop-selection .crop-grid-line.vertical {\n top: 0;\n bottom: 0;\n width: 1px;\n background: repeating-linear-gradient(\n to bottom,\n rgba(255, 255, 255, 0.5) 0,\n rgba(255, 255, 255, 0.5) 4px,\n transparent 4px,\n transparent 8px\n );\n }\n\n .custom-crop-area .ReactCrop__crop-selection .crop-grid-line.h1 { top: 33.33%; }\n .custom-crop-area .ReactCrop__crop-selection .crop-grid-line.h2 { top: 66.66%; }\n .custom-crop-area .ReactCrop__crop-selection .crop-grid-line.v1 { left: 33.33%; }\n .custom-crop-area .ReactCrop__crop-selection .crop-grid-line.v2 { left: 66.66%; }\n\n /* Círculo inscrito */\n .custom-crop-area.with-circle .ReactCrop__crop-selection .crop-circle {\n position: absolute;\n inset: 0;\n border: 1px dashed rgba(255, 255, 255, 0.6);\n border-radius: 50%;\n pointer-events: none;\n }\n`\n\ninterface ImageCropModalProps {\n open: boolean\n onOpenChange: (open: boolean) => void\n imageSrc: string\n aspectRatio: number\n onConfirm: (image: HTMLImageElement, crop: Crop) => void\n showCircleGuide?: boolean\n}\n\nexport function ImageCropModal({\n open,\n onOpenChange,\n imageSrc,\n aspectRatio,\n onConfirm,\n showCircleGuide,\n}: ImageCropModalProps) {\n const imgRef = useRef<HTMLImageElement>(null)\n const containerRef = useRef<HTMLDivElement>(null)\n const [crop, setCrop] = useState<Crop>()\n\n const onImageLoad = (e: React.SyntheticEvent<HTMLImageElement>) => {\n const { naturalWidth, naturalHeight } = e.currentTarget\n\n const initialCrop = centerCrop(\n makeAspectCrop(\n { unit: '%', width: 90 },\n aspectRatio,\n naturalWidth,\n naturalHeight\n ),\n naturalWidth,\n naturalHeight\n )\n\n setCrop(initialCrop)\n }\n\n const handleConfirm = () => {\n if (imgRef.current && crop) {\n onConfirm(imgRef.current, crop)\n }\n }\n\n // Determina se deve mostrar círculo (aspect ratio 1:1 por padrão)\n const shouldShowCircle = showCircleGuide ?? aspectRatio === 1\n\n // Renderiza grid e círculo dentro da área de crop\n const renderSelectionAddon = () => (\n <>\n {/* Grid de 9 quadrados com linhas pontilhadas */}\n <div className=\"crop-grid\">\n <div className=\"crop-grid-line horizontal h1\" />\n <div className=\"crop-grid-line horizontal h2\" />\n <div className=\"crop-grid-line vertical v1\" />\n <div className=\"crop-grid-line vertical v2\" />\n </div>\n {/* Círculo inscrito */}\n {shouldShowCircle && <div className=\"crop-circle\" />}\n </>\n )\n\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <style>{baseCropStyles}</style>\n <DialogContent className=\"max-w-2xl\">\n <DialogHeader>\n <DialogTitle>Ajustar imagem</DialogTitle>\n </DialogHeader>\n\n <div ref={containerRef} className=\"flex justify-center py-4 relative\">\n <ReactCrop\n crop={crop}\n onChange={(c) => setCrop(c)}\n aspect={aspectRatio}\n className={`max-h-[60vh] custom-crop-area ${shouldShowCircle ? 'with-circle' : ''}`}\n keepSelection\n renderSelectionAddon={renderSelectionAddon}\n >\n <img\n ref={imgRef}\n src={imageSrc}\n alt=\"Crop\"\n onLoad={onImageLoad}\n className=\"max-h-[60vh] object-contain\"\n />\n </ReactCrop>\n </div>\n\n <p className=\"paragraph-small-regular text-zinc-500 text-center\">\n Arraste para ajustar a área de corte\n </p>\n\n <DialogFooter>\n <Button variant=\"secondary\" onClick={() => onOpenChange(false)}>\n Cancelar\n </Button>\n <Button onClick={handleConfirm}>Confirmar</Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n )\n}\n"],"mappings":";AA8HI,mBAGI,KADF,YAFF;AA5HJ,SAAS,QAAQ,gBAAgB;AACjC,OAAO;AAAA,EAEL;AAAA,EACA;AAAA,OACK;AACP,OAAO;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,cAAc;AAGvB,MAAM,iBAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAmEhB,SAAS,eAAe;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAwB;AACtB,QAAM,SAAS,OAAyB,IAAI;AAC5C,QAAM,eAAe,OAAuB,IAAI;AAChD,QAAM,CAAC,MAAM,OAAO,IAAI,SAAe;AAEvC,QAAM,cAAc,CAAC,MAA8C;AACjE,UAAM,EAAE,cAAc,cAAc,IAAI,EAAE;AAE1C,UAAM,cAAc;AAAA,MAClB;AAAA,QACE,EAAE,MAAM,KAAK,OAAO,GAAG;AAAA,QACvB;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAEA,YAAQ,WAAW;AAAA,EACrB;AAEA,QAAM,gBAAgB,MAAM;AAC1B,QAAI,OAAO,WAAW,MAAM;AAC1B,gBAAU,OAAO,SAAS,IAAI;AAAA,IAChC;AAAA,EACF;AAGA,QAAM,mBAAmB,mBAAmB,gBAAgB;AAG5D,QAAM,uBAAuB,MAC3B,iCAEE;AAAA,yBAAC,SAAI,WAAU,aACb;AAAA,0BAAC,SAAI,WAAU,gCAA+B;AAAA,MAC9C,oBAAC,SAAI,WAAU,gCAA+B;AAAA,MAC9C,oBAAC,SAAI,WAAU,8BAA6B;AAAA,MAC5C,oBAAC,SAAI,WAAU,8BAA6B;AAAA,OAC9C;AAAA,IAEC,oBAAoB,oBAAC,SAAI,WAAU,eAAc;AAAA,KACpD;AAGF,SACE,qBAAC,UAAO,MAAY,cAClB;AAAA,wBAAC,WAAO,0BAAe;AAAA,IACvB,qBAAC,iBAAc,WAAU,aACvB;AAAA,0BAAC,gBACC,8BAAC,eAAY,4BAAc,GAC7B;AAAA,MAEA,oBAAC,SAAI,KAAK,cAAc,WAAU,qCAChC;AAAA,QAAC;AAAA;AAAA,UACC;AAAA,UACA,UAAU,CAAC,MAAM,QAAQ,CAAC;AAAA,UAC1B,QAAQ;AAAA,UACR,WAAW,iCAAiC,mBAAmB,gBAAgB,EAAE;AAAA,UACjF,eAAa;AAAA,UACb;AAAA,UAEA;AAAA,YAAC;AAAA;AAAA,cACC,KAAK;AAAA,cACL,KAAK;AAAA,cACL,KAAI;AAAA,cACJ,QAAQ;AAAA,cACR,WAAU;AAAA;AAAA,UACZ;AAAA;AAAA,MACF,GACF;AAAA,MAEA,oBAAC,OAAE,WAAU,qDAAoD,qDAEjE;AAAA,MAEA,qBAAC,gBACC;AAAA,4BAAC,UAAO,SAAQ,aAAY,SAAS,MAAM,aAAa,KAAK,GAAG,sBAEhE;AAAA,QACA,oBAAC,UAAO,SAAS,eAAe,uBAAS;AAAA,SAC3C;AAAA,OACF;AAAA,KACF;AAEJ;","names":[]}
@@ -1,7 +1,6 @@
1
1
  import imageCompression from "browser-image-compression";
2
- import { TARGET_FILE_SIZE_MB } from "../constants/image.constants";
3
2
  async function compressImage(file, options = {}) {
4
- const { maxSizeMB = TARGET_FILE_SIZE_MB, maxWidthOrHeight = 1920 } = options;
3
+ const { maxSizeMB = 1, maxWidthOrHeight = 1920 } = options;
5
4
  if (file.size <= maxSizeMB * 1024 * 1024) {
6
5
  return file;
7
6
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/modules/images/utils/compress-image.ts"],"sourcesContent":["import imageCompression from 'browser-image-compression'\nimport type { CompressImageOptions } from '../types/image.type'\nimport { TARGET_FILE_SIZE_MB } from '../constants/image.constants'\n\nexport async function compressImage(\n file: File,\n options: CompressImageOptions = {}\n): Promise<File> {\n const { maxSizeMB = TARGET_FILE_SIZE_MB, maxWidthOrHeight = 1920 } = options\n\n if (file.size <= maxSizeMB * 1024 * 1024) {\n return file\n }\n\n return imageCompression(file, {\n maxSizeMB,\n maxWidthOrHeight,\n useWebWorker: true,\n fileType: file.type as 'image/jpeg' | 'image/png' | 'image/webp',\n })\n}\n"],"mappings":"AAAA,OAAO,sBAAsB;AAE7B,SAAS,2BAA2B;AAEpC,eAAsB,cACpB,MACA,UAAgC,CAAC,GAClB;AACf,QAAM,EAAE,YAAY,qBAAqB,mBAAmB,KAAK,IAAI;AAErE,MAAI,KAAK,QAAQ,YAAY,OAAO,MAAM;AACxC,WAAO;AAAA,EACT;AAEA,SAAO,iBAAiB,MAAM;AAAA,IAC5B;AAAA,IACA;AAAA,IACA,cAAc;AAAA,IACd,UAAU,KAAK;AAAA,EACjB,CAAC;AACH;","names":[]}
1
+ {"version":3,"sources":["../../../../src/modules/images/utils/compress-image.ts"],"sourcesContent":["import imageCompression from 'browser-image-compression'\nimport type { CompressImageOptions } from '../types/image.type'\n\nexport async function compressImage(\n file: File,\n options: CompressImageOptions = {}\n): Promise<File> {\n const { maxSizeMB = 1, maxWidthOrHeight = 1920 } = options\n\n if (file.size <= maxSizeMB * 1024 * 1024) {\n return file\n }\n\n return imageCompression(file, {\n maxSizeMB,\n maxWidthOrHeight,\n useWebWorker: true,\n fileType: file.type as 'image/jpeg' | 'image/png' | 'image/webp',\n })\n}\n"],"mappings":"AAAA,OAAO,sBAAsB;AAG7B,eAAsB,cACpB,MACA,UAAgC,CAAC,GAClB;AACf,QAAM,EAAE,YAAY,GAAG,mBAAmB,KAAK,IAAI;AAEnD,MAAI,KAAK,QAAQ,YAAY,OAAO,MAAM;AACxC,WAAO;AAAA,EACT;AAEA,SAAO,iBAAiB,MAAM;AAAA,IAC5B;AAAA,IACA;AAAA,IACA,cAAc;AAAA,IACd,UAAU,KAAK;AAAA,EACjB,CAAC;AACH;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@greatapps/common",
3
- "version": "1.1.453",
3
+ "version": "1.1.455",
4
4
  "description": "Shared library for GreatApps frontend applications",
5
5
  "main": "./dist/index.mjs",
6
6
  "types": "./src/index.ts",
@@ -16,12 +16,72 @@ import {
16
16
  } from '../../ui/overlay/Dialog'
17
17
  import { Button } from '../../ui/buttons/Button'
18
18
 
19
+ // CSS customizado para grid pontilhado fino e círculo
20
+ const baseCropStyles = `
21
+ .custom-crop-area .ReactCrop__crop-selection {
22
+ border: 2px solid white !important;
23
+ box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.5) !important;
24
+ }
25
+
26
+ /* Grid de 9 quadrados - linhas pontilhadas finas */
27
+ .custom-crop-area .ReactCrop__crop-selection .crop-grid {
28
+ position: absolute;
29
+ inset: 0;
30
+ pointer-events: none;
31
+ }
32
+
33
+ .custom-crop-area .ReactCrop__crop-selection .crop-grid-line {
34
+ position: absolute;
35
+ background: repeating-linear-gradient(
36
+ to right,
37
+ rgba(255, 255, 255, 0.5) 0,
38
+ rgba(255, 255, 255, 0.5) 4px,
39
+ transparent 4px,
40
+ transparent 8px
41
+ );
42
+ }
43
+
44
+ .custom-crop-area .ReactCrop__crop-selection .crop-grid-line.horizontal {
45
+ left: 0;
46
+ right: 0;
47
+ height: 1px;
48
+ }
49
+
50
+ .custom-crop-area .ReactCrop__crop-selection .crop-grid-line.vertical {
51
+ top: 0;
52
+ bottom: 0;
53
+ width: 1px;
54
+ background: repeating-linear-gradient(
55
+ to bottom,
56
+ rgba(255, 255, 255, 0.5) 0,
57
+ rgba(255, 255, 255, 0.5) 4px,
58
+ transparent 4px,
59
+ transparent 8px
60
+ );
61
+ }
62
+
63
+ .custom-crop-area .ReactCrop__crop-selection .crop-grid-line.h1 { top: 33.33%; }
64
+ .custom-crop-area .ReactCrop__crop-selection .crop-grid-line.h2 { top: 66.66%; }
65
+ .custom-crop-area .ReactCrop__crop-selection .crop-grid-line.v1 { left: 33.33%; }
66
+ .custom-crop-area .ReactCrop__crop-selection .crop-grid-line.v2 { left: 66.66%; }
67
+
68
+ /* Círculo inscrito */
69
+ .custom-crop-area.with-circle .ReactCrop__crop-selection .crop-circle {
70
+ position: absolute;
71
+ inset: 0;
72
+ border: 1px dashed rgba(255, 255, 255, 0.6);
73
+ border-radius: 50%;
74
+ pointer-events: none;
75
+ }
76
+ `
77
+
19
78
  interface ImageCropModalProps {
20
79
  open: boolean
21
80
  onOpenChange: (open: boolean) => void
22
81
  imageSrc: string
23
82
  aspectRatio: number
24
83
  onConfirm: (image: HTMLImageElement, crop: Crop) => void
84
+ showCircleGuide?: boolean
25
85
  }
26
86
 
27
87
  export function ImageCropModal({
@@ -30,8 +90,10 @@ export function ImageCropModal({
30
90
  imageSrc,
31
91
  aspectRatio,
32
92
  onConfirm,
93
+ showCircleGuide,
33
94
  }: ImageCropModalProps) {
34
95
  const imgRef = useRef<HTMLImageElement>(null)
96
+ const containerRef = useRef<HTMLDivElement>(null)
35
97
  const [crop, setCrop] = useState<Crop>()
36
98
 
37
99
  const onImageLoad = (e: React.SyntheticEvent<HTMLImageElement>) => {
@@ -57,19 +119,40 @@ export function ImageCropModal({
57
119
  }
58
120
  }
59
121
 
122
+ // Determina se deve mostrar círculo (aspect ratio 1:1 por padrão)
123
+ const shouldShowCircle = showCircleGuide ?? aspectRatio === 1
124
+
125
+ // Renderiza grid e círculo dentro da área de crop
126
+ const renderSelectionAddon = () => (
127
+ <>
128
+ {/* Grid de 9 quadrados com linhas pontilhadas */}
129
+ <div className="crop-grid">
130
+ <div className="crop-grid-line horizontal h1" />
131
+ <div className="crop-grid-line horizontal h2" />
132
+ <div className="crop-grid-line vertical v1" />
133
+ <div className="crop-grid-line vertical v2" />
134
+ </div>
135
+ {/* Círculo inscrito */}
136
+ {shouldShowCircle && <div className="crop-circle" />}
137
+ </>
138
+ )
139
+
60
140
  return (
61
141
  <Dialog open={open} onOpenChange={onOpenChange}>
142
+ <style>{baseCropStyles}</style>
62
143
  <DialogContent className="max-w-2xl">
63
144
  <DialogHeader>
64
145
  <DialogTitle>Ajustar imagem</DialogTitle>
65
146
  </DialogHeader>
66
147
 
67
- <div className="flex justify-center py-4">
148
+ <div ref={containerRef} className="flex justify-center py-4 relative">
68
149
  <ReactCrop
69
150
  crop={crop}
70
151
  onChange={(c) => setCrop(c)}
71
152
  aspect={aspectRatio}
72
- className="max-h-[60vh]"
153
+ className={`max-h-[60vh] custom-crop-area ${shouldShowCircle ? 'with-circle' : ''}`}
154
+ keepSelection
155
+ renderSelectionAddon={renderSelectionAddon}
73
156
  >
74
157
  <img
75
158
  ref={imgRef}
@@ -81,7 +164,7 @@ export function ImageCropModal({
81
164
  </ReactCrop>
82
165
  </div>
83
166
 
84
- <p className="paragraph-small-regular text-gray-500 text-center">
167
+ <p className="paragraph-small-regular text-zinc-500 text-center">
85
168
  Arraste para ajustar a área de corte
86
169
  </p>
87
170
 
@@ -1,12 +1,11 @@
1
1
  import imageCompression from 'browser-image-compression'
2
2
  import type { CompressImageOptions } from '../types/image.type'
3
- import { TARGET_FILE_SIZE_MB } from '../constants/image.constants'
4
3
 
5
4
  export async function compressImage(
6
5
  file: File,
7
6
  options: CompressImageOptions = {}
8
7
  ): Promise<File> {
9
- const { maxSizeMB = TARGET_FILE_SIZE_MB, maxWidthOrHeight = 1920 } = options
8
+ const { maxSizeMB = 1, maxWidthOrHeight = 1920 } = options
10
9
 
11
10
  if (file.size <= maxSizeMB * 1024 * 1024) {
12
11
  return file