DEV Community

loading...
Cover image for Component Libraries with Stencil.js - Going Deeper

Component Libraries with Stencil.js - Going Deeper

John Woodruff
Senior Software Engineer at GoReact, previously Domo. Co-author of Your First Year in Code. Working on something new.
Updated on ・3 min read

This is the fourth in a series of posts about creating a web component library using Stencil.js - Check out the first post

In the last post we created a very simple button component. We're now going to extend that with more options and functionality to be more fully featured. I personally want to have a couple of color options for this button, a couple of button shapes, and a few sizes.

Component Props

To start into this, we'll need a few props to allow the user to specify what they want. Let's call our props color, shape, and size. We also can have multiple button types such as submit and reset, just like a regular HTML button, so we'll add that as a prop too.

@Prop()
type: 'button' | 'reset' | 'submit' = 'button';

@Prop()
color: 'primary' | 'accent' | 'light' = 'primary';

@Prop()
shape: 'square' | 'round' = 'square';

@Prop()
size: 'small' | 'default' | 'large' = 'default';
Enter fullscreen mode Exit fullscreen mode

If you've barely/not used TypeScript before, you'll notice that instead of defining my types for these props to be of type string, which technically they are, I'm going a step further and defining that each prop can be one of several specific strings. This allows for much more effective IntelliSense when you're using this component. I'm defining up front a few specific sizes, colors, shapes, and types that I'm going to be implementing. On top of that, I've initialized those props to my preferred defaults, so that if you simply call my button component with no defined props, it'll look like a default button.

Class Map

Next I'm going to be using those props to build a map of CSS classes to apply to my component. I typically like to define an interface called CssClassMap for this purpose. Let's create that file at src/utils/interfaces.ts, as it's something I'll be reusing for all my components, and use the following content for the file:

export type CssClassMap = { [className: string]: boolean };
Enter fullscreen mode Exit fullscreen mode

As you can see, I'm defining this class map type as a map of a key, the class name, and the value being a boolean. Effectively I'm going to be specifying whether I want classes to be applied or not. Now that we have this interface, let's write a method that builds this class map on render.

export class Button {
  // ...

  render() {
    const classMap = this.getCssClassMap();

    return (
      <button type={this.type} class={classMap} disabled={this.disabled}>
        <slot />
      </button>
    );
  }

  private getCssClassMap(): CssClassMap {
    return {
      [this.color]: true,
      [this.shape]: true,
      [this.size]: true
    };
  }
}
Enter fullscreen mode Exit fullscreen mode

As you can see, I'm calling our function from the render method to get the class map, a fairly simple one in this case, and am applying that to the button. You may also have noticed that I also passed the type prop to the button as well. Now that we have the correct classes being applied, let's actually implement those classes. First we need the correct colors, so let's add a few more SCSS variables to our src/styles/variables.scss file.

//...

// Brand Colors
$blue-steel: #4571c4;
$blue-steel-dark: #315db0;
$coral: #c75943;
$coral-dark: #a83a24;
$light: #f0f1f2;
$light-dark: #e2e3e4;
Enter fullscreen mode Exit fullscreen mode

Now that we have the variables available, let's implement the classes themselves. We don't need to implement the default classes as they are the default that we've already implemented.

button {
  //...

  // Color variations
  &.accent {
    background-color: $coral;

    &:hover {
      background-color: $coral-dark;
    }
    &:active {
      background-color: darken($coral-dark, 10%);
    }
  }
  &.light {
    background-color: $light;
    color: rgba(0, 0, 0, 0.7);

    &:hover {
      background-color: $light-dark;
    }
    &:active {
      background-color: darken($light-dark, 10%);
    }
  }

  // Shape variations
  &.round {
    border-radius: 50px;
  }

  // Size variations
  &.small {
    padding: 2px 8px;
    font-size: 12px;
  }
  &.large {
    padding: 8px 20px;
    font-size: 16px;
  }
}
Enter fullscreen mode Exit fullscreen mode

For the color changing classes, we need to re-implement our hover and active states. We do not need to re-implement our disabled state since our existing one simply applies a lower opacity, looking great in any color. Making the button round is trivial, and then we finally tweak the padding and font-size of our button to look good as a large or small button.

Now that we've implemented these classes, we have some pretty incredible looking button web components!

buttons

Next Steps

We now have an awesome button component we can use anywhere! This is a simple first component, so we're not dealing with many of the other decorators such as @Watch(), @Method(), or @State(). We'll get into those with a new component that is more complex in our next post. See you in the next one!

Simply want to see the end result repo? Check it out here

Discussion (9)

Collapse
fyodorio profile image
Fyodor

Thanks for this great tutorial series, John! Please, continue to write about Stencil!

Isn't there any source code repo for the examples anywhere?

Collapse
johnbwoodruff profile image
John Woodruff Author

Thanks so much! I am working on the 5th installment as I type this. :) It'll be done really soon, I promise!

I've been keeping the repo up to date as I go along and planned to put it on GitHub when I was done. I could put it up earlier if people want and don't mind it being added to as I write the posts.

Collapse
johnbwoodruff profile image
John Woodruff Author

Finally finished post 5! Check it out! :)

Collapse
fyodorio profile image
Fyodor

Great! Thanks!

Thread Thread
johnbwoodruff profile image
John Woodruff Author

@fyodor I know it's been months, but life happened. I finally remembered to put up the source repo. Check it out here

Thread Thread
fyodorio profile image
Fyodor

Thanks! I believe it will be a great helper!

Collapse
jamesramm profile image
James Ramm

Great tutorial! Is part 3 coming? (click handlers? :D )

Collapse
johnbwoodruff profile image
John Woodruff Author

Thanks so much! And yes it is! Life got crazy for a few weeks there, but the next part is coming real soon! :D

Collapse
johnbwoodruff profile image
John Woodruff Author

Took too long, but here's post 5 finally!