Storybook & Atomic Design – 1.12. – Building Our Post Template

Our Post Template is a consistent page layout that our users should be able to identify when visiting content of the same content type within our website.

Templates are a great way to create consistency and define your brand on a website. Giving power to the user by making them feel comfortable and confident knowing they are on the correct website.

Populating the body of the blog post page will be done with a large parsed HTML string for the article content, with accompanying organisms preceding and following the article content itself.

We can expect the article body to be sent to us in a long string like -

const blogContent = "<h1>Post Content</h1><p>You need to have a very firm paint to do this. Didn't you know you had that much power? You can move mountains. You can do anything. This is unplanned it really just happens. Let's make a nice big leafy tree.</p><img src='<http://unsplash.it/1000/400?random&gravity=center>' alt=''/><p>We don't want to set these clouds on fire. There is immense joy in just watching - watching all the little creatures in nature. And maybe, maybe, maybe...</p><p>All you have to learn here is how to have fun. Don't fiddle with it all day. This piece of canvas is your world. And I will hypnotize that just a little bit. Have fun with it.</p><p>In nature, dead trees are just as normal as live trees. I'm sort of a softy, I couldn't shoot Bambi except with a camera. We want to use a lot pressure while using no pressure at all. Let's do that again.</p><p>We're not trying to teach you a thing to copy. We're just here to teach you a technique, then let you loose into the world. Once you learn the technique, ohhh! Turn you loose on the world; you become a tiger. Nice little fluffy clouds laying around in the sky being lazy. Just go out and talk to a tree. Make friends with it.</p><p>We'll play with clouds today. We'll throw some old gray clouds in here just sneaking around and having fun. Anytime you learn something your time and energy are not wasted. Just a little indication.</p><p>If you don't like it - change it. It's your world. Just go back and put one little more happy tree in there. We need a shadow side and a highlight side. It's important to me that you're happy. We spend so much of our life looking - but never seeing. There comes a nice little fluffer.</p>";

Creating Our Post Template

We will want to set up a new post directory in our templates directory for our post files.

- __Storybook__
   - __components__
     - __templates__
       - __post__
         - post.jsx
         - post.knobs.json
         - post.stories.js
         - post.styles.jsx

<strong>post</strong>.jsx

As our post template is composed of several predefined components, we can instead import the relevant JavaScript functions to parse and decode HTML strings to produce the main body of the page, followed by our custom React components for areas of the page like the 'Related posts' at the bottom of the article.

I'll cover the decode and parse functions later in this post.

As the PostTemplate component has many immediate children, we need to wrap them in a parent element, I've gone ahead and used a custom Styled-Component but if you are feeling confident that the components will contain all of the page styles you need, you can optionally use a React.Fragment (<> for short) as the parent component.

The custom Styled-Component StyledPost is an article with no CSS styles at the moment but we'll add some later to center the page content.

As far as our components are concerned, we are using four organisms -

  1. Banner
  2. Footer
  3. Header
  4. Related

