Skip to content

Commit f8f194d

Browse files
authored
Merge pull request velopert#110 from velopert/feature/reading-list
읽기 목록 구현
2 parents f9d0f3b + f932791 commit f8f194d

40 files changed

+544
-685
lines changed

src/App.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import React from 'react';
2-
import { Route, Switch } from 'react-router-dom';
3-
// import MainPage from './pages/main/MainPage';
2+
import { Route, Switch, Redirect } from 'react-router-dom';
43
// import PostPage from './pages/PostPage';
54

65
import loadable from '@loadable/component';
@@ -13,6 +12,7 @@ import ErrorBoundary from './components/error/ErrorBoundary';
1312
import NotFoundPage from './pages/NotFoundPage';
1413
import { Helmet } from 'react-helmet-async';
1514
import HomePage from './pages/home/HomePage';
15+
import MainPageTemplate from './components/main/MainPageTemplate';
1616

1717
const loadableConfig = {
1818
fallback: <PageTemplate />,
@@ -39,6 +39,12 @@ const SettingPage = loadable(
3939
loadableConfig,
4040
);
4141
const SuccessPage = loadable(() => import('./pages/SuccessPage'));
42+
const ReadingListPage = loadable(
43+
() => import('./pages/readingList/ReadingListPage'),
44+
{
45+
fallback: <MainPageTemplate />,
46+
},
47+
);
4248

4349
interface AppProps {}
4450

@@ -73,6 +79,8 @@ const App: React.FC<AppProps> = props => {
7379
<Route path={['/policy/:type?']} component={PolicyPage} />
7480
<Route path="/setting" component={SettingPage} />
7581
<Route path="/success" component={SuccessPage} />
82+
<Route path="/lists/:type(liked|read)" component={ReadingListPage} />
83+
<Route path="/lists" render={() => <Redirect to="/lists/liked" />} />
7684
<Route component={NotFoundPage} />
7785
</Switch>
7886
</ErrorBoundary>

src/components/base/Header.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ 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';
15+
import MainResponsive from '../main/MainResponsive';
1616

1717
const HeaderBlock = styled.div<{ floating: boolean }>`
1818
width: 100%;
@@ -123,7 +123,7 @@ const Header: React.FC<HeaderProps> = ({
123123
style={{ marginTop: floating ? floatingMargin : 0 }}
124124
data-testid="Header"
125125
>
126-
<HomeResponsive>
126+
<MainResponsive>
127127
<div className="wrapper">
128128
<div className="brand">
129129
<HeaderLogo
@@ -181,7 +181,7 @@ const Header: React.FC<HeaderProps> = ({
181181
)}
182182
</div>
183183
</div>
184-
</HomeResponsive>
184+
</MainResponsive>
185185
</HeaderBlock>
186186
{floating && <Placeholder />}
187187
</>

src/components/base/HeaderUserMenu.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ const HeaderUserMenu: React.FC<HeaderUserMenuProps> = ({
3939
내 벨로그
4040
</HeaderUserMenuItem>
4141
<HeaderUserMenuItem to="/saves">임시 글</HeaderUserMenuItem>
42+
<HeaderUserMenuItem to="/lists/liked">읽기 목록</HeaderUserMenuItem>
4243
<HeaderUserMenuItem to="/setting">설정</HeaderUserMenuItem>
4344
<HeaderUserMenuItem onClick={onLogout}>로그아웃</HeaderUserMenuItem>
4445
</div>

src/components/base/HeaderUserMenuItem.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const HeaderUserMenuItemBlock = styled.div`
1414
padding: 0.75rem 1rem;
1515
line-height: 1.5;
1616
font-weight: 500;
17+
cursor: pointer;
1718
&:hover {
1819
background: ${palette.gray0};
1920
}

src/components/common/HorizontalTab.tsx

Lines changed: 44 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React from 'react';
2-
import styled from 'styled-components';
2+
import styled, { css } from 'styled-components';
33
import { Link } from 'react-router-dom';
44
import palette from '../../lib/styles/palette';
55
import { useSpring, animated } from 'react-spring';
@@ -10,13 +10,17 @@ export type HorizontalTabProps = {
1010
children: React.ReactElement<TabItemProps>[];
1111
activeTab: string;
1212
tabWidth: number;
13+
align: 'center' | 'left';
14+
theme: 'teal' | 'gray';
1315
};
1416

1517
function HorizontalTab({
1618
className,
1719
children,
1820
activeTab,
1921
tabWidth,
22+
align,
23+
theme,
2024
}: HorizontalTabProps) {
2125
const activeIndex = React.Children.toArray(children).findIndex(
2226
tab => tab.props.name === activeTab,
@@ -34,22 +38,25 @@ function HorizontalTab({
3438
});
3539

3640
return (
37-
<Block className={className}>
41+
<Block className={className} align={align}>
3842
<div className="tab-wrapper">
3943
{React.Children.map(children, tab => {
4044
return React.cloneElement(tab, {
4145
active: tab.props.name === activeTab,
4246
width: `${tabWidth}rem`,
47+
theme,
4348
});
4449
})}
45-
<Indicator style={springStyle} />
50+
<Indicator style={springStyle} theme={theme} />
4651
</div>
4752
</Block>
4853
);
4954
}
5055

5156
HorizontalTab.defaultProps = {
5257
tabWidth: 8,
58+
align: 'center',
59+
theme: 'teal',
5360
};
5461

5562
export type TabItemProps = {
@@ -58,42 +65,62 @@ export type TabItemProps = {
5865
to: string;
5966
active?: boolean;
6067
width?: string;
68+
theme: 'teal' | 'gray';
6169
};
6270

63-
function TabItem({ name, text, to, active, width }: TabItemProps) {
71+
function TabItem({ name, text, to, active, width, theme }: TabItemProps) {
6472
return (
65-
<StyledLink to={to} className={active ? 'active' : ''} style={{ width }}>
73+
<StyledLink
74+
to={to}
75+
className={active ? 'active' : ''}
76+
style={{ width }}
77+
theme={theme}
78+
>
6679
{text}
6780
</StyledLink>
6881
);
6982
}
7083

71-
const Block = styled.div`
84+
TabItem.defaultProps = {
85+
theme: 'teal',
86+
};
87+
88+
const Block = styled.div<{ align: 'center' | 'left' }>`
7289
display: flex;
73-
justify-content: center;
90+
${props =>
91+
props.align === 'center' &&
92+
css`
93+
justify-content: center;
94+
`}
7495
.tab-wrapper {
7596
display: flex;
7697
position: relative;
7798
}
7899
`;
79100

80-
const Indicator = styled(animated.div)`
101+
const Indicator = styled(animated.div)<{ theme: 'teal' | 'gray' }>`
81102
height: 2px;
82103
display: block;
83104
position: absolute;
84105
bottom: 0px;
85106
background: ${palette.teal5};
107+
${props =>
108+
props.theme === 'gray' &&
109+
css`
110+
background: ${palette.gray8};
111+
`}
86112
`;
87113

88-
const StyledLink = styled(Link)`
114+
const StyledLink = styled(Link)<{
115+
theme: 'teal' | 'gray';
116+
}>`
89117
width: 8rem;
90118
height: 3rem;
91-
font-size: 1.3125rem;
119+
font-size: 1.125rem;
92120
${media.small} {
93-
height: 2.5rem;
94121
font-size: 1rem;
95122
}
96-
color: ${palette.gray7};
123+
color: ${palette.gray6};
97124
display: flex;
98125
align-items: center;
99126
justify-content: center;
@@ -102,6 +129,11 @@ const StyledLink = styled(Link)`
102129
&.active {
103130
font-weight: bold;
104131
color: ${palette.teal5};
132+
${props =>
133+
props.theme === 'gray' &&
134+
css`
135+
color: ${palette.gray8};
136+
`}
105137
}
106138
`;
107139

src/components/common/PostCard.tsx

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@ import usePrefetchPost from '../../lib/hooks/usePrefetchPost';
1616

1717
export type PostCardProps = {
1818
post: PartialPost;
19+
forHome?: boolean;
1920
};
2021

21-
function PostCard({ post }: PostCardProps) {
22+
function PostCard({ post, forHome }: PostCardProps) {
2223
const url = `/@${post.user.username}/${post.url_slug}`;
2324

2425
const prefetch = usePrefetchPost(post.user.username, post.url_slug);
@@ -35,7 +36,11 @@ function PostCard({ post }: PostCardProps) {
3536
};
3637

3738
return (
38-
<Block onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>
39+
<Block
40+
onMouseEnter={onMouseEnter}
41+
onMouseLeave={onMouseLeave}
42+
forHome={!!forHome}
43+
>
3944
{post.thumbnail && (
4045
<StyledLink to={url}>
4146
<RatioImage
@@ -83,9 +88,9 @@ function PostCard({ post }: PostCardProps) {
8388
);
8489
}
8590

86-
export function PostCardSkeleton() {
91+
export function PostCardSkeleton({ forHome }: { forHome?: boolean }) {
8792
return (
88-
<SkeletonBlock>
93+
<SkeletonBlock forHome={!!forHome}>
8994
<div className="skeleton-thumbnail-wrapper">
9095
<Skeleton className="skeleton-thumbnail"></Skeleton>
9196
</div>
@@ -136,7 +141,7 @@ const StyledLink = styled(Link)`
136141
text-decoration: none;
137142
`;
138143

139-
const Block = styled.div`
144+
const Block = styled.div<{ forHome: boolean }>`
140145
width: 20rem;
141146
background: white;
142147
border-radius: 4px;
@@ -153,6 +158,18 @@ const Block = styled.div`
153158
overflow: hidden;
154159
display: flex;
155160
flex-direction: column;
161+
162+
${props =>
163+
!props.forHome &&
164+
css`
165+
${mediaQuery(1440)} {
166+
width: calc(25% - 2rem);
167+
}
168+
${mediaQuery(1312)} {
169+
width: calc(33% - 1.8125rem);
170+
}
171+
`}
172+
156173
${mediaQuery(944)} {
157174
width: calc(50% - 2rem);
158175
}

src/components/common/PostCardGrid.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,20 @@ import { mediaQuery } from '../../lib/styles/media';
66

77
export type PostCardGridProps = {
88
posts: PartialPost[];
9+
loading?: boolean;
10+
forHome?: boolean;
911
};
1012

11-
function PostCardGrid({ posts }: PostCardGridProps) {
13+
function PostCardGrid({ posts, loading, forHome }: PostCardGridProps) {
1214
return (
1315
<Block>
1416
{posts.map(post => (
15-
<PostCard post={post} key={post.id} />
17+
<PostCard post={post} key={post.id} forHome={forHome} />
1618
))}
19+
{loading &&
20+
Array.from({ length: 8 }).map((_, i) => (
21+
<PostCardSkeleton key={i} forHome={forHome} />
22+
))}
1723
</Block>
1824
);
1925
}

src/components/home/FloatingHomeHeader.tsx

Lines changed: 0 additions & 93 deletions
This file was deleted.

0 commit comments

Comments
 (0)