diff --git a/package.json b/package.json
index 2b535f30..526e6ff0 100644
--- a/package.json
+++ b/package.json
@@ -19,6 +19,7 @@
"axios": "^0.26.0",
"react": "^18.1.0",
"react-dom": "^18.1.0",
+ "react-range": "^1.8.13",
"react-router-dom": "^6.2.1",
"react-scripts": "5.0.1",
"react-slick": "^0.29.0",
diff --git a/src/asset/icon/index.ts b/src/asset/icon/index.ts
index 7f0fcdbe..c7f3f433 100644
--- a/src/asset/icon/index.ts
+++ b/src/asset/icon/index.ts
@@ -4,6 +4,8 @@ export { ReactComponent as IcFilterBtn } from "./filterBtn.svg";
export { ReactComponent as IcHamburger } from "./hamburger.svg";
export { ReactComponent as IcLogo } from "./logo.svg";
export { ReactComponent as IcMenuBarImg } from "./MenuBar Profile.svg";
+export { ReactComponent as IcModalCloseBtn } from "./modalCloseBtn.svg";
+export { ReactComponent as IcNextCardBtmn } from "./nextCardBtn.svg";
export { ReactComponent as IcNextCardBtn } from "./nextCardBtn.svg";
export { ReactComponent as IcVoteImg1 } from "./voteImg1.svg";
export { ReactComponent as IcVoteImg2 } from "./voteImg2.svg";
diff --git a/src/asset/icon/modalCloseBtn.svg b/src/asset/icon/modalCloseBtn.svg
new file mode 100644
index 00000000..07d19fe0
--- /dev/null
+++ b/src/asset/icon/modalCloseBtn.svg
@@ -0,0 +1,4 @@
+
diff --git a/src/components/CardCollection/CardSlider/index.tsx b/src/components/CardCollection/CardSlider/index.tsx
index 77a97ced..954605b6 100644
--- a/src/components/CardCollection/CardSlider/index.tsx
+++ b/src/components/CardCollection/CardSlider/index.tsx
@@ -7,6 +7,10 @@ import Card from "../Card";
import LastCard from "../Card/LastCard";
import { St } from "./style";
+interface CardSliderProps {
+ openHandler: () => void;
+}
+
const sliderSettings = {
className: "center",
centerMode: true,
@@ -17,7 +21,9 @@ const sliderSettings = {
slidesToScroll: 1,
};
-export default function CardSlider() {
+export default function CardSlider(props: CardSliderProps) {
+ const { openHandler } = props;
+
return (
@@ -30,7 +36,7 @@ export default function CardSlider() {
{/* 마지막 index에서는 필터버튼 없애주기 */}
-
+
);
}
diff --git a/src/components/CardCollection/FilterModal/IntimacySlider/index.tsx b/src/components/CardCollection/FilterModal/IntimacySlider/index.tsx
new file mode 100644
index 00000000..d135d9b1
--- /dev/null
+++ b/src/components/CardCollection/FilterModal/IntimacySlider/index.tsx
@@ -0,0 +1,34 @@
+import { Range } from "react-range";
+
+import { RangeTrack, St } from "./style";
+
+interface IntimacySliderProps {
+ min: number;
+ max: number;
+ step: number;
+
+ price: number[];
+ onChange: (values: number[]) => void;
+}
+
+export default function IntimacySlider(props: IntimacySliderProps) {
+ const { min, max, step, price, onChange } = props;
+
+ return (
+
+ (
+
+ {children}
+
+ )}
+ renderThumb={({ props }) => }
+ />
+
+ );
+}
diff --git a/src/components/CardCollection/FilterModal/IntimacySlider/style.ts b/src/components/CardCollection/FilterModal/IntimacySlider/style.ts
new file mode 100644
index 00000000..6743150b
--- /dev/null
+++ b/src/components/CardCollection/FilterModal/IntimacySlider/style.ts
@@ -0,0 +1,44 @@
+import { getTrackBackground } from "react-range";
+import styled from "styled-components";
+
+interface RangeTrackProps {
+ min: number;
+ max: number;
+ price: number[];
+}
+
+export const St = {
+ IntimacySlider: styled.div`
+ height: 2.2rem;
+
+ margin: 1.2rem 0 0.2rem;
+ padding: 0 1.5rem;
+ `,
+
+ RangeThumb: styled.div`
+ position: absolute;
+ top: 0;
+ width: 1.8em;
+ height: 1.8rem;
+ border-radius: 2.4rem;
+
+ background-color: ${({ theme }) => theme.colors.white};
+ box-shadow: 0 0.1rem 0.4rem 0.1rem rgba(0, 0, 0, 0.25);
+ `,
+};
+
+export const RangeTrack = styled.div`
+ position: relative;
+ height: 0.8rem;
+ width: 100%;
+ border-radius: 0.4rem;
+ background: ${(props: RangeTrackProps) =>
+ getTrackBackground({
+ values: props.price,
+ colors: ["#19BE7E", "#ffffff"],
+ min: props.min,
+ max: props.max,
+ })};
+
+ box-shadow: inset 0 0.1rem 0.1rem rgba(0, 0, 0, 0.25);
+`;
diff --git a/src/components/CardCollection/FilterModal/index.tsx b/src/components/CardCollection/FilterModal/index.tsx
new file mode 100644
index 00000000..25de2b2c
--- /dev/null
+++ b/src/components/CardCollection/FilterModal/index.tsx
@@ -0,0 +1,99 @@
+import React, { useEffect, useState } from "react";
+
+import Modal from "../../common/Modal";
+import IntimacySlider from "./IntimacySlider";
+import { St } from "./style";
+
+interface FilterModalProps {
+ closeHandler: () => void;
+}
+
+type FilterTags = {
+ type: string;
+ tags: string[];
+};
+
+const filterTags: FilterTags[] = [
+ {
+ type: "성별",
+ tags: ["남", "여"],
+ },
+ {
+ type: "연령대",
+ tags: ["10대", "20대", "30대"],
+ },
+ {
+ type: "술자리 유형",
+ tags: ["개인", "커플", "친구", "단체"],
+ },
+];
+
+const intimacyTags: string[] = ["상관없음", "새로워요", "친근해요", "절친해요"];
+
+export default function FilterModal(props: FilterModalProps) {
+ const { closeHandler } = props;
+ const [checkedTags, setCheckedTags] = useState>(new Set()); // 체크한 태그들을 저장할 state
+ const [intimacyValues, setIntimacyValues] = useState([0]); // 친밀도 value
+ // 태그를 눌렀을 때 함수
+ const toggleTag = (_tag: string) => {
+ const tempCheckedTags = new Set([...checkedTags]);
+ tempCheckedTags.has(_tag) ? tempCheckedTags.delete(_tag) : tempCheckedTags.add(_tag);
+ setCheckedTags(tempCheckedTags);
+ };
+ // 추천 시작하기를 눌렀을 때, 태그 정보들과 친밀도 정보를 담아주고 창닫기
+ const submitFilter = () => {
+ const tempCheckedTags = new Set(checkedTags);
+ tempCheckedTags.add(intimacyTags[intimacyValues[0]]);
+ setCheckedTags(tempCheckedTags);
+ closeHandler();
+ };
+
+ useEffect(() => {
+ console.log(checkedTags);
+ }, [checkedTags]);
+
+ return (
+
+
+ {filterTags.map((filterTag, idx) => (
+
+ {filterTag.type}
+
+ {filterTag.tags.map((tag, index) => (
+ toggleTag(tag)}>
+ {tag}
+
+ ))}
+
+
+ ))}
+
+
+ 친밀도
+ {
+ setIntimacyValues(values);
+ }}
+ />
+
+ {intimacyTags.map((tag, index) => (
+
+ {tag}
+
+ ))}
+
+
+
+
+
+
+ 추천 시작하기
+
+
+
+ );
+}
diff --git a/src/components/CardCollection/FilterModal/style.ts b/src/components/CardCollection/FilterModal/style.ts
new file mode 100644
index 00000000..160a5284
--- /dev/null
+++ b/src/components/CardCollection/FilterModal/style.ts
@@ -0,0 +1,81 @@
+import styled from "styled-components";
+
+export const St = {
+ ModalContentsWrapper: styled.div`
+ padding-top: 3.6rem;
+ `,
+
+ CloseBtn: styled.button`
+ background-image: IcModalCloseBtn;
+ `,
+
+ FilterTitle: styled.strong`
+ ${({ theme }) => theme.fonts.body7}
+ color: ${({ theme }) => theme.colors.black};
+ `,
+
+ FilterTagsWrapper: styled.ul`
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+
+ margin: 0.8rem 0 2.2rem;
+ `,
+
+ FilterTag: styled.li<{ isactive: boolean }>`
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+
+ width: 6.2rem;
+ height: 2.6rem;
+
+ ${({ theme }) => theme.fonts.btn3}
+ background-color: ${({ isactive, theme }) => (isactive ? theme.colors.green : theme.colors.white)};
+ color: ${({ isactive, theme }) => (isactive ? theme.colors.white : theme.colors.black)};
+
+ border-radius: 6.3rem;
+ `,
+
+ FilterIntimacyWrapper: styled.div`
+ display: flex;
+ flex-direction: column;
+
+ margin-bottom: 4rem;
+ `,
+
+ FilterIntimacyRange: styled.input`
+ margin: 1.2rem 0 0.2rem;
+ `,
+
+ FilterIntimacyTagsWrapper: styled.ul`
+ display: flex;
+ justify-content: space-between;
+ `,
+
+ FilterIntimacyTag: styled.li<{ isactive: boolean }>`
+ ${({ theme }) => theme.fonts.caption4}
+ color: ${({ isactive, theme }) => (isactive ? theme.colors.green : theme.colors.gray600)};
+ `,
+
+ SubmitBtnWrapper: styled.div`
+ display: flex;
+ justify-content: center;
+ `,
+
+ SubmitBtn: styled.button`
+ ${({ theme }) => theme.fonts.btn2};
+ background-color: ${({ theme }) => theme.colors.black};
+ color: ${({ theme }) => theme.colors.white};
+
+ width: 17.1rem;
+ height: 3.3rem;
+
+ display: flex;
+ justify-content: center;
+ align-items: center;
+
+ border-radius: 6.6rem;
+ `,
+};
diff --git a/src/components/CardCollection/index.tsx b/src/components/CardCollection/index.tsx
index e49f3fcc..77034531 100644
--- a/src/components/CardCollection/index.tsx
+++ b/src/components/CardCollection/index.tsx
@@ -1,12 +1,25 @@
+import { useState } from "react";
+
import Header from "../common/Header";
import CardSlider from "./CardSlider";
+import FilterModal from "./FilterModal";
import { St } from "./style";
export default function CardCollection() {
+ const [isOpened, setIsOpened] = useState(false);
+
+ const openModal = () => {
+ setIsOpened(true);
+ };
+ const closeModal = () => {
+ setIsOpened(false);
+ };
+
return (
-
+
+ {isOpened && }
);
}
diff --git a/src/components/Main/MenuBar/style.ts b/src/components/Main/MenuBar/style.ts
index 4c6a66ad..87a03242 100644
--- a/src/components/Main/MenuBar/style.ts
+++ b/src/components/Main/MenuBar/style.ts
@@ -1,7 +1,5 @@
import styled from "styled-components";
-import { IcCloseBtn } from "../../../asset/icon";
-
export const St = {
Root: styled.section`
background-color: ${({ theme }) => theme.colors.gray600};
diff --git a/src/components/common/Modal/index.tsx b/src/components/common/Modal/index.tsx
new file mode 100644
index 00000000..9da9a7b7
--- /dev/null
+++ b/src/components/common/Modal/index.tsx
@@ -0,0 +1,20 @@
+import { IcModalCloseBtn } from "../../../asset/icon";
+import { St } from "./style";
+
+type ModalContents = {
+ closeHandler: () => void;
+ children: React.ReactNode;
+};
+
+export default function Modal({ closeHandler, children }: ModalContents) {
+ return (
+
+
+
+
+
+ {children}
+
+
+ );
+}
diff --git a/src/components/common/Modal/style.ts b/src/components/common/Modal/style.ts
new file mode 100644
index 00000000..7724ffad
--- /dev/null
+++ b/src/components/common/Modal/style.ts
@@ -0,0 +1,37 @@
+import styled from "styled-components";
+
+export const St = {
+ Root: styled.div`
+ width: 100%;
+ height: 100%;
+
+ position: absolute;
+ left: 0;
+ top: 0;
+ z-index: 10;
+
+ background-color: rgb(0, 0, 0, 0.5);
+ `,
+
+ Modal: styled.section`
+ position: absolute;
+ left: 1.6rem;
+ right: 1.6rem;
+ top: 9rem;
+
+ background-color: ${({ theme }) => theme.colors.sub_green5};
+ border-radius: 2rem;
+
+ padding: 2rem 1.6rem;
+ `,
+
+ CloseBtn: styled.button`
+ position: absolute;
+ right: 1.6rem;
+ top: 2rem;
+ `,
+
+ ModalContents: styled.article`
+ position: relative;
+ `,
+};
diff --git a/src/style/theme.ts b/src/style/theme.ts
index 5b4294b6..5948773c 100644
--- a/src/style/theme.ts
+++ b/src/style/theme.ts
@@ -1,6 +1,7 @@
const colors = {
red: "#FF0000",
green: "#19BE7E",
+ sub_green: "#DBFFF1",
sub_green1: "#7DE0B9",
sub_green2: "#B5F2DB",
sub_green3: "#DBFFF1",
@@ -47,26 +48,26 @@ const fonts = {
h1: FONT({ weight: 600, size: 2, lineHeight: 130 }),
h2: FONT({ weight: 400, size: 1.2, lineHeight: 140 }),
body1: FONT({ weight: 600, size: 1.6, lineHeight: 140 }),
- body2: FONT({ weight: 700, size: 2.4, lineHeight: 140 }),
- body3: FONT({ weight: 600, size: 1.5, lineHeight: 140 }),
- body4: FONT({ weight: 400, size: 1.2, lineHeight: 140 }),
+ body2: FONT({ weight: 400, size: 1.2, lineHeight: 140 }),
+ body3: FONT({ weight: 700, size: 2.4, lineHeight: 140 }),
+ body4: FONT({ weight: 400, size: 1.4, lineHeight: 140 }),
body5: FONT({ weight: 700, size: 2.4, lineHeight: 140 }),
body6: FONT({ weight: 400, size: 1.4, lineHeight: 140 }),
- body7: FONT({ weight: 500, size: 1.6, lineHeight: 140 }),
- body8: FONT({ weight: 400, size: 1.2, lineHeight: 140, notoSans: true }),
- body9: FONT({ weight: 600, size: 2, lineHeight: 130 }),
- body10: FONT({ weight: 600, size: 2.4, lineHeight: 130 }),
- body11: FONT({ weight: 400, size: 1.5, lineHeight: 140 }),
- body12: FONT({ weight: 400, size: 1.6, lineHeight: 140 }),
- caption1: FONT({ weight: 400, size: 1, lineHeight: 80 }),
- caption2: FONT({ weight: 400, size: 1, lineHeight: 130 }),
+ body7: FONT({ weight: 500, size: 1.6, lineHeight: 224 }),
+ caption1: FONT({ weight: 400, size: 1, lineHeight: 130 }),
+ caption2: FONT({ weight: 500, size: 1.2, lineHeight: 126 }),
caption3: FONT({ weight: 400, size: 1.2, lineHeight: 140 }),
caption4: FONT({ weight: 400, size: 1.2, lineHeight: 140 }),
caption5: FONT({ weight: 400, size: 1.1, lineHeight: 140 }),
btn1: FONT({ weight: 400, size: 1.4, lineHeight: 140 }),
- btn2: FONT({ weight: 400, size: 1.2, lineHeight: 130 }),
- btn3: FONT({ weight: 300, size: 1.6, lineHeight: 140 }),
+ btn2: FONT({ weight: 400, size: 1.2, lineHeight: 150 }),
+ btn3: FONT({ weight: 300, size: 1.6, lineHeight: 224 }),
btn4: FONT({ weight: 300, size: 1, lineHeight: 140 }),
+ body8: FONT({ weight: 400, size: 1.2, lineHeight: 140, notoSans: true }),
+ body9: FONT({ weight: 600, size: 2, lineHeight: 130 }),
+ body10: FONT({ weight: 600, size: 2.4, lineHeight: 130 }),
+ body11: FONT({ weight: 400, size: 1.5, lineHeight: 140 }),
+ body12: FONT({ weight: 400, size: 1.6, lineHeight: 140 }),
footer1: FONT({ weight: 600, size: 1.2, lineHeight: 140 }),
footer2: FONT({ weight: 400, size: 1.2, lineHeight: 140 }),
} as const;
diff --git a/yarn.lock b/yarn.lock
index 38f078dc..895e9fda 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -7588,6 +7588,11 @@ fragment-cache@^0.2.1:
dependencies:
map-cache "^0.2.2"
+framework7-react@^7.0.6:
+ version "7.0.6"
+ resolved "https://registry.yarnpkg.com/framework7-react/-/framework7-react-7.0.6.tgz#b3e6761387da298dbf34fb3aa29c5b78fdb005d0"
+ integrity sha512-BMAEnLkL9+IzyUmNeXGtqpqvLYbiEmx4Q8glel/EkswTw64ftoPdyhBwXfrSgkswhsPNkaks+BmBr7zrcB8gEA==
+
fresh@0.5.2:
version "0.5.2"
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
@@ -12476,6 +12481,11 @@ react-merge-refs@^1.0.0:
resolved "https://registry.yarnpkg.com/react-merge-refs/-/react-merge-refs-1.1.0.tgz#73d88b892c6c68cbb7a66e0800faa374f4c38b06"
integrity sha512-alTKsjEL0dKH/ru1Iyn7vliS2QRcBp9zZPGoWxUOvRGWPUYgjo+V01is7p04It6KhgrzhJGnIj9GgX8W4bZoCQ==
+react-range@^1.8.13:
+ version "1.8.13"
+ resolved "https://registry.yarnpkg.com/react-range/-/react-range-1.8.13.tgz#68dd104dcf70d1ac232a5bc87dd54d72c7e5e66d"
+ integrity sha512-Hp7JhEm5hdmBTkbAZLzFet93RgHtEWyba0LQzavZstJDp6kUAIy3c/9PGuICTRq+tUEFN442eeqGbHqZlq4KeA==
+
react-refresh@^0.11.0:
version "0.11.0"
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.11.0.tgz#77198b944733f0f1f1a90e791de4541f9f074046"