@akhatmirzo/ui

FileUpload

Upload files by drag and drop, file picker or Ctrl+V — with progress, cancel and retry for each file.

Faylni shu yerga tashlang yoki tanlangRasmlar, PDF · 5 MB gacha · ko'pi bilan 5 ta

Jami: 0 · yuklandi: 0

Import

import { FileUpload } from '@akhatmirzo/ui';

When to use

  • Documents, photos, screenshots — in applications, profiles and support forms. Drag and drop, the file dialog and Ctrl+V all work in one component.
  • State the limits up front: with accept, maxSize and maxFiles, a hint like "Rasmlar, PDF · 5 MB gacha · ko'pi bilan 5 ta" appears automatically, and files that break the rules are flagged right away.
  • Need instant uploads? Pass an upload function — progress, cancel and "Retry" come for free.
  • Sending files with a form? Skip upload and pass name: the files end up in FormData just like with a plain <input type="file">.
  • Short on space? Use variant="button" — a single button instead of the dropzone.

The upload function

upload(file, { onProgress, signal }) is called once per valid file and returns a Promise. Report progress with onProgress(0–100); signal is aborted when the user clicks ✕ or the component unmounts. Whatever the Promise resolves to is stored in item.result.

upload.ts
import type { FileUploadContext } from '@akhatmirzo/ui';

/** XMLHttpRequest — unlike fetch, it reports upload progress */
export function uploadFile(file: File, { onProgress, signal }: FileUploadContext) {
  return new Promise<{ url: string }>((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    const body = new FormData();
    body.append('file', file);

    xhr.upload.onprogress = (e) => e.lengthComputable && onProgress((e.loaded / e.total) * 100);
    xhr.onload = () =>
      xhr.status < 300 ? resolve(JSON.parse(xhr.responseText)) : reject(new Error(xhr.statusText));
    xhr.onerror = () => reject(new Error('network'));
    signal.addEventListener('abort', () => xhr.abort());

    xhr.open('POST', '/api/upload');
    xhr.send(body);
  });
}
Documents.tsx
import { FileUpload } from '@akhatmirzo/ui';
import { useState } from 'react';
import { uploadFile } from './upload';

export function Documents() {
  const [urls, setUrls] = useState<string[]>([]);

  return (
    <>
      <FileUpload
        accept={['image/*', '.pdf']}
        maxSize={5 * 1024 * 1024}
        maxFiles={5}
        multiple
        upload={uploadFile}
        onFilesChange={(items) =>
          setUrls(items.flatMap((it) => (it.result ? [it.result.url] : [])))
        }
      />
      <input type="hidden" name="documents" value={urls.join(',')} />
    </>
  );
}

If the Promise rejects, the file row shows an error and a Retry button. Choose the message with getErrorMessage(error) (the default is "Tarmoq xatosi", a network error). In the demo above, any file with xato in its name fails on purpose.

The component owns the list

FileUpload manages its own file list — there's no value prop. Use onFilesChange to read it: it receives the full list on every change (added, progress, done, error, removed).

States

Without multiple, a new file replaces the old one. While dragging, an accepted type gets a primary border; a rejected one gets danger and "Bu turdagi fayl qabul qilinmaydi". error, disabled and required from Field are applied automatically. hint replaces the generated limits text.

Bitta fayl — yangisi eskisini almashtiradi

Faylni shu yerga tashlang yoki tanlangPNG, JPEG · 1 MB gacha
Faylni shu yerga tashlang yoki tanlangPDF

Kamida bitta hujjat yuklang

Faylni shu yerga tashlang yoki tanlang
Faylni shu yerga tashlang yoki tanlangExcel yoki CSV · 10 MB gacha

Files that break a rule still show up in the list with the reason ("Hajmi 5 MB dan katta"), but they aren't uploaded and don't count toward maxFiles. Files over the maxFiles limit aren't added at all; a "Ko'pi bilan 5 ta fayl yuklash mumkin" notice appears instead.

