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

@alsocoder/apna-upload

v0.1.2

Published

Provider-free React file upload with drag-and-drop, progress, cancel, retry, and validation.

Readme

@alsocoder/apna-upload

Provider-free React file upload component with drag-and-drop, progress, cancel, retry, and validation.

Install

npm install @alsocoder/apna-upload

Peer dependencies: react, react-dom.

Optional: pdfjs-dist for PDF first-page thumbnails (enablePdfPreview).

Setup

import "@alsocoder/apna-upload/styles.css"

Basic usage

import { ApnaUpload } from "@alsocoder/apna-upload"

async function uploadFile(file: File, { signal, onProgress }) {
  const body = new FormData()
  body.append("file", file)

  const response = await fetch("/api/upload", {
    method: "POST",
    body,
    signal,
  })

  if (!response.ok) {
    throw new Error("Upload failed")
  }

  onProgress(100)
  return response.json()
}

export function ResumeField() {
  const [resume, setResume] = useState("")

  return (
    <ApnaUpload
      label="Resume"
      accept=".pdf"
      upload={uploadFile}
      transformResult={(result) => result.file.path}
      value={resume}
      onChange={setResume}
    />
  )
}

Multiple files

<ApnaUpload
  multiple
  maxFiles={5}
  accept="image/*"
  upload={uploadFile}
  value={images}
  onChange={setImages}
/>

Result modes

| resultMode | onChange value | |--------------|------------------| | "url" (default) | string or string[] (via transformResult) | | "file" | File or File[] |

Hooks and callbacks

| Prop | Purpose | |------|---------| | beforeUpload | Transform or reject a file before upload (null = reject). Use for crop, compression, watermark, EXIF fix. | | transformResult | Map API response to field value. Default extracts { url } or a string. | | onUploading | Called during upload with (file, progress) — use for analytics or custom progress UI | | onUploaded | Called per file after successful upload | | onRemoved | Called when user removes a completed file | | onError | Per-file error callback (wire your own toast) |

Validation

Built-in rules: accept, maxSize, maxFiles.

For custom rules, use validate:

<ApnaUpload
  accept="image/*"
  validate={(file) => {
    if (!file.name.endsWith(".png")) {
      return "Only PNG allowed"
    }
    return true
  }}
  upload={uploadFile}
/>

Return true to allow, or a string error message to reject. Supports async validators.

Disabled and read-only

| Prop | Behavior | |------|----------| | disabled | No browse, drop, paste, cancel, retry, or remove | | readOnly | Files visible but no interactions (same as disabled for actions) |

<ApnaUpload disabled upload={uploadFile} />
<ApnaUpload readOnly value={url} upload={uploadFile} />

Custom preview

Override the default thumbnail with renderPreview:

<ApnaUpload
  renderPreview={(file, item) => (
    <div className="my-card">
      <strong>{file.name}</strong>
      <span>{item.progress}%</span>
    </div>
  )}
  upload={uploadFile}
/>

Headless hook

Use useApnaUpload() for custom UI — same engine as the component:

import { useApnaUpload } from "@alsocoder/apna-upload"

const { items, enqueueFiles, cancelItem, retryItem, removeItem } = useApnaUpload({
  upload: uploadFile,
  validate: (file) => file.size < 5_000_000 || "Max 5 MB",
  onUploading: (file, progress) => track("upload", { file: file.name, progress }),
})

Architecture

ApnaUpload (UI)
├── Upload Engine   — queue, progress, cancel, retry
├── Validation      — accept, maxSize, maxFiles, validate()
├── Preview         — image/PDF thumbnails, renderPreview
├── Drag & Drop     — dropzone
├── Paste           — clipboard files
├── Reorder         — drag sort (multiple)
└── UI              — dropzone + file list

useApnaUpload() exposes the engine layer without UI.

Features

| Feature | Status | |---------|--------| | Drag & drop | Yes | | Browse | Yes | | Multiple | Yes | | Preview (image) | Yes | | PDF preview | Yes (enablePdfPreview + optional pdfjs-dist) | | Progress | Yes | | Retry | Yes | | Cancel | Yes | | beforeUpload | Yes | | onUploading | Yes | | validate (custom) | Yes | | renderPreview | Yes | | disabled / readOnly | Yes | | onUploaded / onRemoved | Yes | | useApnaUpload() hook | Yes | | Max files / max size / accept | Yes | | resultMode url | file | Yes | | AbortController | Yes | | Paste upload (Ctrl+V) | Yes (enablePaste, default on) | | Reorder (drag) | Yes (enableReorder + multiple) | | Chunk upload | Adapter contract — see below |

Chunk upload (adapter contract)

For large files, your upload function may receive optional chunk metadata:

type ApnaUploadFn = (
  file: File,
  ctx: {
    signal: AbortSignal
    onProgress: (percent: number) => void
    chunk?: { index: number; total: number; slice: Blob }
  }
) => Promise<unknown>

Implement multipart/resumable logic in your adapter — the component does not ship S3/Cloudinary adapters.

Custom upload adapters

Implement any backend in the upload function:

  • S3 presigned URL
  • Cloudinary unsigned upload
  • Firebase Storage
  • Supabase storage
  • Your REST API

Return any JSON shape and use transformResult to map it to your form value.

License

MIT