GatsbyJS Series — Layout with mocked data (Part 3)

Jun 21, 2019

In this part, we’ll lay out our blog with mocked data first. We’ll integrate with Backend/CMS headless later.

Homepage Mockup

home page mockup
Home page mockup

  1. Navigation blog’s logo.
  2. Featured post.
  3. List most recent posts. Each post includes a hero image, title, description, author, published date.
  4. Footer.

Note: I’m an amateur photoshopper so maybe our final product could be different a little bit with the beginning mockup.

1. Navigation with blog’s logo

Let’s take a look at our structure project now:

project structure 2
Project structure

Normally our blog will be contained inside a layout. That’s why we create a Layout.tsx component here:

import React from 'react'
import styled, { ThemeProvider } from 'styled-components'

import Navigation from '@components/Navigation'

import { rhythm, scale } from '@utils/typography'
import { colors } from '@constants/index'

interface ILayout {
  location: ILocation
  children: any
}

const theme = {
  rhythm,
  scale,
  colors,
}

const AppContainer = styled.div`
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  background: ${props => props.theme.colors.mainBackground};
`

const HeaderContainer = styled.div`
`

const MainContainer = styled.div`
  flex: 1;
  padding: 0;
  margin-top: ${props => props.theme.rhythm(1.25)};

  main {
    margin-left: auto;
    margin-right: auto;
    max-width: ${props => props.theme.rhythm(40)};
    padding: 0 ${props => props.theme.rhythm(0.5)};
  }
`

class Layout extends React.Component<ILayout, {}> {
  render() {
    const { children } = this.props

    return (
      <ThemeProvider theme={theme}>
        <AppContainer>
          <HeaderContainer>
            <Navigation />
          </HeaderContainer>
          <MainContainer>
            <main>{children}</main>
          </MainContainer>
        </AppContainer>
      </ThemeProvider>
    )
  }
}

export default Layout

Thank for ThemeProvidersupported from styled-components we can inject some pre-defined properties such as colors, rhythm, scale to the whole application. So that we can use them easily when styling with styled-components such as:

max-width: ${props => props.theme.rhythm(40)};

Next, we’ll take a look at Navigation.tsx component:

import React from 'react'
import styled from 'styled-components'
import { Link } from 'gatsby'

import { routeConstants } from '@constants/index'
import logo from '../../static/mr-leo.png'

const StyledContainer = styled.div`
  margin-left: auto;
  margin-right: auto;
  max-width: ${props => props.theme.rhythm(40)};
  padding: 0 ${props => props.theme.rhythm(0.5)};

  .inner-container {
    border-bottom: 1px solid rgba(0, 0, 0, .1);
    padding: ${props => props.theme.rhythm(0.75)} 0;
  }

  a {
    text-decoration: none;
    background-image: none;
    text-shadow: none;
  }

  img {
    margin-bottom: 0;
  }

  .logo {
    display: flex;
    align-items: center;
    height: ${props => props.theme.rhythm(2.5)};
  }
`

const Navigation = () => (
  <StyledContainer>
    <div className="inner-container">
      <Link to={routeConstants.home.path} className="logo">
        <img src={logo} alt="Logo" width="198" height="76" />
      </Link>
    </div>
  </StyledContainer>
)

export default Navigation

Nothing special except this line

import logo from '../../static/mr-leo.png'

We will get an error from typescript that can’t understand .png extension file. Solving this issue is quite easy by adding that type to declarations.d.ts:

declare module 'typography-theme-github'

// And to shim assets, use (one file extension per `declare`):
declare module '*.png'
declare module '*.jpg'
declare module '*.jpeg'

interface ILocation {
  host: string
  hostname: string
  href: string
  key: string
  origin: string
  pathname: string
  port: string
}

Note: Here we also define global type for location property because we’ll use it many places in our project.

Another thing special in Navigation.tsx file is:

import { Link } from 'gatsby'

For internal navigation, Gatsby includes a built-in <Link> component. One of the powerful of Gatsby’s <Link> is preloading feature. It means that resources are fetched by the time the user navigates to one component. I strongly recommend the official document here for more details.

We use one file named constants/index.ts to contains all constants will be used in our app:

export const colors = {
  primary: '#222',
  secondary: '#F7D02D',
  mainBackground: '#FFFFFF',
  gray500: '#757575',
}

Here is the result after all:

Styling Navigation bar
Styling Navigation bar

2. Featured post

This one is just a dummy React element with mocked data:

import React from 'react'
import styled from 'styled-components'
import { Link } from 'gatsby'

import exampleImage from '../../static/featured-example.png'

