diff --git a/src/assets/icons/Chart.jsx b/src/assets/icons/Chart.jsx new file mode 100644 index 0000000..c33e499 --- /dev/null +++ b/src/assets/icons/Chart.jsx @@ -0,0 +1,43 @@ +import * as React from 'react'; +const Chart = ({ width = 24, height = 25, stroke = 'white', ...props }) => ( + + + + + + +); +export default Chart; diff --git a/src/hooks/useSetNumberOfItemsToShow.js b/src/hooks/useSetNumberOfItemsToShow.js new file mode 100644 index 0000000..fe49d36 --- /dev/null +++ b/src/hooks/useSetNumberOfItemsToShow.js @@ -0,0 +1,27 @@ +import { useEffect, useState } from 'react'; +import { TABLET_WIDTH, MOBILE_WIDTH } from '@/constants/screenSizes'; + +export default function useSetNumOfItemsToShow({ desktop, tablet, mobile }) { + const numberOfItems = [desktop, tablet, mobile]; + const [numOfItemsToShow, setsNumOfItemsToShow] = useState(numberOfItems[0]); + + useEffect(() => { + const handleResize = () => { + if (window.innerWidth > TABLET_WIDTH) { + setsNumOfItemsToShow(numberOfItems[0]); + } else if (window.innerWidth > MOBILE_WIDTH) { + setsNumOfItemsToShow(numberOfItems[1]); + } else { + setsNumOfItemsToShow(numberOfItems[2]); + } + }; + + window.addEventListener('resize', handleResize); + handleResize(); + return () => { + window.removeEventListener('resize', handleResize); + }; + }, []); + + return numOfItemsToShow; +} diff --git a/src/pages/ListPage/Credit/styles.module.scss b/src/pages/ListPage/Credit/styles.module.scss index 7e215a5..e8c8463 100644 --- a/src/pages/ListPage/Credit/styles.module.scss +++ b/src/pages/ListPage/Credit/styles.module.scss @@ -12,7 +12,7 @@ $color-credit-border: #f1eef9; height: 131px; max-width: 1200px; - margin: 0 auto 48px; + margin: 0 auto 50px; padding-inline: 64px; } @@ -60,11 +60,17 @@ $color-credit-border: #f1eef9; } } +@media (max-width: $width-tablet) { + .container { + margin: 0 auto 64px; + } +} + @media (max-width: $width-mobile) { .container { height: 87px; padding-inline: 20px; - margin-top: 8px; + margin: 16px auto 40px; } .credit { diff --git a/src/pages/ListPage/MonthlyChart/components/ChartElement/index.jsx b/src/pages/ListPage/MonthlyChart/components/ChartElement/index.jsx new file mode 100644 index 0000000..f47341a --- /dev/null +++ b/src/pages/ListPage/MonthlyChart/components/ChartElement/index.jsx @@ -0,0 +1,34 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import styles from './styles.module.scss'; +import { numberWithCommas } from '@/utils/numberWithCommas'; +import Profile from '@/components/Profile'; + +/** + * + * @param {object} idol 아이돌 객체 + * @param {number} ranking 아이돌 순위 + */ + +const ChartElement = ({ idol, ranking }) => { + const { name, profilePicture } = idol; + const totalVotes = numberWithCommas(idol.totalVotes); + + return ( +
  • +
    +
    + +
    + {ranking} +
    {name}
    +
    +
    {totalVotes}
    +
  • + ); +}; +ChartElement.propTypes = { + idol: PropTypes.object, + ranking: PropTypes.number, +}; +export default ChartElement; diff --git a/src/pages/ListPage/MonthlyChart/components/ChartElement/styles.module.scss b/src/pages/ListPage/MonthlyChart/components/ChartElement/styles.module.scss new file mode 100644 index 0000000..b57a57b --- /dev/null +++ b/src/pages/ListPage/MonthlyChart/components/ChartElement/styles.module.scss @@ -0,0 +1,59 @@ +@import '../../../../../styles/'; + +.container { + display: flex; + justify-content: space-between; + align-items: center; + /* 태블릿 */ + @media (min-width: ($width-mobile + 1)) and (max-width: $width-tablet) { + width: 100%; + } + /* 모바일 */ + @media (max-width: $width-mobile) { + width: 100%; + } +} + +.idolInfo { + display: flex; + gap: 12px; + align-items: center; +} + +.img { + width: 70px; + height: 70px; +} + +.ranking { + @include font-weight-size(400, 16px); + line-height: 19px; + color: $color-brand-orange; +} + +.name { + @include font-weight-size(500, 16px); + line-height: 19px; + color: #ffffffde; + /* 태블릿 */ + @media (min-width: ($width-mobile + 1)) and (max-width: $width-tablet) { + @include font-weight-size(500, 14px); + } + /* 모바일 */ + @media (max-width: $width-mobile) { + @include font-weight-size(500, 14px); + } +} + +.totalVotes { + @include font-weight-size(400, 16px); + line-height: 19px; + color: #ffffff99; + @media (min-width: ($width-mobile + 1)) and (max-width: $width-tablet) { + @include font-weight-size(400, 14px); + } + /* 모바일 */ + @media (max-width: $width-mobile) { + @include font-weight-size(400, 14px); + } +} diff --git a/src/pages/ListPage/MonthlyChart/components/Tab/index.jsx b/src/pages/ListPage/MonthlyChart/components/Tab/index.jsx new file mode 100644 index 0000000..ccc9a41 --- /dev/null +++ b/src/pages/ListPage/MonthlyChart/components/Tab/index.jsx @@ -0,0 +1,53 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; +import styles from './styles.module.scss'; + +/** + * + * @param {string} currentTab 'girl' or 'boy' + * @param {func}} handleTabChange 탭 바꿈 이벤트 핸들러 + */ + +const Tab = ({ currentTab, handleTabChange }) => { + const selectGirlTab = () => { + handleTabChange('girl'); + }; + const selectBoyTab = () => { + handleTabChange('boy'); + }; + + return ( +
    +
    + 이달의 여자 아이돌 +
    +
    + 이달의 남자 아이돌 +
    +
    + ); +}; + +Tab.propTypes = { + currentTab: PropTypes.string, + handleTabChange: PropTypes.func, +}; + +export default Tab; diff --git a/src/pages/ListPage/MonthlyChart/components/Tab/styles.module.scss b/src/pages/ListPage/MonthlyChart/components/Tab/styles.module.scss new file mode 100644 index 0000000..94f4e2d --- /dev/null +++ b/src/pages/ListPage/MonthlyChart/components/Tab/styles.module.scss @@ -0,0 +1,26 @@ +@import '../../../../../styles/'; + +.tab { + display: flex; + height: 42px; + + div { + flex-grow: 1; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + + font-size: 14px; + font-weight: 400; + line-height: 18px; + letter-spacing: -0.17px; + color: $color-gray-warm; + + &.current { + background-color: #ffffff1a; + color: white; + border-bottom: 1px solid #ffffff; + } + } +} diff --git a/src/pages/ListPage/MonthlyChart/index.jsx b/src/pages/ListPage/MonthlyChart/index.jsx new file mode 100644 index 0000000..20e4ffb --- /dev/null +++ b/src/pages/ListPage/MonthlyChart/index.jsx @@ -0,0 +1,78 @@ +import React, { useEffect, useState } from 'react'; +import classNames from 'classnames'; +import styles from './styles.module.scss'; +import ChartElement from './components/ChartElement'; +import Tab from './components/Tab'; +import CustomButton from '@/components/CustomButton'; +import Chart from '@/assets/icons/Chart'; +import { boys, girls } from './mock'; +import useSetNumOfItemsToShow from '@/hooks/useSetNumberOfItemsToShow'; + +const MonthlyChart = () => { + const [idolList, setIdolList] = useState([]); + const [currentTab, setCurrentTab] = useState('girl'); + const chartClass = classNames(styles.chart, { + [styles.even]: idolList.length % 2 === 0, + }); + const numOfItemsToShow = useSetNumOfItemsToShow({ + desktop: 10, + tablet: 5, + mobile: 5, + }); + + //데이터 가져오기 + const handleLoad = () => { + if (currentTab === 'girl') { + setIdolList(girls.slice(0, numOfItemsToShow)); + } + if (currentTab === 'boy') { + setIdolList(boys.slice(0, numOfItemsToShow)); + } + }; + + // 탭 선택 핸들러 + const handleTabChange = (tab) => { + setCurrentTab(tab); + }; + + //더보기 버튼 + const handleMoreBtn = () => { + const newArr = [ + ...idolList, + { + id: idolList.length, + name: '추가 버튼 눌렀구나', + totalVotes: 1000, + profilePicture: + 'https://sprint-fe-project.s3.ap-northeast-2.amazonaws.com/Fandom-K/idol/1714613892649/ive1.jpeg', + }, + ]; + setIdolList(newArr); + }; + + useEffect(() => { + handleLoad(); + }, [currentTab, numOfItemsToShow]); + + return ( +
    +
    +

    이달의 차트

    + + + +
    + + +
    + +
    +
    + ); +}; + +export default MonthlyChart; diff --git a/src/pages/ListPage/MonthlyChart/mock.js b/src/pages/ListPage/MonthlyChart/mock.js new file mode 100644 index 0000000..0d3c28c --- /dev/null +++ b/src/pages/ListPage/MonthlyChart/mock.js @@ -0,0 +1,230 @@ +/** + * utils 폴더입니다. + * 추후에 이 파일은 삭제해도 됩니다. + */ + +export const boys = [ + { + id: 65, + name: '원우', + gender: 'male', + group: '세븐틴', + profilePicture: + 'https://sprint-fe-project.s3.ap-northeast-2.amazonaws.com/Fandom-K/idol/1714492902115/seven1.jpeg', + totalVotes: 0, + teamId: 12, + rank: 1, + }, + { + id: 68, + name: '산', + gender: 'male', + group: '에이티즈', + profilePicture: + 'https://sprint-fe-project.s3.ap-northeast-2.amazonaws.com/Fandom-K/idol/1714493145628/ateez1.jpeg', + totalVotes: 0, + teamId: 12, + rank: 1, + }, + { + id: 69, + name: '정우', + gender: 'male', + group: '엔시티127', + profilePicture: + 'https://sprint-fe-project.s3.ap-northeast-2.amazonaws.com/Fandom-K/idol/1714493201646/nct1271.jpeg', + totalVotes: 0, + teamId: 12, + rank: 1, + }, + { + id: 71, + name: '재민', + gender: 'male', + group: '엔시티드림', + profilePicture: + 'https://sprint-fe-project.s3.ap-northeast-2.amazonaws.com/Fandom-K/idol/1714493255267/nctdream1.jpeg', + totalVotes: 0, + teamId: 12, + rank: 1, + }, + { + id: 72, + name: '시온', + gender: 'male', + group: '엔시티위시', + profilePicture: + 'https://sprint-fe-project.s3.ap-northeast-2.amazonaws.com/Fandom-K/idol/1714493292425/nctwish.jpeg', + totalVotes: 0, + teamId: 12, + rank: 1, + }, + { + id: 73, + name: '예준', + gender: 'male', + group: '플레이브', + profilePicture: + 'https://sprint-fe-project.s3.ap-northeast-2.amazonaws.com/Fandom-K/idol/1714493395422/prave1.png', + totalVotes: 0, + teamId: 12, + rank: 1, + }, + { + id: 74, + name: '원빈', + gender: 'male', + group: '라이즈', + profilePicture: + 'https://sprint-fe-project.s3.ap-northeast-2.amazonaws.com/Fandom-K/idol/1714493431544/rllze1.jpeg', + totalVotes: 0, + teamId: 12, + rank: 1, + }, + { + id: 75, + name: '명재현', + gender: 'male', + group: '보이넥스트도어', + profilePicture: + 'https://sprint-fe-project.s3.ap-northeast-2.amazonaws.com/Fandom-K/idol/1714493483552/boynextdoor%201.jpeg', + totalVotes: 0, + teamId: 12, + rank: 1, + }, + { + id: 76, + name: '필릭스', + gender: 'male', + group: '스트레이키즈', + profilePicture: + 'https://sprint-fe-project.s3.ap-northeast-2.amazonaws.com/Fandom-K/idol/1714493528093/straykids1.jpeg', + totalVotes: 0, + teamId: 12, + rank: 1, + }, + { + id: 77, + name: '현재', + gender: 'male', + group: '더보이즈', + profilePicture: + 'https://sprint-fe-project.s3.ap-northeast-2.amazonaws.com/Fandom-K/idol/1714493561949/theboyz1.jpeg', + totalVotes: 0, + teamId: 12, + rank: 1, + }, +]; + +export const girls = [ + { + id: 137, + name: '카리나', + gender: 'female', + group: '에스파', + profilePicture: + 'https://sprint-fe-project.s3.ap-northeast-2.amazonaws.com/Fandom-K/idol/1714613612846/aespa1.jpeg', + totalVotes: 0, + teamId: 12, + rank: 1, + }, + { + id: 138, + name: '아현', + gender: 'female', + group: '베이비몬스터', + profilePicture: + 'https://sprint-fe-project.s3.ap-northeast-2.amazonaws.com/Fandom-K/idol/1714613657089/babymonster1.jpeg', + totalVotes: 0, + teamId: 12, + rank: 1, + }, + { + id: 139, + name: '제니', + gender: 'female', + group: '블랙핑크', + profilePicture: + 'https://sprint-fe-project.s3.ap-northeast-2.amazonaws.com/Fandom-K/idol/1714613706249/blackpink1.jpeg', + totalVotes: 0, + teamId: 12, + rank: 1, + }, + { + id: 140, + name: '민니', + gender: 'female', + group: '(여자)아이들', + profilePicture: + 'https://sprint-fe-project.s3.ap-northeast-2.amazonaws.com/Fandom-K/idol/1714613739640/idle1.jpeg', + totalVotes: 0, + teamId: 12, + rank: 1, + }, + { + id: 141, + name: '원희', + gender: 'female', + group: '아일릿', + profilePicture: + 'https://sprint-fe-project.s3.ap-northeast-2.amazonaws.com/Fandom-K/idol/1714613774880/illit1.jpeg', + totalVotes: 0, + teamId: 12, + rank: 1, + }, + { + id: 142, + name: '유나', + gender: 'female', + group: '있지', + profilePicture: + 'https://sprint-fe-project.s3.ap-northeast-2.amazonaws.com/Fandom-K/idol/1714613810908/itty1.jpeg', + totalVotes: 0, + teamId: 12, + rank: 1, + }, + { + id: 143, + name: '안유진', + gender: 'female', + group: '아이브', + profilePicture: + 'https://sprint-fe-project.s3.ap-northeast-2.amazonaws.com/Fandom-K/idol/1714613892649/ive1.jpeg', + totalVotes: 0, + teamId: 12, + rank: 1, + }, + { + id: 144, + name: '장원영', + gender: 'female', + group: '아이브', + profilePicture: + 'https://sprint-fe-project.s3.ap-northeast-2.amazonaws.com/Fandom-K/idol/1714613965309/ive2.jpeg', + totalVotes: 0, + teamId: 12, + rank: 1, + }, + { + id: 145, + name: '벨', + gender: 'female', + group: '키스오브라이프', + profilePicture: + 'https://sprint-fe-project.s3.ap-northeast-2.amazonaws.com/Fandom-K/idol/1714614042940/kissoflife1.jpeg', + totalVotes: 0, + teamId: 12, + rank: 1, + }, + { + id: 146, + name: '김채원', + gender: 'female', + group: '르세라핌', + profilePicture: + 'https://sprint-fe-project.s3.ap-northeast-2.amazonaws.com/Fandom-K/idol/1714614228075/le1.jpeg', + totalVotes: 0, + teamId: 12, + rank: 1, + }, +]; diff --git a/src/pages/ListPage/MonthlyChart/styles.module.scss b/src/pages/ListPage/MonthlyChart/styles.module.scss new file mode 100644 index 0000000..125e122 --- /dev/null +++ b/src/pages/ListPage/MonthlyChart/styles.module.scss @@ -0,0 +1,121 @@ +@import '../../../styles/'; + +.container { + display: flex; + flex-direction: column; + max-width: 1200px; + min-height: 633px; + margin: 80px auto 0; + + /* 태블릿 */ + @media (min-width: ($width-mobile + 1)) and (max-width: $width-tablet) { + max-width: 700px; + min-height: 609px; + margin: 60px auto 0; + } + /* 모바일 */ + @media (max-width: $width-mobile) { + max-width: 327px; + min-height: 599px; + margin: 40px auto 0; + } +} +@media (max-width: 1250px) { + .container { + margin-inline: 24px; + } +} + +.header { + display: flex; + justify-content: space-between; + height: 32px; + margin-bottom: 24px; + + h2 { + @include font-weight-size(700, 24px); + line-height: 26px; + color: white; + /* 태블릿 */ + @media (min-width: ($width-mobile + 1)) and (max-width: $width-tablet) { + @include font-weight-size(700, 20px); + } + /* 모바일 */ + @media (max-width: $width-mobile) { + @include font-weight-size(700, 16px); + } + } + button { + width: 128px; + display: flex; + align-items: center; + justify-content: center; + } +} + +.chart { + min-height: 418px; + margin-top: 24px; + margin-bottom: 51px; + + display: grid; + grid-template-columns: repeat(2, minmax(auto, 588px)); + grid-auto-rows: 79px; + column-gap: 24px; + row-gap: 8px; + + li:not(:last-child) { + border-bottom: 1px solid #ffffff1a; + height: 79px; + padding-bottom: 8px; + } + &.even { + li:nth-last-child(-n + 2) { + border-bottom: none; + height: 70px; + padding-bottom: 0px; + } + } + + /* 태블릿 */ + @media (min-width: ($width-mobile + 1)) and (max-width: $width-tablet) { + display: flex; + flex-direction: column; + flex-wrap: nowrap; + margin-bottom: 27px; + gap: 8px; + &.even { + li:nth-last-child(2) { + border-bottom: 1px solid #ffffff1a; + height: 79px; + padding-bottom: 8px; + } + } + } + /* 모바일 */ + @media (max-width: $width-mobile) { + display: flex; + flex-direction: column; + flex-wrap: nowrap; + margin-top: 16px; + margin-bottom: 33px; + gap: 8px; + + &.even { + li:nth-last-child(2) { + border-bottom: 1px solid #ffffff1a; + height: 79px; + padding-bottom: 8px; + } + } + } +} + +.moreButton { + text-align: center; + button { + width: 326px; + height: 42px; + background: #ffffff1a; + } +} diff --git a/src/pages/ListPage/index.jsx b/src/pages/ListPage/index.jsx index fe0a122..e65fd59 100644 --- a/src/pages/ListPage/index.jsx +++ b/src/pages/ListPage/index.jsx @@ -2,6 +2,7 @@ import Donation from './Donation'; import Header from '@/components/Header'; import style from './styles.module.scss'; import Credit from './Credit'; +import MonthlyChart from './MonthlyChart'; const ListPage = () => { return ( @@ -10,6 +11,7 @@ const ListPage = () => {
    +
    ); diff --git a/src/pages/ListPage/styles.module.scss b/src/pages/ListPage/styles.module.scss index 94260c5..f7a7ea6 100644 --- a/src/pages/ListPage/styles.module.scss +++ b/src/pages/ListPage/styles.module.scss @@ -2,10 +2,19 @@ .container { background-color: $color-black-200; - height: 100vh; } .main { max-width: 1360px; margin: 0 auto; + padding-bottom: 243px; + + + @media (max-width: $width-tablet) { + padding-bottom: 330px; + } + + @media (max-width: $width-mobile) { + padding-bottom: 59px; + } } diff --git a/src/pages/TestPage/index.jsx b/src/pages/TestPage/index.jsx index 0dd4890..6dd79a8 100644 --- a/src/pages/TestPage/index.jsx +++ b/src/pages/TestPage/index.jsx @@ -14,8 +14,12 @@ const TestPage = () => { const [isOpen, openModal, closeModal] = useModal(); return ( -
    +
    +
    +