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

@quanticjs/react-files

v8.0.0

Published

QuanticJS file upload/download React components and hooks

Readme

@quanticjs/react-files

File upload/download React components and hooks for QuanticJS apps: presigned single-part and multipart uploads with progress, validation, and cancellation.

Installation

pnpm add @quanticjs/react-files @quanticjs/react-core

Requires @quanticjs/tailwind-preset >= 8 in the consuming app's CSS build — components render with v8 token utilities (shadow-* tiers, z-(--z-*), animate-*) that compile to nothing on older presets. See docs/MIGRATION-8.md.

Quick start

import { FilesProvider, FileUpload } from '@quanticjs/react-files';

function App() {
  return (
    <FilesProvider apiBaseUrl="/api/files">
      <FileUpload
        allowedTypes={['application/pdf', 'image/*']}
        maxSize={10 * 1024 * 1024}
        onUploadComplete={(ref) => console.log('uploaded', ref.id)}
        onValidationError={(file, error) => console.warn(file.name, error.code, error.message)}
      />
    </FilesProvider>
  );
}

Files above multipartThreshold (default 100 MB) automatically use the multipart flow.

Security guidance

Always set allowedTypes

The HTML accept attribute is a UI hint only — it filters the file picker but does nothing for drag-and-drop and is trivially bypassed. allowedTypes is enforced in JavaScript before any network request:

  • Exact MIME strings (application/pdf) or wildcards (image/*), case-insensitive.
  • The declared file.type and the file extension are both checked. Extensions map to MIME types through a built-in table (pdf, png, jpg/jpeg, gif, webp, csv, xlsx, docx, txt, zip); unknown extensions pass the extension check, but the MIME check still applies.
  • If the browser reports an empty file.type, the extension table is the fallback; if the extension is unknown too, the file is rejected (INVALID_TYPE) — validation fails closed.

Client-side checks are defense-in-depth, not a boundary

Everything this package validates runs in the browser and can be bypassed by anyone talking to your API directly. Your server must re-validate type, size, and content on every upload. Treat allowedTypes/maxSize as a UX and first-line filter only.

Custom validation hook (AV pre-scan, magic bytes)

validateFile runs after the built-in checks and before any request. Return true to accept or a string to reject with that message (a thrown error also rejects, with code CUSTOM):

<FileUpload
  allowedTypes={['application/pdf']}
  validateFile={async (file) => {
    // Magic-byte sniffing: a real PDF starts with %PDF
    const head = new Uint8Array(await file.slice(0, 4).arrayBuffer());
    if (String.fromCharCode(...head) !== '%PDF') return 'File is not a valid PDF';

    // Or hand off to an AV pre-scan endpoint
    const res = await fetch('/api/av/prescan', { method: 'POST', body: file.slice(0, 65536) });
    if (!res.ok) return 'File failed the malware pre-scan';
    return true;
  }}
  onUploadComplete={handleComplete}
/>

Validation

Each rejected file fires onValidationError(file, { code, message }) once (falling back to onUploadError(message) when not provided) with one of:

| Code | Meaning | |---|---| | INVALID_TYPE | MIME/extension not in allowedTypes | | TOO_LARGE | file.size exceeds maxSize | | CUSTOM | validateFile returned a string or threw |

Rejection is a hard stop for that file — no request fires — but other files in the same batch still upload.

Messages are overridable for i18n via the messages prop:

<FileUpload
  allowedTypes={['image/*']}
  maxSize={5 * 1024 * 1024}
  messages={{
    invalidType: (file, allowed) => `${file.name}: nur ${allowed.join(', ')} erlaubt`,
    tooLarge: (file, maxSize) => `${file.name} ist zu groß (max. ${maxSize} Bytes)`,
  }}
  onUploadComplete={handleComplete}
/>

Cancellation

FileUpload shows a Cancel button while uploading. With the hook directly:

import { useFileUpload } from '@quanticjs/react-files';

function Uploader() {
  const { upload, cancel, isUploading, progress } = useFileUpload({
    allowedTypes: ['application/zip'],
    onCancel: () => console.log('upload cancelled'),
  });
  // upload(file) rejects with UploadCancelledError after cancel() —
  // not an error condition, and onUploadError-style handling should ignore it.
}
  • Cancelling aborts the in-flight request (xhr.abort() / fetch abort) and stops scheduling further multipart parts.
  • After cancel, state returns to idle and progress resets; onCancel fires. Cancellation is not reported as an error.
  • If cancel arrives after the multipart complete call was already sent, it is too late — the upload finishes normally.

Multipart integrity

  • Completed parts are sorted by partNumber before the complete call, regardless of the order parts finish in.
  • Any failed part aborts the remaining parts and fails the whole upload — a partial complete is never sent.

API

<FilesProvider apiBaseUrl multipartThreshold?>

Context provider required by all hooks/components.

<FileUpload /> props

| Prop | Type | Description | |---|---|---| | onUploadComplete | (file: FileReference) => void | Required; fires per uploaded file | | onUploadError | (error: string) => void | Upload failures (and validation fallback) | | onValidationError | (file: File, error: FileValidationError) => void | Per-file validation rejections | | onCancel | () => void | After a cancel completes | | allowedTypes | string[] | MIME whitelist (exact or type/*) | | maxSize | number | Max bytes per file | | validateFile | (file: File) => Promise<true \| string> | Custom async check | | messages | UploadMessages | i18n overrides for validation messages | | labels | Partial<FileUploadLabels> | Per-key overrides for rendered strings | | accept / multiple / disabled / className | — | Standard input/picker options |

<FileList /> props

| Prop | Type | Description | |---|---|---| | onDelete / showDelete | — | Delete-action wiring | | emptyMessage | string | Empty-state text; wins over labels.empty | | labels | Partial<FileListLabels> | Per-key overrides for rendered strings | | columns | array or function | Replace or extend the default columns |

useFileUpload(options?)

Returns { upload, cancel, isUploading, progress, error, reset }. Options: allowedTypes, maxSize, validateFile, messages, onCancel. messages resolves per key: hook option > provider catalog (files.messages) > built-in English.

Internationalization

All rendered strings live in labels interfaces with English defaults and resolve per key with the precedence explicit prop > provider catalog > default. App-wide injection goes through TranslationProvider from @quanticjs/react-ui — this package registers the files namespace (FilesTranslations):

import { TranslationProvider } from '@quanticjs/react-ui';

<TranslationProvider
  locale="de-DE"
  translations={{
    files: {
      upload: { preparing: 'Upload wird vorbereitet…' },          // Partial<FileUploadLabels>
      list: { searchPlaceholder: 'Dateien durchsuchen…' },        // Partial<FileListLabels>
      downloadLink: { download: 'Herunterladen' },                 // Partial<FileDownloadLinkLabels>
      messages: { tooLarge: (file) => `„${file.name}" ist zu groß` }, // Partial<UploadMessages>
    },
  }}
>
  <App />
</TranslationProvider>
  • FileUploadLabels: uploadFile, uploading(percentage), preparing, cancel, dropHint(multiple), maxSize(formattedSize)
  • FileListLabels: empty, table headers (name, size, type, uploaded, actions), delete, deleteAriaLabel(fileName), searchPlaceholder, searchAriaLabel, loadError, retry, previous, next, pageStatus(page, totalPages)
  • FileDownloadLinkLabels: download
  • Function-valued labels handle interpolation; JSON-based pipelines wrap them (pageStatus: (p, t) => t('files.pageStatus', { p, t }))

The provider's locale also drives the formatBytes/formatDateTime output in FileList, the max-size hint in FileUpload, and the default too-large validation message — reactively, unlike setDefaultLocale.

Exports

FilesProvider, useFilesContext
useFileUpload, UploadValidationError, UploadCancelledError, type UseFileUploadOptions
useFileList
useFileDownloadUrl
FileUpload, type FileUploadProps
FileList, type FileListProps
FileDownloadLink
DEFAULT_UPLOAD_LABELS, DEFAULT_LIST_LABELS, DEFAULT_DOWNLOAD_LINK_LABELS
type FileUploadLabels, type FileListLabels, type FileDownloadLinkLabels, type FilesTranslations
type FileReference, type FileDto, type FileListResponse
type FileValidationCode, type FileValidationError
type UploadMessages, type UploadProgress, type DownloadUrlResponse, type FilesContextValue

RTL & reduced motion

Components carry no physical direction utilities, so they render correctly under dir="rtl" with no configuration. The drop-zone hover transition and the upload progress bar carry motion-reduce:transition-none; the preset's theme.css additionally disables animation/transition durations globally under prefers-reduced-motion: reduce.