diff --git a/package.json b/package.json
index 6d2efb0b..4fa821ae 100644
--- a/package.json
+++ b/package.json
@@ -19,6 +19,7 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-helmet-async": "^2.0.4",
+ "react-responsive-carousel": "^3.2.23",
"react-router-dom": "^6.15.0",
"react-use": "^17.5.0",
"zustand": "^4.4.1"
diff --git a/src/pages/FeedDetail/FeedDetail.page.tsx b/src/pages/FeedDetail/FeedDetail.page.tsx
index 0d84af5e..1001da9d 100644
--- a/src/pages/FeedDetail/FeedDetail.page.tsx
+++ b/src/pages/FeedDetail/FeedDetail.page.tsx
@@ -3,6 +3,7 @@ import { Badge, Divider, Header, Spacer, Text, TextDivider, Flex, Box } from 'co
import { useNavigate, useParams } from 'react-router-dom';
import Comments from './components/Comments';
+import IdeaImageList from './components/IdeaImageList';
import ModifyDropdown from './components/ModifyDropdown';
import ProfileInfo from './components/ProfileInfo';
import ReactionBar from './components/ReactionBar';
@@ -39,6 +40,7 @@ const FeedDetailPage = () => {
owner,
ownerScrap,
ownerLike,
+ imageResponses,
} = useFeedDetailQuery(feedId);
const openConfirm = useConfirm();
const { deleteIdea } = useDeleteIdea();
@@ -95,6 +97,15 @@ const FeedDetailPage = () => {
+ {imageResponses.length > 0 && (
+ <>
+
+
+
+
+ >
+ )}
+
diff --git a/src/pages/FeedDetail/assets/leftArrow.svg b/src/pages/FeedDetail/assets/leftArrow.svg
new file mode 100644
index 00000000..dfedea2e
--- /dev/null
+++ b/src/pages/FeedDetail/assets/leftArrow.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/pages/FeedDetail/assets/rightArrow.svg b/src/pages/FeedDetail/assets/rightArrow.svg
new file mode 100644
index 00000000..7826ab67
--- /dev/null
+++ b/src/pages/FeedDetail/assets/rightArrow.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/pages/FeedDetail/components/IdeaImageList.tsx b/src/pages/FeedDetail/components/IdeaImageList.tsx
new file mode 100644
index 00000000..1d1181bd
--- /dev/null
+++ b/src/pages/FeedDetail/components/IdeaImageList.tsx
@@ -0,0 +1,64 @@
+import styled from '@emotion/styled';
+import { Box } from 'concept-be-design-system';
+import { useState } from 'react';
+
+import ImageCarousel from './ImageCarousel';
+import { ImageResponse } from '../types';
+
+interface Props {
+ imageResponses: ImageResponse[];
+}
+
+const IdeaImageList = ({ imageResponses }: Props) => {
+ const [selectedIndex, setSelectedIndex] = useState(null);
+
+ const handleImageClick = (index: number) => {
+ setSelectedIndex(index);
+ };
+
+ const handleClose = () => {
+ setSelectedIndex(null);
+ };
+
+ return (
+
+
+ {imageResponses.map(({ id, imageUrl }, index) => (
+ - handleImageClick(index)}>
+
+
+ ))}
+
+ {selectedIndex !== null && (
+ response.imageUrl)}
+ initialIndex={selectedIndex}
+ onClose={handleClose}
+ />
+ )}
+
+ );
+};
+
+export default IdeaImageList;
+
+const Wrapper = styled.div`
+ display: flex;
+ overflow-x: auto;
+ flex-wrap: nowrap;
+ gap: 8px;
+
+ &::-webkit-scrollbar {
+ display: none;
+ }
+`;
+
+const Item = styled.div`
+ flex: 0 0 auto;
+`;
+
+const Image = styled.img`
+ object-fit: cover;
+ width: 120px;
+ height: 120px;
+`;
diff --git a/src/pages/FeedDetail/components/ImageCarousel.tsx b/src/pages/FeedDetail/components/ImageCarousel.tsx
new file mode 100644
index 00000000..8ecc93d1
--- /dev/null
+++ b/src/pages/FeedDetail/components/ImageCarousel.tsx
@@ -0,0 +1,115 @@
+import styled from '@emotion/styled';
+import { Flex, Header, SVGHeaderClose24, Spacer, Text } from 'concept-be-design-system';
+import { CSSProperties, useState } from 'react';
+import { Carousel } from 'react-responsive-carousel';
+import 'react-responsive-carousel/lib/styles/carousel.min.css';
+import { useLockBodyScroll } from 'react-use';
+
+import { ReactComponent as SVGLeftArrow } from '../assets/leftArrow.svg';
+import { ReactComponent as SVGRightArrow } from '../assets/rightArrow.svg';
+
+interface Props {
+ imageUrls: string[];
+ initialIndex: number;
+ onClose: () => void;
+}
+const arrowStyles: CSSProperties = {
+ position: 'absolute',
+ zIndex: 2,
+ top: 'calc(50% - 20px)',
+ width: 40,
+ height: 48,
+ cursor: 'pointer',
+ background: 'rgba(0, 0, 0, 0.15)',
+};
+
+const ImageCarousel = ({ imageUrls, initialIndex, onClose }: Props) => {
+ const [currentIndex, setCurrentIndex] = useState(initialIndex);
+
+ const handleChange = (index: number) => {
+ setCurrentIndex(index);
+ };
+
+ useLockBodyScroll();
+
+ return (
+
+
+
+
+
+
+
+
+ 이미지 상세보기
+
+ (
+
+ {currentIndex + 1}
+
+
+ /{imageUrls.length}
+
+ )
+
+
+
+
+ e.stopPropagation()}>
+
+ hasPrev && (
+
+ )
+ }
+ renderArrowNext={(onClickHandler, hasNext, label) =>
+ hasNext && (
+
+ )
+ }
+ >
+ {imageUrls.map((imageUrl, index) => (
+
+
+
+ ))}
+
+
+
+ );
+};
+
+export default ImageCarousel;
+
+const ModalOverlay = styled.div`
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ z-index: 10;
+ background: rgba(0, 0, 0, 0.6);
+`;
+
+const ModalContent = styled.div`
+ display: flex;
+ position: relative;
+ align-items: center;
+ max-width: 420px;
+ height: 100%;
+ background: rgba(0, 0, 0, 1);
+`;
diff --git a/src/pages/FeedDetail/hooks/queries/useFeedDetailQuery.ts b/src/pages/FeedDetail/hooks/queries/useFeedDetailQuery.ts
index af9a0a6e..75421a33 100644
--- a/src/pages/FeedDetail/hooks/queries/useFeedDetailQuery.ts
+++ b/src/pages/FeedDetail/hooks/queries/useFeedDetailQuery.ts
@@ -6,7 +6,24 @@ const useFeedDetailQuery = (id: string) => {
const { data: feedDetail } = useSuspenseQuery({
queryKey: ['feed', 'detail', id],
queryFn: () => getFeedDetail(id),
- select: (data) => ({ ...data }),
+ select: (data) => ({
+ ...data,
+ imageResponses: [
+ {
+ id: 1,
+ imageUrl: 'https://www.contestkorea.com/admincenter/files/meet/202207070917022079123.jpg',
+ },
+ {
+ id: 2,
+ imageUrl: 'https://news.nateimg.co.kr/orgImg/sh/2022/11/18/6812837_996935_3331.jpg',
+ },
+ {
+ id: 3,
+ imageUrl:
+ 'https://www.syu.ac.kr/wp-content/uploads/2020/07/%EA%B3%B5%EB%AA%A8%EC%A0%84-%ED%8F%AC%EC%8A%A4%ED%84%B0-scaled.jpg',
+ },
+ ],
+ }),
});
return feedDetail;
diff --git a/src/pages/FeedDetail/types/index.ts b/src/pages/FeedDetail/types/index.ts
index de8aa3fb..d7c82269 100644
--- a/src/pages/FeedDetail/types/index.ts
+++ b/src/pages/FeedDetail/types/index.ts
@@ -1,3 +1,9 @@
+export interface ImageResponse {
+ id: number;
+ ideaId: number;
+ imageUrl: string;
+}
+
export interface FeedDetailResponse {
memberId: number;
imageUrl: string;
@@ -18,6 +24,7 @@ export interface FeedDetailResponse {
owner: boolean;
ownerScrap: boolean;
ownerLike: boolean;
+ imageResponses: ImageResponse[];
}
export interface CommentParentResponse {
diff --git a/yarn.lock b/yarn.lock
index 0dc10111..e5b8f668 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1175,6 +1175,11 @@ chalk@^4.0.0:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
+classnames@^2.2.5:
+ version "2.5.1"
+ resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.5.1.tgz#ba774c614be0f016da105c858e7159eae8e7687b"
+ integrity sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==
+
color-convert@^1.9.0:
version "1.9.3"
resolved "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz"
@@ -2248,7 +2253,7 @@ lodash@^4.17.21:
resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
-loose-envify@^1.0.0, loose-envify@^1.1.0:
+loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0:
version "1.4.0"
resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz"
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
@@ -2362,6 +2367,11 @@ node-releases@^2.0.13:
resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz"
integrity sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==
+object-assign@^4.1.1:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
+ integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==
+
object-inspect@^1.12.3, object-inspect@^1.9.0:
version "1.12.3"
resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz"
@@ -2514,6 +2524,15 @@ prettier@^3.0.3:
resolved "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz"
integrity sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==
+prop-types@^15.5.8:
+ version "15.8.1"
+ resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
+ integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
+ dependencies:
+ loose-envify "^1.4.0"
+ object-assign "^4.1.1"
+ react-is "^16.13.1"
+
proxy-from-env@^1.1.0:
version "1.1.0"
resolved "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz"
@@ -2537,6 +2556,13 @@ react-dom@^18.2.0:
loose-envify "^1.1.0"
scheduler "^0.23.0"
+react-easy-swipe@^0.0.21:
+ version "0.0.21"
+ resolved "https://registry.yarnpkg.com/react-easy-swipe/-/react-easy-swipe-0.0.21.tgz#ce9384d576f7a8529dc2ca377c1bf03920bac8eb"
+ integrity sha512-OeR2jAxdoqUMHIn/nS9fgreI5hSpgGoL5ezdal4+oO7YSSgJR8ga+PkYGJrSrJ9MKlPcQjMQXnketrD7WNmNsg==
+ dependencies:
+ prop-types "^15.5.8"
+
react-fast-compare@^3.2.2:
version "3.2.2"
resolved "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz"
@@ -2551,7 +2577,7 @@ react-helmet-async@^2.0.4:
react-fast-compare "^3.2.2"
shallowequal "^1.1.0"
-react-is@^16.7.0:
+react-is@^16.13.1, react-is@^16.7.0:
version "16.13.1"
resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
@@ -2561,6 +2587,15 @@ react-refresh@^0.14.0:
resolved "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz"
integrity sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==
+react-responsive-carousel@^3.2.23:
+ version "3.2.23"
+ resolved "https://registry.yarnpkg.com/react-responsive-carousel/-/react-responsive-carousel-3.2.23.tgz#4c0016ff54603e604bb5c1f9e7ef2d1eda133f1d"
+ integrity sha512-pqJLsBaKHWJhw/ItODgbVoziR2z4lpcJg+YwmRlSk4rKH32VE633mAtZZ9kDXjy4wFO+pgUZmDKPsPe1fPmHCg==
+ dependencies:
+ classnames "^2.2.5"
+ prop-types "^15.5.8"
+ react-easy-swipe "^0.0.21"
+
react-router-dom@^6.15.0:
version "6.15.0"
resolved "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.15.0.tgz"