diff --git a/src/components/Modal/Modal.tsx b/src/components/Modal/Modal.tsx index 0d211d8..f3fe1a6 100644 --- a/src/components/Modal/Modal.tsx +++ b/src/components/Modal/Modal.tsx @@ -82,7 +82,7 @@ const ModalStyle = styled.section` position: fixed; z-index: 99; inset: 0; - background: ${(props) => (props.type !== 'fullscreen' ? 'rgba(0,0,0,0.9)' : variables.colors.white)}; + background: ${(props) => (props.type !== 'fullscreen' ? 'rgba(0,0,0,0.85)' : variables.colors.white)}; padding: 0 2rem 4.8rem; display: flex; flex-direction: column; @@ -90,7 +90,7 @@ const ModalStyle = styled.section` `; const TitleStyle = styled.div` - padding: ${(props) => (props.type === 'fullscreen' ? '1.4rem 0' : '1.8rem 0')}; + padding: ${(props) => (props.type === 'fullscreen' ? '1.4rem 0' : '2.8rem 0')}; display: flex; justify-content: center; align-items: center; @@ -105,13 +105,13 @@ const CloseBtnStyle = styled.button` position: absolute; left: ${(props) => props.mode === 'fullscreen' && 0}; right: ${(props) => props.mode === 'dimmed' && 0}; + z-index: 9; `; const ContentsStyle = styled.div` padding: ${(props) => props.type === 'fullscreen' && '1rem 0'}; flex-grow: 1; overflow-y: auto; - display: ${(props) => props.type === 'dimmed' && 'flex'}; `; const ButtonBoxStyle = styled.div` diff --git a/src/components/Swiper/DimSwiper.tsx b/src/components/Swiper/DimSwiper.tsx index 39e2d4e..86b9b12 100644 --- a/src/components/Swiper/DimSwiper.tsx +++ b/src/components/Swiper/DimSwiper.tsx @@ -1,11 +1,14 @@ +/** @jsxImportSource @emotion/react */ + import { useDimSwiperStore } from '@store/useDimSwiper'; -import { Dispatch, ReactNode, SetStateAction, useEffect, useState } from 'react'; -import { Navigation, Pagination, Virtual } from 'swiper/modules'; +import { Dispatch, ReactNode, SetStateAction, useEffect, useLayoutEffect, useMemo, useState } from 'react'; +import { Navigation, Virtual } from 'swiper/modules'; import { Swiper, SwiperClass } from 'swiper/react'; import { IPortfolio } from 'types/types'; import 'swiper/css'; -import 'swiper/css/pagination'; +import { TypoBodyMdR } from '@styles/Common'; +import { css } from '@emotion/react'; interface IDimSwiper { children: ReactNode; @@ -14,40 +17,96 @@ interface IDimSwiper { } const DimSwiper = ({ children, data, setSlideSet }: IDimSwiper) => { - const [swiperRef, setSwiperRef] = useState(); const { selectedId, setSelectedId } = useDimSwiperStore(); - const isFirstSlide = data[0].id === selectedId; + const [swiperRef, setSwiperRef] = useState(); + const [firstSlide, setFirstSlide] = useState(); + const [lastSlide, setLastSlide] = useState(); + const [activeIndex, setActiveIndex] = useState(0); + const slideIndexMap = useMemo(() => new Map(), []); const getNewSlideSet = (clickedId: number) => { return data.filter(({ id }: { id: number }) => id === clickedId || id === clickedId - 1 || id === clickedId + 1); }; + const setIndexByPortfolioId = () => { + for (let i = 0; i < data.length; i++) { + slideIndexMap.set(data[i].id, i + 1); + } + }; + const handleChange = () => { - if (!swiperRef) return null; + if (!swiperRef || !firstSlide || !lastSlide) return null; + const toNext = swiperRef.swipeDirection === 'next'; + const direction = toNext ? 1 : -1; + + // 첫번째, 마지막 슬라이드에서 슬라이드 변경 금지 + if (selectedId === firstSlide) if (!toNext) return; + if (selectedId === lastSlide) if (toNext) return; + + // 두번째 슬라이드에서 이전 방향으로 변경된 경우 + if (selectedId === firstSlide + 1 && !toNext) { + setSelectedId(selectedId, direction); + swiperRef.slideTo(0, 0, false); + return; + } - const direction = swiperRef.swipeDirection === 'next' ? 1 : -1; setSelectedId(selectedId, direction); - swiperRef.slideTo(isFirstSlide ? 0 : 1, 0, false); + swiperRef.slideTo(1, 0, false); }; const swiperOption = { - modules: [Virtual, Pagination, Navigation], + modules: [Virtual, Navigation], onSwiper: (e: SwiperClass) => setSwiperRef(e), onTransitionEnd: handleChange, slidesPerView: 1, - initialSlide: isFirstSlide ? 0 : 1, - pagination: { - type: 'fraction' as 'fraction', - }, + initialSlide: selectedId === firstSlide ? 0 : 1, centeredSlides: true, + spaceBetween: 20, }; useEffect(() => { - // console.log(isFirstSlide); + setIndexByPortfolioId(); + if (data) { + setFirstSlide(data[0].id); + setLastSlide(data[data.length - 1].id); + } + }, []); + + useLayoutEffect(() => { setSlideSet(getNewSlideSet(selectedId)); + setActiveIndex(slideIndexMap.get(selectedId)); }, [selectedId]); - return {children}; + return ( + firstSlide && ( + <> +

+ {activeIndex} / {data.length} +

+ {children} + + ) + ); }; export default DimSwiper; + +const TitleStyle = css` + padding: 1.8rem 0; + text-align: center; + position: absolute; + inset: 0; + bottom: auto; +`; + +export const SlideImgBox = css` + background: #0f0f0f; + padding: 1rem; + border-radius: 0.6rem; + margin-bottom: 1rem; + + img { + aspect-ratio: 308/340; + object-fit: scale-down; + } +`; diff --git a/src/pages/Studio/StudioPortfolio/PortfolioSwiper.tsx b/src/pages/Studio/StudioPortfolio/PortfolioSwiper.tsx index 5b931ae..951d45b 100644 --- a/src/pages/Studio/StudioPortfolio/PortfolioSwiper.tsx +++ b/src/pages/Studio/StudioPortfolio/PortfolioSwiper.tsx @@ -1,6 +1,7 @@ /** @jsxImportSource @emotion/react */ -import DimSwiper from '@components/Swiper/DimSwiper'; +import DimSwiper, { SlideImgBox } from '@components/Swiper/DimSwiper'; +import { css } from '@emotion/react'; import { TypoBodyMdR } from '@styles/Common'; import { useState } from 'react'; import { SwiperSlide } from 'swiper/react'; @@ -12,10 +13,12 @@ const PortfolioSwiper = ({ data, studioName }: { data: IPortfolio[]; studioName: return ( {slideSet && - slideSet.map(({ id, url, description }) => ( + slideSet.map(({ id, url, menuName }) => ( - {`${studioName}-${id}`} -

