Files
cozsweet-frontend-nextjs/src/app/private-room/components/private-album-gallery.tsx
T

114 lines
3.4 KiB
TypeScript

"use client";
import { useEffect, useRef } from "react";
import Image from "next/image";
import { ChevronLeft, ChevronRight, X } from "lucide-react";
import type { PrivateAlbum } from "@/data/dto/private-room";
import styles from "../private-room-screen.module.css";
export interface PrivateAlbumGalleryProps {
album: PrivateAlbum;
imageIndex: number;
onClose: () => void;
onImageIndexChange: (index: number) => void;
}
export function PrivateAlbumGallery({
album,
imageIndex,
onClose,
onImageIndexChange,
}: PrivateAlbumGalleryProps) {
const touchStartXRef = useRef<number | null>(null);
const currentImage = album.images[imageIndex];
const hasPrevious = imageIndex > 0;
const hasNext = imageIndex < album.images.length - 1;
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === "Escape") onClose();
if (event.key === "ArrowLeft" && hasPrevious) {
onImageIndexChange(imageIndex - 1);
}
if (event.key === "ArrowRight" && hasNext) {
onImageIndexChange(imageIndex + 1);
}
};
document.addEventListener("keydown", handleKeyDown);
return () => document.removeEventListener("keydown", handleKeyDown);
}, [hasNext, hasPrevious, imageIndex, onClose, onImageIndexChange]);
if (!currentImage?.url) return null;
const handleTouchEnd = (event: React.TouchEvent) => {
const touchStartX = touchStartXRef.current;
touchStartXRef.current = null;
if (touchStartX === null) return;
const delta = event.changedTouches[0]?.clientX ?? touchStartX;
const distance = delta - touchStartX;
if (distance > 48 && hasPrevious) onImageIndexChange(imageIndex - 1);
if (distance < -48 && hasNext) onImageIndexChange(imageIndex + 1);
};
return (
<div
className={styles.galleryOverlay}
role="dialog"
aria-modal="true"
aria-label={album.title || "Private album gallery"}
onTouchStart={(event) => {
touchStartXRef.current = event.touches[0]?.clientX ?? null;
}}
onTouchEnd={handleTouchEnd}
>
<button
type="button"
className={styles.galleryClose}
onClick={onClose}
aria-label="Close private album gallery"
>
<X size={24} aria-hidden="true" />
</button>
<div className={styles.galleryImageFrame}>
<Image
src={currentImage.url}
alt={`${album.title || "Private album"} photo ${imageIndex + 1}`}
fill
priority
draggable={false}
sizes="(max-width: 540px) 100vw, 540px"
className={styles.galleryImage}
/>
</div>
<span className={styles.galleryCounter}>
{imageIndex + 1} / {album.images.length}
</span>
{hasPrevious ? (
<button
type="button"
className={`${styles.galleryNav} ${styles.galleryPrevious}`}
onClick={() => onImageIndexChange(imageIndex - 1)}
aria-label="Previous photo"
>
<ChevronLeft size={30} aria-hidden="true" />
</button>
) : null}
{hasNext ? (
<button
type="button"
className={`${styles.galleryNav} ${styles.galleryNext}`}
onClick={() => onImageIndexChange(imageIndex + 1)}
aria-label="Next photo"
>
<ChevronRight size={30} aria-hidden="true" />
</button>
) : null}
</div>
);
}