diff --git a/package-lock.json b/package-lock.json index 1ade4357..2110c546 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2069,7 +2069,6 @@ "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz", "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==" }, - "node_modules/@esbuild/aix-ppc64": { "version": "0.19.11", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.11.tgz", @@ -2422,7 +2421,6 @@ "node": ">=12" } }, - "node_modules/@esbuild/win32-x64": { "version": "0.19.11", "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.11.tgz", @@ -3246,7 +3244,6 @@ } } }, - "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.9.3", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.3.tgz", @@ -3403,7 +3400,6 @@ "win32" ] }, - "node_modules/@rollup/rollup-win32-x64-msvc": { "version": "4.9.3", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.9.3.tgz", @@ -3730,7 +3726,6 @@ } } }, - "node_modules/@swc/core-darwin-arm64": { "version": "1.3.102", "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.102.tgz", @@ -3875,7 +3870,6 @@ "node": ">=10" } }, - "node_modules/@swc/core-win32-x64-msvc": { "version": "1.3.102", "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.102.tgz", @@ -5909,17 +5903,6 @@ "url": "https://github.com/motdotla/dotenv?sponsor=1" } }, - "node_modules/dotenv": { - "version": "16.3.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", - "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/motdotla/dotenv?sponsor=1" - } - }, "node_modules/electron-to-chromium": { "version": "1.4.622", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.622.tgz", @@ -6677,7 +6660,6 @@ "version": "10.17.8", "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-10.17.8.tgz", "integrity": "sha512-Flmw+iVuE3xZGGCF+Oy45RxYuNot0aUg9HngeLf6EdzhC517yV7Kt/dqeazo1drr0xu/PCXEINDc2N96UzlGeQ==", - "dependencies": { "tslib": "^2.4.0" }, diff --git a/src/components/Home/RecommendedItemList/RecommendedItemList.module.scss b/src/components/Home/RecommendedItemList/RecommendedItemList.module.scss index e2eb1e0f..634511ba 100644 --- a/src/components/Home/RecommendedItemList/RecommendedItemList.module.scss +++ b/src/components/Home/RecommendedItemList/RecommendedItemList.module.scss @@ -1,10 +1,10 @@ @use "@/sass" as *; .container { - display: flex; - gap: 16px; + @include slide_button_container; - padding: 0 20px; - - overflow-x: scroll; + .slide_box { + @include slide_list_container; + gap: 16px; + } } diff --git a/src/components/Home/RecommendedItemList/RecommendedItemList.tsx b/src/components/Home/RecommendedItemList/RecommendedItemList.tsx index e0d04a59..a09c4769 100644 --- a/src/components/Home/RecommendedItemList/RecommendedItemList.tsx +++ b/src/components/Home/RecommendedItemList/RecommendedItemList.tsx @@ -2,6 +2,10 @@ import { useEffect, useState } from "react"; import styles from "./RecommendedItemList.module.scss"; +import useComponentSize from "@/hooks/useComponetSize"; + +import SlideButton from "@/components/SlideButton/SlideButton"; + import { getData } from "@/mocks/handlers/home"; import RecommendedItem from "./RecommendedItem/RecommendedItem"; @@ -14,6 +18,8 @@ interface PropsType { function RecommendedItemList(apiNum: PropsType) { const [data, setData] = useState(); + const [slideLocation, setSlideLocation] = useState(0); + const [componentRef, size] = useComponentSize(); useEffect(() => { getData( @@ -24,10 +30,35 @@ function RecommendedItemList(apiNum: PropsType) { return (
- {data && - data.map((data, i) => ( - - ))} + {data && ( + + )} +
+ {data && + data.map((data, i) => ( + + ))} +
); } diff --git a/src/components/Home/RecommendedLocationList/RecommendedLocationList.module.scss b/src/components/Home/RecommendedLocationList/RecommendedLocationList.module.scss index 35b27b42..ba1ada99 100644 --- a/src/components/Home/RecommendedLocationList/RecommendedLocationList.module.scss +++ b/src/components/Home/RecommendedLocationList/RecommendedLocationList.module.scss @@ -1,10 +1,10 @@ @use "@/sass" as *; .container { - display: flex; - gap: 8px; + @include slide_button_container; - padding: 0 20px; - - overflow-x: scroll; + .slide_box { + @include slide_list_container; + gap: 8px; + } } diff --git a/src/components/Home/RecommendedLocationList/RecommendedLocationList.tsx b/src/components/Home/RecommendedLocationList/RecommendedLocationList.tsx index 2d81108f..fd65940c 100644 --- a/src/components/Home/RecommendedLocationList/RecommendedLocationList.tsx +++ b/src/components/Home/RecommendedLocationList/RecommendedLocationList.tsx @@ -2,6 +2,10 @@ import { useEffect, useState } from "react"; import styles from "./RecommendedLocationList.module.scss"; +import useComponentSize from "@/hooks/useComponetSize"; + +import SlideButton from "@/components/SlideButton/SlideButton"; + import { getData } from "@/mocks/handlers/home"; import RecommendedLocation from "./RecommendedLocation/RecommendedLocation"; @@ -10,6 +14,8 @@ import { LocationDataType } from "@/types/home"; function RecommendedLocationList() { const [data, setData] = useState(); + const [slideLocation, setSlideLocation] = useState(0); + const [componentRef, size] = useComponentSize(); useEffect(() => { getData( @@ -20,10 +26,29 @@ function RecommendedLocationList() { return (
- {data && - data.map((data, i) => ( - - ))} + {data && ( + + )} +
+ {data && + data.map((data, i) => ( + + ))} +
); } diff --git a/src/components/Home/TabBar/TabBar.tsx b/src/components/Home/TabBar/TabBar.tsx index 88c82de5..065f7cb6 100644 --- a/src/components/Home/TabBar/TabBar.tsx +++ b/src/components/Home/TabBar/TabBar.tsx @@ -11,7 +11,9 @@ function TabBar() { - + + + ); diff --git a/src/components/Home/TripSpaceAtHome/TripSpaceAtHome.module.scss b/src/components/Home/TripSpaceAtHome/TripSpaceAtHome.module.scss index 35b27b42..ba1ada99 100644 --- a/src/components/Home/TripSpaceAtHome/TripSpaceAtHome.module.scss +++ b/src/components/Home/TripSpaceAtHome/TripSpaceAtHome.module.scss @@ -1,10 +1,10 @@ @use "@/sass" as *; .container { - display: flex; - gap: 8px; + @include slide_button_container; - padding: 0 20px; - - overflow-x: scroll; + .slide_box { + @include slide_list_container; + gap: 8px; + } } diff --git a/src/components/Home/TripSpaceAtHome/TripSpaceAtHome.tsx b/src/components/Home/TripSpaceAtHome/TripSpaceAtHome.tsx index a953e8e4..9fd3d60c 100644 --- a/src/components/Home/TripSpaceAtHome/TripSpaceAtHome.tsx +++ b/src/components/Home/TripSpaceAtHome/TripSpaceAtHome.tsx @@ -2,6 +2,10 @@ import { useEffect, useState } from "react"; import styles from "./TripSpaceAtHome.module.scss"; +import useComponentSize from "@/hooks/useComponetSize"; + +import SlideButton from "@/components/SlideButton/SlideButton"; + import { getData } from "@/mocks/handlers/home"; import TripSpaceItem from "./TripSpaceItem/TripSpaceItem"; @@ -10,6 +14,8 @@ import { TripSpaceDataType } from "@/types/home"; function TripSpaceAtHome() { const [data, setData] = useState(); + const [slideLocation, setSlideLocation] = useState(0); + const [componentRef, size] = useComponentSize(); const dataNull = { tripTitle: "아직 여행 일정이 없어요", @@ -24,13 +30,32 @@ function TripSpaceAtHome() { return (
- {data ? ( - data.map((data, i) => ( - - )) - ) : ( - + {data && ( + )} +
+ {data ? ( + data.map((data, i) => ( + + )) + ) : ( + + )} +
); } diff --git a/src/components/Home/TripSpaceAtHome/TripSpaceItem/TripSpaceItem.tsx b/src/components/Home/TripSpaceAtHome/TripSpaceItem/TripSpaceItem.tsx index 68ef5e92..cd0b2a3a 100644 --- a/src/components/Home/TripSpaceAtHome/TripSpaceItem/TripSpaceItem.tsx +++ b/src/components/Home/TripSpaceAtHome/TripSpaceItem/TripSpaceItem.tsx @@ -1,4 +1,5 @@ import { MdArrowForwardIos } from "react-icons/md"; +import { Link } from "react-router-dom"; import styles from "./TripSpaceItem.module.scss"; @@ -11,7 +12,7 @@ interface PropsData { function TripSpaceItem(data: PropsData) { const imageAlt = `${data.data.tripTitle}의 사진`; return ( -
+
)} -
+ ); } diff --git a/src/components/Home/VoteAtHome/VoteCard/CardHaveVote/CardHaveVote.module.scss b/src/components/Home/VoteAtHome/VoteCard/CardHaveVote/CardHaveVote.module.scss index 4239a61f..9ed15ca3 100644 --- a/src/components/Home/VoteAtHome/VoteCard/CardHaveVote/CardHaveVote.module.scss +++ b/src/components/Home/VoteAtHome/VoteCard/CardHaveVote/CardHaveVote.module.scss @@ -1,80 +1,77 @@ @use "@/sass" as *; -.card_have_vote { - width: 100%; +.container { + @include slide_button_container; + .slide_box { + @include slide_list_container; + gap: 8px; - display: flex; - gap: 8px; + .vote_box { + min-width: 30.8rem; + min-height: 16.4rem; - overflow-x: scroll; + background: linear-gradient( + 119deg, + #62aaff 35.27%, + rgba(98, 170, 255, 0.3) 142.38% + ); - padding: 0 20px; + border-radius: 1.6rem; - .vote_box { - min-width: 30.8rem; - min-height: 16.4rem; + box-sizing: border-box; - background: linear-gradient( - 119deg, - #62aaff 35.27%, - rgba(98, 170, 255, 0.3) 142.38% - ); - - border-radius: 1.6rem; - - box-sizing: border-box; - - .contents { - height: 100%; - - display: flex; - flex-direction: column; - justify-content: space-between; - - padding: 24px; - - .text_box { - width: 100%; + .contents { + height: 100%; display: flex; flex-direction: column; - gap: 4px; + justify-content: space-between; + + padding: 24px; - .vote_title { + .text_box { width: 100%; display: flex; - align-items: center; - gap: 8px; + flex-direction: column; + gap: 4px; + + .vote_title { + width: 100%; + + display: flex; + align-items: center; + gap: 8px; - @include typography(titleMedium); - .date { - @include typography(captionSmall); + @include typography(titleMedium); + .date { + @include typography(captionSmall); + } } - } - .discussion { - width: 100%; + .discussion { + width: 100%; - display: flex; - align-items: center; - gap: 8px; + display: flex; + align-items: center; + gap: 8px; - @include typography(titleSmall); + @include typography(titleSmall); - .profile { - width: 3.2rem; - height: 3.2rem; + .profile { + width: 3.2rem; + height: 3.2rem; - border: 1px solid $neutral0; - border-radius: 3.2rem; + border: 1px solid $neutral0; + border-radius: 3.2rem; + } } } - } - .button_box { - width: 100%; + .button_box { + width: 100%; - display: flex; - justify-content: flex-end; + display: flex; + justify-content: flex-end; + } } } } diff --git a/src/components/Home/VoteAtHome/VoteCard/CardHaveVote/CardHaveVote.tsx b/src/components/Home/VoteAtHome/VoteCard/CardHaveVote/CardHaveVote.tsx index 34de367a..270d801d 100644 --- a/src/components/Home/VoteAtHome/VoteCard/CardHaveVote/CardHaveVote.tsx +++ b/src/components/Home/VoteAtHome/VoteCard/CardHaveVote/CardHaveVote.tsx @@ -1,8 +1,13 @@ +import { useState } from "react"; import { MdArrowForwardIos } from "react-icons/md"; import { Link } from "react-router-dom"; import styles from "./CardHaveVote.module.scss"; +import useComponentSize from "@/hooks/useComponetSize"; + +import SlideButton from "@/components/SlideButton/SlideButton"; + import { VoteDataType } from "@/types/home"; interface PropsType { @@ -10,40 +15,59 @@ interface PropsType { } function CardHaveVote(data: PropsType) { + const [slideLocation, setSlideLocation] = useState(0); + const [componentRef, size] = useComponentSize(); return ( -
- {data && - data.data.map((data, i) => { - const imageAlt = `${data.title}의 사진`; - return ( -
-
-
-

- {data.title} - {data.date} -

-

- {imageAlt} - {data.discussion} -

-
- - - +

+ {imageAlt} + {data.discussion} +

+
+ + + +
- - ); - })} + ); + })} + ); } diff --git a/src/components/Home/VoteAtHome/VoteCard/CardNull/CardNull.module.scss b/src/components/Home/VoteAtHome/VoteCard/CardNull/CardNull.module.scss index d345aa89..d8d13830 100644 --- a/src/components/Home/VoteAtHome/VoteCard/CardNull/CardNull.module.scss +++ b/src/components/Home/VoteAtHome/VoteCard/CardNull/CardNull.module.scss @@ -13,8 +13,6 @@ justify-content: space-between; align-items: center; - padding: 33px 24px; - background: linear-gradient( 119deg, #62aaff 35.27%, diff --git a/src/components/Home/VoteAtHome/VoteCard/CardNull/CardNull.tsx b/src/components/Home/VoteAtHome/VoteCard/CardNull/CardNull.tsx index 880a56b9..0ba9ca42 100644 --- a/src/components/Home/VoteAtHome/VoteCard/CardNull/CardNull.tsx +++ b/src/components/Home/VoteAtHome/VoteCard/CardNull/CardNull.tsx @@ -3,10 +3,16 @@ import { Link } from "react-router-dom"; import styles from "./CardNull.module.scss"; +import useComponentSize from "@/hooks/useComponetSize"; + function CardNull() { + const [componentRef, size] = useComponentSize(); + const responsivePadding = size.width - 360 <= 0 ? 0 : size.width - 360; + const contentsPadding = `32.5px ${responsivePadding / 5 + 24}px`; + return ( -
-
+
+

친구들과 함께 diff --git a/src/components/SlideButton/LeftButton/LeftButton.module.scss b/src/components/SlideButton/LeftButton/LeftButton.module.scss new file mode 100644 index 00000000..0a9bf0e4 --- /dev/null +++ b/src/components/SlideButton/LeftButton/LeftButton.module.scss @@ -0,0 +1,14 @@ +@use "@/sass" as *; + +.container { + left: 10px; + + transform: translate(10px, -50%); + + &__icon { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + } +} diff --git a/src/components/SlideButton/LeftButton/LeftButton.tsx b/src/components/SlideButton/LeftButton/LeftButton.tsx new file mode 100644 index 00000000..02693d78 --- /dev/null +++ b/src/components/SlideButton/LeftButton/LeftButton.tsx @@ -0,0 +1,32 @@ +import { MdArrowBackIosNew } from "react-icons/md"; + +import styles from "./LeftButton.module.scss"; + +import { LeftButtonPropsType } from "@/types/home"; + +function LeftButton({ + slideLocation, + setSlideLocation, + itemWidth, + flexGap, +}: LeftButtonPropsType) { + function handleButton() { + if (-itemWidth < slideLocation) { + setSlideLocation(0); + } else { + setSlideLocation(slideLocation + itemWidth + flexGap); + } + } + + return ( + + ); +} + +export default LeftButton; diff --git a/src/components/SlideButton/RightButton/RightButton.module.scss b/src/components/SlideButton/RightButton/RightButton.module.scss new file mode 100644 index 00000000..dc46ff17 --- /dev/null +++ b/src/components/SlideButton/RightButton/RightButton.module.scss @@ -0,0 +1,14 @@ +@use "@/sass" as *; + +.container { + right: 10px; + + transform: translate(-10px, -50%); + + &__icon { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + } +} diff --git a/src/components/SlideButton/RightButton/RightButton.tsx b/src/components/SlideButton/RightButton/RightButton.tsx new file mode 100644 index 00000000..dd9d434b --- /dev/null +++ b/src/components/SlideButton/RightButton/RightButton.tsx @@ -0,0 +1,43 @@ +import { MdArrowForwardIos } from "react-icons/md"; + +import styles from "./RightButton.module.scss"; + +import { SlideButtonPropsType } from "@/types/home"; + +function RightButton({ + slideLocation, + setSlideLocation, + itemWidth, + itemNumber, + slideSize, + flexGap, +}: SlideButtonPropsType) { + const moveRight = slideLocation - itemWidth - flexGap; + const moveEnd = + -itemWidth * itemNumber - flexGap * (itemNumber - 1) - 40 + slideSize.width; + + function handleButton() { + if (moveRight <= moveEnd) { + setSlideLocation(moveEnd); + } else { + setSlideLocation(moveRight); + } + } + return ( + + ); +} + +export default RightButton; diff --git a/src/components/SlideButton/SlideButton.module.scss b/src/components/SlideButton/SlideButton.module.scss new file mode 100644 index 00000000..e3218fc9 --- /dev/null +++ b/src/components/SlideButton/SlideButton.module.scss @@ -0,0 +1,34 @@ +@use "@/sass" as *; + +.container { + position: absolute; + top: 0; + left: 0; + + width: 100%; + height: 100%; + + color: $neutral900; + + button { + position: absolute; + top: 50%; + + max-width: 4rem; + width: 4rem; + max-height: 4rem; + height: 4rem; + border-radius: 2rem; + + background-color: $neutral0; + box-shadow: $shadow200; + + @include typography(button); + + transition: 0.3s all; + + z-index: 10; + + opacity: 0; + } +} diff --git a/src/components/SlideButton/SlideButton.tsx b/src/components/SlideButton/SlideButton.tsx new file mode 100644 index 00000000..def7d697 --- /dev/null +++ b/src/components/SlideButton/SlideButton.tsx @@ -0,0 +1,39 @@ +import styles from "./SlideButton.module.scss"; + +import LeftButton from "./LeftButton/LeftButton"; +import RightButton from "./RightButton/RightButton"; + +import { SlideButtonPropsType } from "@/types/home"; + +function SlideButton({ + slideLocation, + setSlideLocation, + itemWidth, + itemNumber, + slideSize, + flexGap, +}: SlideButtonPropsType) { + return ( +

+ + +
+ ); +} + +export default SlideButton; diff --git a/src/mocks/handlers/home.ts b/src/mocks/handlers/home.ts index 254d5bce..a6b64c09 100644 --- a/src/mocks/handlers/home.ts +++ b/src/mocks/handlers/home.ts @@ -40,6 +40,51 @@ const recommendedItem = [ reviewNumber: "484", id: "1", }, + { + title: "호텔 loft", + imageURL: + "https://m.eejmall.com/web/product/big/201708/211_shop1_627935.jpg", + location: "제주", + score: "4.4", + reviewNumber: "484", + id: "1", + }, + { + title: "호텔 loft", + imageURL: + "https://m.eejmall.com/web/product/big/201708/211_shop1_627935.jpg", + location: "제주", + score: "4.4", + reviewNumber: "484", + id: "1", + }, + { + title: "호텔 loft", + imageURL: + "https://m.eejmall.com/web/product/big/201708/211_shop1_627935.jpg", + location: "제주", + score: "4.4", + reviewNumber: "484", + id: "1", + }, + { + title: "호텔 loft", + imageURL: + "https://m.eejmall.com/web/product/big/201708/211_shop1_627935.jpg", + location: "제주", + score: "4.4", + reviewNumber: "484", + id: "1", + }, + { + title: "호텔 loft", + imageURL: + "https://m.eejmall.com/web/product/big/201708/211_shop1_627935.jpg", + location: "제주", + score: "4.4", + reviewNumber: "484", + id: "1", + }, ], [ { @@ -78,6 +123,33 @@ const recommendedItem = [ reviewNumber: "184", id: "1", }, + { + title: "호텔 lee", + imageURL: + "https://m.eejmall.com/web/product/big/201708/211_shop1_627935.jpg", + location: "서울", + score: "4.8", + reviewNumber: "184", + id: "1", + }, + { + title: "호텔 lee", + imageURL: + "https://m.eejmall.com/web/product/big/201708/211_shop1_627935.jpg", + location: "서울", + score: "4.8", + reviewNumber: "184", + id: "1", + }, + { + title: "호텔 lee", + imageURL: + "https://m.eejmall.com/web/product/big/201708/211_shop1_627935.jpg", + location: "서울", + score: "4.8", + reviewNumber: "184", + id: "1", + }, ], ]; @@ -110,21 +182,21 @@ const userVoteData = [ date: "1.17-1.19", profile: "https://avatars.githubusercontent.com/u/154430298?s=48&v=4", discussion: "첫째 날 카페 어디갈래?", - voteURL: "/vote", + voteURL: "/voteDetail", }, { title: "부산, 여수 여행", date: "1.17-1.19", profile: "https://avatars.githubusercontent.com/u/154430298?s=48&v=4", discussion: "둘째 날 카페 어디갈래?", - voteURL: "/vote", + voteURL: "/voteDetail", }, { title: "부산, 여수 여행", date: "1.17-1.19", profile: "https://avatars.githubusercontent.com/u/154430298?s=48&v=4", discussion: "셋째 날 카페 어디갈래?", - voteURL: "/vote", + voteURL: "/voteDetail", }, ]; const tripSpaceData = [ diff --git a/src/sass/abstracts/_mixin.scss b/src/sass/abstracts/_mixin.scss index 7ca548c7..2cebba26 100644 --- a/src/sass/abstracts/_mixin.scss +++ b/src/sass/abstracts/_mixin.scss @@ -17,3 +17,23 @@ clip: rect(0, 0, 0, 0); clip-path: polygon(0 0, 0 0, 0 0); } + +@mixin slide_button_container { + position: relative; + + &:hover { + button { + opacity: 1; + } + } +} + +@mixin slide_list_container { + position: relative; + + display: flex; + + padding: 0 20px; + + transition: all 0.5s; +} diff --git a/src/types/home.ts b/src/types/home.ts index 672a2dc5..1bd496f2 100644 --- a/src/types/home.ts +++ b/src/types/home.ts @@ -26,3 +26,20 @@ export interface TripSpaceDataType { tripImg: string; dDay: string | undefined; } + +export interface LeftButtonPropsType { + slideLocation: number; + setSlideLocation: React.Dispatch>; + itemWidth: number; + flexGap: number; +} + +interface ComponentSize { + width: number; + height: number; +} + +export interface SlideButtonPropsType extends LeftButtonPropsType { + slideSize: ComponentSize; + itemNumber: number; +}