{description}

+
+ {`${studioName}-${id}`} +
+

{menuName}

))}
@@ -23,3 +26,7 @@ const PortfolioSwiper = ({ data, studioName }: { data: IPortfolio[]; studioName: }; export default PortfolioSwiper; + +const portfolioSlide = css` + margin-top: 4rem; +`; diff --git a/src/pages/Studio/StudioPortfolio/StudioPortfolio.tsx b/src/pages/Studio/StudioPortfolio/StudioPortfolio.tsx index 725c31c..28eeeed 100644 --- a/src/pages/Studio/StudioPortfolio/StudioPortfolio.tsx +++ b/src/pages/Studio/StudioPortfolio/StudioPortfolio.tsx @@ -40,6 +40,7 @@ const StudioPortfolio = () => { const data = await response.json(); setData(data.portfolioDtos.content); setMenuNames(data.menuNameList); + setStudioName(data.studioName); } catch (err) { console.error('Failed to fetch data'); } @@ -47,7 +48,6 @@ const StudioPortfolio = () => { useEffect(() => { fetchPortfolio(); - if (data.length) setStudioName(data[0].studio); }, []); return ( diff --git a/src/pages/Studio/components/DimmedModal.tsx b/src/pages/Studio/components/DimmedModal.tsx index 0a7f0d7..41d0097 100644 --- a/src/pages/Studio/components/DimmedModal.tsx +++ b/src/pages/Studio/components/DimmedModal.tsx @@ -1,12 +1,11 @@ /** @jsxImportSource @emotion/react */ import Modal from '@components/Modal/Modal'; -// import { css } from '@emotion/react'; import styled from '@emotion/styled'; const DimmedModal = ({ children }: { children: React.ReactNode }) => { return ( <> - + {children} @@ -14,12 +13,9 @@ const DimmedModal = ({ children }: { children: React.ReactNode }) => { }; const DimmedModalStyle = styled.div` - width: 100%; - display: flex; + * { + color: #fff; + } `; -// const swiperStyle = css` -// color: white; -// `; - export default DimmedModal; diff --git a/src/store/useDimSwiper.ts b/src/store/useDimSwiper.ts index 37c0b37..4abf880 100644 --- a/src/store/useDimSwiper.ts +++ b/src/store/useDimSwiper.ts @@ -7,5 +7,5 @@ interface DimSwiperState { export const useDimSwiperStore = create((set) => ({ selectedId: 0, - setSelectedId: (id, direction) => set((state) => ({ selectedId: (id || state.selectedId) + (direction || 1) })), + setSelectedId: (id, direction) => set(() => ({ selectedId: id + (direction || 0) })), })); diff --git a/src/types/types.ts b/src/types/types.ts index 4130a3d..2c8091b 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -7,6 +7,7 @@ export interface IPortfolio { name: string; url: string; menuId: number; + menuName: string; description: string; created_at: string; updated_at: null | string;