- 신랑신부의 이야기와 관계를 사진을 통해 시각적으로 전달합니다.
bunx --bun shadcn@latest add dialog
bunx --bun shadcn@latest add button
import { useState } from 'react';
import { Dialog, DialogContent } from '@/common/components/ui/dialog';
import { Button } from '@/common/components/ui/button';
import { ChevronLeft, ChevronRight, ChevronDown } from 'lucide-react';
import Intersect from '@/common/components/intersect';
const SLICE_SIZE = 9;
export default function Gallery({ images }: { images: string[] }) {
const [displayCount, setDisplayCount] = useState<number>(SLICE_SIZE);
const [selectedImage, setSelectedImage] = useState<string | null>(null);
const [currentIndex, setCurrentIndex] = useState<number>(0);
const handleImageClick = (image: string, index: number) => {
setSelectedImage(image);
setCurrentIndex(index);
};
const handlePrevious = () => {
setCurrentIndex((prev) => (prev > 0 ? prev - 1 : images.length - 1));
};
const handleNext = () => {
setCurrentIndex((prev) => (prev < images.length - 1 ? prev + 1 : 0));
};
return (
<div>
<h2 className='text-center text-xl font-en'>gallery</h2>
<div className='flex justify-center my-4'>
<img src='/ring.png' alt='반지' className='w-6 h-6 aspect-square' />
</div>
{/* 이미지 그리드 */}
<div className='grid grid-cols-3 gap-2'>
{images.slice(0, displayCount).map((image, index) => (
<Intersect key={index} type='data-animate'>
<div
className='aspect-square cursor-pointer overflow-hidden rounded-lg'
onClick={() => handleImageClick(image, index)}
data-animate-stage={(index % SLICE_SIZE) + 1}
>
<img
src={image}
alt={`Wedding photo ${index + 1}`}
className='w-full h-full object-cover hover:scale-105 transition-transform duration-300'
/>
</div>
</Intersect>
))}
</div>
{/* 더보기 버튼 */}
{displayCount < images.length && (
<div className='flex justify-center mt-6'>
<Button
variant='outline'
onClick={() => setDisplayCount((prev) => prev + SLICE_SIZE)}
>
<ChevronDown className='h-6 w-6' />
사진 더보기
</Button>
</div>
)}
{/* 이미지 모달 */}
<Dialog
open={!!selectedImage}
onOpenChange={() => setSelectedImage(null)}
>
<DialogContent className='max-w-4xl p-0 bg-transparent border-none'>
<div className='relative'>
<img
src={images[currentIndex]}
alt={`Wedding photo ${currentIndex + 1}`}
className='w-full h-auto rounded-lg'
/>
<Button
variant='ghost'
size='icon'
className='absolute left-2 top-1/2 -translate-y-1/2 bg-background/80 hover:bg-background'
onClick={handlePrevious}
>
<ChevronLeft className='h-6 w-6' />
</Button>
<Button
variant='ghost'
size='icon'
className='absolute right-2 top-1/2 -translate-y-1/2 bg-background/80 hover:bg-background'
onClick={handleNext}
>
<ChevronRight className='h-6 w-6' />
</Button>
</div>
</DialogContent>
</Dialog>
</div>
);
}
- 상태 관리
-
displayCoun
=> 현재 화면에 표시되는 이미지 수를 관리합니다. 초기값은 SLICE_SIZE(9)
입니다.
-
selectedImage
=> 현재 선택된 이미지의 URL을 저장합니다. null이면 모달이 닫힌 상태입니다.
-
currentIndex
=> 현재 모달(Dialog)에 보이는 이미지 중 이미지의 배열 내 인덱스를 저장합니다. 이전/다음 이미지 탐색에 사용됩니다.
- 모달(Dialog)을 열어 이전/다음 함수
-
handleImageClick
=> 이미지 클릭 시 해당 이미지를 선택하고 모달(Dialog)을 열며, 현재 인덱스를 설정합니다.
-
handlePrevious
=> 이전 이미지로 이동하는 함수로, 첫 번째 이미지에서는 마지막 이미지로 순환합니다.
-
handleNext
=> 다음 이미지로 이동하는 함수로, 마지막 이미지에서는 첫 번째 이미지로 순환합니다.
- 이미지 grid
- 3열 grid로 이미지를 배치하였습니다.
- slice 배열 함수를 사용하여
SLICE_SIZE(9)
에 맞게 처음에는 9장이 보이도록 하였습니다.
-
스크롤 애니메이션을 사용하기에
data-animate-stage={(index % SLICE_SIZE) + 1}
을 사용하여 순차적으로 나올 수 있게 설계하였습니다.
- 더보기 버튼을 만들어 표시된 이미지 수가 전체 이미지 수보다 적을 때만 버튼을 표시합니다.
- 모달(Dialog)
- Dialog에 있는
open
을 통해 open={!!selectedImage}
로 선택된 이미지가 있을 때만 모달을 표시합니다.
-
onOpenChange={() => setSelectedImage(null)}
로 모달이 닫힐 때 선택된 이미지를 초기화합니다.