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 (
+
+
+ {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 (
+
+
+
이달의 차트
+
+
+
+
+
+
+ {idolList.map((idol, index) => {
+ 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 (
-