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"