Skip to content

Commit 18b537e

Browse files
committed
Implement mobile like button
Partially resolves velopert#19
1 parent 9b74947 commit 18b537e

File tree

4 files changed

+132
-6
lines changed

4 files changed

+132
-6
lines changed
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import React from 'react';
2+
import styled, { css } from 'styled-components';
3+
import palette from '../../lib/styles/palette';
4+
import { LikeIcon } from '../../static/svg';
5+
6+
export type MobileLikeButtonProps = {
7+
likes: number;
8+
onToggle: () => void;
9+
liked: boolean;
10+
};
11+
12+
function MobileLikeButton({ likes, onToggle, liked }: MobileLikeButtonProps) {
13+
return (
14+
<Button onClick={onToggle} data-testid="like-btn" liked={liked}>
15+
<LikeIcon />
16+
<span>{likes}</span>
17+
</Button>
18+
);
19+
}
20+
21+
const Button = styled.button<{ liked: boolean }>`
22+
background: white;
23+
border: 1px solid ${palette.gray5};
24+
padding-left: 0.75rem;
25+
padding-right: 0.75rem;
26+
display: flex;
27+
align-items: center;
28+
height: 1.5rem;
29+
border-radius: 0.75rem;
30+
outline: none;
31+
svg {
32+
width: 0.75rem;
33+
height: 0.75rem;
34+
margin-right: 0.75rem;
35+
color: ${palette.gray5};
36+
}
37+
span {
38+
font-size: 0.75rem;
39+
font-weight: bold;
40+
color: ${palette.gray5};
41+
}
42+
${props =>
43+
props.liked &&
44+
css`
45+
border-color: ${palette.teal5};
46+
background: ${palette.teal5};
47+
svg,
48+
span {
49+
color: white;
50+
}
51+
`}
52+
`;
53+
54+
export default MobileLikeButton;

src/components/post/PostHead.tsx

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,13 @@ const SubInfo = styled.div`
7878
`;
7979

8080
const EditRemoveGroup = styled.div`
81+
display: flex;
82+
justify-content: flex-end;
83+
margin-bottom: -1.25rem;
84+
${media.medium} {
85+
margin-top: -0.5rem;
86+
margin-bottom: 1.5rem;
87+
}
8188
button {
8289
padding: 0;
8390
outline: none;
@@ -112,6 +119,14 @@ const Thumbnail = styled.img`
112119
}
113120
`;
114121

122+
const MobileOnly = styled.div`
123+
align-items: center;
124+
display: none;
125+
${media.medium} {
126+
display: flex;
127+
}
128+
`;
129+
115130
export interface PostHeadProps {
116131
title: string;
117132
tags: string[];
@@ -132,6 +147,7 @@ export interface PostHeadProps {
132147
shareButtons: React.ReactNode;
133148
toc: React.ReactNode;
134149
isPrivate?: boolean;
150+
mobileLikeButton: React.ReactNode;
135151
}
136152

137153
const PostHead: React.FC<PostHeadProps> = ({
@@ -149,6 +165,7 @@ const PostHead: React.FC<PostHeadProps> = ({
149165
shareButtons,
150166
toc,
151167
isPrivate,
168+
mobileLikeButton,
152169
}) => {
153170
const [askRemove, toggleAskRemove] = useToggle(false);
154171

@@ -160,6 +177,12 @@ const PostHead: React.FC<PostHeadProps> = ({
160177
<PostHeadBlock>
161178
<div className="head-wrapper">
162179
<h1>{title}</h1>
180+
{ownPost && (
181+
<EditRemoveGroup>
182+
<button onClick={onEdit}>수정</button>
183+
<button onClick={toggleAskRemove}>삭제</button>
184+
</EditRemoveGroup>
185+
)}
163186
<SubInfo>
164187
<div className="information">
165188
<span className="username">
@@ -174,12 +197,7 @@ const PostHead: React.FC<PostHeadProps> = ({
174197
</>
175198
)}
176199
</div>
177-
{ownPost && (
178-
<EditRemoveGroup>
179-
<button onClick={onEdit}>수정</button>
180-
<button onClick={toggleAskRemove}>삭제</button>
181-
</EditRemoveGroup>
182-
)}
200+
<MobileOnly>{mobileLikeButton}</MobileOnly>
183201
</SubInfo>
184202
<TagList tags={tags} link />
185203
{shareButtons}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import * as React from 'react';
2+
import { render, fireEvent } from '@testing-library/react';
3+
import MobileLikeButton, { MobileLikeButtonProps } from '../MobileLikeButton';
4+
import palette from '../../../lib/styles/palette';
5+
6+
describe('MobileLikeButton', () => {
7+
const setup = (props: Partial<MobileLikeButtonProps> = {}) => {
8+
const initialProps: MobileLikeButtonProps = {
9+
likes: 0,
10+
onToggle: () => {},
11+
liked: false,
12+
};
13+
const utils = render(<MobileLikeButton {...initialProps} {...props} />);
14+
return {
15+
...utils,
16+
};
17+
};
18+
it('renders properly', () => {
19+
setup();
20+
});
21+
22+
it('shows likes value', () => {
23+
const utils = setup({ likes: 7 });
24+
utils.getByText('7');
25+
});
26+
27+
it('calls onToggle', () => {
28+
const onToggle = jest.fn();
29+
const utils = setup({ onToggle });
30+
const button = utils.getByTestId('like-btn');
31+
fireEvent.click(button);
32+
expect(onToggle).toBeCalled();
33+
});
34+
35+
it('shows inactive button', () => {
36+
const utils = setup({ liked: false });
37+
const button = utils.getByTestId('like-btn');
38+
expect(button).toHaveStyle('background: white');
39+
});
40+
41+
it('shows active button', () => {
42+
const utils = setup({ liked: true });
43+
const button = utils.getByTestId('like-btn');
44+
expect(button).toHaveStyle(`background: ${palette.teal5}`);
45+
});
46+
});

src/containers/post/PostViewer.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import useNotFound from '../../lib/hooks/useNotFound';
3434
import { Helmet } from 'react-helmet-async';
3535
import { RootState } from '../../modules';
3636
import { toast } from 'react-toastify';
37+
import MobileLikeButton from '../../components/post/MobileLikeButton';
3738

3839
const UserProfileWrapper = styled(VelogResponsive)`
3940
margin-top: 16rem;
@@ -348,6 +349,13 @@ const PostViewer: React.FC<PostViewerProps> = ({
348349
}
349350
toc={<PostToc />}
350351
isPrivate={post.is_private}
352+
mobileLikeButton={
353+
<MobileLikeButton
354+
likes={post.likes}
355+
liked={post.liked}
356+
onToggle={onLikeToggle}
357+
/>
358+
}
351359
/>
352360
<PostContent isMarkdown={post.is_markdown} body={post.body} />
353361
<UserProfileWrapper>

0 commit comments

Comments
 (0)