Add progress indicator on header

This commit is contained in:
Joachim Robert 2019-02-03 23:34:32 +01:00
parent 5a35f675a6
commit 1d99bc4739
5 changed files with 101 additions and 11 deletions

View file

@ -1,16 +1,24 @@
// @flow // @flow
import * as React from 'react'; import * as React from 'react';
import Link from 'next/link'; import Link from 'next/link';
import { Container, ButtonRowContainer, Label } from './style'; import {
Container,
ButtonRowContainer,
Label,
Progression,
ProgressBar } from './style';
import { PrimaryButton, GhostButton } from '../Button'; import { PrimaryButton, GhostButton } from '../Button';
import Logo from './Logo'; import Logo from './Logo';
type Props = { type Props = {
showHeaderShadow: boolean, showHeaderShadow: boolean,
displayProgress: boolean,
totalItemsCount: number,
currentCount: number,
}; };
export default function Header(props: Props) { export default function Header(props: Props) {
const { showHeaderShadow } = props; const { showHeaderShadow, totalItemsCount, currentCount, displayProgress } = props;
return ( return (
<Container showHeaderShadow={showHeaderShadow} data-cy="header"> <Container showHeaderShadow={showHeaderShadow} data-cy="header">
@ -21,6 +29,11 @@ export default function Header(props: Props) {
</a> </a>
</Link> </Link>
<Progression isHidden={!displayProgress}>
{currentCount} of {totalItemsCount} completed
<ProgressBar />
</Progression>
<ButtonRowContainer> <ButtonRowContainer>
<Link href="/about"> <Link href="/about">
<a> <a>

View file

@ -4,8 +4,8 @@ import { theme } from '../theme';
export const Container = styled.div` export const Container = styled.div`
display: grid; display: grid;
grid-template-columns: 1fr 1fr; grid-template-columns: 1fr 1fr 1fr;
grid-template-areas: 'logo actions'; grid-template-areas: 'logo progression actions';
padding: 16px; padding: 16px;
position: fixed; position: fixed;
top: 0; top: 0;
@ -25,7 +25,7 @@ export const Container = styled.div`
@media (max-width: 968px) { @media (max-width: 968px) {
padding: 8px 16px; padding: 8px 16px;
grid-template-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;
grid-template-areas: 'logo actions'; grid-template-areas: "logo actions" "progression progression";
} }
`; `;
@ -36,6 +36,34 @@ export const Logo = styled.h1`
color: ${theme.text.default}; color: ${theme.text.default};
`; `;
export const Progression = styled.div`
grid-area: progression;
text-align: center;
display: ${props =>
props.isHidden ? 'none' : 'block'};
`;
export const ProgressBar = styled.div`
height: 8px;
margin: 6px 0;
position: relative;
overflow: hidden;
border-radius: 4px;
background-image: linear-gradient(to left, #a913de, #6ac9ff);
&:after {
content: '';
position: absolute;
height: 100%;
right: 0;
width: 100%;
background: ${theme.border.default};
max-width: var(--progress);
transition: max-width ${theme.animations.default};
}
`;
export const ButtonRowContainer = styled.div` export const ButtonRowContainer = styled.div`
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;

View file

@ -17,18 +17,25 @@ import {
ScrollToTop, ScrollToTop,
} from './style'; } from './style';
import * as gtag from '../../lib/gtag'; import * as gtag from '../../lib/gtag';
import { getLocalStorageLength } from '../../lib/localStorage';
import data from '../../config/data';
export { SectionHeading, Heading, Subheading }; export { SectionHeading, Heading, Subheading };
type Props = { type Props = {
children: Node, children: Node,
displayProgress: boolean,
}; };
const totalItemsCount = Object.keys(data).length;
export default function Page(props: Props) { export default function Page(props: Props) {
const { children } = props; const { children, displayProgress } = props;
const [lastTrackedPageview, setLastTrackedPageview] = useState(null); const [lastTrackedPageview, setLastTrackedPageview] = useState(null);
const [showHeaderShadow, setHeaderShadow] = useState(false); const [showHeaderShadow, setHeaderShadow] = useState(false);
const [scrollToTopVisible, setScrollToTopVisible] = useState(false); const [scrollToTopVisible, setScrollToTopVisible] = useState(false);
const [progress, setProgress] = useState(0);
const [currentCount, setCurrentCount] = useState(0);
function handleScroll() { function handleScroll() {
const headerShadowState = window && window.pageYOffset > 0; const headerShadowState = window && window.pageYOffset > 0;
@ -39,6 +46,14 @@ export default function Page(props: Props) {
const throttledScroll = throttle(300, handleScroll); const throttledScroll = throttle(300, handleScroll);
function updateProgress() {
console.log("called")
const checkedItemsCount = getLocalStorageLength();
const progressPercentage = checkedItemsCount * 100 / totalItemsCount;
setProgress(progressPercentage);
setCurrentCount(checkedItemsCount);
};
const scrollToTop = () => { const scrollToTop = () => {
if (window) { if (window) {
window.scrollTo(0, 0); window.scrollTo(0, 0);
@ -46,6 +61,8 @@ export default function Page(props: Props) {
}; };
useEffect(() => { useEffect(() => {
updateProgress();
if (window) { if (window) {
window.addEventListener('scroll', throttledScroll); window.addEventListener('scroll', throttledScroll);
} }
@ -56,7 +73,18 @@ export default function Page(props: Props) {
setLastTrackedPageview(null); setLastTrackedPageview(null);
} }
}; };
}, []); }, [progress]);
useEffect(() => {
if (window && displayProgress) {
window.addEventListener('storage:updated', updateProgress );
}
return () => {
if (window && displayProgress) {
window.removeEventListener('storage:updated', updateProgress );
}
};
});
useEffect(() => { useEffect(() => {
if (document) { if (document) {
@ -71,7 +99,18 @@ export default function Page(props: Props) {
return ( return (
<ThemeProvider theme={theme}> <ThemeProvider theme={theme}>
<Container> <Container>
<Header showHeaderShadow={showHeaderShadow} /> <style>{`
:root {
--progress: ${100 - progress}%;
}
`}</style>
<Header
showHeaderShadow={showHeaderShadow}
displayProgress={displayProgress}
totalItemsCount={totalItemsCount}
currentCount={currentCount}
/>
<InnerContainer>{children}</InnerContainer> <InnerContainer>{children}</InnerContainer>
<Footer /> <Footer />
<ScrollToTop isVisible={scrollToTopVisible} onClick={scrollToTop}> <ScrollToTop isVisible={scrollToTopVisible} onClick={scrollToTop}>

View file

@ -10,7 +10,8 @@ export const storeItem = (key, value) => {
if (!localStorage) return; if (!localStorage) return;
try { try {
return localStorage.setItem(key, JSON.stringify(value)); localStorage.setItem(key, JSON.stringify(value));
window.dispatchEvent(new Event('storage:updated'));
} catch (err) {} } catch (err) {}
}; };
@ -18,6 +19,15 @@ export const removeItemFromStorage = key => {
if (!localStorage) return; if (!localStorage) return;
try { try {
return localStorage.removeItem(key); localStorage.removeItem(key);
window.dispatchEvent(new Event('storage:updated'));
} catch (err) {} } catch (err) {}
}; };
export const getLocalStorageLength = () => {
if (!localStorage) return;
try {
return localStorage.length;
} catch (err) {}
}

View file

@ -19,7 +19,7 @@ class Index extends React.Component<{}> {
render() { render() {
return ( return (
<Page> <Page displayProgress={true}>
<SectionHeading> <SectionHeading>
<Heading>Be safe on the internet.</Heading> <Heading>Be safe on the internet.</Heading>
<Subheading> <Subheading>