npm.io
0.1.1 • Published 7h ago

@alsocoder/apna-upload

Licence
MIT
Version
0.1.1
Deps
0
Vulns
0
Weekly
0

@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

Keywords