diff --git a/.eslintrc.json b/.eslintrc.json
index 7be1644..29b8b33 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -37,6 +37,7 @@
"import/prefer-default-export": 0,
"import/newline-after-import": 0,
"react/jsx-props-no-spreading": 0,
+ "no-shadow": 0,
"no-unused-vars": 0,
"import/extensions": [
"error",
diff --git a/components/Carousel/index.tsx b/components/Carousel/index.tsx
index 8cf0f2c..0c62165 100644
--- a/components/Carousel/index.tsx
+++ b/components/Carousel/index.tsx
@@ -24,20 +24,20 @@ function Carousel({ images, showDots }: CarouselType): ReactElement {
slideRef.current.style.transform = `translateX(-${currentSlide}00%)`;
}, [slideRef, currentSlide]);
return (
- <>
-
-
+
+
+
{images.map((image) => (
))}
-
+
{hasPrev && }
{hasNext && (
)}
-
+
{showDots && (
{images.map((_, index) => (
@@ -45,7 +45,7 @@ function Carousel({ images, showDots }: CarouselType): ReactElement {
))}
)}
- >
+
);
}
@@ -55,13 +55,15 @@ Carousel.defaultProps = {
export default Carousel;
-const Container = styled.div`
+const Container = styled.div``;
+
+const SliderContainer = styled.div`
width: 100%;
overflow-x: hidden;
position: relative;
`;
-const SliderContainer = styled.div`
+const ImageContainer = styled.div`
display: flex;
transition: all 0.3s ease-in-out;
`;
@@ -92,9 +94,10 @@ const Button = styled.button`
`;
const DotsContainer = styled.div`
- text-align: center;
- height: 20px;
- margin-bottom: -20px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 40px;
`;
type DotType = {
diff --git a/components/Feed/Anchor.tsx b/components/Feed/Anchor.tsx
new file mode 100644
index 0000000..5f9d814
--- /dev/null
+++ b/components/Feed/Anchor.tsx
@@ -0,0 +1,22 @@
+import React, { ReactElement, ReactNode } from 'react';
+import styled from '@emotion/styled';
+import Link, { LinkProps } from 'next/link';
+
+export type AnchorProps = {
+ children: ReactNode;
+} & LinkProps;
+
+function Anchor({ children, ...linkProps }: AnchorProps): ReactElement {
+ return (
+
+ {children}
+
+ );
+}
+
+export default Anchor;
+
+const StyledAnchor = styled.a`
+ cursor: pointer;
+ font-weight: 600;
+`;
diff --git a/components/Feed/ButtonGroup.tsx b/components/Feed/ButtonGroup.tsx
new file mode 100644
index 0000000..fd8e2a5
--- /dev/null
+++ b/components/Feed/ButtonGroup.tsx
@@ -0,0 +1,56 @@
+import React, { ReactElement } from 'react';
+import Icon from 'components/Icon';
+import styled from '@emotion/styled';
+
+export type ButtonGroupProps = {
+ isLike: boolean;
+};
+
+function ButtonGroup({ isLike }: ButtonGroupProps): ReactElement {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export default ButtonGroup;
+
+const IconButton = styled.button`
+ all: unset;
+ height: 40px;
+ width: 40px;
+ padding: 8px;
+ box-sizing: border-box;
+ cursor: pointer;
+`;
+
+const Container = styled.section`
+ height: 40px;
+ width: 100%;
+ margin-top: -40px;
+ display: flex;
+ align-items: center;
+ ${IconButton}:first-child {
+ margin-left: -8px;
+ }
+ ${IconButton}:last-child {
+ margin-right: -8px;
+ }
+`;
+
+const FlexGap = styled.div`
+ flex-grow: 1;
+`;
diff --git a/components/Feed/CommentMore.tsx b/components/Feed/CommentMore.tsx
new file mode 100644
index 0000000..f9cab5e
--- /dev/null
+++ b/components/Feed/CommentMore.tsx
@@ -0,0 +1,16 @@
+import React, { ReactElement } from 'react';
+import styled from '@emotion/styled';
+
+export type CommentMoreProps = {
+ commentLength: number;
+};
+
+function CommentMore({ commentLength }: CommentMoreProps): ReactElement {
+ return 댓글 {commentLength}개 모두 보기;
+}
+
+export default CommentMore;
+
+const Container = styled.div`
+ margin-bottom: 4px;
+`;
diff --git a/components/Feed/CommentPreview.tsx b/components/Feed/CommentPreview.tsx
new file mode 100644
index 0000000..c3a81c6
--- /dev/null
+++ b/components/Feed/CommentPreview.tsx
@@ -0,0 +1,19 @@
+import React, { ReactElement } from 'react';
+import { PreviewComment } from 'types/index';
+import Anchor from 'components/Feed/Anchor';
+import styled from '@emotion/styled';
+
+function CommentPreview({ author: { displayId }, content }: PreviewComment): ReactElement {
+ return (
+
+ {displayId}
+ {content}
+
+ );
+}
+
+export default CommentPreview;
+
+const Container = styled.div`
+ margin-bottom: 4px;
+`;
diff --git a/components/Feed/ContentBody.tsx b/components/Feed/ContentBody.tsx
new file mode 100644
index 0000000..fa8af77
--- /dev/null
+++ b/components/Feed/ContentBody.tsx
@@ -0,0 +1,24 @@
+import React, { ReactElement } from 'react';
+import { FeedAuthor } from 'types/index';
+import Anchor from 'components/Feed/Anchor';
+import styled from '@emotion/styled';
+
+export type AuthorBodyProps = {
+ body: string;
+ author: FeedAuthor;
+};
+
+function ContentBody({ body, author: { displayId } }: AuthorBodyProps): ReactElement {
+ return (
+
+ {displayId}
+ {body}
+
+ );
+}
+
+export default ContentBody;
+
+const Container = styled.div`
+ margin-bottom: 4px;
+`;
diff --git a/components/Feed/Feed.stories.tsx b/components/Feed/Feed.stories.tsx
new file mode 100644
index 0000000..db45ca8
--- /dev/null
+++ b/components/Feed/Feed.stories.tsx
@@ -0,0 +1,64 @@
+import React from 'react';
+import { Story, Meta } from '@storybook/react/types-6-0';
+
+import Feed, { FeedProps } from './Feed';
+
+export default {
+ title: 'Feed',
+ component: Feed,
+ parameters: {
+ layout: 'fullscreen',
+ },
+} as Meta;
+
+const Template: Story = (args) => ;
+
+export const Default = Template.bind({});
+Default.args = {
+ author: {
+ displayId: '_dohakim',
+ isFollowedByMe: true,
+ },
+ images: [
+ 'https://via.placeholder.com/300?text=1',
+ 'https://via.placeholder.com/300?text=2',
+ 'https://via.placeholder.com/300?text=3',
+ 'https://via.placeholder.com/300?text=4',
+ 'https://via.placeholder.com/300?text=5',
+ 'https://via.placeholder.com/300?text=6',
+ 'https://via.placeholder.com/300?text=7',
+ 'https://via.placeholder.com/300?text=8',
+ 'https://via.placeholder.com/300?text=9',
+ 'https://via.placeholder.com/300?text=10',
+ ],
+ isLike: true,
+ likeLength: 1,
+ likeUser: {
+ displayId: 'dongha',
+ profileImageUrl:
+ 'https://post-phinf.pstatic.net/MjAxODA3MTlfMTIg/MDAxNTMxOTg5ODE5OTAw.edb-H-Rmhr2dFvKAqKA11flZ2k45cRi4Q4IaHirlMF4g.It6ziXN3vtf0R7B2p9DdwOy1hovG7aynuCPwAysStMcg.JPEG/jy180719b2.jpg?type=w1200',
+ isFollowedByMe: false,
+ },
+ body: 'in 송도',
+ commentLength: 2,
+ commentPreview: [
+ {
+ author: {
+ displayId: 'dongha',
+ profileImageUrl:
+ 'https://post-phinf.pstatic.net/MjAxODA3MTlfMTIg/MDAxNTMxOTg5ODE5OTAw.edb-H-Rmhr2dFvKAqKA11flZ2k45cRi4Q4IaHirlMF4g.It6ziXN3vtf0R7B2p9DdwOy1hovG7aynuCPwAysStMcg.JPEG/jy180719b2.jpg?type=w1200',
+ isFollowedByMe: false,
+ },
+ content: '와 멋지다',
+ },
+ {
+ author: {
+ displayId: 'dongha',
+ profileImageUrl:
+ 'https://post-phinf.pstatic.net/MjAxODA3MTlfMTIg/MDAxNTMxOTg5ODE5OTAw.edb-H-Rmhr2dFvKAqKA11flZ2k45cRi4Q4IaHirlMF4g.It6ziXN3vtf0R7B2p9DdwOy1hovG7aynuCPwAysStMcg.JPEG/jy180719b2.jpg?type=w1200',
+ isFollowedByMe: false,
+ },
+ content: '와 멋지다2',
+ },
+ ],
+};
diff --git a/components/Feed/Feed.tsx b/components/Feed/Feed.tsx
new file mode 100644
index 0000000..60b3207
--- /dev/null
+++ b/components/Feed/Feed.tsx
@@ -0,0 +1,39 @@
+import React from 'react';
+import type { ReactElement } from 'react';
+import type { Feed as FeedType } from 'types/index';
+import Carousel from 'components/Carousel';
+import FeedBody from 'components/Feed/FeedBody';
+import FeedHeader from './FeedHeader';
+
+export type FeedProps = FeedType;
+
+function Feed({
+ author,
+ body,
+ commentLength,
+ commentPreview,
+ createdAt,
+ images,
+ isLike,
+ likeLength,
+ likeUser,
+}: FeedProps): ReactElement {
+ return (
+
+
+
+
+
+ );
+}
+
+export default Feed;
diff --git a/components/Feed/FeedBody.tsx b/components/Feed/FeedBody.tsx
new file mode 100644
index 0000000..d4ca7aa
--- /dev/null
+++ b/components/Feed/FeedBody.tsx
@@ -0,0 +1,54 @@
+import React, { ReactElement } from 'react';
+import styled from '@emotion/styled';
+import ButtonGroup from 'components/Feed/ButtonGroup';
+import LikeSection from 'components/Feed/LikeSection';
+import { FeedAuthor, PreviewComment } from 'types/index';
+import ContentBody from 'components/Feed/ContentBody';
+import CommentPreview from 'components/Feed/CommentPreview';
+
+export type FeedBodyProps = {
+ isLike: boolean;
+ likeUser: FeedAuthor;
+ likeLength: number;
+ author: FeedAuthor;
+ body: string;
+ commentLength: number;
+ commentPreview: PreviewComment[];
+ createdAt: string;
+};
+
+function FeedBody({
+ isLike,
+ likeUser,
+ likeLength,
+ body,
+ author,
+ commentLength,
+ commentPreview,
+}: FeedBodyProps): ReactElement {
+ return (
+
+
+ {likeLength > 0 && }
+
+ {commentLength > 0 && 댓글 {commentLength}개 모두 보기}
+ {commentPreview.map((previewComment) => (
+
+ ))}
+
+ );
+}
+
+export default FeedBody;
+
+const Container = styled.div`
+ display: flex;
+ padding: 0 16px;
+ flex-direction: column;
+ font-size: 14px;
+`;
+
+const CommentLengthContainer = styled.div`
+ color: rgb(142, 142, 142);
+ margin-bottom: 4px;
+`;
diff --git a/components/Feed/FeedHeader.tsx b/components/Feed/FeedHeader.tsx
new file mode 100644
index 0000000..ea5add5
--- /dev/null
+++ b/components/Feed/FeedHeader.tsx
@@ -0,0 +1,36 @@
+import React from 'react';
+import type { ReactElement } from 'react';
+import styled from '@emotion/styled';
+import UserAvatar from 'components/UserAvatar';
+import type { FeedAuthor } from 'types/index';
+import Icon from 'components/Icon';
+
+export type FeedHeaderProps = {
+ author: FeedAuthor;
+};
+
+function FeedHeader({ author: { profileImageUrl, displayId } }: FeedHeaderProps): ReactElement {
+ return (
+
+ );
+}
+
+export default FeedHeader;
+
+const Header = styled.header`
+ display: flex;
+ align-items: center;
+ padding: 16px;
+ height: 60px;
+ box-sizing: border-box;
+`;
+
+const DisplayIdContainer = styled.span`
+ margin-left: 12px;
+ font-weight: bold;
+ flex: 1;
+`;
diff --git a/components/Feed/LikeSection.tsx b/components/Feed/LikeSection.tsx
new file mode 100644
index 0000000..4de5afa
--- /dev/null
+++ b/components/Feed/LikeSection.tsx
@@ -0,0 +1,37 @@
+import React, { ReactElement, useMemo } from 'react';
+import { FeedAuthor } from 'types/index';
+import styled from '@emotion/styled';
+import UserAvatar from 'components/UserAvatar';
+import Anchor from 'components/Feed/Anchor';
+
+export type LikeSectionProps = {
+ likeUser: FeedAuthor;
+ likeLength: number;
+};
+
+function LikeSection({ likeUser: { profileImageUrl, displayId }, likeLength }: LikeSectionProps): ReactElement {
+ const count = useMemo(() => likeLength - 1, [likeLength]);
+ return (
+
+
+
+
+ {displayId}님{count > 0 && 외 {count}명}이
+ 좋아합니다.
+
+
+ );
+}
+
+export default LikeSection;
+
+const Container = styled.section`
+ margin-bottom: 8px;
+ height: 20px;
+`;
+
+const Paragraph = styled.p`
+ display: flex;
+ align-items: center;
+ margin: 0;
+`;
diff --git a/components/Feed/index.ts b/components/Feed/index.ts
new file mode 100644
index 0000000..e69de29
diff --git a/components/Icon/Icon.stories.tsx b/components/Icon/Icon.stories.tsx
index 3832b33..9250209 100644
--- a/components/Icon/Icon.stories.tsx
+++ b/components/Icon/Icon.stories.tsx
@@ -37,9 +37,9 @@ Search.args = {
size: 'big',
};
-export const Favarite = Template.bind({});
-Favarite.args = {
- name: 'favorite',
+export const Like = Template.bind({});
+Like.args = {
+ name: 'like',
size: 'big',
};
diff --git a/components/Icon/svg/index.ts b/components/Icon/svg/index.ts
index f118bc8..301403f 100644
--- a/components/Icon/svg/index.ts
+++ b/components/Icon/svg/index.ts
@@ -2,9 +2,10 @@ export { ReactComponent as menu } from './menu.svg';
export { ReactComponent as home } from './home.svg';
export { ReactComponent as activity } from './activity.svg';
export { ReactComponent as search } from './search.svg';
-export { ReactComponent as favorite } from './favorite.svg';
+export { ReactComponent as likeFilled } from './likeFilled.svg';
export { ReactComponent as comment } from './comment.svg';
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 like } from './like.svg';
diff --git a/components/Icon/svg/like.svg b/components/Icon/svg/like.svg
new file mode 100644
index 0000000..c54c5bd
--- /dev/null
+++ b/components/Icon/svg/like.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/components/Icon/svg/favorite.svg b/components/Icon/svg/likeFilled.svg
similarity index 100%
rename from components/Icon/svg/favorite.svg
rename to components/Icon/svg/likeFilled.svg
diff --git a/components/UserAvatar/index.stories.tsx b/components/UserAvatar/index.stories.tsx
index f11df4e..336b04b 100644
--- a/components/UserAvatar/index.stories.tsx
+++ b/components/UserAvatar/index.stories.tsx
@@ -1,7 +1,7 @@
import React from 'react';
// also exported from '@storybook/react' if you can deal with breaking changes in 6.1
import { Story, Meta } from '@storybook/react/types-6-0';
-import UserAvatar, { UserAvatarType } from '../UserAvatar';
+import UserAvatar, { UserAvatarType } from '.';
export default {
title: 'UserAvatar',
@@ -11,16 +11,17 @@ export default {
const Template: Story = (args) => ;
export const Default = Template.bind({});
-Default.args = {
-};
+Default.args = {};
export const Photo = Template.bind({});
Photo.args = {
- thumbnail: 'https://post-phinf.pstatic.net/MjAxODA3MTlfMTIg/MDAxNTMxOTg5ODE5OTAw.edb-H-Rmhr2dFvKAqKA11flZ2k45cRi4Q4IaHirlMF4g.It6ziXN3vtf0R7B2p9DdwOy1hovG7aynuCPwAysStMcg.JPEG/jy180719b2.jpg?type=w1200'
-}
+ profileImageUrl:
+ 'https://post-phinf.pstatic.net/MjAxODA3MTlfMTIg/MDAxNTMxOTg5ODE5OTAw.edb-H-Rmhr2dFvKAqKA11flZ2k45cRi4Q4IaHirlMF4g.It6ziXN3vtf0R7B2p9DdwOy1hovG7aynuCPwAysStMcg.JPEG/jy180719b2.jpg?type=w1200',
+};
export const Big = Template.bind({});
Big.args = {
- size:"big",
- thumbnail: 'https://post-phinf.pstatic.net/MjAxODA3MTlfMTIg/MDAxNTMxOTg5ODE5OTAw.edb-H-Rmhr2dFvKAqKA11flZ2k45cRi4Q4IaHirlMF4g.It6ziXN3vtf0R7B2p9DdwOy1hovG7aynuCPwAysStMcg.JPEG/jy180719b2.jpg?type=w1200'
-}
\ No newline at end of file
+ size: 'big',
+ profileImageUrl:
+ 'https://post-phinf.pstatic.net/MjAxODA3MTlfMTIg/MDAxNTMxOTg5ODE5OTAw.edb-H-Rmhr2dFvKAqKA11flZ2k45cRi4Q4IaHirlMF4g.It6ziXN3vtf0R7B2p9DdwOy1hovG7aynuCPwAysStMcg.JPEG/jy180719b2.jpg?type=w1200',
+};
diff --git a/components/UserAvatar/index.tsx b/components/UserAvatar/index.tsx
index 4f96d35..2a49aa8 100644
--- a/components/UserAvatar/index.tsx
+++ b/components/UserAvatar/index.tsx
@@ -2,22 +2,21 @@ import React from 'react';
import styled from '@emotion/styled';
export type UserAvatarType = {
- thumbnail?: null | string;
- size?: 'small' | 'big';
+ profileImageUrl?: null | string;
+ size: keyof typeof sizes;
};
-const DEFAULT_THUMBNAIL =
- 'https://scontent-mxp1-2.cdninstagram.com/v/t51.2885-19/44884218_345707102882519_2446069589734326272_n.jpg?_nc_ht=scontent-mxp1-2.cdninstagram.com&_nc_ohc=q2X-4RcAgecAX-QGx5q&oh=911ea87522a15a1d1657ee9b1070086b&oe=606FB88F&ig_cache_key=YW5vbnltb3VzX3Byb2ZpbGVfcGlj.2';
+const DEFAULT_THUMBNAIL = '/img/defaultAvatar.jpg';
const defaultProps = {
- thumbnail: DEFAULT_THUMBNAIL,
- size: 'small',
+ profileImageUrl: DEFAULT_THUMBNAIL,
+ size: 'default',
};
-function UserAvatar({ size, thumbnail }: UserAvatarType) {
+function UserAvatar({ size, profileImageUrl }: UserAvatarType) {
return (
-
+
);
}
@@ -26,13 +25,19 @@ UserAvatar.defaultProps = defaultProps;
export default UserAvatar;
+const sizes = {
+ small: '20px',
+ default: '32px',
+ big: '77px',
+};
+
type ContainerType = {
- size?: 'small' | 'big';
+ size: keyof typeof sizes;
};
const Container = styled.div`
- height: ${({ size }) => (size === 'big' ? '77px' : '32px')};
- width: ${({ size }) => (size === 'big' ? '77px' : '32px')};
+ height: ${({ size }) => sizes[size]};
+ width: ${({ size }) => sizes[size]};
border-radius: 50%;
overflow: hidden;
`;
diff --git a/components/UserItem/UserItem.tsx b/components/UserItem/UserItem.tsx
index e46a25e..46db785 100644
--- a/components/UserItem/UserItem.tsx
+++ b/components/UserItem/UserItem.tsx
@@ -26,7 +26,7 @@ function UserItem({
}: UserItemProps & typeof defaultProps): ReactElement {
return (
-
+
{displayId}
{nickname && {nickname}}
diff --git a/public/img/defaultAvatar.jpg b/public/img/defaultAvatar.jpg
new file mode 100644
index 0000000..7a8a843
Binary files /dev/null and b/public/img/defaultAvatar.jpg differ
diff --git a/types/index.ts b/types/index.ts
index 28bd906..e538b6a 100644
--- a/types/index.ts
+++ b/types/index.ts
@@ -20,11 +20,20 @@ export type FeedAuthor = {
profileImageUrl?: string;
};
+export type PreviewComment = {
+ id: number;
+ content: string;
+ author: Member;
+ isLike: boolean;
+ likeLength: number;
+ created: string;
+};
+
export type Feed = {
author: FeedAuthor;
body: string;
commentLength: number;
- commentPreview: any[];
+ commentPreview: PreviewComment[];
createdAt: string;
id: number;
images: string[];