Skip to content

Commit 8927d90

Browse files
committed
Implement reading-list
1 parent 9fb638b commit 8927d90

File tree

15 files changed

+309
-55
lines changed

15 files changed

+309
-55
lines changed

src/App.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import ErrorBoundary from './components/error/ErrorBoundary';
1212
import NotFoundPage from './pages/NotFoundPage';
1313
import { Helmet } from 'react-helmet-async';
1414
import HomePage from './pages/home/HomePage';
15+
import MainPageTemplate from './components/main/MainPageTemplate';
1516

1617
const loadableConfig = {
1718
fallback: <PageTemplate />,
@@ -38,6 +39,12 @@ const SettingPage = loadable(
3839
loadableConfig,
3940
);
4041
const SuccessPage = loadable(() => import('./pages/SuccessPage'));
42+
const ReadingListPage = loadable(
43+
() => import('./pages/readingList/ReadingListPage'),
44+
{
45+
fallback: <MainPageTemplate />,
46+
},
47+
);
4148

4249
interface AppProps {}
4350

@@ -72,6 +79,7 @@ const App: React.FC<AppProps> = props => {
7279
<Route path={['/policy/:type?']} component={PolicyPage} />
7380
<Route path="/setting" component={SettingPage} />
7481
<Route path="/success" component={SuccessPage} />
82+
<Route path="/lists/:type(liked|read)" component={ReadingListPage} />
7583
<Route component={NotFoundPage} />
7684
</Switch>
7785
</ErrorBoundary>

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/HomeTab.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ const Block = styled.div`
100100
font-size: 1.125rem;
101101
text-decoration: none;
102102
color: ${palette.gray6};
103-
height: 2.875rem;
103+
height: 3rem;
104104
105105
svg {
106106
font-size: 1.5rem;

src/components/main/FloatingHomeHeader.tsx

Lines changed: 43 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,21 @@ import MainHeader from './MainHeader';
44
import HomeTab from '../home/HomeTab';
55
import MainResponsive from './MainResponsive';
66
import { getScrollTop } from '../../lib/utils';
7+
import { Route } from 'react-router-dom';
8+
import ReadingListTab from '../readingList/ReadingListTab';
79

810
export type FloatingMainHeaderProps = {};
911

1012
function FloatingMainHeader(props: FloatingMainHeaderProps) {
1113
const [visible, setVisible] = useState(false);
12-
const [marginTop, setMarginTop] = useState(-102);
14+
const blockRef = useRef<HTMLDivElement>(null);
15+
const [height, setHeight] = useState(0);
16+
const [marginTop, setMarginTop] = useState(0);
17+
useEffect(() => {
18+
if (!blockRef.current) return;
19+
setHeight(blockRef.current.clientHeight);
20+
setMarginTop(-1 * blockRef.current.clientHeight);
21+
}, []);
1322

1423
const prevScrollTop = useRef(0);
1524
const direction = useRef<'UP' | 'DOWN'>('DOWN');
@@ -31,20 +40,22 @@ function FloatingMainHeader(props: FloatingMainHeaderProps) {
3140
if (
3241
direction.current === 'UP' &&
3342
nextDirection === 'DOWN' &&
34-
scrollTop - transitionPoint.current < -102
43+
scrollTop - transitionPoint.current < -1 * height
3544
) {
36-
transitionPoint.current = scrollTop + 102;
45+
transitionPoint.current = scrollTop + height;
3746
}
3847

3948
if (scrollTop < 64) {
4049
setVisible(false);
4150
}
4251

43-
setMarginTop(Math.min(0, -102 + transitionPoint.current - scrollTop));
52+
setMarginTop(
53+
Math.min(0, -1 * height + transitionPoint.current - scrollTop),
54+
);
4455

4556
direction.current = nextDirection;
4657
prevScrollTop.current = scrollTop;
47-
}, []);
58+
}, [height]);
4859

4960
useEffect(() => {
5061
document.addEventListener('scroll', onScroll);
@@ -62,17 +73,33 @@ function FloatingMainHeader(props: FloatingMainHeaderProps) {
6273
display: 'block',
6374
}
6475
: {
65-
display: 'none',
76+
marginTop: -1 * height,
77+
opacity: 0,
6678
}
6779
}
80+
ref={blockRef}
6881
>
6982
<MainHeader />
70-
<div className="tab-wrapper">
71-
<MainResponsive>
72-
<HomeTab />
73-
</MainResponsive>
74-
</div>
75-
<div></div>
83+
<Route
84+
path={['/', '/:mode(recent|trending)']}
85+
render={() => (
86+
<div className="tab-wrapper">
87+
<MainResponsive>
88+
<HomeTab />
89+
</MainResponsive>
90+
</div>
91+
)}
92+
exact
93+
/>
94+
<Route
95+
path="/lists/:type(liked|read)"
96+
render={({ match }) => (
97+
<StyledMainResponsive>
98+
<ReadingListTab type={match.params.type} />
99+
</StyledMainResponsive>
100+
)}
101+
exact
102+
/>
76103
</Block>
77104
);
78105
}
@@ -90,4 +117,8 @@ const Block = styled.div`
90117
}
91118
`;
92119

120+
const StyledMainResponsive = styled(MainResponsive)`
121+
margin-top: 1.5rem;
122+
`;
123+
93124
export default FloatingMainHeader;

src/components/main/MainHeader.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ function MainHeader(props: MainHeaderProps) {
1919
return (
2020
<Block>
2121
<Inner>
22-
<Logo />
22+
<StyledLink to="/">
23+
<Logo />
24+
</StyledLink>
2325
{user ? (
2426
<Right>
2527
<SearchButton to="/search">
@@ -62,6 +64,11 @@ const Block = styled.div`
6264
height: 4rem;
6365
`;
6466

67+
const StyledLink = styled(Link)`
68+
display: flex;
69+
align-items: center;
70+
`;
71+
6572
const SearchButton = styled(Link)`
6673
display: flex;
6774
align-items: center;

0 commit comments

Comments
 (0)