Skip to content

Commit c63f99a

Browse files
committed
Implement responsive design to grid
1 parent ecd41df commit c63f99a

20 files changed

+479
-98
lines changed

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
"@types/react-router-dom": "^5.1.3",
4545
"@types/react-textarea-autosize": "^4.3.5",
4646
"@types/react-toastify": "^4.1.0",
47+
"@types/react-virtualized": "^9.21.8",
4748
"@types/sanitize-html": "^1.20.2",
4849
"@types/styled-components": "^4.4.1",
4950
"@types/throttle-debounce": "^2.1.0",
@@ -120,6 +121,7 @@
120121
"react-textarea-autosize": "^7.1.2",
121122
"react-toastify": "^5.5.0",
122123
"react-use": "^13.12.2",
124+
"react-virtualized": "^9.21.2",
123125
"redux": "^4.0.4",
124126
"redux-devtools-extension": "^2.13.8",
125127
"remark": "^11.0.2",

src/components/base/Header.tsx

Lines changed: 56 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,16 @@ import HeaderLogo from './HeaderLogo';
1212
import media from '../../lib/styles/media';
1313
import { SearchIcon2 } from '../../static/svg';
1414
import { Link } from 'react-router-dom';
15+
import HomeResponsive from '../home/HomeResponsive';
1516

