fix: файлы в поле отображаются горизонтально с переносом по ширине
- FileFieldRow: список файлов в одну строку, перенос на следующую только при нехватке места - 'Добавить файл' идёт в той же строке после файлов - Добавлена лёгкая подложка bg-muted/40 вокруг каждого файла для визуального разделения
This commit is contained in:
@@ -533,72 +533,74 @@ function FileFieldRow({
|
||||
<Upload className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex-1 min-w-0 flex flex-wrap items-center gap-x-2 gap-y-0.5">
|
||||
<strong>{field.name}:</strong>{' '}
|
||||
{field.maxFileCount != null && (
|
||||
<span className="text-xs text-muted-foreground ml-1">({files.length}/{field.maxFileCount})</span>
|
||||
<span className="text-xs text-muted-foreground">({files.length}/{field.maxFileCount})</span>
|
||||
)}
|
||||
{isUploading ? (
|
||||
<span className="text-muted-foreground text-sm">Загрузка...</span>
|
||||
) : files.length > 0 ? (
|
||||
<div className="flex flex-col gap-0.5 mt-0.5">
|
||||
{files.map((fileInfo, idx) => {
|
||||
const vType = detectViewerType(fileInfo.url, fileInfo.name);
|
||||
const canPreview = vType === 'pdf' || vType === 'word' || vType === 'excel' || vType === 'powerpoint' || vType === 'video' || vType === 'audio' || vType === 'image';
|
||||
return (
|
||||
<span key={idx} className="inline-flex items-center gap-1 group/file">
|
||||
{canPreview ? (
|
||||
<>
|
||||
<div className="inline-flex flex-wrap items-center gap-x-2 gap-y-0.5">
|
||||
{files.map((fileInfo, idx) => {
|
||||
const vType = detectViewerType(fileInfo.url, fileInfo.name);
|
||||
const canPreview = vType === 'pdf' || vType === 'word' || vType === 'excel' || vType === 'powerpoint' || vType === 'video' || vType === 'audio' || vType === 'image';
|
||||
return (
|
||||
<span key={idx} className="inline-flex items-center gap-1 group/file bg-muted/40 hover:bg-muted/60 rounded px-1.5 py-0.5">
|
||||
{canPreview ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setViewerFile({ url: fileInfo.url, name: fileInfo.name })}
|
||||
className="inline-flex items-center gap-1 text-sm text-primary hover:underline"
|
||||
data-testid={`file-link-${field.code}-${idx}`}
|
||||
>
|
||||
<FileText className="w-3 h-3" />
|
||||
{fileInfo.name}
|
||||
{fileInfo.size !== undefined && (
|
||||
<span className="text-muted-foreground text-xs">({formatFileSize(fileInfo.size)})</span>
|
||||
)}
|
||||
</button>
|
||||
) : (
|
||||
<a
|
||||
href={addFileToken(fileInfo.url)}
|
||||
onClick={(e) => { e.preventDefault(); openFilePresigned(fileInfo.url); }}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex items-center gap-1 text-sm text-primary hover:underline"
|
||||
data-testid={`file-link-${field.code}-${idx}`}
|
||||
>
|
||||
<FileText className="w-3 h-3" />
|
||||
{fileInfo.name}
|
||||
{fileInfo.size !== undefined && (
|
||||
<span className="text-muted-foreground text-xs">({formatFileSize(fileInfo.size)})</span>
|
||||
)}
|
||||
<Download className="w-3 h-3 text-muted-foreground" />
|
||||
</a>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setViewerFile({ url: fileInfo.url, name: fileInfo.name })}
|
||||
className="inline-flex items-center gap-1 text-sm text-primary hover:underline"
|
||||
data-testid={`file-link-${field.code}-${idx}`}
|
||||
onClick={() => handleRemoveFile(idx)}
|
||||
className="opacity-0 group-hover/file:opacity-100 transition-opacity text-muted-foreground hover:text-destructive"
|
||||
title="Удалить файл"
|
||||
data-testid={`file-clear-${field.code}-${idx}`}
|
||||
>
|
||||
<FileText className="w-3 h-3" />
|
||||
{fileInfo.name}
|
||||
{fileInfo.size !== undefined && (
|
||||
<span className="text-muted-foreground text-xs ml-1">({formatFileSize(fileInfo.size)})</span>
|
||||
)}
|
||||
<X className="w-3 h-3" />
|
||||
</button>
|
||||
) : (
|
||||
<a
|
||||
href={addFileToken(fileInfo.url)}
|
||||
onClick={(e) => { e.preventDefault(); openFilePresigned(fileInfo.url); }}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex items-center gap-1 text-sm text-primary hover:underline"
|
||||
data-testid={`file-link-${field.code}-${idx}`}
|
||||
>
|
||||
<FileText className="w-3 h-3" />
|
||||
{fileInfo.name}
|
||||
{fileInfo.size !== undefined && (
|
||||
<span className="text-muted-foreground text-xs ml-1">({formatFileSize(fileInfo.size)})</span>
|
||||
)}
|
||||
<Download className="w-3 h-3 text-muted-foreground" />
|
||||
</a>
|
||||
)}
|
||||
<button
|
||||
onClick={() => handleRemoveFile(idx)}
|
||||
className="opacity-0 group-hover/file:opacity-100 transition-opacity ml-1 text-muted-foreground hover:text-destructive"
|
||||
title="Удалить файл"
|
||||
data-testid={`file-clear-${field.code}-${idx}`}
|
||||
>
|
||||
<X className="w-3 h-3" />
|
||||
</button>
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{(field.maxFileCount == null || files.length < field.maxFileCount) && (
|
||||
<button
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
disabled={isUploading}
|
||||
className="inline-flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground mt-0.5 w-fit disabled:opacity-30"
|
||||
className="inline-flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground disabled:opacity-30"
|
||||
>
|
||||
<Upload className="w-3 h-3" />
|
||||
Добавить файл
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<span className="text-muted-foreground text-sm" data-testid={`file-empty-${field.code}`}>Нет файла</span>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user