From c12b50bae0bb643e63cbe89c255c6560e4cd37a7 Mon Sep 17 00:00:00 2001 From: Jun Date: Tue, 24 Oct 2023 00:13:28 +0900 Subject: [PATCH 01/14] =?UTF-8?q?chore:=20headless=20emotion-util=20?= =?UTF-8?q?=EB=9D=BC=EC=9D=B4=EB=B8=8C=EB=9F=AC=EB=A6=AC=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 1 + yarn.lock | 39 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 64735f4f3..cecef724c 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "@radix-ui/react-tooltip": "^1.0.5", "@sopt-makers/colors": "^2.2.0", "@tanstack/react-query": "^4.27.0", + "@toss/emotion-utils": "^1.1.10", "await-to-js": "^3.0.0", "axios": "^0.27.2", "cmdk": "^0.2.0", diff --git a/yarn.lock b/yarn.lock index 94e1333d3..df001dcaa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1913,6 +1913,15 @@ __metadata: languageName: node linkType: hard +"@babel/runtime@npm:^7.14.8": + version: 7.23.2 + resolution: "@babel/runtime@npm:7.23.2" + dependencies: + regenerator-runtime: ^0.14.0 + checksum: 6c4df4839ec75ca10175f636d6362f91df8a3137f86b38f6cd3a4c90668a0fe8e9281d320958f4fbd43b394988958585a17c3aab2a4ea6bf7316b22916a371fb + languageName: node + linkType: hard + "@babel/runtime@npm:^7.9.2": version: 7.22.15 resolution: "@babel/runtime@npm:7.22.15" @@ -6396,6 +6405,31 @@ __metadata: languageName: node linkType: hard +"@toss/emotion-utils@npm:^1.1.10": + version: 1.1.10 + resolution: "@toss/emotion-utils@npm:1.1.10" + dependencies: + "@babel/runtime": ^7.14.8 + "@toss/utils": ^1.4.4 + csstype: ^3.0.9 + peerDependencies: + "@emotion/react": "*" + react: ">= 16 || ^18" + react-dom: ">= 16 || ^18" + checksum: ea1f41f91d6b4449326a6f88f59f987c191a4b19be441d9a273416cd431bd1809eb69d0126855679d3921f914530700bb091b9d013e2a00bb89f094663b8c544 + languageName: node + linkType: hard + +"@toss/utils@npm:^1.4.4": + version: 1.4.4 + resolution: "@toss/utils@npm:1.4.4" + dependencies: + "@babel/runtime": ^7.14.8 + date-fns: ^2.25.0 + checksum: 7013b5eb191ebdae3e53844383390a79fbb6751281528165d3730ecd4a9914d09d934fd487c5d79d231a1afa990551cfc7626a89da91dad66a33e8b3fd6e5325 + languageName: node + linkType: hard + "@trysound/sax@npm:0.2.0": version: 0.2.0 resolution: "@trysound/sax@npm:0.2.0" @@ -9461,7 +9495,7 @@ __metadata: languageName: node linkType: hard -"csstype@npm:^3.0.2": +"csstype@npm:^3.0.2, csstype@npm:^3.0.9": version: 3.1.2 resolution: "csstype@npm:3.1.2" checksum: e1a52e6c25c1314d6beef5168da704ab29c5186b877c07d822bd0806717d9a265e8493a2e35ca7e68d0f5d472d43fac1cdce70fd79fd0853dff81f3028d857b5 @@ -9493,7 +9527,7 @@ __metadata: languageName: node linkType: hard -"date-fns@npm:^2.0.1, date-fns@npm:^2.24.0, date-fns@npm:^2.29.1": +"date-fns@npm:^2.0.1, date-fns@npm:^2.24.0, date-fns@npm:^2.25.0, date-fns@npm:^2.29.1": version: 2.30.0 resolution: "date-fns@npm:2.30.0" dependencies: @@ -17745,6 +17779,7 @@ __metadata: "@tanstack/react-query-devtools": ^4.27.0 "@testing-library/jest-dom": ^6.1.3 "@testing-library/react": ^14.0.0 + "@toss/emotion-utils": ^1.1.10 "@types/jest": ^29.4.0 "@types/lodash-es": ^4.17.6 "@types/node": 17.0.25 From 7873f601d0adf5b7c6aae2f60ec9c3b50f361336 Mon Sep 17 00:00:00 2001 From: Jun Date: Tue, 24 Oct 2023 00:14:00 +0900 Subject: [PATCH 02/14] =?UTF-8?q?feat:=20desktop=20Feed=20Card=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feed/main/feedCard/FeedCard.stories.tsx | 106 +++++++++ .../feed/main/feedCard/FeedCard.tsx | 207 ++++++++++++++++++ 2 files changed, 313 insertions(+) create mode 100644 src/components/feed/main/feedCard/FeedCard.stories.tsx create mode 100644 src/components/feed/main/feedCard/FeedCard.tsx diff --git a/src/components/feed/main/feedCard/FeedCard.stories.tsx b/src/components/feed/main/feedCard/FeedCard.stories.tsx new file mode 100644 index 000000000..8dad9b2ae --- /dev/null +++ b/src/components/feed/main/feedCard/FeedCard.stories.tsx @@ -0,0 +1,106 @@ +import { Meta, StoryObj } from '@storybook/react'; + +import FeedCard from './FeedCard'; + +const meta = { + component: FeedCard, +} satisfies Meta; +export default meta; + +type Story = StoryObj; + +export const Default = { + args: { + author: { + name: '이준호', + info: 'Frontend Developer @Toss', + profileImage: + 'https://s3.ap-northeast-2.amazonaws.com/sopt-makers-internal//prod/image/project/f7ca1cbd-87e4-4dd3-92cf-e2420afee237-IMG_9435.JPG', + }, + lastUpdatedAt: '14분 전', + title: '다음주 일요일에 클라이밍하러가실 분', + content: + '처음해봐도 괜찮으니까 편하게 오세요! 장소는 클라이밍파크 신논현점 생각하고 있어요. 2시쯤 만나서 하고, 끝나고 같이 고기 먹어요~', + comments: [ + { + name: '주수한', + comment: '저도 같이 가고 싶어요!', + }, + { + name: '이정연', + comment: '도요 도요 저도요!', + }, + { + name: '남부장', + comment: '어허, 나는 왜 빼려고 하는가 허허', + }, + ], + views: 23, + images: [], + }, +} satisfies Story; + +export const 익명 = { + args: { + ...Default.args, + isAnonymous: true, + }, +} satisfies Story; + +export const 질문 = { + args: { + ...Default.args, + title: 'CSS 질문 있습니다!', + content: 'Flexbox container 안의 자식 item이 container의 width를 넘어가는데 왜 그런지 아시나요?', + isQuestion: true, + comments: [ + { + name: '박건영', + comment: + 'flexbox의 자식 컴포넌트의 기본 min-width값은 min-width: auto 에요. 따라서 컨테이너의 width를 넘어가지 않을 것으로 예상되지만, flex-basis: auto보다 min-width: auto가 먼저 적용되어 자식 컴포넌트가 옆으로 길어질 경우 컨테이너의 너비를 초과하게 돼요. 이를 해결하려면 min-width: 0 을 주어야 해요!', + }, + { + name: '남부장', + comment: '커사원 잘했네.', + }, + ], + }, +} satisfies Story; + +export const 댓글한개 = { + args: { + ...Default.args, + comments: [ + { + name: 'Evan Kim', + comment: 'LGTM!', + }, + ], + }, +} satisfies Story; + +const 당근_배경 = + 'https://github.com/sopt-makers/sopt-playground-frontend/assets/26808056/ab298948-935c-41e9-801f-420ba4482a10'; +const 노트북_당근 = + 'https://github.com/sopt-makers/sopt-playground-frontend/assets/26808056/33e150c1-e8f9-4d02-bf32-1fe1f1e7cc8b'; +const 폰배경_당근 = + 'https://github.com/sopt-makers/sopt-playground-frontend/assets/26808056/0b168abb-8f7f-4562-874a-b179a1ddd012'; + +export const 이미지 = { + args: { + ...Default.args, + title: '이번에 당근 리브랜딩', + content: '너무 귀엽지 않나요? 당근이 배경화면 공유합니다', + comments: [ + { + name: '이화정', + comment: '너무 귀여워요!! 당근 너무 좋아요', + }, + { + name: '민솔', + comment: '배고파요...', + }, + ], + images: [당근_배경, 노트북_당근, 폰배경_당근], + }, +} satisfies Story; diff --git a/src/components/feed/main/feedCard/FeedCard.tsx b/src/components/feed/main/feedCard/FeedCard.tsx new file mode 100644 index 000000000..0d8e15cce --- /dev/null +++ b/src/components/feed/main/feedCard/FeedCard.tsx @@ -0,0 +1,207 @@ +import styled from '@emotion/styled'; +import { colors } from '@sopt-makers/colors'; +import { Flex, Stack } from '@toss/emotion-utils'; + +import Text from '@/components/common/Text'; +import { textStyles } from '@/styles/typography'; + +interface Comment { + name: string; + comment: string; +} + +interface FeedCardProps { + isMyFeed?: boolean; + isAnonymous?: boolean; + isQuestion?: boolean; + author: { + name: string; + profileImage: string; + info: string; // api interface 들어오면 확정짓기 + }; + // date로 변경되면 바꾸기 + lastUpdatedAt: string; + title: string; + content: string; + comments: Comment[]; + views: number; + images: string[]; +} + +// desktop +const FeedCard = ({ + isMyFeed = false, + author, + lastUpdatedAt, + isAnonymous = false, + isQuestion = false, + title, + content, + comments, + views, + images, +}: FeedCardProps) => { + return ( + + {isAnonymous ? : } + + + {isAnonymous ? ( + 익명 + ) : ( + + {author.name} + + ∙ + + + {author.info} + + + )} + + + {lastUpdatedAt} + + + + + + {isQuestion ? 질문 : null} + {title} + + {content} + {images.length > 0 ? ( + + {images.map((image, index) => ( + + ))} + + ) : null} + {comments.length > 0 ? ( + + {comments.map((comment, index) => ( + + {comment.name} + {comment.comment} + + ))} + + ) : null} + + {`댓글 ${comments.length}개`} + + {`조회수 ${views}회`} + + + + ); +}; + +export default FeedCard; + +const StyeledFeedCard = styled.div` + display: flex; + gap: 8px; + background-color: ${colors.gray950}; + padding: 16px; + width: 560px; +`; + +const ProfileImage = styled.img` + flex-shrink: 0; + border-radius: 50%; + width: 32px; + height: 32px; + object-fit: cover; +`; + +const StyledStack = styled(Stack.Vertical)` + min-width: 0; +`; + +const Content = styled(Text)` + line-height: 22px; +`; + +const FeedImageWrapper = styled.div` + display: flex; + gap: 8px; + overflow: auto; + white-space: nowrap; +`; + +const FeedImage = styled.img` + border-radius: 12px; + height: 160px; + object-fit: cover; +`; + +const CommentWrapper = styled.div` + display: flex; + gap: 8px; + overflow-x: auto; + white-space: nowrap; +`; + +const Comment = styled.div` + display: flex; + flex-grow: 1; + gap: 8px; + border: 0.5px solid ${colors.gray700}; + border-radius: 10px; + padding: 10px 12px; + + ${textStyles.SUIT_13_R}; +`; + +const Bottom = styled(Stack.Horizontal)` + color: ${colors.gray400}; + ${textStyles.SUIT_13_R} +`; + +const QuestionBadge = styled.div` + border-radius: 5px; + background-color: rgb(255 110 29 / 20%); + padding: 3px 5px; + color: ${colors.secondary}; + ${textStyles.SUIT_12_SB}; +`; + +const IconMoreHoriz = () => ( + + + + + +); + +const IconMember = () => ( +
+ + + + + +
+); From 5b8e91c497090102b9a96df665b46fa78b2ab72c Mon Sep 17 00:00:00 2001 From: Jun Date: Wed, 25 Oct 2023 18:23:44 +0900 Subject: [PATCH 03/14] =?UTF-8?q?chore:=20=EC=A0=9C=EB=AA=A9=20=EB=A7=90?= =?UTF-8?q?=EC=A4=84=EC=9E=84=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feed/main/feedCard/FeedCard.stories.tsx | 8 ++++++++ src/components/feed/main/feedCard/FeedCard.tsx | 13 ++++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/components/feed/main/feedCard/FeedCard.stories.tsx b/src/components/feed/main/feedCard/FeedCard.stories.tsx index 8dad9b2ae..1dfb1c7b3 100644 --- a/src/components/feed/main/feedCard/FeedCard.stories.tsx +++ b/src/components/feed/main/feedCard/FeedCard.stories.tsx @@ -104,3 +104,11 @@ export const 이미지 = { images: [당근_배경, 노트북_당근, 폰배경_당근], }, } satisfies Story; + +export const 제목겁나길때 = { + args: { + ...Default.args, + title: + '제목이 겁나 길어서 두 줄로 줄바꿈 될 뿐만 아니라, 말줄임표까지 생기게 하는게 제 목표에요. 당근이는 귀엽지만, 그래도 춘식이에 비하면 그냥 토끼옷을 입은 강아지일 뿐이에요.', + }, +} satisfies Story; diff --git a/src/components/feed/main/feedCard/FeedCard.tsx b/src/components/feed/main/feedCard/FeedCard.tsx index 0d8e15cce..e849c221a 100644 --- a/src/components/feed/main/feedCard/FeedCard.tsx +++ b/src/components/feed/main/feedCard/FeedCard.tsx @@ -70,7 +70,7 @@ const FeedCard = ({ {isQuestion ? 질문 : null} - {title} + {title} {content} {images.length > 0 ? ( @@ -126,6 +126,17 @@ const Content = styled(Text)` line-height: 22px; `; +const Title = styled(Text)` + /* stylelint-disable */ + display: -webkit-box; + overflow: hidden; + text-overflow: ellipsis; + line-height: 22px; + height: 44px; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; +`; + const FeedImageWrapper = styled.div` display: flex; gap: 8px; From 0fc6a5a05d562c6215cb5b8445b54de5b32d9313 Mon Sep 17 00:00:00 2001 From: Jun Date: Fri, 27 Oct 2023 14:08:04 +0900 Subject: [PATCH 04/14] =?UTF-8?q?chore:=20=EC=A0=9C=EB=AA=A9=EC=97=86?= =?UTF-8?q?=EC=9D=84=EB=95=8C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/feed/main/feedCard/FeedCard.stories.tsx | 7 +++++++ src/components/feed/main/feedCard/FeedCard.tsx | 1 - 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/components/feed/main/feedCard/FeedCard.stories.tsx b/src/components/feed/main/feedCard/FeedCard.stories.tsx index 1dfb1c7b3..2173f2065 100644 --- a/src/components/feed/main/feedCard/FeedCard.stories.tsx +++ b/src/components/feed/main/feedCard/FeedCard.stories.tsx @@ -112,3 +112,10 @@ export const 제목겁나길때 = { '제목이 겁나 길어서 두 줄로 줄바꿈 될 뿐만 아니라, 말줄임표까지 생기게 하는게 제 목표에요. 당근이는 귀엽지만, 그래도 춘식이에 비하면 그냥 토끼옷을 입은 강아지일 뿐이에요.', }, } satisfies Story; + +export const 제목없을때 = { + args: { + ...Default.args, + title: '', + }, +} satisfies Story; diff --git a/src/components/feed/main/feedCard/FeedCard.tsx b/src/components/feed/main/feedCard/FeedCard.tsx index e849c221a..9087e3e55 100644 --- a/src/components/feed/main/feedCard/FeedCard.tsx +++ b/src/components/feed/main/feedCard/FeedCard.tsx @@ -132,7 +132,6 @@ const Title = styled(Text)` overflow: hidden; text-overflow: ellipsis; line-height: 22px; - height: 44px; -webkit-box-orient: vertical; -webkit-line-clamp: 2; `; From beaf8f72ff0147ff5f6231a6eb86a5e7d17f6598 Mon Sep 17 00:00:00 2001 From: Jun Date: Sat, 28 Oct 2023 17:13:53 +0900 Subject: [PATCH 05/14] =?UTF-8?q?chore:=20=EB=A7=90=EC=A4=84=EC=9E=84=20&?= =?UTF-8?q?=20=EB=8D=94=EB=B3=B4=EA=B8=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feed/main/feedCard/FeedCard.stories.tsx | 13 +++++++++- .../feed/main/feedCard/FeedCard.tsx | 25 +++++++++++++++---- src/constants/links.ts | 1 + 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/src/components/feed/main/feedCard/FeedCard.stories.tsx b/src/components/feed/main/feedCard/FeedCard.stories.tsx index 2173f2065..0f6531257 100644 --- a/src/components/feed/main/feedCard/FeedCard.stories.tsx +++ b/src/components/feed/main/feedCard/FeedCard.stories.tsx @@ -43,7 +43,7 @@ export const Default = { export const 익명 = { args: { ...Default.args, - isAnonymous: true, + isBlindWriter: true, }, } satisfies Story; @@ -119,3 +119,14 @@ export const 제목없을때 = { title: '', }, } satisfies Story; + +export const 내용겁나길때 = { + args: { + ...Default.args, + title: + '제목이 겁나 길어서 두 줄로 줄바꿈 될 뿐만 아니라, 말줄임표까지 생기게 하는게 제 목표에요. 당근이는 귀엽지만, 그래도 춘식이에 비하면 그냥 토끼옷을 입은 강아지일 뿐이에요.', + content: `내용도 겁나 길어서 두 줄로 줄바꿈 될 뿐만 아니라, 말줄임표까지 생기게 하는게 제 목표에요. 당근이는 귀엽지만, 그래도 춘식이에 비하면 그냥 토끼옷을 입은 강아지일 뿐이에요.\n +제목이 겁나 길어서 두 줄로 줄바꿈 될 뿐만 아니라, 말줄임표까지 생기게 하는게 제 목표에요. 당근이는 귀엽지만, 그래도 춘식이에 비하면 그냥 토끼옷을 입은 강아지일 뿐이에요. + `, + }, +} satisfies Story; diff --git a/src/components/feed/main/feedCard/FeedCard.tsx b/src/components/feed/main/feedCard/FeedCard.tsx index 9087e3e55..61543a035 100644 --- a/src/components/feed/main/feedCard/FeedCard.tsx +++ b/src/components/feed/main/feedCard/FeedCard.tsx @@ -12,7 +12,7 @@ interface Comment { interface FeedCardProps { isMyFeed?: boolean; - isAnonymous?: boolean; + isBlindWriter?: boolean; isQuestion?: boolean; author: { name: string; @@ -33,7 +33,7 @@ const FeedCard = ({ isMyFeed = false, author, lastUpdatedAt, - isAnonymous = false, + isBlindWriter = false, isQuestion = false, title, content, @@ -43,10 +43,10 @@ const FeedCard = ({ }: FeedCardProps) => { return ( - {isAnonymous ? : } + {isBlindWriter ? : } - {isAnonymous ? ( + {isBlindWriter ? ( 익명 ) : ( @@ -72,7 +72,7 @@ const FeedCard = ({ {isQuestion ? 질문 : null} {title} - {content} + {renderContent(content)} {images.length > 0 ? ( {images.map((image, index) => ( @@ -124,6 +124,7 @@ const StyledStack = styled(Stack.Vertical)` const Content = styled(Text)` line-height: 22px; + white-space: pre-wrap; `; const Title = styled(Text)` @@ -180,6 +181,20 @@ const QuestionBadge = styled.div` ${textStyles.SUIT_12_SB}; `; +const renderContent = (content: string) => { + if (content.length > 140) { + return ( + <> + {content.slice(0, 140) + '... '} + + 더보기 + + + ); + } + return content; +}; + const IconMoreHoriz = () => ( `/sopticle/success`, mentoringDetail: (id: number) => `/mentoring/${id}`, wordchain: () => `/wordchain`, + feedList: () => `/feed`, }; From abdf424e4eeead52b3315844ff18eb256a52c375 Mon Sep 17 00:00:00 2001 From: Jun Date: Sun, 29 Oct 2023 00:11:47 +0900 Subject: [PATCH 06/14] =?UTF-8?q?feat:=20createdAt=20relative=20time=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feed/main/feedCard/FeedCard.stories.tsx | 14 +++++++-- .../feed/main/feedCard/FeedCard.tsx | 31 +++++++++---------- src/components/feed/utils.ts | 11 +++++++ 3 files changed, 37 insertions(+), 19 deletions(-) create mode 100644 src/components/feed/utils.ts diff --git a/src/components/feed/main/feedCard/FeedCard.stories.tsx b/src/components/feed/main/feedCard/FeedCard.stories.tsx index 0f6531257..1f564a12d 100644 --- a/src/components/feed/main/feedCard/FeedCard.stories.tsx +++ b/src/components/feed/main/feedCard/FeedCard.stories.tsx @@ -11,31 +11,34 @@ type Story = StoryObj; export const Default = { args: { - author: { + member: { name: '이준호', info: 'Frontend Developer @Toss', profileImage: 'https://s3.ap-northeast-2.amazonaws.com/sopt-makers-internal//prod/image/project/f7ca1cbd-87e4-4dd3-92cf-e2420afee237-IMG_9435.JPG', }, - lastUpdatedAt: '14분 전', + createdAt: '2023.10.23.11:00:11', title: '다음주 일요일에 클라이밍하러가실 분', content: '처음해봐도 괜찮으니까 편하게 오세요! 장소는 클라이밍파크 신논현점 생각하고 있어요. 2시쯤 만나서 하고, 끝나고 같이 고기 먹어요~', comments: [ { + id: 1, name: '주수한', comment: '저도 같이 가고 싶어요!', }, { + id: 2, name: '이정연', comment: '도요 도요 저도요!', }, { + id: 3, name: '남부장', comment: '어허, 나는 왜 빼려고 하는가 허허', }, ], - views: 23, + hits: 23, images: [], }, } satisfies Story; @@ -55,11 +58,13 @@ export const 질문 = { isQuestion: true, comments: [ { + id: 1, name: '박건영', comment: 'flexbox의 자식 컴포넌트의 기본 min-width값은 min-width: auto 에요. 따라서 컨테이너의 width를 넘어가지 않을 것으로 예상되지만, flex-basis: auto보다 min-width: auto가 먼저 적용되어 자식 컴포넌트가 옆으로 길어질 경우 컨테이너의 너비를 초과하게 돼요. 이를 해결하려면 min-width: 0 을 주어야 해요!', }, { + id: 2, name: '남부장', comment: '커사원 잘했네.', }, @@ -72,6 +77,7 @@ export const 댓글한개 = { ...Default.args, comments: [ { + id: 1, name: 'Evan Kim', comment: 'LGTM!', }, @@ -93,10 +99,12 @@ export const 이미지 = { content: '너무 귀엽지 않나요? 당근이 배경화면 공유합니다', comments: [ { + id: 1, name: '이화정', comment: '너무 귀여워요!! 당근 너무 좋아요', }, { + id: 2, name: '민솔', comment: '배고파요...', }, diff --git a/src/components/feed/main/feedCard/FeedCard.tsx b/src/components/feed/main/feedCard/FeedCard.tsx index 61543a035..d1a1f4284 100644 --- a/src/components/feed/main/feedCard/FeedCard.tsx +++ b/src/components/feed/main/feedCard/FeedCard.tsx @@ -3,9 +3,11 @@ import { colors } from '@sopt-makers/colors'; import { Flex, Stack } from '@toss/emotion-utils'; import Text from '@/components/common/Text'; +import { getRelativeTime } from '@/components/feed/utils'; import { textStyles } from '@/styles/typography'; interface Comment { + id: number; name: string; comment: string; } @@ -14,54 +16,51 @@ interface FeedCardProps { isMyFeed?: boolean; isBlindWriter?: boolean; isQuestion?: boolean; - author: { + member: { name: string; profileImage: string; info: string; // api interface 들어오면 확정짓기 }; - // date로 변경되면 바꾸기 - lastUpdatedAt: string; + createdAt: string; title: string; content: string; comments: Comment[]; - views: number; + hits: number; images: string[]; } -// desktop const FeedCard = ({ - isMyFeed = false, - author, - lastUpdatedAt, + member, + createdAt, isBlindWriter = false, isQuestion = false, title, content, comments, - views, + hits, images, }: FeedCardProps) => { return ( - {isBlindWriter ? : } + {isBlindWriter ? : } {isBlindWriter ? ( 익명 ) : ( - {author.name} + {member.name} - {author.info} + {member.info} )} - {lastUpdatedAt} + {getRelativeTime(createdAt)} diff --git a/src/components/feed/utils.ts b/src/components/feed/utils.ts new file mode 100644 index 000000000..45ffc9c49 --- /dev/null +++ b/src/components/feed/utils.ts @@ -0,0 +1,11 @@ +import 'dayjs/locale/ko'; + +import dayjs from 'dayjs'; +import relativeTime from 'dayjs/plugin/relativeTime'; + +dayjs.locale('ko'); +dayjs.extend(relativeTime); + +export const getRelativeTime = (date: string) => { + return dayjs(date).fromNow(); +}; From 90eb2a1f3d8eddaab8abeb81cfa444e9a7753f93 Mon Sep 17 00:00:00 2001 From: Jun Date: Sun, 29 Oct 2023 00:36:25 +0900 Subject: [PATCH 07/14] =?UTF-8?q?chore:=20=ED=94=BC=EB=93=9C=20=EC=9E=84?= =?UTF-8?q?=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feed/detail/DetailFeedCard.stories.tsx | 17 +++++ src/components/feed/detail/DetailFeedCard.tsx | 63 +++++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 src/components/feed/detail/DetailFeedCard.stories.tsx create mode 100644 src/components/feed/detail/DetailFeedCard.tsx diff --git a/src/components/feed/detail/DetailFeedCard.stories.tsx b/src/components/feed/detail/DetailFeedCard.stories.tsx new file mode 100644 index 000000000..979c28cc4 --- /dev/null +++ b/src/components/feed/detail/DetailFeedCard.stories.tsx @@ -0,0 +1,17 @@ +import { Meta, StoryObj } from '@storybook/react'; + +import DetailFeedCard from './DetailFeedCard'; + +const meta = { + component: DetailFeedCard, +} satisfies Meta; +export default meta; + +type Story = StoryObj; + +export const Default = { + args: { + category: '파트', + tag: '기획', + }, +} satisfies Story; diff --git a/src/components/feed/detail/DetailFeedCard.tsx b/src/components/feed/detail/DetailFeedCard.tsx new file mode 100644 index 000000000..e3ab1ddd6 --- /dev/null +++ b/src/components/feed/detail/DetailFeedCard.tsx @@ -0,0 +1,63 @@ +import styled from '@emotion/styled'; +import { colors } from '@sopt-makers/colors'; +import { Flex } from '@toss/emotion-utils'; +import React from 'react'; + +interface DetailFeedCardProps { + category: string; + tag: string; +} + +const DetailFeedCard = ({ category, tag }: DetailFeedCardProps) => { + return ( + <> +
+ + + + {category} + + {tag} + + +
+ + ); +}; + +export default DetailFeedCard; + +const Header = styled.header` + padding: 0 24px; +`; + +const IconChevronLeft = () => ( + + + +); + +const Chip = styled.div` + display: flex; + align-items: center; + border-radius: 21px; + background-color: ${colors.gray400}; + padding: 7px 12px; + color: ${colors.gray10}; +`; + +const IconChevronRight = () => ( + + + +); From 3c84d2157781a71dbe5d43012657a6b416bd566c Mon Sep 17 00:00:00 2001 From: Jun Date: Sat, 4 Nov 2023 19:23:51 +0900 Subject: [PATCH 08/14] =?UTF-8?q?chore:=20storybook=EC=97=90=EC=84=9C=20em?= =?UTF-8?q?otion=20css=20prop=20=EC=82=AC=EC=9A=A9=20=EA=B0=80=EB=8A=A5?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .storybook/main.ts | 4 +++ package.json | 3 +- yarn.lock | 82 ++++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 81 insertions(+), 8 deletions(-) diff --git a/.storybook/main.ts b/.storybook/main.ts index 6f1a8690d..1695d9d02 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -39,6 +39,10 @@ const config: StorybookConfig = { return config; }, + babel: (options) => ({ + ...options, + presets: [...(options.presets ?? []), '@emotion/babel-preset-css-prop'], + }), staticDirs: ['../public'], docs: { autodocs: false, diff --git a/package.json b/package.json index cecef724c..c347cadd6 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "@radix-ui/react-slot": "^1.0.1", "@radix-ui/react-tabs": "^1.0.2", "@radix-ui/react-tooltip": "^1.0.5", - "@sopt-makers/colors": "^2.2.0", + "@sopt-makers/colors": "^3.0.0", "@tanstack/react-query": "^4.27.0", "@toss/emotion-utils": "^1.1.10", "await-to-js": "^3.0.0", @@ -73,6 +73,7 @@ "@cloudflare/workers-types": "^3.18.0", "@commitlint/cli": "^16.2.3", "@commitlint/config-conventional": "^16.2.1", + "@emotion/babel-preset-css-prop": "^11.11.0", "@mdx-js/react": "^1.6.22", "@next/bundle-analyzer": "^13.1.1", "@storybook/addon-actions": "^7.0.23", diff --git a/yarn.lock b/yarn.lock index df001dcaa..35cf95908 100644 --- a/yarn.lock +++ b/yarn.lock @@ -340,6 +340,15 @@ __metadata: languageName: node linkType: hard +"@babel/helper-module-imports@npm:^7.22.15": + version: 7.22.15 + resolution: "@babel/helper-module-imports@npm:7.22.15" + dependencies: + "@babel/types": ^7.22.15 + checksum: ecd7e457df0a46f889228f943ef9b4a47d485d82e030676767e6a2fdcbdaa63594d8124d4b55fd160b41c201025aec01fc27580352b1c87a37c9c6f33d116702 + languageName: node + linkType: hard + "@babel/helper-module-transforms@npm:^7.21.5, @babel/helper-module-transforms@npm:^7.22.5": version: 7.22.5 resolution: "@babel/helper-module-transforms@npm:7.22.5" @@ -441,6 +450,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-validator-identifier@npm:^7.22.20": + version: 7.22.20 + resolution: "@babel/helper-validator-identifier@npm:7.22.20" + checksum: 136412784d9428266bcdd4d91c32bcf9ff0e8d25534a9d94b044f77fe76bc50f941a90319b05aafd1ec04f7d127cd57a179a3716009ff7f3412ef835ada95bdc + languageName: node + linkType: hard + "@babel/helper-validator-option@npm:^7.21.0, @babel/helper-validator-option@npm:^7.22.5": version: 7.22.5 resolution: "@babel/helper-validator-option@npm:7.22.5" @@ -843,7 +859,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-jsx@npm:^7.22.5, @babel/plugin-syntax-jsx@npm:^7.7.2": +"@babel/plugin-syntax-jsx@npm:^7.17.12, @babel/plugin-syntax-jsx@npm:^7.22.5, @babel/plugin-syntax-jsx@npm:^7.7.2": version: 7.22.5 resolution: "@babel/plugin-syntax-jsx@npm:7.22.5" dependencies: @@ -1464,6 +1480,21 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-react-jsx@npm:^7.17.12": + version: 7.22.15 + resolution: "@babel/plugin-transform-react-jsx@npm:7.22.15" + dependencies: + "@babel/helper-annotate-as-pure": ^7.22.5 + "@babel/helper-module-imports": ^7.22.15 + "@babel/helper-plugin-utils": ^7.22.5 + "@babel/plugin-syntax-jsx": ^7.22.5 + "@babel/types": ^7.22.15 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 3899054e89550c3a0ef041af7c47ee266e2e934f498ee80fefeda778a6aa177b48aa8b4d2a8bf5848de977fec564571699ab952d9fa089c4c19b45ddb121df09 + languageName: node + linkType: hard + "@babel/plugin-transform-react-jsx@npm:^7.19.0, @babel/plugin-transform-react-jsx@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-react-jsx@npm:7.22.5" @@ -1989,6 +2020,17 @@ __metadata: languageName: node linkType: hard +"@babel/types@npm:^7.22.15": + version: 7.23.0 + resolution: "@babel/types@npm:7.23.0" + dependencies: + "@babel/helper-string-parser": ^7.22.5 + "@babel/helper-validator-identifier": ^7.22.20 + to-fast-properties: ^2.0.0 + checksum: 215fe04bd7feef79eeb4d33374b39909ce9cad1611c4135a4f7fdf41fe3280594105af6d7094354751514625ea92d0875aba355f53e86a92600f290e77b0e604 + languageName: node + linkType: hard + "@babel/types@npm:~7.21.2": version: 7.21.5 resolution: "@babel/types@npm:7.21.5" @@ -2248,6 +2290,17 @@ __metadata: languageName: node linkType: hard +"@emotion/babel-plugin-jsx-pragmatic@npm:^0.2.1": + version: 0.2.1 + resolution: "@emotion/babel-plugin-jsx-pragmatic@npm:0.2.1" + dependencies: + "@babel/plugin-syntax-jsx": ^7.17.12 + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 2c7e9c687c8e12855c9a419e9a8e530b236e1c42f5a196f1abaf5079947d18069733f1f8257e90c6bafa302c87f433577745ea799b63d4211c82d7b3a57917d6 + languageName: node + linkType: hard + "@emotion/babel-plugin@npm:^11.11.0": version: 11.11.0 resolution: "@emotion/babel-plugin@npm:11.11.0" @@ -2267,6 +2320,20 @@ __metadata: languageName: node linkType: hard +"@emotion/babel-preset-css-prop@npm:^11.11.0": + version: 11.11.0 + resolution: "@emotion/babel-preset-css-prop@npm:11.11.0" + dependencies: + "@babel/plugin-transform-react-jsx": ^7.17.12 + "@babel/runtime": ^7.18.3 + "@emotion/babel-plugin": ^11.11.0 + "@emotion/babel-plugin-jsx-pragmatic": ^0.2.1 + peerDependencies: + "@babel/core": ^7.0.0 + checksum: fcaddd107e2fb431cf22254e8a764d4e3308d1dab38611dc55d40a5e746f14b04aee0c12d349e3e0e4af1049d43efa52f8b8d5cc78167204159a40dffb7b4fde + languageName: node + linkType: hard + "@emotion/cache@npm:^11.11.0": version: 11.11.0 resolution: "@emotion/cache@npm:11.11.0" @@ -4960,10 +5027,10 @@ __metadata: languageName: node linkType: hard -"@sopt-makers/colors@npm:^2.2.0": - version: 2.2.0 - resolution: "@sopt-makers/colors@npm:2.2.0" - checksum: 29146fa24433fa947c53ba6dd52433bc681d0857ae0690100f5dfb6e12471d0561991a6b2f121502f480f2887c34ccb04b531b76f8e3afdd5cdd68284fe3d1a1 +"@sopt-makers/colors@npm:^3.0.0": + version: 3.0.0 + resolution: "@sopt-makers/colors@npm:3.0.0" + checksum: c2eaf6e818eeb54b12cdb31e405ea72fc3cb9bad6a6792b012dae88068bec55ae8705a5dd4f0890c25cf39031087db8051f429e30b60eae3530dec6f189c813d languageName: node linkType: hard @@ -4975,7 +5042,7 @@ __metadata: "@emotion/styled": ^11.8.1 "@radix-ui/react-dialog": 1.0.2 "@radix-ui/react-dropdown-menu": 2.0.2 - "@sopt-makers/colors": ^2.2.0 + "@sopt-makers/colors": ^3.0.0 react-remove-scroll: 2.5.5 peerDependencies: react: ^16.8 || ^17.0 || ^18.0 @@ -17748,6 +17815,7 @@ __metadata: "@cloudflare/workers-types": ^3.18.0 "@commitlint/cli": ^16.2.3 "@commitlint/config-conventional": ^16.2.1 + "@emotion/babel-preset-css-prop": ^11.11.0 "@emotion/react": ^11.9.0 "@emotion/styled": ^11.8.1 "@headlessui/react": ^1.6.6 @@ -17764,7 +17832,7 @@ __metadata: "@radix-ui/react-slot": ^1.0.1 "@radix-ui/react-tabs": ^1.0.2 "@radix-ui/react-tooltip": ^1.0.5 - "@sopt-makers/colors": ^2.2.0 + "@sopt-makers/colors": ^3.0.0 "@storybook/addon-actions": ^7.0.23 "@storybook/addon-docs": ^7.0.23 "@storybook/addon-essentials": ^7.0.23 From 48cb62c508e8d0dc20a2ee06ff15423cc66ccb3b Mon Sep 17 00:00:00 2001 From: Jun Date: Sat, 4 Nov 2023 19:34:27 +0900 Subject: [PATCH 09/14] =?UTF-8?q?feat:=20checkbox=20small=20size=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/common/Checkbox/index.tsx | 33 +++++++++++++++++------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/src/components/common/Checkbox/index.tsx b/src/components/common/Checkbox/index.tsx index 987a9bedf..adf19248d 100644 --- a/src/components/common/Checkbox/index.tsx +++ b/src/components/common/Checkbox/index.tsx @@ -6,15 +6,18 @@ import { forwardRef, InputHTMLAttributes } from 'react'; import IconCheck from '@/public/icons/icon-check.svg'; import { MOBILE_MEDIA_QUERY } from '@/styles/mediaQuery'; -interface CheckboxProps extends InputHTMLAttributes { +interface CheckboxProps extends Omit, 'size'> { checked?: boolean; + size?: 'small' | 'medium' | 'large'; } -const Checkbox = forwardRef(({ checked = false, ...props }, ref) => { +const Checkbox = forwardRef(({ checked = false, size = 'medium', ...props }, ref) => { return ( - {checked && } + + {checked && } + ); }); @@ -45,12 +48,6 @@ const StyledCheckbox = styled.span` background-color: transparent; width: 22.5px; height: 22.5px; - ${({ checked }) => - checked && - css` - border: 1px solid ${colors.blue300}; - background-color: ${colors.success}; - `} & > svg { width: 14px; @@ -58,6 +55,24 @@ const StyledCheckbox = styled.span` color: ${colors.gray10}; } + ${({ size }) => + size === 'small' && + css` + width: 16px; + height: 16px; + & > svg { + width: 10px; + height: 6.5px; + } + `} + + ${({ checked }) => + checked && + css` + border: 1px solid ${colors.blue300}; + background-color: ${colors.success}; + `} + @media ${MOBILE_MEDIA_QUERY} { width: 17.5px; height: 17.5px; From 570eafc5a52271ee83dfca54ea0b8acd5d8fc27a Mon Sep 17 00:00:00 2001 From: Jun Date: Sat, 4 Nov 2023 19:35:23 +0900 Subject: [PATCH 10/14] =?UTF-8?q?refactor:=20main=20FeedCard=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/feed/Icon.tsx | 107 ++++++++ src/components/feed/main/FeedCard.stories.tsx | 196 +++++++++++++++ src/components/feed/main/FeedCard.tsx | 184 ++++++++++++++ .../feed/main/feedCard/FeedCard.stories.tsx | 140 ----------- .../feed/main/feedCard/FeedCard.tsx | 231 ------------------ 5 files changed, 487 insertions(+), 371 deletions(-) create mode 100644 src/components/feed/Icon.tsx create mode 100644 src/components/feed/main/FeedCard.stories.tsx create mode 100644 src/components/feed/main/FeedCard.tsx delete mode 100644 src/components/feed/main/feedCard/FeedCard.stories.tsx delete mode 100644 src/components/feed/main/feedCard/FeedCard.tsx diff --git a/src/components/feed/Icon.tsx b/src/components/feed/Icon.tsx new file mode 100644 index 000000000..ebabf584d --- /dev/null +++ b/src/components/feed/Icon.tsx @@ -0,0 +1,107 @@ +export const IconMoreHoriz = () => ( + + + + + +); + +export const IconChevronLeft = () => ( + + + +); + +export const IconChevronRight = () => ( + + + +); + +export const IconShare = () => ( + + + + +); + +export const IconMoreVert = () => ( + + + + + +); + +export const IconMember = () => ( + + + + + +); + +export const IconSendFill = () => ( + + + + + + + + + + +); diff --git a/src/components/feed/main/FeedCard.stories.tsx b/src/components/feed/main/FeedCard.stories.tsx new file mode 100644 index 000000000..97a6eed93 --- /dev/null +++ b/src/components/feed/main/FeedCard.stories.tsx @@ -0,0 +1,196 @@ +import { Meta } from '@storybook/react'; +import { ComponentProps } from 'react'; + +import FeedCard from './FeedCard'; + +const meta = { + component: FeedCard, +} satisfies Meta; +export default meta; + +const COMMENTS = [ + { + id: 1, + name: '주수한', + comment: '저도 같이 가고 싶어요!', + }, + { + id: 2, + name: '이정연', + comment: '도요 도요 저도요!', + }, + { + id: 3, + name: '남부장', + comment: '어허, 나는 왜 빼려고 하는가 허허', + }, +]; + +const defaultProps: ComponentProps = { + name: '이준호', + info: 'Frontend Developer @Toss', + profileImage: + 'https://s3.ap-northeast-2.amazonaws.com/sopt-makers-internal//prod/image/project/f7ca1cbd-87e4-4dd3-92cf-e2420afee237-IMG_9435.JPG', + createdAt: '2023.10.23.11:00:11', + title: '다음주 일요일에 클라이밍하러가실 분', + content: + '처음해봐도 괜찮으니까 편하게 오세요! 장소는 클라이밍파크 신논현점 생각하고 있어요. 2시쯤 만나서 하고, 끝나고 같이 고기 먹어요~', + hits: 23, + commentLength: COMMENTS.length, +}; + +export const 기본 = () => { + return ( + + + {COMMENTS.map((comment) => ( + + ))} + + + ); +}; + +export const 익명 = () => { + return ( + + + {COMMENTS.map((comment) => ( + + ))} + + + ); +}; + +const QURESTION_COMMENTS = [ + { + id: 1, + name: '박건영', + comment: + 'flexbox의 자식 컴포넌트의 기본 min-width값은 min-width: auto 에요. 따라서 컨테이너의 width를 넘어가지 않을 것으로 예상되지만, flex-basis: auto보다 min-width: auto가 먼저 적용되어 자식 컴포넌트가 옆으로 길어질 경우 컨테이너의 너비를 초과하게 돼요. 이를 해결하려면 min-width: 0 을 주어야 해요!', + }, + { + id: 2, + name: '남부장', + comment: '커사원 잘했네.', + }, +]; + +export const 질문 = () => { + return ( + + + {QURESTION_COMMENTS.map((comment) => ( + + ))} + + + ); +}; + +export const 댓글한개 = () => { + return ( + + + {[ + { + id: 1, + name: 'Evan Kim', + comment: 'LGTM!', + }, + ].map((comment) => ( + + ))} + + + ); +}; + +const 당근_배경 = + 'https://github.com/sopt-makers/sopt-playground-frontend/assets/26808056/ab298948-935c-41e9-801f-420ba4482a10'; +const 노트북_당근 = + 'https://github.com/sopt-makers/sopt-playground-frontend/assets/26808056/33e150c1-e8f9-4d02-bf32-1fe1f1e7cc8b'; +const 폰배경_당근 = + 'https://github.com/sopt-makers/sopt-playground-frontend/assets/26808056/0b168abb-8f7f-4562-874a-b179a1ddd012'; + +const IMAGES = [당근_배경, 노트북_당근, 폰배경_당근]; +const IMAGES_COMMENTS = [ + { + id: 1, + name: '이화정', + comment: '너무 귀여워요!! 당근 너무 좋아요', + }, + { + id: 2, + name: '민솔', + comment: '배고파요...', + }, +]; + +export const 이미지와함께 = () => { + return ( + + + {IMAGES.map((image) => ( + + ))} + + + {IMAGES_COMMENTS.map((comment) => ( + + ))} + + + ); +}; + +export const 제목겁나길때 = () => { + return ( + + + {COMMENTS.map((comment) => ( + + ))} + + + ); +}; + +export const 제목없을때 = () => { + return ( + + + {COMMENTS.map((comment) => ( + + ))} + + + ); +}; + +export const 내용겁나길때 = () => { + return ( + + + {COMMENTS.map((comment) => ( + + ))} + + + ); +}; diff --git a/src/components/feed/main/FeedCard.tsx b/src/components/feed/main/FeedCard.tsx new file mode 100644 index 000000000..16a9eee2e --- /dev/null +++ b/src/components/feed/main/FeedCard.tsx @@ -0,0 +1,184 @@ +import styled from '@emotion/styled'; +import { colors } from '@sopt-makers/colors'; +import { Flex, Stack } from '@toss/emotion-utils'; +import { PropsWithChildren } from 'react'; + +import Text from '@/components/common/Text'; +import { IconMember, IconMoreHoriz } from '@/components/feed/Icon'; +import { getRelativeTime } from '@/components/feed/utils'; +import { textStyles } from '@/styles/typography'; + +interface BaseProps { + profileImage: string; + name: string; + info: string; + title: string; + content: string; + createdAt: string; + isBlindWriter?: boolean; + isQuestion?: boolean; + commentLength: number; + hits: number; +} + +const Base = ({ + profileImage, + name, + info, + title, + content, + createdAt, + isBlindWriter = false, + isQuestion = false, + commentLength, + hits, + children, +}: PropsWithChildren) => { + return ( + + {isBlindWriter ? : } + + + {isBlindWriter ? ( + 익명 + ) : ( + + {name} + + ∙ + + + {info} + + + )} + + + {getRelativeTime(createdAt)} + + + + + + {isQuestion ? 질문 : null} + {title} + + + {renderContent(content)} + + {children} + + {`댓글 ${commentLength}개`} + + {`조회수 ${hits}회`} + + + + ); +}; + +const ProfileImage = styled.img` + flex-shrink: 0; + border-radius: 50%; + width: 32px; + height: 32px; + object-fit: cover; +`; + +const Title = styled(Text)` + /* stylelint-disable */ + display: -webkit-box; + overflow: hidden; + text-overflow: ellipsis; + line-height: 22px; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; +`; + +const QuestionBadge = styled.div` + border-radius: 5px; + background-color: ${colors.orangeAlpha200}; + padding: 6px; + color: ${colors.secondary}; + ${textStyles.SUIT_12_SB}; +`; + +const Bottom = styled(Stack.Horizontal)` + color: ${colors.gray400}; + ${textStyles.SUIT_13_R} +`; + +const renderContent = (content: string) => { + if (content.length > 140) { + return ( + <> + {content.slice(0, 140) + '... '} + {/* TODO: 연결 */} + + 더보기 + + + ); + } + return content; +}; + +const Image = ({ children }: PropsWithChildren) => { + return {children}; +}; + +const ImageItem = styled.img` + border-radius: 12px; + height: 160px; + object-fit: cover; +`; + +const Comment = ({ children }: PropsWithChildren) => { + return {children}; +}; + +interface CommentItemProps { + name: string; + comment: string; +} + +const CommentItem = ({ name, comment }: CommentItemProps) => { + return ( + + {name} + {comment} + + ); +}; + +const StyledCommentItem = styled.div` + display: flex; + flex-grow: 1; + gap: 8px; + border: 0.5px solid ${colors.gray700}; + border-radius: 10px; + padding: 10px 12px; + + ${textStyles.SUIT_13_R}; +`; + +export default Object.assign(Base, { + Image, + ImageItem, + Comment, + CommentItem, +}); diff --git a/src/components/feed/main/feedCard/FeedCard.stories.tsx b/src/components/feed/main/feedCard/FeedCard.stories.tsx deleted file mode 100644 index 1f564a12d..000000000 --- a/src/components/feed/main/feedCard/FeedCard.stories.tsx +++ /dev/null @@ -1,140 +0,0 @@ -import { Meta, StoryObj } from '@storybook/react'; - -import FeedCard from './FeedCard'; - -const meta = { - component: FeedCard, -} satisfies Meta; -export default meta; - -type Story = StoryObj; - -export const Default = { - args: { - member: { - name: '이준호', - info: 'Frontend Developer @Toss', - profileImage: - 'https://s3.ap-northeast-2.amazonaws.com/sopt-makers-internal//prod/image/project/f7ca1cbd-87e4-4dd3-92cf-e2420afee237-IMG_9435.JPG', - }, - createdAt: '2023.10.23.11:00:11', - title: '다음주 일요일에 클라이밍하러가실 분', - content: - '처음해봐도 괜찮으니까 편하게 오세요! 장소는 클라이밍파크 신논현점 생각하고 있어요. 2시쯤 만나서 하고, 끝나고 같이 고기 먹어요~', - comments: [ - { - id: 1, - name: '주수한', - comment: '저도 같이 가고 싶어요!', - }, - { - id: 2, - name: '이정연', - comment: '도요 도요 저도요!', - }, - { - id: 3, - name: '남부장', - comment: '어허, 나는 왜 빼려고 하는가 허허', - }, - ], - hits: 23, - images: [], - }, -} satisfies Story; - -export const 익명 = { - args: { - ...Default.args, - isBlindWriter: true, - }, -} satisfies Story; - -export const 질문 = { - args: { - ...Default.args, - title: 'CSS 질문 있습니다!', - content: 'Flexbox container 안의 자식 item이 container의 width를 넘어가는데 왜 그런지 아시나요?', - isQuestion: true, - comments: [ - { - id: 1, - name: '박건영', - comment: - 'flexbox의 자식 컴포넌트의 기본 min-width값은 min-width: auto 에요. 따라서 컨테이너의 width를 넘어가지 않을 것으로 예상되지만, flex-basis: auto보다 min-width: auto가 먼저 적용되어 자식 컴포넌트가 옆으로 길어질 경우 컨테이너의 너비를 초과하게 돼요. 이를 해결하려면 min-width: 0 을 주어야 해요!', - }, - { - id: 2, - name: '남부장', - comment: '커사원 잘했네.', - }, - ], - }, -} satisfies Story; - -export const 댓글한개 = { - args: { - ...Default.args, - comments: [ - { - id: 1, - name: 'Evan Kim', - comment: 'LGTM!', - }, - ], - }, -} satisfies Story; - -const 당근_배경 = - 'https://github.com/sopt-makers/sopt-playground-frontend/assets/26808056/ab298948-935c-41e9-801f-420ba4482a10'; -const 노트북_당근 = - 'https://github.com/sopt-makers/sopt-playground-frontend/assets/26808056/33e150c1-e8f9-4d02-bf32-1fe1f1e7cc8b'; -const 폰배경_당근 = - 'https://github.com/sopt-makers/sopt-playground-frontend/assets/26808056/0b168abb-8f7f-4562-874a-b179a1ddd012'; - -export const 이미지 = { - args: { - ...Default.args, - title: '이번에 당근 리브랜딩', - content: '너무 귀엽지 않나요? 당근이 배경화면 공유합니다', - comments: [ - { - id: 1, - name: '이화정', - comment: '너무 귀여워요!! 당근 너무 좋아요', - }, - { - id: 2, - name: '민솔', - comment: '배고파요...', - }, - ], - images: [당근_배경, 노트북_당근, 폰배경_당근], - }, -} satisfies Story; - -export const 제목겁나길때 = { - args: { - ...Default.args, - title: - '제목이 겁나 길어서 두 줄로 줄바꿈 될 뿐만 아니라, 말줄임표까지 생기게 하는게 제 목표에요. 당근이는 귀엽지만, 그래도 춘식이에 비하면 그냥 토끼옷을 입은 강아지일 뿐이에요.', - }, -} satisfies Story; - -export const 제목없을때 = { - args: { - ...Default.args, - title: '', - }, -} satisfies Story; - -export const 내용겁나길때 = { - args: { - ...Default.args, - title: - '제목이 겁나 길어서 두 줄로 줄바꿈 될 뿐만 아니라, 말줄임표까지 생기게 하는게 제 목표에요. 당근이는 귀엽지만, 그래도 춘식이에 비하면 그냥 토끼옷을 입은 강아지일 뿐이에요.', - content: `내용도 겁나 길어서 두 줄로 줄바꿈 될 뿐만 아니라, 말줄임표까지 생기게 하는게 제 목표에요. 당근이는 귀엽지만, 그래도 춘식이에 비하면 그냥 토끼옷을 입은 강아지일 뿐이에요.\n -제목이 겁나 길어서 두 줄로 줄바꿈 될 뿐만 아니라, 말줄임표까지 생기게 하는게 제 목표에요. 당근이는 귀엽지만, 그래도 춘식이에 비하면 그냥 토끼옷을 입은 강아지일 뿐이에요. - `, - }, -} satisfies Story; diff --git a/src/components/feed/main/feedCard/FeedCard.tsx b/src/components/feed/main/feedCard/FeedCard.tsx deleted file mode 100644 index d1a1f4284..000000000 --- a/src/components/feed/main/feedCard/FeedCard.tsx +++ /dev/null @@ -1,231 +0,0 @@ -import styled from '@emotion/styled'; -import { colors } from '@sopt-makers/colors'; -import { Flex, Stack } from '@toss/emotion-utils'; - -import Text from '@/components/common/Text'; -import { getRelativeTime } from '@/components/feed/utils'; -import { textStyles } from '@/styles/typography'; - -interface Comment { - id: number; - name: string; - comment: string; -} - -interface FeedCardProps { - isMyFeed?: boolean; - isBlindWriter?: boolean; - isQuestion?: boolean; - member: { - name: string; - profileImage: string; - info: string; // api interface 들어오면 확정짓기 - }; - createdAt: string; - title: string; - content: string; - comments: Comment[]; - hits: number; - images: string[]; -} - -const FeedCard = ({ - member, - createdAt, - isBlindWriter = false, - isQuestion = false, - title, - content, - comments, - hits, - images, -}: FeedCardProps) => { - return ( - - {isBlindWriter ? : } - - - {isBlindWriter ? ( - 익명 - ) : ( - - {member.name} - - ∙ - - - {member.info} - - - )} - - - {getRelativeTime(createdAt)} - - - - - - {isQuestion ? 질문 : null} - {title} - - {renderContent(content)} - {images.length > 0 ? ( - - {images.map((image, index) => ( - - ))} - - ) : null} - {comments.length > 0 ? ( - - {comments.map((comment) => ( - - {comment.name} - {comment.comment} - - ))} - - ) : null} - - {`댓글 ${comments.length}개`} - - {`조회수 ${hits}회`} - - - - ); -}; - -export default FeedCard; - -const StyeledFeedCard = styled.div` - display: flex; - gap: 8px; - background-color: ${colors.gray950}; - padding: 16px; - width: 560px; -`; - -const ProfileImage = styled.img` - flex-shrink: 0; - border-radius: 50%; - width: 32px; - height: 32px; - object-fit: cover; -`; - -const StyledStack = styled(Stack.Vertical)` - min-width: 0; -`; - -const Content = styled(Text)` - line-height: 22px; - white-space: pre-wrap; -`; - -const Title = styled(Text)` - /* stylelint-disable */ - display: -webkit-box; - overflow: hidden; - text-overflow: ellipsis; - line-height: 22px; - -webkit-box-orient: vertical; - -webkit-line-clamp: 2; -`; - -const FeedImageWrapper = styled.div` - display: flex; - gap: 8px; - overflow: auto; - white-space: nowrap; -`; - -const FeedImage = styled.img` - border-radius: 12px; - height: 160px; - object-fit: cover; -`; - -const CommentWrapper = styled.div` - display: flex; - gap: 8px; - overflow-x: auto; - white-space: nowrap; -`; - -const Comment = styled.div` - display: flex; - flex-grow: 1; - gap: 8px; - border: 0.5px solid ${colors.gray700}; - border-radius: 10px; - padding: 10px 12px; - - ${textStyles.SUIT_13_R}; -`; - -const Bottom = styled(Stack.Horizontal)` - color: ${colors.gray400}; - ${textStyles.SUIT_13_R} -`; - -const QuestionBadge = styled.div` - border-radius: 5px; - background-color: rgb(255 110 29 / 20%); - padding: 3px 5px; - color: ${colors.secondary}; - ${textStyles.SUIT_12_SB}; -`; - -const renderContent = (content: string) => { - if (content.length > 140) { - return ( - <> - {content.slice(0, 140) + '... '} - - 더보기 - - - ); - } - return content; -}; - -const IconMoreHoriz = () => ( - - - - - -); - -const IconMember = () => ( -
- - - - - -
-); From ab4adc7bc39bffa9f0f6f92f5262d16efb572709 Mon Sep 17 00:00:00 2001 From: Jun Date: Sat, 4 Nov 2023 19:35:44 +0900 Subject: [PATCH 11/14] =?UTF-8?q?refactor:=20detail=20FeedCard=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/community/page.tsx | 96 ++++- .../feed/detail/DetailFeedCard.stories.tsx | 106 +++++- src/components/feed/detail/DetailFeedCard.tsx | 330 +++++++++++++++--- 3 files changed, 481 insertions(+), 51 deletions(-) diff --git a/src/components/community/page.tsx b/src/components/community/page.tsx index 0f6e501bd..fdb46e3cc 100644 --- a/src/components/community/page.tsx +++ b/src/components/community/page.tsx @@ -1,14 +1,17 @@ import { colors } from '@sopt-makers/colors'; -import { FC } from 'react'; +import { FC, useState } from 'react'; import Responsive from '@/components/common/Responsive'; import DesktopCommunityLayout from '@/components/community/layout/DesktopCommunityLayout'; import MobileCommunityLayout from '@/components/community/layout/MobileCommunityLayout'; import { FeedDetailLink, TagLink, useFeedDetailParam, useTagParam } from '@/components/community/queryParam'; +import DetailFeedCard from '@/components/feed/detail/DetailFeedCard'; const CommunityPage: FC = () => { const [feed] = useFeedDetailParam(); const [tag] = useTagParam(); + const [value, setValue] = useState(''); + const [isBlind, setIsBlind] = useState(false); const feedList = (
@@ -31,7 +34,48 @@ const CommunityPage: FC = () => { return ( <> - + + + + + + + + + {COMMENTS.map((comment) => ( + + ))} + + + + } + /> @@ -41,3 +85,51 @@ const CommunityPage: FC = () => { }; export default CommunityPage; + +const COMMENTS = [ + { + id: 1, + name: '이정연', + image: + 'https://s3.ap-northeast-2.amazonaws.com/sopt-makers-internal//prod/image/project/f7ca1cbd-87e4-4dd3-92cf-e2420afee237-IMG_9435.JPG', + info: '28기 디자인 파트', + comment: + '라면을 끓일때 엄청난 비법이 필요하기보단 정량의 물을 넣고, 레시피에 주어진 시간만큼 끓이는 것이 중요하듯 기본적인 조언이지만 스타트업 경영과 성장 역시도 필수적인 것을 지키지 않았을 때 크리티컬한 만큼', + }, + { + id: 1, + name: '이정연', + image: + 'https://s3.ap-northeast-2.amazonaws.com/sopt-makers-internal//prod/image/project/f7ca1cbd-87e4-4dd3-92cf-e2420afee237-IMG_9435.JPG', + info: '28기 디자인 파트', + comment: + '라면을 끓일때 엄청난 비법이 필요하기보단 정량의 물을 넣고, 레시피에 주어진 시간만큼 끓이는 것이 중요하듯 기본적인 조언이지만 스타트업 경영과 성장 역시도 필수적인 것을 지키지 않았을 때 크리티컬한 만큼', + }, + { + id: 1, + name: '이정연', + image: + 'https://s3.ap-northeast-2.amazonaws.com/sopt-makers-internal//prod/image/project/f7ca1cbd-87e4-4dd3-92cf-e2420afee237-IMG_9435.JPG', + info: '28기 디자인 파트', + comment: + '라면을 끓일때 엄청난 비법이 필요하기보단 정량의 물을 넣고, 레시피에 주어진 시간만큼 끓이는 것이 중요하듯 기본적인 조언이지만 스타트업 경영과 성장 역시도 필수적인 것을 지키지 않았을 때 크리티컬한 만큼', + }, + { + id: 1, + name: '이정연', + image: + 'https://s3.ap-northeast-2.amazonaws.com/sopt-makers-internal//prod/image/project/f7ca1cbd-87e4-4dd3-92cf-e2420afee237-IMG_9435.JPG', + info: '28기 디자인 파트', + comment: + '라면을 끓일때 엄청난 비법이 필요하기보단 정량의 물을 넣고, 레시피에 주어진 시간만큼 끓이는 것이 중요하듯 기본적인 조언이지만 스타트업 경영과 성장 역시도 필수적인 것을 지키지 않았을 때 크리티컬한 만큼', + }, + { + id: 1, + name: '이정연', + image: + 'https://s3.ap-northeast-2.amazonaws.com/sopt-makers-internal//prod/image/project/f7ca1cbd-87e4-4dd3-92cf-e2420afee237-IMG_9435.JPG', + info: '28기 디자인 파트', + comment: + '라면을 끓일때 엄청난 비법이 필요하기보단 정량의 물을 넣고, 레시피에 주어진 시간만큼 끓이는 것이 중요하듯 기본적인 조언이지만 스타트업 경영과 성장 역시도 필수적인 것을 지키지 않았을 때 크리티컬한 만큼', + }, +]; diff --git a/src/components/feed/detail/DetailFeedCard.stories.tsx b/src/components/feed/detail/DetailFeedCard.stories.tsx index 979c28cc4..68a87d4f7 100644 --- a/src/components/feed/detail/DetailFeedCard.stories.tsx +++ b/src/components/feed/detail/DetailFeedCard.stories.tsx @@ -1,17 +1,107 @@ -import { Meta, StoryObj } from '@storybook/react'; +import { Meta } from '@storybook/react'; +import { useState } from 'react'; import DetailFeedCard from './DetailFeedCard'; const meta = { component: DetailFeedCard, + decorators: [ + (Story) => ( +
+ +
+ ), + ], } satisfies Meta; export default meta; -type Story = StoryObj; - -export const Default = { - args: { - category: '파트', - tag: '기획', +const COMMENTS = [ + { + id: 1, + name: '이정연', + image: + 'https://s3.ap-northeast-2.amazonaws.com/sopt-makers-internal//prod/image/project/f7ca1cbd-87e4-4dd3-92cf-e2420afee237-IMG_9435.JPG', + info: '28기 디자인 파트', + comment: + '라면을 끓일때 엄청난 비법이 필요하기보단 정량의 물을 넣고, 레시피에 주어진 시간만큼 끓이는 것이 중요하듯 기본적인 조언이지만 스타트업 경영과 성장 역시도 필수적인 것을 지키지 않았을 때 크리티컬한 만큼', + }, + { + id: 2, + name: '이정연', + image: + 'https://s3.ap-northeast-2.amazonaws.com/sopt-makers-internal//prod/image/project/f7ca1cbd-87e4-4dd3-92cf-e2420afee237-IMG_9435.JPG', + info: '28기 디자인 파트', + comment: + '라면을 끓일때 엄청난 비법이 필요하기보단 정량의 물을 넣고, 레시피에 주어진 시간만큼 끓이는 것이 중요하듯 기본적인 조언이지만 스타트업 경영과 성장 역시도 필수적인 것을 지키지 않았을 때 크리티컬한 만큼', + }, + { + id: 3, + name: '이정연', + image: + 'https://s3.ap-northeast-2.amazonaws.com/sopt-makers-internal//prod/image/project/f7ca1cbd-87e4-4dd3-92cf-e2420afee237-IMG_9435.JPG', + info: '28기 디자인 파트', + comment: + '라면을 끓일때 엄청난 비법이 필요하기보단 정량의 물을 넣고, 레시피에 주어진 시간만큼 끓이는 것이 중요하듯 기본적인 조언이지만 스타트업 경영과 성장 역시도 필수적인 것을 지키지 않았을 때 크리티컬한 만큼', + }, + { + id: 4, + name: '이정연', + image: + 'https://s3.ap-northeast-2.amazonaws.com/sopt-makers-internal//prod/image/project/f7ca1cbd-87e4-4dd3-92cf-e2420afee237-IMG_9435.JPG', + info: '28기 디자인 파트', + comment: + '라면을 끓일때 엄청난 비법이 필요하기보단 정량의 물을 넣고, 레시피에 주어진 시간만큼 끓이는 것이 중요하듯 기본적인 조언이지만 스타트업 경영과 성장 역시도 필수적인 것을 지키지 않았을 때 크리티컬한 만큼', }, -} satisfies Story; + { + id: 5, + name: '이정연', + image: + 'https://s3.ap-northeast-2.amazonaws.com/sopt-makers-internal//prod/image/project/f7ca1cbd-87e4-4dd3-92cf-e2420afee237-IMG_9435.JPG', + info: '28기 디자인 파트', + comment: + '라면을 끓일때 엄청난 비법이 필요하기보단 정량의 물을 넣고, 레시피에 주어진 시간만큼 끓이는 것이 중요하듯 기본적인 조언이지만 스타트업 경영과 성장 역시도 필수적인 것을 지키지 않았을 때 크리티컬한 만큼', + }, +]; + +export const Default = () => { + const [value, setValue] = useState(''); + const [isBlind, setIsBlind] = useState(false); + + return ( + + + + + + + + + {COMMENTS.map((comment) => ( + + ))} + + + + ); +}; diff --git a/src/components/feed/detail/DetailFeedCard.tsx b/src/components/feed/detail/DetailFeedCard.tsx index e3ab1ddd6..f033b9595 100644 --- a/src/components/feed/detail/DetailFeedCard.tsx +++ b/src/components/feed/detail/DetailFeedCard.tsx @@ -1,63 +1,311 @@ import styled from '@emotion/styled'; import { colors } from '@sopt-makers/colors'; -import { Flex } from '@toss/emotion-utils'; -import React from 'react'; +import { Flex, Stack } from '@toss/emotion-utils'; +import { m } from 'framer-motion'; +import { PropsWithChildren, useEffect, useRef, useState } from 'react'; -interface DetailFeedCardProps { +import Checkbox from '@/components/common/Checkbox'; +import Text from '@/components/common/Text'; +import { FeedDetailLink } from '@/components/community/queryParam'; +import { + IconChevronLeft, + IconChevronRight, + IconMember, + IconMoreVert, + IconSendFill, + IconShare, +} from '@/components/feed/Icon'; +import { getRelativeTime } from '@/components/feed/utils'; +import { textStyles } from '@/styles/typography'; + +const Base = ({ children }: PropsWithChildren) => { + return {children}; +}; + +const StyledBase = styled(Flex)` + border-right: 1px solid ${colors.gray800}; + border-left: 1px solid ${colors.gray800}; + height: 100%; +`; + +interface HeaderProps { category: string; tag: string; } -const DetailFeedCard = ({ category, tag }: DetailFeedCardProps) => { +const Header = ({ category, tag }: HeaderProps) => { return ( - <> -
- + + + - - {category} - - {tag} - + + + {category} + + {tag} + + + + + + + + ); +}; + +const Chip = styled(Flex)` + border-radius: 21px; + background-color: ${colors.gray800}; + padding: 7px 12px; + color: ${colors.gray10}; +`; + +const Body = ({ children }: PropsWithChildren) => { + return ( + + + {children} + + + ); +}; + +const StyledBody = styled(Flex)` + position: relative; + flex: 1; + padding: 16px 24px; + overflow: auto; +`; + +const Main = ({ children }: PropsWithChildren) => { + return ( + + {children} + + ); +}; + +interface TopProps { + isBlindWriter?: boolean; + profileImage: string; + name: string; + info: string; + createdAt: string; +} + +const Top = ({ isBlindWriter = false, profileImage, name, info, createdAt }: TopProps) => { + return ( + + + {isBlindWriter ? : } + + + {isBlindWriter ? '익명' : name} + + {!isBlindWriter ? ( + + {info} + + ) : null} + + + + {getRelativeTime(createdAt)} + + + ); +}; + +const ProfileImage = styled.img` + flex-shrink: 0; + border-radius: 50%; + width: 40px; + height: 40px; + object-fit: cover; +`; + +interface ContentProps { + isQuestion?: boolean; + title: string; + content: string; + hits: number; + commentLength: number; +} + +const Content = ({ isQuestion = false, title, content, hits, commentLength }: ContentProps) => { + return ( + <> + + + {isQuestion ? 질문 : null} + {title} -
+ {content} + + {`댓글 ${commentLength}개 ∙ ${hits}명 읽음`} ); }; -export default DetailFeedCard; +const QuestionBadge = styled.div` + border-radius: 5px; + background-color: ${colors.orangeAlpha200}; + padding: 6px; + color: ${colors.secondary}; +`; -const Header = styled.header` - padding: 0 24px; +const Divider = styled.hr` + margin: 0; + border: none; + background-color: ${colors.gray800}; + height: 1px; `; -const IconChevronLeft = () => ( - - - -); +interface CommentProps { + image: string; + name: string; + info: string; + comment: string; +} +const Comment = ({ image, name, info, comment }: CommentProps) => { + return ( + + + + + + + {name}∙ + + + {info} + + + + {comment} + + + + ); +}; -const Chip = styled.div` +const StyledComment = styled.div` + padding: 20px 24px 12px; +`; + +interface InputProps { + value: string; + onChange: (value: string) => void; + isBlindChecked: boolean; + onChangeIsBlindChecked: (checked: boolean) => void; +} + +const Input = ({ value, onChange, isBlindChecked, onChangeIsBlindChecked }: InputProps) => { + const [isFocus, setIsFocus] = useState(false); + const containerRef = useRef(null); + + const is버튼액티브 = isFocus && value.length > 0; + + useEffect(() => { + const handleOutsideClick = (event: MouseEvent) => { + if (containerRef.current && !containerRef.current.contains(event.target as Node)) { + setIsFocus(false); + } + }; + document.addEventListener('click', handleOutsideClick); + return () => { + document.removeEventListener('click', handleOutsideClick); + }; + }, []); + + return ( + + + + onChangeIsBlindChecked(e.target.checked)} /> + 익명으로 남기기 + + + + onChange(e.target.value)} + onFocus={() => setIsFocus(true)} + placeholder='댓글을 남겨주세요.' + /> + + + + + + ); +}; + +const Container = styled.div` + border-top: 1px solid ${colors.gray800}; + border-bottom: 1px solid ${colors.gray800}; + border-radius: 10px; + background-color: ${colors.gray950}; + padding: 16px; +`; + +const InputAnimateArea = styled(m.div)` + position: relative; + overflow: hidden; +`; + +const InputContent = styled.div` display: flex; + position: absolute; + top: 0; + right: 0; + left: 0; + gap: 4px; align-items: center; - border-radius: 21px; - background-color: ${colors.gray400}; - padding: 7px 12px; - color: ${colors.gray10}; `; -const IconChevronRight = () => ( - - - -); +const StyledInput = styled.input` + flex: 1; + border: none; + ${textStyles.SUIT_16_M}; + + :focus { + outline: none; + } + + ::placeholder { + color: ${colors.gray500}; + } +`; + +const SendButton = styled(m.button)` + border-radius: 12px; + padding: 8px; +`; + +export default Object.assign(Base, { + Header, + Body, + Main, + Top, + Content, + Divider, + Comment, + Input, +}); From 8415ff0253651300066dfeeba473359a0cb3c5ac Mon Sep 17 00:00:00 2001 From: Jun Date: Sat, 4 Nov 2023 20:11:22 +0900 Subject: [PATCH 12/14] =?UTF-8?q?feat:=20=EC=98=88=EC=8B=9C=20=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=84=B0=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/community/list/FeedList.tsx | 147 ++++++++++++++++-- .../community/page/FeedHomePage.tsx | 1 - src/components/feed/main/FeedCard.tsx | 11 +- 3 files changed, 147 insertions(+), 12 deletions(-) diff --git a/src/components/community/list/FeedList.tsx b/src/components/community/list/FeedList.tsx index ca618d5f8..8aacaebc2 100644 --- a/src/components/community/list/FeedList.tsx +++ b/src/components/community/list/FeedList.tsx @@ -1,9 +1,10 @@ import styled from '@emotion/styled'; -import { FC } from 'react'; +import { colors } from '@sopt-makers/colors'; +import { ComponentProps, FC } from 'react'; import CategorySelect from '@/components/community/list/CategorySelect'; import { FeedDetailLink } from '@/components/community/queryParam'; -import { colors } from '@sopt-makers/colors'; +import FeedCard from '@/components/feed/main/FeedCard'; interface FeedListProps {} @@ -36,13 +37,74 @@ const FeedList: FC = ({}) => { ]} /> -
- {[...new Array(100)].map((_, idx) => ( -
- Feed {idx} -
- ))} -
+ {/* TODO: api 나오면 변경 */} + + + + {COMMENTS.map((comment) => ( + + ))} + + + + + {COMMENTS.map((comment) => ( + + ))} + + + + + {QURESTION_COMMENTS.map((comment) => ( + + ))} + + + + + {[ + { + id: 1, + name: 'Evan Kim', + comment: 'LGTM!', + }, + ].map((comment) => ( + + ))} + + + + + {IMAGES.map((image) => ( + + ))} + + + {IMAGES_COMMENTS.map((comment) => ( + + ))} + + + + + {COMMENTS.map((comment) => ( + + ))} + + + ); @@ -69,3 +131,70 @@ const CategoryArea = styled.div` top: 0; background-color: ${colors.background}; `; + +// dummy +const COMMENTS = [ + { + id: 1, + name: '주수한', + comment: '저도 같이 가고 싶어요!', + }, + { + id: 2, + name: '이정연', + comment: '도요 도요 저도요!', + }, + { + id: 3, + name: '남부장', + comment: '어허, 나는 왜 빼려고 하는가 허허', + }, +]; + +const defaultProps: ComponentProps = { + name: '이준호', + info: 'Frontend Developer @Toss', + profileImage: + 'https://s3.ap-northeast-2.amazonaws.com/sopt-makers-internal//prod/image/project/f7ca1cbd-87e4-4dd3-92cf-e2420afee237-IMG_9435.JPG', + createdAt: '2023.10.23.11:00:11', + title: '다음주 일요일에 클라이밍하러가실 분', + content: + '처음해봐도 괜찮으니까 편하게 오세요! 장소는 클라이밍파크 신논현점 생각하고 있어요. 2시쯤 만나서 하고, 끝나고 같이 고기 먹어요~', + hits: 23, + commentLength: COMMENTS.length, +}; + +const 당근_배경 = + 'https://github.com/sopt-makers/sopt-playground-frontend/assets/26808056/ab298948-935c-41e9-801f-420ba4482a10'; +const 노트북_당근 = + 'https://github.com/sopt-makers/sopt-playground-frontend/assets/26808056/33e150c1-e8f9-4d02-bf32-1fe1f1e7cc8b'; +const 폰배경_당근 = + 'https://github.com/sopt-makers/sopt-playground-frontend/assets/26808056/0b168abb-8f7f-4562-874a-b179a1ddd012'; + +const IMAGES = [당근_배경, 노트북_당근, 폰배경_당근]; +const IMAGES_COMMENTS = [ + { + id: 1, + name: '이화정', + comment: '너무 귀여워요!! 당근 너무 좋아요', + }, + { + id: 2, + name: '민솔', + comment: '배고파요...', + }, +]; + +const QURESTION_COMMENTS = [ + { + id: 1, + name: '박건영', + comment: + 'flexbox의 자식 컴포넌트의 기본 min-width값은 min-width: auto 에요. 따라서 컨테이너의 width를 넘어가지 않을 것으로 예상되지만, flex-basis: auto보다 min-width: auto가 먼저 적용되어 자식 컴포넌트가 옆으로 길어질 경우 컨테이너의 너비를 초과하게 돼요. 이를 해결하려면 min-width: 0 을 주어야 해요!', + }, + { + id: 2, + name: '남부장', + comment: '커사원 잘했네.', + }, +]; diff --git a/src/components/community/page/FeedHomePage.tsx b/src/components/community/page/FeedHomePage.tsx index 2200a4e9a..add69380b 100644 --- a/src/components/community/page/FeedHomePage.tsx +++ b/src/components/community/page/FeedHomePage.tsx @@ -69,7 +69,6 @@ const CommunityPage: FC = () => { } /> -
diff --git a/src/components/feed/main/FeedCard.tsx b/src/components/feed/main/FeedCard.tsx index 16a9eee2e..5bbf617d4 100644 --- a/src/components/feed/main/FeedCard.tsx +++ b/src/components/feed/main/FeedCard.tsx @@ -32,6 +32,7 @@ const Base = ({ isQuestion = false, commentLength, hits, + children, }: PropsWithChildren) => { return ( @@ -42,8 +43,14 @@ const Base = ({ gap: 8, }} > - {isBlindWriter ? : } - + {isBlindWriter ? ( +
+ +
+ ) : ( + + )} + {isBlindWriter ? ( 익명 From 8dc276ecb04972cc70019bb3356f1ba37a86b17e Mon Sep 17 00:00:00 2001 From: Jun Date: Sat, 4 Nov 2023 20:14:19 +0900 Subject: [PATCH 13/14] =?UTF-8?q?chore:=20DetailFeedCard=20Chip=20?= =?UTF-8?q?=ED=98=B8=EB=B2=84=EC=8A=A4=ED=83=80=EC=9D=BC=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/feed/detail/DetailFeedCard.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/feed/detail/DetailFeedCard.tsx b/src/components/feed/detail/DetailFeedCard.tsx index f033b9595..5b931098b 100644 --- a/src/components/feed/detail/DetailFeedCard.tsx +++ b/src/components/feed/detail/DetailFeedCard.tsx @@ -40,7 +40,7 @@ const Header = ({ category, tag }: HeaderProps) => { - + {category} {tag} @@ -59,10 +59,15 @@ const Header = ({ category, tag }: HeaderProps) => { }; const Chip = styled(Flex)` + transition: background-color 0.2s ease-in-out; border-radius: 21px; background-color: ${colors.gray800}; padding: 7px 12px; color: ${colors.gray10}; + + :hover { + background-color: ${colors.gray700}; + } `; const Body = ({ children }: PropsWithChildren) => { From dd54b605818f941264a66e1fd7eda57fb43d5fce Mon Sep 17 00:00:00 2001 From: Jun Date: Sun, 5 Nov 2023 02:56:15 +0900 Subject: [PATCH 14/14] =?UTF-8?q?feat:=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../community/page/FeedHomePage.tsx | 8 ++++++++ .../feed/detail/DetailFeedCard.stories.tsx | 6 ++++++ src/components/feed/detail/DetailFeedCard.tsx | 19 +++++++++++++++++-- src/components/feed/main/FeedCard.tsx | 1 - 4 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/components/community/page/FeedHomePage.tsx b/src/components/community/page/FeedHomePage.tsx index add69380b..b432e47ce 100644 --- a/src/components/community/page/FeedHomePage.tsx +++ b/src/components/community/page/FeedHomePage.tsx @@ -47,6 +47,7 @@ const CommunityPage: FC = () => { hits={23} commentLength={COMMENTS.length} content='라면을 끓일때 엄청난 비법이 필요하기보단 정량의 물을 넣고, 레시피에 주어진 시간만큼 끓이는 것이 중요하듯 기본적인 조언이지만 스타트업 경영과 성장 역시도 필수적인 것을 지키지 않았을 때 크리티컬한 만큼' + images={IMAGES} /> @@ -126,3 +127,10 @@ const COMMENTS = [ '라면을 끓일때 엄청난 비법이 필요하기보단 정량의 물을 넣고, 레시피에 주어진 시간만큼 끓이는 것이 중요하듯 기본적인 조언이지만 스타트업 경영과 성장 역시도 필수적인 것을 지키지 않았을 때 크리티컬한 만큼', }, ]; + +const 당근데탑큰것 = + 'https://github.com/sopt-makers/sopt-playground-frontend/assets/26808056/37aeca19-f53c-4a96-a42a-a6bfe1cc5f5b'; +const 당근데탑작은것 = + 'https://github.com/sopt-makers/sopt-playground-frontend/assets/26808056/45efc303-1449-4cc3-b4a0-b475af17ae57'; + +const IMAGES = [당근데탑큰것, 당근데탑작은것, 당근데탑큰것]; diff --git a/src/components/feed/detail/DetailFeedCard.stories.tsx b/src/components/feed/detail/DetailFeedCard.stories.tsx index 68a87d4f7..9a32f5ad5 100644 --- a/src/components/feed/detail/DetailFeedCard.stories.tsx +++ b/src/components/feed/detail/DetailFeedCard.stories.tsx @@ -63,6 +63,11 @@ const COMMENTS = [ }, ]; +const 당근데탑큰것 = + 'https://github.com/sopt-makers/sopt-playground-frontend/assets/26808056/37aeca19-f53c-4a96-a42a-a6bfe1cc5f5b'; +const 당근데탑작은것 = + 'https://github.com/sopt-makers/sopt-playground-frontend/assets/26808056/45efc303-1449-4cc3-b4a0-b475af17ae57'; + export const Default = () => { const [value, setValue] = useState(''); const [isBlind, setIsBlind] = useState(false); @@ -83,6 +88,7 @@ export const Default = () => { hits={23} commentLength={COMMENTS.length} content='라면을 끓일때 엄청난 비법이 필요하기보단 정량의 물을 넣고, 레시피에 주어진 시간만큼 끓이는 것이 중요하듯 기본적인 조언이지만 스타트업 경영과 성장 역시도 필수적인 것을 지키지 않았을 때 크리티컬한 만큼' + images={[당근데탑큰것, 당근데탑작은것, 당근데탑큰것]} /> diff --git a/src/components/feed/detail/DetailFeedCard.tsx b/src/components/feed/detail/DetailFeedCard.tsx index 5b931098b..f30bf2410 100644 --- a/src/components/feed/detail/DetailFeedCard.tsx +++ b/src/components/feed/detail/DetailFeedCard.tsx @@ -140,9 +140,10 @@ interface ContentProps { content: string; hits: number; commentLength: number; + images: string[]; } -const Content = ({ isQuestion = false, title, content, hits, commentLength }: ContentProps) => { +const Content = ({ isQuestion = false, title, content, hits, commentLength, images }: ContentProps) => { return ( <> @@ -152,6 +153,13 @@ const Content = ({ isQuestion = false, title, content, hits, commentLength }: Co {content} + {images.length !== 0 ? ( + + {images.map((image, index) => ( + + ))} + + ) : null} {`댓글 ${commentLength}개 ∙ ${hits}명 읽음`} ); @@ -164,7 +172,15 @@ const QuestionBadge = styled.div` color: ${colors.secondary}; `; +const ImageItem = styled.img` + flex: 0; + border-radius: 12px; + height: 240px; + object-fit: cover; +`; + const Divider = styled.hr` + flex-shrink: 0; margin: 0; border: none; background-color: ${colors.gray800}; @@ -191,7 +207,6 @@ const Comment = ({ image, name, info, comment }: CommentProps) => { {info} - {comment}
diff --git a/src/components/feed/main/FeedCard.tsx b/src/components/feed/main/FeedCard.tsx index 5bbf617d4..92236d9d7 100644 --- a/src/components/feed/main/FeedCard.tsx +++ b/src/components/feed/main/FeedCard.tsx @@ -32,7 +32,6 @@ const Base = ({ isQuestion = false, commentLength, hits, - children, }: PropsWithChildren) => { return (