1617
const HeaderBlock = styled.div<{ floating: boolean }>`
1718
width: 100%;
18-
> .wrapper {
19-
width: 1200px;
19+
.wrapper {
20+
/* width: 1200px; */
21+
width: 100%;
2022
height: 4rem;
21-
margin: 0 auto;
22-
padding-left: 1rem;
23-
padding-right: 1rem;
23+
/* padding-left: 1rem;
24+
padding-right: 1rem; */
2425
display: flex;
2526
justify-content: space-between;
2627
align-items: center;
@@ -36,10 +37,10 @@ const HeaderBlock = styled.div<{ floating: boolean }>`
3637
}
3738
3839
${media.large} {
39-
width: 1024px;
40+
/* width: 1024px; */
4041
}
4142
${media.medium} {
42-
width: 100%;
43+
/* width: 100%; */
4344
.write-button {
4445
display: none;
4546
}
@@ -122,63 +123,65 @@ const Header: React.FC<HeaderProps> = ({
122123
style={{ marginTop: floating ? floatingMargin : 0 }}
123124
data-testid="Header"
124125
>
125-
<div className="wrapper">
126-
<div className="brand">
127-
<HeaderLogo
128-
custom={custom}
129-
userLogo={userLogo}
130-
velogUsername={velogUsername}
131-
/>
132-
</div>
133-
<div className="right">
134-
{/* {velogUsername ? (
126+
<HomeResponsive>
127+
<div className="wrapper">
128+
<div className="brand">
129+
<HeaderLogo
130+
custom={custom}
131+
userLogo={userLogo}
132+
velogUsername={velogUsername}
133+
/>
134+
</div>
135+
<div className="right">
136+
{/* {velogUsername ? (
135137
<SearchIcon2 className="search" />
136138
) : (
137139
<Link to="/search">
138140
<SearchIcon2 className="search" />
139141
</Link>
140142
)} */}
141-
{!isSearch && (
142-
<Link
143-
to={
144-
velogUsername
145-
? `/search?username=${velogUsername}`
146-
: '/search'
147-
}
148-
>
149-
<SearchIcon2 className="search" />
150-
</Link>
151-
)}
152-
{user ? (
153-
<div className="logged-in">
143+
{!isSearch && (
144+
<Link
145+
to={
146+
velogUsername
147+
? `/search?username=${velogUsername}`
148+
: '/search'
149+
}
150+
>
151+
<SearchIcon2 className="search" />
152+
</Link>
153+
)}
154+
{user ? (
155+
<div className="logged-in">
156+
<RoundButton
157+
border
158+
color="darkGray"
159+
style={{ marginRight: '1.25rem' }}
160+
to="/write"
161+
className="write-button"
162+
>
163+
새 글 작성
164+
</RoundButton>
165+
<HeaderUserIcon user={user} onClick={toggleUserMenu} />
166+
<HeaderUserMenu
167+
onClose={toggleUserMenu}
168+
username={user.username}
169+
onLogout={onLogout}
170+
visible={userMenu}
171+
/>
172+
</div>
173+
) : (
154174
<RoundButton
155-
border
156175
color="darkGray"
157-
style={{ marginRight: '1.25rem' }}
158-
to="/write"
159-
className="write-button"
176+
onClick={onLoginClick}
177+
className="login-button"
160178
>
161-
새 글 작성
179+
로그인
162180
</RoundButton>
163-
<HeaderUserIcon user={user} onClick={toggleUserMenu} />
164-
<HeaderUserMenu
165-
onClose={toggleUserMenu}
166-
username={user.username}
167-
onLogout={onLogout}
168-
visible={userMenu}
169-
/>
170-
</div>
171-
) : (
172-
<RoundButton
173-
color="darkGray"
174-
onClick={onLoginClick}
175-
className="login-button"
176-
>
177-
로그인
178-
</RoundButton>
179-
)}
181+
)}
182+
</div>
180183
</div>
181-
</div>
184+
</HomeResponsive>
182185
</HeaderBlock>
183186
{floating && <Placeholder />}
184187
</>

src/components/common/PostCard.tsx

Lines changed: 131 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,45 +8,55 @@ import { PartialPost } from '../../lib/graphql/post';
88
import { formatDate } from '../../lib/utils';
99
import { userThumbnail } from '../../static/images';
1010
import optimizeImage from '../../lib/optimizeImage';
11+
import SkeletonTexts from './SkeletonTexts';
12+
import Skeleton from './Skeleton';
13+
import { mediaQuery } from '../../lib/styles/media';
14+
import { Link } from 'react-router-dom';
1115

1216
export type PostCardProps = {
1317
post: PartialPost;
1418
};
1519

1620
function PostCard({ post }: PostCardProps) {
21+
const url = `/@${post.user.username}/${post.url_slug}`;
22+
1723
return (
1824
<Block>
1925
{post.thumbnail && (
20-
<RatioImage
21-
widthRatio={1.916}
22-
heightRatio={1}
23-
src={optimizeImage(post.thumbnail, 640)}
24-
/>
26+
<StyledLink to={url}>
27+
<RatioImage
28+
widthRatio={1.916}
29+
heightRatio={1}
30+
src={optimizeImage(post.thumbnail, 640)}
31+
/>
32+
</StyledLink>
2533
)}
2634
<Content clamp={!!post.thumbnail}>
27-
<h4>{post.title}</h4>
28-
<div className="description-wrapper">
29-
<p>
30-
{post.short_description}
31-
{post.short_description.length === 150 && '...'}
32-
</p>
33-
</div>
35+
<StyledLink to={url}>
36+
<h4>{post.title}</h4>
37+
<div className="description-wrapper">
38+
<p>
39+
{post.short_description.replace(/&#x3A;/g, ':')}
40+
{post.short_description.length === 150 && '...'}
41+
</p>
42+
</div>
43+
</StyledLink>
3444
<div className="sub-info">
3545
<span>{formatDate(post.released_at)}</span>
3646
<span className="separator">·</span>
3747
<span>{post.comments_count}개의 댓글</span>
3848
</div>
3949
</Content>
4050
<Footer>
41-
<div className="userinfo">
51+
<Link className="userinfo" to={`/@${post.user.username}`}>
4252
<img
4353
src={post.user.profile.thumbnail || userThumbnail}
4454
alt={`user thumbnail of ${post.user.username}`}
4555
/>
4656
<span>
4757
by <b>{post.user.username}</b>
4858
</span>
49-
</div>
59+
</Link>
5060
<div className="likes">
5161
<LikeIcon />
5262
{post.likes}
@@ -56,15 +66,83 @@ function PostCard({ post }: PostCardProps) {
5666
);
5767
}
5868

69+
export function PostCardSkeleton() {
70+
return (
71+
<SkeletonBlock>
72+
<div className="skeleton-thumbnail-wrapper">
73+
<Skeleton className="skeleton-thumbnail"></Skeleton>
74+
</div>
75+
<Content clamp={true}>
76+
<h4>
77+
<SkeletonTexts wordLengths={[2, 4, 3, 6, 5]} />
78+
</h4>
79+
<div className="description-wrapper">
80+
<div className="lines">
81+
<div className="line">
82+
<SkeletonTexts wordLengths={[2, 4, 3, 6, 2, 7]} useFlex />
83+
</div>
84+
<div className="line">
85+
<SkeletonTexts wordLengths={[3, 2]} />
86+
</div>
87+
</div>
88+
</div>
89+
<div className="sub-info">
90+
<span>
91+
<Skeleton width="3rem" />
92+
</span>
93+
<span className="separator"></span>
94+
<span>
95+
<Skeleton width="4rem" />
96+
</span>
97+
</div>
98+
</Content>
99+
<Footer>
100+
<div className="userinfo">
101+
<Skeleton
102+
width="1.5rem"
103+
height="1.5rem"
104+
marginRight="0.5rem"
105+
circle
106+
/>
107+
<span>
108+
<Skeleton width="6rem" />
109+
</span>
110+
</div>
111+
</Footer>
112+
</SkeletonBlock>
113+
);
114+
}
115+
116+
const StyledLink = styled(Link)`
117+
display: block;
118+
color: inherit;
119+
text-decoration: none;
120+
`;
121+
59122
const Block = styled.div`
60123
width: 20rem;
61124
background: white;
62125
border-radius: 4px;
63126
box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.04);
127+
transition: 0.25s box-shadow ease-in, 0.25s transform ease-in;
128+
&:hover {
129+
transform: translateY(-8px);
130+
box-shadow: 0 12px 20px 0 rgba(0, 0, 0, 0.08);
131+
${mediaQuery(1024)} {
132+
transform: none;
133+
}
134+
}
64135
margin: 1rem;
65136
overflow: hidden;
66137
display: flex;
67138
flex-direction: column;
139+
${mediaQuery(767)} {
140+
margin: 0;
141+
width: 100%;
142+
& + & {
143+
margin-top: 1rem;
144+
}
145+
}
68146
`;
69147

70148
const Content = styled.div<{ clamp: boolean }>`
@@ -79,13 +157,16 @@ const Content = styled.div<{ clamp: boolean }>`
79157
line-height: 1.5;
80158
${ellipsis}
81159
color: ${palette.gray9};
160+
${mediaQuery(767)} {
161+
white-space: initial;
162+
}
82163
}
83164
.description-wrapper {
84165
flex: 1;
85166
}
86167
p {
87168
margin: 0;
88-
word-break: keep-all;
169+
word-break: break-word;
89170
overflow-wrap: break-word;
90171
font-size: 0.875rem;
91172
line-height: 1.5;
@@ -99,6 +180,11 @@ const Content = styled.div<{ clamp: boolean }>`
99180
overflow: hidden;
100181
text-overflow: ellipsis;
101182
`}
183+
/* ${props =>
184+
!props.clamp &&
185+
css`
186+
height: 15.875rem;
187+
`} */
102188
103189
color: ${palette.gray7};
104190
margin-bottom: 1.5rem;
@@ -122,9 +208,12 @@ const Footer = styled.div`
122208
line-height: 1.5;
123209
justify-content: space-between;
124210
.userinfo {
211+
text-decoration: none;
212+
color: inherit;
125213
display: flex;
126214
align-items: center;
127215
img {
216+
object-fit: cover;
128217
border-radius: 50%;
129218
width: 1.5rem;
130219
height: 1.5rem;
@@ -149,4 +238,30 @@ const Footer = styled.div`
149238
}
150239
`;
151240

152-
export default PostCard;
241+
const SkeletonBlock = styled(Block)`
242+
.skeleton-thumbnail-wrapper {
243+
width: 100%;
244+
padding-top: 52.19%;
245+
position: relative;
246+
.skeleton-thumbnail {
247+
position: absolute;
248+
top: 0;
249+
left: 0;
250+
width: 100%;
251+
height: 100%;
252+
}
253+
}
254+
.lines {
255+
height: 3.9375rem;
256+
font-size: 0.875rem;
257+
margin-bottom: 1.5rem;
258+
.line {
259+
display: flex;
260+
}
261+
.line + .line {
262+
margin-top: 0.3rem;
263+
}
264+
}
265+
`;
266+
267+
export default React.memo(PostCard);

0 commit comments

Comments
 (0)