152 lines
5.1 KiB
TypeScript
152 lines
5.1 KiB
TypeScript
'use client';
|
|
|
|
import { useEffect } from 'react';
|
|
import { ChevronLeft, ChevronRight, Download, X } from 'lucide-react';
|
|
import { Button } from '@/components/ui/button';
|
|
import { MediaImageSkeleton } from '@/components/ui/media-image-skeleton';
|
|
import type { ChatMessage } from '@/lib/api';
|
|
import { cn } from '@/lib/utils';
|
|
|
|
interface ChatImageLightboxProps {
|
|
open: boolean;
|
|
images: ChatMessage[];
|
|
index: number;
|
|
mediaUrls: Record<string, { blobUrl?: string; fileName?: string } | undefined>;
|
|
onClose: () => void;
|
|
onIndexChange: (index: number) => void;
|
|
onDownload?: (message: ChatMessage) => void;
|
|
}
|
|
|
|
export function ChatImageLightbox({
|
|
open,
|
|
images,
|
|
index,
|
|
mediaUrls,
|
|
onClose,
|
|
onIndexChange,
|
|
onDownload
|
|
}: ChatImageLightboxProps) {
|
|
const current = images[index];
|
|
|
|
useEffect(() => {
|
|
if (!open) return;
|
|
function handleKeyDown(event: KeyboardEvent) {
|
|
if (event.key === 'Escape') onClose();
|
|
if (event.key === 'ArrowLeft') onIndexChange(Math.max(0, index - 1));
|
|
if (event.key === 'ArrowRight') onIndexChange(Math.min(images.length - 1, index + 1));
|
|
}
|
|
window.addEventListener('keydown', handleKeyDown);
|
|
return () => window.removeEventListener('keydown', handleKeyDown);
|
|
}, [open, index, images.length, onClose, onIndexChange]);
|
|
|
|
if (!open || !current?.storageKey) return null;
|
|
|
|
const media = mediaUrls[current.storageKey];
|
|
|
|
return (
|
|
<div className="fixed inset-0 z-[250] flex flex-col bg-[#0f1117]/96">
|
|
<div className="flex items-center justify-between px-4 py-3 text-white">
|
|
<div className="min-w-0">
|
|
<p className="truncate text-sm font-medium">{current.senderName}</p>
|
|
<p className="text-xs text-white/60">
|
|
{index + 1} из {images.length}
|
|
</p>
|
|
</div>
|
|
<div className="flex items-center gap-1">
|
|
{onDownload ? (
|
|
<Button
|
|
type="button"
|
|
size="icon"
|
|
variant="ghost"
|
|
className="text-white hover:bg-white/10"
|
|
aria-label="Скачать"
|
|
onClick={() => onDownload(current)}
|
|
>
|
|
<Download className="h-5 w-5" />
|
|
</Button>
|
|
) : null}
|
|
<Button
|
|
type="button"
|
|
size="icon"
|
|
variant="ghost"
|
|
className="text-white hover:bg-white/10"
|
|
aria-label="Закрыть"
|
|
onClick={onClose}
|
|
>
|
|
<X className="h-5 w-5" />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="relative flex flex-1 items-center justify-center px-3 pb-4">
|
|
<Button
|
|
type="button"
|
|
size="icon"
|
|
variant="ghost"
|
|
className={cn(
|
|
'absolute left-3 z-10 h-11 w-11 rounded-full text-white hover:bg-white/10',
|
|
index <= 0 && 'pointer-events-none opacity-30'
|
|
)}
|
|
aria-label="Предыдущее"
|
|
onClick={() => onIndexChange(Math.max(0, index - 1))}
|
|
>
|
|
<ChevronLeft className="h-6 w-6" />
|
|
</Button>
|
|
|
|
<div className="flex max-h-full max-w-full items-center justify-center">
|
|
{media?.blobUrl ? (
|
|
// eslint-disable-next-line @next/next/no-img-element
|
|
<img src={media.blobUrl} alt="" className="max-h-[calc(100vh-140px)] max-w-full object-contain" />
|
|
) : (
|
|
<MediaImageSkeleton className="h-[min(70vh,520px)] w-[min(92vw,720px)]" rounded="2xl" />
|
|
)}
|
|
</div>
|
|
|
|
<Button
|
|
type="button"
|
|
size="icon"
|
|
variant="ghost"
|
|
className={cn(
|
|
'absolute right-3 z-10 h-11 w-11 rounded-full text-white hover:bg-white/10',
|
|
index >= images.length - 1 && 'pointer-events-none opacity-30'
|
|
)}
|
|
aria-label="Следующее"
|
|
onClick={() => onIndexChange(Math.min(images.length - 1, index + 1))}
|
|
>
|
|
<ChevronRight className="h-6 w-6" />
|
|
</Button>
|
|
</div>
|
|
|
|
{current.content ? (
|
|
<div className="border-t border-white/10 px-4 py-3 text-sm text-white/85">{current.content}</div>
|
|
) : null}
|
|
|
|
{images.length > 1 ? (
|
|
<div className="flex gap-2 overflow-x-auto px-4 pb-4">
|
|
{images.map((image, imageIndex) => {
|
|
const thumb = image.storageKey ? mediaUrls[image.storageKey] : undefined;
|
|
return (
|
|
<button
|
|
key={image.id}
|
|
type="button"
|
|
className={cn(
|
|
'h-14 w-14 shrink-0 overflow-hidden rounded-lg border-2',
|
|
imageIndex === index ? 'border-[#3390ec]' : 'border-transparent opacity-70'
|
|
)}
|
|
onClick={() => onIndexChange(imageIndex)}
|
|
>
|
|
{thumb?.blobUrl ? (
|
|
// eslint-disable-next-line @next/next/no-img-element
|
|
<img src={thumb.blobUrl} alt="" className="h-full w-full object-cover" />
|
|
) : (
|
|
<MediaImageSkeleton className="h-full w-full min-h-0" rounded="none" />
|
|
)}
|
|
</button>
|
|
);
|
|
})}
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
);
|
|
}
|