Attachment
@cloud-ru/ds-attachment — Пакет компонентов для отображения прикреплённых файлов — Attachment (горизонтальный) и AttachmentSquare (квадратный).
Пакет @cloud-ru/ds-attachment предоставляет два компонента для отображения прикреплённых файлов:
- Attachment — горизонтальный список-айтем с иконкой/превью, заголовком, описанием и actions.
- AttachmentSquare — квадратный тайл с hover-оверлеем; удобен для галерей превью.
Оба используют общие токены s / m, поддерживают loading/error/disabled/checked (множественный выбор) и автоматически показывают image-превью, если file — изображение.
Когда использовать
- В списках прикреплённых файлов с возможностью скачать/удалить —
Attachment. - В галереях превью со state
hovered-overlay —AttachmentSquare.
Когда не нужен пакет: для drag&drop-загрузки — Dropzone; для одиночных preview-карточек без actions — Card.
Анатомия
Оба компонента строятся вокруг трёх слотов: превью (иконка или image из file), текстовый блок (заголовок + описание / сообщение об ошибке), панель actions (download / delete, или retry при error).
Установка
pnpm add @cloud-ru/ds-attachment
import { Attachment, AttachmentSquare } from '@cloud-ru/ds-attachment'
Attachment
Горизонтальный айтем прикреплённого файла с превью/иконкой, заголовком, описанием, ошибкой и actions (download/delete/retry).
Горизонтальный айтем прикреплённого файла. Слева — иконка типа файла или превью (если file — изображение), в середине — заголовок и описание (или сообщение об ошибке), справа — actions (download / delete, или retry в состоянии ошибки).
Когда использовать
- В списках прикреплённых файлов с возможностью скачать/удалить.
- Когда у вас есть
File-объект — заголовок и расширение проставляются автоматически.
Когда не нужен Attachment:
- Для галерей превью — используйте
AttachmentSquare. - Для drag&drop загрузки — используйте
Dropzone.
Анатомия
Size (default s)
s— компактная строка, высота 48px (emblem 32 + 2×8 padding).m— увеличенная строка, высота 56px (emblem 40 + 2×8 padding).
Высота строки content-driven: задаётся размером emblem'а и вертикальным padding'ом Card-корня (8px фиксированно). Токены attachment.anatomy.size.*.container.height относятся к AttachmentSquare, а не к строке.
State
Состояния выражаются через смежные пропы (а не через одну ось):
loading— спиннер вместо иконки, actions скрыты.disabled— карточка не реагирует на клик; actions всё равно доступны если passedonDelete/onDownload.error— показывается ошибочное сообщение, actionretryзаменяетdownload; фон карточки переключается наred/background.checked— accent-borderprimary/accent+ accent-state-layer (activated/default/filled, на hover —activated/hovered/filled) поверх фона + check-badge в левом верхнем углу.
Внутри использует Card с view='outline'; реакция на курсор (cursor: pointer, focus-ring) подключается только если задан onClick.
Примеры использования
Базовый Attachment
File с автозаголовком и onDownload
import { Attachment } from '@cloud-ru/ds-attachment';
import { PDF_FILE } from './sample';
export function BasicAttachment() {
return <Attachment file={PDF_FILE} onDownload={file => console.info('download', file?.name)} />;
}
С изображением
Превью автоматически из image-файла через useImage
import { Attachment } from '@cloud-ru/ds-attachment';
import { useEffect, useState } from 'react';
import pictureUrl from './picture.jpg?url';
export function AttachmentWithImage() {
const [file, setFile] = useState<File>();
useEffect(() => {
fetch(pictureUrl)
.then(r => r.blob())
.then(blob => setFile(new File([blob], 'picture.jpg', { type: 'image/jpg' })));
}, []);
return (
<Attachment
file={file}
title='Скриншот'
description='JPG'
onDownload={f => console.info('download', f?.name)}
onDelete={f => console.info('delete', f?.name)}
/>
);
}
Loading
Спиннер вместо иконки, actions скрыты
import { Attachment } from '@cloud-ru/ds-attachment';
import { PDF_FILE } from './sample';
export function AttachmentLoading() {
return <Attachment file={PDF_FILE} loading />;
}
Ошибка с retry
error заменяет description; retry button заменяет download
import { Attachment } from '@cloud-ru/ds-attachment';
import { useState } from 'react';
import { PDF_FILE } from './sample';
export function AttachmentError() {
const [retries, setRetries] = useState(0);
return (
<Attachment
file={PDF_FILE}
error={`Не удалось загрузить (попыток: ${retries})`}
onRetry={() => setRetries(n => n + 1)}
onDelete={file => console.info('delete', file?.name)}
/>
);
}
Выбираемый (controlled)
checked + onClick — типичный паттерн multiSelect
import { Attachment } from '@cloud-ru/ds-attachment';
import { useState } from 'react';
import { PDF_FILE } from './sample';
export function AttachmentSelectable() {
const [checked, setChecked] = useState(false);
return (
<Attachment
file={PDF_FILE}
checked={checked}
onClick={() => setChecked(v => !v)}
onDelete={file => console.info('delete', file?.name)}
/>
);
}
Props
AttachmentProps
| Prop | Type | Default | Description |
|---|---|---|---|
checked | boolean | — | Состояние «выбран» (множественный выбор) |
className | string | — | CSS-класс корня |
data-test-id | string | — | |
description | string | fileExtension | Описание |
disabled | boolean | — | Заблокировано |
error | string | — | Сообщение об ошибке |
file | File | — | Файл |
fullWidth | boolean | true | Растянуть карточку на всю ширину контейнера. Выключенная — ужимается по содержимому. |
icon | JSXElementConstructor<{ size?: number; className?: string; }> | undefined | — | Иконка для файла @defaultIcon FileSVG |
loading | boolean | — | Управление состоянием загрузки |
onClick | ((event: MouseEvent<HTMLDivElement, MouseEvent>) => void) | — | Клик по карточке |
onDelete | ((file?: File) => void) | — | Колбек на клик по кнопке удаления |
onDownload | ((file?: File) => void) | — | Колбек на клик по кнопке скачивания |
onRetry | (() => void) | — | Колбек на клик по кнопке повторения |
size | "m" | "s" | s | Размер |
title | string | fileName | Заголовок |
truncate | { title?: number; description?: number; error?: number | undefined; } | undefined | — | |
truncateVariant | "end" | "middle" | — | Вариант обрезания строки: - end — с конца; - middle — по середине |
Related types
Size="m"|"s"
AttachmentSquare
Квадратный тайл прикреплённого файла с hover-оверлеем — для галерей превью и компактных списков.
Квадратный тайл прикреплённого файла. По умолчанию показывает превью или иконку; при hover (или фокусе на actions) поверх появляется overlay с заголовком/описанием и actions.
Когда использовать
- В галереях превью и сетках, где у файлов есть визуальный смысл.
- Когда важна плотность —
s(80×80) иm(100×100).
Анатомия
Size (default s)
s— 80×80.m— 100×100.
Hover-overlay
При hover и при фокусе на любой action-кнопке (download/delete/retry) над основным контентом появляется overlay с заголовком/описанием и actions. В image-mode overlay плотный (solid neutral/background1Level) и перекрывает превью; в icon-mode — прозрачный, текст MainContent проступает сквозь, а Actions подменяют слот иконки. Состояние «фокус удерживает overlay» реализовано через внутренний контекст AttachmentFocusActionsContext.
State
loading— спиннер вместо превью, actions скрыты, hover-overlay отключён.disabled— карточка не реагирует на клик, opacity снижается доeffect/opacity/disabled.error— фон тайла переключается наred/background, actionretryзаменяетdownload; в error-режиме overlay прозрачный, чтобы красная заливка пропускалась.checked— accent-border + собственныйcheckedOverlayповерх MainContent с токенамиactivated/default/filled→activated/hovered/filled(Card-овский state-layer закрыт full-bleed image, поэтому overlay рендерится отдельным слоем). Check-badge в верхнем левом углу.
Примеры использования
Базовый
Файл с иконкой и кнопками
import { AttachmentSquare } from '@cloud-ru/ds-attachment';
import { PDF_FILE } from './sample';
export function AttachmentSquareBasic() {
return (
<AttachmentSquare
file={PDF_FILE}
onDownload={file => console.info('download', file?.name)}
onDelete={file => console.info('delete', file?.name)}
/>
);
}
С изображением (size m)
Превью автоматически из image-файла
import { AttachmentSquare } from '@cloud-ru/ds-attachment';
import { useEffect, useState } from 'react';
import pictureUrl from './picture.jpg?url';
export function AttachmentSquareImage() {
const [file, setFile] = useState<File>();
useEffect(() => {
fetch(pictureUrl)
.then(r => r.blob())
.then(blob => setFile(new File([blob], 'picture.jpg', { type: 'image/jpg' })));
}, []);
return (
<AttachmentSquare
size='m'
file={file}
onDownload={f => console.info('download', f?.name)}
onDelete={f => console.info('delete', f?.name)}
/>
);
}
Props
AttachmentSquareProps
| Prop | Type | Default | Description |
|---|---|---|---|
checked | boolean | — | Состояние «выбран» (множественный выбор) |
className | string | — | CSS-класс корня |
data-test-id | string | — | |
description | string | fileExtension | Описание |
disabled | boolean | — | Заблокировано |
error | string | — | Сообщение об ошибке |
file | File | — | Файл |
icon | JSXElementConstructor<{ size?: number; className?: string; }> | undefined | — | Иконка для файла @defaultIcon FileSVG |
loading | boolean | — | Управление состоянием загрузки |
onClick | ((event: MouseEvent<HTMLDivElement, MouseEvent>) => void) | — | Клик по карточке |
onDelete | ((file?: File) => void) | — | Колбек на клик по кнопке удаления |
onDownload | ((file?: File) => void) | — | Колбек на клик по кнопке скачивания |
onRetry | (() => void) | — | Колбек на клик по кнопке повторения |
size | "m" | "s" | s | Размер |
title | string | fileName | Заголовок |
truncateVariant | "end" | "middle" | — | Вариант обрезания строки: - end — с конца; - middle — по середине |
Related types
Size="m"|"s"