dstpabuaran.com/resources/js/components/dialogs/image-preview-modal.tsx

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>
);
}