const StyledContainer = styled.div`
  article {
    display: flex;

    .thumb {
      width: 66.66%;
      padding-right: ${props => props.theme.rhythm(0.5)};

      img {
        height: ${props => props.theme.rhythm(13)};
        width: 100%;
      }
    }

    .content {
      width: 33.34%;
      padding: 0 ${props => props.theme.rhythm(0.5)};
    }

    .description, .info {
      color: ${props => props.theme.colors.gray500};
    }
  }

  a {
    text-shadow: none;
    background-image: none;
  }

  @media only screen and (max-width: 767px) {
    article {
      display: block;

      .thumb {
        width: 100%;
        padding-right: 0;

        img {
          height: ${props => props.theme.rhythm(7)};
          width: 100%;
        }
      }

      .content {
        width: 100%;
        padding: 0;
      }
    }
  }
`

const FeaturedPost = () => {
  const title = 'Convert flowed-project to Typescript'
  const slug = 'convert-flowed-project-to-typescript'
  const heroImage = exampleImage
  const publishDate = 'Jan 23, 2018'
  const description = "First, I do not recommend everyone to move to TypeScript. Who's being happy with Flow, it’s fine"

  return (
    <StyledContainer>
      <Link to={`/${slug}`}>
        <article>
          <div className="thumb">
            <img src={heroImage} alt="" />
          </div>
          <div className="content">
            <h3>{title}</h3>
            <p className="description">{description}</p>
            <div className="info">{publishDate}</div>
          </div>
        </article>
      </Link>
    </StyledContainer>
  )
}

export default FeaturedPost

For the heroImage we’re using normal img html tag. We’ll optimize image loading & performance later thank for Gatsby plugin support gatsby-image. Right now just keep it simple like that.

Adding to our home page component src/pages/index.tsx:

import React from 'react'
import styled from 'styled-components'

import Layout from '@components/Layout'
import FeaturedPost from '@components/FeaturedPost'

interface IProps {
  location: ILocation
}

const StyledContainer = styled.div`
`

class HomePage extends React.Component<IProps, {}> {
  render() {
    return (
      <Layout location={this.props.location}>
        <StyledContainer>
          <FeaturedPost />
        </StyledContainer>
      </Layout>
    )
  }
}

export default HomePage

Result:

Home page with featured post
Home page with featured post

3. Recent Posts

Recent posts section lists all posts we’ll create and sorted by published date by default.

Create a ThumbnailPost.tsx file:

import React from 'react'
import styled from 'styled-components'
import { Link } from 'gatsby'

interface IProps {
  title: string
  slug: string
  publishedDate: string
  description: string
  heroImage: string
}

const StyledContainer = styled.div`
  width: 33.3333%;

  article {
    padding-right: ${props => props.theme.rhythm(1)};

    .thumb {
      img {
        height: ${props => props.theme.rhythm(8)};
        border: 1px solid rgba(0, 0, 0, .15);
        width: 100%;
      }
    }

    .content {
      padding: 0 ${props => props.theme.rhythm(0.5)} 0 0;
    }

    .description, .info {
      color: ${props => props.theme.colors.gray500};
    }

    .info {
      ${props => ({ ...props.theme.scale(-0.5) })}
    }
  }

  a {
    text-shadow: none;
    background-image: none;
  }

  @media only screen and (max-width: 767px) {
    width: 100%;

    article {
      display: block;
      padding-right: 0;

      .thumb {
        width: 100%;
        padding-right: 0;

        img {
          height: ${props => props.theme.rhythm(7)};
          width: 100%;
        }
      }

      .content {
        width: 100%;
        padding: 0;
        margin-bottom: ${props => props.theme.rhythm(1.25)};
      }
    }
  }
`

const ThumbnailPost = (props: IProps) => {
  const {
    slug,
    heroImage,
    title,
    description,
    publishedDate,
  } = props

  return (
    <StyledContainer>
      <article>
        <Link to={`/${slug}`}>
          <div className="thumb">
            <img src={heroImage} alt="" />
          </div>
          <div className="content">
            <h2>{title}</h2>
            <p className="description">{description}</p>
            <div className="info">{publishedDate}</div>
          </div>
        </Link>
      </article>
    </StyledContainer>
  )
}

export default ThumbnailPost

Now in the home page component we need to adapt the code a little bit to show recent posts section:

import React from 'react'
import styled from 'styled-components'

import Layout from '@components/Layout'
import FeaturedPost from '@components/FeaturedPost'
import ThumbnailPost from '@components/ThumbnailPost'

import exampleImage1 from '../../static/example-1.jpeg'
import exampleImage2 from '../../static/example-2.png'
import exampleImage3 from '../../static/example-3.png'

