Skip to content
This repository was archived by the owner on Apr 26, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 25 additions & 28 deletions src/components/navigation-item.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { useRef, useEffect } from 'react';
import { Link } from 'gatsby';

type Props = {
Expand All @@ -7,39 +7,36 @@ type Props = {
slug: string;
title: string;
onClick: () => void;
}
};

class NavigationItem extends React.Component<Props> {
private element?: HTMLAnchorElement;

setReference = (ref: HTMLAnchorElement) => {
if (this.props.isActive) {
this.element = ref;
const NavigationItem = ({ isDone, isActive, slug, title, onClick }: Props) => {
const element = useRef<HTMLAnchorElement | null>(null);
const handleRef = (ref?: HTMLAnchorElement | null) => {
if (ref && isActive) {
element.current = ref;
}
}

componentDidMount() {
if (this.element) {
useEffect(() => {
if (element.current) {
// TODO: Scroll ref element in to view
// this.element.scrollIntoView(true);
// maybe use utils/scrollTo
}
}
});

render() {
const { isDone, isActive, slug, title, onClick } = this.props;
let className = 'side-nav__item ';
if (isDone) {
className += 'side-nav__item--done';
} else if (isActive) {
className += 'side-nav__item--active';
}

return (
<Link to={`/learn/${slug}`} ref={this.setReference} onClick={onClick} className={className}>
{title}
</Link>
)
let className = 'side-nav__item ';
if (isDone) {
className += 'side-nav__item--done';
} else if (isActive) {
className += 'side-nav__item--active';
}
}

export default NavigationItem
return (
<Link ref={handleRef} to={`/learn/${slug}`} onClick={onClick} className={className}>
{title}
</Link>
);
};

export default NavigationItem;
91 changes: 31 additions & 60 deletions src/components/navigation.tsx
Original file line number Diff line number Diff line change
@@ -1,64 +1,35 @@
import React from 'react';
import React, { useState } from 'react';
import NavigationSection from './navigation-section';
import { RemarkPage, NavigationSectionData } from '../types';

/** Small screen width
* If the width of the viewport is lesser than this value
* it means that the website is viewed in a tablet or mobile
* TODO have this number shared in one place in the project
*/
const MAX_SMALLSCREEN_WIDTH = 1262
import { NavigationSectionData } from '../types';
import { isSmallScreen } from '../util/isSmallScreen';

type Props = {
activePage: RemarkPage;
sections: NavigationSectionData[];
}

type State = {
isOpen: boolean;
}

class Navigation extends React.Component<Props, State> {
state = {
isOpen: false
}

toggle = () => {
this.setState({ isOpen: !this.state.isOpen });
}

onItemClick = () => {
// Get viewport width
// Source - https://stackoverflow.com/a/8876069/2621400
const w = Math.max(
document.documentElement.clientWidth,
window.innerWidth || 0
)
// If width is lesser or equal to max small screen width
if (w <= MAX_SMALLSCREEN_WIDTH) {
this.toggle();
}
}

render() {
const className = this.state.isOpen ? 'side-nav side-nav--open' : 'side-nav';

return (
<nav className={className}>
<button className="side-nav__open" onClick={this.toggle}>Menu</button>
{this.props.sections.map((section: NavigationSectionData) => {
return (
<NavigationSection
title={section.title}
items={section.items}
key={section.title}
onItemClick={this.onItemClick}
/>
)
})}
</nav>
)
}
}

export default Navigation
};

const Navigation = ({ sections }: Props) => {
const [isOpen, setIsOpen] = useState(false);
const toggle = () => setIsOpen(!isOpen);
const onItemClick = () => isSmallScreen() && toggle();
const className = isOpen ? 'side-nav side-nav--open' : 'side-nav';

return (
<nav className={className}>
<button className="side-nav__open" onClick={toggle}>
Menu
</button>
{sections.map((section: NavigationSectionData) => {
return (
<NavigationSection
title={section.title}
items={section.items}
key={section.title}
onItemClick={onItemClick}
/>
);
})}
</nav>
);
};

export default Navigation;
154 changes: 75 additions & 79 deletions src/pages/learn.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { useRef, useEffect } from 'react';
import { graphql } from 'gatsby';
import { LearnPageData } from '../types';
import Layout from '../components/layout';
Expand All @@ -11,99 +11,95 @@ import Page404 from './404';
type Props = {
data: LearnPageData;
location: Location;
}
};

export default class LearnPage extends React.Component<Props> {
prevOffset: number = -1;

componentDidMount() {
this.magicHeroNumber();
}
export default ({ data, location }: Props) => {
const prevOffset = useRef(-1);

/**
* When on the "Learn" page, we need to update the background gradient
* that runs the side menu's title color change from white to black
* as it becomes sticky and overlays the hero banner.
*/
magicHeroNumber = () => {
if (typeof window === 'undefined') { return; } // Guard for SSR.
const magicHeroNumber = () => {
if (typeof window === 'undefined') {
return;
} // Guard for SSR.
const doc = window.document;
const offset = Math.min(doc.scrollingElement!.scrollTop - 62, 210);
if (Math.abs(this.prevOffset - offset) > 5) {
this.prevOffset = offset;
if (Math.abs(prevOffset.current - offset) > 5) {
prevOffset.current = offset;
doc.body.setAttribute('style', `--magic-hero-number: ${356 - offset}px`);
}
window.requestAnimationFrame(this.magicHeroNumber);
}
window.requestAnimationFrame(magicHeroNumber);
};

render() {
const { data, location } = this.props;
const currentPage = location.pathname.split('/').pop();
const { activePage, previousPage, nextPage, navigationSections } = findActive(data.sections.group, currentPage);
if (!activePage) {
// Rendering 404 page as a component here
// The reason is to show the 404 component but maintaining the url (instead of redirecting to 404)
return <Page404 />
}
useEffect(magicHeroNumber);

return (
<Layout
title={`${activePage.frontmatter.title} by ${activePage.frontmatter.author}`}
description={activePage.frontmatter.description}
>
<Hero title={activePage.frontmatter.title} />
<Navigation activePage={activePage} sections={navigationSections} />
<Article page={activePage} previous={previousPage} next={nextPage} />
</Layout>
);
const currentPage = location.pathname.split('/').pop();
const { activePage, previousPage, nextPage, navigationSections } = findActive(
data.sections.group,
currentPage
);

if (!activePage) {
// Rendering 404 page as a component here
// The reason is to show the 404 component but maintaining the url (instead of redirecting to 404)
return <Page404 />;
}
}

export const query = graphql`{
sections: allMarkdownRemark(
sort: {
fields: [fileAbsolutePath]
order: ASC
}
) {
group(field: frontmatter___section) {
fieldValue
edges {
node {
id
fileAbsolutePath
html,
parent {
... on File {
relativePath
return (
<Layout
title={`${activePage.frontmatter.title} by ${
activePage.frontmatter.author
}`}
description={activePage.frontmatter.description}>
<Hero title={activePage.frontmatter.title} />
<Navigation sections={navigationSections} />
<Article page={activePage} previous={previousPage} next={nextPage} />
</Layout>
);
};

export const query = graphql`
{
sections: allMarkdownRemark(
sort: { fields: [fileAbsolutePath], order: ASC }
) {
group(field: frontmatter___section) {
fieldValue
edges {
node {
id
fileAbsolutePath
html
parent {
... on File {
relativePath
}
}
frontmatter {
title
description
author
}
fields {
slug
}
}
frontmatter {
title
description
author
}
fields {
slug
}
}
next {
frontmatter {
title
}
fields {
slug
}
}
previous {
frontmatter {
title
next {
frontmatter {
title
}
fields {
slug
}
}
fields {
slug
previous {
frontmatter {
title
}
fields {
slug
}
}
}
}
}
}
}`;
`;
16 changes: 16 additions & 0 deletions src/util/isSmallScreen.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/** Small screen width
* If the width of the viewport is lesser than this value
* it means that the website is viewed in a tablet or mobile
*/
export const MAX_SMALLSCREEN_WIDTH = 1262;

export const isSmallScreen = () => {
// Get viewport width
// Source - https://stackoverflow.com/a/8876069/2621400
const w = Math.max(
document.documentElement.clientWidth,
window.innerWidth || 0
);

return w <= MAX_SMALLSCREEN_WIDTH;
}
14 changes: 14 additions & 0 deletions test/util/isSmallScreen.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { isSmallScreen } from '../../src/util/isSmallScreen';

describe('Tests for isSmallScreen', () => {
it('returns true for small screens', () => {
// @ts-ignore
window.innerWidth = 1262;
expect(isSmallScreen()).toEqual(true);
});
it('returns false for large screens', () => {
// @ts-ignore
window.innerWidth = 1263;
expect(isSmallScreen()).toEqual(false);
});
});