gatsby-theme-terminal.jpg

gatsby-theme-terminal

Date published: 28-Feb-2020
3 min read / 895 words
Author: Paul Scanlon

gatsby.js
gatsby-theme
gatsby-plugin-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.

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 / blog looking and working perfectly.

The problem i've found with Wordpress is, it's fine if you want your site / blog to be exactly the same 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 for instance uses gatsby-theme-gatstats and whilst it does serve the purpose there are one or two areas where i've found it to be restrictive.

My new theme gatsby-theme-terminal 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.

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 as 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.

<SourceList filter="posts">
{(posts) => (
<Flex sx={{ flexWrap: "wrap" }}>
{posts.map((edge, index) => {
const {
frontmatter: { title, featuredImage, tags, date },
fields: { slug },
excerpt,
} = edge.node;
return (
<Box
key={index}
sx={{
display: "flex",
flex: "1 1 auto",
flexDirection: "column",
mb: 3,
width: ["100%", "100%", "50%", "33.333%"],
}}
>
<Link
href={slug}
sx={{
textDecoration: "none",
display: "flex",
flex: "1 1 auto",
flexDirection: "column",
m: (theme) => `0px ${theme.space[2]}px`,
minHeight: "1px",
}}
>
<Card
sx={{
display: "flex",
flex: "1 1 auto",
flexDirection: "column",
minHeight: "1px",
}}
>
{featuredImage && featuredImage.childImageSharp && (
<Box sx={{ minHeight: "1px" }}>
<Image
src={featuredImage.childImageSharp.fluid.src}
alt={featuredImage.childImageSharp.fluid.originalName}
/>
</Box>
)}
<Box
sx={{
display: "flex",
flex: "1 1 auto",
flexDirection: "column",
p: 3,
}}
>
<Heading variant="styles.h4" sx={{ color: "text" }}>
{title}
</Heading>
<Text sx={{ mb: 1, color: "muted" }}>{date}</Text>
<Text sx={{ mb: 1, color: "text" }}>{excerpt}</Text>
</Box>
<Box sx={{ p: 3 }}>
<Text>View Post</Text>
</Box>
</Card>
</Link>
</Box>
);
})}
</Flex>
)}
</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 pauliescalon.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