diff --git a/components/Comment/AuthorComment.tsx b/components/Comment/AuthorComment.tsx new file mode 100644 index 0000000..d9766fb --- /dev/null +++ b/components/Comment/AuthorComment.tsx @@ -0,0 +1,74 @@ +import React from 'react'; +import { FeedAuthor } from 'types/index'; +import styled from '@emotion/styled'; +import UserAvatar from 'components/UserAvatar'; +import { getTimeDiff } from 'utils/api/time'; + +export type AuthorCommentProps = { + content: string; + author: FeedAuthor; + created: string; +}; + +function AuthorComment({ content, author: { displayId, profileImageUrl }, created }: AuthorCommentProps) { + return ( + + {/* Todo: UserAvatar에 링크달기 */} + + + +
+
+ + {/* Todo: displayId에 링크 달기 */} + {displayId} + + {content} +
+ {getTimeDiff(created)} +
+
+ ); +} + +const StyledCreatedItem = styled.div` + display: inline-block; + justify-content: space-between; + color: rgb(142, 142, 142); + margin-right: 12px; + font-size: 12px; + font-weight: 400; + text-align: left; + border: none; + background-color: transparent; +`; + +const StyledAnchor = styled.a` + text-decoration: none; + color: inherit; +`; + +const VerticalMiddleDiv = styled.div` + border-bottom: 1px solid rgb(239, 239, 239); + margin-bottom: 16px; + padding-bottom: 16px; + display: flex; + align-items: center; +`; + +const StyledSpan = styled.span` + font-size: 14px; +`; + +const InlineH3 = styled.h3` + display: inline-block; + font-size: 18px; + margin-right: 10px; +`; + +const ThumbnailDiv = styled.div` + display: inline-block; + margin: 0 16px; +`; + +export default AuthorComment; diff --git a/components/Comment/Body.tsx b/components/Comment/Body.tsx new file mode 100644 index 0000000..73f87ab --- /dev/null +++ b/components/Comment/Body.tsx @@ -0,0 +1,91 @@ +import React, { useCallback, useState } from 'react'; +import CreatedBottom from 'components/Comment/CommentBottom'; + +import { Member } from 'types/index'; +import styled from '@emotion/styled'; +import Icon from 'components/Icon'; +import UserAvatar from 'components/UserAvatar'; + +export type BodyProps = { + feedAuthorDisplayId: string; + id: number; + content: string; + author: Member; + isLike: Boolean; + likeLength: number; + created: string; +}; + +function Body({ feedAuthorDisplayId, id, content, author:{displayId, profileImageUrl}, isLike, likeLength, created }: BodyProps) { + const [like, setLike] = useState(isLike); + // Todo: api 구현, comment id 필요 + const toggleLike = useCallback(() => { + setLike((prev) => !prev); + }, []); + // Todo: 조건별 모달 구현, feedAuthor displayId 필요 + return ( + + + + + + + + {/* Todo: displayId link 걸기 */} + {displayId} + + {content} + + + + + + + + ); +} + +const InlineDiv = styled.div` + display: inline-block; +`; + +const StyledDiv = styled.div` + display: flex; +`; + +const StyledAnchor = styled.a` + text-decoration: none; + color: inherit; +`; + +const ThumbnailDiv = styled.div` + display: inline-block; + vertical-align: middle; + margin: 0 16px; +`; + +const VerticalMiddleDiv = styled.div` + display: flex; + flex-grow: 1; + align-items: center; +`; + +const StyledSpan = styled.span` + display: inline-block; + font-size: 14px; +`; + +const InlineH3 = styled.h3` + display: inline-block; + font-size: 18px; + margin-right: 10px; +`; + +const StyledButton = styled.button` + display: flex; + align-items: center; + background-color: transparent; + border: none; +`; + +export default Body; diff --git a/components/Comment/CommentArea.tsx b/components/Comment/CommentArea.tsx new file mode 100644 index 0000000..4a06b9d --- /dev/null +++ b/components/Comment/CommentArea.tsx @@ -0,0 +1,73 @@ +import { Member } from 'types/index'; +import styled from '@emotion/styled'; +import React from 'react'; +import UserAvatar from 'components/UserAvatar'; + +export type CommentAreaProps = { + author: Member; +}; + +function CommentArea({ author: { profileImageUrl } }: CommentAreaProps) { + // Todo : onKeyPressed - enter, onClick 함수구현 + return ( + + + + + + + 게시 + + + ); +} + +const InlineDiv = styled.div` + display: inline-block; + margin: 0 16px; +`; + +const StyledTextArea = styled.textarea` + display: flex; + resize: none; + flex-grow: 1; + background-color: transparent; + padding: 0; + height: 18px; + border: 0; + color: rgb(142, 142, 142); +`; + +const StyledForm = styled.form` + display: flex; + flex-grow: 1; + align-items: center; + border-radius: 30px; + margin-right: 16px; + padding: 12px 16px; + font-size: 14px; + font-weight: 400; + background-color: rgb(255, 255, 255); + border: 1px solid rgb(219, 219, 219); +`; + +const StyledCommentArea = styled.div` + display: flex; + align-items: center; + background-color: rgb(239, 239, 239); + border-top: 1px solid rgb(219, 219, 219); + border-bottom: 1px solid rgb(219, 219, 219); + padding: 8px 0; +`; + +const StyledSubmitButton = styled.button` + display: flex; + justify-content: flex-end; + height: 18px; + background-color: transparent; + border: 0; + color: rgba(0, 149, 246, 0.3); + font-size: 14px; +`; + +export default CommentArea; diff --git a/components/Comment/CommentBottom.tsx b/components/Comment/CommentBottom.tsx new file mode 100644 index 0000000..5812c87 --- /dev/null +++ b/components/Comment/CommentBottom.tsx @@ -0,0 +1,60 @@ +import styled from '@emotion/styled'; +import { getTimeDiff } from 'utils/api/time'; +import React from 'react'; + +export type CommentBottomProps = { + likeLength: number; + created: string; +}; + +function CommentBottom({ likeLength, created }: CommentBottomProps) { + return ( + + {getTimeDiff(created)} + 좋아요 {likeLength}개 + 답글 달기 + + ); +} + +const StyledCreatedItem = styled.div` + display: inline-block; + justify-content: space-between; + color: rgb(142, 142, 142); + margin-right: 12px; + font-size: 12px; + font-weight: 400; + text-align: left; + border: none; + background-color: transparent; +`; + +const StyledDiv = styled.div` + display: flex; +`; + +const StyledLikeItem = styled.button` + color: rgb(142, 142, 142); + font-size: 12px; + font-weight: 600; + text-align: center; + border: none; + background-color: transparent; + align-items: flex-start; + box-sizing: border-box; + width: auto; + -webkit-text-size-adjust: 100%; +`; + +const StyledReplyItem = styled.button` + color: rgb(142, 142, 142); + font-size: 12px; + font-weight: 600; + text-align: center; + border: none; + background-color: transparent; + align-items: flex-start; + box-sizing: border-box; +`; + +export default CommentBottom; diff --git a/components/Comment/CommentItem.tsx b/components/Comment/CommentItem.tsx new file mode 100644 index 0000000..f4709fe --- /dev/null +++ b/components/Comment/CommentItem.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import { Comment } from 'types/index'; +import Body from 'components/Comment/Body'; +import ReplyComponent from 'components/Comment/ReplyComponent'; +import styled from '@emotion/styled'; + +export type CommentItemProps = { + comment: Comment; +}; + +function CommentItem({ comment }: CommentItemProps) { + const { replyLength, id, feedAuthorDisplayId, likeLength, isLike, created, content, author } = comment; + return ( + + + {replyLength > 0 && } + + ); +} + +const Styledli = styled.li` + list-style: none; +`; + +export default CommentItem; diff --git a/components/Comment/CommentNav.tsx b/components/Comment/CommentNav.tsx new file mode 100644 index 0000000..283cb09 --- /dev/null +++ b/components/Comment/CommentNav.tsx @@ -0,0 +1,52 @@ +import styled from '@emotion/styled'; +import React from 'react'; +import Icon from 'components/Icon'; + +function CommentNav() { + // Todo: 뒤로가기 구현 + // Todo: DM 으로 이동 구현 + return ( + + + + + + + + 댓글 + + + + + + + + ); +} + +const StyledIcon = styled.button` + transform: rotate(-90deg); +`; + +const StyledH1 = styled.h1` + margin-block-start: 0; + margin-block-end: 0; + font-size: 16px; +`; + +const StyledButton = styled.button` + background-color: transparent; + border: 0; +`; + +const StyledDiv = styled.div` + position: relative; +`; + +const StyledNav = styled.div` + display: flex; + justify-content: space-between; + flex: 0 0 60px; +`; + +export default CommentNav; diff --git a/components/Comment/ReplyComponent.tsx b/components/Comment/ReplyComponent.tsx new file mode 100644 index 0000000..6e315b0 --- /dev/null +++ b/components/Comment/ReplyComponent.tsx @@ -0,0 +1,67 @@ +import styled from '@emotion/styled'; +import React, { useCallback, useState } from 'react'; +import { Reply } from 'types/index'; + +export type CommentItemProps = { + replySize: number; + commentId: number; +}; + +function ReplyComponent({ replySize, commentId }: CommentItemProps) { + const [show, setShow] = useState(false); + const [replies, setReplies] = useState([]); + const [replyLength, setReplyLength] = useState(replySize); + // Todo : api 구현 + const getReplyList = () => {}; + const openReply = useCallback(() => { + setShow(true); + getReplyList(); + }, []); + const closeReply = useCallback(() => { + setShow(false); + }, []); + + const showReply = (show && replyLength > 0) || !show; + return ( + + ); +} + +const StyledReply = styled.button` + margin-top: 16px; + margin-left: 28px; + border: none; + background-color: transparent; +`; + +const StyledLine = styled.div` + border-bottom: 1px solid rgb(142, 142, 142); + display: inline-block; + height: 0; + margin-right: 16px; + vertical-align: middle; + width: 24px; +`; + +const StyledCommentReply = styled.span` + color: rgb(142, 142, 142); + font-size: 12px; + font-weight: 600; + text-align: center; +`; + +export default ReplyComponent; diff --git a/components/Comment/ReplyItem.tsx b/components/Comment/ReplyItem.tsx new file mode 100644 index 0000000..64c87d6 --- /dev/null +++ b/components/Comment/ReplyItem.tsx @@ -0,0 +1,32 @@ +import React from 'react'; +import { Reply } from 'types/index'; +import styled from '@emotion/styled'; +import Body from 'components/Comment/Body'; + +export type ReplyItemProps = { + reply: Reply; +}; + +function ReplyItem({ + reply: { feedAuthorDisplayId, author, content, created, id, isLike, likeLength }, +}: ReplyItemProps) { + return ( + + + + ); +} + +const Styledli = styled.li` + list-style: none; +`; + +export default ReplyItem; diff --git a/components/Comment/comment.stories.tsx b/components/Comment/comment.stories.tsx new file mode 100644 index 0000000..1b299b4 --- /dev/null +++ b/components/Comment/comment.stories.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import { Story, Meta } from '@storybook/react/types-6-0'; + +import { FeedAuthor, Member, Comment } from 'types'; +import AuthorComment from 'components/Comment/AuthorComment'; +import CommentArea from 'components/Comment/CommentArea'; +import CommentNav from 'components/Comment/CommentNav'; +import CommentItem from './CommentItem'; +import CommentComponent , { CommentsProps } from '.'; + +export default { + title: 'Comment', + component: CommentComponent, +} as Meta; + +const user1: Member = { + displayId: 'hangneu', + nickname: '김하은', + profileImageUrl: + 'https://post-phinf.pstatic.net/MjAxODA3MTlfMTIg/MDAxNTMxOTg5ODE5OTAw.edb-H-Rmhr2dFvKAqKA11flZ2k45cRi4Q4IaHirlMF4g.It6ziXN3vtf0R7B2p9DdwOy1hovG7aynuCPwAysStMcg.JPEG/jy180719b2.jpg?type=w1200', +}; + +const author: FeedAuthor = { + displayId: 'kdongha', + isFollowedByMe: true, + profileImageUrl: + 'https://post-phinf.pstatic.net/MjAxODA3MTlfMTIg/MDAxNTMxOTg5ODE5OTAw.edb-H-Rmhr2dFvKAqKA11flZ2k45cRi4Q4IaHirlMF4g.It6ziXN3vtf0R7B2p9DdwOy1hovG7aynuCPwAysStMcg.JPEG/jy180719b2.jpg?type=w1200', +}; + +const date = '2020-03-26 20:30:11'; +const comment: Comment = { + feedAuthorDisplayId: author.displayId, + id: 1, + author: user1, + created: date, + isLike: true, + replyLength: 1, + content: '킹동하 팬이에요!', + likeLength: 3, +}; + +export const Template: Story = () => ( + <> + + + +
    + +
+ +); diff --git a/components/Comment/index.tsx b/components/Comment/index.tsx new file mode 100644 index 0000000..d5247d8 --- /dev/null +++ b/components/Comment/index.tsx @@ -0,0 +1,34 @@ +import React, { useState } from 'react'; +import Icon from 'components/Icon'; +import { Comment, Feed } from 'types/index'; +import CommentNav from 'components/Comment/CommentNav'; +import AuthorComment from 'components/Comment/AuthorComment'; + +export type CommentsProps = { + feed: Feed; +}; + +function CommentComponent({ feed }: CommentsProps) { + const { author, id, createdAt, body, commentLength: defaultCommentLength } = feed; + const [comments, setComments] = useState([]); + const [commentLength, setCommentLength] = useState(defaultCommentLength); + // Todo: api 구현 map [] + // Todo: context Api로 me 변수 만들기 + const getCommentList = () => {}; + const moreComment = commentLength > 0; + return ( + <> + + {/* CommentArea author = {me} */} + +
    {comments}
+ {moreComment && ( // 남은 댓글 있음 + + )} + + ); +} + +export default CommentComponent; diff --git a/components/Icon/svg/index.ts b/components/Icon/svg/index.ts index f118bc8..c183550 100644 --- a/components/Icon/svg/index.ts +++ b/components/Icon/svg/index.ts @@ -8,3 +8,4 @@ export { ReactComponent as share } from './share.svg'; export { ReactComponent as bookmark } from './bookmark.svg'; export { ReactComponent as newpost } from './newpost.svg'; export { ReactComponent as back } from './back.svg'; +export { ReactComponent as plus } from './plus.svg'; diff --git a/components/Icon/svg/plus.svg b/components/Icon/svg/plus.svg new file mode 100644 index 0000000..5194655 --- /dev/null +++ b/components/Icon/svg/plus.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/package.json b/package.json index d67d276..1a50122 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "@emotion/react": "^11.1.5", "@emotion/styled": "^11.1.5", "axios": "^0.21.1", + "date-fns": "^2.20.1", "eslint": "^7.23.0", "next": "latest", "next-images": "^1.7.0", diff --git a/types/index.ts b/types/index.ts index 28bd906..37ed380 100644 --- a/types/index.ts +++ b/types/index.ts @@ -1,17 +1,28 @@ export type Member = { nickname?: string; displayId: string; - profileImageUrl: null | string; + profileImageUrl?: string; }; export type Comment = { + feedAuthorDisplayId: string; id: number; content: string; author: Member; isLike: Boolean; likeLength: number; replyLength: number; - created: number; // millisecond + created: string; +}; + +export type Reply = { + feedAuthorDisplayId: string; + id: number; + content: string; + author: Member; + isLike: Boolean; + likeLength: number; + created: string; }; export type FeedAuthor = { diff --git a/utils/api/time.ts b/utils/api/time.ts new file mode 100644 index 0000000..3ba622a --- /dev/null +++ b/utils/api/time.ts @@ -0,0 +1,45 @@ +import { + differenceInYears, + differenceInMonths, + differenceInWeeks, + differenceInDays, + differenceInHours, + differenceInMinutes, + differenceInSeconds, +} from 'date-fns'; + +export const getTimeDiff = (currentTime: string) => { + // 지금, 초, 분, 시, 일, 주, 달, 년 + const createdTime = new Date(currentTime); + const now = new Date(Date.now()); + const convertedSecond = differenceInSeconds(createdTime, now); + const convertedMinute = differenceInMinutes(createdTime, now); + const convertedHour = differenceInHours(createdTime, now); + const convertedDay = differenceInDays(createdTime, now); + const convertedWeek = differenceInWeeks(createdTime, now); + const convertedMonth = differenceInMonths(createdTime, now); + const convertedYear = differenceInYears(createdTime, now); + + if (convertedYear >= 1) { + return `${convertedYear}년`; + } + if (convertedMonth >= 1) { + return `${convertedMonth}월`; + } + if (convertedWeek >= 1) { + return `${convertedWeek}주`; + } + if (convertedDay >= 1) { + return `${convertedDay}일`; + } + if (convertedHour >= 1) { + return `${convertedHour}시간`; + } + if (convertedMinute >= 1) { + return `${convertedMinute}분`; + } + if (convertedSecond >= 1) { + return `${convertedSecond}초`; + } + return '지금'; +}; \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 0722a3c..83b934b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4546,6 +4546,11 @@ data-uri-to-buffer@3.0.1: resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz#594b8973938c5bc2c33046535785341abc4f3636" integrity sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og== +date-fns@^2.20.1: + version "2.20.1" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.20.1.tgz#7e60b7035284a5f83e37500376e738d9f49ecfd3" + integrity sha512-8P5M8Kxbnovd0zfvOs7ipkiVJ3/zZQ0F/nrBW4x5E+I0uAZVZ80h6CKd24fSXQ5TLK5hXMtI4yb2O5rEZdUt2A== + debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"