refactor: PC public 페이지 공통 컴포넌트 및 유틸 적용
- LightboxIndicator 공통 컴포넌트 생성 및 AlbumDetail, AlbumGallery에 적용 - formatDate를 utils/date에서 import하도록 변경 (Album, Members, AlbumDetail) - 중복 코드 약 100줄 제거
This commit is contained in:
parent
22db79e960
commit
cdca23e317
5 changed files with 55 additions and 98 deletions
42
frontend/src/components/common/LightboxIndicator.jsx
Normal file
42
frontend/src/components/common/LightboxIndicator.jsx
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import { memo } from 'react';
|
||||
|
||||
/**
|
||||
* 라이트박스 인디케이터 컴포넌트
|
||||
* 이미지 갤러리에서 현재 위치를 표시하는 슬라이딩 점 인디케이터
|
||||
* CSS transition 사용으로 GPU 가속
|
||||
*/
|
||||
const LightboxIndicator = memo(function LightboxIndicator({ count, currentIndex, goToIndex }) {
|
||||
const translateX = -(currentIndex * 18) + 100 - 6;
|
||||
|
||||
return (
|
||||
<div className="absolute bottom-6 left-1/2 -translate-x-1/2 overflow-hidden" style={{ width: '200px' }}>
|
||||
{/* 양옆 페이드 그라데이션 */}
|
||||
<div className="absolute inset-0 pointer-events-none z-10" style={{
|
||||
background: 'linear-gradient(to right, rgba(0,0,0,1) 0%, transparent 20%, transparent 80%, rgba(0,0,0,1) 100%)'
|
||||
}} />
|
||||
{/* 슬라이딩 컨테이너 - CSS transition으로 GPU 가속 */}
|
||||
<div
|
||||
className="flex items-center gap-2 justify-center"
|
||||
style={{
|
||||
width: `${count * 18}px`,
|
||||
transform: `translateX(${translateX}px)`,
|
||||
transition: 'transform 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94)'
|
||||
}}
|
||||
>
|
||||
{Array.from({ length: count }).map((_, i) => (
|
||||
<button
|
||||
key={i}
|
||||
className={`rounded-full flex-shrink-0 transition-all duration-300 ${
|
||||
i === currentIndex
|
||||
? 'w-3 h-3 bg-white'
|
||||
: 'w-2.5 h-2.5 bg-white/40 hover:bg-white/60'
|
||||
}`}
|
||||
onClick={() => goToIndex(i)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export default LightboxIndicator;
|
||||
|
|
@ -3,6 +3,7 @@ import { useNavigate } from 'react-router-dom';
|
|||
import { motion } from 'framer-motion';
|
||||
import { Calendar, Music } from 'lucide-react';
|
||||
import { getAlbums } from '../../../api/public/albums';
|
||||
import { formatDate } from '../../../utils/date';
|
||||
|
||||
function Album() {
|
||||
const navigate = useNavigate();
|
||||
|
|
@ -21,13 +22,6 @@ function Album() {
|
|||
});
|
||||
}, []);
|
||||
|
||||
// 날짜 포맷팅
|
||||
const formatDate = (dateStr) => {
|
||||
if (!dateStr) return '';
|
||||
const date = new Date(dateStr);
|
||||
return `${date.getFullYear()}.${String(date.getMonth() + 1).padStart(2, '0')}.${String(date.getDate()).padStart(2, '0')}`;
|
||||
};
|
||||
|
||||
// 타이틀곡 찾기
|
||||
const getTitleTrack = (tracks) => {
|
||||
if (!tracks || tracks.length === 0) return '';
|
||||
|
|
@ -149,7 +143,7 @@ function Album() {
|
|||
</p>
|
||||
<div className="flex items-center gap-2 text-sm text-gray-500">
|
||||
<Calendar size={14} />
|
||||
<span>{formatDate(album.release_date)}</span>
|
||||
<span>{formatDate(album.release_date, 'YYYY.MM.DD')}</span>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
|
|
|||
|
|
@ -3,41 +3,9 @@ import { useParams, useNavigate } from 'react-router-dom';
|
|||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import { Calendar, Music2, Clock, X, ChevronLeft, ChevronRight, Download, MoreVertical, FileText } from 'lucide-react';
|
||||
import { getAlbumByName } from '../../../api/public/albums';
|
||||
import { formatDate } from '../../../utils/date';
|
||||
import LightboxIndicator from '../../../components/common/LightboxIndicator';
|
||||
|
||||
// 인디케이터 컴포넌트 - CSS transition 사용으로 JS 블로킹에 영향받지 않음
|
||||
const LightboxIndicator = memo(function LightboxIndicator({ count, currentIndex, setLightbox }) {
|
||||
const translateX = -(currentIndex * 18) + 100 - 6;
|
||||
|
||||
return (
|
||||
<div className="absolute bottom-6 left-1/2 -translate-x-1/2 overflow-hidden" style={{ width: '200px' }}>
|
||||
{/* 양옆 페이드 그라데이션 */}
|
||||
<div className="absolute inset-0 pointer-events-none z-10" style={{
|
||||
background: 'linear-gradient(to right, rgba(0,0,0,1) 0%, transparent 20%, transparent 80%, rgba(0,0,0,1) 100%)'
|
||||
}} />
|
||||
{/* 슬라이딩 컨테이너 - CSS transition으로 GPU 가속 */}
|
||||
<div
|
||||
className="flex items-center gap-2 justify-center"
|
||||
style={{
|
||||
width: `${count * 18}px`,
|
||||
transform: `translateX(${translateX}px)`,
|
||||
transition: 'transform 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94)'
|
||||
}}
|
||||
>
|
||||
{Array.from({ length: count }).map((_, i) => (
|
||||
<button
|
||||
key={i}
|
||||
className={`rounded-full flex-shrink-0 transition-all duration-300 ${
|
||||
i === currentIndex
|
||||
? 'w-3 h-3 bg-white'
|
||||
: 'w-2.5 h-2.5 bg-white/40 hover:bg-white/60'
|
||||
}`}
|
||||
onClick={() => setLightbox(prev => ({ ...prev, index: i }))}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
function AlbumDetail() {
|
||||
const { name } = useParams();
|
||||
const navigate = useNavigate();
|
||||
|
|
@ -171,13 +139,6 @@ function AlbumDetail() {
|
|||
|
||||
// URL 헬퍼 함수는 더 이상 필요 없음 - API에서 직접 제공
|
||||
|
||||
// 날짜 포맷팅
|
||||
const formatDate = (dateStr) => {
|
||||
if (!dateStr) return '';
|
||||
const date = new Date(dateStr);
|
||||
return `${date.getFullYear()}.${String(date.getMonth() + 1).padStart(2, '0')}.${String(date.getDate()).padStart(2, '0')}`;
|
||||
};
|
||||
|
||||
// 총 재생 시간 계산
|
||||
const getTotalDuration = () => {
|
||||
if (!album?.tracks) return '';
|
||||
|
|
@ -317,7 +278,7 @@ function AlbumDetail() {
|
|||
<div className="flex items-center gap-6 text-gray-500 mb-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<Calendar size={18} />
|
||||
<span>{formatDate(album.release_date)}</span>
|
||||
<span>{formatDate(album.release_date, 'YYYY.MM.DD')}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Music2 size={18} />
|
||||
|
|
@ -568,12 +529,12 @@ function AlbumDetail() {
|
|||
</button>
|
||||
)}
|
||||
|
||||
{/* 인디케이터 - memo 컴포넌트로 분리 */}
|
||||
{/* 인디케이터 - 공통 컴포넌트 사용 */}
|
||||
{lightbox.images.length > 1 && (
|
||||
<LightboxIndicator
|
||||
count={lightbox.images.length}
|
||||
currentIndex={lightbox.index}
|
||||
setLightbox={setLightbox}
|
||||
goToIndex={(i) => setLightbox(prev => ({ ...prev, index: i }))}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -5,41 +5,7 @@ import { X, ChevronLeft, ChevronRight, Download } from 'lucide-react';
|
|||
import { RowsPhotoAlbum } from 'react-photo-album';
|
||||
import 'react-photo-album/rows.css';
|
||||
import { getAlbumByName } from '../../../api/public/albums';
|
||||
|
||||
// 인디케이터 컴포넌트 - CSS transition 사용으로 JS 블로킹에 영향받지 않음
|
||||
const LightboxIndicator = memo(function LightboxIndicator({ count, currentIndex, setLightbox }) {
|
||||
const translateX = -(currentIndex * 18) + 100 - 6;
|
||||
|
||||
return (
|
||||
<div className="absolute bottom-6 left-1/2 -translate-x-1/2 overflow-hidden" style={{ width: '200px' }}>
|
||||
{/* 양옆 페이드 그라데이션 */}
|
||||
<div className="absolute inset-0 pointer-events-none z-10" style={{
|
||||
background: 'linear-gradient(to right, rgba(0,0,0,1) 0%, transparent 20%, transparent 80%, rgba(0,0,0,1) 100%)'
|
||||
}} />
|
||||
{/* 슬라이딩 컨테이너 - CSS transition으로 GPU 가속 */}
|
||||
<div
|
||||
className="flex items-center gap-2 justify-center"
|
||||
style={{
|
||||
width: `${count * 18}px`,
|
||||
transform: `translateX(${translateX}px)`,
|
||||
transition: 'transform 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94)'
|
||||
}}
|
||||
>
|
||||
{Array.from({ length: count }).map((_, i) => (
|
||||
<button
|
||||
key={i}
|
||||
className={`rounded-full flex-shrink-0 transition-all duration-300 ${
|
||||
i === currentIndex
|
||||
? 'w-3 h-3 bg-white'
|
||||
: 'w-2.5 h-2.5 bg-white/40 hover:bg-white/60'
|
||||
}`}
|
||||
onClick={() => setLightbox(prev => ({ ...prev, index: i }))}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
import LightboxIndicator from '../../../components/common/LightboxIndicator';
|
||||
|
||||
// CSS로 호버 효과 추가 + overflow 문제 수정 + 로드 애니메이션
|
||||
const galleryStyles = `
|
||||
|
|
@ -392,11 +358,11 @@ function AlbumGallery() {
|
|||
</button>
|
||||
)}
|
||||
|
||||
{/* 하단 점 인디케이터 - memo 컴포넌트로 분리 */}
|
||||
{/* 하단 점 인디케이터 - 공통 컴포넌트 사용 */}
|
||||
<LightboxIndicator
|
||||
count={photos.length}
|
||||
currentIndex={lightbox.index}
|
||||
setLightbox={setLightbox}
|
||||
goToIndex={(i) => setLightbox(prev => ({ ...prev, index: i }))}
|
||||
/>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { useState, useEffect } from 'react';
|
|||
import { motion } from 'framer-motion';
|
||||
import { Instagram, Calendar } from 'lucide-react';
|
||||
import { getMembers } from '../../../api/public/members';
|
||||
import { formatDate } from '../../../utils/date';
|
||||
|
||||
function Members() {
|
||||
const [members, setMembers] = useState([]);
|
||||
|
|
@ -19,13 +20,6 @@ function Members() {
|
|||
});
|
||||
}, []);
|
||||
|
||||
// 날짜 포맷팅 함수
|
||||
const formatDate = (dateStr) => {
|
||||
if (!dateStr) return '';
|
||||
const date = new Date(dateStr);
|
||||
return `${date.getFullYear()}.${String(date.getMonth() + 1).padStart(2, '0')}.${String(date.getDate()).padStart(2, '0')}`;
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="py-16 flex justify-center items-center min-h-[60vh]">
|
||||
|
|
@ -83,7 +77,7 @@ function Members() {
|
|||
|
||||
<div className="flex items-center gap-2 text-sm text-gray-500 mb-4">
|
||||
<Calendar size={14} />
|
||||
<span>{formatDate(member.birth_date)}</span>
|
||||
<span>{formatDate(member.birth_date, 'YYYY.MM.DD')}</span>
|
||||
</div>
|
||||
|
||||
{/* 인스타그램 링크 */}
|
||||
|
|
@ -142,7 +136,7 @@ function Members() {
|
|||
|
||||
<div className="flex items-center gap-2 text-sm text-gray-400">
|
||||
<Calendar size={14} />
|
||||
<span>{formatDate(member.birth_date)}</span>
|
||||
<span>{formatDate(member.birth_date, 'YYYY.MM.DD')}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue