By Paul Scanlon

gatsby-theme-terminal

This is my second attempt at creating a Gatsby theme and my approach to this build was very different to the first time round.

If you want to dive right in you can see a demo here https://gatsby-theme-terminal.netlify.com/

In my first theme gatsby-theme-gatstats i was treating the theme like it was a fully complete UI toolkit / Component Library and data provider all rolled in to oneā€¦

ā€¦this feels wrong āŒ

ā€œWrongā€ sounds a bit harsh but Gatsby Themes are different to Wordpress Themes.

If youā€™ve worked with Wordpress Themes youā€™ll know the score, they are as i mentioned above ā€œfully completeā€ and after you install and add your content you have a website or blog looking and working perfectly.

The problem iā€™ve found with Wordpress is, itā€™s fine if you want your site or blog to look and function in exactly the same way as the theme, but the troubles begin when you need something to be slightly different.

Your only options are to hack over the top of the theme until you arrive at something both you and your client are happy with.

This, in the ā€œpastā€ was an acceptable approach to web development but with Gatsby Themes the team have ushered in a new way of thinking, you can of course create a theme that does as iā€™ve mentioned above, but Gatsby themes are and can be so much more!

I learned a lot from building my first theme but i learned a lot more when i started to use it ā€¦ this blog used to use gatsby-theme-gatstats and whilst it does serve a purpose there were one or two areas where iā€™ve found it to be restrictive.

My new theme gatsby-theme-terminal (the theme this blog is now built on) tries to remove those restrictions and aims to separate two very important and different parts of a theme.

  1. The data
  2. The presentation

The data

The data is somewhat a fixed set of rules. You canā€™t for a number of reasons provide users of your theme the ability to create new frontmatter fields because your themeā€™s GraphQL queries needs to know what theyā€™re called.

That said, by providing a decent set of frontmatter options for your users they should be able to get the right data to and from their file nodes.

gatsby-theme-terminal exposes the following frontmatter fields:

title: Some Post
date: 2020-01-01
dateModified: 20-20-2020
status: draft // => means it won't be rendered
isPrivate: // -> it will be rendered but you can use this prop as a filter
author: Paul Scanlon
tags: ["JavaScript", "React", "GatsbyJs", "HTML", "CSS", "theme-ui"]
featuredImage: markus-spiske-466ENaLuhLY-unsplash.jpg
embeddedImages:
  - markus-spiske-FXFz-sW0uwo-unsplash.jpg

The presentation

This is where we use the data, but how we use the data shouldnā€™t be a job for the theme, whats to say that my requirements for creating a ā€œposts listā€ will be the same for every user of the theme, and why restrict this to a single component that renders a list in a particular way, or even renders a list at all.

What we really want is to provide the theme users with both a set of UI components that they can compose any which way they like and the data.

This abstraction was fundamental to my approach with gatsby-theme-terminal

Letā€™s start with data and by looking at the <SourceList /> component provided by the theme.

<SourceList>
  {(source) => (
    <ul>
      {source.map((edge, index) => {
        const {
          frontmatter: { title },
        } = edge.node;
        return <li key={index}>{title}</li>;
      })}
    </ul>
  )}
</SourceList>

The <SourceList /> components finds and returns all data for all the .mdx files that have been defined in the theme options and gives you back an array of objects a bit like this.

{
  "node": {
    "id": "24b1d743-83cb-5014-a5b1-4094974e3a4d",
    "body": "(removed for brevity)",
    "excerpt": "Lorem ipsum dolor sit amet...",
    "frontmatter": {
      "title": "gatsby-theme-terminal",
      "tags": [
        "React", "JavaScript", "Gatsby.js"
      ],
      "date": "2020-02-27T00:00:00.000Z",
      "dateModified": 2020-02-28T00:00:00.000Z,
      "author": Paul Scanlon,
      "status": published,
      "featuredImage": "gatsby-theme-terminal-featured-image.jpg",
      "embeddedImages": gatsby-theme-terminal-image-1.jpg
    },
    "fields": {
      "slug": "/posts/2020/02/gatsby-theme-terminal/"
    }
  }
}

These are pretty much what GraphQL returns and in this component iā€™ve left the data untouched so theme users can map over the array and return any DOM node(s) they like.

Now letā€™s look at the presentation.

Instead of a ul / li it might be nice to return some cards which have the featured image, the date, an excerpt, any tags used in the post and a ā€œView Postā€ call to action, and then link it via the slug to the actual page.

To do this we can de-structure a bit more of the data returned from the <SourceList /> component

const {
  frontmatter: { title, featuredImage, tags, date },
  fields: { slug },
  excerpt,
} = edge.node;

ā€¦and then use some of the theme-ui/components which are provided by the theme to create something a bit more eye catching, like one of <Card />components used on the posts page, like this šŸ‘‡

ā€¦ and hereā€™s the source code

<SourceList filter='posts'>
  {(posts) => {
    const {
      frontmatter: { title, featuredImage, tags, date },
      excerpt,
      fields: { slug },
    } = posts[3].node;
    return <Card title={title} featuredImage={featuredImage} tags={tags} date={date} excerpt={excerpt} slug={slug} />;
  }}
</SourceList>

To understand whatā€™s going on here you do need to be a little familiar with theme-ui/components but in short they are UI components that can be re-styled by adding object keys to your theme-ui theme.

A little segway here but with gatsby-plugin-theme-ui you can write all your styles in one place and by providing design tokens all of your HTML can be styled very easily.

for example

// gatsby-plugin-theme-ui/index.js

export default {
  ...
  colors: {
    text: "#ffffff",
    primary: "#ff79c6",
  },

  styles: {
    h1: {
      color: "primary",
    },
    p: {
      color: "text",
    },
  },
}

ā€¦ and if you wanted to provide styles for any or all of the theme-ui/components components you can do something like this

// gatsby-plugin-theme-ui/index.js

export default {
  ...
  colors: {
    text: "#ffffff",
    primary: "#ff79c6",
  },
  shadows: [
    `0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24)`,
  ],

  styles: {
    h1: {
      color: "primary",
    },
    p: {
      color: "text",
    },
  },
 // here are the styles for the Card component
  card : {
    primary: {
    boxShadow: 0,
    },
  }
}

Using this method means that it doesnā€™t matter how your theme users want to render their data because youā€™ve already provided them with a set of UI components that when used already feel like part of the same theme. Furthermore if what your theme users want isnā€™t part of this components list they can use the sx={{}} prop to add theme specific styles to any HTML element.

Thereā€™s one more step that i feel is pretty essential in theme development and thatā€™s how to provide the components.

In order to use the <Card /> component in .mdx you would either have to import it as you normally would in .js

like this

import { Card } from '@theme-ui/components';

Or continue on with the ā€œpowerā€ approach and provide all of these components via the MDXProvider

like this

// some-layout.js
import * as themeUiComponents from '@theme-ui/components';
<MDXProvider components={themeUiComponents}>{children}</MDXProvider>;

Iā€™m pretty confident in this approach and have just used this theme to update my commercial portfolio pauliescanlon.io and found it to be as flexible as iā€™d intended.

Iā€™m always looking to improve my work so if youā€™re using this theme and have any questions or problems please do let me know, iā€™d honestly love to hear from you @pauliescanlon

Hey!

Leave a reaction and let me know how I'm doing.

  • 0
  • 0
  • 0
  • 0
  • 0
Powered byNeon