This commit is contained in:
lendry
2026-07-10 12:37:54 +03:00
parent f00f3d411d
commit 7fc3ca7952
3 changed files with 77 additions and 23 deletions

View File

@@ -6,13 +6,14 @@ import { Button } from '@/components/ui/button';
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { Input } from '@/components/ui/input';
import { createFilePreviewUrl, isCompressibleImageFile, readImageDimensions } from '@/lib/chat-image-compress';
import { formatFileSize } from '@/lib/chat-media';
import { formatFileSize, getComposeDialogTitle, getComposeMediaKind, type ComposeMediaKind } from '@/lib/chat-media';
import { cn } from '@/lib/utils';
export interface ChatMediaComposeItem {
id: string;
file: File;
previewUrl: string | null;
mediaKind: ComposeMediaKind;
sendAsFile: boolean;
width?: number;
height?: number;
@@ -25,6 +26,7 @@ function createComposeItem(file: File, sendAsFile = false): ChatMediaComposeItem
id: crypto.randomUUID(),
file,
previewUrl: createFilePreviewUrl(file),
mediaKind: getComposeMediaKind(file),
sendAsFile
};
}
@@ -159,32 +161,21 @@ export function ChatMediaComposeDialog({
}: ChatMediaComposeDialogProps) {
const addInputRef = useRef<HTMLInputElement>(null);
const activePreview = items[0] ?? null;
const dialogTitle = getComposeDialogTitle(items.map((item) => item.file));
return (
<Dialog open={open} onOpenChange={(next) => !next && onClose()}>
<DialogContent className="overflow-hidden rounded-[20px] p-0 sm:max-w-[460px]">
<div className="border-b border-[#eceef4] px-5 py-4">
<DialogHeader className="mb-0">
<DialogTitle>{items.length > 1 ? `Отправить ${items.length} файла` : 'Отправить изображение'}</DialogTitle>
<DialogTitle>{dialogTitle}</DialogTitle>
</DialogHeader>
</div>
<div className="space-y-4 px-5 py-4">
{activePreview ? (
<div className="relative overflow-hidden rounded-2xl bg-[#0f1117]">
{activePreview.previewUrl ? (
// eslint-disable-next-line @next/next/no-img-element
<img
src={activePreview.previewUrl}
alt={activePreview.file.name}
className="mx-auto max-h-[320px] w-full object-contain"
/>
) : (
<div className="flex min-h-[180px] flex-col items-center justify-center gap-2 px-6 text-center text-white/80">
<p className="text-sm font-medium">{activePreview.file.name}</p>
<p className="text-xs text-white/55">{formatFileSize(activePreview.file.size)}</p>
</div>
)}
<ComposeMediaPreview item={activePreview} className="mx-auto max-h-[320px] w-full object-contain" />
<button
type="button"
className="absolute right-3 top-3 rounded-full bg-black/55 p-1.5 text-white transition hover:bg-black/70"
@@ -206,18 +197,15 @@ export function ChatMediaComposeDialog({
onClick={() => onRemoveItem(item.id)}
title="Нажмите, чтобы удалить"
>
{item.previewUrl ? (
// eslint-disable-next-line @next/next/no-img-element
<img src={item.previewUrl} alt="" className="h-full w-full object-cover" />
) : (
<div className="flex h-full items-center justify-center px-1 text-[10px] text-[#667085]">{item.file.name.slice(0, 8)}</div>
)}
<ComposeMediaPreview item={item} className="h-full w-full object-cover" thumbnail />
<span className="absolute inset-0 bg-black/0 transition group-hover:bg-black/20" />
</button>
))}
</div>
) : activePreview?.previewUrl ? (
) : activePreview?.mediaKind === 'image' && activePreview.previewUrl ? (
<p className="text-center text-xs text-[#667085]">Для редактирования нажмите на изображение</p>
) : activePreview?.mediaKind === 'video' ? (
<p className="text-center text-xs text-[#667085]">Проверьте видео перед отправкой</p>
) : null}
{hasImages ? (
@@ -314,6 +302,43 @@ export function ChatMediaDropOverlay({ visible, onDropCompressed, onDropAsFile }
);
}
function ComposeMediaPreview({
item,
className,
thumbnail = false
}: {
item: ChatMediaComposeItem;
className?: string;
thumbnail?: boolean;
}) {
if (item.mediaKind === 'video' && item.previewUrl) {
return (
<video
src={item.previewUrl}
className={cn('bg-black', className)}
controls={!thumbnail}
playsInline
muted={thumbnail}
preload="metadata"
/>
);
}
if (item.mediaKind === 'image' && item.previewUrl) {
return (
// eslint-disable-next-line @next/next/no-img-element
<img src={item.previewUrl} alt={item.file.name} className={className} />
);
}
return (
<div className="flex min-h-[180px] flex-col items-center justify-center gap-2 px-6 text-center text-white/80">
<p className="text-sm font-medium">{item.file.name}</p>
<p className="text-xs text-white/55">{formatFileSize(item.file.size)}</p>
</div>
);
}
function DropZone({
title,
subtitle,