{"_id":"@alsocoder/apna-upload","name":"@alsocoder/apna-upload","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@alsocoder/apna-upload","version":"0.1.0","description":"Provider-free React file upload with drag-and-drop, progress, cancel, retry, and validation.","license":"MIT","author":{"name":"Also Coder","url":"https://alsocoder.com"},"type":"module","main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./styles.css":"./dist/styles.css"},"sideEffects":["*.css"],"scripts":{"build":"tsup","dev":"tsup --watch","dev:playground":"npm run dev --prefix playground","typecheck":"tsc --noEmit","prepublishOnly":"npm run build"},"keywords":["react","react-component","file-upload","drag-and-drop","dropzone","ui-component","css-variables","typescript","apna-upload","alsocoder"],"browserslist":["Chrome >= 90","Firefox >= 90","Safari >= 14","Edge >= 90","iOS >= 14","Samsung >= 15","not dead"],"repository":{"type":"git","url":"git+https://github.com/alsocoders/apna-upload.git"},"bugs":{"url":"https://github.com/alsocoders/apna-upload/issues"},"homepage":"https://github.com/alsocoders/apna-upload#readme","publishConfig":{"access":"public"},"engines":{"node":">=18"},"peerDependencies":{"react":">=18","react-dom":">=18"},"peerDependenciesMeta":{"pdfjs-dist":{"optional":true}},"optionalDependencies":{"pdfjs-dist":"^4.10.38"},"dependencies":{"clsx":"^2.1.1"},"devDependencies":{"@types/react":"^19.1.8","@types/react-dom":"^19.1.6","react":"^19.1.0","react-dom":"^19.1.0","tsup":"^8.5.0","typescript":"^5.9.2"},"_id":"@alsocoder/apna-upload@0.1.0","_nodeVersion":"24.16.0","_npmVersion":"12.0.1","dist":{"integrity":"sha512-36xNwy7IYdQQXTPRl80nAtIGzAeFgpElnNJI6SSBVh38SczLx3cD5vZtJ4pjG6B70Tl6BgdzxD6lLTPxmL/DMA==","shasum":"6b3c6effc3e66ab02ad1b5f1cadfe08a2d30c72b","tarball":"https://registry.npmjs.org/@alsocoder/apna-upload/-/apna-upload-0.1.0.tgz","fileCount":14,"unpackedSize":4723128,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCICSbzCTjWztbF7sUTq+8J5ROeFeMDQdrggwrKmZhjCppAiEA/83XV51CbpeaLdWyfLHWalYup9gezI2nG946mL40rqE="}]},"_npmUser":{"name":"alsocoder-org","email":"alsocoder@gmail.com"},"directories":{},"maintainers":[{"name":"alsocoder-org","email":"alsocoder@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/apna-upload_0.1.0_1785812625525_0.5859421149889004"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-04T03:03:45.289Z","0.1.0":"2026-08-04T03:03:45.692Z","modified":"2026-08-04T03:03:45.952Z"},"maintainers":[{"name":"alsocoder-org","email":"alsocoder@gmail.com"}],"description":"Provider-free React file upload with drag-and-drop, progress, cancel, retry, and validation.","homepage":"https://github.com/alsocoders/apna-upload#readme","keywords":["react","react-component","file-upload","drag-and-drop","dropzone","ui-component","css-variables","typescript","apna-upload","alsocoder"],"repository":{"type":"git","url":"git+https://github.com/alsocoders/apna-upload.git"},"author":{"name":"Also Coder","url":"https://alsocoder.com"},"bugs":{"url":"https://github.com/alsocoders/apna-upload/issues"},"license":"MIT","readme":"# @alsocoder/apna-upload\r\n\r\nProvider-free React file upload component with drag-and-drop, progress, cancel, retry, and validation.\r\n\r\n## Install\r\n\r\n```bash\r\nnpm install @alsocoder/apna-upload\r\n```\r\n\r\nPeer dependencies: `react`, `react-dom`.\r\n\r\nOptional: `pdfjs-dist` for PDF first-page thumbnails (`enablePdfPreview`).\r\n\r\n## Setup\r\n\r\n```tsx\r\nimport \"@alsocoder/apna-upload/styles.css\"\r\n```\r\n\r\n## Basic usage\r\n\r\n```tsx\r\nimport { ApnaUpload } from \"@alsocoder/apna-upload\"\r\n\r\nasync function uploadFile(file: File, { signal, onProgress }) {\r\n  const body = new FormData()\r\n  body.append(\"file\", file)\r\n\r\n  const response = await fetch(\"/api/upload\", {\r\n    method: \"POST\",\r\n    body,\r\n    signal,\r\n  })\r\n\r\n  if (!response.ok) {\r\n    throw new Error(\"Upload failed\")\r\n  }\r\n\r\n  onProgress(100)\r\n  return response.json()\r\n}\r\n\r\nexport function ResumeField() {\r\n  const [resume, setResume] = useState(\"\")\r\n\r\n  return (\r\n    <ApnaUpload\r\n      label=\"Resume\"\r\n      accept=\".pdf\"\r\n      upload={uploadFile}\r\n      transformResult={(result) => result.file.path}\r\n      value={resume}\r\n      onChange={setResume}\r\n    />\r\n  )\r\n}\r\n```\r\n\r\n## Multiple files\r\n\r\n```tsx\r\n<ApnaUpload\r\n  multiple\r\n  maxFiles={5}\r\n  accept=\"image/*\"\r\n  upload={uploadFile}\r\n  value={images}\r\n  onChange={setImages}\r\n/>\r\n```\r\n\r\n## Result modes\r\n\r\n| `resultMode` | `onChange` value |\r\n|--------------|------------------|\r\n| `\"url\"` (default) | `string` or `string[]` (via `transformResult`) |\r\n| `\"file\"` | `File` or `File[]` |\r\n\r\n## Hooks and callbacks\r\n\r\n| Prop | Purpose |\r\n|------|---------|\r\n| `beforeUpload` | Transform or reject a file before upload (`null` = reject). Use for crop, compression, watermark, EXIF fix. |\r\n| `transformResult` | Map API response to field value. Default extracts `{ url }` or a string. |\r\n| `onUploading` | Called during upload with `(file, progress)` — use for analytics or custom progress UI |\r\n| `onUploaded` | Called per file after successful upload |\r\n| `onRemoved` | Called when user removes a completed file |\r\n| `onError` | Per-file error callback (wire your own toast) |\r\n\r\n## Validation\r\n\r\nBuilt-in rules: `accept`, `maxSize`, `maxFiles`.\r\n\r\nFor custom rules, use `validate`:\r\n\r\n```tsx\r\n<ApnaUpload\r\n  accept=\"image/*\"\r\n  validate={(file) => {\r\n    if (!file.name.endsWith(\".png\")) {\r\n      return \"Only PNG allowed\"\r\n    }\r\n    return true\r\n  }}\r\n  upload={uploadFile}\r\n/>\r\n```\r\n\r\nReturn `true` to allow, or a string error message to reject. Supports async validators.\r\n\r\n## Disabled and read-only\r\n\r\n| Prop | Behavior |\r\n|------|----------|\r\n| `disabled` | No browse, drop, paste, cancel, retry, or remove |\r\n| `readOnly` | Files visible but no interactions (same as disabled for actions) |\r\n\r\n```tsx\r\n<ApnaUpload disabled upload={uploadFile} />\r\n<ApnaUpload readOnly value={url} upload={uploadFile} />\r\n```\r\n\r\n## Custom preview\r\n\r\nOverride the default thumbnail with `renderPreview`:\r\n\r\n```tsx\r\n<ApnaUpload\r\n  renderPreview={(file, item) => (\r\n    <div className=\"my-card\">\r\n      <strong>{file.name}</strong>\r\n      <span>{item.progress}%</span>\r\n    </div>\r\n  )}\r\n  upload={uploadFile}\r\n/>\r\n```\r\n\r\n## Headless hook\r\n\r\nUse `useApnaUpload()` for custom UI — same engine as the component:\r\n\r\n```tsx\r\nimport { useApnaUpload } from \"@alsocoder/apna-upload\"\r\n\r\nconst { items, enqueueFiles, cancelItem, retryItem, removeItem } = useApnaUpload({\r\n  upload: uploadFile,\r\n  validate: (file) => file.size < 5_000_000 || \"Max 5 MB\",\r\n  onUploading: (file, progress) => track(\"upload\", { file: file.name, progress }),\r\n})\r\n```\r\n\r\n## Architecture\r\n\r\n```\r\nApnaUpload (UI)\r\n├── Upload Engine   — queue, progress, cancel, retry\r\n├── Validation      — accept, maxSize, maxFiles, validate()\r\n├── Preview         — image/PDF thumbnails, renderPreview\r\n├── Drag & Drop     — dropzone\r\n├── Paste           — clipboard files\r\n├── Reorder         — drag sort (multiple)\r\n└── UI              — dropzone + file list\r\n```\r\n\r\n`useApnaUpload()` exposes the engine layer without UI.\r\n\r\n## Features\r\n\r\n| Feature | Status |\r\n|---------|--------|\r\n| Drag & drop | Yes |\r\n| Browse | Yes |\r\n| Multiple | Yes |\r\n| Preview (image) | Yes |\r\n| PDF preview | Yes (`enablePdfPreview` + optional `pdfjs-dist`) |\r\n| Progress | Yes |\r\n| Retry | Yes |\r\n| Cancel | Yes |\r\n| `beforeUpload` | Yes |\r\n| `onUploading` | Yes |\r\n| `validate` (custom) | Yes |\r\n| `renderPreview` | Yes |\r\n| `disabled` / `readOnly` | Yes |\r\n| `onUploaded` / `onRemoved` | Yes |\r\n| `useApnaUpload()` hook | Yes |\r\n| Max files / max size / accept | Yes |\r\n| `resultMode` url \\| file | Yes |\r\n| AbortController | Yes |\r\n| Paste upload (Ctrl+V) | Yes (`enablePaste`, default on) |\r\n| Reorder (drag) | Yes (`enableReorder` + `multiple`) |\r\n| Chunk upload | Adapter contract — see below |\r\n\r\n## Chunk upload (adapter contract)\r\n\r\nFor large files, your `upload` function may receive optional chunk metadata:\r\n\r\n```ts\r\ntype ApnaUploadFn = (\r\n  file: File,\r\n  ctx: {\r\n    signal: AbortSignal\r\n    onProgress: (percent: number) => void\r\n    chunk?: { index: number; total: number; slice: Blob }\r\n  }\r\n) => Promise<unknown>\r\n```\r\n\r\nImplement multipart/resumable logic in your adapter — the component does not ship S3/Cloudinary adapters.\r\n\r\n## Custom upload adapters\r\n\r\nImplement any backend in the `upload` function:\r\n\r\n- S3 presigned URL\r\n- Cloudinary unsigned upload\r\n- Firebase Storage\r\n- Supabase storage\r\n- Your REST API\r\n\r\nReturn any JSON shape and use `transformResult` to map it to your form value.\r\n\r\n## License\r\n\r\nMIT\r\n","readmeFilename":"README.md","_rev":"1-f181b11d320d128c7197a48af5aaace3"}