Storybook & Atomic Design – 1.13. – Building Our Homepage

When building out pages with React and Storybook it is best to think of the page not as a component but as a high-level grouping wrapper.

The page concept should not have much, if any, custom logic within the component itself. Instead, it should rely on the styles and functionality that we've created in our smaller concepts (atoms, molecules, and organisms).

A login form should be an organism of it's own and imported to a login page.

A homepage shouldn't have any CSS tightly coupled to it, it should implement the styles found within the components themselves instead.

If you find yourself reaching for CSS or functions that you need for a page, first think about how you can build these in reusable components that aren't confined to the single page you are working on.

Creating Our Homepage Component

We will set up our homepage similar to how did in the previous lesson with our post template, except this time the page will exist in a new directory pages/homepage.

- __Storybook__
   - __components__
     - __pages__
       - __homepage__
         - homepage.jsx
         - homepage.knobs.json
         - homepage.stories.js

homepage.jsx

The homepage structure is very similar to the blog post template.

The main difference here is that the page doesn't include a Styled-Component as the parent wrapper of the page.

This is because, again, we don't want to tightly couple any styles associated with a page in our application. Doing so will prevent components from becoming reusable, and often creates tricky situations later when we have overriding styles fighting one another in our application.

import React from "react";

import Banner from "../../organisms/banner/banner";
import Carousel from "../../organisms/carousel/carousel";
import Footer from "../../organisms/footer/footer";
import Header from "../../organisms/header/header";

const Homepage = ({ banner, carousel, footer, header }) => (
	<>
		<Header {...header} />
		<Banner {...banner} />
		<Carousel {...carousel} />
		<Footer {...footer} />
	</>
);

export default Homepage;

page.knobs.json

To emulate how our page will act later on we'll need some JSON data to parse out into React components.

Again, if you've been following along this won't be new, we're using the Storybook knobs to create interactive fields for our page template, and separating the default values, labels and group ID for these fields into a JSON file.

{
	"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"
	},
	"banner": {
		"content": {
			"default": "Creating a Positive Day",
			"group": "Banner",
			"label": "Banner Content"
		},
		"cta": {
			"default": {
				"href": "#",
				"label": "Call to action",
				"target": null
			},
			"group": "Banner",
			"label": "Banner Call to Action"
		},
		"title": {
			"default": "Creating a Positive Day",
			"group": "Banner",
			"label": "Banner Title"
		}
	},
	"carousel": {
		"items": {
			"default": [
				{
					"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"
				}
			],
			"group": "Content",
			"label": "Related Items"
		},
		"intro": {
			"cta": {
				"default": {
					"href": "/shop",
					"label": "View all products",
					"target": null
				},
				"group": "Content",
				"label": "Header Call to Action"
			},
			"subtitle": {
				"default": "Our products",
				"group": "Content",
				"label": "Subtitle"
			},
			"text": {
				"default": "Multi Award Winning Spa Manager Clare Pritchard shares the story of Celtic Elements.",
				"group": "Content",
				"label": "Intro"
			},
			"title": {
				"default": "Premium, handcrafted care",
				"group": "Content",
				"label": "Title"
			}
		}
	}
}

page.stories.js

We can then import the JSON payload in our Page 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 { array, object, text } from "@storybook/addon-knobs";
import { withDesign } from "storybook-addon-designs";
import Homepage from "./homepage";

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

export const homepageExample = () => (
	<Homepage
		footer={object(footer.label, footer.default, footer.group)}
		header={object(header.label, header.default, header.group)}
		banner={{
			content: text(
				banner.content.label,
				banner.content.default,
				banner.content.group
			),
			cta: object(banner.cta.label, banner.cta.default, banner.cta.group),
			title: text(banner.title.label, banner.title.default, banner.title.group)
		}}
		carousel={{
			intro: {
				cta: object(
					carousel.intro.cta.label,
					carousel.intro.cta.default,
					carousel.intro.cta.group
				),
				subtitle: text(
					carousel.intro.subtitle.label,
					carousel.intro.subtitle.default,
					carousel.intro.subtitle.group
				),
				text: text(
					carousel.intro.text.label,
					carousel.intro.text.default,
					carousel.intro.text.group
				),
				title: text(
					carousel.intro.title.label,
					carousel.intro.title.default,
					carousel.intro.title.group
				)
			},
			items: array(
				carousel.items.label,
				carousel.items.default,
				carousel.items.group
			)
		}}
	/>
);

homepageExample.story = {
	parameters: {
		design: {
			type: "figma",
			url:
				"<https://www.figma.com/file/uihfnI2u5KSj2LuAVZR7lt/Celtic-Elements?node-id=16%3A858>"
		}
	}
};

export default {
	component: Homepage,
	decorators: [withDesign],
	title: "Pages|Homepage"
};

Conclusion

We now have a homepage built within our system which we can send to QA (Quality Assurance) stakeholders or designers for review.

The page has been built using modular, reusable components which means that if there is feedback on a component, we can update it in isolation and have the changes ripple throughout all of our developed pages, reducing the time required for feedback amends and creating a more flexible system.

In the next episode we are going to be covering the last atomic design principle (although it's unofficial), particles, where we will be creating an Apollo Provider and interfacing with an external GraphQL API to pull external data into our application from a live source.

Continue Reading 📚