@xsolla/xui-image-uploader 0.149.0 → 0.150.0

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/native/index.mjs CHANGED
@@ -279,6 +279,8 @@ var ImageUploader = forwardRef(
279
279
  const fileInputRef = useRef(null);
280
280
  const isControlled = value !== void 0;
281
281
  const [internalPreview, setInternalPreview] = useState(null);
282
+ const [isAutoUploading, setIsAutoUploading] = useState(false);
283
+ const [autoError, setAutoError] = useState(null);
282
284
  const [isHover, setIsHover] = useState(false);
283
285
  const [isFocus, setIsFocus] = useState(false);
284
286
  const [isPreviewHover, setIsPreviewHover] = useState(false);
@@ -304,10 +306,11 @@ var ImageUploader = forwardRef(
304
306
  const alertBorder = theme.colors.control.alert.border;
305
307
  const alertText = theme.colors.content.alert.primary;
306
308
  const scrim = theme.colors.layer.scrim;
307
- const hasError = !!errorMessage;
309
+ const resolvedError = errorMessage || autoError;
310
+ const hasError = !!resolvedError;
308
311
  let state;
309
312
  if (disabled) state = "disable";
310
- else if (loading) state = "uploading";
313
+ else if (loading || isAutoUploading) state = "uploading";
311
314
  else if (hasError) state = "error";
312
315
  else if (preview)
313
316
  state = isPreviewHover || isHoverless ? "uploadedHover" : "uploaded";
@@ -315,25 +318,42 @@ var ImageUploader = forwardRef(
315
318
  else if (isHover) state = "hover";
316
319
  else state = "default";
317
320
  const emitFile = (img) => {
321
+ setAutoError(null);
318
322
  if (!isControlled) setInternalPreview(img.uri);
319
- onUpload?.(img);
320
323
  onChange?.({
321
324
  filename: img.name,
322
325
  url: img.uri
323
326
  });
327
+ const result = onUpload?.(img);
328
+ if (result && typeof result.then === "function") {
329
+ setIsAutoUploading(true);
330
+ Promise.resolve(result).then((uploaded) => {
331
+ let next = null;
332
+ if (typeof uploaded === "string") {
333
+ next = { url: uploaded };
334
+ } else if (uploaded && typeof uploaded === "object" && "url" in uploaded) {
335
+ next = uploaded;
336
+ }
337
+ if (next) {
338
+ if (!isControlled) setInternalPreview(next.url ?? null);
339
+ onChange?.(next);
340
+ }
341
+ }).catch((err) => {
342
+ setAutoError(err?.message || "Upload failed");
343
+ if (!isControlled) setInternalPreview(null);
344
+ onChange?.(null, err);
345
+ }).finally(() => setIsAutoUploading(false));
346
+ }
324
347
  };
325
348
  const handleWebFile = (file) => {
326
- if (!file.type.startsWith("image/")) return;
327
349
  const reader = new FileReader();
328
350
  reader.onload = (e) => {
329
351
  const uri = e.target?.result;
330
- emitFile({
331
- name: file.name,
332
- size: file.size,
333
- uri,
334
- mimeType: file.type,
335
- file
336
- });
352
+ const enriched = file;
353
+ enriched.uri = uri;
354
+ enriched.mimeType = file.type;
355
+ enriched.file = enriched;
356
+ emitFile(enriched);
337
357
  };
338
358
  reader.readAsDataURL(file);
339
359
  };
@@ -377,6 +397,7 @@ var ImageUploader = forwardRef(
377
397
  };
378
398
  const removeImage = (e) => {
379
399
  e?.stopPropagation?.();
400
+ setAutoError(null);
380
401
  if (!isControlled) setInternalPreview(null);
381
402
  setIsPreviewHover(false);
382
403
  onDelete?.();
@@ -420,6 +441,7 @@ var ImageUploader = forwardRef(
420
441
  break;
421
442
  }
422
443
  const rootGap = state === "error" ? sizeStyles.errorGap : 0;
444
+ const placeholderContent = state === "uploading" ? uploadingPlaceholder : placeholder;
423
445
  const renderInner = () => {
424
446
  if (state === "uploaded" || state === "uploadedHover") {
425
447
  return /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -480,7 +502,7 @@ var ImageUploader = forwardRef(
480
502
  ...isWeb && { "aria-hidden": "true" }
481
503
  }
482
504
  ) : /* @__PURE__ */ jsx3(Box, { ...isWeb && { "aria-hidden": "true" }, children: /* @__PURE__ */ jsx3(Image2, { size: sizeStyles.iconSize, color: iconColor }) }),
483
- placeholder && /* @__PURE__ */ jsx3(
505
+ placeholderContent && (typeof placeholderContent === "string" ? /* @__PURE__ */ jsx3(
484
506
  Text,
485
507
  {
486
508
  "data-testid": "image-uploader__placeholder",
@@ -496,9 +518,9 @@ var ImageUploader = forwardRef(
496
518
  textOverflow: "ellipsis",
497
519
  whiteSpace: "nowrap"
498
520
  } : void 0,
499
- children: state === "uploading" ? uploadingPlaceholder : placeholder
521
+ children: placeholderContent
500
522
  }
501
- ),
523
+ ) : /* @__PURE__ */ jsx3(Box, { "data-testid": "image-uploader__placeholder", children: placeholderContent })),
502
524
  description && wideView && state !== "uploading" && state !== "error" && (typeof description === "string" ? /* @__PURE__ */ jsx3(
503
525
  Text,
504
526
  {
@@ -556,9 +578,9 @@ var ImageUploader = forwardRef(
556
578
  outline: "none",
557
579
  transition: "background-color 0.15s ease, border-color 0.15s ease"
558
580
  } : {};
559
- const buttonAriaLabel = preview ? `Remove image${value?.filename ? `: ${value.filename}` : ""}` : state === "uploading" ? uploadingPlaceholder : placeholder;
581
+ const buttonAriaLabel = preview ? `Remove image${value?.filename ? `: ${value.filename}` : ""}` : state === "uploading" ? typeof uploadingPlaceholder === "string" ? uploadingPlaceholder : "Uploading" : typeof placeholder === "string" ? placeholder : "Upload";
560
582
  const describedBy = [
561
- state === "error" && errorMessage ? errorId : null,
583
+ state === "error" && resolvedError ? errorId : null,
562
584
  description && wideView && state !== "uploading" && state !== "error" ? descriptionId : null
563
585
  ].filter(Boolean).join(" ") || void 0;
564
586
  return /* @__PURE__ */ jsxs(
@@ -569,7 +591,6 @@ var ImageUploader = forwardRef(
569
591
  gap: rootGap,
570
592
  alignItems: "flex-start",
571
593
  width: wideView ? "100%" : void 0,
572
- maxWidth: wideView ? sizeStyles.wideWidth : void 0,
573
594
  children: [
574
595
  isWeb && /* @__PURE__ */ jsx3(
575
596
  "input",
@@ -601,7 +622,6 @@ var ImageUploader = forwardRef(
601
622
  ...webOnlyHandlers,
602
623
  position: "relative",
603
624
  width: wideView ? "100%" : sizeStyles.box,
604
- maxWidth: wideView ? sizeStyles.wideWidth : void 0,
605
625
  height: sizeStyles.box,
606
626
  flexDirection: "column",
607
627
  alignItems: "center",
@@ -618,7 +638,7 @@ var ImageUploader = forwardRef(
618
638
  children: renderInner()
619
639
  }
620
640
  ),
621
- state === "error" && errorMessage && /* @__PURE__ */ jsx3(
641
+ state === "error" && resolvedError && /* @__PURE__ */ jsx3(
622
642
  Text,
623
643
  {
624
644
  "data-testid": "image-uploader__error",
@@ -626,7 +646,7 @@ var ImageUploader = forwardRef(
626
646
  fontSize: sizeStyles.errorFontSize,
627
647
  lineHeight: sizeStyles.errorLineHeight,
628
648
  ...isWeb && { id: errorId, role: "alert" },
629
- children: errorMessage
649
+ children: resolvedError
630
650
  }
631
651
  )
632
652
  ]
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/ImageUploader.tsx","../../../../foundation/primitives-native/src/Box.tsx","../../../../foundation/primitives-native/src/Text.tsx","../../../../foundation/primitives-native/src/index.tsx"],"sourcesContent":["import React, { useRef, useState, useEffect, forwardRef } from \"react\";\n// @ts-expect-error - this will be resolved at build time\nimport { Box, Text, isWeb } from \"@xsolla/xui-primitives\";\nimport {\n useId,\n useResolvedTheme,\n type ThemeOverrideProps,\n} from \"@xsolla/xui-core\";\nimport { Image, TrashCan } from \"@xsolla/xui-icons-base\";\nimport { Spinner } from \"@xsolla/xui-spinner\";\n\nexport type ImageUploaderSize = \"xl\" | \"lg\" | \"md\" | \"sm\" | \"xs\";\n\n/** Controlled value shape. */\nexport interface ImageUploaderValue {\n filename: string;\n url?: string;\n}\n\n/**\n * Normalized file shape produced by the platform picker / drag-drop pipeline.\n * - `uri` is a data-URL on web and a file URI on native.\n * - On web, the original `File` is passed through for consumers that need it\n * (e.g. to upload via FormData / fetch).\n */\nexport type ImageUploaderFile = {\n name: string;\n size: number;\n uri: string;\n mimeType?: string;\n /** The original DOM File (web only) */\n file?: File;\n};\n\nexport interface ImageUploaderProps extends ThemeOverrideProps {\n /** Size of the uploader. Figma default is `xl`. */\n size?: ImageUploaderSize;\n /** Placeholder text shown under the icon (e.g. \"Upload\" or filename in error). */\n placeholder?: string;\n /** Placeholder text shown while the file is uploading. */\n uploadingPlaceholder?: string;\n /** Description below the placeholder. Only rendered when `wideView` is true. Accepts a string or a custom React node. */\n description?: React.ReactNode;\n /** Error message — when provided, component renders in the error state. */\n errorMessage?: string;\n /** Wide view (horizontal layout). When true, box uses `wideWidth` from sizing. */\n wideView?: boolean;\n /** Disabled state. */\n disabled?: boolean;\n /** Controlled loading state (shows spinner + \"Uploading\" label). */\n loading?: boolean;\n\n /** Controlled value. When provided, the component reflects this value. */\n value?: ImageUploaderValue | null;\n\n /**\n * Fires when the user picks (or drops) a file. Use this to perform the\n * actual upload — the component itself does no I/O.\n */\n onUpload?: (file: ImageUploaderFile) => void;\n /**\n * Fires when the displayed value changes — on file pick (with a local\n * data-URL preview) and on remove (`null`).\n */\n onChange?: (value: ImageUploaderValue | null) => void;\n /** Fires when the user removes the image (trash click / clear). */\n onDelete?: () => void;\n\n /** Accepted file types (web only — ignored on native). */\n accept?: string;\n /**\n * Native file picker hook. Required on native (no DOM `<input type=\"file\">`).\n * On web, omit this and the component falls back to a hidden file input.\n */\n openPicker?: () => Promise<ImageUploaderFile | null>;\n}\n\ntype InternalState =\n | \"default\"\n | \"hover\"\n | \"focus\"\n | \"uploading\"\n | \"uploaded\"\n | \"uploadedHover\"\n | \"error\"\n | \"disable\";\n\nexport const ImageUploader = forwardRef<HTMLInputElement, ImageUploaderProps>(\n (\n {\n size = \"xl\",\n placeholder = \"Upload\",\n uploadingPlaceholder = \"Uploading\",\n description,\n errorMessage,\n wideView = false,\n accept = \"image/*\",\n openPicker,\n value,\n onUpload,\n onChange,\n onDelete,\n disabled = false,\n loading = false,\n themeMode,\n themeProductContext,\n },\n ref\n ) => {\n const { theme } = useResolvedTheme({ themeMode, themeProductContext });\n const fileInputRef = useRef<HTMLInputElement>(null);\n const isControlled = value !== undefined;\n const [internalPreview, setInternalPreview] = useState<string | null>(null);\n const [isHover, setIsHover] = useState(false);\n const [isFocus, setIsFocus] = useState(false);\n const [isPreviewHover, setIsPreviewHover] = useState(false);\n // Touch / native devices have no hover, so the trash overlay would never\n // appear. Detect once on mount and force `uploadedHover` for those cases.\n const [isHoverless] = useState(\n () =>\n !isWeb ||\n (typeof window !== \"undefined\" &&\n !!window.matchMedia?.(\"(hover: none)\").matches)\n );\n\n const reactId = useId();\n const baseId = `image-uploader-${reactId.replace(/[^a-zA-Z0-9-]/g, \"\")}`;\n const descriptionId = `${baseId}-description`;\n const errorId = `${baseId}-error`;\n\n const preview = isControlled ? (value?.url ?? null) : internalPreview;\n\n useEffect(() => {\n if (!isControlled && value === null) setInternalPreview(null);\n }, [isControlled, value]);\n\n React.useImperativeHandle(\n ref,\n () => fileInputRef.current as HTMLInputElement,\n []\n );\n\n const sizeStyles = theme.sizing.imageUploader(size);\n const inputColors = theme.colors.control.input;\n const focusColors = theme.colors.control.focus;\n const alertBorder = theme.colors.control.alert.border;\n const alertText = theme.colors.content.alert.primary;\n const scrim = theme.colors.layer.scrim;\n\n const hasError = !!errorMessage;\n\n let state: InternalState;\n if (disabled) state = \"disable\";\n else if (loading) state = \"uploading\";\n else if (hasError) state = \"error\";\n else if (preview)\n state = isPreviewHover || isHoverless ? \"uploadedHover\" : \"uploaded\";\n else if (isFocus) state = \"focus\";\n else if (isHover) state = \"hover\";\n else state = \"default\";\n\n const emitFile = (img: ImageUploaderFile) => {\n if (!isControlled) setInternalPreview(img.uri);\n onUpload?.(img);\n onChange?.({\n filename: img.name,\n url: img.uri,\n });\n };\n\n const handleWebFile = (file: File) => {\n if (!file.type.startsWith(\"image/\")) return;\n const reader = new FileReader();\n reader.onload = (e) => {\n const uri = e.target?.result as string;\n emitFile({\n name: file.name,\n size: file.size,\n uri,\n mimeType: file.type,\n file,\n });\n };\n reader.readAsDataURL(file);\n };\n\n const handleClick = async () => {\n if (disabled || loading) return;\n if (preview) {\n removeImage();\n return;\n }\n if (openPicker) {\n const picked = await openPicker();\n if (picked) emitFile(picked);\n return;\n }\n if (isWeb) {\n fileInputRef.current?.click();\n }\n };\n\n const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n const file = e.target.files?.[0];\n e.target.value = \"\";\n if (file) handleWebFile(file);\n };\n\n const handleDragOver = (e: React.DragEvent) => {\n e.preventDefault();\n e.stopPropagation();\n if (!disabled && !loading && !preview) setIsHover(true);\n };\n\n const handleDragLeave = (e: React.DragEvent) => {\n e.preventDefault();\n e.stopPropagation();\n setIsHover(false);\n };\n\n const handleDrop = (e: React.DragEvent) => {\n e.preventDefault();\n e.stopPropagation();\n setIsHover(false);\n if (disabled || loading || preview) return;\n const file = e.dataTransfer.files?.[0];\n if (file) handleWebFile(file);\n };\n\n const removeImage = (e?: { stopPropagation?: () => void }) => {\n e?.stopPropagation?.();\n if (!isControlled) setInternalPreview(null);\n setIsPreviewHover(false);\n onDelete?.();\n onChange?.(null);\n if (isWeb && fileInputRef.current) fileInputRef.current.value = \"\";\n };\n\n let backgroundColor: string = inputColors.bg;\n let borderColor: string = inputColors.border;\n let borderStyle: \"dashed\" | \"solid\" = \"dashed\";\n let labelColor: string = inputColors.text;\n let descriptionColor: string = inputColors.placeholder;\n let iconColor: string = inputColors.text;\n\n switch (state) {\n case \"hover\":\n backgroundColor = inputColors.bgHover;\n borderColor = inputColors.borderHover;\n break;\n case \"focus\":\n case \"uploading\":\n backgroundColor = focusColors.bg;\n borderColor = focusColors.border;\n break;\n case \"uploaded\":\n case \"uploadedHover\":\n backgroundColor = \"transparent\";\n borderColor = \"transparent\";\n break;\n case \"error\":\n backgroundColor = inputColors.bg;\n borderColor = alertBorder;\n borderStyle = \"solid\";\n labelColor = alertText;\n iconColor = alertText;\n break;\n case \"disable\":\n backgroundColor = inputColors.bgDisable;\n borderColor = inputColors.borderDisable;\n labelColor = inputColors.textDisable;\n descriptionColor = inputColors.textDisable;\n iconColor = inputColors.textDisable;\n break;\n }\n\n const rootGap = state === \"error\" ? sizeStyles.errorGap : 0;\n\n const renderInner = () => {\n if (state === \"uploaded\" || state === \"uploadedHover\") {\n return (\n <>\n <Box\n as=\"img\"\n src={preview || undefined}\n alt={value?.filename ?? \"Uploaded image\"}\n position=\"absolute\"\n top={0}\n left={0}\n right={0}\n bottom={0}\n borderRadius={sizeStyles.radius}\n resizeMode=\"cover\"\n {...(isWeb && { width: \"100%\", height: \"100%\" })}\n style={isWeb ? { objectFit: \"cover\" } : undefined}\n />\n {state === \"uploadedHover\" && !disabled && (\n <>\n <Box\n position=\"absolute\"\n top={0}\n left={0}\n right={0}\n bottom={0}\n backgroundColor={scrim}\n borderRadius={sizeStyles.radius}\n {...(isWeb && { \"aria-hidden\": \"true\" })}\n />\n <Box\n position=\"relative\"\n width={sizeStyles.iconSize}\n height={sizeStyles.iconSize}\n alignItems=\"center\"\n justifyContent=\"center\"\n zIndex={1}\n pointerEvents=\"none\"\n {...(isWeb && { \"aria-hidden\": \"true\" })}\n >\n <TrashCan size={sizeStyles.iconSize} color=\"#ffffff\" />\n </Box>\n </>\n )}\n </>\n );\n }\n\n return (\n <>\n {state === \"uploading\" ? (\n <Spinner\n size={size}\n color={theme.colors.content.brand.primary}\n {...(isWeb && { \"aria-hidden\": \"true\" })}\n />\n ) : (\n <Box {...(isWeb && { \"aria-hidden\": \"true\" })}>\n <Image size={sizeStyles.iconSize} color={iconColor} />\n </Box>\n )}\n\n {placeholder && (\n <Text\n data-testid=\"image-uploader__placeholder\"\n color={labelColor}\n fontSize={sizeStyles.labelFontSize}\n lineHeight={sizeStyles.labelLineHeight}\n fontWeight=\"500\"\n textAlign=\"center\"\n numberOfLines={1}\n style={\n isWeb\n ? {\n maxWidth: \"100%\",\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n whiteSpace: \"nowrap\",\n }\n : undefined\n }\n >\n {state === \"uploading\" ? uploadingPlaceholder : placeholder}\n </Text>\n )}\n\n {description &&\n wideView &&\n state !== \"uploading\" &&\n state !== \"error\" &&\n (typeof description === \"string\" ? (\n <Text\n data-testid=\"image-uploader__description\"\n color={descriptionColor}\n fontSize={sizeStyles.descriptionFontSize}\n lineHeight={sizeStyles.descriptionLineHeight}\n textAlign=\"center\"\n numberOfLines={1}\n style={\n isWeb\n ? {\n maxWidth: \"100%\",\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n whiteSpace: \"nowrap\",\n }\n : undefined\n }\n {...(isWeb && { id: descriptionId })}\n >\n {description}\n </Text>\n ) : (\n <Box\n data-testid=\"image-uploader__description\"\n {...(isWeb && { id: descriptionId })}\n >\n {description}\n </Box>\n ))}\n </>\n );\n };\n\n // Web-only DOM event handlers — stripped on native.\n const webOnlyHandlers = isWeb\n ? {\n onMouseEnter: () => {\n if (disabled || loading) return;\n if (preview) setIsPreviewHover(true);\n else setIsHover(true);\n },\n onMouseLeave: () => {\n if (preview) setIsPreviewHover(false);\n else setIsHover(false);\n },\n onFocus: (e: React.FocusEvent<HTMLElement>) => {\n if (disabled || loading || preview) return;\n // Only show the focus visual state for keyboard focus, not for\n // pointer focus (e.g. after the native file picker is cancelled).\n if (\n typeof e.target?.matches === \"function\" &&\n !e.target.matches(\":focus-visible\")\n ) {\n return;\n }\n setIsFocus(true);\n },\n onBlur: () => setIsFocus(false),\n onDragOver: handleDragOver,\n onDragLeave: handleDragLeave,\n onDrop: handleDrop,\n }\n : {};\n\n // Web-only CSS props — dropped on native to avoid style warnings.\n const webOnlyStyle = isWeb\n ? {\n boxSizing: \"border-box\" as const,\n display: \"flex\" as const,\n cursor: (disabled || loading\n ? \"not-allowed\"\n : \"pointer\") as React.CSSProperties[\"cursor\"],\n outline: \"none\",\n transition: \"background-color 0.15s ease, border-color 0.15s ease\",\n }\n : {};\n\n const buttonAriaLabel = preview\n ? `Remove image${value?.filename ? `: ${value.filename}` : \"\"}`\n : state === \"uploading\"\n ? uploadingPlaceholder\n : placeholder;\n\n const describedBy =\n [\n state === \"error\" && errorMessage ? errorId : null,\n description && wideView && state !== \"uploading\" && state !== \"error\"\n ? descriptionId\n : null,\n ]\n .filter(Boolean)\n .join(\" \") || undefined;\n\n return (\n <Box\n data-testid=\"image-uploader\"\n flexDirection=\"column\"\n gap={rootGap}\n alignItems=\"flex-start\"\n width={wideView ? \"100%\" : undefined}\n maxWidth={wideView ? sizeStyles.wideWidth : undefined}\n >\n {isWeb && (\n <input\n type=\"file\"\n ref={fileInputRef}\n accept={accept}\n onChange={handleFileChange}\n style={{ display: \"none\" }}\n disabled={disabled}\n tabIndex={-1}\n aria-hidden=\"true\"\n data-testid=\"image-uploader__input\"\n />\n )}\n\n <Box\n as={isWeb ? \"button\" : \"div\"}\n disabled={disabled || loading}\n data-testid=\"image-uploader__button\"\n onPress={handleClick}\n {...(isWeb && {\n \"aria-label\": buttonAriaLabel,\n \"aria-busy\": loading || undefined,\n \"aria-invalid\": state === \"error\" || undefined,\n \"aria-describedby\": describedBy,\n })}\n {...webOnlyHandlers}\n position=\"relative\"\n width={wideView ? \"100%\" : sizeStyles.box}\n maxWidth={wideView ? sizeStyles.wideWidth : undefined}\n height={sizeStyles.box}\n flexDirection=\"column\"\n alignItems=\"center\"\n justifyContent=\"center\"\n gap={sizeStyles.gap}\n padding={state === \"uploaded\" ? 0 : sizeStyles.padding}\n borderWidth={sizeStyles.borderWidth}\n borderStyle={borderStyle}\n borderColor={borderColor}\n borderRadius={sizeStyles.radius}\n backgroundColor={backgroundColor}\n overflow=\"hidden\"\n style={webOnlyStyle}\n >\n {renderInner()}\n </Box>\n\n {state === \"error\" && errorMessage && (\n <Text\n data-testid=\"image-uploader__error\"\n color={alertText}\n fontSize={sizeStyles.errorFontSize}\n lineHeight={sizeStyles.errorLineHeight}\n {...(isWeb && { id: errorId, role: \"alert\" })}\n >\n {errorMessage}\n </Text>\n )}\n </Box>\n );\n }\n);\n\nImageUploader.displayName = \"ImageUploader\";\n","import React from \"react\";\nimport {\n View,\n Pressable,\n Image,\n ViewStyle,\n ImageStyle,\n DimensionValue,\n AnimatableNumericValue,\n} from \"react-native\";\nimport { BoxProps } from \"@xsolla/xui-primitives-core\";\n\nexport const Box: React.FC<BoxProps> = ({\n children,\n onPress,\n onLayout,\n onMoveShouldSetResponder,\n onResponderGrant,\n onResponderMove,\n onResponderRelease,\n onResponderTerminate,\n backgroundColor,\n borderColor,\n borderWidth,\n borderBottomWidth,\n borderBottomColor,\n borderTopWidth,\n borderTopColor,\n borderLeftWidth,\n borderLeftColor,\n borderRightWidth,\n borderRightColor,\n borderRadius,\n borderStyle,\n height,\n padding,\n paddingHorizontal,\n paddingVertical,\n margin,\n marginTop,\n marginBottom,\n marginLeft,\n marginRight,\n flexDirection,\n alignItems,\n justifyContent,\n position,\n top,\n bottom,\n left,\n right,\n width,\n minWidth,\n minHeight,\n maxWidth,\n maxHeight,\n flex,\n overflow,\n zIndex,\n hoverStyle,\n pressStyle,\n style,\n \"data-testid\": dataTestId,\n testID,\n as,\n src,\n alt,\n ...rest\n}) => {\n const getContainerStyle = (pressed?: boolean): ViewStyle => ({\n backgroundColor:\n pressed && pressStyle?.backgroundColor\n ? pressStyle.backgroundColor\n : backgroundColor,\n borderColor,\n borderWidth,\n borderBottomWidth,\n borderBottomColor,\n borderTopWidth,\n borderTopColor,\n borderLeftWidth,\n borderLeftColor,\n borderRightWidth,\n borderRightColor,\n borderRadius: borderRadius as AnimatableNumericValue,\n borderStyle: borderStyle as ViewStyle[\"borderStyle\"],\n overflow,\n zIndex,\n height: height as DimensionValue,\n width: width as DimensionValue,\n minWidth: minWidth as DimensionValue,\n minHeight: minHeight as DimensionValue,\n maxWidth: maxWidth as DimensionValue,\n maxHeight: maxHeight as DimensionValue,\n padding: padding as DimensionValue,\n paddingHorizontal: paddingHorizontal as DimensionValue,\n paddingVertical: paddingVertical as DimensionValue,\n margin: margin as DimensionValue,\n marginTop: marginTop as DimensionValue,\n marginBottom: marginBottom as DimensionValue,\n marginLeft: marginLeft as DimensionValue,\n marginRight: marginRight as DimensionValue,\n flexDirection,\n alignItems,\n justifyContent,\n position: position as ViewStyle[\"position\"],\n top: top as DimensionValue,\n bottom: bottom as DimensionValue,\n left: left as DimensionValue,\n right: right as DimensionValue,\n flex,\n ...(style as ViewStyle),\n });\n\n const finalTestID = dataTestId || testID;\n\n // Destructure and drop web-only props from rest before passing to RN components\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const {\n role,\n tabIndex,\n onKeyDown,\n onKeyUp,\n \"aria-label\": _ariaLabel,\n \"aria-labelledby\": _ariaLabelledBy,\n \"aria-current\": _ariaCurrent,\n \"aria-disabled\": _ariaDisabled,\n \"aria-live\": _ariaLive,\n className,\n \"data-testid\": _dataTestId,\n ...nativeRest\n } = rest as Record<string, unknown>;\n\n // Handle as=\"img\" for React Native\n if (as === \"img\" && src) {\n const imageStyle: ImageStyle = {\n width: width as DimensionValue,\n height: height as DimensionValue,\n borderRadius: borderRadius as number,\n position: position as ImageStyle[\"position\"],\n top: top as DimensionValue,\n bottom: bottom as DimensionValue,\n left: left as DimensionValue,\n right: right as DimensionValue,\n ...(style as ImageStyle),\n };\n\n return (\n <Image\n source={{ uri: src }}\n style={imageStyle}\n testID={finalTestID}\n resizeMode=\"cover\"\n {...nativeRest}\n />\n );\n }\n\n if (onPress) {\n return (\n <Pressable\n onPress={onPress}\n onLayout={onLayout}\n onMoveShouldSetResponder={onMoveShouldSetResponder}\n onResponderGrant={onResponderGrant}\n onResponderMove={onResponderMove}\n onResponderRelease={onResponderRelease}\n onResponderTerminate={onResponderTerminate}\n style={({ pressed }) => getContainerStyle(pressed)}\n testID={finalTestID}\n {...nativeRest}\n >\n {children}\n </Pressable>\n );\n }\n\n return (\n <View\n style={getContainerStyle()}\n testID={finalTestID}\n onLayout={onLayout}\n onMoveShouldSetResponder={onMoveShouldSetResponder}\n onResponderGrant={onResponderGrant}\n onResponderMove={onResponderMove}\n onResponderRelease={onResponderRelease}\n onResponderTerminate={onResponderTerminate}\n {...nativeRest}\n >\n {children}\n </View>\n );\n};\n","import React from \"react\";\nimport {\n Text as RNText,\n TextStyle,\n AccessibilityRole,\n StyleSheet,\n} from \"react-native\";\nimport { TextProps } from \"@xsolla/xui-primitives-core\";\n\nconst roleMap: Record<string, AccessibilityRole> = {\n alert: \"alert\",\n heading: \"header\",\n button: \"button\",\n link: \"link\",\n text: \"text\",\n};\n\nconst parseNumericValue = (\n value: string | number | undefined\n): number | undefined => {\n if (value === undefined) return undefined;\n if (typeof value === \"number\") return value;\n const parsed = parseFloat(value);\n return isNaN(parsed) ? undefined : parsed;\n};\n\nexport const Text: React.FC<TextProps> = ({\n children,\n color,\n fontSize,\n fontWeight,\n fontFamily,\n textAlign,\n lineHeight,\n numberOfLines,\n id,\n role,\n style: styleProp,\n ...props\n}) => {\n let resolvedFontFamily = fontFamily\n ? fontFamily.split(\",\")[0].replace(/['\"]/g, \"\").trim()\n : undefined;\n\n if (\n resolvedFontFamily === \"Pilat Wide\" ||\n resolvedFontFamily === \"Pilat Wide Bold\" ||\n resolvedFontFamily === \"Aktiv Grotesk\"\n ) {\n resolvedFontFamily = undefined;\n }\n\n const incomingStyle = StyleSheet.flatten(styleProp) as TextStyle | undefined;\n\n const baseStyle: TextStyle = {\n color: color ?? incomingStyle?.color,\n fontSize: typeof fontSize === \"number\" ? fontSize : undefined,\n fontWeight: fontWeight as TextStyle[\"fontWeight\"],\n fontFamily: resolvedFontFamily,\n textDecorationLine: props.textDecoration as TextStyle[\"textDecorationLine\"],\n textAlign: textAlign ?? incomingStyle?.textAlign,\n lineHeight: parseNumericValue(lineHeight ?? incomingStyle?.lineHeight),\n marginTop: parseNumericValue(\n incomingStyle?.marginTop as number | string | undefined\n ),\n marginBottom: parseNumericValue(\n incomingStyle?.marginBottom as number | string | undefined\n ),\n };\n\n const accessibilityRole = role ? roleMap[role] : undefined;\n\n return (\n <RNText\n style={baseStyle}\n numberOfLines={numberOfLines}\n testID={id}\n accessibilityRole={accessibilityRole}\n >\n {children}\n </RNText>\n );\n};\n","export * from \"./Box\";\nexport * from \"./Text\";\nexport * from \"./Spinner\";\nexport * from \"./Icon\";\nexport * from \"./Divider\";\nexport * from \"./Input\";\nexport * from \"./TextArea\";\nexport * from \"./LinearGradient\";\n\nexport const isWeb = false;\nexport const isNative = true;\n"],"mappings":"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","names":["jsx","Image","jsx"]}
1
+ {"version":3,"sources":["../../src/ImageUploader.tsx","../../../../foundation/primitives-native/src/Box.tsx","../../../../foundation/primitives-native/src/Text.tsx","../../../../foundation/primitives-native/src/index.tsx"],"sourcesContent":["import React, { useRef, useState, useEffect, forwardRef } from \"react\";\n// @ts-expect-error - this will be resolved at build time\nimport { Box, Text, isWeb } from \"@xsolla/xui-primitives\";\nimport {\n useId,\n useResolvedTheme,\n type ThemeOverrideProps,\n} from \"@xsolla/xui-core\";\nimport { Image, TrashCan } from \"@xsolla/xui-icons-base\";\nimport { Spinner } from \"@xsolla/xui-spinner\";\n\nexport type ImageUploaderSize = \"xl\" | \"lg\" | \"md\" | \"sm\" | \"xs\";\n\n/** Controlled value shape. */\nexport interface ImageUploaderValue {\n filename?: string;\n url?: string;\n}\n\n/**\n * Normalized file shape produced by the platform picker / drag-drop pipeline.\n * - `uri` is a data-URL on web and a file URI on native.\n * - On web, the original `File` is passed through for consumers that need it\n * (e.g. to upload via FormData / fetch).\n */\nexport type ImageUploaderFile = {\n name: string;\n size: number;\n uri: string;\n mimeType?: string;\n /** The original DOM File (web only) */\n file?: File;\n};\n\nexport interface ImageUploaderProps extends ThemeOverrideProps {\n /** Size of the uploader. Figma default is `xl`. */\n size?: ImageUploaderSize;\n /** Placeholder shown under the icon. Accepts a string or a custom React node. */\n placeholder?: React.ReactNode;\n /** Placeholder shown while the file is uploading. Accepts a string or a custom React node. */\n uploadingPlaceholder?: React.ReactNode;\n /** Description below the placeholder. Only rendered when `wideView` is true. Accepts a string or a custom React node. */\n description?: React.ReactNode;\n /** Error message — when provided, component renders in the error state. */\n errorMessage?: string;\n /** Wide view (horizontal layout). When true, the box stretches to its parent's full width. */\n wideView?: boolean;\n /** Disabled state. */\n disabled?: boolean;\n /** Controlled loading state (shows spinner + \"Uploading\" label). */\n loading?: boolean;\n\n /** Controlled value. When provided, the component reflects this value. */\n value?: ImageUploaderValue | null;\n\n /**\n * Fires when the user picks (or drops) a file. Use this to perform the\n * actual upload — the component itself does no I/O.\n *\n * If the handler returns a `Promise`:\n * - The component automatically shows the uploading state (spinner +\n * `uploadingPlaceholder`) until the promise settles.\n * - If the promise resolves to an `ImageUploaderValue` (`{url, filename?}`)\n * or a `string` URL, the component automatically calls `onChange(value)`\n * with that value. The string form is sugar for `{url: <string>}`.\n * - If the promise rejects, the component calls `onChange(null, error)`.\n *\n * For finer-grained control, pass `loading` explicitly instead.\n */\n onUpload?: (\n file: ImageUploaderFile\n ) => void | Promise<ImageUploaderValue | string | void>;\n /**\n * Fires when the displayed value changes — on file pick (with a local\n * data-URL preview), after upload resolves (with the server value), or on\n * remove (`null`). The optional second argument carries any error thrown by\n * the consumer's `onUpload`.\n */\n onChange?: (value: ImageUploaderValue | null, error?: Error) => void;\n /** Fires when the user removes the image (trash click / clear). */\n onDelete?: () => void;\n\n /**\n * Accepted file types — passed through to the hidden `<input accept>` as a\n * picker hint (web only — ignored on native). Standard HTML syntax:\n * comma-separated list of MIME types (`image/png`), MIME wildcards\n * (`image/*`), or file extensions (`.png`). Defaults to `image/*`.\n *\n * No runtime validation is performed — the consumer's `onUpload` (or the\n * backend it calls) is responsible for accepting/rejecting files.\n */\n accept?: string;\n /**\n * Native file picker hook. Required on native (no DOM `<input type=\"file\">`).\n * On web, omit this and the component falls back to a hidden file input.\n */\n openPicker?: () => Promise<ImageUploaderFile | null>;\n}\n\ntype InternalState =\n | \"default\"\n | \"hover\"\n | \"focus\"\n | \"uploading\"\n | \"uploaded\"\n | \"uploadedHover\"\n | \"error\"\n | \"disable\";\n\nexport const ImageUploader = forwardRef<HTMLInputElement, ImageUploaderProps>(\n (\n {\n size = \"xl\",\n placeholder = \"Upload\",\n uploadingPlaceholder = \"Uploading\",\n description,\n errorMessage,\n wideView = false,\n accept = \"image/*\",\n openPicker,\n value,\n onUpload,\n onChange,\n onDelete,\n disabled = false,\n loading = false,\n themeMode,\n themeProductContext,\n },\n ref\n ) => {\n const { theme } = useResolvedTheme({ themeMode, themeProductContext });\n const fileInputRef = useRef<HTMLInputElement>(null);\n const isControlled = value !== undefined;\n const [internalPreview, setInternalPreview] = useState<string | null>(null);\n const [isAutoUploading, setIsAutoUploading] = useState(false);\n // Captures errors thrown by the consumer's `onUpload`. Surfaced via the\n // component's normal error UI when no explicit `errorMessage` prop is set.\n const [autoError, setAutoError] = useState<string | null>(null);\n const [isHover, setIsHover] = useState(false);\n const [isFocus, setIsFocus] = useState(false);\n const [isPreviewHover, setIsPreviewHover] = useState(false);\n // Touch / native devices have no hover, so the trash overlay would never\n // appear. Detect once on mount and force `uploadedHover` for those cases.\n const [isHoverless] = useState(\n () =>\n !isWeb ||\n (typeof window !== \"undefined\" &&\n !!window.matchMedia?.(\"(hover: none)\").matches)\n );\n\n const reactId = useId();\n const baseId = `image-uploader-${reactId.replace(/[^a-zA-Z0-9-]/g, \"\")}`;\n const descriptionId = `${baseId}-description`;\n const errorId = `${baseId}-error`;\n\n const preview = isControlled ? (value?.url ?? null) : internalPreview;\n\n useEffect(() => {\n if (!isControlled && value === null) setInternalPreview(null);\n }, [isControlled, value]);\n\n React.useImperativeHandle(\n ref,\n () => fileInputRef.current as HTMLInputElement,\n []\n );\n\n const sizeStyles = theme.sizing.imageUploader(size);\n const inputColors = theme.colors.control.input;\n const focusColors = theme.colors.control.focus;\n const alertBorder = theme.colors.control.alert.border;\n const alertText = theme.colors.content.alert.primary;\n const scrim = theme.colors.layer.scrim;\n\n // Resolved error message: explicit prop wins, fall back to caught upload error.\n const resolvedError = errorMessage || autoError;\n const hasError = !!resolvedError;\n\n let state: InternalState;\n if (disabled) state = \"disable\";\n else if (loading || isAutoUploading) state = \"uploading\";\n else if (hasError) state = \"error\";\n else if (preview)\n state = isPreviewHover || isHoverless ? \"uploadedHover\" : \"uploaded\";\n else if (isFocus) state = \"focus\";\n else if (isHover) state = \"hover\";\n else state = \"default\";\n\n const emitFile = (img: ImageUploaderFile) => {\n // Clear any prior error from a previous upload attempt.\n setAutoError(null);\n if (!isControlled) setInternalPreview(img.uri);\n onChange?.({\n filename: img.name,\n url: img.uri,\n });\n const result = onUpload?.(img);\n // If the consumer's onUpload returns a Promise:\n // - show the uploading state until it settles\n // - on resolve with a value, treat it as the canonical onChange payload\n // - on reject, surface the error internally AND via onChange(null, error)\n if (result && typeof (result as Promise<unknown>).then === \"function\") {\n setIsAutoUploading(true);\n Promise.resolve(result)\n .then((uploaded) => {\n // Accept either an ImageUploaderValue ({url, filename?}) or a\n // bare string URL as sugar for {url: <string>}.\n let next: ImageUploaderValue | null = null;\n if (typeof uploaded === \"string\") {\n next = { url: uploaded };\n } else if (\n uploaded &&\n typeof uploaded === \"object\" &&\n \"url\" in uploaded\n ) {\n next = uploaded as ImageUploaderValue;\n }\n if (next) {\n if (!isControlled) setInternalPreview(next.url ?? null);\n onChange?.(next);\n }\n })\n .catch((err: Error) => {\n // Surface internally so the error UI lights up without the\n // consumer having to wire up local state.\n setAutoError(err?.message || \"Upload failed\");\n // Reset preview so we don't keep showing the failed pick.\n if (!isControlled) setInternalPreview(null);\n onChange?.(null, err);\n })\n .finally(() => setIsAutoUploading(false));\n }\n };\n\n const handleWebFile = (file: File) => {\n const reader = new FileReader();\n reader.onload = (e) => {\n const uri = e.target?.result as string;\n // Mutate the DOM File so it satisfies BOTH the DOM File interface (for\n // direct use in FormData / fetch / .size checks) and ImageUploaderFile\n // (with .uri / .mimeType added). `.file` self-references for back-compat\n // with consumers that previously dereferenced `imageFile.file`.\n const enriched = file as File & ImageUploaderFile;\n (enriched as { uri?: string }).uri = uri;\n (enriched as { mimeType?: string }).mimeType = file.type;\n (enriched as { file?: File }).file = enriched;\n emitFile(enriched);\n };\n reader.readAsDataURL(file);\n };\n\n const handleClick = async () => {\n if (disabled || loading) return;\n if (preview) {\n removeImage();\n return;\n }\n if (openPicker) {\n const picked = await openPicker();\n if (picked) emitFile(picked);\n return;\n }\n if (isWeb) {\n fileInputRef.current?.click();\n }\n };\n\n const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n const file = e.target.files?.[0];\n e.target.value = \"\";\n if (file) handleWebFile(file);\n };\n\n const handleDragOver = (e: React.DragEvent) => {\n e.preventDefault();\n e.stopPropagation();\n if (!disabled && !loading && !preview) setIsHover(true);\n };\n\n const handleDragLeave = (e: React.DragEvent) => {\n e.preventDefault();\n e.stopPropagation();\n setIsHover(false);\n };\n\n const handleDrop = (e: React.DragEvent) => {\n e.preventDefault();\n e.stopPropagation();\n setIsHover(false);\n if (disabled || loading || preview) return;\n const file = e.dataTransfer.files?.[0];\n if (file) handleWebFile(file);\n };\n\n const removeImage = (e?: { stopPropagation?: () => void }) => {\n e?.stopPropagation?.();\n setAutoError(null);\n if (!isControlled) setInternalPreview(null);\n setIsPreviewHover(false);\n onDelete?.();\n onChange?.(null);\n if (isWeb && fileInputRef.current) fileInputRef.current.value = \"\";\n };\n\n let backgroundColor: string = inputColors.bg;\n let borderColor: string = inputColors.border;\n let borderStyle: \"dashed\" | \"solid\" = \"dashed\";\n let labelColor: string = inputColors.text;\n let descriptionColor: string = inputColors.placeholder;\n let iconColor: string = inputColors.text;\n\n switch (state) {\n case \"hover\":\n backgroundColor = inputColors.bgHover;\n borderColor = inputColors.borderHover;\n break;\n case \"focus\":\n case \"uploading\":\n backgroundColor = focusColors.bg;\n borderColor = focusColors.border;\n break;\n case \"uploaded\":\n case \"uploadedHover\":\n backgroundColor = \"transparent\";\n borderColor = \"transparent\";\n break;\n case \"error\":\n backgroundColor = inputColors.bg;\n borderColor = alertBorder;\n borderStyle = \"solid\";\n labelColor = alertText;\n iconColor = alertText;\n break;\n case \"disable\":\n backgroundColor = inputColors.bgDisable;\n borderColor = inputColors.borderDisable;\n labelColor = inputColors.textDisable;\n descriptionColor = inputColors.textDisable;\n iconColor = inputColors.textDisable;\n break;\n }\n\n const rootGap = state === \"error\" ? sizeStyles.errorGap : 0;\n const placeholderContent =\n state === \"uploading\" ? uploadingPlaceholder : placeholder;\n\n const renderInner = () => {\n if (state === \"uploaded\" || state === \"uploadedHover\") {\n return (\n <>\n <Box\n as=\"img\"\n src={preview || undefined}\n alt={value?.filename ?? \"Uploaded image\"}\n position=\"absolute\"\n top={0}\n left={0}\n right={0}\n bottom={0}\n borderRadius={sizeStyles.radius}\n resizeMode=\"cover\"\n {...(isWeb && { width: \"100%\", height: \"100%\" })}\n style={isWeb ? { objectFit: \"cover\" } : undefined}\n />\n {state === \"uploadedHover\" && !disabled && (\n <>\n <Box\n position=\"absolute\"\n top={0}\n left={0}\n right={0}\n bottom={0}\n backgroundColor={scrim}\n borderRadius={sizeStyles.radius}\n {...(isWeb && { \"aria-hidden\": \"true\" })}\n />\n <Box\n position=\"relative\"\n width={sizeStyles.iconSize}\n height={sizeStyles.iconSize}\n alignItems=\"center\"\n justifyContent=\"center\"\n zIndex={1}\n pointerEvents=\"none\"\n {...(isWeb && { \"aria-hidden\": \"true\" })}\n >\n <TrashCan size={sizeStyles.iconSize} color=\"#ffffff\" />\n </Box>\n </>\n )}\n </>\n );\n }\n\n return (\n <>\n {state === \"uploading\" ? (\n <Spinner\n size={size}\n color={theme.colors.content.brand.primary}\n {...(isWeb && { \"aria-hidden\": \"true\" })}\n />\n ) : (\n <Box {...(isWeb && { \"aria-hidden\": \"true\" })}>\n <Image size={sizeStyles.iconSize} color={iconColor} />\n </Box>\n )}\n\n {placeholderContent &&\n (typeof placeholderContent === \"string\" ? (\n <Text\n data-testid=\"image-uploader__placeholder\"\n color={labelColor}\n fontSize={sizeStyles.labelFontSize}\n lineHeight={sizeStyles.labelLineHeight}\n fontWeight=\"500\"\n textAlign=\"center\"\n numberOfLines={1}\n style={\n isWeb\n ? {\n maxWidth: \"100%\",\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n whiteSpace: \"nowrap\",\n }\n : undefined\n }\n >\n {placeholderContent}\n </Text>\n ) : (\n <Box data-testid=\"image-uploader__placeholder\">\n {placeholderContent}\n </Box>\n ))}\n\n {description &&\n wideView &&\n state !== \"uploading\" &&\n state !== \"error\" &&\n (typeof description === \"string\" ? (\n <Text\n data-testid=\"image-uploader__description\"\n color={descriptionColor}\n fontSize={sizeStyles.descriptionFontSize}\n lineHeight={sizeStyles.descriptionLineHeight}\n textAlign=\"center\"\n numberOfLines={1}\n style={\n isWeb\n ? {\n maxWidth: \"100%\",\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n whiteSpace: \"nowrap\",\n }\n : undefined\n }\n {...(isWeb && { id: descriptionId })}\n >\n {description}\n </Text>\n ) : (\n <Box\n data-testid=\"image-uploader__description\"\n {...(isWeb && { id: descriptionId })}\n >\n {description}\n </Box>\n ))}\n </>\n );\n };\n\n // Web-only DOM event handlers — stripped on native.\n const webOnlyHandlers = isWeb\n ? {\n onMouseEnter: () => {\n if (disabled || loading) return;\n if (preview) setIsPreviewHover(true);\n else setIsHover(true);\n },\n onMouseLeave: () => {\n if (preview) setIsPreviewHover(false);\n else setIsHover(false);\n },\n onFocus: (e: React.FocusEvent<HTMLElement>) => {\n if (disabled || loading || preview) return;\n // Only show the focus visual state for keyboard focus, not for\n // pointer focus (e.g. after the native file picker is cancelled).\n if (\n typeof e.target?.matches === \"function\" &&\n !e.target.matches(\":focus-visible\")\n ) {\n return;\n }\n setIsFocus(true);\n },\n onBlur: () => setIsFocus(false),\n onDragOver: handleDragOver,\n onDragLeave: handleDragLeave,\n onDrop: handleDrop,\n }\n : {};\n\n // Web-only CSS props — dropped on native to avoid style warnings.\n const webOnlyStyle = isWeb\n ? {\n boxSizing: \"border-box\" as const,\n display: \"flex\" as const,\n cursor: (disabled || loading\n ? \"not-allowed\"\n : \"pointer\") as React.CSSProperties[\"cursor\"],\n outline: \"none\",\n transition: \"background-color 0.15s ease, border-color 0.15s ease\",\n }\n : {};\n\n // aria-label needs a string; fall back to defaults when placeholder/uploadingPlaceholder\n // are passed as React nodes.\n const buttonAriaLabel = preview\n ? `Remove image${value?.filename ? `: ${value.filename}` : \"\"}`\n : state === \"uploading\"\n ? typeof uploadingPlaceholder === \"string\"\n ? uploadingPlaceholder\n : \"Uploading\"\n : typeof placeholder === \"string\"\n ? placeholder\n : \"Upload\";\n\n const describedBy =\n [\n state === \"error\" && resolvedError ? errorId : null,\n description && wideView && state !== \"uploading\" && state !== \"error\"\n ? descriptionId\n : null,\n ]\n .filter(Boolean)\n .join(\" \") || undefined;\n\n return (\n <Box\n data-testid=\"image-uploader\"\n flexDirection=\"column\"\n gap={rootGap}\n alignItems=\"flex-start\"\n width={wideView ? \"100%\" : undefined}\n >\n {isWeb && (\n <input\n type=\"file\"\n ref={fileInputRef}\n accept={accept}\n onChange={handleFileChange}\n style={{ display: \"none\" }}\n disabled={disabled}\n tabIndex={-1}\n aria-hidden=\"true\"\n data-testid=\"image-uploader__input\"\n />\n )}\n\n <Box\n as={isWeb ? \"button\" : \"div\"}\n disabled={disabled || loading}\n data-testid=\"image-uploader__button\"\n onPress={handleClick}\n {...(isWeb && {\n \"aria-label\": buttonAriaLabel,\n \"aria-busy\": loading || undefined,\n \"aria-invalid\": state === \"error\" || undefined,\n \"aria-describedby\": describedBy,\n })}\n {...webOnlyHandlers}\n position=\"relative\"\n width={wideView ? \"100%\" : sizeStyles.box}\n height={sizeStyles.box}\n flexDirection=\"column\"\n alignItems=\"center\"\n justifyContent=\"center\"\n gap={sizeStyles.gap}\n padding={state === \"uploaded\" ? 0 : sizeStyles.padding}\n borderWidth={sizeStyles.borderWidth}\n borderStyle={borderStyle}\n borderColor={borderColor}\n borderRadius={sizeStyles.radius}\n backgroundColor={backgroundColor}\n overflow=\"hidden\"\n style={webOnlyStyle}\n >\n {renderInner()}\n </Box>\n\n {state === \"error\" && resolvedError && (\n <Text\n data-testid=\"image-uploader__error\"\n color={alertText}\n fontSize={sizeStyles.errorFontSize}\n lineHeight={sizeStyles.errorLineHeight}\n {...(isWeb && { id: errorId, role: \"alert\" })}\n >\n {resolvedError}\n </Text>\n )}\n </Box>\n );\n }\n);\n\nImageUploader.displayName = \"ImageUploader\";\n","import React from \"react\";\nimport {\n View,\n Pressable,\n Image,\n ViewStyle,\n ImageStyle,\n DimensionValue,\n AnimatableNumericValue,\n} from \"react-native\";\nimport { BoxProps } from \"@xsolla/xui-primitives-core\";\n\nexport const Box: React.FC<BoxProps> = ({\n children,\n onPress,\n onLayout,\n onMoveShouldSetResponder,\n onResponderGrant,\n onResponderMove,\n onResponderRelease,\n onResponderTerminate,\n backgroundColor,\n borderColor,\n borderWidth,\n borderBottomWidth,\n borderBottomColor,\n borderTopWidth,\n borderTopColor,\n borderLeftWidth,\n borderLeftColor,\n borderRightWidth,\n borderRightColor,\n borderRadius,\n borderStyle,\n height,\n padding,\n paddingHorizontal,\n paddingVertical,\n margin,\n marginTop,\n marginBottom,\n marginLeft,\n marginRight,\n flexDirection,\n alignItems,\n justifyContent,\n position,\n top,\n bottom,\n left,\n right,\n width,\n minWidth,\n minHeight,\n maxWidth,\n maxHeight,\n flex,\n overflow,\n zIndex,\n hoverStyle,\n pressStyle,\n style,\n \"data-testid\": dataTestId,\n testID,\n as,\n src,\n alt,\n ...rest\n}) => {\n const getContainerStyle = (pressed?: boolean): ViewStyle => ({\n backgroundColor:\n pressed && pressStyle?.backgroundColor\n ? pressStyle.backgroundColor\n : backgroundColor,\n borderColor,\n borderWidth,\n borderBottomWidth,\n borderBottomColor,\n borderTopWidth,\n borderTopColor,\n borderLeftWidth,\n borderLeftColor,\n borderRightWidth,\n borderRightColor,\n borderRadius: borderRadius as AnimatableNumericValue,\n borderStyle: borderStyle as ViewStyle[\"borderStyle\"],\n overflow,\n zIndex,\n height: height as DimensionValue,\n width: width as DimensionValue,\n minWidth: minWidth as DimensionValue,\n minHeight: minHeight as DimensionValue,\n maxWidth: maxWidth as DimensionValue,\n maxHeight: maxHeight as DimensionValue,\n padding: padding as DimensionValue,\n paddingHorizontal: paddingHorizontal as DimensionValue,\n paddingVertical: paddingVertical as DimensionValue,\n margin: margin as DimensionValue,\n marginTop: marginTop as DimensionValue,\n marginBottom: marginBottom as DimensionValue,\n marginLeft: marginLeft as DimensionValue,\n marginRight: marginRight as DimensionValue,\n flexDirection,\n alignItems,\n justifyContent,\n position: position as ViewStyle[\"position\"],\n top: top as DimensionValue,\n bottom: bottom as DimensionValue,\n left: left as DimensionValue,\n right: right as DimensionValue,\n flex,\n ...(style as ViewStyle),\n });\n\n const finalTestID = dataTestId || testID;\n\n // Destructure and drop web-only props from rest before passing to RN components\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const {\n role,\n tabIndex,\n onKeyDown,\n onKeyUp,\n \"aria-label\": _ariaLabel,\n \"aria-labelledby\": _ariaLabelledBy,\n \"aria-current\": _ariaCurrent,\n \"aria-disabled\": _ariaDisabled,\n \"aria-live\": _ariaLive,\n className,\n \"data-testid\": _dataTestId,\n ...nativeRest\n } = rest as Record<string, unknown>;\n\n // Handle as=\"img\" for React Native\n if (as === \"img\" && src) {\n const imageStyle: ImageStyle = {\n width: width as DimensionValue,\n height: height as DimensionValue,\n borderRadius: borderRadius as number,\n position: position as ImageStyle[\"position\"],\n top: top as DimensionValue,\n bottom: bottom as DimensionValue,\n left: left as DimensionValue,\n right: right as DimensionValue,\n ...(style as ImageStyle),\n };\n\n return (\n <Image\n source={{ uri: src }}\n style={imageStyle}\n testID={finalTestID}\n resizeMode=\"cover\"\n {...nativeRest}\n />\n );\n }\n\n if (onPress) {\n return (\n <Pressable\n onPress={onPress}\n onLayout={onLayout}\n onMoveShouldSetResponder={onMoveShouldSetResponder}\n onResponderGrant={onResponderGrant}\n onResponderMove={onResponderMove}\n onResponderRelease={onResponderRelease}\n onResponderTerminate={onResponderTerminate}\n style={({ pressed }) => getContainerStyle(pressed)}\n testID={finalTestID}\n {...nativeRest}\n >\n {children}\n </Pressable>\n );\n }\n\n return (\n <View\n style={getContainerStyle()}\n testID={finalTestID}\n onLayout={onLayout}\n onMoveShouldSetResponder={onMoveShouldSetResponder}\n onResponderGrant={onResponderGrant}\n onResponderMove={onResponderMove}\n onResponderRelease={onResponderRelease}\n onResponderTerminate={onResponderTerminate}\n {...nativeRest}\n >\n {children}\n </View>\n );\n};\n","import React from \"react\";\nimport {\n Text as RNText,\n TextStyle,\n AccessibilityRole,\n StyleSheet,\n} from \"react-native\";\nimport { TextProps } from \"@xsolla/xui-primitives-core\";\n\nconst roleMap: Record<string, AccessibilityRole> = {\n alert: \"alert\",\n heading: \"header\",\n button: \"button\",\n link: \"link\",\n text: \"text\",\n};\n\nconst parseNumericValue = (\n value: string | number | undefined\n): number | undefined => {\n if (value === undefined) return undefined;\n if (typeof value === \"number\") return value;\n const parsed = parseFloat(value);\n return isNaN(parsed) ? undefined : parsed;\n};\n\nexport const Text: React.FC<TextProps> = ({\n children,\n color,\n fontSize,\n fontWeight,\n fontFamily,\n textAlign,\n lineHeight,\n numberOfLines,\n id,\n role,\n style: styleProp,\n ...props\n}) => {\n let resolvedFontFamily = fontFamily\n ? fontFamily.split(\",\")[0].replace(/['\"]/g, \"\").trim()\n : undefined;\n\n if (\n resolvedFontFamily === \"Pilat Wide\" ||\n resolvedFontFamily === \"Pilat Wide Bold\" ||\n resolvedFontFamily === \"Aktiv Grotesk\"\n ) {\n resolvedFontFamily = undefined;\n }\n\n const incomingStyle = StyleSheet.flatten(styleProp) as TextStyle | undefined;\n\n const baseStyle: TextStyle = {\n color: color ?? incomingStyle?.color,\n fontSize: typeof fontSize === \"number\" ? fontSize : undefined,\n fontWeight: fontWeight as TextStyle[\"fontWeight\"],\n fontFamily: resolvedFontFamily,\n textDecorationLine: props.textDecoration as TextStyle[\"textDecorationLine\"],\n textAlign: textAlign ?? incomingStyle?.textAlign,\n lineHeight: parseNumericValue(lineHeight ?? incomingStyle?.lineHeight),\n marginTop: parseNumericValue(\n incomingStyle?.marginTop as number | string | undefined\n ),\n marginBottom: parseNumericValue(\n incomingStyle?.marginBottom as number | string | undefined\n ),\n };\n\n const accessibilityRole = role ? roleMap[role] : undefined;\n\n return (\n <RNText\n style={baseStyle}\n numberOfLines={numberOfLines}\n testID={id}\n accessibilityRole={accessibilityRole}\n >\n {children}\n </RNText>\n );\n};\n","export * from \"./Box\";\nexport * from \"./Text\";\nexport * from \"./Spinner\";\nexport * from \"./Icon\";\nexport * from \"./Divider\";\nexport * from \"./Input\";\nexport * from \"./TextArea\";\nexport * from \"./LinearGradient\";\n\nexport const isWeb = false;\nexport const isNative = true;\n"],"mappings":"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","names":["jsx","Image","jsx"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xsolla/xui-image-uploader",
3
- "version": "0.149.0",
3
+ "version": "0.150.0",
4
4
  "main": "./web/index.js",
5
5
  "module": "./web/index.mjs",
6
6
  "types": "./web/index.d.ts",
@@ -13,10 +13,10 @@
13
13
  "test:coverage": "vitest run --coverage"
14
14
  },
15
15
  "dependencies": {
16
- "@xsolla/xui-core": "0.149.0",
17
- "@xsolla/xui-icons-base": "0.149.0",
18
- "@xsolla/xui-primitives-core": "0.149.0",
19
- "@xsolla/xui-spinner": "0.149.0"
16
+ "@xsolla/xui-core": "0.150.0",
17
+ "@xsolla/xui-icons-base": "0.150.0",
18
+ "@xsolla/xui-primitives-core": "0.150.0",
19
+ "@xsolla/xui-spinner": "0.150.0"
20
20
  },
21
21
  "peerDependencies": {
22
22
  "react": ">=16.8.0"
package/web/index.d.mts CHANGED
@@ -4,7 +4,7 @@ import { ThemeOverrideProps } from '@xsolla/xui-core';
4
4
  type ImageUploaderSize = "xl" | "lg" | "md" | "sm" | "xs";
5
5
  /** Controlled value shape. */
6
6
  interface ImageUploaderValue {
7
- filename: string;
7
+ filename?: string;
8
8
  url?: string;
9
9
  }
10
10
  /**
@@ -24,15 +24,15 @@ type ImageUploaderFile = {
24
24
  interface ImageUploaderProps extends ThemeOverrideProps {
25
25
  /** Size of the uploader. Figma default is `xl`. */
26
26
  size?: ImageUploaderSize;
27
- /** Placeholder text shown under the icon (e.g. "Upload" or filename in error). */
28
- placeholder?: string;
29
- /** Placeholder text shown while the file is uploading. */
30
- uploadingPlaceholder?: string;
27
+ /** Placeholder shown under the icon. Accepts a string or a custom React node. */
28
+ placeholder?: React.ReactNode;
29
+ /** Placeholder shown while the file is uploading. Accepts a string or a custom React node. */
30
+ uploadingPlaceholder?: React.ReactNode;
31
31
  /** Description below the placeholder. Only rendered when `wideView` is true. Accepts a string or a custom React node. */
32
32
  description?: React.ReactNode;
33
33
  /** Error message — when provided, component renders in the error state. */
34
34
  errorMessage?: string;
35
- /** Wide view (horizontal layout). When true, box uses `wideWidth` from sizing. */
35
+ /** Wide view (horizontal layout). When true, the box stretches to its parent's full width. */
36
36
  wideView?: boolean;
37
37
  /** Disabled state. */
38
38
  disabled?: boolean;
@@ -43,16 +43,36 @@ interface ImageUploaderProps extends ThemeOverrideProps {
43
43
  /**
44
44
  * Fires when the user picks (or drops) a file. Use this to perform the
45
45
  * actual upload — the component itself does no I/O.
46
+ *
47
+ * If the handler returns a `Promise`:
48
+ * - The component automatically shows the uploading state (spinner +
49
+ * `uploadingPlaceholder`) until the promise settles.
50
+ * - If the promise resolves to an `ImageUploaderValue` (`{url, filename?}`)
51
+ * or a `string` URL, the component automatically calls `onChange(value)`
52
+ * with that value. The string form is sugar for `{url: <string>}`.
53
+ * - If the promise rejects, the component calls `onChange(null, error)`.
54
+ *
55
+ * For finer-grained control, pass `loading` explicitly instead.
46
56
  */
47
- onUpload?: (file: ImageUploaderFile) => void;
57
+ onUpload?: (file: ImageUploaderFile) => void | Promise<ImageUploaderValue | string | void>;
48
58
  /**
49
59
  * Fires when the displayed value changes — on file pick (with a local
50
- * data-URL preview) and on remove (`null`).
60
+ * data-URL preview), after upload resolves (with the server value), or on
61
+ * remove (`null`). The optional second argument carries any error thrown by
62
+ * the consumer's `onUpload`.
51
63
  */
52
- onChange?: (value: ImageUploaderValue | null) => void;
64
+ onChange?: (value: ImageUploaderValue | null, error?: Error) => void;
53
65
  /** Fires when the user removes the image (trash click / clear). */
54
66
  onDelete?: () => void;
55
- /** Accepted file types (web only — ignored on native). */
67
+ /**
68
+ * Accepted file types — passed through to the hidden `<input accept>` as a
69
+ * picker hint (web only — ignored on native). Standard HTML syntax:
70
+ * comma-separated list of MIME types (`image/png`), MIME wildcards
71
+ * (`image/*`), or file extensions (`.png`). Defaults to `image/*`.
72
+ *
73
+ * No runtime validation is performed — the consumer's `onUpload` (or the
74
+ * backend it calls) is responsible for accepting/rejecting files.
75
+ */
56
76
  accept?: string;
57
77
  /**
58
78
  * Native file picker hook. Required on native (no DOM `<input type="file">`).
package/web/index.d.ts CHANGED
@@ -4,7 +4,7 @@ import { ThemeOverrideProps } from '@xsolla/xui-core';
4
4
  type ImageUploaderSize = "xl" | "lg" | "md" | "sm" | "xs";
5
5
  /** Controlled value shape. */
6
6
  interface ImageUploaderValue {
7
- filename: string;
7
+ filename?: string;
8
8
  url?: string;
9
9
  }
10
10
  /**
@@ -24,15 +24,15 @@ type ImageUploaderFile = {
24
24
  interface ImageUploaderProps extends ThemeOverrideProps {
25
25
  /** Size of the uploader. Figma default is `xl`. */
26
26
  size?: ImageUploaderSize;
27
- /** Placeholder text shown under the icon (e.g. "Upload" or filename in error). */
28
- placeholder?: string;
29
- /** Placeholder text shown while the file is uploading. */
30
- uploadingPlaceholder?: string;
27
+ /** Placeholder shown under the icon. Accepts a string or a custom React node. */
28
+ placeholder?: React.ReactNode;
29
+ /** Placeholder shown while the file is uploading. Accepts a string or a custom React node. */
30
+ uploadingPlaceholder?: React.ReactNode;
31
31
  /** Description below the placeholder. Only rendered when `wideView` is true. Accepts a string or a custom React node. */
32
32
  description?: React.ReactNode;
33
33
  /** Error message — when provided, component renders in the error state. */
34
34
  errorMessage?: string;
35
- /** Wide view (horizontal layout). When true, box uses `wideWidth` from sizing. */
35
+ /** Wide view (horizontal layout). When true, the box stretches to its parent's full width. */
36
36
  wideView?: boolean;
37
37
  /** Disabled state. */
38
38
  disabled?: boolean;
@@ -43,16 +43,36 @@ interface ImageUploaderProps extends ThemeOverrideProps {
43
43
  /**
44
44
  * Fires when the user picks (or drops) a file. Use this to perform the
45
45
  * actual upload — the component itself does no I/O.
46
+ *
47
+ * If the handler returns a `Promise`:
48
+ * - The component automatically shows the uploading state (spinner +
49
+ * `uploadingPlaceholder`) until the promise settles.
50
+ * - If the promise resolves to an `ImageUploaderValue` (`{url, filename?}`)
51
+ * or a `string` URL, the component automatically calls `onChange(value)`
52
+ * with that value. The string form is sugar for `{url: <string>}`.
53
+ * - If the promise rejects, the component calls `onChange(null, error)`.
54
+ *
55
+ * For finer-grained control, pass `loading` explicitly instead.
46
56
  */
47
- onUpload?: (file: ImageUploaderFile) => void;
57
+ onUpload?: (file: ImageUploaderFile) => void | Promise<ImageUploaderValue | string | void>;
48
58
  /**
49
59
  * Fires when the displayed value changes — on file pick (with a local
50
- * data-URL preview) and on remove (`null`).
60
+ * data-URL preview), after upload resolves (with the server value), or on
61
+ * remove (`null`). The optional second argument carries any error thrown by
62
+ * the consumer's `onUpload`.
51
63
  */
52
- onChange?: (value: ImageUploaderValue | null) => void;
64
+ onChange?: (value: ImageUploaderValue | null, error?: Error) => void;
53
65
  /** Fires when the user removes the image (trash click / clear). */
54
66
  onDelete?: () => void;
55
- /** Accepted file types (web only — ignored on native). */
67
+ /**
68
+ * Accepted file types — passed through to the hidden `<input accept>` as a
69
+ * picker hint (web only — ignored on native). Standard HTML syntax:
70
+ * comma-separated list of MIME types (`image/png`), MIME wildcards
71
+ * (`image/*`), or file extensions (`.png`). Defaults to `image/*`.
72
+ *
73
+ * No runtime validation is performed — the consumer's `onUpload` (or the
74
+ * backend it calls) is responsible for accepting/rejecting files.
75
+ */
56
76
  accept?: string;
57
77
  /**
58
78
  * Native file picker hook. Required on native (no DOM `<input type="file">`).