Storybook & Atomic Design - 1.13. - Building Our Homepage

by Jack Pritchard

Hey! Just so you know, this article is over 2 years old. Some of the information in it might be outdated, so take it with a grain of salt. I'm not saying it's not worth a read, but don't take everything in it as gospel. If you're curious about something, it never hurts to double-check with a more up-to-date source!

GitHub - whatjackhasmade/storybook-atomic-design-react

Contribute to whatjackhasmade/storybook-atomic-design-react development by creating an account on GitHub.

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": "<http://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": "<http://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": "<http://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: '<http://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.