npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@xsolla/xui-image-uploader

v0.159.0

Published

A cross-platform React image uploader component supporting click-to-pick, drag-and-drop (web), controlled and uncontrolled usage, image preview with hover-to-remove, automatic loading state from async `onUpload`, error states, and a wide horizontal layout

Readme

ImageUploader

A cross-platform React image uploader component supporting click-to-pick, drag-and-drop (web), controlled and uncontrolled usage, image preview with hover-to-remove, automatic loading state from async onUpload, error states, and a wide horizontal layout.

Installation

npm install @xsolla/xui-image-uploader

Demo

Basic (Uncontrolled)

import * as React from "react";
import {
  ImageUploader,
  type ImageUploaderFile,
} from "@xsolla/xui-image-uploader";

export default function Basic() {
  const handleUpload = (file: ImageUploaderFile) => {
    console.log("Picked file:", file.name, file.size);
  };

  return <ImageUploader onUpload={handleUpload} />;
}

Controlled

import * as React from "react";
import {
  ImageUploader,
  type ImageUploaderValue,
} from "@xsolla/xui-image-uploader";

export default function Controlled() {
  const [value, setValue] = React.useState<ImageUploaderValue | null>(null);

  return (
    <ImageUploader
      value={value}
      onChange={setValue}
      placeholder="Upload avatar"
    />
  );
}

Wide View with Description

import * as React from "react";
import { ImageUploader } from "@xsolla/xui-image-uploader";

export default function WideView() {
  return (
    <ImageUploader
      wideView
      placeholder="Upload cover image"
      description="PNG or JPG, up to 5 MB"
    />
  );
}

Async Upload (Recommended)

If onUpload returns a Promise, the component automatically:

  • Shows the uploading state (spinner + uploadingPlaceholder) until the promise settles.
  • On resolve with { url, filename? } or a bare string URL, fires onChange(value) with the canonical value — no manual wiring needed.
  • On reject, fires onChange(null, error).

You don't need async/await in the handler — just return the upload promise:

import { ImageUploader } from "@xsolla/xui-image-uploader";

export default function AutoUpload() {
  return (
    <ImageUploader
      uploadingPlaceholder="Uploading…"
      // uploadFn can return Promise<string> or Promise<{ url, filename? }>
      onUpload={(file) => uploadFn(file.file)}
      onChange={(value) => form.setFieldValue("avatar", value?.url ?? null)}
    />
  );
}

Manual Loading Control

For finer-grained control (e.g. uploads triggered outside the component, or to keep the spinner visible during post-upload work), pass loading explicitly:

import * as React from "react";
import {
  ImageUploader,
  type ImageUploaderFile,
} from "@xsolla/xui-image-uploader";

export default function WithLoading() {
  const [loading, setLoading] = React.useState(false);

  const handleUpload = async (file: ImageUploaderFile) => {
    setLoading(true);
    try {
      await uploadToServer(file.file);
    } finally {
      setLoading(false);
    }
  };

  return (
    <ImageUploader
      loading={loading}
      uploadingPlaceholder="Uploading…"
      onUpload={handleUpload}
    />
  );
}

Error State

import * as React from "react";
import { ImageUploader } from "@xsolla/xui-image-uploader";

export default function WithError() {
  return (
    <ImageUploader
      placeholder="cover.png"
      errorMessage="File is too large (max 5 MB)."
    />
  );
}

React Native (Custom Picker)

On native there is no DOM <input type="file">. Pass an openPicker prop that returns a normalized ImageUploaderFile (e.g. wrapping expo-image-picker or react-native-image-picker).

import * as React from "react";
import * as ImagePicker from "expo-image-picker";
import {
  ImageUploader,
  type ImageUploaderFile,
} from "@xsolla/xui-image-uploader";

export default function Native() {
  const openPicker = async (): Promise<ImageUploaderFile | null> => {
    const result = await ImagePicker.launchImageLibraryAsync({
      mediaTypes: ImagePicker.MediaTypeOptions.Images,
    });
    if (result.canceled) return null;
    const asset = result.assets[0];
    return {
      name: asset.fileName ?? "image",
      size: asset.fileSize ?? 0,
      uri: asset.uri,
      mimeType: asset.mimeType,
    };
  };

  return <ImageUploader openPicker={openPicker} />;
}

Anatomy

+--------------------------+
|                          |
|         [Image]          |  <- icon (or Spinner while loading)
|         Upload           |  <- placeholder
|     PNG/JPG, up to 5MB   |  <- description (wideView only)
|                          |
+--------------------------+
  File is too large…         <- errorMessage (when present)

