Skip to content

Commit 6ded80e

Browse files
outline done
1 parent 6529af0 commit 6ded80e

File tree

15 files changed

+312
-14
lines changed

15 files changed

+312
-14
lines changed

package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,20 @@
1515
"test": "jest"
1616
},
1717
"dependencies": {
18+
"@reduxjs/toolkit": "^1.6.0",
1819
"babel-plugin-styled-components": "^1.12.0",
1920
"beprofiler": "^0.2.3",
2021
"date-fns": "^2.11.1",
2122
"gray-matter": "^4.0.2",
2223
"i18n-keys": "^1.0.8",
2324
"i18next": "^20.3.1",
25+
"lodash": "^4.17.21",
2426
"next": "^10.0.0",
27+
"next-redux-wrapper": "^7.0.2",
2528
"react": "17.0.1",
2629
"react-dom": "17.0.1",
2730
"react-markdown": "^6.0.2",
31+
"react-redux": "^7.2.4",
2832
"react-syntax-highlighter": "^15.4.3",
2933
"styled-components": "^5.3.0",
3034
"tailwindcss": "^2.1.2",
@@ -33,6 +37,7 @@
3337
"devDependencies": {
3438
"@babel/core": "^7.14.3",
3539
"@types/jest": "^26.0.23",
40+
"@types/lodash": "^4.14.170",
3641
"@types/node": "^15.6.0",
3742
"@types/react": "^17.0.6",
3843
"@types/react-syntax-highlighter": "^13.5.0",

src/app/app.store.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { configureStore } from '@reduxjs/toolkit';
2+
import { createWrapper } from 'next-redux-wrapper';
3+
import { postSlice } from '../logic/store/post.slice';
4+
5+
export const makeStore = () =>
6+
configureStore({
7+
reducer: {
8+
[postSlice.name]: postSlice.reducer,
9+
},
10+
});
11+
12+
export type Store = ReturnType<typeof makeStore>;
13+
export type StoreState = ReturnType<Store['getState']>;
14+
15+
export const wrapper = createWrapper<Store>(makeStore);

src/components/layout.tsx

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,14 @@ import { translate, translationKeys } from '../logic/translations/translation.se
33
import styled from 'styled-components';
44
import { Menu } from './menu/menu';
55
import { Footer } from './footer/footer';
6+
import { ReactNode } from 'react';
7+
import { MIDDLE_COLUMN_SIZE, POST_MARGIN_BOTTOM } from '../const/sizes';
68

7-
export const Layout: React.FC = ({ children }) => {
9+
interface LayoutProps {
10+
leftSection?: ReactNode;
11+
rightSection?: ReactNode;
12+
}
13+
export const Layout: React.FC<LayoutProps> = ({ children, leftSection, rightSection }) => {
814
return (
915
<LayoutContainer>
1016
<div>
@@ -28,9 +34,11 @@ export const Layout: React.FC = ({ children }) => {
2834
<header>
2935
<Menu />
3036
</header>
31-
<main>
37+
<MainContainer>
38+
<Side>{leftSection}</Side>
3239
<Main>{children}</Main>
33-
</main>
40+
<Side>{rightSection}</Side>
41+
</MainContainer>
3442
</div>
3543
<footer>
3644
<Footer />
@@ -46,8 +54,25 @@ const LayoutContainer = styled.div`
4654
display: flex;
4755
`;
4856

57+
const MainContainer = styled.main`
58+
display: flex;
59+
flex-direction: row;
60+
justify-content: space-between;
61+
margin-top: 2rem;
62+
63+
@media only screen and (max-width: 768px) {
64+
display: inherit;
65+
}
66+
`;
67+
4968
const Main = styled.div`
50-
max-width: 36rem;
51-
padding: 0 1rem;
52-
margin: 0 auto 6rem;
69+
max-width: ${MIDDLE_COLUMN_SIZE};
70+
padding: 0 1rem ${POST_MARGIN_BOTTOM}px 1rem;
71+
`;
72+
73+
const Side = styled.div`
74+
flex: 1;
75+
@media only screen and (max-width: 768px) {
76+
display: none;
77+
}
5378
`;

src/components/outline.tsx

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import React, { useEffect, useRef, useState } from 'react';
2+
import { useCallback } from 'react';
3+
import { useDispatch, useSelector } from 'react-redux';
4+
import styled from 'styled-components';
5+
import {
6+
LAYOUT_FOOTER_HEIGHT,
7+
LAYOUT_HEADER_HEIGHT,
8+
MARGIN,
9+
MIDDLE_COLUMN_SIZE,
10+
POST_MARGIN_BOTTOM,
11+
} from '../const/sizes';
12+
import { getIsOutline } from '../logic/store/post.selector';
13+
import { postSlice } from '../logic/store/post.slice';
14+
15+
export const Outline: React.FC = () => {
16+
const dispatch = useDispatch();
17+
const [outLineTop, setOutLineTop] = React.useState(0);
18+
const [outlineHeight, setOutlineHeight] = useState(0);
19+
const ref = useRef<HTMLDivElement>(null);
20+
const items = useSelector(getIsOutline);
21+
22+
useEffect(() => {
23+
setOutlineHeight(ref.current?.clientHeight || 0);
24+
dispatch(postSlice.actions.reset());
25+
}, [dispatch]);
26+
27+
const handleScroll = useCallback(() => {
28+
const { scrollY, innerHeight } = window;
29+
const clientHeight = document.body.clientHeight;
30+
const isReachedTop = scrollY > LAYOUT_HEADER_HEIGHT - MARGIN;
31+
32+
if (isReachedTop) {
33+
const visibleFooterPart = Math.max(
34+
scrollY + innerHeight - clientHeight + LAYOUT_FOOTER_HEIGHT + POST_MARGIN_BOTTOM,
35+
0
36+
);
37+
const top = Math.min(innerHeight - outlineHeight - visibleFooterPart, MARGIN);
38+
39+
setOutLineTop(top);
40+
} else {
41+
setOutLineTop(0);
42+
}
43+
}, [outlineHeight]);
44+
45+
useEffect(() => {
46+
window.addEventListener('scroll', handleScroll);
47+
return () => window.removeEventListener('scroll', handleScroll);
48+
}, [handleScroll]);
49+
50+
const handleOnItemClick = useCallback(
51+
(y: number) => () => {
52+
window.scrollTo(0, y);
53+
},
54+
[]
55+
);
56+
57+
return (
58+
<OutlineSticker outLineTop={outLineTop} ref={ref}>
59+
<OutlineContainer>
60+
<OutlineItems>
61+
{items?.map((item, index) => (
62+
<OutlineItem key={index} onClick={handleOnItemClick(item.offsetTop)}>
63+
{item.isReached ? <strong>{item.title}</strong> : item.title}
64+
</OutlineItem>
65+
))}
66+
</OutlineItems>
67+
</OutlineContainer>
68+
</OutlineSticker>
69+
);
70+
};
71+
72+
const OutlineSticker = styled.div<{ outLineTop: number }>`
73+
${({ outLineTop }) =>
74+
outLineTop
75+
? `
76+
position: fixed;
77+
top: ${outLineTop}px;
78+
width: calc((100% - ${MIDDLE_COLUMN_SIZE}) / 2);
79+
`
80+
: ''}
81+
`;
82+
83+
const OutlineContainer = styled.div`
84+
display: flex;
85+
justify-content: center;
86+
margin: 0 ${MARGIN}px;
87+
`;
88+
89+
const OutlineItems = styled.div`
90+
font-size: 12px;
91+
font-weight: 100;
92+
`;
93+
94+
const OutlineItem = styled.div`
95+
margin-bottom: 5px;
96+
cursor: pointer;
97+
`;

src/components/title.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ import styled from 'styled-components';
33
export const Title = styled.h1`
44
font-size: 36px;
55
font-weight: 400;
6+
margin-block-start: 0;
67
`;

src/const/sizes.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export const MIDDLE_COLUMN_SIZE = '36rem';
2+
3+
export const LAYOUT_HEADER_HEIGHT = 124;
4+
export const LAYOUT_FOOTER_HEIGHT = 161;
5+
6+
export const MARGIN = 32;
7+
export const POST_MARGIN_BOTTOM = 200;

src/content/rxjs-redux-observable-redux-toolkit-part-3-scenarios.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ RxJs offers a large number of operators and stream creators. However, for a typi
3333

3434
The complete source code you can see [here](https://github.com/WojciechCendrzak/react-redux-toolkit-rxjs-scenarios)
3535

36-
## 1. fetching from API
36+
## 1. Fetching from API
3737

3838
To start from, we will go with something simple.
3939
We will try to fetch some product from API and store it in the redux store.
@@ -236,7 +236,7 @@ Let's break down the code line with **forkJoin**:
236236

237237
Finally, all responses array is mapped to urls array that **setPhotos** action require.
238238

239-
## 5. Emitting more than one action from epic
239+
## 5. Emitting more than one action
240240

241241
Up to now, all our epic emits one action as a result of one action incoming.
242242
But what if we want to emit more than one action?

src/logic/store/outline.model.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export interface OutlineItem {
2+
title: string;
3+
isReached: boolean;
4+
offsetTop: number;
5+
}

src/logic/store/post.logic.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { reduce } from 'lodash';
2+
import { OutlineItem } from './outline.model';
3+
4+
export const getOutlineArray = (outlineByTitle: Record<string, OutlineItem>) =>
5+
reduce(outlineByTitle, (acc, item) => [...acc, item], [] as OutlineItem[]);

src/logic/store/post.selector.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { StoreState } from '../../app/app.store';
2+
3+
export const getIsOutlineItemReached = (title: string) => (store: StoreState) => store.post.outlineByTitle[title];
4+
export const getIsOutline = (store: StoreState) => store.post.outline;

0 commit comments

Comments
 (0)