Button variant

variant="button" swaps the dropzone for a "Fayl tanlash" button. The file list works the same.

Old va orqa tomoni

Rasmlar, PDF · 5 MB gacha · ko'pi bilan 2 ta

With a form

Without upload, files are only selected (status: 'idle'). Pass name and the component keeps a hidden <input type="file" name="..."> in sync — every file without an error is written to it and submitted with the form.

PDF yoki DOCX, 2 MB gacha

PDF, DOCX · 2 MB gacha

Paste from the clipboard

By default (paste={true}), Ctrl+V adds a copied file or screenshot while focus is inside the component. paste="global" listens on the whole page (except inputs and textareas); paste={false} turns it off.

Rasmni nusxalang va sahifaning istalgan joyida Ctrl+V bosing

Faylni shu yerga tashlang yoki tanlangRasmlar · ko'pi bilan 3 ta

Props

Accepts the remaining <div> attributes (className, id, aria-* ...).

PropTypeDefaultDescription
acceptstring[]—Allowed types: ['image/*', '.pdf', 'application/zip']
maxSizenumber—Maximum size in bytes
maxFilesnumber—Maximum number of files (1 without multiple)
multiplebooleanfalseAllow several files
upload(file: File, ctx: FileUploadContext) => Promise<R>—Upload function. Without it, files are only selected
onFilesChange(files: FileUploadItem<R>[]) => void—Called with the full list on every change
variant'dropzone' | 'button''dropzone'Large drop area or a single button
disabledboolean—
invalidboolean—Automatic inside a Field with an error
requiredboolean—
namestring—Name of the <input type="file"> for forms
hintReactNode—Limits text; built from accept, maxSize and maxFiles if omitted
pasteboolean | 'global'trueCtrl+V: true — while focused, 'global' — anywhere on the page
getErrorMessage(error: unknown) => string | undefined—Turns an upload error into a message
labelsFileUploadLabels—All UI strings

FileUploadItem

Each item passed to onFilesChange:

PropTypeDefaultDescription
idstring—
fileFile—
status'idle' | 'uploading' | 'done' | 'error'—idle — no upload function, the file is just selected
progressnumber—0–100
errorstring—Error message
errorKind'type' | 'size' | 'upload'—Only upload errors get a Retry button
resultR—What upload resolved with
previewUrlstring—A blob: URL for images

Strings (labels)

prompt, browse, dragActive, dragReject, button, images, videos, audio, maxSize, maxFiles, tooManyFiles, invalidType, tooLarge, uploadError, uploaded, retry, cancel, remove, dismiss, list. The ones that include a size or a count are functions:

<FileUpload
  labels={{
    prompt: 'Drop a file here or',
    browse: 'browse',
    maxSize: (size) => `up to ${size}`,
    tooLarge: (size) => `Larger than ${size}`,
  }}
/>

matchesAccept(file, accept) is exported too, so you can apply the same rule elsewhere.

Data attributes

AttributeElementValue
data-variantrootdropzone · button
data-disabledrootpresent when disabled
data-invalidrootpresent when invalid
data-dragdropzoneidle · over · reject
data-statusfile row (li)idle · uploading · done · error

Accessibility

  • The dropzone has role="button", is reachable with Tab, and Enter or Space opens the file dialog. Its name combines the Field label and its own text; the limits text is read via aria-describedby.
  • Drag feedback ("Faylni qo'yib yuboring", "Bu turdagi fayl qabul qilinmaydi") is announced through aria-live="polite", and so is the maxFiles notice.
  • Progress uses role="progressbar" with aria-valuenow and the file name.
  • Row buttons include the file name: "Bekor qilish: report.pdf" (cancel, while uploading), "O'chirish: …" (remove), and "Olib tashlash: …" (dismiss) for errors.
  • The list is a <ul aria-label="Tanlangan fayllar">; long names are shortened in the middle, with the full name in title.
  • Errors are exposed as aria-invalid on the dropzone or button.

On this page