When a file has been uploaded, the box renders the image preview and reveals a trash-can affordance over a dimming scrim on hover; clicking (or tapping) removes the image. On touch and React Native devices — where there is no hover — the trash overlay is shown unconditionally so the delete affordance is always discoverable.

API Reference

ImageUploader

| Prop | Type | Default | Description | | :------------------- | :----------------------------------------------------------------------------------- | :------------ | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | testID | string | — | Test ID for testing frameworks. On web this renders as data-testid; on React Native it renders as testID. | | size | "xl" \| "lg" \| "md" \| "sm" \| "xs" | "xl" | Visual size of the uploader. | | placeholder | React.ReactNode | "Upload" | Label shown under the icon (or filename in error state). Accepts a string or a custom React node. | | uploadingPlaceholder | React.ReactNode | "Uploading" | Label shown while loading is true. Accepts a string or a custom React node. | | description | React.ReactNode | - | Helper text below the placeholder. Only rendered when wideView is true. | | errorMessage | string | - | Error message — when provided, the component renders in the error state. | | wideView | boolean | false | Use the horizontal layout. The box stretches to fill its parent's width. | | disabled | boolean | false | Disabled state. | | loading | boolean | false | Controlled loading state (shows spinner + uploading label). | | value | ImageUploaderValue \| null | - | Controlled value. When provided, the component reflects this value. | | onUpload | (file: ImageUploaderFile) => void \| Promise<ImageUploaderValue \| string \| void> | - | Fires when the user picks (or drops) a file. If it returns a Promise, the component shows the uploading state until it settles, forwards a resolved value (object or bare URL string) to onChange, and forwards a rejection to onChange(null, error). | | onChange | (value: ImageUploaderValue \| null, error?: Error) => void | - | Fires when the displayed value changes — on file pick (with a local data-URL preview), after onUpload resolves (with the server value), or on remove (null). The optional error arg carries any rejection from onUpload. | | onDelete | () => void | - | Fires when the user removes the image (trash click / clear). | | accept | string | "image/*" | Accepted MIME types for the file input (web only). | | openPicker | () => Promise<ImageUploaderFile \| null> | - | Native file picker hook. Required on native (no DOM <input type="file">). On web, omit this and the component falls back to a hidden file input. | | themeMode | ThemeMode | - | Override the theme mode for this instance. | | themeProductContext | ThemeProductContext | - | Override the product theme context for this instance. |

ImageUploaderValue

Controlled value shape.

interface ImageUploaderValue {
  filename: string;
  url?: string;
}

Loading and error states are driven by the loading and errorMessage props, not by fields on ImageUploaderValue.

ImageUploaderFile

Normalized file shape produced by the platform picker / drag-drop pipeline. On web, uri is a data-URL and the original DOM File is passed through for consumers that need it (e.g. to upload via FormData / fetch). On native, uri is a file URI and file is omitted.

interface ImageUploaderFile {
  name: string;
  size: number;
  uri: string;
  mimeType?: string;
  /** The original DOM File (web only) */
  file?: File;
}

Platform Support

This package works on both web and React Native. The component uses cross-platform primitives (Box, Text, Spinner) and gates web-only APIs (DOM file input, drag-and-drop, FileReader) behind an isWeb check. On native, supply an openPicker prop to integrate with your image picker library of choice.

Touch / Mobile

On touch web (detected via matchMedia("(hover: none)")) and on React Native, the component skips the hover-only delete affordance and always shows the trash overlay over the image preview, ensuring the remove action is reachable without a pointer.

Layout

In wideView, the component stretches to fill its parent's full width (width: 100%), letting the consumer's layout (form column, grid cell, etc.) control the maximum size. The square (non-wide) variants use fixed pixel dimensions from the size tokens.

Accessibility

  • The picker surface is rendered as a <button> on web with an accessible label that reflects the current state ("Upload", "Uploading", or "Remove image: <filename>").
  • aria-busy is set while loading is true.
  • aria-invalid is set in the error state and the error message is linked via aria-describedby (and rendered with role="alert").
  • The description element (when present) is linked via aria-describedby.
  • Decorative icons and the dimming scrim are marked aria-hidden.
  • Focus styling only appears for keyboard focus (:focus-visible), not for pointer focus left over after the native picker is dismissed.
  • The hidden file input is reset after every selection so the same file can be re-selected to retry a failed upload.