- api/: common/, pc/common/, pc/public/, pc/admin/ 구조로 변경 - components/: pc/public/, pc/admin/ 구조로 변경 - hooks/: common/, pc/admin/ 구조로 변경 - pages/: pc/public/, mobile/ 구조로 변경 - confetti.js를 utils/로 이동 - 모든 import 경로 업데이트 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
125 lines
3.6 KiB
JavaScript
125 lines
3.6 KiB
JavaScript
import { useState, useEffect, useCallback } from 'react';
|
|
|
|
/**
|
|
* 라이트박스 상태 및 동작 관리 훅
|
|
* @param {Object} options
|
|
* @param {Array<{url: string, thumb_url?: string}|string>} options.images - 이미지 배열
|
|
* @param {Function} options.onClose - 닫기 콜백 (optional)
|
|
* @returns {Object} 라이트박스 상태 및 메서드
|
|
*/
|
|
export function useLightbox({ images = [], onClose } = {}) {
|
|
const [isOpen, setIsOpen] = useState(false);
|
|
const [currentIndex, setCurrentIndex] = useState(0);
|
|
|
|
// 라이트박스 열기
|
|
const open = useCallback((index = 0) => {
|
|
setCurrentIndex(index);
|
|
setIsOpen(true);
|
|
window.history.pushState({ lightbox: true }, '');
|
|
}, []);
|
|
|
|
// 라이트박스 닫기
|
|
const close = useCallback(() => {
|
|
setIsOpen(false);
|
|
onClose?.();
|
|
}, [onClose]);
|
|
|
|
// 이전 이미지
|
|
const goToPrev = useCallback(() => {
|
|
if (images.length <= 1) return;
|
|
setCurrentIndex((prev) => (prev > 0 ? prev - 1 : images.length - 1));
|
|
}, [images.length]);
|
|
|
|
// 다음 이미지
|
|
const goToNext = useCallback(() => {
|
|
if (images.length <= 1) return;
|
|
setCurrentIndex((prev) => (prev < images.length - 1 ? prev + 1 : 0));
|
|
}, [images.length]);
|
|
|
|
// 특정 인덱스로 이동
|
|
const goToIndex = useCallback(
|
|
(index) => {
|
|
if (index >= 0 && index < images.length) {
|
|
setCurrentIndex(index);
|
|
}
|
|
},
|
|
[images.length]
|
|
);
|
|
|
|
// 뒤로가기 처리
|
|
useEffect(() => {
|
|
if (!isOpen) return;
|
|
|
|
const handlePopState = () => {
|
|
close();
|
|
};
|
|
|
|
window.addEventListener('popstate', handlePopState);
|
|
return () => window.removeEventListener('popstate', handlePopState);
|
|
}, [isOpen, close]);
|
|
|
|
// body 스크롤 방지 (Lightbox 컴포넌트에서 처리하므로 여기선 생략)
|
|
|
|
// 이미지 프리로딩
|
|
useEffect(() => {
|
|
if (!isOpen || images.length === 0) return;
|
|
|
|
// 현재, 이전, 다음 이미지 프리로드
|
|
const indicesToPreload = [
|
|
currentIndex,
|
|
(currentIndex + 1) % images.length,
|
|
(currentIndex - 1 + images.length) % images.length,
|
|
];
|
|
|
|
indicesToPreload.forEach((index) => {
|
|
const img = new Image();
|
|
const imageItem = images[index];
|
|
img.src = typeof imageItem === 'string' ? imageItem : imageItem?.url || imageItem;
|
|
});
|
|
}, [isOpen, currentIndex, images]);
|
|
|
|
// 이미지 다운로드
|
|
const downloadImage = useCallback(async () => {
|
|
const imageItem = images[currentIndex];
|
|
const imageUrl = typeof imageItem === 'string' ? imageItem : imageItem?.url || imageItem;
|
|
if (!imageUrl) return;
|
|
|
|
try {
|
|
const response = await fetch(imageUrl);
|
|
const blob = await response.blob();
|
|
const url = URL.createObjectURL(blob);
|
|
const link = document.createElement('a');
|
|
link.href = url;
|
|
link.download = `image_${currentIndex + 1}.jpg`;
|
|
document.body.appendChild(link);
|
|
link.click();
|
|
document.body.removeChild(link);
|
|
URL.revokeObjectURL(url);
|
|
} catch (error) {
|
|
console.error('이미지 다운로드 실패:', error);
|
|
}
|
|
}, [images, currentIndex]);
|
|
|
|
// 현재 이미지 URL 가져오기
|
|
const getCurrentImageUrl = useCallback(() => {
|
|
const imageItem = images[currentIndex];
|
|
return typeof imageItem === 'string' ? imageItem : imageItem?.url || imageItem;
|
|
}, [images, currentIndex]);
|
|
|
|
return {
|
|
isOpen,
|
|
currentIndex,
|
|
currentImage: images[currentIndex],
|
|
currentImageUrl: getCurrentImageUrl(),
|
|
totalCount: images.length,
|
|
open,
|
|
close,
|
|
goToPrev,
|
|
goToNext,
|
|
goToIndex,
|
|
downloadImage,
|
|
setCurrentIndex,
|
|
};
|
|
}
|
|
|
|
export default useLightbox;
|