Building a React Component Library | Part 2

Written by _alanbsmith | Published 2017/06/03
Tech Story Tags: open-source | javascript | css | react | nodejs

TLDRvia the TL;DR App

a tutorial for publishing your own component library to npm

Introduction

The purpose of this series is to walk through creating a small component library so you can learn how to build your own.

This is Part 2 of this series. If you haven’t already read Part 1, I would recommend reading that first. Also, we’ll be talking a bit about Atomic Design. If you’re not already familiar with the concept, it might be helpful to get a little context here.

In this section we’ll discuss:

  • Making our components more adaptable via styled-components
  • Adding a color palette to our library
  • Playing with some polished features
  • Effective development workflow with npm link, babel, and eslint
  • Applying Atomic Design principles to our library structure

Atomic Design & Components

Now that we have a lot of the initial setup finished, let’s spend some time thinking about how to build a useful component library. To do this, we’ll borrow some of the concepts from Brad Frost’s Atomic Design. The book is definitely worth a read, and if you’d like to learn more you can buy it here. But for brevity, we’ll stick to the high-level concepts. Brad breaks down web UI into 5 distinct parts: atoms, molecules, organisms, templates, and pages.

Our library will consist of atoms and molecules. This will allow people to quickly build their organisms, templates, and pages.

Atoms -> Elements

Atoms are basically the smallest indivisible bits of UI. Think of buttons, links, inputs, etc. Going forward in this tutorial, we’ll call them “elements.” Remember we created the Button component and put it in the /elements directory? 💡

We’ll start our library by creating elements first. Once we have a enough, we’ll add a molecule. I like starting with elements because it keeps us from making our components overly complicated. Each element should be able to stand on its own. And we should be able to combine these elements to create molecules. Similar to legos, they should fit together out of the box. No power tools necessary. When we start building molecules, if something doesn’t fit properly, then we need to go back to our elements and reexamine their design.

Molecules -> Components

As I eluded to above, molecules are simple and distinct combinations of atoms. Going forward we’ll call these, you guessed it, “components”. The word “component” is very natural for the React world, but we’ll be using it in a specific way. An example would be a search field, which is a combination of a label, an input, and a button. Another example would be a dropdown list, which consists of a button (to toggle the dropdown) a list, and list items.

Okay, but what’s not a component then?

Good question. Thanks for asking. A navigation bar would not be considered a component by our definition. A navigation bar is more like an organism. It can have a lot of various components built into it, and it can vary pretty drastically depending on what it contains. A login form would be another example of an organism. It would probably contain a header, the actual labels, inputs, and button for the form, a reset password link, and the div wrapping all those elements.

Why not add organisms to the library?

Another great question. 🏆 There’s nothing wrong with adding organisms. But we should let those bubble up as they appear. The goal of this library is to create components that are easily extensible and reusable. If we start by building a lot of organisms, we’ll be constraining our users to only follow our particular patterns. We’re adding components so users can have quick access to consistent UI. But if they need something different, or don’t like our particular component, they can fall back to the elements and build their own easily.

Keep in mind there might be some occasions where it makes sense to add organisms. For example, let’s say you have three main applications for your business, and you would like a consistent login form for all of those apps. Great! Throw it in the lib!

Okay. now that we’ve covered a lot of the theory behind our library, we can get to the fun part and start building! 🛠

Adding Effective Development Workflow

We ended Part 1 with the statement, “… it would be really nice to have a local test environment to experiment with before we publish to npm.” And, as promised, that’s what we’ll do now.

Setting up our Playground

We’ll need an app to experiment with our components locally. Luckily, I have a small app just for that. You’ll need to pull it down from GitHub and install the dependencies.

$ git clone git@github.com:alanbsmith/component-lib-playground.git
$ cd component-lib-playground
$ npm install

Or, if you’re using Yarn, run yarn.

If you run npm run dev or yarn dev, open you’re browser to http://localhost:8080, and see “Hello, World!”, you’re good to go! If you hop into src/components/App.js and modify some of the JSX, you’ll notice it hot-reloads as well. This will be a nice little spot to test out our lib. Next we’ll link our library to this app.

Linking our Library

NOTE: You’ll probably want to keep your component library open in one terminal pane and the app in another.

First we’ll add a build:watch script to our library’s package.json. The -w tells Babel to keep an eye on any changes to the lib directory and update the build directory.

