{"_id":"@alphinex/files","_rev":"2-724967970a7f594d6776aedeac0402e3","name":"@alphinex/files","dist-tags":{"latest":"1.0.1"},"versions":{"1.0.0":{"name":"@alphinex/files","version":"1.0.0","license":"UNLICENSED","_id":"@alphinex/files@1.0.0","maintainers":[{"name":"anasaliqureshi","email":"anas@alphinex.com"}],"dist":{"shasum":"a47016ea24501875be898692c49f051fef080686","tarball":"https://registry.npmjs.org/@alphinex/files/-/files-1.0.0.tgz","fileCount":5,"integrity":"sha512-Cd00580GiwuzQbCSMXWwB0UrjwnZKFLLExdZgfithIYhMMhYWMeM9myHCDkzrTlE2SPSZD//V9LQ8MPAlcGkmQ==","signatures":[{"sig":"MEQCIEbYnZQAaPotQRzyQUIqK0RJhfVxN0xbHcRzSwhRmjiMAiBTCr0cL+FZVNUBs7rfuDnDOJWhzmiFOiho09CybkfGyw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":150902},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./package.json":"./package.json"},"private":false,"scripts":{"dev":"vite build --watch","lint":"eslint .","test":"vitest run","build":"vite build","clean":"rimraf dist .turbo","typecheck":"tsc --noEmit"},"_npmUser":{"name":"anasaliqureshi","email":"anas@alphinex.com"},"description":"File manager UI, upload queue with progress, gallery lightbox and image cropper. Upload transport is injected by the app — no @alphinex/api dependency. See documentation/PRODUCT-RESEARCH-MASTER-PLAN.md §10.3 (Sprint 38).","directories":{},"sideEffects":false,"_nodeVersion":"22.14.0","dependencies":{"@alphinex/ui":"2.6.0","@alphinex/core":"1.0.0","@alphinex/hooks":"1.0.2","@alphinex/icons":"1.2.0","@alphinex/theme":"1.1.0","@alphinex/utils":"1.1.0"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^6.0.5","jsdom":"^25.0.1","react":"^19.0.0","eslint":"^9.17.0","vitest":"^2.1.8","axe-core":"^4.10.2","react-dom":"^19.0.0","typescript":"^5.7.2","@types/react":"^19.0.2","@types/react-dom":"^19.0.2","@vitejs/plugin-react":"^4.3.4","@alphinex/build-config":"0.0.0","@testing-library/react":"^16.1.0","@alphinex/eslint-config":"0.0.0","@testing-library/jest-dom":"^6.6.3","@alphinex/typescript-config":"0.0.0","@testing-library/user-event":"^14.5.2"},"peerDependencies":{"react":"^19.0.0","react-dom":"^19.0.0"},"peerDependenciesMeta":{"react":{"optional":false},"react-dom":{"optional":false}},"_npmOperationalInternal":{"tmp":"tmp/files_1.0.0_1786525490648_0.052068909563520505","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"@alphinex/files","version":"1.0.1","private":false,"description":"File manager UI, upload queue with progress, gallery lightbox and image cropper. Upload transport is injected by the app — no @alphinex/api dependency. See documentation/PRODUCT-RESEARCH-MASTER-PLAN.md §10.3 (Sprint 38).","license":"UNLICENSED","type":"module","sideEffects":false,"main":"./dist/index.js","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./package.json":"./package.json"},"dependencies":{"@alphinex/core":"1.0.0","@alphinex/hooks":"1.0.2","@alphinex/theme":"1.1.0","@alphinex/icons":"1.2.0","@alphinex/utils":"1.1.0","@alphinex/ui":"2.7.0"},"peerDependencies":{"react":"^19.0.0","react-dom":"^19.0.0"},"peerDependenciesMeta":{"react":{"optional":false},"react-dom":{"optional":false}},"devDependencies":{"typescript":"^5.7.2","vite":"^6.0.5","vitest":"^2.1.8","react":"^19.0.0","react-dom":"^19.0.0","@types/react":"^19.0.2","@types/react-dom":"^19.0.2","@testing-library/react":"^16.1.0","@testing-library/jest-dom":"^6.6.3","@testing-library/user-event":"^14.5.2","@vitejs/plugin-react":"^4.3.4","axe-core":"^4.10.2","jsdom":"^25.0.1","eslint":"^9.17.0","@alphinex/typescript-config":"0.0.0","@alphinex/eslint-config":"0.0.0","@alphinex/build-config":"0.0.0"},"scripts":{"build":"vite build","dev":"vite build --watch","lint":"eslint .","typecheck":"tsc --noEmit","test":"vitest run","clean":"rimraf dist .turbo"},"_nodeVersion":"22.14.0","_id":"@alphinex/files@1.0.1","dist":{"integrity":"sha512-tMwxOsGkQuphsqGptOhtx/mEOByLV1Y5DUezxcAg2HnQHy4G1qnpgF9RedAcyW/Hmt3pOqKtFUoz0nNebioZ1A==","shasum":"ef2f5d5befb235c65f2855c93aa599ad3b5b2855","tarball":"https://registry.npmjs.org/@alphinex/files/-/files-1.0.1.tgz","fileCount":5,"unpackedSize":150902,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQD+RoheBqAWxutT5BwNUEt7/IS/3Jb48aIkvza+UP7XKQIgNdWp5c7cdn6fn02EEvTlNrJZsIE2Ot3wTovFH38geLY="}]},"_npmUser":{"name":"anasaliqureshi","email":"anas@alphinex.com"},"directories":{},"maintainers":[{"name":"anasaliqureshi","email":"anas@alphinex.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/files_1.0.1_1786546532176_0.7577360475411365"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-12T09:04:50.495Z","modified":"2026-08-12T14:55:32.544Z","1.0.0":"2026-08-12T09:04:50.786Z","1.0.1":"2026-08-12T14:55:32.358Z"},"license":"UNLICENSED","description":"File manager UI, upload queue with progress, gallery lightbox and image cropper. Upload transport is injected by the app — no @alphinex/api dependency. See documentation/PRODUCT-RESEARCH-MASTER-PLAN.md §10.3 (Sprint 38).","maintainers":[{"name":"anasaliqureshi","email":"anas@alphinex.com"}],"readme":"# @alphinex/files\n\nFile management UI for React: an upload queue with real progress, a file browser, a gallery\nlightbox, and an image cropper.\n\n**The backend is injected, never imported.** You supply a `FileTransport`; this package never\ntouches `@alphinex/api` or any HTTP client, so it works the same against REST, a signed S3 URL, or\na fake in tests.\n\n```bash\nnpm install @alphinex/files\n```\n\nPeer dependencies: `react` and `react-dom` 19+. Requires a `ThemeProvider` from\n`@alphinex/theme` somewhere above it, like the rest of the platform.\n\n---\n\n## The transport\n\nOne required method. `remove` and `rename` are optional — their affordances simply don't render\nwhen you don't supply them.\n\n```tsx\nimport type { FileTransport } from \"@alphinex/files\";\n\nconst transport: FileTransport = {\n  async upload(file, { signal, onProgress }) {\n    const body = new FormData();\n    body.append(\"file\", file);\n\n    const response = await fetch(\"/api/files\", { method: \"POST\", body, signal });\n    if (!response.ok) throw new Error(\"Upload failed\");\n\n    const saved = await response.json();\n    return { id: saved.id, name: file.name, size: file.size, url: saved.url };\n  },\n};\n```\n\n`fetch` cannot report upload progress in any shipping browser. If you want a progress bar, use\n`uploadFile` from `@alphinex/api`, which is `XMLHttpRequest`-based for exactly this reason:\n\n```tsx\nimport { uploadFile } from \"@alphinex/api\";\n\nconst transport: FileTransport = {\n  upload: (file, { signal, onProgress }) =>\n    uploadFile({ url: \"/api/files\", file, signal, onProgress }),\n};\n```\n\n---\n\n## Uploading\n\n`useUploadQueue` runs the transport with a concurrency limit, and `UploadList` renders it.\n\n```tsx\nimport { FileUpload } from \"@alphinex/ui\";\nimport { UploadList, useUploadQueue } from \"@alphinex/files\";\n\nfunction Attachments() {\n  const queue = useUploadQueue({\n    transport,\n    concurrency: 3,\n    onComplete: (uploaded) => console.log(`${uploaded.length} files stored`),\n  });\n\n  return (\n    <>\n      <FileUpload\n        aria-label=\"Attachments\"\n        multiple\n        accept=\".pdf,.png\"\n        maxSize={5_000_000}\n        showFileList={false}\n        onChange={queue.add}\n      />\n      <UploadList\n        tasks={queue.tasks}\n        onCancel={queue.cancel}\n        onRetry={queue.retry}\n        onRemove={queue.remove}\n      />\n    </>\n  );\n}\n```\n\nCancelling aborts the request through an `AbortSignal` and frees the slot for the next queued\nfile. A cancelled upload cannot be resurrected by a late-arriving response, and a finished one\ncannot be cancelled — the bytes are already on the server.\n\n`queue.summary` gives you `{ total, queued, uploading, success, error, canceled, overallPercent,\nisUploading }` for a combined progress bar.\n\n---\n\n## Browsing\n\n```tsx\nimport { FileManager } from \"@alphinex/files\";\n\n<FileManager\n  files={files}\n  onOpen={(file) => setPreview(file)}\n  onDelete={(selected) => remove(selected.map((f) => f.id))}\n  onDownload={(file) => window.open(file.url)}\n/>;\n```\n\nSearch, sorting (name, size, modified, type) and a grid/list toggle are built in. Selection is by\ncheckbox and opening is by clicking the tile — two distinct actions, deliberately not overloaded\nonto one click.\n\nName sorting is numeric-aware, so `file9` comes before `file10`.\n\n---\n\n## Gallery\n\n```tsx\nimport { FileGallery } from \"@alphinex/files\";\n\n<FileGallery files={files} isOpen={isOpen} onClose={close} onDownload={download} />;\n```\n\nArrow keys page, Home/End jump to the ends, Escape closes. Paging **clamps** at each end rather\nthan wrapping, so you always know where you are in a long set. Non-image files get a\n\"no preview available\" placeholder rather than a broken `<img>`.\n\n---\n\n## Cropping\n\n```tsx\nimport { ImageCropper } from \"@alphinex/files\";\n\n<ImageCropper\n  src={objectUrl}\n  aspect={1}\n  onApply={(blob) => upload(new File([blob], \"avatar.png\"))}\n/>;\n```\n\nThe crop box is fully keyboard-operable — arrows nudge, `Shift`+arrows resize, `Alt` makes either\none-pixel-precise. A crop tool that only responds to dragging is unusable without a mouse.\n\n`onApply` and `onCropChange` report in **natural image pixels**, not display pixels. Handing\ndisplay coordinates to a canvas is the classic cropper bug, and `toNaturalRect` is the single\nplace the two coordinate spaces meet.\n\n---\n\n## With `@alphinex/forms`\n\n```tsx\nimport { FileUploadField } from \"@alphinex/forms\";\n\n<FileUploadField\n  name=\"attachments\"\n  label=\"Attachments\"\n  multiple\n  accept=\".pdf\"\n  maxSize={5_000_000}\n/>;\n```\n\nThe stored value is always a `File[]`, even in single-file mode, so `z.array(z.instanceof(File))`\nvalidates both cases without branching.\n\n---\n\n## Exported helpers\n\nEverything below is a pure function, usable without rendering anything:\n\n| Function                                                  | Purpose                                                         |\n| --------------------------------------------------------- | --------------------------------------------------------------- |\n| `fileKindFrom(name, mime?)`                               | Classifies a file as image/video/audio/pdf/archive/text/other   |\n| `previewUrlOf(file)`                                      | Thumbnail URL, or `undefined` when there's nothing safe to show |\n| `sortFiles`, `filterFiles`                                | The manager's sorting and search                                |\n| `toggleSelection`, `selectRange`, `selectAllState`        | Selection maths, including the tri-state header                 |\n| `uploadQueueReducer`, `nextQueued`, `summarize`           | The queue state machine                                         |\n| `centeredRect`, `resizeRect`, `moveRect`, `toNaturalRect` | Crop geometry                                                   |\n\n---\n\n## Deliberately not included\n\n- **Folder hierarchy and breadcrumbs.** `ManagedFile` carries a `folderId`, but navigation is the\n  app's concern; no folder tree ships here.\n- **Chunked or resumable uploads.** A transport can implement them; the queue doesn't assume them.\n- **Server-side thumbnail generation.** Supply `thumbnailUrl` and it's used.\n\nSee `documentation/adr/0025-*.md` for the reasoning behind the injected transport.\n","readmeFilename":""}