And we are using one molecule -

  1. PostContent (We'll be covering this soon)

Then using prop values we are populating each component with page data.

This data is currently hardcoded using a JSON file (post.knobs.json) and inherited from our stories file (post.stories.js) but you could use a CMS (Content Management System) or external data-source to populate this template.

import React from "react";

import StyledPost from "./post.styles";

import PostContent from "../../molecules/post/postContent";

import Banner from "../../organisms/banner/banner";
import Footer from "../../organisms/footer/footer";
import Header from "../../organisms/header/header";
import Related from "../../organisms/related/related";

const PostTemplate = ({ banner, content, footer, header, related }) => (
	<StyledPost>
		<Header {...header} />
		<PostContent content={content} />
		<Banner {...banner} />
		<Related {...related} />
		<Footer {...footer} />
	</StyledPost>
);

export default PostTemplate;

PostContent (Molecule)

To handle the content of our page, we'll be using a new molecule named 'PostContent'.

This component could be kept in the same file as our Post Template as it's very specific to this page layout, but later we could rework the component to be reusable in other contexts.

- __Storybook__
   - __components__
     - __atoms__
     - __molecules__
       - __post__
         - postContent.jsx
         - postContent.styles.jsx
import React from "react";

import StyledPostContent from "./postContent.styles";

import ParseHTML from "../../particles/parseHTML";

const PostContent = ({ content }) => (
	<StyledPostContent>{ParseHTML(content)}</StyledPostContent>
);

export default PostContent;

The PostContent component uses a Styled-Component which is an article HTML element responsible for styling any blog post content (images, headings, paragraphs, lists).

In terms of data, our PostContent component takes one prop of content and uses it in a custom particle function ParseHTML.

ParseHTML will convert our HTML content string into live HTML elements within our application. Converting <p>Hello world</p> to an actual paragraph element within our React environment and not rendering it as a string.

parseHTML

Our parseHTML function is repsonsible for converting HTML strings to HTML elements. To achieve this, we could use the React dangerouslySetInnerHTML property on our <article> element, but I prefer to lean on a popular package for parsing to avoid security risks and to add some custom parsing for our links.

HTML to React parser that works on both the server (Node.js) and the client (browser). It converts an HTML string to one or more React elements. There's also an option to replace an element with your own.

html-react-parser

Our body for the post article element will be available in the content key as a large HTML string which contains HTML markup similar to the following example -

"content": "<h1>Post Content</h1><p>You need to have a very firm paint to do this. Didn't you know you had that much power? You can move mountains. You can do anything. This is unplanned it really just happens. Let's make a nice big leafy tree.</p><img src='<http://unsplash.it/1000/400?random&gravity=center>' alt=''/><p>We don't want to set these clouds on fire. There is immense joy in just watching - watching all the little creatures in nature. And maybe, maybe, maybe...</p><p>All you have to learn here is how to have fun. Don't fiddle with it all day. This piece of canvas is your world. And I will hypnotize that just a little bit. Have fun with it.</p><p>In nature, dead trees are just as normal as live trees. I'm sort of a softy, I couldn't shoot Bambi except with a camera. We want to use a lot pressure while using no pressure at all. Let's do that again.</p><p>We're not trying to teach you a thing to copy. We're just here to teach you a technique, then let you loose into the world. Once you learn the technique, ohhh! Turn you loose on the world; you become a tiger. Nice little fluffy clouds laying around in the sky being lazy. Just go out and talk to a tree. Make friends with it.</p><p>We'll play with clouds today. We'll throw some old gray clouds in here just sneaking around and having fun. Anytime you learn something your time and energy are not wasted. Just a little indication.</p><p>If you don't like it - change it. It's your world. Just go back and put one little more happy tree in there. We need a shadow side and a highlight side. It's important to me that you're happy. We spend so much of our life looking - but never seeing. There comes a nice little fluffer.</p>"

Credit to https://www.bobrosslipsum.com/ for the placeholder text.

Within our parseHTML function we first import React and Parser from html-react-parser.

As the parsing is fairly vanilla in our case, we'll leave the configuration as an empty object, but you could use this to find and replace specific instances of class names, nodes, ids and more if you have custom logic you need to implement.

The setup we have for the parseHTML function is fairly, standard, we are accepting an HTML string as defined above, and returning the 'clean' elements which are HTML element nodes for our application.

import React from "react";
import Parser from "html-react-parser";

const config = {};

export const ParseHTML = html => {
	const clean = Parser(html, config);
	return clean;
};

export default ParseHTML;

post.knobs.json

To emulate a blog post templates render, we can use a hopeful JSON payload to populate our component story.

{
	"header": {
		"default": {
			"navigation": [
				{
					"title": "general",
					"items": [
						{
							"icon": null,
							"title": "Shop",
							"url": "#"
						},
						{
							"icon": null,
							"title": "About Celtic Elements",
							"url": "#"
						},
						{
							"icon": null,
							"title": "FAQ",
							"url": "#"
						},
						{
							"icon": null,
							"title": "Contact",
							"url": "#"
						}
					]
				},
				{
					"title": "account",
					"items": [
						{
							"icon": null,
							"title": "Insights",
							"url": "#"
						},
						{
							"icon": null,
							"title": "Account",
							"url": "#"
						},
						{
							"icon": "user",
							"title": "User",
							"url": "#"
						},
						{
							"icon": "bag",
							"title": "Cart",
							"url": "#"
						}
					]
				}
			]
		},
		"group": "Global",
		"label": "Header content"
	},
	"footer": {
		"default": {
			"menus": [
				{
					"title": "Menu 1",
					"items": [{ "title": "Home", "url": "/" }]
				},
				{
					"title": "Menu 2",
					"items": [{ "title": "About", "url": "/" }]
				},
				{
					"title": "Menu 3",
					"items": [{ "title": "Contact", "url": "/" }]
				}
			]
		},
		"label": "Footer Content",
		"group": "Global"
	},
	"related": {
		"default": {
			"items": [
				{
					"category": {
						"href": "/category/beauty-routine",
						"label": "Beauty routine"
					},
					"description": "Celtic Elements is a Welsh, Vegan, Wellness brand. We use Welsh natural ingredients from the hillsides & coast of Wales in our Skincare, Body care and Well being ranges.",
					"image": "<https://source.unsplash.com/random/500x300>",
					"slug": "creating-a-positive-day",
					"title": "Creating a Positive Day"
				},
				{
					"category": {
						"href": "/category/beauty-routine",
						"label": "Beauty routine"
					},
					"description": "Celtic Elements is a Welsh, Vegan, Wellness brand. We use Welsh natural ingredients from the hillsides & coast of Wales in our Skincare, Body care and Well being ranges.",
					"image": "<https://source.unsplash.com/random/500x300>",
					"slug": "creating-a-positive-day",
					"title": "Creating a Positive Day"
				},
				{
					"category": {
						"href": "/category/beauty-routine",
						"label": "Beauty routine"
					},
					"description": "Celtic Elements is a Welsh, Vegan, Wellness brand. We use Welsh natural ingredients from the hillsides & coast of Wales in our Skincare, Body care and Well being ranges.",
					"image": "<https://source.unsplash.com/random/500x300>",
					"slug": "creating-a-positive-day",
					"title": "Creating a Positive Day"
				}
			],
			"intro": {
				"cta": {
					"href": "/posts",
					"label": "View all posts",
					"target": null
				},
				"subtitle": "Related posts",
				"text": "Multi Award Winning Spa Manager Clare Pritchard shares the story of Celtic Elements.",
				"title": "Continue reading our beauty insights"
			}
		},
		"group": "Content",
		"label": "Related Items"
	},
	"banner": {
		"default": {
			"content": "Multi Award Winning Spa Manager Clare Pritchard shares the story of Celtic Elements.",
			"cta": {
				"title": "Lowered action",
				"url": "/discount"
			},
			"title": "Launch discount",
			"variant": "primary"
		},
		"group": "Content",
		"label": "Banner info"
	}
}

post.stories.js

We can then import the JSON payload in our Post Template Storybook story and configure the object knob to give viewers of the component an input field to manipulate the prop data provided.

import React from "react";
import { withKnobs, object } from "@storybook/addon-knobs";

import PostTemplate from "./post";

import knobData from "./post.knobs.json";
const { banner, footer, header, related } = knobData;

const blogContent =
	"<h1>Post Content</h1><p>You need to have a very firm paint to do this. Didn't you know you had that much power? You can move mountains. You can do anything. This is unplanned it really just happens. Let's make a nice big leafy tree.</p><img src='<http://unsplash.it/1000/400?random&gravity=center>' alt=''/><p>We don't want to set these clouds on fire. There is immense joy in just watching - watching all the little creatures in nature. And maybe, maybe, maybe...</p><p>All you have to learn here is how to have fun. Don't fiddle with it all day. This piece of canvas is your world. And I will hypnotize that just a little bit. Have fun with it.</p><p>In nature, dead trees are just as normal as live trees. I'm sort of a softy, I couldn't shoot Bambi except with a camera. We want to use a lot pressure while using no pressure at all. Let's do that again.</p><p>We're not trying to teach you a thing to copy. We're just here to teach you a technique, then let you loose into the world. Once you learn the technique, ohhh! Turn you loose on the world; you become a tiger. Nice little fluffy clouds laying around in the sky being lazy. Just go out and talk to a tree. Make friends with it.</p><p>We'll play with clouds today. We'll throw some old gray clouds in here just sneaking around and having fun. Anytime you learn something your time and energy are not wasted. Just a little indication.</p><p>If you don't like it - change it. It's your world. Just go back and put one little more happy tree in there. We need a shadow side and a highlight side. It's important to me that you're happy. We spend so much of our life looking - but never seeing. There comes a nice little fluffer.</p>";

export const examplePost = () => (
	<PostTemplate
		banner={object(banner.label, banner.default, banner.group)}
		content={blogContent}
		footer={object(footer.label, footer.default, footer.group)}
		header={object(header.label, header.default, header.group)}
		related={object(related.label, related.default, related.group)}
	/>
);

export default {
	component: PostTemplate,
	decorators: [withKnobs],
	title: "Templates|Blog Post"
};

Conclusion

We now have a blog post template, which uses a static layout and dynamic content to populate the body of the page. Allowing the editors of the website to focus on the content, rather than the structure when creating new blog posts.

In the next lesson, we'll be covering the homepage page which is the last official concept under the methodology of atomic design.

Continue Reading 📚