diff --git a/src/hooks/useSliderPaging.jsx b/src/hooks/useSliderPaging.jsx index e01315a..9e7b740 100644 --- a/src/hooks/useSliderPaging.jsx +++ b/src/hooks/useSliderPaging.jsx @@ -1,60 +1,34 @@ // src/hooks/useSliderPaging.js -import { useState, useRef, useEffect, useCallback } from 'react'; +import { useState, useEffect, useMemo, useCallback } from 'react'; -export function useSliderPaging({ totalItems, pageSize, cardWidth, gap, breakpoint = 1200 }) { - const wrapperRef = useRef(null); - - // 1) 뷰포트가 데스크톱 모드인지 +export function useSliderPaging({ totalItems, pageSize, breakpoint = 1200 }) { + // 1) 뷰포트 모드(데스크톱/모바일) 감지 const [isDesktop, setIsDesktop] = useState(window.innerWidth >= breakpoint); useEffect(() => { - const onResize = () => setIsDesktop(window.innerWidth >= breakpoint); - window.addEventListener('resize', onResize); - return () => window.removeEventListener('resize', onResize); + const handler = () => setIsDesktop(window.innerWidth >= breakpoint); + window.addEventListener('resize', handler); + return () => window.removeEventListener('resize', handler); }, [breakpoint]); - // 2) 페이지 인덱스 + // 2) 페이지 인덱스 관리 const [pageIndex, setPageIndex] = useState(0); - const totalPage = Math.max(0, Math.ceil(totalItems / pageSize) - 1); - - // 3) 한 페이지당 스크롤 픽셀 - const offset = pageSize * (cardWidth + gap); - - // 4) 데스크톱: 버튼 클릭 시 페이지 이동 - const slideTo = useCallback( - (idx) => { - const el = wrapperRef.current; - if (!el) return; - el.scrollTo({ left: idx * offset, behavior: 'smooth' }); - setPageIndex(idx); - }, - [offset], + const totalPage = useMemo( + () => Math.max(0, Math.ceil(totalItems / pageSize) - 1), + [totalItems, pageSize], ); + // 3) 이전/다음 버튼 활성화 여부 const canPrev = pageIndex > 0; const canNext = pageIndex < totalPage; - const goPrev = () => canPrev && slideTo(pageIndex - 1); - const goNext = () => canNext && slideTo(pageIndex + 1); - // 5) 모바일·태블릿: 직접 스크롤 → 페이지 인덱스 동기화 - useEffect(() => { - if (isDesktop) return; - const el = wrapperRef.current; - if (!el) return; - const onScroll = () => { - const idx = Math.round(el.scrollLeft / offset); - setPageIndex(Math.min(Math.max(idx, 0), totalPage)); - }; - el.addEventListener('scroll', onScroll, { passive: true }); - return () => el.removeEventListener('scroll', onScroll); - }, [isDesktop, offset, totalPage]); + // 4) 페이지 이동 함수 + const goPrev = useCallback(() => { + if (canPrev) setPageIndex((idx) => idx - 1); + }, [canPrev]); + + const goNext = useCallback(() => { + if (canNext) setPageIndex((idx) => idx + 1); + }, [canNext]); - return { - wrapperRef, - isDesktop, - pageIndex, - canPrev, - canNext, - goPrev, - goNext, - }; + return { isDesktop, pageIndex, totalPage, canPrev, canNext, goPrev, goNext }; } diff --git a/src/pages/ListPage/ListPage.jsx b/src/pages/ListPage/ListPage.jsx index 20651eb..4d4f4e5 100644 --- a/src/pages/ListPage/ListPage.jsx +++ b/src/pages/ListPage/ListPage.jsx @@ -62,7 +62,7 @@ const ListPage = () => { setRecentHasNext(!!next); }, [recentData, recentLoading, recentOffset]); - // 인기 무한스크롤 로드 + // 인기 카드 추가 로드 함수 const loadMorePopular = () => { if (popularLoading || !popularHasNext) return; const newOffset = popularOffset + 20; @@ -70,7 +70,7 @@ const ListPage = () => { getPopularList({ limit: 20, offset: newOffset, sortLike: true }); }; - // 최신 무한스크롤 로드 + // 최신 카드 추가 로드 함수 const loadMoreRecent = () => { if (recentLoading || !recentHasNext) return; const newOffset = recentOffset + 20; diff --git a/src/pages/ListPage/ListPage.module.scss b/src/pages/ListPage/ListPage.module.scss index 7c79d2b..2bd03f5 100644 --- a/src/pages/ListPage/ListPage.module.scss +++ b/src/pages/ListPage/ListPage.module.scss @@ -59,6 +59,7 @@ @media screen and (min-width: 355px) and (max-width: 1199px) { width: 100%; + margin-bottom: 20px; } } } diff --git a/src/pages/ListPage/components/ItemCard.jsx b/src/pages/ListPage/components/ItemCard.jsx index ffc2bb0..6838093 100644 --- a/src/pages/ListPage/components/ItemCard.jsx +++ b/src/pages/ListPage/components/ItemCard.jsx @@ -2,45 +2,32 @@ import React from 'react'; import { Link } from 'react-router-dom'; import styles from './ItemCard.module.scss'; -import { COLOR_STYLES } from '@/constants/colorThemeStyle'; import ShowAvatars from './ShowAvatars'; import ShowEmoji from './ShowEmoji'; +import { getBackgroundStylesFromPostData } from '@/utils/getBackgroundStylesFromPostData'; +import { getContentStylesFromPostData } from '@/utils/getContentStylesFromPostData'; const ItemCard = ({ - data: { - id, - name, - backgroundColor, - backgroundImageURL, - messageCount, - recentMessages = [], - topReactions = [], - }, + id, + name, + backgroundColor, + backgroundImageURL, + messageCount, + recentMessages = [], + topReactions = [], }) => { - const { primary, border, accent } = COLOR_STYLES[backgroundColor] || {}; - const backgroundStyle = backgroundImageURL - ? { - backgroundImage: `url(${backgroundImageURL})`, - backgroundSize: 'cover', - backgroundPosition: 'center', - } - : { backgroundColor: primary, borderColor: border }; + // 배경 설정: 이미지가 있으면 이미지, 없으면 컬러 + const backgroundStyle = getBackgroundStylesFromPostData({ backgroundColor, backgroundImageURL }); - const contentClass = backgroundImageURL - ? `${styles['item-card__content']} ${styles['item-card__content--image']}` - : styles['item-card__content']; + // 밝은 색: 검정 텍스트 + // 어두운 색: 하얀색 텍스트 + // 이미지: 하얀색 텍스트 / 어두운색 overlay 적용 + const contentStyle = getContentStylesFromPostData(backgroundImageURL); return (
- {backgroundImageURL && ( -
- )} - -
+

