FileUpload
Fayl yuklash: sudrab tashlash, tanlash yoki Ctrl+V; har bir fayl uchun progress, bekor qilish va qayta urinish.
Jami: 0 · yuklandi: 0
'use client';
import { Field, FileUpload, type FileUploadContext, type FileUploadItem } from '@akhatmirzo/ui';
import { useState } from 'react';
const MB = 1024 * 1024;
/** Soxta yuklash: progress bilan ~2 soniya. Nomida "xato" bo'lsa — tarmoq xatosi */
function fakeUpload(file: File, { onProgress, signal }: FileUploadContext) {
return new Promise<{ url: string }>((resolve, reject) => {
let progress = 0;
const timer = setInterval(() => {
progress = Math.min(100, progress + 8);
onProgress(progress);
if (file.name.includes('xato') && progress > 50) {
clearInterval(timer);
reject(new Error('network'));
} else if (progress === 100) {
clearInterval(timer);
resolve({ url: `/uploads/${encodeURIComponent(file.name)}` });
}
}, 150);
signal.addEventListener('abort', () => clearInterval(timer));
});
}
export default function FileUploadBasic() {
const [files, setFiles] = useState<FileUploadItem[]>([]);
const done = files.filter((f) => f.status === 'done').length;
return (
<div className="flex w-full max-w-lg flex-col gap-2">
<Field label="Hujjatlar">
<FileUpload
accept={['image/*', '.pdf']}
maxSize={5 * MB}
maxFiles={5}
multiple
upload={fakeUpload}
onFilesChange={setFiles}
/>
</Field>
<p className="text-sm text-text-muted">
Jami: {files.length} · yuklandi: {done}
</p>
</div>
);
}
Import
import { FileUpload } from '@akhatmirzo/ui';Qachon ishlatish
- Hujjat, rasm, skrinshot yuklash — ariza, profil, murojaat formalarida. Faylni sudrab
tashlash, tanlash oynasi va
Ctrl+V— uchala yo'l bitta komponentda. - Cheklovlarni oldindan ayting:
accept,maxSize,maxFilesberilsa, ostida "Rasmlar, PDF · 5 MB gacha · ko'pi bilan 5 ta" kabi matn avtomatik chiqadi va qoidaga mos kelmagan fayl darhol xato bilan belgilanadi. - Darhol yuklash kerak bo'lsa —
uploadfunksiyasini bering: progress, bekor qilish va "Qayta urinish" tayyor. - Forma bilan birga yuborish kerak bo'lsa —
uploadbermang,namebering: fayllar oddiy<input type="file">dagidekFormDataga tushadi. - Joy tor bo'lsa —
variant="button": dropzone o'rniga bitta tugma.
Yuklash funksiyasi
upload(file, { onProgress, signal }) har bir mos fayl uchun alohida chaqiriladi va Promise
qaytaradi. Progressni onProgress(0–100) bilan bering; signal — foydalanuvchi ✕ ni bosganda
yoki komponent yopilganda bekor qilinadi. Promise natijasi item.result ga yoziladi.
import type { FileUploadContext } from '@akhatmirzo/ui';
/** XMLHttpRequest — fetch'dan farqli o'laroq yuborish progressini beradi */
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);
});
}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(',')} />
</>
);
}Promise rad etilsa, fayl qatorida xato chiqadi va "Qayta urinish" tugmasi paydo bo'ladi.
Xato matnini getErrorMessage(error) bilan o'zingiz tanlaysiz (standart — "Tarmoq xatosi"). Yuqoridagi
demoda nomida xato bo'lgan fayl ataylab xato beradi.
Holat komponent ichida
FileUpload fayllar ro'yxatini o'zi boshqaradi — value prop'i yo'q. Natijani olish uchun
onFilesChange dan foydalaning: u har bir o'zgarishda (qo'shildi, progress, tayyor, xato,
o'chirildi) to'liq ro'yxatni beradi.
Holatlar
Bitta fayl rejimida (multiple yo'q) yangi fayl eskisini almashtiradi. Fayl sudralganda mos tur —
primary chegara, mos kelmagan tur — danger va "Bu turdagi fayl qabul qilinmaydi". Field
dagi error, disabled va required avtomatik ulanadi. hint — o'z cheklov matningiz.
Bitta fayl — yangisi eskisini almashtiradi
Kamida bitta hujjat yuklang
'use client';
import { Field, FileUpload } from '@akhatmirzo/ui';
export default function FileUploadStates() {
return (
<div className="grid w-full gap-4 md:grid-cols-2">
<Field label="Faqat rasm" description="Bitta fayl — yangisi eskisini almashtiradi">
<FileUpload accept={['image/png', 'image/jpeg']} maxSize={1024 * 1024} />
</Field>
<Field label="Shartnoma" error="Kamida bitta hujjat yuklang" required>
<FileUpload accept={['.pdf']} />
</Field>
<Field label="O'chirilgan" disabled>
<FileUpload />
</Field>
<Field label="O'z cheklov matni bilan">
<FileUpload hint="Excel yoki CSV · 10 MB gacha" accept={['.xlsx', '.csv']} />
</Field>
</div>
);
}
Qoidaga mos kelmagan fayllar ham ro'yxatga tushadi — sababi bilan ("Hajmi 5 MB dan katta"), lekin
yuklanmaydi va maxFiles hisobiga kirmaydi. maxFiles dan oshgan fayllar umuman qo'shilmaydi,
ostida "Ko'pi bilan 5 ta fayl yuklash mumkin" xabari chiqadi.
Tugma varianti
variant="button" — dropzone o'rniga "Fayl tanlash" tugmasi. Fayllar ro'yxati xuddi shunday
ishlaydi.
Old va orqa tomoni
'use client';
import { Field, FileUpload } from '@akhatmirzo/ui';
export default function FileUploadButton() {
return (
<div className="w-full max-w-sm">
<Field label="Pasport nusxasi" description="Old va orqa tomoni">
<FileUpload
variant="button"
accept={['image/*', '.pdf']}
maxSize={5 * 1024 * 1024}
maxFiles={2}
multiple
/>
</Field>
</div>
);
}
Forma bilan
upload bo'lmasa, fayllar faqat tanlanadi (status: 'idle'). name bersangiz, komponent ichida
yashirin <input type="file" name="..."> bo'ladi — xatosiz fayllar unga yoziladi va forma
yuborilganda FormData ga tushadi.
'use client';
import { formatFileSize } from '@akhatmirzo/core';
import { Button, Field, FileUpload } from '@akhatmirzo/ui';
import { useState, type FormEvent } from 'react';
export default function FileUploadForm() {
const [sent, setSent] = useState<string | null>(null);
const onSubmit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
const files = (new FormData(e.currentTarget).getAll('rezyume') as File[]).filter(
(f) => f.size > 0,
);
setSent(files.map((f) => `${f.name} (${formatFileSize(f.size)})`).join(', ') || "Fayl yo'q");
};
return (
<form onSubmit={onSubmit} className="flex w-full max-w-sm flex-col gap-3">
<Field label="Rezyume" description="PDF yoki DOCX, 2 MB gacha">
<FileUpload
variant="button"
name="rezyume"
accept={['.pdf', '.docx']}
maxSize={2 * 1024 * 1024}
/>
</Field>
<div className="flex items-center gap-3">
<Button type="submit" variant="primary" size="sm">
Yuborish
</Button>
{sent && <span className="text-sm text-text-muted">{sent}</span>}
</div>
</form>
);
}
Clipboard'dan joylash
Standart holatda (paste={true}) komponent ichida fokus bo'lsa, Ctrl+V bilan nusxalangan fayl
yoki skrinshot qo'shiladi. paste="global" — sahifaning istalgan joyida (input va textarea'dan
tashqari) ishlaydi; paste={false} — o'chiq.
Rasmni nusxalang va sahifaning istalgan joyida Ctrl+V bosing
'use client';
import { Field, FileUpload } from '@akhatmirzo/ui';
export default function FileUploadPaste() {
return (
<div className="w-full max-w-lg">
<Field
label="Skrinshot"
description="Rasmni nusxalang va sahifaning istalgan joyida Ctrl+V bosing"
>
<FileUpload accept={['image/*']} multiple maxFiles={3} paste="global" />
</Field>
</div>
);
}
Props
Qolgan <div> atributlarini (className, id, aria-* ...) qabul qiladi.
| Prop | Tip | Standart | Tavsif |
|---|---|---|---|
accept | string[] | — | Ruxsat etilgan turlar: ['image/*', '.pdf', 'application/zip'] |
maxSize | number | — | Eng katta hajm, bayt |
maxFiles | number | — | Eng ko'p fayl soni (multiple bo'lmasa — 1) |
multiple | boolean | false | Bir nechta fayl |
upload | (file: File, ctx: FileUploadContext) => Promise<R> | — | Yuklash funksiyasi. Berilmasa — fayllar faqat tanlanadi |
onFilesChange | (files: FileUploadItem<R>[]) => void | — | Ro'yxat o'zgarganda — to'liq ro'yxat |
variant | 'dropzone' | 'button' | 'dropzone' | Katta maydon yoki bitta tugma |
disabled | boolean | — | |
invalid | boolean | — | Field ichida error bo'lsa avtomatik |
required | boolean | — | |
name | string | — | Forma uchun <input type="file"> nomi |
hint | ReactNode | — | Cheklovlar matni; berilmasa accept, maxSize, maxFiles dan yasaladi |
paste | boolean | 'global' | true | Ctrl+V: true — fokusda, 'global' — sahifaning istalgan joyida |
getErrorMessage | (error: unknown) => string | undefined | — | Yuklash xatosidan matn |
labels | FileUploadLabels | — | Barcha matnlar |
FileUploadItem
onFilesChange dagi har bir element:
| Prop | Tip | Standart | Tavsif |
|---|---|---|---|
id | string | — | |
file | File | — | |
status | 'idle' | 'uploading' | 'done' | 'error' | — | idle — upload yo'q, fayl faqat tanlangan |
progress | number | — | 0–100 |
error | string | — | Xato matni |
errorKind | 'type' | 'size' | 'upload' | — | Faqat upload xatosida "Qayta urinish" chiqadi |
result | R | — | upload qaytargan natija |
previewUrl | string | — | Rasm bo'lsa — blob: manzil |
Matnlar (labels)
prompt, browse, dragActive, dragReject, button, images, videos, audio, maxSize,
maxFiles, tooManyFiles, invalidType, tooLarge, uploadError, uploaded, retry,
cancel, remove, dismiss, list. Hajm va son bilan bog'liqlari — funksiya:
<FileUpload
labels={{
prompt: 'Drop a file here or',
browse: 'browse',
maxSize: (size) => `up to ${size}`,
tooLarge: (size) => `Larger than ${size}`,
}}
/>matchesAccept(file, accept) ham eksport qilinadi — server tekshiruvidan oldin xuddi shu qoidani
ishlatish uchun.
Data atributlari
| Atribut | Element | Qiymat |
|---|---|---|
data-variant | ildiz | dropzone · button |
data-disabled | ildiz | disabled bo'lganda mavjud |
data-invalid | ildiz | xato bo'lganda mavjud |
data-drag | dropzone | idle · over · reject |
data-status | fayl qatori (li) | idle · uploading · done · error |
Accessibility
- Dropzone —
role="button",Tabbilan fokuslanadi,EnteryokiSpacefayl tanlash oynasini ochadi. NomiFieldlabel'i va ichidagi matndan yig'iladi; cheklovlar matniaria-describedbyorqali o'qiladi. - Sudrash holati ("Faylni qo'yib yuboring", "Bu turdagi fayl qabul qilinmaydi")
aria-live="polite"bilan e'lon qilinadi;maxFilesdan oshish xabari ham. - Progress —
role="progressbar",aria-valuenowva fayl nomi bilan. - Qator tugmalari fayl nomi bilan nomlangan: "Bekor qilish: hisobot.pdf" (yuklanayotganda), "O'chirish: …", xatoda — "Olib tashlash: …".
- Ro'yxat —
<ul aria-label="Tanlangan fayllar">; uzun nomlar o'rtadan qisqartiriladi, to'liq nomtitleda. - Xato
aria-invalidsifatida dropzone/tugmaga beriladi.