interface IProps {
  location: ILocation
}

interface IPost {
  title: string
  slug: string
  publishedDate: string
  description: string
  heroImage: string
}

const StyledContainer = styled.div`
  .thumbnail-post-container {
    display: flex;
    margin-bottom: ${props => props.theme.rhythm(1.25)};
  }

  @media only screen and (max-width: 767px) {
    .thumbnail-post-container {
      display: block;
    }
  }
`

class HomePage extends React.Component<IProps, {}> {
  render() {
    const recentPosts = [
      {
        title: 'Dealing with Timezone and Moment.js handling',
        slug: 'dealing-with-timezone-and-moment-handling',
        publishedDate: 'Dec 22, 2018',
        description: "You’re a good developer. You and your nice team are producing useful features for your clients.",
        heroImage: exampleImage1,
      },
      {
        title: 'Dockerize NodeJS application (Part 7 — Final)',
        slug: 'dockerize-nodejs-application-part-7-final',
        publishedDate: 'Dec 16, 2018',
        description: "This post in one of part in my series about Building real APIs with NodeJS for beginners. All main contents in this series in case we want to navigate quickly.",
        heroImage: exampleImage2,
      },
      {
        title: 'Writing Unit test for API NodeJS by Jest framework (Part 6)',
        slug: 'writing-unit-test-for-api-nodejs-by-jest-framework-part-6',
        publishedDate: 'Dec 12, 2018',
        description: "When I investigated how to write unit tests for API in NodeJS, most of the articles I found that mention how to implement with Mocha, Chai, istanbul, etc…",
        heroImage: exampleImage3,
      }
    ]

    return (
      <Layout location={this.props.location}>
        <StyledContainer>
          <FeaturedPost />
          <div className="thumbnail-post-container">
            {recentPosts.map((post: IPost) => (
              <ThumbnailPost
                title={post.title}
                slug={post.slug}
                publishedDate={post.publishedDate}
                description={post.description}
                heroImage={post.heroImage}
              />
            ))}
          </div>
          <div className="thumbnail-post-container">
            {recentPosts.map((post: IPost) => (
              <ThumbnailPost
                title={post.title}
                slug={post.slug}
                publishedDate={post.publishedDate}
                description={post.description}
                heroImage={post.heroImage}
              />
            ))}
          </div>
        </StyledContainer>
      </Layout>
    )
  }
}

export default HomePage

Here is the result:

Home page with thumbnail posts
Home page with thumbnail posts

4. Footer

The footer of our blog is a very simple component. Right now we just put some content link to our home page:

import React from 'react'
import styled from 'styled-components'
import { Link } from 'gatsby'

import { routeConstants } from '@constants/index'

const StyledContainer = styled.footer`
  margin-left: auto;
  margin-right: auto;
  max-width: ${props => props.theme.rhythm(40)};
  padding: 0 ${props => props.theme.rhythm(0.5)};

  .inner-container {
    padding: ${props => props.theme.rhythm(1.25)} 0;
    border-top: 1px solid rgba(0, 0, 0, .1);
  }

  .menu {
    text-align: right;
  }

  .item {
    color: ${props => props.theme.colors.gray500};

    &:first-child {
      margin-left: ${props => props.theme.rhythm(1.25)};
    }
  }

  .dot-divider {
    color: ${props => props.theme.colors.gray500};
    padding: 0 ${props => props.theme.rhythm(0.25)};
  }

  @media only screen and (max-width: 767px) {
    .menu {
      display: flex;
      flex-direction: column;
      text-align: left;
    }

    .item {
      &:first-child {
        margin-left: 0;
      }

      line-height: ${props => props.theme.rhythm(1.55)};
    }

    .dot-divider {
      display: none;
    }
  }
`

const Footer = () => (
  <StyledContainer>
    <div className="inner-container">
      <div className="menu">
        <Link to={routeConstants.home.path} className="item">
          About Mr Leo
        </Link>
        <span className="dot-divider">&middot;</span>
        <Link to={routeConstants.home.path} className="item">
          Latest Stories
        </Link>
        <span className="dot-divider">&middot;</span>
        <Link to={routeConstants.home.path} className="item">
          Contact
        </Link>
      </div>
    </div>
  </StyledContainer>
)

export default Footer

Footer preview
Footer preview

Summary

So far so good. Now we already have a looking good home page for our blog. If you already familiar with React so working with Gatsby is easy, right? Almost everything we have done so far in this part is dummy React components along with styled-components css.

Next part is an interested one. We’ll work with backend/cms headless framework to manage and produce data for our blog. Besides, we’ll get acquainted with Graphql basic to query our data. See you there!