@ikatec/nebula-react 1.0.26 → 1.0.28

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/index.js CHANGED
@@ -28,6 +28,8 @@ var dateFns = require('date-fns');
28
28
  var reactDayPicker = require('react-day-picker');
29
29
  var locale = require('react-day-picker/locale');
30
30
  var mask = require('@react-input/mask');
31
+ var SliderPrimitive = require('@radix-ui/react-slider');
32
+ var RadioGroupPrimitive = require('@radix-ui/react-radio-group');
31
33
 
32
34
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
33
35
 
@@ -67,6 +69,8 @@ var AccordionPrimitive__namespace = /*#__PURE__*/_interopNamespace(AccordionPrim
67
69
  var TabsPrimitive__namespace = /*#__PURE__*/_interopNamespace(TabsPrimitive);
68
70
  var RPNInput__namespace = /*#__PURE__*/_interopNamespace(RPNInput);
69
71
  var flags__default = /*#__PURE__*/_interopDefault(flags);
72
+ var SliderPrimitive__namespace = /*#__PURE__*/_interopNamespace(SliderPrimitive);
73
+ var RadioGroupPrimitive__namespace = /*#__PURE__*/_interopNamespace(RadioGroupPrimitive);
70
74
 
71
75
  // src/button.tsx
72
76
 
@@ -1157,8 +1161,16 @@ var messages5 = {
1157
1161
  };
1158
1162
  var file_upload_default = messages5;
1159
1163
 
1164
+ // src/i18n/messages/en/cropper.ts
1165
+ var messages6 = {
1166
+ applyButtonLabel: "Apply",
1167
+ dialogTitle: "Adjust image"
1168
+ };
1169
+ var cropper_default = messages6;
1170
+
1160
1171
  // src/i18n/messages/en/index.ts
1161
1172
  var enMessages = {
1173
+ cropper: cropper_default,
1162
1174
  pagination: pagination_default,
1163
1175
  inputSelect: input_select_default,
1164
1176
  inputPhone: input_phone_default,
@@ -1167,7 +1179,7 @@ var enMessages = {
1167
1179
  };
1168
1180
 
1169
1181
  // src/i18n/messages/es/pagination.ts
1170
- var messages6 = {
1182
+ var messages7 = {
1171
1183
  totalResultsLabel(pagesSize, totalResults) {
1172
1184
  if (totalResults < pagesSize) {
1173
1185
  pagesSize = totalResults;
@@ -1178,16 +1190,16 @@ var messages6 = {
1178
1190
  return `P\xE1gina ${currentPage} de ${totalPages}`;
1179
1191
  }
1180
1192
  };
1181
- var pagination_default2 = messages6;
1193
+ var pagination_default2 = messages7;
1182
1194
 
1183
1195
  // src/i18n/messages/es/input-select.ts
1184
- var messages7 = {
1196
+ var messages8 = {
1185
1197
  noOptions: "No hay opciones disponibles"
1186
1198
  };
1187
- var input_select_default2 = messages7;
1199
+ var input_select_default2 = messages8;
1188
1200
 
1189
1201
  // src/i18n/messages/es/input-phone.ts
1190
- var messages8 = {
1202
+ var messages9 = {
1191
1203
  countries: {
1192
1204
  empty: "Seleccionar",
1193
1205
  AF: "Afganist\xE1n",
@@ -1437,22 +1449,30 @@ var messages8 = {
1437
1449
  ZW: "Zimbabue"
1438
1450
  }
1439
1451
  };
1440
- var input_phone_default2 = messages8;
1452
+ var input_phone_default2 = messages9;
1441
1453
 
1442
1454
  // src/i18n/messages/es/time-picker.ts
1443
- var messages9 = {
1455
+ var messages10 = {
1444
1456
  label: "Tiempo"
1445
1457
  };
1446
- var time_picker_default2 = messages9;
1458
+ var time_picker_default2 = messages10;
1447
1459
 
1448
1460
  // src/i18n/messages/es/file-upload.ts
1449
- var messages10 = {
1461
+ var messages11 = {
1450
1462
  deleteAll: "Remover todos"
1451
1463
  };
1452
- var file_upload_default2 = messages10;
1464
+ var file_upload_default2 = messages11;
1465
+
1466
+ // src/i18n/messages/es/cropper.ts
1467
+ var messages12 = {
1468
+ applyButtonLabel: "Aplicar",
1469
+ dialogTitle: "Ajustar la imagen"
1470
+ };
1471
+ var cropper_default2 = messages12;
1453
1472
 
1454
1473
  // src/i18n/messages/es/index.ts
1455
1474
  var esMessages = {
1475
+ cropper: cropper_default2,
1456
1476
  pagination: pagination_default2,
1457
1477
  inputSelect: input_select_default2,
1458
1478
  inputPhone: input_phone_default2,
@@ -1461,7 +1481,7 @@ var esMessages = {
1461
1481
  };
1462
1482
 
1463
1483
  // src/i18n/messages/pt-br/pagination.ts
1464
- var messages11 = {
1484
+ var messages13 = {
1465
1485
  totalResultsLabel(pagesSize, totalResults) {
1466
1486
  if (totalResults < pagesSize) {
1467
1487
  pagesSize = totalResults;
@@ -1472,16 +1492,16 @@ var messages11 = {
1472
1492
  return `P\xE1gina ${currentPage} de ${totalPages}`;
1473
1493
  }
1474
1494
  };
1475
- var pagination_default3 = messages11;
1495
+ var pagination_default3 = messages13;
1476
1496
 
1477
1497
  // src/i18n/messages/pt-br/input-select.ts
1478
- var messages12 = {
1498
+ var messages14 = {
1479
1499
  noOptions: "Nenhuma op\xE7\xE3o dispon\xEDvel"
1480
1500
  };
1481
- var input_select_default3 = messages12;
1501
+ var input_select_default3 = messages14;
1482
1502
 
1483
1503
  // src/i18n/messages/pt-br/input-phone.ts
1484
- var messages13 = {
1504
+ var messages15 = {
1485
1505
  countries: {
1486
1506
  empty: "Selecione",
1487
1507
  AF: "Afeganist\xE3o",
@@ -1731,19 +1751,26 @@ var messages13 = {
1731
1751
  ZW: "Zimb\xE1bue"
1732
1752
  }
1733
1753
  };
1734
- var input_phone_default3 = messages13;
1754
+ var input_phone_default3 = messages15;
1735
1755
 
1736
1756
  // src/i18n/messages/pt-br/time-picker.ts
1737
- var messages14 = {
1757
+ var messages16 = {
1738
1758
  label: "Hor\xE1rio"
1739
1759
  };
1740
- var time_picker_default3 = messages14;
1760
+ var time_picker_default3 = messages16;
1741
1761
 
1742
1762
  // src/i18n/messages/pt-br/file-upload.ts
1743
- var messages15 = {
1763
+ var messages17 = {
1744
1764
  deleteAll: "Remover todos"
1745
1765
  };
1746
- var file_upload_default3 = messages15;
1766
+ var file_upload_default3 = messages17;
1767
+
1768
+ // src/i18n/messages/pt-br/cropper.ts
1769
+ var messages18 = {
1770
+ applyButtonLabel: "Aplicar",
1771
+ dialogTitle: "Ajustar imagem"
1772
+ };
1773
+ var cropper_default3 = messages18;
1747
1774
 
1748
1775
  // src/i18n/messages/pt-br/index.ts
1749
1776
  var ptBrMessages = {
@@ -1751,7 +1778,8 @@ var ptBrMessages = {
1751
1778
  inputSelect: input_select_default3,
1752
1779
  inputPhone: input_phone_default3,
1753
1780
  timePicker: time_picker_default3,
1754
- fileUpload: file_upload_default3
1781
+ fileUpload: file_upload_default3,
1782
+ cropper: cropper_default3
1755
1783
  };
1756
1784
 
1757
1785
  // src/i18n/message-storage-handler.ts
@@ -1776,7 +1804,7 @@ var setNebulaLanguage = (language) => {
1776
1804
  }
1777
1805
  localStorage.setItem(getNebulaI18nStorageKey(), language);
1778
1806
  };
1779
- var messages16 = /* @__PURE__ */ new Map([
1807
+ var messages19 = /* @__PURE__ */ new Map([
1780
1808
  [null, enMessages],
1781
1809
  [void 0, enMessages],
1782
1810
  ["en-US", enMessages],
@@ -1794,14 +1822,14 @@ var NebulaI18nProvider = ({
1794
1822
  () => customI18nStorageKey ?? localStorageKey,
1795
1823
  [customI18nStorageKey]
1796
1824
  );
1797
- const [messages17, setMessages] = React8.useState(
1798
- messages16.get(getNebulaLanguage()) ?? messages16.get("en-US")
1825
+ const [messages20, setMessages] = React8.useState(
1826
+ messages19.get(getNebulaLanguage()) ?? messages19.get("en-US")
1799
1827
  );
1800
1828
  const handleStorageChange = React8.useCallback(
1801
1829
  ({ detail }) => {
1802
1830
  if (detail.key === storageKey) {
1803
1831
  setMessages(
1804
- messages16.get(detail.value) ?? messages16.get("en-US")
1832
+ messages19.get(detail.value) ?? messages19.get("en-US")
1805
1833
  );
1806
1834
  }
1807
1835
  },
@@ -1845,7 +1873,7 @@ var NebulaI18nProvider = ({
1845
1873
  NebulaI18nContext.Provider,
1846
1874
  {
1847
1875
  value: {
1848
- messages: messages17,
1876
+ messages: messages20,
1849
1877
  locale: getNebulaLanguage()
1850
1878
  },
1851
1879
  children
@@ -1867,7 +1895,7 @@ var Pagination = ({
1867
1895
  onChangePage,
1868
1896
  ...props
1869
1897
  }) => {
1870
- const { messages: messages17 } = useNebulaI18n();
1898
+ const { messages: messages20 } = useNebulaI18n();
1871
1899
  const totalPages = React8.useMemo(() => {
1872
1900
  return Math.ceil(total / (pageSize || 1));
1873
1901
  }, [total, pageSize]);
@@ -1900,13 +1928,13 @@ var Pagination = ({
1900
1928
  }, [totalPages, pageSize, total]);
1901
1929
  const totalResultsLabel = React8.useMemo(() => {
1902
1930
  if (page === totalPages) {
1903
- return messages17.pagination.totalResultsLabel(lastPageSize, total);
1931
+ return messages20.pagination.totalResultsLabel(lastPageSize, total);
1904
1932
  }
1905
- return messages17.pagination.totalResultsLabel(pageSize, total);
1906
- }, [messages17.pagination, pageSize, total, page, totalPages, lastPageSize]);
1933
+ return messages20.pagination.totalResultsLabel(pageSize, total);
1934
+ }, [messages20.pagination, pageSize, total, page, totalPages, lastPageSize]);
1907
1935
  const currentPageLabel = React8.useMemo(
1908
- () => messages17.pagination.currentPageLabel(normalizedPage, totalPages),
1909
- [messages17.pagination, normalizedPage, totalPages]
1936
+ () => messages20.pagination.currentPageLabel(normalizedPage, totalPages),
1937
+ [messages20.pagination, normalizedPage, totalPages]
1910
1938
  );
1911
1939
  return /* @__PURE__ */ jsxRuntime.jsxs(
1912
1940
  "nav",
@@ -2179,7 +2207,7 @@ var createStyledSelect = (BaseSelect, displayName) => {
2179
2207
  isError = false,
2180
2208
  ...props
2181
2209
  }) => {
2182
- const { messages: messages17 } = useNebulaI18n();
2210
+ const { messages: messages20 } = useNebulaI18n();
2183
2211
  const customClassNames = React8.useMemo(() => {
2184
2212
  return {
2185
2213
  control: (props2) => controlStyles(props2, isError),
@@ -2226,7 +2254,7 @@ var createStyledSelect = (BaseSelect, displayName) => {
2226
2254
  isDisabled: disabled,
2227
2255
  components: customComponents,
2228
2256
  classNames: customClassNames,
2229
- noOptionsMessage: () => /* @__PURE__ */ jsxRuntime.jsx("p", { children: messages17.inputSelect.noOptions }),
2257
+ noOptionsMessage: () => /* @__PURE__ */ jsxRuntime.jsx("p", { children: messages20.inputSelect.noOptions }),
2230
2258
  ...props
2231
2259
  }
2232
2260
  );
@@ -2256,12 +2284,13 @@ var InputText = React8__namespace.forwardRef(
2256
2284
  "div",
2257
2285
  {
2258
2286
  className: cn(
2259
- "w-full flex outline-none",
2287
+ "flex outline-none",
2260
2288
  "rounded-input",
2261
2289
  "border border-inputText-border-default focus-within:ring-[3px] focus-within:ring-inputText-border-focus focus-within:border-inputText-border-focus",
2262
2290
  "focus-within:text-inputText-text-focus placeholder:text-inputText-text-default disabled:text-inputText-text-disabled",
2263
2291
  isError && "border-inputText-border-danger focus-within:border-inputText-border-danger focus-within:ring-button-danger-border-focus",
2264
- disabled && "pointer-events-none"
2292
+ disabled && "pointer-events-none",
2293
+ className
2265
2294
  ),
2266
2295
  children: [
2267
2296
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nebula-ds relative w-full", children: [
@@ -2270,7 +2299,7 @@ var InputText = React8__namespace.forwardRef(
2270
2299
  {
2271
2300
  ref,
2272
2301
  className: cn(
2273
- "w-full h-10 outline-none text-sm leading-none font-medium",
2302
+ "h-10 outline-none text-sm leading-none font-medium",
2274
2303
  "bg-inputText-background-default disabled:bg-inputText-background-disabled",
2275
2304
  "text-inputText-text-filled",
2276
2305
  "disabled:cursor-not-allowed",
@@ -2282,7 +2311,7 @@ var InputText = React8__namespace.forwardRef(
2282
2311
  "pr-10": initialInputType === "password",
2283
2312
  "pr-10 pl-4": !!icon && iconPlacement === "end" && initialInputType !== "password"
2284
2313
  },
2285
- className
2314
+ "!w-full"
2286
2315
  ),
2287
2316
  ...{ ...props, icon: void 0, iconPlacement: void 0 },
2288
2317
  disabled,
@@ -2372,16 +2401,17 @@ var DialogOverlay = React8__namespace.forwardRef(({ className, ...props }, ref)
2372
2401
  }
2373
2402
  ));
2374
2403
  DialogOverlay.displayName = DialogPrimitive__namespace.Overlay.displayName;
2375
- var DialogContent = React8__namespace.forwardRef(({ className, children, portal = false, ...props }, ref) => {
2376
- const Comp = portal ? DialogPortal : React8__namespace.Fragment;
2377
- return /* @__PURE__ */ jsxRuntime.jsxs(Comp, { children: [
2378
- /* @__PURE__ */ jsxRuntime.jsx(DialogOverlay, {}),
2379
- /* @__PURE__ */ jsxRuntime.jsxs(
2380
- DialogPrimitive__namespace.Content,
2381
- {
2382
- ref,
2383
- className: cn(
2384
- `rounded-2xl
2404
+ var DialogContent = React8__namespace.forwardRef(
2405
+ ({ className, children, portal = false, showClose = true, ...props }, ref) => {
2406
+ const Comp = portal ? DialogPortal : React8__namespace.Fragment;
2407
+ return /* @__PURE__ */ jsxRuntime.jsxs(Comp, { children: [
2408
+ /* @__PURE__ */ jsxRuntime.jsx(DialogOverlay, {}),
2409
+ /* @__PURE__ */ jsxRuntime.jsxs(
2410
+ DialogPrimitive__namespace.Content,
2411
+ {
2412
+ ref,
2413
+ className: cn(
2414
+ `rounded-2xl
2385
2415
  fixed
2386
2416
  left-[50%]
2387
2417
  top-[50%]
@@ -2406,29 +2436,30 @@ var DialogContent = React8__namespace.forwardRef(({ className, children, portal
2406
2436
  data-[state=closed]:slide-out-to-top-[48%]
2407
2437
  data-[state=open]:slide-in-from-left-1/2
2408
2438
  data-[state=open]:slide-in-from-top-[48%]`,
2409
- className
2410
- ),
2411
- ...props,
2412
- children: [
2413
- children,
2414
- /* @__PURE__ */ jsxRuntime.jsx(
2415
- DialogPrimitive__namespace.Close,
2416
- {
2417
- asChild: true,
2418
- className: `absolute
2419
- right-4
2420
- top-4`,
2421
- children: /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "ghost", size: "sm", icon: true, children: [
2422
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "nebula-ds !h-4 !w-4 !text-dialog-icon" }),
2423
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "nebula-ds sr-only", children: "Close" })
2424
- ] })
2425
- }
2426
- )
2427
- ]
2428
- }
2429
- )
2430
- ] });
2431
- });
2439
+ className
2440
+ ),
2441
+ ...props,
2442
+ children: [
2443
+ children,
2444
+ showClose && /* @__PURE__ */ jsxRuntime.jsx(
2445
+ DialogPrimitive__namespace.Close,
2446
+ {
2447
+ asChild: true,
2448
+ className: `absolute
2449
+ right-4
2450
+ top-4`,
2451
+ children: /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "ghost", size: "sm", icon: true, children: [
2452
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "nebula-ds !h-4 !w-4 !text-dialog-icon" }),
2453
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "nebula-ds sr-only", children: "Close" })
2454
+ ] })
2455
+ }
2456
+ )
2457
+ ]
2458
+ }
2459
+ )
2460
+ ] });
2461
+ }
2462
+ );
2432
2463
  DialogContent.displayName = DialogPrimitive__namespace.Content.displayName;
2433
2464
  var DialogHeader = ({
2434
2465
  className,
@@ -3244,13 +3275,13 @@ function custom(message, options) {
3244
3275
  }
3245
3276
  );
3246
3277
  }
3247
- async function promise(promise2, messages17, options) {
3278
+ async function promise(promise2, messages20, options) {
3248
3279
  const loadingToast = sonner.toast.custom(
3249
3280
  (t) => /* @__PURE__ */ jsxRuntime.jsx(
3250
3281
  ToastComponent,
3251
3282
  {
3252
3283
  type: "info",
3253
- message: messages17.loading,
3284
+ message: messages20.loading,
3254
3285
  options,
3255
3286
  t
3256
3287
  }
@@ -3264,7 +3295,7 @@ async function promise(promise2, messages17, options) {
3264
3295
  ToastComponent,
3265
3296
  {
3266
3297
  type: "success",
3267
- message: messages17.success,
3298
+ message: messages20.success,
3268
3299
  options,
3269
3300
  t
3270
3301
  }
@@ -3279,7 +3310,7 @@ async function promise(promise2, messages17, options) {
3279
3310
  ToastComponent,
3280
3311
  {
3281
3312
  type: "error",
3282
- message: messages17.error,
3313
+ message: messages20.error,
3283
3314
  options,
3284
3315
  t
3285
3316
  }
@@ -3449,8 +3480,8 @@ var CountrySelect = ({
3449
3480
  const handleSelect = (event) => {
3450
3481
  onChange(event.target.value);
3451
3482
  };
3452
- const { messages: messages17 } = useNebulaI18n();
3453
- const { countries } = messages17.inputPhone;
3483
+ const { messages: messages20 } = useNebulaI18n();
3484
+ const { countries } = messages20.inputPhone;
3454
3485
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nebula-ds rounded-s-[20px] relative inline-flex items-center self-stretch py-2 ps-4 pe-2 transition-[color,box-shadow] outline-none has-disabled:pointer-events-none has-disabled:opacity-50", children: [
3455
3486
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nebula-ds inline-flex items-center gap-1", "aria-hidden": "true", children: [
3456
3487
  /* @__PURE__ */ jsxRuntime.jsx(FlagComponent, { country: value, countryName: value, "aria-hidden": "true" }),
@@ -3489,10 +3520,11 @@ var PhoneInput = React8__namespace.default.forwardRef(({ className, ...props },
3489
3520
  "data-slot": "phone-input",
3490
3521
  ref,
3491
3522
  className: cn(
3492
- "-ms-px rounded-s-none shadow-none focus-visible:z-10 ring-0 focus:ring-0 border-0 w-full h-auto",
3523
+ "-ms-px rounded-s-none shadow-none focus-visible:z-10 ring-0 focus-within:ring-0 border-0 w-full h-auto",
3493
3524
  className
3494
3525
  ),
3495
- ...props
3526
+ ...props,
3527
+ type: "text"
3496
3528
  }
3497
3529
  )
3498
3530
  ] });
@@ -4270,7 +4302,7 @@ var InputDateTimePickerSingle = ({
4270
4302
  formattedDateByLanguage ? new Date(formattedDateByLanguage) : void 0
4271
4303
  );
4272
4304
  const [popoverIsOpen, setPopoverIsOpen] = React8.useState(false);
4273
- const { locale, messages: messages17 } = useNebulaI18n();
4305
+ const { locale, messages: messages20 } = useNebulaI18n();
4274
4306
  const [month, setMonth] = React8.useState(/* @__PURE__ */ new Date());
4275
4307
  const inputTimeRef = React8.useRef(null);
4276
4308
  const handleClearValue = () => {
@@ -4442,7 +4474,7 @@ var InputDateTimePickerSingle = ({
4442
4474
  disabled: disabledDates,
4443
4475
  footer: /* @__PURE__ */ jsxRuntime.jsxs(Space, { className: "nebula-ds items-center", children: [
4444
4476
  /* @__PURE__ */ jsxRuntime.jsxs(Label, { children: [
4445
- messages17.timePicker.label,
4477
+ messages20.timePicker.label,
4446
4478
  ":"
4447
4479
  ] }),
4448
4480
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -4978,102 +5010,1587 @@ var TextArea = React8__namespace.forwardRef(
4978
5010
  }
4979
5011
  );
4980
5012
  TextArea.displayName = "TextArea";
4981
- var ProfileImage = ({
4982
- maxSizeMB = 2,
4983
- subtitle,
4984
- onError,
4985
- maxFiles = Infinity,
4986
- onRemove,
4987
- ...rest
5013
+ function clamp(value, min, max) {
5014
+ return Math.min(Math.max(value, min), max);
5015
+ }
5016
+ var CropperContext = React8.createContext(null);
5017
+ var useCropperContext = () => {
5018
+ const context = React8.useContext(CropperContext);
5019
+ if (!context) {
5020
+ throw new Error("useCropperContext must be used within a Cropper.Root");
5021
+ }
5022
+ return context;
5023
+ };
5024
+ var CropperRoot = ({
5025
+ image,
5026
+ cropPadding = 25,
5027
+ aspectRatio = 1,
5028
+ minZoom = 1,
5029
+ maxZoom = 3,
5030
+ zoomSensitivity = 5e-3,
5031
+ keyboardStep = 10,
5032
+ className,
5033
+ style,
5034
+ zoom: zoomProp,
5035
+ onCropChange,
5036
+ onZoomChange,
5037
+ children,
5038
+ ...restProps
4988
5039
  }) => {
4989
- const maxSize = maxSizeMB * 1024 * 1024;
4990
- const [
4991
- { files, isDragging, errors },
4992
- {
4993
- handleDragEnter,
4994
- handleDragLeave,
4995
- handleDragOver,
4996
- handleDrop,
4997
- openFileDialog,
4998
- removeFile,
4999
- getInputProps
5040
+ const descriptionId = React8.useId();
5041
+ const [imgWidth, setImgWidth] = React8.useState(null);
5042
+ const [imgHeight, setImgHeight] = React8.useState(null);
5043
+ const containerRef = React8.useRef(null);
5044
+ const [cropAreaWidth, setCropAreaWidth] = React8.useState(0);
5045
+ const [cropAreaHeight, setCropAreaHeight] = React8.useState(0);
5046
+ const [imageWrapperWidth, setImageWrapperWidth] = React8.useState(0);
5047
+ const [imageWrapperHeight, setImageWrapperHeight] = React8.useState(0);
5048
+ const [offsetX, setOffsetX] = React8.useState(0);
5049
+ const [offsetY, setOffsetY] = React8.useState(0);
5050
+ const [internalZoom, setInternalZoom] = React8.useState(minZoom);
5051
+ const [isDragging, setIsDragging] = React8.useState(false);
5052
+ const dragStartPointRef = React8.useRef({ x: 0, y: 0 });
5053
+ const dragStartOffsetRef = React8.useRef({ x: 0, y: 0 });
5054
+ const latestRestrictedOffsetRef = React8.useRef({
5055
+ x: offsetX,
5056
+ y: offsetY
5057
+ });
5058
+ const latestZoomRef = React8.useRef(internalZoom);
5059
+ const isInitialSetupDoneRef = React8.useRef(false);
5060
+ const initialPinchDistanceRef = React8.useRef(0);
5061
+ const initialPinchZoomRef = React8.useRef(1);
5062
+ const isPinchingRef = React8.useRef(false);
5063
+ const hasWarnedRef = React8.useRef(false);
5064
+ const isZoomControlled = zoomProp !== void 0;
5065
+ const effectiveZoom = isZoomControlled ? zoomProp : internalZoom;
5066
+ const updateZoom = React8.useCallback(
5067
+ (newZoomValue) => {
5068
+ const clampedZoom = clamp(newZoomValue, minZoom, maxZoom);
5069
+ if (onZoomChange) {
5070
+ onZoomChange(clampedZoom);
5071
+ } else if (!isZoomControlled) {
5072
+ setInternalZoom(clampedZoom);
5073
+ }
5074
+ return clampedZoom;
5075
+ },
5076
+ [minZoom, maxZoom, onZoomChange, isZoomControlled]
5077
+ );
5078
+ React8.useEffect(() => {
5079
+ latestZoomRef.current = effectiveZoom;
5080
+ }, [effectiveZoom]);
5081
+ React8.useEffect(() => {
5082
+ setOffsetX(0);
5083
+ setOffsetY(0);
5084
+ if (!isZoomControlled) {
5085
+ setInternalZoom(minZoom);
5000
5086
  }
5001
- ] = useFileUpload({
5002
- multiple: false,
5003
- maxSize: maxSize > 0 ? maxSize : void 0,
5004
- accept: "image/*",
5005
- ...rest,
5006
- maxFiles
5087
+ isInitialSetupDoneRef.current = false;
5088
+ if (!image) {
5089
+ setImgWidth(null);
5090
+ setImgHeight(null);
5091
+ return;
5092
+ }
5093
+ let isMounted = true;
5094
+ const img = new Image();
5095
+ img.onload = () => {
5096
+ if (isMounted) {
5097
+ setImgWidth(img.naturalWidth);
5098
+ setImgHeight(img.naturalHeight);
5099
+ }
5100
+ };
5101
+ img.onerror = () => {
5102
+ if (isMounted) {
5103
+ setImgWidth(null);
5104
+ setImgHeight(null);
5105
+ }
5106
+ };
5107
+ img.src = image;
5108
+ return () => {
5109
+ isMounted = false;
5110
+ };
5111
+ }, [image, minZoom, isZoomControlled]);
5112
+ const updateCropAreaDimensions = React8.useCallback(
5113
+ (containerWidth, containerHeight) => {
5114
+ if (containerWidth <= 0 || containerHeight <= 0) {
5115
+ setCropAreaWidth(0);
5116
+ setCropAreaHeight(0);
5117
+ return;
5118
+ }
5119
+ const maxPossibleWidth = Math.max(0, containerWidth - cropPadding * 2);
5120
+ const maxPossibleHeight = Math.max(0, containerHeight - cropPadding * 2);
5121
+ let targetCropW = 0;
5122
+ let targetCropH = 0;
5123
+ if (maxPossibleWidth / aspectRatio >= maxPossibleHeight) {
5124
+ targetCropH = maxPossibleHeight;
5125
+ targetCropW = targetCropH * aspectRatio;
5126
+ } else {
5127
+ targetCropW = maxPossibleWidth;
5128
+ targetCropH = targetCropW / aspectRatio;
5129
+ }
5130
+ setCropAreaWidth(targetCropW);
5131
+ setCropAreaHeight(targetCropH);
5132
+ },
5133
+ [aspectRatio, cropPadding]
5134
+ );
5135
+ React8.useEffect(() => {
5136
+ const element = containerRef.current;
5137
+ if (!element) return;
5138
+ const observer = new ResizeObserver((entries) => {
5139
+ for (const entry of entries) {
5140
+ const { width, height } = entry.contentRect;
5141
+ if (width > 0 && height > 0) updateCropAreaDimensions(width, height);
5142
+ }
5143
+ });
5144
+ observer.observe(element);
5145
+ const initialWidth = element.clientWidth;
5146
+ const initialHeight = element.clientHeight;
5147
+ if (initialWidth > 0 && initialHeight > 0)
5148
+ updateCropAreaDimensions(initialWidth, initialHeight);
5149
+ return () => observer.disconnect();
5150
+ }, [updateCropAreaDimensions]);
5151
+ React8.useEffect(() => {
5152
+ if (cropAreaWidth <= 0 || cropAreaHeight <= 0 || !imgWidth || !imgHeight) {
5153
+ setImageWrapperWidth(0);
5154
+ setImageWrapperHeight(0);
5155
+ return;
5156
+ }
5157
+ const naturalAspect = imgWidth / imgHeight;
5158
+ const cropAspect = cropAreaWidth / cropAreaHeight;
5159
+ let targetWrapperWidth = 0;
5160
+ let targetWrapperHeight = 0;
5161
+ if (naturalAspect >= cropAspect) {
5162
+ targetWrapperHeight = cropAreaHeight;
5163
+ targetWrapperWidth = targetWrapperHeight * naturalAspect;
5164
+ } else {
5165
+ targetWrapperWidth = cropAreaWidth;
5166
+ targetWrapperHeight = targetWrapperWidth / naturalAspect;
5167
+ }
5168
+ setImageWrapperWidth(targetWrapperWidth);
5169
+ setImageWrapperHeight(targetWrapperHeight);
5170
+ }, [cropAreaWidth, cropAreaHeight, imgWidth, imgHeight]);
5171
+ const restrictOffset = React8.useCallback(
5172
+ (dragOffsetX, dragOffsetY, currentZoom) => {
5173
+ if (imageWrapperWidth <= 0 || imageWrapperHeight <= 0 || cropAreaWidth <= 0 || cropAreaHeight <= 0)
5174
+ return { x: 0, y: 0 };
5175
+ const effectiveWrapperWidth = imageWrapperWidth * currentZoom;
5176
+ const effectiveWrapperHeight = imageWrapperHeight * currentZoom;
5177
+ const maxDragX = Math.max(0, (effectiveWrapperWidth - cropAreaWidth) / 2);
5178
+ const maxDragY = Math.max(
5179
+ 0,
5180
+ (effectiveWrapperHeight - cropAreaHeight) / 2
5181
+ );
5182
+ return {
5183
+ x: clamp(dragOffsetX, -maxDragX, maxDragX),
5184
+ y: clamp(dragOffsetY, -maxDragY, maxDragY)
5185
+ };
5186
+ },
5187
+ [imageWrapperWidth, imageWrapperHeight, cropAreaWidth, cropAreaHeight]
5188
+ );
5189
+ const calculateCropData = React8.useCallback(
5190
+ (finalOffsetX, finalOffsetY, finalZoom) => {
5191
+ const currentOffsetX = finalOffsetX !== void 0 ? finalOffsetX : latestRestrictedOffsetRef.current.x;
5192
+ const currentOffsetY = finalOffsetY !== void 0 ? finalOffsetY : latestRestrictedOffsetRef.current.y;
5193
+ const currentZoom = finalZoom !== void 0 ? finalZoom : effectiveZoom;
5194
+ if (!imgWidth || !imgHeight || imageWrapperWidth <= 0 || imageWrapperHeight <= 0 || cropAreaWidth <= 0 || cropAreaHeight <= 0)
5195
+ return null;
5196
+ const scaledWrapperWidth = imageWrapperWidth * currentZoom;
5197
+ const scaledWrapperHeight = imageWrapperHeight * currentZoom;
5198
+ const topLeftOffsetX = currentOffsetX + (cropAreaWidth - scaledWrapperWidth) / 2;
5199
+ const topLeftOffsetY = currentOffsetY + (cropAreaHeight - scaledWrapperHeight) / 2;
5200
+ const baseScale = imgWidth / imageWrapperWidth;
5201
+ if (isNaN(baseScale) || baseScale === 0) return null;
5202
+ const sx = -topLeftOffsetX * baseScale / currentZoom;
5203
+ const sy = -topLeftOffsetY * baseScale / currentZoom;
5204
+ const sWidth = cropAreaWidth * baseScale / currentZoom;
5205
+ const sHeight = cropAreaHeight * baseScale / currentZoom;
5206
+ const finalX = clamp(Math.round(sx), 0, imgWidth);
5207
+ const finalY = clamp(Math.round(sy), 0, imgHeight);
5208
+ const finalWidth = clamp(Math.round(sWidth), 0, imgWidth - finalX);
5209
+ const finalHeight = clamp(Math.round(sHeight), 0, imgHeight - finalY);
5210
+ if (finalWidth <= 0 || finalHeight <= 0) return null;
5211
+ return { x: finalX, y: finalY, width: finalWidth, height: finalHeight };
5212
+ },
5213
+ [
5214
+ imgWidth,
5215
+ imgHeight,
5216
+ imageWrapperWidth,
5217
+ imageWrapperHeight,
5218
+ cropAreaWidth,
5219
+ cropAreaHeight,
5220
+ effectiveZoom
5221
+ ]
5222
+ );
5223
+ React8.useEffect(() => {
5224
+ if (imageWrapperWidth > 0 && imageWrapperHeight > 0 && cropAreaWidth > 0 && cropAreaHeight > 0) {
5225
+ const currentZoomForSetup = effectiveZoom;
5226
+ if (!isInitialSetupDoneRef.current) {
5227
+ const initialX = 0;
5228
+ const initialY = 0;
5229
+ const restrictedInitial = restrictOffset(
5230
+ initialX,
5231
+ initialY,
5232
+ currentZoomForSetup
5233
+ );
5234
+ setOffsetX(restrictedInitial.x);
5235
+ setOffsetY(restrictedInitial.y);
5236
+ if (!isZoomControlled) setInternalZoom(currentZoomForSetup);
5237
+ dragStartOffsetRef.current = restrictedInitial;
5238
+ latestRestrictedOffsetRef.current = restrictedInitial;
5239
+ latestZoomRef.current = currentZoomForSetup;
5240
+ if (onCropChange)
5241
+ onCropChange(
5242
+ calculateCropData(
5243
+ restrictedInitial.x,
5244
+ restrictedInitial.y,
5245
+ currentZoomForSetup
5246
+ )
5247
+ );
5248
+ isInitialSetupDoneRef.current = true;
5249
+ } else {
5250
+ const restrictedCurrent = restrictOffset(
5251
+ latestRestrictedOffsetRef.current.x,
5252
+ latestRestrictedOffsetRef.current.y,
5253
+ currentZoomForSetup
5254
+ );
5255
+ if (restrictedCurrent.x !== latestRestrictedOffsetRef.current.x || restrictedCurrent.y !== latestRestrictedOffsetRef.current.y) {
5256
+ setOffsetX(restrictedCurrent.x);
5257
+ setOffsetY(restrictedCurrent.y);
5258
+ latestRestrictedOffsetRef.current = restrictedCurrent;
5259
+ dragStartOffsetRef.current = restrictedCurrent;
5260
+ }
5261
+ if (onCropChange)
5262
+ onCropChange(
5263
+ calculateCropData(
5264
+ restrictedCurrent.x,
5265
+ restrictedCurrent.y,
5266
+ currentZoomForSetup
5267
+ )
5268
+ );
5269
+ }
5270
+ } else {
5271
+ isInitialSetupDoneRef.current = false;
5272
+ setOffsetX(0);
5273
+ setOffsetY(0);
5274
+ if (!isZoomControlled) setInternalZoom(minZoom);
5275
+ dragStartOffsetRef.current = { x: 0, y: 0 };
5276
+ latestRestrictedOffsetRef.current = { x: 0, y: 0 };
5277
+ latestZoomRef.current = effectiveZoom;
5278
+ if (onCropChange) onCropChange(null);
5279
+ }
5280
+ }, [
5281
+ imageWrapperWidth,
5282
+ imgHeight,
5283
+ cropAreaWidth,
5284
+ cropAreaHeight,
5285
+ restrictOffset,
5286
+ onCropChange,
5287
+ calculateCropData,
5288
+ minZoom,
5289
+ effectiveZoom,
5290
+ isZoomControlled,
5291
+ updateZoom,
5292
+ imageWrapperHeight
5293
+ ]);
5294
+ React8.useEffect(() => {
5295
+ const checkTimeout = setTimeout(() => {
5296
+ if (containerRef.current && !hasWarnedRef.current) {
5297
+ const hasDescription = document.getElementById(descriptionId);
5298
+ if (!hasDescription) {
5299
+ hasWarnedRef.current = true;
5300
+ }
5301
+ }
5302
+ }, 100);
5303
+ return () => clearTimeout(checkTimeout);
5304
+ }, [descriptionId]);
5305
+ const handleInteractionEnd = React8.useCallback(() => {
5306
+ if (onCropChange) {
5307
+ const finalData = calculateCropData(
5308
+ latestRestrictedOffsetRef.current.x,
5309
+ latestRestrictedOffsetRef.current.y,
5310
+ effectiveZoom
5311
+ );
5312
+ onCropChange(finalData);
5313
+ }
5314
+ }, [onCropChange, calculateCropData, effectiveZoom]);
5315
+ const handleMouseDown = React8.useCallback(
5316
+ (e) => {
5317
+ if (e.button !== 0 || !containerRef.current) return;
5318
+ e.preventDefault();
5319
+ setIsDragging(true);
5320
+ isPinchingRef.current = false;
5321
+ dragStartPointRef.current = { x: e.clientX, y: e.clientY };
5322
+ dragStartOffsetRef.current = {
5323
+ x: latestRestrictedOffsetRef.current.x,
5324
+ y: latestRestrictedOffsetRef.current.y
5325
+ };
5326
+ const handleMouseMove = (ev) => {
5327
+ const deltaX = ev.clientX - dragStartPointRef.current.x;
5328
+ const deltaY = ev.clientY - dragStartPointRef.current.y;
5329
+ const targetOffsetX = dragStartOffsetRef.current.x + deltaX;
5330
+ const targetOffsetY = dragStartOffsetRef.current.y + deltaY;
5331
+ const restricted = restrictOffset(
5332
+ targetOffsetX,
5333
+ targetOffsetY,
5334
+ effectiveZoom
5335
+ );
5336
+ latestRestrictedOffsetRef.current = restricted;
5337
+ setOffsetX(restricted.x);
5338
+ setOffsetY(restricted.y);
5339
+ };
5340
+ const handleMouseUp = () => {
5341
+ setIsDragging(false);
5342
+ window.removeEventListener("mousemove", handleMouseMove);
5343
+ window.removeEventListener("mouseup", handleMouseUp);
5344
+ handleInteractionEnd();
5345
+ };
5346
+ window.addEventListener("mousemove", handleMouseMove);
5347
+ window.addEventListener("mouseup", handleMouseUp);
5348
+ },
5349
+ [restrictOffset, effectiveZoom, handleInteractionEnd]
5350
+ );
5351
+ const handleWheel = React8.useCallback(
5352
+ (e) => {
5353
+ e.preventDefault();
5354
+ e.stopPropagation();
5355
+ if (!containerRef.current || imageWrapperWidth <= 0 || imageWrapperHeight <= 0)
5356
+ return;
5357
+ const currentZoom = latestZoomRef.current;
5358
+ const currentOffsetX = latestRestrictedOffsetRef.current.x;
5359
+ const currentOffsetY = latestRestrictedOffsetRef.current.y;
5360
+ const delta = e.deltaY * -zoomSensitivity;
5361
+ const targetZoom = currentZoom + delta;
5362
+ if (clamp(targetZoom, minZoom, maxZoom) === currentZoom) return;
5363
+ const rect = containerRef.current.getBoundingClientRect();
5364
+ const pointerX = e.clientX - rect.left - rect.width / 2;
5365
+ const pointerY = e.clientY - rect.top - rect.height / 2;
5366
+ const imagePointX = (pointerX - currentOffsetX) / currentZoom;
5367
+ const imagePointY = (pointerY - currentOffsetY) / currentZoom;
5368
+ const finalNewZoom = updateZoom(targetZoom);
5369
+ const newOffsetX = pointerX - imagePointX * finalNewZoom;
5370
+ const newOffsetY = pointerY - imagePointY * finalNewZoom;
5371
+ const restrictedNewOffset = restrictOffset(
5372
+ newOffsetX,
5373
+ newOffsetY,
5374
+ finalNewZoom
5375
+ );
5376
+ setOffsetX(restrictedNewOffset.x);
5377
+ setOffsetY(restrictedNewOffset.y);
5378
+ latestRestrictedOffsetRef.current = restrictedNewOffset;
5379
+ if (onCropChange) {
5380
+ const finalData = calculateCropData(
5381
+ restrictedNewOffset.x,
5382
+ restrictedNewOffset.y,
5383
+ finalNewZoom
5384
+ );
5385
+ onCropChange(finalData);
5386
+ }
5387
+ },
5388
+ [
5389
+ restrictOffset,
5390
+ calculateCropData,
5391
+ imageWrapperWidth,
5392
+ imageWrapperHeight,
5393
+ onCropChange,
5394
+ minZoom,
5395
+ maxZoom,
5396
+ zoomSensitivity,
5397
+ updateZoom
5398
+ ]
5399
+ );
5400
+ const getPinchDistance = (touches) => Math.sqrt(
5401
+ Math.pow(touches[1].clientX - touches[0].clientX, 2) + Math.pow(touches[1].clientY - touches[0].clientY, 2)
5402
+ );
5403
+ const getPinchCenter = (touches) => ({
5404
+ x: (touches[0].clientX + touches[1].clientX) / 2,
5405
+ y: (touches[0].clientY + touches[1].clientY) / 2
5007
5406
  });
5407
+ const handleTouchStart = React8.useCallback(
5408
+ (e) => {
5409
+ if (!containerRef.current || imageWrapperWidth <= 0 || imageWrapperHeight <= 0)
5410
+ return;
5411
+ e.preventDefault();
5412
+ const touches = e.touches;
5413
+ if (touches.length === 1) {
5414
+ setIsDragging(true);
5415
+ isPinchingRef.current = false;
5416
+ dragStartPointRef.current = {
5417
+ x: touches[0].clientX,
5418
+ y: touches[0].clientY
5419
+ };
5420
+ dragStartOffsetRef.current = {
5421
+ x: latestRestrictedOffsetRef.current.x,
5422
+ y: latestRestrictedOffsetRef.current.y
5423
+ };
5424
+ } else if (touches.length === 2) {
5425
+ setIsDragging(false);
5426
+ isPinchingRef.current = true;
5427
+ initialPinchDistanceRef.current = getPinchDistance(touches);
5428
+ initialPinchZoomRef.current = latestZoomRef.current;
5429
+ dragStartOffsetRef.current = {
5430
+ x: latestRestrictedOffsetRef.current.x,
5431
+ y: latestRestrictedOffsetRef.current.y
5432
+ };
5433
+ }
5434
+ },
5435
+ [imageWrapperWidth, imageWrapperHeight]
5436
+ );
5437
+ const handleTouchMove = React8.useCallback(
5438
+ (e) => {
5439
+ if (!containerRef.current || imageWrapperWidth <= 0 || imageWrapperHeight <= 0)
5440
+ return;
5441
+ e.preventDefault();
5442
+ const touches = e.touches;
5443
+ if (touches.length === 1 && isDragging && !isPinchingRef.current) {
5444
+ const deltaX = touches[0].clientX - dragStartPointRef.current.x;
5445
+ const deltaY = touches[0].clientY - dragStartPointRef.current.y;
5446
+ const targetOffsetX = dragStartOffsetRef.current.x + deltaX;
5447
+ const targetOffsetY = dragStartOffsetRef.current.y + deltaY;
5448
+ const restricted = restrictOffset(
5449
+ targetOffsetX,
5450
+ targetOffsetY,
5451
+ effectiveZoom
5452
+ );
5453
+ latestRestrictedOffsetRef.current = restricted;
5454
+ setOffsetX(restricted.x);
5455
+ setOffsetY(restricted.y);
5456
+ } else if (touches.length === 2 && isPinchingRef.current) {
5457
+ const currentPinchDistance = getPinchDistance(touches);
5458
+ const scale = currentPinchDistance / initialPinchDistanceRef.current;
5459
+ const currentZoom = initialPinchZoomRef.current;
5460
+ const targetZoom = currentZoom * scale;
5461
+ if (clamp(targetZoom, minZoom, maxZoom) === latestZoomRef.current)
5462
+ return;
5463
+ const pinchCenter = getPinchCenter(touches);
5464
+ const rect = containerRef.current.getBoundingClientRect();
5465
+ const pinchCenterX = pinchCenter.x - rect.left - rect.width / 2;
5466
+ const pinchCenterY = pinchCenter.y - rect.top - rect.height / 2;
5467
+ const currentOffsetX = dragStartOffsetRef.current.x;
5468
+ const currentOffsetY = dragStartOffsetRef.current.y;
5469
+ const imagePointX = (pinchCenterX - currentOffsetX) / currentZoom;
5470
+ const imagePointY = (pinchCenterY - currentOffsetY) / currentZoom;
5471
+ const finalNewZoom = updateZoom(targetZoom);
5472
+ const newOffsetX = pinchCenterX - imagePointX * finalNewZoom;
5473
+ const newOffsetY = pinchCenterY - imagePointY * finalNewZoom;
5474
+ const restrictedNewOffset = restrictOffset(
5475
+ newOffsetX,
5476
+ newOffsetY,
5477
+ finalNewZoom
5478
+ );
5479
+ setOffsetX(restrictedNewOffset.x);
5480
+ setOffsetY(restrictedNewOffset.y);
5481
+ latestRestrictedOffsetRef.current = restrictedNewOffset;
5482
+ if (onCropChange) {
5483
+ const finalData = calculateCropData(
5484
+ restrictedNewOffset.x,
5485
+ restrictedNewOffset.y,
5486
+ finalNewZoom
5487
+ );
5488
+ onCropChange(finalData);
5489
+ }
5490
+ }
5491
+ },
5492
+ [
5493
+ isDragging,
5494
+ restrictOffset,
5495
+ minZoom,
5496
+ maxZoom,
5497
+ imageWrapperWidth,
5498
+ imageWrapperHeight,
5499
+ effectiveZoom,
5500
+ updateZoom,
5501
+ onCropChange,
5502
+ calculateCropData
5503
+ ]
5504
+ );
5505
+ const handleTouchEnd = React8.useCallback(
5506
+ (e) => {
5507
+ e.preventDefault();
5508
+ const touches = e.touches;
5509
+ if (isPinchingRef.current && touches.length < 2) {
5510
+ isPinchingRef.current = false;
5511
+ if (touches.length === 1) {
5512
+ setIsDragging(true);
5513
+ dragStartPointRef.current = {
5514
+ x: touches[0].clientX,
5515
+ y: touches[0].clientY
5516
+ };
5517
+ dragStartOffsetRef.current = {
5518
+ x: latestRestrictedOffsetRef.current.x,
5519
+ y: latestRestrictedOffsetRef.current.y
5520
+ };
5521
+ } else {
5522
+ setIsDragging(false);
5523
+ handleInteractionEnd();
5524
+ }
5525
+ } else if (isDragging && touches.length === 0) {
5526
+ setIsDragging(false);
5527
+ handleInteractionEnd();
5528
+ }
5529
+ },
5530
+ [isDragging, handleInteractionEnd]
5531
+ );
5532
+ const handleKeyDown = React8.useCallback(
5533
+ (e) => {
5534
+ if (imageWrapperWidth <= 0) return;
5535
+ let targetOffsetX = latestRestrictedOffsetRef.current.x;
5536
+ let targetOffsetY = latestRestrictedOffsetRef.current.y;
5537
+ let moved = false;
5538
+ switch (e.key) {
5539
+ case "ArrowUp":
5540
+ targetOffsetY += keyboardStep;
5541
+ moved = true;
5542
+ break;
5543
+ case "ArrowDown":
5544
+ targetOffsetY -= keyboardStep;
5545
+ moved = true;
5546
+ break;
5547
+ case "ArrowLeft":
5548
+ targetOffsetX += keyboardStep;
5549
+ moved = true;
5550
+ break;
5551
+ case "ArrowRight":
5552
+ targetOffsetX -= keyboardStep;
5553
+ moved = true;
5554
+ break;
5555
+ default:
5556
+ return;
5557
+ }
5558
+ if (moved) {
5559
+ e.preventDefault();
5560
+ const restricted = restrictOffset(
5561
+ targetOffsetX,
5562
+ targetOffsetY,
5563
+ effectiveZoom
5564
+ );
5565
+ if (restricted.x !== latestRestrictedOffsetRef.current.x || restricted.y !== latestRestrictedOffsetRef.current.y) {
5566
+ latestRestrictedOffsetRef.current = restricted;
5567
+ setOffsetX(restricted.x);
5568
+ setOffsetY(restricted.y);
5569
+ }
5570
+ }
5571
+ },
5572
+ [keyboardStep, imageWrapperWidth, restrictOffset, effectiveZoom]
5573
+ );
5574
+ const handleKeyUp = React8.useCallback(
5575
+ (e) => {
5576
+ if (["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(e.key)) {
5577
+ handleInteractionEnd();
5578
+ }
5579
+ },
5580
+ [handleInteractionEnd]
5581
+ );
5008
5582
  React8.useEffect(() => {
5009
- onError?.(errors);
5010
- }, [errors, onError]);
5011
- const [file] = files;
5012
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nebula-ds flex flex-col gap-3", children: [
5013
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nebula-ds flex justify-center", children: /* @__PURE__ */ jsxRuntime.jsxs(
5014
- "div",
5583
+ const node = containerRef.current;
5584
+ if (!node) return;
5585
+ const options = { passive: false };
5586
+ node.addEventListener("wheel", handleWheel, options);
5587
+ node.addEventListener("touchstart", handleTouchStart, options);
5588
+ node.addEventListener("touchmove", handleTouchMove, options);
5589
+ node.addEventListener("touchend", handleTouchEnd, options);
5590
+ node.addEventListener("touchcancel", handleTouchEnd, options);
5591
+ return () => {
5592
+ node.removeEventListener("wheel", handleWheel, options);
5593
+ node.removeEventListener("touchstart", handleTouchStart, options);
5594
+ node.removeEventListener("touchmove", handleTouchMove, options);
5595
+ node.removeEventListener("touchend", handleTouchEnd, options);
5596
+ node.removeEventListener("touchcancel", handleTouchEnd, options);
5597
+ };
5598
+ }, [handleWheel, handleTouchStart, handleTouchMove, handleTouchEnd]);
5599
+ const getRootProps = React8.useCallback(
5600
+ () => ({
5601
+ className,
5602
+ style,
5603
+ onMouseDown: handleMouseDown,
5604
+ onKeyDown: handleKeyDown,
5605
+ onKeyUp: handleKeyUp,
5606
+ tabIndex: 0,
5607
+ role: "application",
5608
+ "aria-label": "Interactive image cropper",
5609
+ "aria-describedby": descriptionId,
5610
+ "aria-valuemin": minZoom,
5611
+ "aria-valuemax": maxZoom,
5612
+ "aria-valuenow": effectiveZoom,
5613
+ "aria-valuetext": `Zoom: ${Math.round(effectiveZoom * 100)}%`
5614
+ }),
5615
+ [
5616
+ className,
5617
+ style,
5618
+ handleMouseDown,
5619
+ handleKeyDown,
5620
+ handleKeyUp,
5621
+ descriptionId,
5622
+ minZoom,
5623
+ maxZoom,
5624
+ effectiveZoom
5625
+ ]
5626
+ );
5627
+ const getImageWrapperStyle = React8.useCallback(
5628
+ () => ({
5629
+ width: imageWrapperWidth,
5630
+ height: imageWrapperHeight,
5631
+ transform: `translate3d(${offsetX}px, ${offsetY}px, 0px) scale(${effectiveZoom})`,
5632
+ position: "absolute",
5633
+ left: `calc(50% - ${imageWrapperWidth / 2}px)`,
5634
+ top: `calc(50% - ${imageWrapperHeight / 2}px)`,
5635
+ willChange: "transform"
5636
+ }),
5637
+ [imageWrapperWidth, imageWrapperHeight, offsetX, offsetY, effectiveZoom]
5638
+ );
5639
+ const getImageProps = React8.useCallback(
5640
+ () => ({
5641
+ src: image,
5642
+ alt: "Image being cropped",
5643
+ draggable: false,
5644
+ "aria-hidden": true
5645
+ }),
5646
+ [image]
5647
+ );
5648
+ const getCropAreaStyle = React8.useCallback(
5649
+ () => ({
5650
+ width: cropAreaWidth,
5651
+ height: cropAreaHeight
5652
+ }),
5653
+ [cropAreaWidth, cropAreaHeight]
5654
+ );
5655
+ const getCropAreaProps = React8.useCallback(
5656
+ () => ({
5657
+ style: getCropAreaStyle(),
5658
+ "aria-hidden": true
5659
+ }),
5660
+ [getCropAreaStyle]
5661
+ );
5662
+ const contextValue = {
5663
+ containerRef,
5664
+ image,
5665
+ imgWidth,
5666
+ imgHeight,
5667
+ cropAreaWidth,
5668
+ cropAreaHeight,
5669
+ imageWrapperWidth,
5670
+ imageWrapperHeight,
5671
+ offsetX,
5672
+ offsetY,
5673
+ effectiveZoom,
5674
+ minZoom,
5675
+ maxZoom,
5676
+ getRootProps,
5677
+ getImageProps,
5678
+ getImageWrapperStyle,
5679
+ getCropAreaProps,
5680
+ getCropAreaStyle,
5681
+ descriptionId
5682
+ };
5683
+ return /* @__PURE__ */ jsxRuntime.jsx(CropperContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxRuntime.jsx("div", { ref: containerRef, ...getRootProps(), ...restProps, children }) });
5684
+ };
5685
+ var CropperImage = ({ className, ...restProps }) => {
5686
+ const { image, getImageProps, getImageWrapperStyle } = useCropperContext();
5687
+ if (!image) return null;
5688
+ const imageProps = getImageProps();
5689
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: getImageWrapperStyle(), children: /* @__PURE__ */ jsxRuntime.jsx("img", { ...imageProps, className, ...restProps }) });
5690
+ };
5691
+ var CropperCropArea = ({
5692
+ className,
5693
+ style,
5694
+ ...restProps
5695
+ }) => {
5696
+ const { cropAreaWidth, cropAreaHeight, getCropAreaProps, getCropAreaStyle } = useCropperContext();
5697
+ if (cropAreaWidth <= 0 || cropAreaHeight <= 0) return null;
5698
+ const areaProps = getCropAreaProps();
5699
+ const areaStyle = getCropAreaStyle();
5700
+ return /* @__PURE__ */ jsxRuntime.jsx(
5701
+ "div",
5702
+ {
5703
+ ...areaProps,
5704
+ style: { ...areaProps.style, ...style, ...areaStyle },
5705
+ className,
5706
+ ...restProps
5707
+ }
5708
+ );
5709
+ };
5710
+ var CropperDescription = ({
5711
+ children,
5712
+ className,
5713
+ ...restProps
5714
+ }) => {
5715
+ const { descriptionId } = useCropperContext();
5716
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { id: descriptionId, className, ...restProps, children: children ?? // Default description if none provided by user
5717
+ "Use mouse wheel or pinch gesture to zoom. Drag with mouse or touch, or use arrow keys to pan the image within the crop area." });
5718
+ };
5719
+ var Cropper = {
5720
+ Root: CropperRoot,
5721
+ Image: CropperImage,
5722
+ CropArea: CropperCropArea,
5723
+ Description: CropperDescription
5724
+ };
5725
+ function Slider({
5726
+ className,
5727
+ defaultValue,
5728
+ value,
5729
+ min = 0,
5730
+ max = 100,
5731
+ ...props
5732
+ }) {
5733
+ const [internalValues, setInternalValues] = React8__namespace.useState(
5734
+ Array.isArray(value) ? value : Array.isArray(defaultValue) ? defaultValue : [min, max]
5735
+ );
5736
+ React8__namespace.useEffect(() => {
5737
+ if (value !== void 0) {
5738
+ setInternalValues(Array.isArray(value) ? value : [value]);
5739
+ }
5740
+ }, [value]);
5741
+ const handleValueChange = (newValue) => {
5742
+ setInternalValues(newValue);
5743
+ props.onValueChange?.(newValue);
5744
+ };
5745
+ const renderThumb = () => {
5746
+ const thumb = /* @__PURE__ */ jsxRuntime.jsx(
5747
+ SliderPrimitive__namespace.Thumb,
5015
5748
  {
5016
- role: "button",
5017
- onClick: openFileDialog,
5018
- onDragEnter: handleDragEnter,
5019
- onDragLeave: handleDragLeave,
5020
- onDragOver: handleDragOver,
5021
- onDrop: handleDrop,
5022
- "data-dragging": isDragging || void 0,
5023
- className: cn(
5024
- "relative border border-transparent rounded-full size-fit",
5025
- "bg-fileUpload-background hover:bg-fileUpload-backgroundHover transition-colors",
5026
- !file && "border-dashed border-fileUpload-border"
5749
+ "data-testid": "slider-thumb",
5750
+ "data-slot": "slider-thumb",
5751
+ className: "nebula-ds border-2 border-slider-rangeColor bg-slider-thumbColor ring-ring/50 block size-4 shrink-0 rounded-full transition-[color,box-shadow] outline-none hover:ring-4 focus-visible:ring-4 disabled:pointer-events-none disabled:opacity-50"
5752
+ }
5753
+ );
5754
+ return thumb;
5755
+ };
5756
+ return /* @__PURE__ */ jsxRuntime.jsxs(
5757
+ SliderPrimitive__namespace.Root,
5758
+ {
5759
+ "data-slot": "slider",
5760
+ defaultValue,
5761
+ value,
5762
+ min,
5763
+ max,
5764
+ className: cn(
5765
+ "relative flex w-full touch-none items-center select-none data-[disabled]:opacity-50 data-[orientation=vertical]:h-full data-[orientation=vertical]:min-h-44 data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col",
5766
+ className
5767
+ ),
5768
+ onValueChange: handleValueChange,
5769
+ ...props,
5770
+ children: [
5771
+ /* @__PURE__ */ jsxRuntime.jsx(
5772
+ SliderPrimitive__namespace.Track,
5773
+ {
5774
+ "data-slot": "slider-track",
5775
+ className: cn(
5776
+ "bg-slider-trackColor relative grow overflow-hidden rounded-full data-[orientation=horizontal]:h-1.5 data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-1.5"
5777
+ ),
5778
+ children: /* @__PURE__ */ jsxRuntime.jsx(
5779
+ SliderPrimitive__namespace.Range,
5780
+ {
5781
+ "data-slot": "slider-range",
5782
+ className: cn(
5783
+ "bg-slider-rangeColor absolute data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full"
5784
+ )
5785
+ }
5786
+ )
5787
+ }
5027
5788
  ),
5028
- "data-testid": "select-image-profile",
5029
- children: [
5030
- /* @__PURE__ */ jsxRuntime.jsx(
5031
- "input",
5032
- {
5033
- ...getInputProps(),
5034
- className: "nebula-ds sr-only",
5035
- "aria-label": "Upload file"
5036
- }
5037
- ),
5038
- /* @__PURE__ */ jsxRuntime.jsxs(
5039
- "div",
5040
- {
5041
- className: "nebula-ds flex size-12 shrink-0 items-center justify-center rounded-full",
5042
- "aria-hidden": "true",
5043
- children: [
5044
- file && file.preview && /* @__PURE__ */ jsxRuntime.jsx(
5045
- "img",
5789
+ Array.from({ length: internalValues.length }, (_, index) => /* @__PURE__ */ jsxRuntime.jsx(React8__namespace.Fragment, { children: renderThumb() }, index))
5790
+ ]
5791
+ }
5792
+ );
5793
+ }
5794
+ function CropperRoot2({
5795
+ className,
5796
+ ...props
5797
+ }) {
5798
+ return /* @__PURE__ */ jsxRuntime.jsx(
5799
+ Cropper.Root,
5800
+ {
5801
+ "data-slot": "cropper",
5802
+ className: cn(
5803
+ "relative flex w-full cursor-move touch-none items-center justify-center overflow-hidden focus:outline-none",
5804
+ className
5805
+ ),
5806
+ ...props
5807
+ }
5808
+ );
5809
+ }
5810
+ function CropperDescription2({
5811
+ className,
5812
+ ...props
5813
+ }) {
5814
+ return /* @__PURE__ */ jsxRuntime.jsx(
5815
+ Cropper.Description,
5816
+ {
5817
+ "data-slot": "cropper-description",
5818
+ className: cn("sr-only", className),
5819
+ ...props
5820
+ }
5821
+ );
5822
+ }
5823
+ function CropperImage2({
5824
+ className,
5825
+ ...props
5826
+ }) {
5827
+ return /* @__PURE__ */ jsxRuntime.jsx(
5828
+ Cropper.Image,
5829
+ {
5830
+ "data-slot": "cropper-image",
5831
+ className: cn(
5832
+ "pointer-events-none h-full w-full object-cover",
5833
+ className
5834
+ ),
5835
+ ...props
5836
+ }
5837
+ );
5838
+ }
5839
+ function CropperCropArea2({
5840
+ className,
5841
+ rounded,
5842
+ ...props
5843
+ }) {
5844
+ return /* @__PURE__ */ jsxRuntime.jsx(
5845
+ Cropper.CropArea,
5846
+ {
5847
+ "data-slot": "cropper-crop-area",
5848
+ className: cn(
5849
+ "pointer-events-none absolute shadow-[0_0_0_9999px_rgba(0,0,0,0.5)] in-[[data-slot=cropper]:focus-visible]:ring-[2px] in-[[data-slot=cropper]:focus-visible]:ring-white",
5850
+ rounded && "rounded-full",
5851
+ className,
5852
+ "border-2 border-cropper-cropAreaBorderColor"
5853
+ ),
5854
+ ...props
5855
+ }
5856
+ );
5857
+ }
5858
+ var createImage = (url) => new Promise((resolve, reject) => {
5859
+ const image = new Image();
5860
+ image.addEventListener("load", () => resolve(image));
5861
+ image.addEventListener("error", (error2) => reject(error2));
5862
+ image.setAttribute("crossOrigin", "anonymous");
5863
+ image.src = url;
5864
+ });
5865
+ async function getCroppedImg(imageSrc, pixelCrop, outputWidth = pixelCrop.width, outputHeight = pixelCrop.height) {
5866
+ try {
5867
+ const image = await createImage(imageSrc);
5868
+ const canvas = document.createElement("canvas");
5869
+ const ctx = canvas.getContext("2d");
5870
+ if (!ctx) {
5871
+ return null;
5872
+ }
5873
+ canvas.width = outputWidth;
5874
+ canvas.height = outputHeight;
5875
+ ctx.drawImage(
5876
+ image,
5877
+ pixelCrop.x,
5878
+ pixelCrop.y,
5879
+ pixelCrop.width,
5880
+ pixelCrop.height,
5881
+ 0,
5882
+ 0,
5883
+ outputWidth,
5884
+ // Draw onto the output size
5885
+ outputHeight
5886
+ );
5887
+ return new Promise((resolve) => {
5888
+ canvas.toBlob((blob) => {
5889
+ resolve(blob);
5890
+ }, "image/jpeg");
5891
+ });
5892
+ } catch (error2) {
5893
+ return null;
5894
+ }
5895
+ }
5896
+ function Cropper2({
5897
+ onOpenChange,
5898
+ open,
5899
+ previewUrl,
5900
+ onApply,
5901
+ onCancelCrop,
5902
+ rounded = false,
5903
+ portal = false
5904
+ }) {
5905
+ const [finalImageUrl, setFinalImageUrl] = React8.useState(null);
5906
+ const [croppedAreaPixels, setCroppedAreaPixels] = React8.useState(null);
5907
+ const [zoom, setZoom] = React8.useState(1);
5908
+ const handleCropChange = React8.useCallback((pixels) => {
5909
+ setCroppedAreaPixels(pixels);
5910
+ }, []);
5911
+ const handleApply = async () => {
5912
+ if (!previewUrl || !croppedAreaPixels) {
5913
+ if (previewUrl) {
5914
+ setCroppedAreaPixels(null);
5915
+ }
5916
+ return;
5917
+ }
5918
+ try {
5919
+ const croppedBlob = await getCroppedImg(previewUrl, croppedAreaPixels);
5920
+ if (!croppedBlob) {
5921
+ throw new Error("Failed to generate cropped image blob.");
5922
+ }
5923
+ const newFinalUrl = URL.createObjectURL(croppedBlob);
5924
+ if (finalImageUrl) {
5925
+ URL.revokeObjectURL(finalImageUrl);
5926
+ }
5927
+ setFinalImageUrl(newFinalUrl);
5928
+ onOpenChange(false);
5929
+ onApply(newFinalUrl, croppedBlob);
5930
+ } catch (error2) {
5931
+ onOpenChange(false);
5932
+ }
5933
+ };
5934
+ const handleCancelCrop = () => {
5935
+ setFinalImageUrl(null);
5936
+ setCroppedAreaPixels(null);
5937
+ setZoom(1);
5938
+ onOpenChange?.(false);
5939
+ onCancelCrop?.();
5940
+ };
5941
+ React8.useEffect(() => {
5942
+ const currentFinalUrl = finalImageUrl;
5943
+ return () => {
5944
+ if (currentFinalUrl && currentFinalUrl.startsWith("blob:")) {
5945
+ URL.revokeObjectURL(currentFinalUrl);
5946
+ }
5947
+ };
5948
+ }, [finalImageUrl]);
5949
+ const { cropper } = useNebulaI18n().messages;
5950
+ return /* @__PURE__ */ jsxRuntime.jsx(
5951
+ Dialog,
5952
+ {
5953
+ open,
5954
+ onOpenChange: (isOpen) => {
5955
+ if (!isOpen && !finalImageUrl) {
5956
+ handleCancelCrop();
5957
+ }
5958
+ onOpenChange?.(isOpen);
5959
+ },
5960
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
5961
+ DialogContent,
5962
+ {
5963
+ className: "nebula-ds gap-0 p-0 sm:max-w-140 *:[button]:hidden border border-cropper-dialogBorderColor",
5964
+ portal,
5965
+ showClose: false,
5966
+ onOpenAutoFocus: (e) => e.preventDefault(),
5967
+ children: [
5968
+ /* @__PURE__ */ jsxRuntime.jsx(DialogHeader, { className: "nebula-ds contents space-y-0 text-left", children: /* @__PURE__ */ jsxRuntime.jsxs(DialogTitle, { className: "nebula-ds flex items-center justify-between p-4 text-base", children: [
5969
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nebula-ds flex items-center gap-2", children: [
5970
+ /* @__PURE__ */ jsxRuntime.jsx(
5971
+ Button,
5046
5972
  {
5047
- src: file.preview,
5048
- alt: file.file.name,
5049
- className: "nebula-ds rounded-[inherit] object-cover h-full w-full"
5973
+ icon: true,
5974
+ type: "button",
5975
+ variant: "ghost",
5976
+ onClick: handleCancelCrop,
5977
+ "aria-label": "close-cropper-dialog",
5978
+ "data-testid": "close-cropper-dialog",
5979
+ size: "sm",
5980
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.XIcon, { "aria-hidden": "true" })
5050
5981
  }
5051
5982
  ),
5052
- !file && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.UserIcon, { className: "nebula-ds size-4 opacity-60 text-fileUpload-icon" })
5053
- ]
5054
- }
5983
+ cropper.dialogTitle
5984
+ ] }),
5985
+ /* @__PURE__ */ jsxRuntime.jsx(
5986
+ Button,
5987
+ {
5988
+ autoFocus: false,
5989
+ onClick: handleApply,
5990
+ disabled: !previewUrl,
5991
+ variant: "primary",
5992
+ size: "sm",
5993
+ type: "button",
5994
+ "aria-label": "apply-cropper-image",
5995
+ "data-testid": "apply-cropper-image",
5996
+ children: cropper.applyButtonLabel
5997
+ }
5998
+ )
5999
+ ] }) }),
6000
+ previewUrl && /* @__PURE__ */ jsxRuntime.jsxs(
6001
+ CropperRoot2,
6002
+ {
6003
+ className: "nebula-ds h-96 sm:h-120",
6004
+ image: previewUrl,
6005
+ zoom,
6006
+ onCropChange: handleCropChange,
6007
+ onZoomChange: setZoom,
6008
+ children: [
6009
+ /* @__PURE__ */ jsxRuntime.jsx(CropperDescription2, {}),
6010
+ /* @__PURE__ */ jsxRuntime.jsx(CropperImage2, {}),
6011
+ /* @__PURE__ */ jsxRuntime.jsx(CropperCropArea2, { rounded })
6012
+ ]
6013
+ }
6014
+ ),
6015
+ /* @__PURE__ */ jsxRuntime.jsx(DialogFooter, { className: "nebula-ds border-t border-t-cropper-dialogBorderColor px-4 py-6", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nebula-ds mx-auto flex w-full max-w-80 items-center gap-4", children: [
6016
+ /* @__PURE__ */ jsxRuntime.jsx(
6017
+ lucideReact.MinusIcon,
6018
+ {
6019
+ className: "nebula-ds shrink-0 size-5 text-cropper-sliderIconColor",
6020
+ size: 16,
6021
+ "aria-hidden": "true"
6022
+ }
6023
+ ),
6024
+ /* @__PURE__ */ jsxRuntime.jsx(
6025
+ Slider,
6026
+ {
6027
+ defaultValue: [1],
6028
+ value: [zoom],
6029
+ min: 1,
6030
+ max: 3,
6031
+ step: 0.1,
6032
+ onValueChange: (value) => setZoom(value[0]),
6033
+ "aria-label": "Zoom slider"
6034
+ }
6035
+ ),
6036
+ /* @__PURE__ */ jsxRuntime.jsx(
6037
+ lucideReact.PlusIcon,
6038
+ {
6039
+ className: "nebula-ds shrink-0 size-5 text-cropper-sliderIconColor",
6040
+ size: 16,
6041
+ "aria-hidden": "true"
6042
+ }
6043
+ )
6044
+ ] }) })
6045
+ ]
6046
+ }
6047
+ )
6048
+ }
6049
+ );
6050
+ }
6051
+ var ProfileImage = ({
6052
+ maxSizeMB = 5,
6053
+ subtitle,
6054
+ onError,
6055
+ maxFiles = Infinity,
6056
+ onRemove,
6057
+ image,
6058
+ cropperProps,
6059
+ onChange
6060
+ }) => {
6061
+ const maxSize = maxSizeMB * 1024 * 1024;
6062
+ const id = React8.useId();
6063
+ const withCropper = React8.useMemo(() => !!cropperProps, [cropperProps]);
6064
+ const handleFileChange = (file2, blob) => {
6065
+ if (!file2) {
6066
+ return;
6067
+ }
6068
+ let fileLike = file2;
6069
+ if (withCropper && !blob) {
6070
+ return;
6071
+ }
6072
+ if (withCropper && blob) {
6073
+ fileLike = new File([blob], file2.name, { type: file2.type });
6074
+ }
6075
+ onChange?.(fileLike);
6076
+ };
6077
+ const [
6078
+ { files, isDragging, errors },
6079
+ {
6080
+ handleDragEnter,
6081
+ handleDragLeave,
6082
+ handleDragOver,
6083
+ handleDrop,
6084
+ openFileDialog,
6085
+ removeFile,
6086
+ getInputProps
6087
+ }
6088
+ ] = useFileUpload({
6089
+ maxFiles,
6090
+ initialFiles: image ? [
6091
+ {
6092
+ id,
6093
+ url: image,
6094
+ name: image,
6095
+ size: 0,
6096
+ type: "image"
6097
+ }
6098
+ ] : [],
6099
+ multiple: false,
6100
+ maxSize: maxSize > 0 ? maxSize : void 0,
6101
+ accept: "image/*",
6102
+ onFilesChange([file2]) {
6103
+ handleFileChange(file2?.file);
6104
+ }
6105
+ });
6106
+ const [finalImageUrl, setFinalImageUrl] = React8.useState(null);
6107
+ const [isDialogOpen, setIsDialogOpen] = React8.useState(false);
6108
+ React8.useEffect(() => {
6109
+ onError?.(errors);
6110
+ }, [errors, onError]);
6111
+ const [file] = files;
6112
+ const fileId = file?.id;
6113
+ const previousFileIdRef = React8.useRef(null);
6114
+ const previewUrl = file?.preview || null;
6115
+ const profileImagePreview = React8.useMemo(() => {
6116
+ if (withCropper) return finalImageUrl;
6117
+ return previewUrl;
6118
+ }, [previewUrl, withCropper, finalImageUrl]);
6119
+ React8.useEffect(() => {
6120
+ if (!withCropper) return;
6121
+ if (fileId && fileId !== previousFileIdRef.current) {
6122
+ setIsDialogOpen(true);
6123
+ }
6124
+ previousFileIdRef.current = fileId;
6125
+ }, [fileId, withCropper]);
6126
+ const handleApply = async (croppedUrl, croppedBlob) => {
6127
+ try {
6128
+ const newFinalUrl = URL.createObjectURL(croppedBlob);
6129
+ if (finalImageUrl) {
6130
+ URL.revokeObjectURL(finalImageUrl);
6131
+ }
6132
+ setFinalImageUrl(newFinalUrl);
6133
+ handleFileChange(file?.file, croppedBlob);
6134
+ setIsDialogOpen(false);
6135
+ } catch (error2) {
6136
+ setIsDialogOpen(false);
6137
+ }
6138
+ };
6139
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
6140
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nebula-ds flex flex-col gap-3", children: [
6141
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nebula-ds flex justify-center", children: /* @__PURE__ */ jsxRuntime.jsxs(
6142
+ "div",
6143
+ {
6144
+ role: "button",
6145
+ onClick: openFileDialog,
6146
+ onDragEnter: handleDragEnter,
6147
+ onDragLeave: handleDragLeave,
6148
+ onDragOver: handleDragOver,
6149
+ onDrop: handleDrop,
6150
+ "data-dragging": isDragging || void 0,
6151
+ className: cn(
6152
+ "relative border border-transparent rounded-full size-fit",
6153
+ "bg-fileUpload-background hover:bg-fileUpload-backgroundHover transition-colors",
6154
+ !file && "border-dashed border-fileUpload-border"
5055
6155
  ),
5056
- file && /* @__PURE__ */ jsxRuntime.jsx(
5057
- "button",
5058
- {
5059
- className: "nebula-ds box-border absolute flex items-center justify-center -top-1 -right-1 rounded-full size-5 text-profileImage-removeImageButton-icon border-2 border-profileImage-removeImageButton-border bg-profileImage-removeImageButton-background hover:bg-profileImage-removeImageButton-hover",
5060
- "data-testid": "remove-profile-image",
5061
- onClick: (e) => {
5062
- e.stopPropagation();
5063
- removeFile(file.id);
5064
- onRemove?.();
5065
- },
5066
- type: "button",
5067
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.XIcon, { className: "nebula-ds size-2" })
5068
- }
5069
- )
5070
- ]
6156
+ "data-testid": "select-image-profile",
6157
+ children: [
6158
+ /* @__PURE__ */ jsxRuntime.jsx(
6159
+ "input",
6160
+ {
6161
+ ...getInputProps(),
6162
+ className: "nebula-ds sr-only",
6163
+ "aria-label": "Upload file"
6164
+ }
6165
+ ),
6166
+ /* @__PURE__ */ jsxRuntime.jsxs(
6167
+ "div",
6168
+ {
6169
+ className: "nebula-ds flex size-12 shrink-0 items-center justify-center rounded-full",
6170
+ "aria-hidden": "true",
6171
+ children: [
6172
+ profileImagePreview && /* @__PURE__ */ jsxRuntime.jsx(
6173
+ "img",
6174
+ {
6175
+ src: profileImagePreview,
6176
+ alt: fileId,
6177
+ className: "nebula-ds rounded-[inherit] object-cover h-full w-full"
6178
+ }
6179
+ ),
6180
+ !file && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.UserIcon, { className: "nebula-ds size-4 opacity-60 text-fileUpload-icon" })
6181
+ ]
6182
+ }
6183
+ ),
6184
+ profileImagePreview && file && /* @__PURE__ */ jsxRuntime.jsx(
6185
+ "button",
6186
+ {
6187
+ className: "nebula-ds box-border absolute flex items-center justify-center -top-1 -right-1 rounded-full size-5 text-profileImage-removeImageButton-icon border-2 border-profileImage-removeImageButton-border bg-profileImage-removeImageButton-background hover:bg-profileImage-removeImageButton-hover",
6188
+ "data-testid": "remove-profile-image",
6189
+ onClick: (e) => {
6190
+ e.stopPropagation();
6191
+ removeFile(file.id);
6192
+ setFinalImageUrl(null);
6193
+ onChange?.(void 0);
6194
+ onRemove?.();
6195
+ },
6196
+ type: "button",
6197
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.XIcon, { className: "nebula-ds size-2" })
6198
+ }
6199
+ )
6200
+ ]
6201
+ }
6202
+ ) }),
6203
+ !!subtitle && /* @__PURE__ */ jsxRuntime.jsx(Paragraph, { className: "nebula-ds text-center", size: "sm", children: subtitle })
6204
+ ] }),
6205
+ withCropper && /* @__PURE__ */ jsxRuntime.jsx(
6206
+ Cropper2,
6207
+ {
6208
+ ...cropperProps,
6209
+ open: isDialogOpen,
6210
+ previewUrl,
6211
+ onOpenChange: setIsDialogOpen,
6212
+ onApply: handleApply,
6213
+ onCancelCrop: () => {
6214
+ fileId && removeFile(fileId);
6215
+ setFinalImageUrl(null);
6216
+ onChange?.(void 0);
6217
+ }
5071
6218
  }
5072
- ) }),
5073
- !!subtitle && /* @__PURE__ */ jsxRuntime.jsx(Paragraph, { className: "nebula-ds text-center", size: "sm", children: subtitle })
6219
+ )
5074
6220
  ] });
5075
6221
  };
5076
6222
  ProfileImage.displayName = "ProfileImage";
6223
+ function RadioGroup2({
6224
+ className,
6225
+ ...props
6226
+ }) {
6227
+ return /* @__PURE__ */ jsxRuntime.jsx(
6228
+ RadioGroupPrimitive__namespace.Root,
6229
+ {
6230
+ "data-slot": "radio-group",
6231
+ className: cn("grid gap-3", className),
6232
+ ...props
6233
+ }
6234
+ );
6235
+ }
6236
+ function RadioGroupItem({
6237
+ className,
6238
+ ...props
6239
+ }) {
6240
+ return /* @__PURE__ */ jsxRuntime.jsx(
6241
+ RadioGroupPrimitive__namespace.Item,
6242
+ {
6243
+ "data-slot": "radio-group-item",
6244
+ className: cn(
6245
+ `
6246
+ border-2
6247
+ data-[state=checked]:border-4
6248
+
6249
+ border-radio-border-unselected-default
6250
+ data-[state=checked]:border-radio-background-selected-default
6251
+ hover:border-radio-border-unselected-hover
6252
+ data-[state=checked]:hover:border-radio-background-selected-hover
6253
+
6254
+ bg-radio-background-unselected-default
6255
+ data-[state=checked]:bg-radio-background-unselected-default
6256
+ hover:bg-radio-background-unselected-hover
6257
+ data-[state=checked]:hover:bg-radio-background-unselected-hover
6258
+
6259
+ disabled:border-radio-border-unselected-disabled
6260
+ disabled:bg-radio-background-unselected-disabled
6261
+ data-[state=checked]:disabled:bg-radio-background-unselected-default
6262
+ data-[state=checked]:disabled:border-radio-background-selected-disabled
6263
+
6264
+ focus-visible:ring-[2px]
6265
+ focus-visible:border-radio-border-selected-focus
6266
+
6267
+ aria-invalid:ring-destructive/20
6268
+ dark:aria-invalid:ring-destructive/40
6269
+ aria-invalid:border-destructive
6270
+
6271
+ aspect-square
6272
+ size-4
6273
+ shrink-0
6274
+ rounded-full
6275
+ shadow-xs
6276
+ transition-all
6277
+ outline-none
6278
+ disabled:cursor-not-allowed
6279
+ `,
6280
+ className
6281
+ ),
6282
+ ...props,
6283
+ children: /* @__PURE__ */ jsxRuntime.jsx(RadioGroupPrimitive__namespace.Indicator, { className: "nebula-ds flex items-center justify-center text-current", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nebula-ds rounded-full w-2 h-2" }) })
6284
+ }
6285
+ );
6286
+ }
6287
+ var StepperContext = React8.createContext(void 0);
6288
+ var StepItemContext = React8.createContext(
6289
+ void 0
6290
+ );
6291
+ var useStepper = () => {
6292
+ const context = React8.useContext(StepperContext);
6293
+ if (!context) {
6294
+ throw new Error("useStepper must be used within a Stepper");
6295
+ }
6296
+ return context;
6297
+ };
6298
+ var useStepItem = () => {
6299
+ const context = React8.useContext(StepItemContext);
6300
+ if (!context) {
6301
+ throw new Error("useStepItem must be used within a StepperItem");
6302
+ }
6303
+ return context;
6304
+ };
6305
+ function Stepper({
6306
+ defaultValue = 0,
6307
+ value,
6308
+ onValueChange,
6309
+ orientation = "horizontal",
6310
+ className,
6311
+ ...props
6312
+ }) {
6313
+ const [activeStep, setInternalStep] = React8__namespace.useState(defaultValue);
6314
+ const setActiveStep = React8__namespace.useCallback(
6315
+ (step) => {
6316
+ if (value === void 0) {
6317
+ setInternalStep(step);
6318
+ }
6319
+ onValueChange?.(step);
6320
+ },
6321
+ [value, onValueChange]
6322
+ );
6323
+ const currentStep = value ?? activeStep;
6324
+ return /* @__PURE__ */ jsxRuntime.jsx(
6325
+ StepperContext.Provider,
6326
+ {
6327
+ value: {
6328
+ activeStep: currentStep,
6329
+ setActiveStep,
6330
+ orientation
6331
+ },
6332
+ children: /* @__PURE__ */ jsxRuntime.jsx(
6333
+ "div",
6334
+ {
6335
+ "data-slot": "stepper",
6336
+ className: cn(
6337
+ `
6338
+ group/stepper
6339
+ gap-2
6340
+ inline-flex
6341
+ data-[orientation=horizontal]:w-full
6342
+ data-[orientation=horizontal]:flex-row
6343
+ data-[orientation=vertical]:flex-col
6344
+ `,
6345
+ className
6346
+ ),
6347
+ "data-orientation": orientation,
6348
+ ...props
6349
+ }
6350
+ )
6351
+ }
6352
+ );
6353
+ }
6354
+ function StepperItem({
6355
+ step,
6356
+ completed = false,
6357
+ disabled = false,
6358
+ loading = false,
6359
+ className,
6360
+ children,
6361
+ ...props
6362
+ }) {
6363
+ const { activeStep } = useStepper();
6364
+ const state = completed || step < activeStep ? "completed" : activeStep === step ? "active" : "inactive";
6365
+ const isLoading = loading && step === activeStep;
6366
+ return /* @__PURE__ */ jsxRuntime.jsx(
6367
+ StepItemContext.Provider,
6368
+ {
6369
+ value: { step, state, isDisabled: disabled, isLoading },
6370
+ children: /* @__PURE__ */ jsxRuntime.jsx(
6371
+ "div",
6372
+ {
6373
+ "data-slot": "stepper-item",
6374
+ className: cn(
6375
+ `
6376
+ group/step
6377
+ gap-2
6378
+ flex
6379
+ items-center
6380
+ group-data-[orientation=horizontal]/stepper:flex-row
6381
+ group-data-[orientation=vertical]/stepper:flex-col
6382
+ group-data-[orientation=vertical]/stepper:items-start
6383
+ `,
6384
+ className
6385
+ ),
6386
+ "data-state": state,
6387
+ ...isLoading ? { "data-loading": true } : {},
6388
+ ...props,
6389
+ children
6390
+ }
6391
+ )
6392
+ }
6393
+ );
6394
+ }
6395
+ function StepperTrigger({
6396
+ asChild = false,
6397
+ className,
6398
+ children,
6399
+ isClickable = true,
6400
+ ...props
6401
+ }) {
6402
+ const { setActiveStep } = useStepper();
6403
+ const { step, isDisabled } = useStepItem();
6404
+ if (asChild) {
6405
+ const Comp = asChild ? reactSlot.Slot : "span";
6406
+ return /* @__PURE__ */ jsxRuntime.jsx(Comp, { "data-slot": "stepper-trigger", className, children });
6407
+ }
6408
+ return /* @__PURE__ */ jsxRuntime.jsx(
6409
+ "button",
6410
+ {
6411
+ "data-slot": "stepper-trigger",
6412
+ className: cn(
6413
+ `
6414
+ gap-2
6415
+ inline-flex
6416
+ items-center
6417
+ rounded-full
6418
+ outline-none
6419
+ focus-visible:z-10
6420
+ focus-visible:border-ring
6421
+ focus-visible:ring-ring/50
6422
+ focus-visible:ring-[3px]
6423
+ disabled:pointer-events-none
6424
+ disabled:opacity-50
6425
+ `,
6426
+ className
6427
+ ),
6428
+ onClick: () => isClickable ? setActiveStep(step) : null,
6429
+ disabled: isDisabled,
6430
+ ...props,
6431
+ children
6432
+ }
6433
+ );
6434
+ }
6435
+ function StepperIndicator({
6436
+ asChild = false,
6437
+ className,
6438
+ children,
6439
+ ...props
6440
+ }) {
6441
+ const { state, step, isLoading } = useStepItem();
6442
+ return /* @__PURE__ */ jsxRuntime.jsx(
6443
+ "span",
6444
+ {
6445
+ "data-slot": "stepper-indicator",
6446
+ className: cn(
6447
+ `
6448
+ flex
6449
+ size-5
6450
+ shrink-0
6451
+ items-center
6452
+ justify-center
6453
+ rounded-full
6454
+ font-semibold
6455
+ text-xs
6456
+ transition-colors
6457
+ bg-stepper-background-default
6458
+ text-stepper-count-default
6459
+ data-[state=active]:bg-stepper-background-active
6460
+ data-[state=active]:text-stepper-count-active
6461
+ data-[state=completed]:bg-stepper-background-active
6462
+ data-[state=completed]:text-stepper-count-active
6463
+ `,
6464
+ className
6465
+ ),
6466
+ "data-state": state,
6467
+ ...props,
6468
+ children: asChild ? children : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
6469
+ isLoading ? /* @__PURE__ */ jsxRuntime.jsx(
6470
+ lucideReact.LoaderCircleIcon,
6471
+ {
6472
+ className: "nebula-ds animate-spin",
6473
+ size: 14,
6474
+ "aria-hidden": "true"
6475
+ }
6476
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
6477
+ "span",
6478
+ {
6479
+ className: cn(
6480
+ `
6481
+ transition-all
6482
+ group-data-[state=completed]/step:scale-0
6483
+ group-data-[state=completed]/step:opacity-0
6484
+ `
6485
+ ),
6486
+ children: step
6487
+ }
6488
+ ),
6489
+ /* @__PURE__ */ jsxRuntime.jsx(
6490
+ lucideReact.CheckIcon,
6491
+ {
6492
+ className: cn(
6493
+ `
6494
+ absolute
6495
+ scale-0
6496
+ opacity-0
6497
+ transition-all
6498
+ group-data-[state=completed]/step:scale-100
6499
+ group-data-[state=completed]/step:opacity-100
6500
+ `
6501
+ ),
6502
+ size: 16,
6503
+ "aria-hidden": "true"
6504
+ }
6505
+ )
6506
+ ] })
6507
+ }
6508
+ );
6509
+ }
6510
+ function StepperTitle({
6511
+ className,
6512
+ ...props
6513
+ }) {
6514
+ const { state } = useStepItem();
6515
+ return /* @__PURE__ */ jsxRuntime.jsx(
6516
+ "h3",
6517
+ {
6518
+ "data-slot": "stepper-title",
6519
+ className: cn(
6520
+ `
6521
+ text-sm
6522
+ font-medium
6523
+ text-stepper-title-default
6524
+ data-[state=active]:text-stepper-title-active
6525
+ data-[state=completed]:text-stepper-title-active
6526
+ `,
6527
+ className
6528
+ ),
6529
+ "data-state": state,
6530
+ ...props
6531
+ }
6532
+ );
6533
+ }
6534
+ function StepperDescription({
6535
+ className,
6536
+ ...props
6537
+ }) {
6538
+ return /* @__PURE__ */ jsxRuntime.jsx(
6539
+ "p",
6540
+ {
6541
+ "data-slot": "stepper-description",
6542
+ className: cn(
6543
+ `
6544
+ text-sm
6545
+ text-muted-foreground
6546
+ `,
6547
+ className
6548
+ ),
6549
+ ...props
6550
+ }
6551
+ );
6552
+ }
6553
+ function StepperSeparator({
6554
+ className,
6555
+ ...props
6556
+ }) {
6557
+ const { step } = useStepItem();
6558
+ const { activeStep, orientation } = useStepper();
6559
+ const separatorState = step < activeStep ? "completed" : "inactive";
6560
+ const line = /* @__PURE__ */ jsxRuntime.jsx(
6561
+ "div",
6562
+ {
6563
+ "data-slot": "stepper-separator",
6564
+ "data-orientation": orientation,
6565
+ className: cn(
6566
+ `
6567
+ rounded-full
6568
+ bg-stepper-separator-default
6569
+ `,
6570
+ orientation === "horizontal" && `
6571
+ w-5
6572
+ h-[2px]
6573
+ flex-1
6574
+ `,
6575
+ orientation === "vertical" && `
6576
+ w-[2px]
6577
+ h-5
6578
+ flex-none
6579
+ `,
6580
+ separatorState === "completed" && `
6581
+ bg-stepper-separator-active
6582
+ `,
6583
+ className
6584
+ ),
6585
+ "data-state": separatorState,
6586
+ ...props
6587
+ }
6588
+ );
6589
+ if (orientation === "vertical") {
6590
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "nebula-ds flex w-5 justify-center", children: line });
6591
+ }
6592
+ return line;
6593
+ }
5077
6594
 
5078
6595
  // src/tailwind.ts
5079
6596
  function content({ base = "./" } = {}) {
@@ -5127,6 +6644,10 @@ exports.Calendar = Calendar;
5127
6644
  exports.Caption = Caption;
5128
6645
  exports.Checkbox = Checkbox;
5129
6646
  exports.Creatable = StyledCreatable;
6647
+ exports.Cropper = Cropper2;
6648
+ exports.CropperCropArea = CropperCropArea2;
6649
+ exports.CropperDescription = CropperDescription2;
6650
+ exports.CropperImage = CropperImage2;
5130
6651
  exports.Dialog = Dialog;
5131
6652
  exports.DialogBody = DialogBody;
5132
6653
  exports.DialogClose = DialogClose;
@@ -5178,12 +6699,22 @@ exports.Popover = Popover;
5178
6699
  exports.PopoverContent = PopoverContent;
5179
6700
  exports.PopoverTrigger = PopoverTrigger;
5180
6701
  exports.ProfileImage = ProfileImage;
6702
+ exports.RadioGroup = RadioGroup2;
6703
+ exports.RadioGroupItem = RadioGroupItem;
5181
6704
  exports.Select = StyledSelect;
5182
6705
  exports.Separator = Separator2;
5183
6706
  exports.Skeleton = Skeleton;
6707
+ exports.Slider = Slider;
5184
6708
  exports.Space = Space;
5185
6709
  exports.SpaceDirectionEnum = SpaceDirectionEnum;
5186
6710
  exports.SpaceSizeEnum = SpaceSizeEnum;
6711
+ exports.Stepper = Stepper;
6712
+ exports.StepperDescription = StepperDescription;
6713
+ exports.StepperIndicator = StepperIndicator;
6714
+ exports.StepperItem = StepperItem;
6715
+ exports.StepperSeparator = StepperSeparator;
6716
+ exports.StepperTitle = StepperTitle;
6717
+ exports.StepperTrigger = StepperTrigger;
5187
6718
  exports.Switch = Switch;
5188
6719
  exports.Table = Table;
5189
6720
  exports.TableBody = TableBody;
@@ -5211,7 +6742,7 @@ exports.dateIsAvailable = dateIsAvailable;
5211
6742
  exports.formatBytes = formatBytes;
5212
6743
  exports.getNebulaLanguage = getNebulaLanguage;
5213
6744
  exports.localeByi18nKey = localeByi18nKey;
5214
- exports.messages = messages16;
6745
+ exports.messages = messages19;
5215
6746
  exports.separatorVariants = separatorVariants;
5216
6747
  exports.setNebulaLanguage = setNebulaLanguage;
5217
6748
  exports.tagVariantsEnum = tagVariantsEnum;