"scripts": {
  "build": "babel lib -d build",
  "build:watch": "babel lib -w -d build",
  "lint": "eslint lib/**; exit 0",
  ...

Now we’ll create a symlink of our library locally. If you’d like to learn more about npm link, here’s some helpful documentation. From the root of our component library, run npm link. You’ll probably notice it’s running the prepublish script in the console.

Now we’ll tell our playground app that we want to use that symlinked library. From the root of the playground app run:

NOTE: Remember to replace component-lib with the name of your library.

npm link component-lib

And that’s it! We are ready to use our component library in the playground!

Adding Button to the Playground

In src/components/App.js we’ll import and call our component just like we would with any other external library:

import React from 'react';
import PropTypes from 'prop-types';
import { Button } from 'component-lib'

... 

const App = ({ name }) => {
  return (
    <div>
      <h1>Hello, {name}!</h1>
      <Button>Click Me!</Button>
    </div>
  );
};

...

Once updated, the playground hot-reload, and the button should appear!

Adding our first component to the playground app!

💡 Notice that our playground app doesn’t have any idea what styled-components are. Anyone can use our lib without needing to install other supporting libraries. That was our main reason for putting everything in devDependencies.

Great! Now let’s make some changes to Button. It would be nice if that button was a different color. Maybe purple?

const Button = styled.button`
  background: #7E5BEF;
  ...
  &:hover {
    background: #592DEA;
  }
`;

You’ll notice when our lib was updated, the playground didn’t reflect that change. That’s because we need to run npm run build again to update the build directory. This isn’t optimal. Having to rebuild manually after every file change is a bit sluggish and painful. Remember that build:watch script we added earlier? We can use that now.

$ npm run build:watch

Boom! Our lib is now auto-updating on every change! 🎊 While we’re at it, let’s split that pane and run npm run eslint:watch so we know if we have any syntax errors right away. Your terminal should look something like this:

Open terminal with playground app, build:watch script, and lint:watch script running

Now that our changes to the library are immediately reflected in the app, we can easily experiment.

Adding a Color Palette

Having static colors for a button limits this element a lot. It would be nice to have a palette available that our users could choose from by passing in a prop. Let’s add a new file, colors.js, to /lib/styles in our library. Inside we’ll add this:

module.exports = {
  // light shades
  white: '#FFFFFF',
  snow: '#F9FAFC',
  darkSnow: '#EFF2F7',
  extraDarkSnow: '#E5E9F2',
  // dark tones
  silver: '#8492A6',
  slate: '#3C4858',
  steel: '#273444',
  black: '#1F2D3D',
  // dark shades
  smoke: '#E0E6ED',
  darkSmoke: '#D3DCE6',
  extraDarkSmoke: '#C0CCDA',
  // blue shades
  lightBlue: '#85D7FF',
  blue: '#1FB6FF',
  darkBlue: '#009EEB',
  // purple shades
  lightPurple: '#A389F4',
  purple: '#7E5BEF',
  darkPurple: '#592DEA',
  // pink shades
  lightPink: '#FF7CE5',
  pink: '#FF49DB',
  darkPink: '#FF16D1',
  // orange shades
  lightOrange: '#FF9E7C',
  orange: '#FF7849',
  darkOrange: '#FF5216',
  // green shades
  lightGreen: '#29EB7F',
  green: '#13CE66',
  darkGreen: '#0F9F4F',
  // yellow shades
  lightYellow: '#FFD55F',
  yellow: '#FFC82C',
  darkYellow: '#F8B700',
  // ui colors
  info: '#1FB6FF',
  success: '#13CE66',
  danger: '#FF4949',
  warning: '#FFC82C',
};

These colors are from Marvel’s styleguide. You’re welcome to use your own, but these work really great for our purposes.

Now we’ll update the Button element to use these colors.

...
import * as colors from '../styles/colors';

const Button = styled.button`
  background: ${({ bgColor })  => colors[bgColor]};
  ...
  color: ${({ fontColor })  => colors[fontColor]};
  ...
  &:hover {
    background: ${({ hoverColor })  => colors[hoverColor]};
  }
`;

Button.defaultProps = {
  bgColor: 'blue',
  fontColor: 'white',
  hoverColor: 'darkBlue',
};

export default Button;

Cool. Now with a few props, bgColor, fontColor, and hoverColor, our users can use any of our colors in our palette. We’ve also added defaultProps for each color, so if the user doesn’t pass any props, things don’t blow up.

Great! When we playground re-renders, the button should be blue. Let’s add some props and mess with the color a bit. I’ll set it to orange here, but feel free to use any color from our palette.

const App = ({ name }) => {
  return (
    <div>
      <h1>Hello, {name}!</h1>
      <Button
        bgColor="orange"
        hoverColor="darkOrange"
      >
        Click Me!
      </Button>
    </div>
  );
};

Nice! It’s really great for our users to be able to update the button colors on the fly without having to set anything up. It’s a little annoying to have to add the hoverColor though. And if we don’t, we get darkBlue, which is also annoying. Maybe polished could help?

Adding Some Polish

Polished is a lightweight toolset that gives us some handy Sass features. We already have it installed in our library, so we only need to import it now. We’ll add the darken function to replace our hoverColor prop.

import styled from 'styled-components';
import { darken } from 'polished';

import * as colors from '../styles/colors';

const Button = styled.button`
  background: ${({ bgColor })  => colors[bgColor]};
  ...
  color: ${({ fontColor })  => colors[fontColor]};
  ...
  &:hover {
    background: ${({ bgColor })  => darken(0.1, colors[bgColor])};
  }
`;

Button.defaultProps = {
  bgColor: 'blue',
  fontColor: 'white',
};

Notice that we removed the hoverColor prop altogether. We’re now using darken and bgColor to get our new hover background color. The 0.1 is the amount we’re darkening the color. We can also remove the hoverColor prop.

...
<Button bgColor="orange">
  Click Me!
</Button>
...

Great! That makes this component a lot easier to use! Notice again that our playground app has no idea what Polished is, and it just works.

Wrapping Up

NOTE: This would be a great place to save your work and commit.

$ git status
$ git add -A
$ git commit -m 'adds color palette and dynamic styling for Button'

Now we have an efficient workflow, a nice color palette, and some dynamic styling for our Button element. In Part 3 we’ll work through adding other elements to our library!

I hope this was helpful. If you enjoyed reading, let me know! And if you think it would end helpful for others, feel free to share!

Ready for more? Head to Part 3!


Published by HackerNoon on 2017/06/03