mirror of
https://github.com/brianlovin/security-checklist.git
synced 2026-03-11 08:55:31 +00:00
Add progress indicator on header
This commit is contained in:
parent
5a35f675a6
commit
1d99bc4739
5 changed files with 101 additions and 11 deletions
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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}>
|
||||||
|
|
|
||||||
|
|
@ -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) {}
|
||||||
|
}
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue