152 lines
5.2 KiB
TypeScript
152 lines
5.2 KiB
TypeScript
import { Button } from '@/components/ui/button';
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogDescription,
|
|
DialogFooter,
|
|
DialogHeader,
|
|
DialogTitle
|
|
} from '@/components/ui/dialog';
|
|
import { ChevronLeft, ChevronRight } from 'lucide-react';
|
|
import { useEffect, useState } from 'react';
|
|
|
|
export type ImagePreviewItem = {
|
|
id?: string | number;
|
|
src: string | null;
|
|
sources?: string[];
|
|
title?: string;
|
|
description?: string;
|
|
};
|
|
|
|
type ImagePreviewModalProps = {
|
|
open: boolean;
|
|
onOpenChange: (open: boolean) => void;
|
|
src: string | null;
|
|
title?: string;
|
|
description?: string;
|
|
alt?: string;
|
|
sources?: string[];
|
|
items?: ImagePreviewItem[];
|
|
startIndex?: number;
|
|
};
|
|
|
|
export function ImagePreviewModal({
|
|
open,
|
|
onOpenChange,
|
|
src,
|
|
title,
|
|
description,
|
|
alt = 'Preview',
|
|
sources,
|
|
items,
|
|
startIndex = 0,
|
|
}: ImagePreviewModalProps) {
|
|
const [itemIndex, setItemIndex] = useState(startIndex);
|
|
const [imageIndex, setImageIndex] = useState(0);
|
|
|
|
useEffect(() => {
|
|
if (open) {
|
|
setItemIndex(startIndex);
|
|
setImageIndex(0);
|
|
}
|
|
}, [open, startIndex]);
|
|
|
|
useEffect(() => {
|
|
setImageIndex(0);
|
|
}, [itemIndex]);
|
|
|
|
const isMultiItem = items && items.length > 1;
|
|
const currentItem = isMultiItem ? items[itemIndex] : null;
|
|
|
|
const allImages = currentItem
|
|
? (currentItem.sources?.length ? currentItem.sources : currentItem.src ? [currentItem.src] : [])
|
|
: (sources?.length ? sources : src ? [src] : []);
|
|
|
|
const currentSrc = allImages[imageIndex] ?? allImages[0] ?? currentItem?.src ?? src;
|
|
const currentTitle = currentItem?.title ?? title;
|
|
const currentDescription = currentItem?.description ?? description;
|
|
const hasMultipleImages = allImages.length > 1;
|
|
|
|
function handleImagePrev() {
|
|
setImageIndex((prev) => prev === 0 ? allImages.length - 1 : prev - 1);
|
|
}
|
|
|
|
function handleImageNext() {
|
|
setImageIndex((prev) => prev === allImages.length - 1 ? 0 : prev + 1);
|
|
}
|
|
|
|
function handleItemPrev() {
|
|
setItemIndex((prev) => prev === 0 ? items.length - 1 : prev - 1);
|
|
}
|
|
|
|
function handleItemNext() {
|
|
setItemIndex((prev) => prev === items.length - 1 ? 0 : prev + 1);
|
|
}
|
|
|
|
return (
|
|
<Dialog
|
|
open={open}
|
|
onOpenChange={(v) => {
|
|
if (!v) {
|
|
setItemIndex(startIndex);
|
|
setImageIndex(0);
|
|
}
|
|
onOpenChange(v);
|
|
}}
|
|
>
|
|
<DialogContent showCloseButton>
|
|
{(currentTitle || currentDescription) && (
|
|
<DialogHeader>
|
|
{currentTitle && <DialogTitle>{currentTitle}</DialogTitle>}
|
|
{currentDescription && <DialogDescription>{currentDescription}</DialogDescription>}
|
|
</DialogHeader>
|
|
)}
|
|
{currentSrc && (
|
|
<div className="relative">
|
|
<img
|
|
src={currentSrc}
|
|
alt={alt}
|
|
className="max-h-[80vh] w-full rounded-lg object-contain"
|
|
/>
|
|
{hasMultipleImages && (
|
|
<>
|
|
<Button
|
|
variant="secondary"
|
|
size="icon"
|
|
className="absolute left-2 top-1/2 h-8 w-8 -translate-y-1/2 rounded-full bg-black/50 text-white hover:bg-black/70"
|
|
onClick={handleImagePrev}
|
|
>
|
|
<ChevronLeft className="h-4 w-4" />
|
|
</Button>
|
|
<Button
|
|
variant="secondary"
|
|
size="icon"
|
|
className="absolute right-2 top-1/2 h-8 w-8 -translate-y-1/2 rounded-full bg-black/50 text-white hover:bg-black/70"
|
|
onClick={handleImageNext}
|
|
>
|
|
<ChevronRight className="h-4 w-4" />
|
|
</Button>
|
|
<div className="absolute bottom-2 left-1/2 -translate-x-1/2 rounded-full bg-black/50 px-2 py-1 text-xs text-white">
|
|
{imageIndex + 1} / {allImages.length}
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
)}
|
|
{isMultiItem && (
|
|
<DialogFooter>
|
|
<Button variant="outline" onClick={handleItemPrev}>
|
|
<ChevronLeft className="h-4 w-4 mr-1" />
|
|
Sebelumnya
|
|
</Button>
|
|
<Button variant="outline" onClick={handleItemNext}>
|
|
Selanjutnya
|
|
<ChevronRight className="h-4 w-4 ml-1" />
|
|
</Button>
|
|
</DialogFooter>
|
|
)}
|
|
</DialogContent>
|
|
</Dialog>
|
|
);
|
|
}
|