UploadFiles
Поле загрузки файлов с поддержкой drag-and-drop и клика. Валидирует выбранные файлы по формату, размеру и количеству, показывает прогресс и ошибки по каждому вложению и загружает их через переданную функцию upload. Компонент управляет только UI и состоянием вложений — сетевой запрос остаётся за потребителем.
Когда использовать
- Загрузка вложений в форме (договоры, изображения, отчёты) с превью и удалением.
- Когда нужна валидация формата/размера/количества файлов на клиенте до отправки.
- Когда загрузка идёт на ваш бэкенд через кастомный запрос — он передаётся в
upload.
Когда не нужен:
- Для одиночного триггера-кнопки без зоны перетаскивания — используйте
FileUploadиз @cloud-ru/ds-dropzone. - Для скрытой оверлей-зоны над произвольным контентом —
HiddenDropZoneиз @cloud-ru/ds-dropzone.
Анатомия
Accept (по умолчанию — любые файлы)
accept — массив допустимых типов файлов. По умолчанию [{ extention: '*' }] — принимаются файлы любого формата без иконок и подписей. Каждый элемент описывает:
extention— расширение файла (например.pdf, либо*для всех типов), попадает в нативный атрибутacceptдиалога выбора.displayExtension— человекочитаемое имя формата для подписи и текста ошибки (напримерPDF).icon— иконка вложения для файлов этого типа.
Лимит файлов (default 3)
maxFiles ограничивает количество вложений. При превышении поле показывает сводную ошибку и счётчик текущее/максимум.
Размер файла (default 5 МБ)
maxSize (в байтах) ограничивает размер одного файла. Файл больше лимита попадает во вложения как ошибочный, с подписью о превышении размера.
Установка
pnpm add @cloud-ru/ds-uikit-product-upload-files
import { UploadFiles } from '@cloud-ru/ds-uikit-product-upload-files'
Примеры использования
1. Базовое поле
Опционально
tsx
import { LocaleProvider } from '@cloud-ru/ds-locale';
import { UploadFileItem, UploadFiles } from '@cloud-ru/ds-uikit-product-upload-files';
import { useState } from 'react';
async function upload(file: File) {
await new Promise(resolve => setTimeout(resolve, 600));
return { url: `https://example.com/${file.name}` };
}
export function Basic() {
const [files, setFiles] = useState<UploadFileItem[]>([]);
return (
<LocaleProvider lang='ru-RU'>
<UploadFiles label='Документы' value={files} onChange={setFiles} upload={upload} />
</LocaleProvider>
);
}2. Свои форматы и лимиты
Опционально
tsx
import { LocaleProvider } from '@cloud-ru/ds-locale';
import { UploadFileItem, UploadFiles, UploadFilesAcceptItem } from '@cloud-ru/ds-uikit-product-upload-files';
import { useState } from 'react';
async function upload(file: File) {
await new Promise(resolve => setTimeout(resolve, 600));
return { url: `https://example.com/${file.name}` };
}
const accept: UploadFilesAcceptItem[] = [
{ extention: '.png', displayExtension: 'PNG' },
{ extention: '.jpg', displayExtension: 'JPG' },
];
export function CustomFormats() {
const [files, setFiles] = useState<UploadFileItem[]>([]);
return (
<LocaleProvider lang='ru-RU'>
<UploadFiles
label='Изображения'
hint='До 5 файлов, каждый не больше 2 МБ'
accept={accept}
maxFiles={5}
maxSize={2 * 1024 * 1024}
value={files}
onChange={setFiles}
upload={upload}
/>
</LocaleProvider>
);
}3. Обязательное поле формы
Обязательное поле
tsx
import { LocaleProvider } from '@cloud-ru/ds-locale';
import { UPLOAD_STATUS, UploadFileItem, UploadFiles } from '@cloud-ru/ds-uikit-product-upload-files';
import { useState } from 'react';
async function upload(file: File) {
await new Promise(resolve => setTimeout(resolve, 600));
return { url: `https://example.com/${file.name}` };
}
export function FormField() {
const [files, setFiles] = useState<UploadFileItem[]>([]);
const hasUploaded = files.some(item => item.status === UPLOAD_STATUS.Success);
return (
<LocaleProvider lang='ru-RU'>
<UploadFiles
label='Договор'
optional={false}
value={files}
onChange={setFiles}
upload={upload}
error={hasUploaded ? undefined : 'Обязательное поле'}
/>
</LocaleProvider>
);
}4. Заблокировано
Опционально
tsx
import { LocaleProvider } from '@cloud-ru/ds-locale';
import { UPLOAD_STATUS, UploadFileItem, UploadFiles } from '@cloud-ru/ds-uikit-product-upload-files';
async function upload(file: File) {
return { url: `https://example.com/${file.name}` };
}
function buildValue(): UploadFileItem[] {
return [
{
id: 'demo-1',
file: new File([new Uint8Array(1024)], 'договор.pdf', { type: 'application/pdf' }),
status: UPLOAD_STATUS.Success,
result: { url: 'https://example.com/договор.pdf' },
},
];
}
export function Disabled() {
return (
<LocaleProvider lang='ru-RU'>
<UploadFiles label='Документы' disabled value={buildValue()} upload={upload} />
</LocaleProvider>
);
}Do / Don’t
- ✅ Передавайте реальный сетевой запрос в
upload, возвращая промис с результатом загрузки. - ❌ Не делайте
uploadno-op — без промиса вложение зависнет в статусе загрузки. - ✅ Описывайте
acceptчерезdisplayExtension— формат попадёт в подсказку и текст ошибки. - ❌ Не дублируйте ограничения текстом в
hint, если они уже заданы черезmaxFiles/maxSize— описание дропзоны соберётся автоматически. - ✅ Используйте
errorдля ошибок уровня формы (например required из react-hook-form). - ❌ Не используйте
errorдля ошибок отдельных файлов — формат и размер компонент валидирует и показывает на вложениях сам. - ✅ Прерывайте загрузку через
ctx.signal(AbortSignal) внутриupload— компонент отменяет запрос при удалении и размонтировании. - ❌ Не игнорируйте
signal— отменённые запросы продолжат расходовать сеть.
Props
Types
Props
UploadFilesProps| Prop | Type | Default | Required | Description |
|---|---|---|---|---|
accept | UploadFilesAcceptItem[] | [{ extention: '*' }] | no | Допустимые типы файлов с иконкой для каждого расширения |
attachmentClassname | string | — | no | CSS-класс прикрепленного файла |
className | string | — | no | CSS-класс корня |
data-test-id | string | — | no | |
defaultValue | UploadFileItem<unknown>[] | — | no | Начальное значение |
disabled | boolean | false | no | Заблокировано |
error | string | — | no | Ошибка формы (например required из RHF) |
hint | ReactNode | — | no | Подсказка question tooltip у метки |
label | string | — | no | Текст метки поля |
maxFiles | number | 3 | no | Максимальное количество файлов |
maxSize | number | 5 * 1024 * 1024 | no | Максимальный размер файла в байтах |
name | string | — | no | Имя поля формы |
onBlur | FocusEventHandler<HTMLDivElement> | — | no | |
onChange | ((items: UploadFileItem<unknown>[]) => void) | — | no | Колбэк изменения значения |
optional | boolean | true | no | Показывает «Опционально» справа от метки |
upload | UploadFn<unknown> | — | yes | Обязательная кастомная функция загрузки |
value | UploadFileItem<unknown>[] | — | no | Контролируемое значение |