To. {name}

{messageCount}명이 작성했어요!

diff --git a/src/pages/ListPage/components/Slider.jsx b/src/pages/ListPage/components/Slider.jsx index 35a1c62..9331fd3 100644 --- a/src/pages/ListPage/components/Slider.jsx +++ b/src/pages/ListPage/components/Slider.jsx @@ -1,56 +1,60 @@ -import { useRef } from 'react'; +import React, { useRef } from 'react'; import styles from './Slider.module.scss'; import ItemCard from './ItemCard'; import ArrowButton from '@/components/Button/ArrowButton'; -import HorizontalScrollContainer from '@/components/HorizontalScrollContainer/HorizontalScrollContainer'; import { useSliderPaging } from '@/hooks/useSliderPaging'; import InfinityScrollWrapper from '@/components/InfinityScrollWrapper/InfinityScrollWrapper'; -const CARD_WIDTH = 275; -const GAP = 16; const PAGE_SIZE = 4; const Slider = ({ cards, hasNext, loadMore }) => { const scrollObserverRef = useRef(null); - const { wrapperRef, isDesktop, pageIndex, canPrev, canNext, goPrev, goNext } = useSliderPaging({ + const { isDesktop, pageIndex, canPrev, canNext, goPrev, goNext } = useSliderPaging({ totalItems: cards.length, pageSize: PAGE_SIZE, - cardWidth: CARD_WIDTH, - gap: GAP, breakpoint: 1200, }); - // 데스크톱 전용: 현재 페이지에 해당하는 카드만 + // 데스크탑: 현재 페이지*4 ~ 페이지*4+4 슬라이스 + // 모바일: 전체 cards (무한 스크롤) const visibleCards = isDesktop ? cards.slice(pageIndex * PAGE_SIZE, pageIndex * PAGE_SIZE + PAGE_SIZE) : cards; - // 오른쪽 화살표 클릭 핸들러 const handleNext = () => { - if (isDesktop) { - const totalPage = Math.max(0, Math.ceil(cards.length / PAGE_SIZE) - 1); - if (pageIndex < totalPage) { - goNext(); - } else if (pageIndex === totalPage && hasNext) { - // 마지막 페이지이면서 서버에 더 있으면 추가 로드 - loadMore(); - } - } else { - // 모바일에서는 그냥 loadMore()로 충분 + if (canNext) { + goNext(); + } else if (hasNext) { + goNext(); loadMore(); } }; return (
- {isDesktop && canPrev && ( + {isDesktop && (
- +
)} -
- +
+ {isDesktop ? ( +
+ {visibleCards.map((card) => ( + + ))} +
+ ) : ( { scrollObserverRef={scrollObserverRef} >
- {visibleCards.map((c) => ( - + {visibleCards.map((card) => ( + ))}
- + )}
{isDesktop && (canNext || hasNext) && (
- +
)}