Files
IdP/apps/frontend/components/documents/document-photo-viewer.tsx
2026-06-24 14:37:15 +03:00

110 lines
4.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import * as React from 'react';
import { ChevronLeft, ChevronRight, X, ZoomIn } from 'lucide-react';
import { cn } from '@/lib/utils';
interface DocumentFullscreenViewerProps {
images: { storageKey: string; url: string }[];
initialIndex?: number;
open: boolean;
onOpenChange: (open: boolean) => void;
}
export function DocumentFullscreenViewer({ images, initialIndex = 0, open, onOpenChange }: DocumentFullscreenViewerProps) {
const [index, setIndex] = React.useState(initialIndex);
React.useEffect(() => {
if (open) setIndex(initialIndex);
}, [initialIndex, open]);
React.useEffect(() => {
if (!open) return;
function onKeyDown(event: KeyboardEvent) {
if (event.key === 'Escape') onOpenChange(false);
if (event.key === 'ArrowLeft') setIndex((current) => Math.max(0, current - 1));
if (event.key === 'ArrowRight') setIndex((current) => Math.min(images.length - 1, current + 1));
}
window.addEventListener('keydown', onKeyDown);
return () => window.removeEventListener('keydown', onKeyDown);
}, [images.length, onOpenChange, open]);
if (!open || images.length === 0) return null;
const current = images[index];
return (
<div className="fixed inset-0 z-[300] flex flex-col bg-black/95">
<div className="flex items-center justify-between px-4 py-3 text-white">
<span className="text-sm text-white/80">
{index + 1} / {images.length}
</span>
<button type="button" onClick={() => onOpenChange(false)} className="rounded-full p-2 hover:bg-white/10" aria-label="Закрыть">
<X className="h-5 w-5" />
</button>
</div>
<div className="relative flex flex-1 items-center justify-center px-4 pb-6">
{images.length > 1 ? (
<button
type="button"
disabled={index === 0}
onClick={() => setIndex((value) => Math.max(0, value - 1))}
className="absolute left-4 z-10 rounded-full bg-white/10 p-2 text-white disabled:opacity-30"
aria-label="Предыдущее фото"
>
<ChevronLeft className="h-6 w-6" />
</button>
) : null}
{/* eslint-disable-next-line @next/next/no-img-element */}
<img src={current.url} alt="Фото документа" className="max-h-[calc(100vh-120px)] max-w-full object-contain" />
{images.length > 1 ? (
<button
type="button"
disabled={index === images.length - 1}
onClick={() => setIndex((value) => Math.min(images.length - 1, value + 1))}
className="absolute right-4 z-10 rounded-full bg-white/10 p-2 text-white disabled:opacity-30"
aria-label="Следующее фото"
>
<ChevronRight className="h-6 w-6" />
</button>
) : null}
</div>
</div>
);
}
interface DocumentPhotoThumbnailProps {
url?: string;
loading?: boolean;
className?: string;
onClick?: () => void;
}
export function DocumentPhotoThumbnail({ url, loading, className, onClick }: DocumentPhotoThumbnailProps) {
return (
<button
type="button"
onClick={onClick}
className={cn(
'group relative aspect-[4/3] overflow-hidden rounded-2xl border border-[#eceef4] bg-[#f4f5f8] transition hover:border-[#20212b]',
className
)}
>
{loading ? <div className="h-full w-full animate-pulse bg-[#eceef4]" /> : null}
{!loading && url ? (
<>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img src={url} alt="Фото документа" className="h-full w-full object-cover" />
<div className="absolute inset-0 flex items-center justify-center bg-black/0 transition group-hover:bg-black/20">
<ZoomIn className="h-6 w-6 text-white opacity-0 transition group-hover:opacity-100" />
</div>
</>
) : null}
{!loading && !url ? <div className="flex h-full items-center justify-center text-xs text-[#667085]">Нет превью</div> : null}
</button>
);
}