Developing Web Components With Svelte Building A Library of Reusable UI Components
Developing Web Components With Svelte Building A Library of Reusable UI Components
Components
with Svelte
Building a Library of Reusable
UI Components
—
Alex Libby
Developing
Web Components
with Svelte
Building a Library of
Reusable UI Components
Alex Libby
Developing Web Components with Svelte: Building a Library of Reusable
UI Components
Alex Libby
Belper, Derbyshire, UK
Acknowledgments�����������������������������������������������������������������������������xiii
Introduction����������������������������������������������������������������������������������������xv
v
Table of Contents
vi
Table of Contents
vii
Table of Contents
viii
Table of Contents
ix
Table of Contents
Index�������������������������������������������������������������������������������������������������337
x
About the Author
Alex Libby is a front-end engineer and seasoned book author who hails
from England. His passion for all things open source dates back to the
days of his degree studies, where he first came across web development
and has been hooked ever since. His daily work involves extensive use
of React, Node.js, JavaScript, HTML, and CSS. Alex enjoys tinkering with
different open source libraries to see how they work. He has spent a stint
maintaining the jQuery Tools library and enjoys writing about open source
technologies, principally for front-end UI development.
xi
Acknowledgments
Writing a book can be a long but rewarding process; it is not possible to
complete it without the help of other people. I would like to offer a huge
vote of thanks to my editors – in particular, Shobana Srinivasan, Rami
Morrar, Gryffin Winkler, and James Robinson-Prior; my thanks also to
Tanner Dolby as my technical reviewer, James Markham for his help
during the process, and others at Apress for getting this book into print. All
have made writing this book a painless and enjoyable process, even with
the edits!
My thanks also to my family for being understanding and supporting
me while writing. I frequently spend a lot of late nights writing alone,
or pass up times when I should be with them, so their words of
encouragement and support have been a real help in getting past those
bumps in the road and producing the finished book that you now hold in
your hands.
Lastly, it is particularly poignant that the book was written at a time
when the world is emerging from events of an unprecedented nature,
where memories are still too raw. It was too easy to think about those
who lost the greatest thing we as humans could ever have; life hasn’t
been easy for anyone. Having a project to work on, no matter how
simple or complex it might be, has helped me get through those tough
times and with the hope that we face a new, improved, and hopefully
better future.
xiii
Introduction
Developing Web Components with Svelte is for people who want to learn
how to quickly create web components that are efficient and fast using the
upcoming Svelte framework and associated tools.
This project-oriented book simplifies the setting up of a Svelte
component library as a starting point before beginning to explore the
benefits of using Svelte to create components not only usable in this
framework but equally reusable in others such as React, Vue, and Angular.
We can use this as a basis for developing an offer that we can customize
to our needs, across multiple frameworks. It will equip you with a starting
toolset that you can use to create future component libraries, incorporate
the processes into your workflow, and that will allow you to take your
components to the next level.
Throughout this book, I’ll take you on a journey through creating the
base library, before adding a variety of components such as a select box,
tabs, and the typical tooltip components. We will also touch on subjects
such as writing documentation, testing components, and deploying into
production – showing you how easy it is to develop simple components
that we can augment later quickly. With the minimum of fuss and plenty of
practical exercises, we’ll focus on topics such as building the functionality,
styling, testing in a self-contained environment, and more – right through
to producing the final result viewable from any browser!
Developing Web Components with Svelte uses nothing more than
standard JavaScript, CSS, and HTML, three of the most powerful tools
available for developers: you can enhance, extend, and configure your
components as requirements dictate. With Svelte, the art of possible is
only limited by the extent of your imagination and the power of JavaScript,
HTML, and Node.js.
xv
CHAPTER 1
Getting Started
Let’s suppose for a moment that you’ve spent any time developing with
frameworks such as React. In that case, I’m sure you will have come across
the principle of creating web components – these self-contained, reusable
packages of code that we can drop into any number of projects, with only
minor tweaks needed to configure the package for use in your project.
Sound familiar?
What if you found yourself creating multiple components and were
beginning to reuse them across multiple projects? We could use them
individually, but that wouldn’t be the most effective way – instead, why not
create a component library?
Creating such a library opens up some real possibilities – we could
build our library around standard components that everyone uses or focus
on a select few that follow a theme, such as forms. At this point, you’re
probably assuming that we’d do something in React, right?
Wrong. Anyone who knows me knows that I like to keep things
simple – while there is nothing technically wrong with React (it’s a great
framework), I want to do something different.
We’re going to build such a component library for this book, but the
framework I’ve elected to use is a relatively new kid on the block – Svelte.
There are many reasons for doing this, but performance is the most
important one – Svelte’s architecture is different from most frameworks,
making it super-fast than many of its rivals. Throughout this book, we’ll
explore how to write web components using Svelte, learn how to bring
them together in a unified library, and explore the steps required to release
them to the world at large with minimal effort.
In time-honored tradition, we must start somewhere – there’s no better
place than to kick off with a look at what we will create through this book,
set some boundaries, and get some of the tools and resources ready for
use. Before we do so (and get anyone up to speed, who hasn’t used web
components), let’s first answer this question.
This definition is just a small part of what they are – in addition, it’s
essential to know that they
Wow – that’s powerful stuff! Gone are the days when we had to use
a React component in a React-based site, or likewise for Angular. Just
imagine: we could build a component in Svelte and then use it in different
frameworks – as long as they are based on JavaScript.
2
Chapter 1 Getting Started
There is one question, though, that I bet some of you are asking: Why
choose Svelte? It’s a valid question, as Svelte is not so well known as other
frameworks such as React.
However, there are three reasons for choosing this framework:
3
Chapter 1 Getting Started
2. Next, go ahead and download the archive file from the code
download that accompanies this book – extract the contents to
a new folder, not your project folder.
3. Once extracted, open the .env file at the root of the folder
you created in step 2, then add your API key from step 1, as
indicated in the file.
4
Chapter 1 Getting Started
6. Once done, enter npm run dev at the prompt, and press Enter
to run the application. We should see a weather component
displayed on the page if all is well, as shown in Figure 1-1.
5
Chapter 1 Getting Started
What makes our demo tick, though, is the code within the src folder –
there are other files and folders present, but we will come back to these
later in the book. The src folder is where we store all of the core component
code – ours has lib and assets folders, as well as vite-env.d.ts and
App.svelte. The lib folder holds the code for each component – in this
example, we have two, Date.svelte and Current.svelte.
Although Svelte comes with two files that act as a starting page for
a Svelte site (main.js, in the \src folder, and the index.html file
at root), it’s only the former we will really need to use. The plan for our
library is to display each component using Markdown files in a Storybook
installation, but to also use the index.html file to demonstrate how we
might reference each component outside of a Svelte environment. Don’t
worry too much about the specifics of how we will do this – we will go
through everything in detail over the course of this book! For now, it’s
important to know where our components will be stored, and that we have
two ways to display them in our environment.
There are other files and folders that we will use throughout this
book – some you will recognize, such as package.json. Others may
not be so familiar; we will go through examples throughout this book.
Okay – let’s move on: now that we’ve created a demo component, it’s time
we got stuck into the star attraction for this book: our component library!
Throughout this book, we’ll create the basis for our component and
then flesh it out with a selection of components. There is plenty we could
choose from – indeed, space constraints mean we can’t add them all! The
key is that we’ll learn how to structure our library, add components, test
them, and generally make sure we have something worthwhile toward the
end of the book.
Let’s start first with the background to this project, so we can set the
scene and understand what’s coming up later in the book.
6
Chapter 1 Getting Started
Background to the Project
So – where do we begin? Let me introduce you to what we will be creating:
the Cobalt UI library.
This UI library will contain a mix of components – all of these you will
find in use on many websites, particularly e-commerce ones! The great
thing about creating a component library is that you can pick and choose
which components to add; if people don’t like one or are not using it, we
can always deprecate and remove it from the library.
Hopefully, that won’t be the case with the ones I’ve chosen – I’ve listed
them in Table 1-1.
7
Chapter 1 Getting Started
Okay – let’s crack on: now that we’ve decided what we will include in
our library, let’s turn our attention to strategy. What approach will we take?
It’s time to decide on some of the tools we will use and our approach for
each component.
8
Chapter 1 Getting Started
Okay – I think that’s enough for now: let’s move on! The next task is
to determine what we need in terms of accounts, tools, and the like. As a
developer, you may already have some of these tools installed; feel free to
use them, or use alternatives if you prefer! That aside, let’s take a look at the
list in more detail.
9
Chapter 1 Getting Started
10
Chapter 1 Getting Started
Setting Up the Project
The first task in building our library is to get Svelte installed – assuming
you have Node.js installed, we can use NPM to download and install the
framework. Let’s look at the steps involved in more detail as part of the
next exercise.
INSTALLING SVELTE
To get the basis for our library set up, follow these steps:
11
Chapter 1 Getting Started
cd cobalt
npm install
npm run dev
12
Chapter 1 Getting Started
Figure 1-3. The initial file listing for our component library
13
Chapter 1 Getting Started
Vite is the bundling tool used by Svelte to package code ready for
deployment – we ran the npm create command to create what is effectively
a Vite site, but we use a template to format it as a Svelte site. It’s worth
noting that as part of running this command, we had to download Vite –
this is a one-off; we won’t be prompted if we create more Svelte sites.
Once the download had been completed, we then changed into
the cobalt folder and ran a typical npm install command to set up
dependencies. With that done, we then fired up the Svelte development
server, before browsing the results in our browser. We still have a long way
to go, but this last step helps confirm we at least have a solid basis in place,
ready for building our project!
Okay – let’s move on to our next task. We will, of course, be building
components throughout this book, but – how are we going to display
them? We need the means to show them off to potential users to see how
they look and assess if they will suit their requirements.
The best way to do this is to use a tool called Storybook – it’s available
for download from https://storybook.js.org/ and works with various
frameworks, including Svelte. Let’s set up an instance as part of our
next demo.
Integrating a Playground
If you’ve spent any time developing code – particularly with frameworks
such as React – you may well have come across Storybook.
For the uninitiated, it’s an excellent tool for showcasing any
components we developers write – the tool supports a wide range of
frameworks, including Svelte. We’ll be using it in our project to showcase
the components we create for our library – let’s dive in and explore how to
set it up as part of our next exercise.
14
Chapter 1 Getting Started
SETTING UP STORYBOOK
If Storybook fails to detect Svelte, choose yes and use the arrow
keys to go down to svelte, then press Enter to select. Storybook will
manually add support for Svelte.
To fix it, we have to make a change – first, change the line "type":
"module", to "type": "commonjs", in package.json.
15
Chapter 1 Getting Started
8. With those updates out of the way, we can now execute npm run
storybook to launch our instance of Storybook. If all is well, we
should see it appear, as shown in the extract in Figure 1-4.
We have one last update to do: delete the ./stories folder. This folder is the
Storybook examples folder, which we don’t need for our project.
16
Chapter 1 Getting Started
17
Chapter 1 Getting Started
Summary
We can see creating components and a library as something of a
rollercoaster – there will be highs and lows, successes, and challenges to
deal with, as we begin to develop what will become our final library. Over
these last few pages, we’ve started to look at our project’s background
and get ourselves ready to create the component library – let’s take
a moment to review what we have learned before beginning the real
development work.
We started with a quick demo of a Svelte component that I had
adapted – this was to get a feel for typical code and how one would run.
We then moved on to discussing the background of our project, before
defining the approach and strategy we would take, along with what we
would need.
We finished by setting up the initial framework ready for use, before
finishing with integrating an instance of Storybook, ready for displaying
our components.
Excellent – we have our initial structure in place, along with confirmed
requirements: it’s time we began the real development! We’ll start with
something simple first: creating the basic components, which we will do in
the next chapter.
18
CHAPTER 2
Creating
Basic Components
With our initial project set up, it’s time to start creating and adding
components!
For this (and the next few chapters), we will build some sample
components ready for inclusion in our library. We could have chosen to
include any one of dozens of different components, but to keep things
simple, I’ve decided to pick three to start with: Input box, Checkbox,
and Slider.
For each component, I’ve made a few assumptions in terms of how we
will develop these components:
Keeping this approach in mind, let’s start with the first addition to our
library, which is creating the Input field component.
1. First, go ahead and create a new folder called lib under the
src folder.
2. Next, crack open a new file in your text editor, then add this
code – there is a good chunk, so we’ll add it section by
section, starting with a Svelte directive to convert it into a web
component:
3. Leave the next line blank, then add this script block – this
sets up some export declarations, along with an onInput event
handler:
<script>
20
Chapter 2 Creating Basic Components
function onInput(event) {
event.target.dispatchEvent(new CustomEvent("oninput",
{ composed: true }));
}
</script>
4. Once added, skip a line, then add in this markup – this will form
the basis of our component:
<div class="cobalt">
{#if label}
<label for={fieldID}>{label}</label>
{/if}
<input type={fieldType}
id={fieldID}
name={inputName}
placeholder={placeholder}
disabled={disabled}
on:input = {onInput}
{...$$props}
>
</div>
5. Miss a line after the closing </div> tag, then add this
styling code:
<style>
.cobalt { display: flex; flex-direction: row;
font-family: Arial, Helvetica, sans-serif;
}
21
Chapter 2 Creating Basic Components
The final task for this exercise was to add the markup that will form
the basis for the Input component (plus the styles we will use for our
component) – we based it on typical markup for a text input field but
adapted it to reference each exported field. There are two exceptions:
on:input and the {...$$props} spread operator.
22
Chapter 2 Creating Basic Components
It’s worth noting that you don’t always need to put the callback for
the on:input; changing on:input={on:input} may also work
just as well. If you use this route here, you should also remove the
export declaration for onInput too. The same principle applies for
other components we create later in the book, such as Checkbox.
We also have the {...$$props} operator – this tells Svelte to pass all
remaining prop values into the component. If you’ve worked with the likes
of React, then you will likely be familiar with {...props} – it works in the
same manner.
Okay – let’s move on: next up, we need to test our component. We will
use the Storybook instance we set up in the previous chapter, and it’s a
perfect way to test the original component and add variants – let’s dive in
and take a look in more detail.
23
Chapter 2 Creating Basic Components
Don’t worry, though – Storybook is still perfectly stable and usable for
our needs: we will use Svelte-formatted files to create the Storybook effect
and Markdown content for documentation. It might seem a little confusing
for now, but bear with me – it will all become apparent in the next exercise.
ADDING TO STORYBOOK
2. Next, go ahead and add this code – we’ll break it into sections,
starting with three import statements:
<Meta
title = "Cobalt UI Library/Basic Components/Input"
component={Input}
parameters={{page: null}}
/>
The parameters entry here hides the default page that shows,
ready for us to add a custom one later in this demo.
24
Chapter 2 Creating Basic Components
<Story name="Default"
args = {{
placeholder: "example text",
label: "Text:",
}}
parameters={{
docs: {
page: InputDocs,
},
}}>
{Template.bind({})}
</Story>
25
Chapter 2 Creating Basic Components
8. Save and close the file. Next, switch to your Node.js terminal
session, then set the working folder to our cobalt project area.
26
Chapter 2 Creating Basic Components
10. Now click on the Docs link at the top of the page, just above
our component – if all is well, we should see an extract of the
documentation appear, as in Figure 2-2.
Excellent – things are starting to take shape now! We now have the first
of many components set up in Storybook: it might be a simple one, but
that doesn’t matter! The critical point here is that we have a sound basis for
building and developing our components.
In the meantime, now would be an excellent opportunity to review the
code changes we have made so far. We’ve already talked about the core
component, but we’ve covered some valuable features in the Storybook
implementation, so let’s take some time to review the code in more detail.
27
Chapter 2 Creating Basic Components
We then have our title formatted by the use of a single # mark, as per
standard Markdown syntax:
# Input
## Input
- [Text](#text)
28
Chapter 2 Creating Basic Components
### Text
<!-- the IDs can be retrieved from the URL when opening a
story -->
<Preview>
<Story id="cobalt-ui-library-basic-components-input--
default" />
</Preview>
The simplest way to get the ID is to take the full URL of the
Storybook page with the instance of our component – in our case,
it was http://localhost:6006/?path=/docs/cobalt-ui-
library-basic-components-input--default. Simply keep
the part highlighted, and drop the rest – you now have the ID.
Adding Variants
It is something you will hear about when creating component libraries
such as ours and which highlights the importance of good planning:
variants. So what are they?
29
Chapter 2 Creating Basic Components
<Story name="Email"
args={{
placeholder: 'email@example.com',
label: 'Email:',
fieldType: 'email',
onInput: () => alert('this is an email field')
}}
parameters={{
docs: {
page: InputDocs
},
}}>
{Template.bind({})} />
30
Chapter 2 Creating Basic Components
Okay – time to move on; let’s turn our attention to creating our next
component: the humble checkbox. It’s a component that features everywhere,
on millions of forms and pages all over the Internet; it’s straightforward to
construct something as a starting point for future development.
2. Next, crack open your text editor, and create a new file called
Checkbox.svelte. Add the following code to this file,
beginning with the <script> block, to import a stylesheet and
define some exported variables:
31
Chapter 2 Creating Basic Components
<script>
export let checked = true;
export let label = "This is a default checkbox";
export let disabled = false;
function onChange(event) {
event.target.dispatchEvent(new
CustomEvent("onchange", { composed: true }));
}
3. We can now add in the markup that will form the basis of our
component – for this, add this code below the <script> block,
missing a line first:
<div class="cobalt">
<input
type="checkbox"
id="name"
{checked}
{disabled}
on:change = {onChange}
{...$$props}
/>
<label for="name">
{label}
</label>
</div>
32
Chapter 2 Creating Basic Components
<style>
.cobalt {
display: flex;
align-items: center;
font-family: Arial, Helvetica, sans-serif;
}
input[type="checkbox"] {
-webkit-appearance: none;
appearance: none;
margin: 0;
font: inherit;
color: currentColor;
width: 18px;
height: 18px;
border: 2px solid currentColor;
border-radius: 2px;
transform: translateY(-1px);
display: grid;
place-content: center;
}
input[type="checkbox"]::before {
content: "";
width: 10px;
height: 10px;
clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100%
16%, 80% 0%, 43% 62%);
transform: scale(0);
transform-origin: bottom left;
transition: 120ms transform ease-in-out;
33
Chapter 2 Creating Basic Components
input[type="checkbox"]:checked::before {
transform: scale(1);
}
input[type="checkbox"]:disabled {
color: #959495;
cursor: not-allowed;
}
label {
margin-left: 5px;
}
</style>
5. Save the file and close it – the component is now in place.
Exploring the Code
The first task was to create a folder for our new component – inside this,
we added Checkbox.svelte, which contains the code for our component.
We added an import for the stylesheet, followed by exports for several
variables, including checked and label, which we make available for
consumption in code, such as in Storybook.
34
Chapter 2 Creating Basic Components
We then added the HTML markup for the component before switching
to extracting a copy of the stylesheet from the code download and adding it
to our component folder.
Although our code uses the same format as the previous component,
there are three things I want to highlight: the order of properties, the use of
on:change, and the use of the {...$$props} spread operator.
I’m a great believer in keeping consistency when it comes to coding –
not only is using a proper naming convention worthwhile but keeping the
same order of values is equally important. It keeps things tidier and makes
it easier to trace issues if you have random values being passed between
components! You will notice that I put the on:change event handler after
the properties and then leave the $$props spread operator until last. It
helps to ensure we collect all prop values in the right order.
Okay – let’s move on: it’s time to test our component using the
Storybook instance we set up in the previous chapter. We’ll use similar
techniques as before, which helps make it quicker to add – let’s dive in and
explore the steps required in more detail.
35
Chapter 2 Creating Basic Components
ADDING VARIATIONS
1. First, crack open a new file in your text editor, then add in this
code – as before, we will go though it in blocks, starting with
the declarations:
2. Next, leave a line blank, then add the title for the page where
we will render our component in Storybook:
<Meta
title="Cobalt UI Library/Basic Components/Checkbox"
component={Checkbox}
parameters={{page: null}}
/>
36
Chapter 2 Creating Basic Components
<Story
name="Default"
args={{
checked: true,
onClick: () => alert('this is a text field')
}}
parameters={{
docs: {
page: CheckboxDocs
}
}}>
{Template.bind({})}
</Story>
Note here that the onChange reference is not the actual event
handler but a reference to the one in our component – we use this
parameter to pass the function through to the actual event handler in
the component.
37
Chapter 2 Creating Basic Components
The code download contains the expanded version if you need any
inspiration!
We’ve now added our second component; we’ve almost finished the
Basic Components section for our library! There is one more we will add
shortly, but before doing so, let’s first break for a moment to review the
code we added in the last demo in more detail.
38
Chapter 2 Creating Basic Components
In the <Meta...> tag object, you will notice that we pass a similar
parameters value, but this time set docs: null. It probably isn’t
necessary, but it is a useful belt-and-braces approach to ensuring we
display the correct documentation in Storybook.
39
Chapter 2 Creating Basic Components
40
Chapter 2 Creating Basic Components
We also have the slugify constant, but this is just to help provide an
ID for each radio button, should we need to test for the contents.
41
Chapter 2 Creating Basic Components
So – as you can see from the code, there are some subtle but important
differences: it means that while it would be nice to use the same format as
in previous components, it’s not always possible!
42
Chapter 2 Creating Basic Components
Okay – let’s crack on: for this chapter’s third and final component,
we will explore creating a Slider component. It’s not one you’re likely to
see as often as the others, particularly on e-commerce sites, but it is still
an equally important tool to have in the toolbox. Let’s dive in and take a
closer look at how we might set up such a component.
To build the final component for this chapter, follow these steps:
2. Next, crack open your text editor, then add this code – we’ll do
this in blocks, starting with importing the stylesheet and setting
some exported declarations:
<script>
export let id = undefined;
export let min = 0;
export let max = 100;
43
Chapter 2 Creating Basic Components
<div class="cobalt">
<input
type="range"
id="{id}" {min} {max} {step}
name="{id}"
bind:value={val}
disabled = {disabled}
/>
<label for="{id}">{val}</label>
</div>
<style>
.cobalt {
display: flex;
flex-direction: row;
font-family: Arial, Helvetica, sans-serif;
}
input[type="range"] {
-webkit-appearance: none;
width: 160px;
height: 20px;
margin: 10px 50px;
44
Chapter 2 Creating Basic Components
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
height: 20px;
width: 20px;
border-radius: 50%;
cursor: ew-resize;
box-shadow: 0 0 2px 0 #555;
transition: background 0.3s ease-in-out;
background: #6666ff;
position: relative;
z-index: 3;
box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.3);
}
input[type="range"]::-moz-range-thumb {
-webkit-appearance: none;
height: 20px;
width: 20px;
border-radius: 50%;
background: #6666ff;
cursor: ew-resize;
box-shadow: 0 0 2px 0 #555;
transition: background 0.3s ease-in-out;
}
45
Chapter 2 Creating Basic Components
input[type="range"]::-ms-thumb {
-webkit-appearance: none;
height: 20px;
width: 20px;
border-radius: 50%;
background: #6666ff;
cursor: ew-resize;
box-shadow: 0 0 2px 0 #555;
transition: background 0.3s ease-in-out;
}
input[type="range"]::-webkit-slider-thumb:hover,
input[type="range"]::-moz-range-thumb:hover,
input[type="range"]::-ms-thumb:hover {
background: #9393ff;
}
input[type="range"]::-moz-range-track {
-webkit-appearance: none;
box-shadow: none;
border: none;
background: transparent;
}
46
Chapter 2 Creating Basic Components
input[type="range"]::-ms-track {
-webkit-appearance: none;
box-shadow: none;
border: none;
background: transparent;
}
</style>
With the component now in place, we can now add the component and
documentation to Storybook:
47
Chapter 2 Creating Basic Components
2. Next, go ahead and add this code – we’ll break it down section
by section, beginning with the relevant imports:
3. We now need to add a title for our Storybook page – add this
Meta tag in, first leaving a blank line:
<Meta
title="Cobalt UI Library/Basic Components/Slider"
component={Slider}
parameters={{page: null}}
/>
5. With the template in place, we can now add in the Story code to
render our new Slider component:
<Story name="Default"
args={{
val: 1,
min: 0,
max: 100,
step: 10
}}
48
Chapter 2 Creating Basic Components
parameters={{
docs: {
page: SliderDocs
},
}}>
{Template.bind({})}
</Story>
6. Let’s add a variant – take a copy of the code from step 5, then
miss a line and paste it into the file. Change the Story name to
“Disabled,” then add disabled: true below the line step:
10 (and before the closing brackets).
49
Chapter 2 Creating Basic Components
Great – we’ve created the first set of components for our library! Things
are shaping up well; we have a solid basis for developing the code at a
later date. In the next chapter, we will focus on adding the next batch of
components, but for now, let’s round out this chapter with a final look at
the changes made in the last exercise.
Exploring the Code
Adding components to our Storybook instance should be a little more
familiar now – the key to it is preparing the code for the first, which we can
reuse in subsequent components.
Keeping that thought in mind, we started by creating the Slider.
stories.mdx file for Storybook, into which we first added some imports
(component, documentation, and some features required from
Storybook). We then added a title using the <Meta.../> tag, into which
we told it how to set up the navigation in Storybook and that we would be
using the Slider component. At the same time, we also set the page value to
null to hide the default documentation page generated by Storybook.
50
Chapter 2 Creating Basic Components
Next up, we then created a template – something we built for the first
component and which you will see added for all future components. We
then set up our initial <Story..> block, which we labeled Default. Usually,
we would use this to refer to a component out of the box, with no changes –
this isn’t possible here, though, as we need to provide some values:
something to bear in mind!
We then switched to creating a variant – we talked about how this
should be straightforward, given our desire to use consistent code, and
that this should make adding variants easier. We then rounded out the
demo by adding a prepared SliderDocs.mdx documentation file before
firing up Storybook and previewing the results in a browser.
Summary
In Chapter 1, I mentioned that creating components and a library can be a
rollercoaster. As we develop what will become our final library, there will
be highs and lows, successes, and challenges to overcome. Over these last
few pages, we’ve started that journey to add in our component – let’s take
a moment to review what we learned in this chapter.
The focus throughout this chapter was creating the code for each
component – we started with constructing the code for a typical Input field
before hooking it into our Storybook instance and adding in some variants
to showcase how we can make our component more useful.
We then moved to create our second component, the Checkbox: this
followed essentially the same format, but we also touched on how we
might adapt the code to create a RadioButton component. As both share
similar properties, one might forgive us for thinking it should be easy, but a
closer inspection revealed this is not the case!
The third and final component we covered for this chapter was the
Slider – we worked through creating the core component. Adding it
to Storybook was more straightforward, though, as this is one of those
51
Chapter 2 Creating Basic Components
52
CHAPTER 3
Building
Action Components
Lights, camera, action…
Okay – we’re not about to create the next movie blockbuster! Instead,
it’s the turn of the next batch of components we will be building, which all
have some form of action (if you pardon the pun).
In the previous chapter, we started by creating some simple
components based on standard HTML5 elements, but which we could
refine into more complex versions as the library grows more mature over
time. Our next batch of components are a little more involved and show
a moving part in (most) respects – hence the reference to the title of this
chapter!
Over the following few pages, we will, in turn, create SelectBox,
Spinner, and Accordion components – let’s begin with the SelectBox.
2. Next, crack open a new file and add this code – we’ll start
with adding a tag to turn our code into a web component and
creating a few variables for export:
<script>
export let options = [];
export let displayText = a => a.text;
export let index = 0;
3. Miss a line, then add in this little function and the closing
script tag:
function onChange(event) {
event.target.dispatchEvent(new
CustomEvent("onchange", { composed: true }));
}
</script>
4. We can now add the markup for our component – much of this
standard HTML markup, but it does include some Svelte tags:
<div class="cobalt">
<select bind:value={index} {disabled}
on:change={onChange}>
{#each options as option, i}
<option value={i}>{displayText(option)}</option>
54
Chapter 3 Building Action Components
{/each}
</select>
</div>
5. Next, we need to add some styling. Leave a line blank, then add
this code:
<style>
.cobalt {
display: flex;
}
select {
padding: 5px 100px 5px 5px; /* 100px required to make
sure image displays */
font-size: 16px;
border: 1px solid #19247c;
height: 34px;
border-radius: 10px;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
background: url("./icon.png") 96% / 15% no-repeat
#9393ff;
}
</style>
55
Chapter 3 Building Action Components
We now have our component in place, although you will notice that
we’ve not yet tested it – we will do that once we hook the component into
our Storybook instance. For now, let’s take some time out to review the
code in more detail – there are some exciting features present, which are
helpful to know!
56
Chapter 3 Building Action Components
1. First, go ahead and create a new file, then add this code – as
before, we have a reasonable chunk to add. Let’s start with
the initial <script> block to import the component and
documentation, along with some functions from Storybook:
import SelectBox from "./SelectBox.svelte";
import SelectBoxDocs from "./SelectBoxDocs.mdx";
import { Meta, Story } from '@storybook/addon-docs';
2. Next, leave a line blank, then add this <Meta> tag – it adds a
title, sets the component we want to use, and blocks the default
documentation page from being displayed:
<Meta
title = "Cobalt UI Library/Action Components/SelectBox"
component={SelectBox}
parameters={{ page: null }}
/>
57
Chapter 3 Building Action Components
4. We can now render our component – for this, we will use the
<Story> tag. Go ahead and add this block:
<Story name="Default"
args={{
options: [{"text":"aaa"},{"text":"bbb"},{"text":"
ccc"}],
}}
parameters={{
docs: {
page: SelectBoxDocs
},
}}
/>
5. Let’s also add a second story – this one will disable the
component:
<Story name="Disabled"
args={{
options: [{"text":"aaa"},{"text":"bbb"},{"text":"
ccc"}],
disabled: true
}}
58
Chapter 3 Building Action Components
parameters={{
docs: {
page: SelectBoxDocs
},
}}
/>
7. You will see from the code that we’ve specified a file as our
documentation but haven’t yet added it. We need to extract a
copy of SelectBoxDocs.mdx from the code download, then
drop it into the SelectBox folder.
59
Chapter 3 Building Action Components
60
Chapter 3 Building Action Components
61
Chapter 3 Building Action Components
Right, let’s move on – we’re making great progress, with our second
component now in place and working. It’s for us to look at the next one
in this bunch. It’s one where we could get into a spin if we’re not careful
(oops – sorry about the pun!). Yes, our next one is a spinner – essential if
you need to render a lot of data on the page that might take a while to load.
62
Chapter 3 Building Action Components
<div class="cobalt">
<div
class="circle"
style="--size: {size}px; --color: {color};
--duration: {duration}" />
</div>
<style>
.circle {
height: var(--size);
width: var(--size);
border-color: var(--color) transparent var(--color)
var(--color);
border-width: calc(var(--size) / 12);
border-style: solid;
border-radius: 50%;
animation: var(--duration) linear 0s infinite normal
none running rotateCircle;
}
@keyframes rotateCircle {
0% {
transform: rotate(0);
}
63
Chapter 3 Building Action Components
100% {
transform: rotate(360deg);
}
}
</style>
64
Chapter 3 Building Action Components
1. First, go ahead and create a new file, then add this code –
as before, we have a reasonable chunk to add. Let’s start
with the initial import block to import the component and
documentation, along with some functions from Storybook:
<Meta
title="Cobalt UI Library/Action Components/Spinner"
65
Chapter 3 Building Action Components
component={Spinner}
parameters={{page: null}}
/>
<Story name="Default"
args={{
color: "#19247c",
duration: "0.75s",
size: "40"
}}
parameters={{
docs: { page: SpinnerDocs },
}}
/>
6. You will see from the code that we’ve specified a file as our
documentation but haven’t yet added it. We need to extract a
copy of SpinnerDocs.mdx from the code download and then
drop it into the \src\lib\storybook folder.
66
Chapter 3 Building Action Components
67
Chapter 3 Building Action Components
demo in more detail. Much of what we added will start to look familiar
(remember that point earlier about reusability!), but it’s still worth looking
to recap what we added in the demo.
Creating Variants
We set up the Spinner component to operate in Storybook in that last
demo. The process should be relatively familiar, as we’ve tried to keep
it similar for all components. However, remember how I stated that if
we added a variant for Spinner, it would likely be very different from
something added for SelectBox?
68
Chapter 3 Building Action Components
I’ve titled this next exercise slightly differently than the others, but with good
reason. Although we will be creating a variant, it looks so different from the
original that it could equally be a separate component in its own right! That
aside, here’s what we need to do to add that new variant to our demo:
4. Next, we need to add the CSS for our variant – go ahead and
add this below the rotate block:
.jumper {
height: var(--size);
width: var(--size);
border-radius: 100%;
animation-fill-mode: both;
position: absolute;
69
Chapter 3 Building Action Components
opacity: 0;
background-color: var(--color);
animation: bounce var(--duration) linear infinite;
}
@keyframes bounce {
0% { opacity: 0; transform: scale(0); }
5% { opacity: 1; }
100% { opacity: 0; transform: scale(1); }
}
7. Leave a line blank after that closing {/if} tag, then add this
code for our variant:
70
Chapter 3 Building Action Components
Note This should be before the closing </div> tag at the end of
the page!
8. Save and close the file. Fortunately, the changes required for
Storybook are not so complex! For this, crack open Spinner.
stories.mdx, then scroll to the bottom of the page and add
this block:
71
Chapter 3 Building Action Components
10. At the prompt, enter npm run storybook and hit Enter – if
all is well, we should see Storybook launch and display in our
browser at http://localhost:6006/.
11. Click on the Jumper link under Spinner on the left to display
the variant we just created, as shown in the screenshot in
Figure 3-3.
Wow – our Spinner looks different now! This is the beauty of this
component: even though the core markup is largely the same, varying the
properties we pass in can render something completely different.
72
Chapter 3 Building Action Components
73
Chapter 3 Building Action Components
74
Chapter 3 Building Action Components
<div class="cobalt-accordion">
{#each data as entry}
<AccordionItem title={entry.title} entry={entry.
text} />
{/each}
</div>
4. Next, miss a line, then add this block – it will provide some
basic styling for our Accordion container:
<style>
.cobalt-accordion {
display: flex;
flex-direction: column;
width: 500px;
}
</style>
75
Chapter 3 Building Action Components
7. Last but by no means least, we need to add the markup for our
component – this first block defines the button used to open
and close each list item:
{#if isOpen}
<ul transition:slide={{ duration: 300 }}>
{#each entry[1] as item}
<li>{item}</li>
{/each}
</ul>
{/if}
76
Chapter 3 Building Action Components
9. We need to add some styling – for this, miss a line after the
closing {/if}, then add this code:
<style>
svg { transition: transform 0.2s ease-in; }
[aria-expanded="true"] svg {
transform: rotate(0.25turn);
}
button[aria-expanded="false"].accordionItem
{ margin-bottom: 2px; }
button.accordionItem:hover { background-color:
#19247c; }
77
Chapter 3 Building Action Components
<script>
import AccordionItem from './AccordionItem.svelte'
export let data = []
</script>
It’s worth noting that we could change the format of the data presented
in our Accordion – for example, we could replace the data export shown
previously with this:
This would allow us to import data from an external JSON file – why is
this significant? Well, the answer lies in how we can pass data to a Svelte
web component – it can only be in string format, so passing boolean
values, for example, isn’t allowed!
Moving on, we then set up the markup for each item within the
Accordion. We iterate through the data block using a Svelte #each
function while at the same time destructuring each item as an instance of
entry. This we pass into the AccordionItem component as a value for the
title prop.
78
Chapter 3 Building Action Components
1. First, go ahead and create a new file in the same way as we’ve
done before, then add this code – we’ll start with the initial
<script> block to import the component and documentation,
along with some functions from Storybook:
79
Chapter 3 Building Action Components
<Meta
title="Cobalt UI Library/Action Components/Accordion"
component={Accordion}
parameters={{page: null}}
/>
4. We can now render our component – for this, we will use the
same <Story> tag as before and into it pass the properties
required to configure our Accordion component. Go ahead and
add this block:
<Story name="Default"
args={{
open: false,
data: [
{
"title": "Heading 1",
80
Chapter 3 Building Action Components
}}
parameters={{
docs: { page: AccordionDocs },
}}
/>
81
Chapter 3 Building Action Components
We’re starting to cook now, to coin that phrase! We’ve created the code
for all three components for this chapter and added them to our Storybook
instance. Before moving on to the next chapter and exploring our next
batch of features, let’s review the changes made in the last demo to see
how our Accordion component hooks into Storybook.
Reviewing the Code
Adding an Accordion component to Storybook should now be a relatively
familiar process – as before, we start by creating our Storybook page and
importing the feature, documentation, and some functions from Storybook
to help support the documentation.
We then added a <Meta> tag with properties to display the component
page in the correct order. In the same way, as we did for SelectBox,
82
Chapter 3 Building Action Components
we set Cobalt UI Library for the top title, with Action Components
as the subtitle for our group and Accordion as the name for our group
component’s page.
We then moved on to the critical part – our template; here, we added
a Story block, into which we passed the open and data properties to
control when the Accordion is open and the data to display. At the same
time, we also set the page value to AccordionDocs to display our custom
documentation page. As the final few steps, we saved and closed all files
before extracting a copy of the AccordionDocs.mdx documentation and
launching Storybook to preview the results in our browser.
Summary
“And that’s a wrap…!”
Yes, indeed – we’ve added all three Action components to our library; each
has its respective page in our Storybook instance. It means we’ve reached
the halfway point in constructing features for our library, with only two
more categories to add later in the book. Before we get on building the
next category of components, let’s take a moment to review what we have
learned in this chapter.
As we saw back in the previous chapter, the focus is on adding each
component to our library and setting it up in Storybook. We started with
the SelectBox component before swiftly moving on to creating the Spinner
component. It was a little more involved as we explored adding a new
variant – we learned that even though we use the same markup, changes in
styling effectively meant we had the equivalent of a new component.
We explored setting up an Accordion component for the third and
final component in this chapter. It was a little more complex, as we had to
create two components: the main Accordion as the parent container and
AccordionItem for displaying each item in the Accordion component.
83
Chapter 3 Building Action Components
Okay, let’s crack with creating the next batch: next up is our
navigation-based component group. We’ll look at creating components
such as a navigation bar and buttons, a menu and tabs, and more –
intrigued? Stay with me, and I promise to navigate you through all in the
next chapter if you pardon that terrible pun!
84
CHAPTER 4
Building
the Navigation
Components
“I may not have gone where I intended to go, but I think I have
ended up where I needed to be.”
That quote by the author Douglas Adams, from his 1988 detective novel,
The Long Dark Tea-Time of the Soul, may have had a somewhat humorous
edge, but I think it’s an apt phrase to describe the theme for this chapter –
they are all about navigation.
Good navigation is essential for any site – we can produce all manner
of different components (such as the ones we’ve built so far) for different
pages, but if we can’t navigate to them, we might as well pack up and
go home!
Over the following few pages, we will build three components – a
set of Tabs, a Breadcrumb trail, and a SideBar menu. We will use similar
methods throughout to help keep consistency and make it easier to
develop; let’s start with the Breadcrumb component.
For the custom image, I’ve used two SVG icons from the Ionic library
at https://ionic.io/ionicons; they are arrow-forward-
outline.svg and chevron-forward-outline.svg. Both are in
the code download; feel free to use alternatives if you prefer, but you
will need to adjust the code to suit.
86
Chapter 4 Building the Navigation Components
2. Next, crack open a new file and add this code – we’ll start with
importing the stylesheet, creating a few variables for export,
and adding some default data from a JSON file:
3. We can now add the markup for our component – much of this
standard HTML markup, but it does include some Svelte tags.
The first takes care of checking to see if we display a custom
image or plain text as a divider:
<div class="cobalt">
<ul class="breadcrumb">
{#each breadcrumbItems as breadcrumbItem, i}
<li>
<!-- Breadcrumb divider -->
{#if i !== 0}
{#if !image}
<span>{divider}</span>
{/if}
{#if image}
87
Chapter 4 Building the Navigation Components
5. Next, miss a line after the closing </div>, and add these
style rules:
<style>
.cobalt { display: flex; font-family: Arial, Helvetica,
sans-serif; }
ul.breadcrumb li a:hover {
88
Chapter 4 Building the Navigation Components
color: #9393ff;
text-decoration: underline;
}
ul.breadcrumb li span {
display: inline;
padding: 8px;
}
</style>
Excellent – we have our component in place, along with the two icons
we need to test the custom image option of our component. The next task
is to try it to make sure it works; as before, we’ll work through adding it
to our Storybook instance. Before we get to that, let’s take a moment to
review the code changes made – most of it should be self-explanatory, but
some interesting Svelte techniques within the code are worth exploring in
more detail.
89
Chapter 4 Building the Navigation Components
90
Chapter 4 Building the Navigation Components
ADDING TO STORYBOOK
1. First, go ahead and create a new file, then add this code – as
before, we have a reasonable chunk to add. Let’s start with
the initial declaration block to import the component and
documentation, along with some functions from Storybook:
</script>
<Meta
title="Cobalt UI Library/Navigation Components/
Breadcrumbs"
component={Breadcrumbs}
parameters={{page: null}}
/>
<Template let:args>
<Breadcrumbs {...args} />
</Template>
91
Chapter 4 Building the Navigation Components
4. We can now render our component – for this, we will use the
<Story> tag. Go ahead and add this block:
<Story
name="Default"
args={{
image: false,
image: false,
breadcrumbItems = [
{ href: "/", text: "Dashboard" },
{ href: "/reports", text: "Annual reports" },
{ href: "/reports/2019", text: "2019" },
]; }}
parameters={{
docs: {
page: BreadcrumbsDocs
},
}}
/>
5. Let’s also add a second story – this one will show a custom
image instead of a text-based character as our divider:
<Story
name="Custom image"
args={{
image: true,
breadcrumbItems = [
{ href: "/", text: "Dashboard" },
{ href: "/reports", text: "Annual reports" },
{ href: "/reports/2019", text: "2019" },
];
}}
parameters={{
docs: {
92
Chapter 4 Building the Navigation Components
page: BreadcrumbsDocs
},
}}
/>
93
Chapter 4 Building the Navigation Components
10. Click on the Custom image link on the left – this variant swaps
out the double slash and replaces it with a custom image, as
shown in Figure 4-2.
You will note we added two images when creating the initial
component – try swapping the images over in the code. You may
need to adjust the styling a little, but the critical point here is that we
can use any SVG icon as our divider – we could even modify the code
to pass in an image name too!
94
Chapter 4 Building the Navigation Components
95
Chapter 4 Building the Navigation Components
for desktops? With mobile usage exploding, it is ever more critical that we
can create a usable navigation for this platform. There is no better way to
do it than using a hamburger menu, such as our next component.
To create one, we will have to set up not one but two components:
a hamburger icon and the sidebar itself. We’ll do this over a two-part
exercise, with breaks in between – let’s crack on with part 1, which is to
create the hamburger component.
To construct the hamburger icon and menu button, follow these steps:
2. For this component, crack open a new file and add this code –
we need to add two variables for export, along with the
svelte:options directive and a check to determine if the
sidebar is open or closed:
3. Next, miss a line, then add this markup for our button:
96
Chapter 4 Building the Navigation Components
</svg>
</button>
4. With the button in place, all we need to add for this component
is some styling – go ahead and drop these rules in the file,
missing a line first:
<style>
.open #top {
transform: translate(6px, 0px) rotate(45deg)
}
97
Chapter 4 Building the Navigation Components
That’s component number one down, one left to go! We now have
the hamburger button set up, so the only component left to create is our
sidebar.
One of the great things about Svelte is that it doesn’t try to overload
HTML markup with a lot of extra cruft – this makes creating components
such as our SideBar very clean! To see what I mean, let’s look at what’s
involved in the next part of this exercise.
To construct our final part of this component, the sidebar, follow these steps:
1. For this component, crack open a new file and add this code –
unlike other components, we only need to add an open variable
for export:
2. Next up, miss a line, then add this markup for our component –
this will render the menu in the sidebar, when opened, along
with the contents of any HTML markup provided when calling
the component:
98
Chapter 4 Building the Navigation Components
{#if sidebar_show}
<nav transition:fly={{ x: 250, opacity: 1 }}>
<slot />
</nav>
{/if}
<style>
nav {
position: fixed;
top: 0;
right: 0;
height: 100%;
padding: 32px 16px 9.6px;
border-left: 1px solid #aaa;
background: #fff;
overflow-y: auto;
width: 160px;
box-shadow: 2px 3px 3px 3px #000000;
}
</style>
That might have seemed like a lengthy exercise (even if it was over two
parts), but we now have our component in place! It looks like a lot of code,
and indeed, much of it is standard HTML markup and CSS styling. We do
use a few Svelte features within – let’s take a closer look at the code we
used to understand how it all hangs together in our demo.
99
Chapter 4 Building the Navigation Components
100
Chapter 4 Building the Navigation Components
<svelte:head>
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.
min.css" rel="stylesheet"/>
</svelte:head>
101
Chapter 4 Building the Navigation Components
the format we’ve used so far, but “there is method in the madness,” as I’m
sure someone once said. Building it this way does raise a few interesting
questions, which we will explore later, but for now – let’s crack on with
setting up the demo for our SideBar component.
We will construct this demo using the following files – these should be present
already, but as a precaution, make sure you have these two files before
continuing with this demo:
\index.html
\src\main.js
2. Save the file and close it. Next, open the index.html file
at the root of the project folder; we need to make a series of
changes to this file. First, go ahead and change the content for
the <title> tag:
3. Next, add this style block immediately below the <title> tags:
<style>
body { margin: 0; }
102
Chapter 4 Building the Navigation Components
<div id="hamburger">
<cobalt-sidebar show="false">
<p>About</p>
<p>Work</p>
<p>Blog</p>
<p>Contact</p>
</cobalt-sidebar>
</div>
<main>
<h1>Testing Page:</h1>
<p>This page is to test calling each web component
outside of a Svelte / Storybook environment</p>
</main>
103
Chapter 4 Building the Navigation Components
I’ve shortened the dummy text displayed here for brevity – the full
version is available in the code download for this book.
6. Make sure you have this line present below the dummy text
block – it should already be in the code:
8. At the prompt, enter npm run dev and hit Enter – if all is well,
we should see Storybook launch and display in our browser at
http://localhost:5173/. Click on the button at the top left in
the blue menu to open the menu (Figure 4-3) – the styling we’ve
used is to provide a more authentic look and feel. I’ve added some
Lorem Ipsum dummy text for a more authentic look and feel.
104
Chapter 4 Building the Navigation Components
That might have seemed like a lot of work, but we now have a working
component on display. There is one thing, though – while the code
should be reasonably familiar by now, it does raise a couple of interesting
questions around how we implemented it, particularly with the call to the
second component in our template.
Before we crack on with the third and final component for this chapter,
let’s take some time to review the code changes and understand how it all
works in more detail.
105
Chapter 4 Building the Navigation Components
106
Chapter 4 Building the Navigation Components
2. Next, crack open a new file and add this code – unlike other
components, we need to add a little more code, plus a new
function, onMount, and a click handler:
<script>
import tabItems from "./tabsdata.json";
export let activeTabValue = "0";
export let items;
export let vertical = false;
107
Chapter 4 Building the Navigation Components
3. Next up, miss a line, then add the markup to render our Tabs
component:
<span on:click={handleClick(id)}>
{JSON.stringify(items[id].name).replace(/
['"]+/g, "")}
</span>
</li>
{/each}
</ul>
<div class="content">
{#each Object.entries(items) as [id, text]}
{#if activeTabValue === id}
{JSON.stringify(items[id].text).replace(/
['"]+/g, "")}
{/if}
{/each} </div>
</div>
ul {
108
Chapter 4 Building the Navigation Components
display: flex;
flex-wrap: wrap;
padding-left: 0;
margin-bottom: 0;
list-style: none;
border-bottom: 1px solid #dee2e6;
}
.content {
padding: 10px; }
span {
border: 1px solid transparent;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
display: block;
padding: 8px 16px;
cursor: pointer;
}
span:hover {
border-color: #e9ecef #e9ecef #dee2e6;
background-color: #9393ff;
}
Great – that’s the first part done! We now have a Tabs component ready
to pull into Storybook and test that it works as expected. This next stage
should be relatively familiar by now, so let’s crack on with adding the new
component to Storybook without further ado.
109
Chapter 4 Building the Navigation Components
110
Chapter 4 Building the Navigation Components
Accessibility – A Note
So far, we’ve developed a good set of components, but there is one thing
we’ve not covered: accessibility.
Accessibility is of course an essential part of any component – with one
in five people living with a disability, illness, or long-term impairment, we
risk excluding up to 20% of the population if they can’t use a site due to
inaccessible components!
It does raise some important questions about how far we go – we
can’t cater for every impairment, so where do we draw the line? Making
component accessible is a big task; not only do we need to add the right
tags, but we also need to decide to what level we strive to attain.
It’s part of the reason why I’ve not included accessibility for now;
I felt it more important to get core functionality working and then add
accessibility later. Some might argue that this isn’t the right approach, but I
think it’s important to make sure any component you develop functions as
expected, before fine-tuning it and making it more accessible.
111
Chapter 4 Building the Navigation Components
1. First, go ahead and create a new file, then add this code – as
before, we have a reasonable chunk to add. We’ll start with
adding the initial component imports, some functions from
Storybook, and link in placeholder documentation:
<Meta
title = "Cobalt UI Library/Navigation Components/Tabs"
component={Tabs}
parameters={{ page: null }}
/>
112
Chapter 4 Building the Navigation Components
4. We can now render our component – for this, we will use the
<Story> tag. Go ahead and add this block:
<Story
name="Default"
args={{
vertical: false,
items: [
{ "id": 1, "name": "Tab 1", "text": "This is
a test"},
{ "id": 2, "name": "Tab 2", "text": "Here is
tab 2"},
{ "id": 3, "name": "Tab 3", "text": "And this is
tab 3"},
],
}}
parameters={{
docs: {
page: TabsDocs,
},
}}
/>
6. You will see from the code that we’ve specified a file as our
documentation but haven’t yet added it. We need to extract a
copy of TabsDocs.mdx from the code download and then drop
it into the Tabs folder.
113
Chapter 4 Building the Navigation Components
There – that doesn’t look too shabby, does it? Granted, it doesn’t have
all of the features other Tabs components may have, but that will come in
time; we’ve created a solid base for further development.
That isn’t the end of it, though – there is scope to add a variation of our
component, which we will do momentarily. Before we do so, let’s pause
for a moment to review the code changes we just made to see how the Tabs
component renders in Storybook in more detail.
114
Chapter 4 Building the Navigation Components
Creating a Variant
For the last demo in this chapter, we’re going to modify how our Tabs
component looks – in many cases, we would display the component
horizontally, but there may be occasions where displaying the tab “heads”
on the side would be a preferred option.
Fortunately, the changes needed to implement our new variant are
pretty straightforward; let’s crack on and implement them so we can see
how our new variant appears in Storybook.
CREATING A VARIANT
1. First, crack open the Tabs.svelte file, then add this line
immediately below the last export:
115
Chapter 4 Building the Navigation Components
2. Next, scroll down to the opening <div> tag for the markup;
amend the code as highlighted:
<Story name="Default"
args={{
vertical: false
items: [
{ "id": 1, "name": "Tab 1", "text":
"This is a test"},
{ "id": 2, "name": "Tab 2", "text":
"Here is tab 2"},
{ "id": 3, "name": "Tab 3", "text":
"And this is tab 3"},
],
}}
5. Leave a line blank after that story, then add this new story code:
<Story
name="Vertical"
args={{
vertical: true,
items: [
{ "id": 1, "name": "Tab 1", "text": "This is
a test"},
{ "id": 2, "name": "Tab 2", "text": "Here is
tab 2"},
116
Chapter 4 Building the Navigation Components
6. Save the file and close it. Crack open the tabs.css file, then
scroll to the bottom – go ahead and add these style rules:
/* variant */
div.cobalt.vertical { display: flex; }
div.cobalt.vertical ul {
flex-direction: column;
border-bottom: none;
align-self: baseline;
margin-top: 0;
}
div.cobalt.vertical div.content { width: 300px;
height: 200px; }
7. We have everything in place, so let’s test it! Save and close the
file, then switch to a Node.js terminal session, and make sure
the working folder points to our cobalt project area.
117
Chapter 4 Building the Navigation Components
Perfect – with only a few minor changes (and of which most were CSS
based), we have a new variant for our Tabs component! Sure, this is only
one variant, and with a bit of work, we could add more variants (such
as different tabs, language support, and so on). It does show that with
minimal changes, we can turn an existing component into something
different and usable by developers consuming our library.
So – how did we get here? We started by adding an exported variable
vertical – this would be the trigger to tell the component to display our
tab set horizontally or vertically. We then updated the opening <div> tag
118
Chapter 4 Building the Navigation Components
for our markup to use the class: directive; similar to before, this tells the
component to include the vertical class if our variable vertical is true.
Next up, we then added a new markup block for our variant – into this,
we pass the vertical variable, which is set to true. We then added a handful
of styles to re-render the Tabs in a vertical format. That’s one of the
great things about Svelte – most of the work is done using CSS, with only
minimal markup required to refactor our component!
Summary
“And that’s the end of this journey, ladies and gentlemen. I
hope you’ve enjoyed what you’ve seen…”
Creating excellent navigation for a site is essential – it’s the bread and butter
we need to help customers find what they want and keep them within
the confines of our site. To help with that, we’ve created three Navigation
components for our library; each has its respective page in our Storybook
instance. We’re now over halfway, with only one more component category to
add to our library! Before we get on building the next category of components,
let’s take a moment to review what we have learned in this chapter.
As we saw back in the previous chapter, the focus is on adding each
component to our library and setting it up in Storybook. We started with
creating the Breadcrumbs trail component before swiftly moving on to
building the more complex SideBar component.
We explored setting up a Tabs component for the third and final tool
in our toolbox. It was a little more involved as we examined adding a new
variant – we learned that even though we use the same markup, changes in
styling effectively meant we had the equivalent of a new component.
Okay, let’s crack with creating the penultimate batch of components:
the notification group. We’ll look at creating components such as an
overlay, modal dialog boxes, and more – intrigued? Stay with me, and I’ll
reveal it all in the next chapter.
119
CHAPTER 5
Creating Notification
Components
I spend many an hour reading and researching for the books I’ve written –
I’ve come across all manner of different articles, views, and ideas; too
many to count! There was, however, one thing that I found that I think is
very apt for this chapter:
“You can be happy with less and miserable with more.”
This little gem, from the author and entrepreneur Robert Gill, is
perfect for the following few pages – particularly when I say we’re going
to look at creating notification components! One hopes that we never get
any indicating an error of some kind; indeed, the less we get, the more
we’re happy!
Keeping that thought, for now, we’re going to work our way through
creating three more components – an Alert, Dialog, and Tooltip. Much of
what you are about to see will reuse many of the principles we’ve already
covered, so without further ado, let’s crack on with creating the first, which
is the Alert component.
Sourcing the Icons
Our Alert component will use a couple of SVG icons from the Ionicons
library at https://ionic.io/ionicons, which we used back in Chapter 4.
I’ve picked two, and edited versions of them for the exercise; these will be
available in the code download, along with the (renamed) originals.
If you want to use different ones, browse to the link and enter “alert”
or “warn” in the search box. It will come back with at least two options – to
download the SVGs, click on one of the icons, then hit the SVG icon to the
right of the brown box that appears at the foot of the screen in Figure 5-1.
122
Chapter 5 Creating Notification Components
You will need to update the SVG markup used in one of the files in the
next exercise – I will point out which one, at the appropriate point. Okay –
with that in mind, let’s begin with the next exercise to construct our Alert
component.
Building the Component
Although we’re building what should be a simple Alert component, the
code we need to use is a little more complex than some of our other
components! We will have to create a few files and add the SVGs we talked
about just now – we’ll start with creating the core component first.
2. Next, crack open a new file and add this code – we’ll start
with importing an Icon component and setting a few variables
for export:
<script>
123
Chapter 5 Creating Notification Components
3. Next, leave a line blank, then add the second part of our
script block:
switch (type) {
case "warn":
typeClass = "alert-warn";
break;
case "dark":
typeClass = "alert-dark";
break;
case "error":
typeClass = "alert-error";
break;
case "info":
typeClass = "alert-info";
break;
case "success":
typeClass = "alert-success";
break;
default:
typeClass = "";
}
const classes = ["alert", typeClass, showAnimation ?
"fade-in" : ""]
.filter((klass) => klass.length)
.join(" ");
124
Chapter 5 Creating Notification Components
show = false;
};
</script>
4. We can now add the markup for our component – much of this
standard HTML markup, but with a few Svelte tags in the mix.
Miss a line, then add this block:
{#if show}
<dialog open on:click={close} class={classes}
role="alert">
<div class="icon">
{#if showIcon}<Icon iconType={type} />{/if}
</div>
<div class="message">
<strong>
{title}
</strong>
{description}
</div>
<div>
<button on:click={closeAlert}>✖</button>
</div>
</dialog>
{/if}
<style>
dialog { min-width: 300px; display: flex; justify-
content: space-between; font-family: Arial, Helvetica,
sans-serif; border: none; }
125
Chapter 5 Creating Notification Components
@keyframes fade-in {
from {
opacity: 0%;
}
}
6. Save the file as Alert.svelte, then close it. Next, crack open
a new file and add this code – this time we first need to set
three exported variables before adding what will be the markup
for the first of two icons we add to our component:
<script>
export let width = "24px";
export let height = "24px";
export let iconType = "";
let icons = [
{
box: 512,
name: "info",
126
Chapter 5 Creating Notification Components
7. Next up, add these lines – this will form the second icon for
our demo:
{
box: 512,
name: "warn",
svg: `<path d="M448 256c0-106-86-192-192-192S64
150 64 256s86 192 192 192 192-86 192-192z"
stroke="#000000" fill="none" stroke-miterlimit="10"
stroke-width="32"/><path d="M250.26 166.05L256
288l5.73-121.95a5.74 5.74 0 00-5.79-6h0a5.74 5.74 0
00-5.68 6z" fill="none" stroke="currentColor" stroke-
linecap="round" stroke-linejoin="round" stroke-
width="32"/><path d="M256 367.91a20 20 0 1120-20 20 20 0
01-20 20z" fill="#000000" />`,
},
];
127
Chapter 5 Creating Notification Components
8. With the SVG markup in place, we now need to call it – for this,
miss a line, then add this markup:
<svg
class={$$props.class}
{width}
{height}
viewBox="0 0 {displayIcon.box} {displayIcon.box}">
{@html displayIcon.svg}
</svg>
9. Save the file as Icon.svelte in the Alert folder, and close it.
128
Chapter 5 Creating Notification Components
However, the reality is that it is all a pipe dream and that we still need
to display the occasional alert! With that in mind, and to construct our
component, we started by creating the usual component folder before
setting some variables for export. At the same time, we imported an Icon
component and set two variables for use internally.
Next up, we set up a somewhat lengthy switch statement for type
– this works out what class to set based on the value assigned to type.
For example, if we had passed in warn, then the class applied to the
component would be alert-warn and so on. We then concatenate all of
the classes together, ready for use in our component.
We then moved on to adding the markup for our component – this
is where things get a little more complex. We wrap everything in a Svelte
if block; if show is set to true, we render the component; otherwise, we
hide it. The core part of the component is built around an HTML5 dialog
element, into which we pass the classes we set earlier, along with setting
an on:click event handler to close the alert. The rest of the markup is
standard HTML, with the exception of the second Svelte if block and
the event handler assigned to the close button. To round off that part, we
then add some basic styling, which includes a simple animation to render
the alert.
Next up, we created Icon.svelte – this contained the markup for two
SVGs, with three exported properties so we can control the width, height,
and iconType from outside the component file. The magic happens in the
displayIcons function, where we filter icons based on the iconType name
passed into the component.
Once filtered, we render an SVG icon using the prop values and
content from the icons object. We then rounded out the demo by adding
a copy of the stylesheet alert.css from the code download – this contained
the styles for the main Alert component and some additional styles we will
use later in this chapter.
129
Chapter 5 Creating Notification Components
Right – let’s crack on: we still have plenty to do! It’s time we tested our
component to ensure it works, so as with others, let’s dive in and hook our
component into Storybook.
ADDING TO STORYBOOK
1. First, go ahead and create a new file, then add this code – as
before, we have a reasonable chunk to add. Let’s start with the
initial block to import the component and documentation, along
with some functions from Storybook:
<Meta
title = "Cobalt UI Library/Notification
Components/Alert"
component={Alert}
parameters={{page: null}}
/>
130
Chapter 5 Creating Notification Components
4. We can now render our component – for this, we will use the
now-familiar <Story> tag. Miss a line, then add this code:
<Story
name="Info"
args={{
show: true,
description: "An info description",
title: "Simple Info",
icon: "true",
type: "info",
}}
parameters={{
docs: {
page: AlertDocs
}
}}>
{Template.bind({})}
</Story>
6. You will see from the code that we’ve specified a file as our
documentation but haven’t yet added it. We need to extract a
copy of AlertDocs.mdx from the code download and then
drop it into the Alert folder.
131
Chapter 5 Creating Notification Components
132
Chapter 5 Creating Notification Components
Creating a Variant
Cast your mind back to the end of the last section but one – remember how
I said it’s easy to add different variants for the Alert component, such as
displaying a warning message instead?
133
Chapter 5 Creating Notification Components
Well – we won’t need to change the structure as such but tell the Alert
component to use different values to display the desired message. It’s easy
enough to effect the changes, so let’s dive in and add a variant to display a
warning message in our component.
2. Add a blank line, then this code – this will display a warning
style message in our component:
<Story
name="Warning"
args={{
show: true,
description: "An warning message",
title: "Simple warning",
icon: "true",
type: "warn",
}}
parameters={{
docs: {
page: AlertDocs
}
}}>
{Template.bind({})}
</Story>
134
Chapter 5 Creating Notification Components
3. We have everything in place, so let’s test it! Save and close the
file, then switch to a Node.js terminal session, and make sure
the working folder points to our cobalt project area.
Perfect – it shows that with only a few styling changes (and, of course,
the message we display), we can express something that looks a little
different and customize it to our needs.
A question, though: notice anything about the styling, say…how we
achieved it? Some of you will undoubtedly see that we didn’t add any
styling for the variants, yet each appeared in its own colors. How did we
manage to apply styling? That is indeed a good question and one that uses
a clever feature in Svelte – let’s take a moment to explore that and the rest
of the variant code in more detail.
135
Chapter 5 Creating Notification Components
136
Chapter 5 Creating Notification Components
3. Next, crack open a new file and add this code – we have a good
chunk to cover, so we will add it in sections, starting with an
export, one import, and setting a local variable:
<script>
import CloseIcon from "./Close.svelte";
export let show = "true";
let showDialog = show == "true";
</script>
{#if showDialog}
<div class="cobalt">
<div id="background" />
<div id="modal">
<div class="header">
<h3>Modal title</h3>
<button
type="button"
class="close"
on:click={() => (showDialog = false)}
>
<CloseIcon />
</button>
</div>
<p>Click on the X to close me</p>
137
Chapter 5 Creating Notification Components
</div>
</div>
{/if}
5. Finally (for this file), skip a line, then add this block of styles:
<style>
.cobalt { font-family: Arial, Helvetica, sans-serif; }
@keyframes fadein {
from { opacity: 0; }
to { opacity: 1; }
}
138
Chapter 5 Creating Notification Components
Adding to Storybook
So now that we’ve created our Dialog component, how do we get it into
Storybook? We’ll use the same process as we’ve done before, but this time,
we won’t be adding any variants. Dialogs are more for displaying content,
rather than producing different designs that are not consistent, which
could confuse our users! With that in mind, let’s jump in and take a look at
the steps to set up our component in more detail.
139
Chapter 5 Creating Notification Components
ADDING TO STORYBOOK
1. First, go ahead and create a new file, then add this code – as
before, we have a reasonable chunk to add. Let’s start with the
initial block to import the component and documentation, along
with some functions from Storybook:
<Meta
title="Cobalt UI Library/Notification Components/Dialog"
component={Dialog}
parameters={{ page: null }}
/>
140
Chapter 5 Creating Notification Components
4. We can now render our component – for this, we will use the
<Story> tag. Go ahead and add this block:
<Story name="Default"
args={{
showDialog: "false",
}}
parameters={{
docs: {
page: DialogDocs
},
}}
/>
6. You will see from the code that we’ve specified a file as our
documentation but haven’t yet added it. We need to extract a
copy of DialogDocs.mdx from the code download, then drop
it into the Toast folder.
141
Chapter 5 Creating Notification Components
Perfect – that’s the first iteration of our Dialog component done, ready
for use! For now, we’ve not added any variants, although with a little care,
we could perhaps change the formatting, or even add another button
(such as an OK or Cancel), perhaps?
That thought aside, we’re almost done with creating components
for the Notifications part of our library; before we move on to the next
category, there is one more we’ll develop. This next component might
seem an intriguing choice for some, but it does notify people – and you
have to have one in your toolkit at some point! I’m talking about the
Tooltip component – over the following few pages, we will develop our
own version for the library.
142
Chapter 5 Creating Notification Components
2. Next, crack open a new file and add this code – we’ll start
with importing the fade function from Svelte, creating a few
variables for export, and setting some placeholder variables for
use within the component:
<script>
import { fade } from "svelte/transition";
export let id = "tooltip";
export let label;
export let tip;
export let timeout = "400";
143
Chapter 5 Creating Notification Components
function handleKeydown(e) {
if (e.key === "Escape") {
active = false;
e.target.blur();
}
}
function handleMouseEnter() {
enterTrigger = setTimeout(() => {
active = true;
}, parseInt(timeout, 0));
}
4. Leave a line blank, then add the remaining two functions – they
deal with onMouseLeave and handling interaction:
function handleMouseLeave() {
if (enterTrigger) {
clearTimeout(enterTrigger);
enterTrigger = null;
}
leaveTrigger = setTimeout(() => {
144
Chapter 5 Creating Notification Components
active = false;
}, parseInt(timeout, 0));
}
function handleInteraction() {
if (leaveTrigger) {
clearTimeout(leaveTrigger);
leaveTrigger = null;
}
}
</script>
5. We can now add the markup for our component – much of this
standard HTML markup, but it does include some Svelte tags.
We’ll do it in two sections – first, leave a new line blank, then
add this code:
<div class="tooltip">
<button
aria-describedby={id}
type="button"
class="trigger"
on:click={() => (active = true)}
on:keydown={handleKeydown}
on:mouseenter={handleMouseEnter}
on:mouseleave={handleMouseLeave}
>
?
</button>
145
Chapter 5 Creating Notification Components
<div
transition:fade
class="content"
on:mouseenter={handleInteraction}
on:mouseleave={handleMouseLeave}
>
{#if displayHTML}
{@html tip}
{:else}
{tip}
{/if}
</div>
{/if}
</div>
</div>""""""""""""
<style>
.tooltip { position: relative; z-index: 2; }
146
Chapter 5 Creating Notification Components
147
Chapter 5 Creating Notification Components
show HTML code, or should show plain text; this is controlled by the
displayHTML value. To finish, we then added a set of CSS style rules to
make our tooltip look at least presentable when displayed in the browser.
ADDING TO STORYBOOK
1. First, go ahead and create a new file, then add this code – as
before, we have a reasonable chunk to add. Let’s start with the
initial block to import the component and documentation, along
with some functions from Storybook:
148
Chapter 5 Creating Notification Components
<Meta
title="Cobalt UI Library/Notification Components/
Tooltip"
component={Tooltip}
parameters={{ page: null }}
/>
4. We can now render our component – for this, we will use the
<Story> tag. Go ahead and add this block:
<Story
name="Default"
args={{
tip: '<p>This is an informational tooltip - to
learn more <a href="/tutorial">click here</a><p>',
showHTML: "false",
timeout": "400",
label": "more info",
}}
parameters={{
docs: {
page: TooltipDocs,
},
}}
/>
149
Chapter 5 Creating Notification Components
6. You will see from the code that we’ve specified a file as our
documentation but haven’t yet added it. We need to extract a
copy of TooltipDocs.mdx from the code download and then
drop it into the Tooltip folder.
9. When you run the demo for the first time, you may notice that
the tooltip is not 100% visible; this is easy to fix. Crack open the
preview-head.html file in the ./storybook folder, and add
this code at the bottom:
150
Chapter 5 Creating Notification Components
<style>
.innerZoomElementWrapper {
height: 200px;
}
</style>
10. Save the file and refresh your browser – you will find that each
Story preview window will now be larger, to better fit the demo.
As it so happens, this fix will also resolve the same issue with the
Spinner component, which you may have seen earlier in the book!
151
Chapter 5 Creating Notification Components
to use and to pass in any prop values set as arguments. We then finish
the demo by adding a Story block for the default instance of our Tooltip
component, along with our documentation file, before previewing the
results in a browser.
Creating a Variant
Throughout this book, we’ve added a few variants to components along
the way. In many cases, these have been as additional entries in Storybook.
Our Tooltip component is no different – although we will create a
second “story” for it, we are in reality copying most of the code from the
original story and changing a single value! To see what I mean, let’s dive in
and take a look.
<Story
name="Show HTML"
args={{
tip: '<p>This is an informational tooltip - <a
href="/tutorial">learn more</a></p>',
showHTML: "true",
timeout: "400",
label: "more info",
}}
152
Chapter 5 Creating Notification Components
parameters={{
docs: {
page: TooltipDocs,
},
}}
/>
153
Chapter 5 Creating Notification Components
Excellent – we’ve completed our Tooltip component and tested it: it’s
now ready for use. That brings us to the close of this chapter and where we
move on to what will be the next batch of components to add, but before
we do so, let’s quickly cover off the changes made in this last demo. The
only change we had to effect was to change showHTML from "false" to
"true"; this triggers the displayHTML check in our code, so allowing us to
use HTML. The rest of the code is the same, albeit with slightly different
markup; you will notice though that the markup is rendered properly this
time, not as plain text in our demo.
Summary
No one likes getting more notifications than is necessary – it’s essential
to get the balance right. Otherwise, we are likely to end up irritating our
customers! We still need to have something available, and while getting
the balance right is something that only comes with testing, we can at least
ensure we have suitable components available for use.
To help with that, we’ve created three components for our library;
each has its respective page in our Storybook instance. It brings us up
to the penultimate component group in our library, with only one more
component category to add to our library! Before we build the final
category of components, let’s take a moment to review what we have
learned in this chapter.
As we saw in the previous chapter, the focus is on adding each
component to our library and setting it up in Storybook. We started with
creating the Alert trail component before swiftly moving on to building the
more complex Notification component. Both follow the same principle of
displaying a notification, but they each do it differently, and choosing the
best one to use will depend on where we need to use it.
154
Chapter 5 Creating Notification Components
155
CHAPTER 6
Creating
Grid Components
So far, we’ve created a reasonably sized collection of components, most of
which we’ve added to Storybook and checked that they run as expected
in a browser. We have one more set of components to create before
we update the documentation in the next chapter – the last batch of
components is an ImageGrid.
Hold on a moment. That’s just one component, right? Well, yes – and
no: it is one component, but due to how Web Components work in Svelte,
we need to make it from no less than three components!
I’m sure you’re probably a little confused by now – don’t worry: we will
still use the same approach as before, but this time, I’ll show you how with
a bit of planning, we can bring all three components together to create a
starting point for our ImageGrid component. Let’s begin with setting the
scene for the construction of this component.
Determining the Approach
When I started researching for this chapter, I had initially planned to
create a layout grid component (or set of components). However, this soon
proved too large for this book’s scope – it would have meant subsuming
a large part of the CSS Grid or CSS Flexbox layout concepts, which could
almost form a book itself! So – how can I scale this back to something more
manageable?
I was still keen to use native CSS standards where possible and not use
third-party libraries to help keep the component light and dependency-
free (so to speak). One component came to mind that fitted the bill –
what about an ImageGrid? We can use these in e-commerce sites to
display products; this is a perfect fit for using CSS Grid to build a working
component. Sure, we could do this from scratch, but why reinvent the
wheel when someone has something you can use?
So – how to make this a set of components? Well, two quirks helped in
this respect:
With this in mind, let’s begin with building the first of our three
components – the Table component, which will act as our container for the
ImageGrid.
158
Chapter 6 Creating Grid Components
2. Next, open a new file and add this code – save it as Table.
svelte. We’ll start with setting the svelte:options tag,
followed by importing two components and setting four
variables for export:
<script>
import Grid from "./Grid.svelte";
import Cell from "./Cell.svelte";
159
Chapter 6 Creating Grid Components
3. Next, we have the markup – first, miss a line, then add this
block of code:
That was a short exercise – it doesn’t look like it does much at face
value! It still performs an important role, though – to understand how it fits
into the larger picture, let’s take a moment to review the code changes in
more detail.
160
Chapter 6 Creating Grid Components
from a string to an integer; for each instance of itemCount, we call the Cell
component and tell it whether it should display placeholder images when
viewing the component in our browser.
Okay – let’s move on: we’ve referenced the Grid and Cell components
in our last demo, but the Table component won’t work yet, as neither Cell
nor Grid exists yet! That’s easy to fix: let’s dive in and look at setting up
both; we’ll start with Grid as the next component.
We have the initial Table component in place – the next one to develop is the
Grid component. To set this component up, work through these steps:
2. Next, crack open a new file and add this code – we’ll start with
setting the svelte:options tag, followed by setting four
variables for export and two for internal use in the component:
<script>
export let columns = "2";
export let rows = "4";
export let border = "1px solid #000000";
161
Chapter 6 Creating Grid Components
<div
style="
grid-template-rows: repeat({rowInt}, 1fr);
grid-template-columns: repeat({colInt}, auto);
border: {border};
"
>
<slot />
</div>
<style>
div {
font-family: Arial, Helvetica, sans-serif;
display: grid;
grid-column-gap: 10px;
grid-row-gap: 5px;
grid-auto-flow: column;
border: 1px solid black;
}
</style>
5. Go ahead and save and close the file – the changes for this
component are complete.
162
Chapter 6 Creating Grid Components
Excellent – that’s two components down, one left to complete for our
ImageGrid! We base most of this component around standard HTML
markup and CSS styling, but there are a couple of exciting code features
we’ve used – let’s review the code changes we made to understand how
they work in more detail.
163
Chapter 6 Creating Grid Components
You will also notice the presence of <slot /> – this we use to
display whatever HTML or text is rendered inside the call to Grid
when using the component.
Right, let’s crack on – we have one more component to create, which
is Cell.
We’ve reached the third and final component for this chapter – to set it up,
follow these steps:
164
Chapter 6 Creating Grid Components
<script>
export let placeholder = "false";
let imgHolder = JSON.parse(placeholder);
</script>
<div class="cell">
{#if imgHolder}
<div>
<img src="https://loremflickr.com/150/160/camera"
alt="placeholder" />
<div class="description">This is a test image</div>
</div>
{:else}
<slot />
{/if}
</div>
4. As the last part, let’s finish our component off with some
styling:
<style>
.cell { border: 1px solid black; text-align: center; }
5. Save the file and close it – we have completed all of the
necessary changes for now.
165
Chapter 6 Creating Grid Components
166
Chapter 6 Creating Grid Components
Adding to Storybook
By now, I suspect this next part should be somewhat familiar to you –
we’ve added all of our components (except one) to Storybook, so there
isn’t likely to be anything too new for our next task. We’re now at a stage
where we can test the ImageGrid component, so as before, let’s crack
on with adding an instance to Storybook so we can prove it works as
we expect.
Although we’ve created a component, we won’t see how it works until we get
it into our demo. To do so, follow these steps:
1. First, create a new file, then add this code – as before, we have
a reasonable chunk to add. Let’s start with the initial block to
import the component and documentation, along with some
functions from Storybook:
2. Next, leave a line blank, then add the now-familiar Meta tag,
as shown:
<Meta
title="Cobalt UI Library/Grid Components/ImageGrid"
component={Table}
parameters={{page: null}}
/>
167
Chapter 6 Creating Grid Components
4. We can now add the Story to our file, which will render the
component on the page:
<Story name="Default"
args={{
columnCount: "1",
rowCount: "4",
border: "none",
placeholderImages: "false",
itemCount: "12",
}}
parameters={{
docs: {
page: TableDocs
},
}}
/>
6. You will see from the code that we’ve specified a file as our
documentation but haven’t yet added it. We need to extract a
copy of TableDocs.mdx from the code download and then
drop it into the Alert folder.
168
Chapter 6 Creating Grid Components
169
Chapter 6 Creating Grid Components
Exploring in Detail
By now, you will hopefully be familiar with most of the steps we’ve used
to add our component to Storybook – using the same format may seem a
little repetitive, but the flip side is that it does make it quicker to replicate
for other components.
In our case, we set up an instance of the Storybook page for the
ImageGrid component, even though we’re using Table as the main
container for our component. We added three imports, namely, the Table
component, documentation, and two functions from the Addon-Docs
component for Storybook.
Next came the usual <Meta> tag – we used this to create a section
called ImageGrid in Storybook and point it to the Table component as the
entry point. We also disabled Storybook from automatically generating its
documentation, as we will replace it shortly.
The most important part came next – we added a template for
our component to render it on-screen; here, we set it to use the Table
component and pass in args as a props call.
To combine it and complete the component, we added an instance
of <Story> to tell Storybook how to render the component and which
props values to use. We told it to use the TableDocs.mdx file as our
documentation; this comes from the code download accompanying
this book.
Adding a Variant
We now have our ImageGrid displayed in Storybook – it looks good and
resizes well (or at least within the confines of Storybook). The trouble is it
seems a little…well, plain. Can we do anything about this?
170
Chapter 6 Creating Grid Components
ADDING A VARIANT
To add in both an image and label will require some changes to the story we
set up in Storybook – to see what needs changing, follow these steps:
171
Chapter 6 Creating Grid Components
This change looks a little more enticing, wouldn’t you agree? There will
be cases where we aren’t ready to display our images, so having something
in place gives a little more visual interest.
As you will have seen, we’ve used an image placeholder service, so
images are not instant – this would be a perfect candidate for updating to a
fetch feature (more on this later). For now, though, let’s concentrate on the
code changes we made – most of it should be self-explanatory by now, but
it’s still worth reviewing the code in more detail.
172
Chapter 6 Creating Grid Components
Even though we only made one change, it’s still an important one –
we added a new instance of a Story block, but this time changed the
placeholderImages property to true. It tells the component to render
placeholder images from the LoremFlickr service we added earlier, along
with the labels below each picture. This was the only change we needed to
make – we finished by running the usual steps to preview the results in our
browser.
Summary
Adding the ImageGrid/Table components marks a significant milestone –
we now have all of the components set up in our library and available
in Storybook. It might have taken a while to get there, but we are indeed
there. Or are we? I’ll come back to that question in a moment, but we’ve
covered some important material in this chapter, so let’s pause to review
what we have learned.
We briefly looked at how we would approach this particular group.
We noted a couple of limitations around only accepting string values
and Storybook not making things easy for us, so we decided to create an
ImageGrid component as a basis for the subcomponents in this chapter.
In total, we created three components based on the CSS Grid
framework supported natively in most browsers, which makes them more
lightweight and easier to develop. We started first with Table, which acts as
our entry point for Storybook, and followed this with Grid, then Cell – all
three used minimal markup with styling that you might typically use when
styling with CSS Grid.
We then rounded off the chapter with a look at how to hook the
components into Storybook as the ImageGrid, before adding a simple
variant to display placeholder images in our component.
173
Chapter 6 Creating Grid Components
Phew – all the components are now up and running; what’s next? We
are there in terms of development, but it’s time for one of those tasks that I
know people don’t always enjoy…documentation. Yes, it’s time to do some
writing; stay with me as I show you how we use Markdown to create our
documentation within Storybook in the next chapter.
174
CHAPTER 7
Writing
Documentation
Throughout this book, we’ve created a host of new components to form
our component library – they may only be simple ones, but as they say: we
must start somewhere!
There is one important task we need to perform, and that is to
document how these components work. Given we’re using Storybook
(at least for most of them), then we can add some documentation files
to each component, which are accessible from the Docs tab at the top of
Storybook, as shown in Figure 7-1.
You will no doubt notice that we’ve already done this for most (if not
all) components – the trouble is they are placeholder pages and in dire
need of updating! This updating is easy enough to do – the placeholder
pages we’ve added so far use Markdown, similar to what you might use if
you’re creating pages in GitHub, for example.
Don’t worry if you’re not familiar with Markdown – most of it is text
based, with a relatively simple syntax for creating items such as titles. I’ll
take you through everything step by step as we go through this chapter.
Rather than being too prescriptive about the final article, we’ll keep
each relatively fluid, so you can use them as a basis for expanding and
developing your versions in the future.
Okay – we have plenty to cover: rather than go through each category
of components (which will get a little tedious, I’m sure), we’re going to take
a different approach. In this chapter, we’ll work through two categories
step by step. In Chapter 8, though, the focus will be on you! I’ll provide the
necessary labels and values, but it will be up to you to work through each
of the remaining categories. Don’t worry, though – if you get stuck, all the
answers will be available in the accompanying code download. With all
of that in mind, let’s explore the process we will use for performing the
update for each documentation page.
Setting the Scene
Cast your mind back to the last component we created – in each instance,
we added a file called XXXXX.stories.mdx to the storybook folder for each
component (where XXXXX is the name of the component, such as Slider).
This file contains our placeholder documentation – each will, of
course, vary depending on the components and variants we create, but all
will have some key elements for consistency:
176
Chapter 7 Writing Documentation
<Meta
title="Cobalt UI Library/Basic Components/Checkbox"
component={Checkbox}
parameters={{ page: null }}
/>
Copies of all the source files will be in the code download if you
get stuck – but I recommend working through the changes bit by
bit to learn how it hangs together! Documentation should evolve;
we’ll begin with something simple for now, but we can expand and
develop in the future.
177
Chapter 7 Writing Documentation
To add status badges, follow these steps – we'll use the Checkbox component
as our example:
"addons": [
"@storybook/addon-links",
"@storybook/addon-essentials",
"@storybook/addon-interactions",
"@geometricpanda/storybook-addon-badges",
],
178
Chapter 7 Writing Documentation
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
badgesConfig: {
beta: {
styles: {
backgroundColor: '#FFF',
borderColor: '#018786',
color: '#018786',
},
title: 'Beta',
},
deprecated: {
styles: {
backgroundColor: '#FFF',
borderColor: '#6200EE',
color: '#6200EE',
},
title: 'Deprecated',
},
},
},
}
parameters={{
docs: {
page: CheckboxDocs
},
badges: [BADGE.EXPERIMENTAL]
}}>
{Template.bind({})}
9. Save and close all open files. Revert to the Node.js terminal
session from earlier; then at the prompt, enter npm run
storybook and browse to http://localhost:6006/. If
all is well, we should see the Experimental badge shown in
Figure 7-2.
Great – we can now add badges to our site! At first glance, one marked
EXPERIMENTAL might scare a few people – don’t worry: it’s not intended!
What’s more important here – for now – is the principle of adding badges;
we can easily add new custom badges or override the styles of existing
ones at a later date.
As it happens, we’ve already added two examples (see step 6), so if
you change BADGE.EXPERIMENTAL to BADGE.BETA (from step 8), you will
hopefully see something a little less scary! That aside, this is a valuable
feature in Storybook, so let’s spend a few moments reviewing the changes
in more detail.
180
Chapter 7 Writing Documentation
badgesConfig: {
beta: {
styles: {
backgroundColor: '#FFF',
borderColor: '#018786',
color: '#018786',
},
title: 'Beta',
},
181
Chapter 7 Writing Documentation
deprecated: {
styles: {
backgroundColor: '#FFF',
borderColor: '#6200EE',
color: '#6200EE',
},
title: 'Deprecated',
},
...
At first glance, it might take a moment to work out how it all hangs
together, but it’s simpler than it might appear. We have the badgesConfig
object, inside which we can add as many badge types as we require.
Our example has two at present: beta and deprecated – we might
want to add labels such as Alpha, Windows Only, Mac Only, and so on.
Whatever labels we add will entirely be based on our requirements,
although I would recommend not adding more labels than is necessary!
The key to making it all work is two properties: styles and title. Both
should be self-explanatory, but to confirm, they contain the CSS styles and
title name for each label.
Try changing the BADGE.EXPERIMENTAL to BADGE.BETA, which we set up
in the last exercise – it should look something like this:
parameters={{
docs: {
page: CheckboxDocs
},
badges: [BADGE.BETA]
}}>
{Template.bind({})}
182
Chapter 7 Writing Documentation
Figure 7-3. The new BETA label on the Checkbox docs page in
StoryBook
The remaining styles for each label are just standard CSS color
properties – this will vary according to the color palette you’re using for
your site.
183
Chapter 7 Writing Documentation
Writing Documentation
for Basic Components
For the first set of documentation pages, we will focus on the components
from the Basic group, namely, Checkbox, Input, and Slider.
Most of what we add should be self-explanatory, although I
recommend keeping the spacing as laid out in the examples in the code
download. Markdown text is space specific – not including the correct
spaces can frequently lead to Markdown formatting errors.
For logistical purposes, we’ll work through the process of updating the
documentation as a three-part exercise; each part will focus on a specific
component. With that thought in mind, let’s start working through the
steps required in more detail.
184
Chapter 7 Writing Documentation
To update the documentation for the Checkbox component, follow these steps:
2. Next up, miss a line, then add this title – this will ensure we
set up the correct navigation for our plug-in, as well as tell
Storybook which component we're using:
3. Once you've added that line, miss another line, then add this
block – it contains some introductory text and some jump links
to the relevant section for each variant (in this case, three):
# CheckBox
185
Chapter 7 Writing Documentation
- [Default](#default)
- [No Label](#no-label)
- [Disabled](#disabled)
4. Next, skip a line, then add this block – this takes care of the
Default variant for our component:
## Default
<Canvas>
<Story id="cobalt-ui-library-basic-components-checkbox-
-default-story" />
</Canvas>
5. Repeat the same action as before, but this time, add this
block – this one covers the No Label variant of our Checkbox
component:
## No Label
<Canvas>
<Story id="cobalt-ui-library-basic-components-checkbox-
-no-label" />
</Canvas>
186
Chapter 7 Writing Documentation
6. Last but by no means least – here's the code for the Disabled
variant:
## Disabled Input
<Canvas>
<Story id="cobalt-ui-library-basic-components-checkbox-
-disabled" />
</Canvas>
## Properties of component
8. Save the file and close it – fire up a Node.js command prompt;
then at the prompt, switch the working folder to the project
area and run this command: npm run storybook.
187
Chapter 7 Writing Documentation
To add the documentation for the Input component, follow these steps:
188
Chapter 7 Writing Documentation
2. Next up, miss a line, then add this title – this will ensure we
set up the correct navigation for our plug-in, as well as tell
Storybook which component we're using:
3. Once you've added that line, miss another line, then add this
block – it contains some introductory text and some jump links
to the relevant section for each variant (in this case, four):
# Input
- [Default](#default)
- [Email](#email)
- [No Label](#no-label)
- [Disabled](#disabled)
4. Next, skip a line, then add this block – this takes care of the
Default variant for our component:
## Default
<Canvas>
<Story id="cobalt-ui-library-basic-components-input--
default-story" />
</Canvas>
189
Chapter 7 Writing Documentation
5. Next, skip a line, then add this block – this deals with the Email
variant for our component:
<Canvas>
<Story id="cobalt-ui-library-basic-components-input--
email" />
</Canvas>
6. Next, skip a line, then add this block – this section covers the
No Label variant for our component:
## No Label
This variant hides the label normally seen with the Input
component.
<Canvas>
<Story id="cobalt-ui-library-basic-components-input--
no-label" />
</Canvas>
190
Chapter 7 Writing Documentation
7. Last but by no means least, skip a line, then add this block –
this takes care of the Disabled variant for our component:
## Disabled Input
<Canvas>
<Story id="cobalt-ui-library-basic-components-input--
disabled" />
</Canvas>
## Properties of component
9. Save the file and close it – fire up a Node.js command prompt;
then at the prompt, switch the working folder to the project
area and run this command: npm run storybook.
191
Chapter 7 Writing Documentation
Great – two down, one more to go…at least for this group! As
before, take a moment to catch your breath, then let’s continue creating
documentation for this group’s third and final component.
To add the documentation for the Slider component, follow these steps:
192
Chapter 7 Writing Documentation
2. Next up, miss a line, then add this title – this will ensure we
set up the correct navigation for our plug-in, as well as tell
Storybook which component we're using:
3. Once you've added that line, miss another line, then add this
block – it contains some introductory text and some jump links
to the relevant section for each variant (in this case, two):
# Slider
- [Default](#default)
- [Disabled](#disabled)
4. Next, skip a line, then add this block – this block deals with the
Default variant for our component:
## Default
<Canvas>
<Story id="cobalt-ui-library-basic-components-slider--
default-story" />
</Canvas>
193
Chapter 7 Writing Documentation
5. Next, skip a line, then add this block – this takes care of the
Email variant for our component:
## Disabled
<Canvas>
<Story id="cobalt-ui-library-basic-components-slider--
disabled" />
</Canvas>
## Properties of component
7. Save the file and close it – fire up a Node.js command prompt;
then at the prompt, switch the working folder to the project
area and run this command: npm run storybook.
194
Chapter 7 Writing Documentation
195
Chapter 7 Writing Documentation
The story ID links are case sensitive – they must match what is in
the address bar, including the case. Otherwise, they will not operate
correctly.
Okay – let’s crack on with the next set of components: it’s the turn of
the Action group. The process will be very similar to the one we’ve just
used, if not near identical – let’s dive in and take a look.
196
Chapter 7 Writing Documentation
To add the documentation for the Accordion component, follow these steps:
2. Next up, miss a line, then add this title – this will ensure we
set up the correct navigation for our plug-in, as well as tell
Storybook which component we're using:
197
Chapter 7 Writing Documentation
3. Once you've added that line, miss another line, then add this
block – it contains some introductory text and some jump links
to the relevant section for each variant (in this case, just one):
# Accordion
- [Default](#default)
4. Next, skip a line, then add this block – this takes care of the
Default variant for our component:
## Default
<Canvas>
<Story id="cobalt-ui-library-action-components-
accordion--default-story" />
</Canvas>
6. Save the file and close it – fire up a Node.js command prompt;
then at the prompt, switch the working folder to the project
area and run this command: npm run storybook.
198
Chapter 7 Writing Documentation
Perfect – that’s component number four done, but still plenty more
to do! Take a breather for a moment, or grab a drink – when you’re ready,
we’ll continue with the next component.
199
Chapter 7 Writing Documentation
To add the documentation for the SelectBox component, follow these steps:
2. Next up, miss a line, then add this title – this will ensure we
set up the correct navigation for our plug-in, as well as tell
Storybook which component we're using:
3. Once you've added that line, miss another line, then add this
block – it contains some introductory text and some jump links
to the relevant section for each variant (in this case, two):
# SelectBox
- [Default](#default)
- [Disabled](#disabled)
200
Chapter 7 Writing Documentation
4. Next, skip a line, then add this block – this takes care of the
Default variant for our component:
## Default
<Canvas>
<Story id="cobalt-ui-library-action-components-
selectbox--default-story" />
</Canvas>
5. Next, skip a line, then add this block – this takes care of the
Disabled variant for our component:
## Disabled
<Canvas>
<Story id="cobalt-ui-library-action-components-
selectbox--disabled" />
</Canvas>
## Properties of component
201
Chapter 7 Writing Documentation
7. Save the file and close it – fire up a Node.js command prompt;
then at the prompt, switch the working folder to the project
area and run this command: npm run storybook.
Great – that’s another one done: five so far! We still have plenty more
to do, so take a breather for a moment or grab a drink – when you’re ready,
we’ll continue with component number six.
202
Chapter 7 Writing Documentation
To add the documentation for the Spinner component, follow these steps:
2. Next up, miss a line, then add this title – this will ensure we
set up the correct navigation for our plug-in, as well as tell
Storybook which component we're using:
3. Once you've added that line, miss another line, then add this
block – it contains some introductory text and some jump links
to the relevant section for each variant (in this case, just two):
# Spinner
- [Default](#default)
- [Jumper](#jumper)
203
Chapter 7 Writing Documentation
4. Next, skip a line, then add this block – this takes care of the
Default variant for our component:
## Default
<Canvas>
<Story id="cobalt-ui-library-action-components-spinner-
-default-story" />
</Canvas>
5. Next, skip a line, then add this block – this takes care of the
Jumper variant for our component:
## Jumper
<Canvas>
<Story id="cobalt-ui-library-action-components-spinner-
-jumper" />
</Canvas>
## Properties of component
204
Chapter 7 Writing Documentation
7. Save the file and close it – fire up a Node.js command prompt;
then at the prompt, switch the working folder to the project
area and run this command: npm run storybook.
And we can breathe easy now! That’s the end of the Action components
group – we now have six components in our library. We still have more to
do, but before we do so, let’s quickly review the changes made in the last
demo in more detail.
205
Chapter 7 Writing Documentation
The story ID links are case sensitive – they must match what is in
the address bar, including the case. Otherwise, they will not operate
correctly.
206
Chapter 7 Writing Documentation
Summary
Documentation of how a component works is an oft-neglected but
essential part of any component library; I’ve lost count of the number
of libraries I’ve seen where the developer provides the bare minimum,
making things awkward for working out how to achieve a task.
Gaps may exist in the documentation from the get-go, but it should
at least be accurate. We covered a lot of material in this chapter, so review
what we have learned.
We started by exploring how to add Storybook badges – we saw how
this is a helpful tool to help identify the state of any component, such as
Experimental, Stable, or (heaven forbid), Deprecated. Adding this feature
was a simple change to make; at the same time, we learned about how to
customize the labels so that we could display our text.
Next up, we began the lengthy process of updating the documentation
files for each component. Given the number involved and the high level
of duplication, we focused on the Basic and Action groups. This was all in
preparation for working through the remaining pages as practice in the
next chapter – I’ll list the details needed, ready for you to work them into
your own version. We’ll then finish with a quick note about tidying up
some loose ends before we begin testing in Chapter 9.
Don’t worry – it might seem complex, but if you remember that I’ve
designed the pages to use the same format, it will be easier than you might
expect! With that thought in mind, stay with me, and let’s move on to
starting those changes in the next chapter.
207
CHAPTER 8
Documenting More
Components
Throughout this book, we’ve created a host of new components to form
our component library – they may only be simple ones, but as they say: we
must start somewhere!
With all the components in place, we had one important task to
document how each works. We began this in Chapter 7, but we still have
a few groups to complete. It’s at this stage where I turn things over to you!
This chapter will focus on making the remaining changes as practice.
Adding Documentation
for Notification Components
For the first of the remaining three groups, we’ll focus on the Notification
components: Dialog, Alert, and Tooltip. We can use a similar process
for all three, as we have already done so far; let’s begin with the Dialog
component.
210
Chapter 8 Documenting More Components
2. Next, leave a line blank, then add this code – there is a good
chunk to add, so we’ll go through it block by block, beginning
with the shortcut links for each variant:
# Dialog
- [Default](#default)
## Default
<Canvas>
<Story id="cobalt-ui-library-notification-components-
dialog--default-story" />
</Canvas>
4. Miss a line, then add this block – it takes care of displaying the
properties of our component:
## Properties of component
211
Chapter 8 Documenting More Components
A tip – although we’ve worked through code, you may notice that
you can create a lot of these files using copies of the Spinner.mdx
file and search/replace with the appropriate component name. Take
care though if you do – some properties are case sensitive! All of the
completed files are in the code download that accompanies this book.
212
Chapter 8 Documenting More Components
2. Next, leave a line blank, then add this code – there is a good
chunk to add, so we’ll go through it block by block, beginning
with the shortcut links for each variant:
# Alert
- [Info](#info)
- [Warning](#warning)
## Info
<Canvas>
213
Chapter 8 Documenting More Components
<Story id="cobalt-ui-library-notification-components-
alert--info" />
</Canvas>
4. This next block is the text for the Warning variant of our
component:
## Warning
<Canvas>
<Story id="cobalt-ui-library-notification-components-
alert--warning" />
</Canvas>
5. Miss a line, then add this block – it takes care of displaying the
properties of our component:
## Properties of component
Below is a list of arguments available for this
component:
214
Chapter 8 Documenting More Components
215
Chapter 8 Documenting More Components
2. Next, leave a line blank, then add this code – there is a good
chunk to add, so we’ll go through it block by block, beginning
with the shortcut links for each variant:
# Tooltip
- [Default](#default)
- [Custom HTML](#customhtml)
## Default
<Canvas>
216
Chapter 8 Documenting More Components
<Story id="cobalt-ui-library-notification-components-
tooltip--default-story" />
</Canvas>
4. We need to add the code for our variant – miss a line, then add
this block:
## Custom HTML
<Canvas>
<Story id="cobalt-ui-library-notification-components-
tooltip--show-html" />
</Canvas>
## Properties of component
6. Save and close the file. Once you run the demo in Storybook,
if all is well, we should see something akin to the screenshot
shown in Figure 8-4, where we see the tooltip display
our content (and what will happen if we use HTML in the
default mode).
217
Chapter 8 Documenting More Components
218
Chapter 8 Documenting More Components
219
Chapter 8 Documenting More Components
The story ID links are case sensitive – they must match what is in
the address bar, including the case. Otherwise, they will not operate
correctly.
Okay – let’s crack on with the next set of components: it’s the turn of
the Action group. The process will be very similar to the one we’ve just
used, if not near identical – let’s dive in and take a look.
U
pdating Documentation
for Navigation Components
We’ve done one of the groups as an interactive exercise – hopefully,
it wasn’t too scary! We still have two groups to cover, which will be an
excellent way to practice what we learned earlier in this chapter. Let’s
begin with the first of these two groups: the Navigation Components.
220
Chapter 8 Documenting More Components
2. Next, leave a line blank, then add this code – there is a good
chunk to add, so we’ll go through it block by block, beginning
with the shortcut links for each variant:
# Breadcrumbs
- [Default](#default)
- [Custom Image](#customimage)
## Default
<Canvas>
<Story id="cobalt-ui-library-navigation-components-
breadcrumbs--default-story" />
</Canvas>
## Custom Image
<Canvas>
<Story id="cobalt-ui-library-navigation-components-
breadcrumbs--custom-image" />
</Canvas>
221
Chapter 8 Documenting More Components
## Properties of component
6. Save and close the file. Once you run the demo in Storybook,
if all is well, we should see something akin to the screenshot
shown in Figure 8-6, where we see the breadcrumbs on
display.
222
Chapter 8 Documenting More Components
If all went well, we should have the documentation file in place for our
Breadcrumb component. Take a breather for a moment, and then when
you are ready, let’s continue with the second part of this group: Tabs.
To update the documentation for our Tabs component, follow these steps:
223
Chapter 8 Documenting More Components
2. Next, leave a line blank, then add this code – there is a good
chunk to add, so we’ll go through it block by block, beginning
with the shortcut links for each variant:
# Tabs
- [Default](#default)
- [Vertical](#vertical)
## Default
<Canvas>
<Story id="cobalt-ui-library-navigation-components-
tabs--default-story" />
</Canvas>
4. Next, let’s add the code for the second – this will display the
tabs in a vertical format:
## Vertical
<Canvas>
<Story id="cobalt-ui-library-navigation-components-
tabs--vertical" />
</Canvas>
224
Chapter 8 Documenting More Components
## Properties of component
225
Chapter 8 Documenting More Components
Hopefully, all went well, and you now have the documentation file in
place for the Tabs component! Take a breather for a moment, and then
when you are ready, let’s continue with the final part of this group: Part 3.
So far, we’ve created documentation for each component that follows
a consistent format – the last one for this group will be a little different.
Remember how we built and tested our SideBar component outside
Storybook, owing to issues with rendering it inside the application?
Well, that change also has a knock-on effect here – as we’re not able to
run the component in Storybook correctly (at least not yet), we won’t be
able to include some of the properties we need so that the documentation
will be shorter.
It’s not ideal, but something we need to look at – for now, let’s create
something that we can refine over time as we develop the component.
226
Chapter 8 Documenting More Components
To update the documentation for our SideBar component, follow these steps:
2. Next, leave a line blank, then add this code – there is a good
chunk to add, so we’ll go through it block by block, beginning
with the shortcut links for each variant:
# SideBar
- [Default](#default)
3. Next, miss a line, then add this code in for the variant:
## Default
## Properties of component
227
Chapter 8 Documenting More Components
228
Chapter 8 Documenting More Components
The story ID links are case sensitive – they must match what is in
the address bar, including the case. Otherwise, they will not operate
correctly.
229
Chapter 8 Documenting More Components
Updating Documentation
for Grid Components
This last category is something of an oddity. You may remember in
Chapter 6 that we talked about creating Grid components, yet we only
created one. Seems a bit odd, no?
Well, this is one of those occasions where we could have gone about
this differently – we could have focused on creating three individual, fully
fledged components and brought them together. Or – as we have done –
create something that we were able to split into three subcomponents.
Both methods have their merits, but it all depends on how you want to
architect or develop your components in the future.
For this reason, you will see from the documentation that I initially
called this group “Grid Components” but then reference a Table
component, which in turn uses a Grid component and a Cell component,
respectively.
The latter two are really dependencies of the Table component – after
all, we can’t have cells without a table, right? I admit that this can make
things a little confusing regarding naming and structure. I’m hoping
that, over time, we can add features and develop each component into
something more standalone and reusable for other projects.
In the meantime, let’s dive in and look at how we can update the
documentation for our ImageGrid component in more detail.
To update the documentation for our ImageGrid component, follow these steps:
230
Chapter 8 Documenting More Components
2. Next, leave a line blank, then add this code – compared to other
components, we don’t have too much to add! We’ll start as
always with the shortcut links for the only variant:
# Grid
- [Default](#default)
- [Placeholder Images](#placeholder)
3. Next, miss a line, then add this code for the main (and only)
variant:
## Default
## Placeholder Images
<Canvas>
231
Chapter 8 Documenting More Components
<Story id="cobalt-ui-library-grid-components-image-
grid--placeholder-images" />
</Canvas>
5. We’ll finish off by adding the block that takes care of displaying
the properties for our component:
## Properties of component
6. Save the file and close it. Once you run the demo in Storybook,
if all is well, we should see the Tabs appear, as shown in
Figure 8-10.
232
Chapter 8 Documenting More Components
233
Chapter 8 Documenting More Components
In reality, we did both: it shows that with some planning, it’s perfectly
possible to create a working component based on others we add to the
library. It does make naming a little more complex, but that’s just part and
parcel of developing composite components.
To document the ImageGrid component, we followed the same process
as previous components. We first updated the imports and Meta tag before
replacing the remaining content with new content from a copy of the Spinner
documentation. This part was necessary to get the correct documentation
format in place, such as the links, anchors, and arguments table.
We then added a Story block into the file: with that block in place, the
last step was to work through the documentation, replacing content or
labels where appropriate. We deliberately kept the same format for each
component throughout to make it easier to search and replace automatically
or manually, where applicable.
We can then preview the results in our browser to confirm that all the
correct links are in place for our ImageGrid component.
A Final Tidy-Up
“And relax…” – or can we?
Truth be told, it’s very tempting to sit back and rest on our laurels at this
stage, but the reality is that we still have a few more tasks to do! We made
many changes throughout this chapter, so it’s worth spending a little time
just to review the state of the library and perform a little tidying up if we
can – it all helps keep things shipshape!
There are a couple of tasks that come to mind that we should do before
we move on to testing our components in the next chapter:
234
Chapter 8 Documenting More Components
These may only be small tasks, but every little one helps – the critical
point here is to ensure our code is as tidy as possible and ready for the
testing stage in the next chapter!
Summary
Phew – we’ve covered a lot over the last two chapters when it comes to
documenting our components!
The reality is that it demonstrates how important it is to have good
documentation – it need not all be present from the get-go, but it should
at least be accurate. We’ve covered a lot of material in this chapter, so let’s
review what we have learned.
This chapter’s focus was on updating each of the existing
documentation files with the values provided in the text. We had worked
through some of the pages in the previous chapter, so this time, it was
reusing the same process but updating with different values where
appropriate. We then rounded off with a quick note about tidying up some
loose ends so that our site is as ready as it can be for the next stage of the
development process.
Talking of the next stage – what is that, I wonder? We’ve written a lot of
code, so it’s time to test it! It’s an essential part of the development process,
but hopefully, not one that will be too testing for us (oops – sorry, another
pun!). There’s plenty more to do, so stay with me, and I will reveal it all in
the next chapter – including a reference to Cyprus – and no, I don’t mean
the country!
235
CHAPTER 9
Testing Components
So far, we’ve spent time creating our masterpiece, ready for use, but – can
we be sure it all works? What guarantee do we have that it won’t suddenly
collapse in a heap the first time someone tries to use it?
Before we release our library to the outside world, we need to test
each component to ensure they all work as expected. In this chapter, we
will go through the process of setting up unit testing for our library before
exploring some examples of tests we can write, so end users can see how
each component performs in a real-world capacity.
To get the Svelte Testing Library set up within our project area, follow
these steps:
3. Next, switch to your editor and add the following code to a new
file, saving it as babel.config.cjs:
module.exports = {
presets: [
[
'@babel/preset-env',
{
targets: {
node: 'current',
},
},
],
],
}
module.exports = {
transform: {
238
Chapter 9 Testing Components
'^.+\\.svelte$': 'svelte-jester',
'^.+\\.js$': 'babel-jest',
},
moduleFileExtensions: ['js', 'svelte'],
testEnvironment: "jsdom",
}
"build-storybook": "build-storybook",
"test": "jest"
},
Note the addition of the comma after the end of the build-
storybook line!
239
Chapter 9 Testing Components
Note If you’ve used Jest before, you might already be aware that
Jest sets up the jsdom environment by default in versions before
Jest version 28. Newer versions do not install jsdom by default,
hence the extra dependency at the start of the exercise.
The next part is the critical bit where we installed Jest – we had to
install the dependencies first. Otherwise, Jest might not install correctly if
it can’t find them during its installation! To round out the demo, we added
an entry to the script block to allow us to run tests as a shortcut before
creating a folder for sorting tests that we will write in upcoming exercises
in this chapter.
240
Chapter 9 Testing Components
Okay – let’s crack on: with the testing suite up and running, it’s time to
test it to see if it works as expected. There is no better way to do this than
by writing a test, so let’s dive in and look at what is involved in more detail.
Testing the Components
So – what should we test? How in-depth should our tests be?
These are both great questions – we’d test absolutely everything in an
ideal world to ensure we have covered all possible eventualities. However,
that isn’t always possible (or even practical) – you can bet that someone
will find a way to use a component that wasn’t in the manner we intended
and so could claim it’s not working as expected! This “use” opens up
that proverbial back-and-forth can of worms about what should have/
shouldn’t be tested…you get the picture.
Leaving that aside (and for reasons of space), let’s start creating the
tests for our library; we’ll focus on the Basic group of components but
touch on the remaining tests at the end of this next demo.
241
Chapter 9 Testing Components
type="checkbox"
3. Below this new entry, go ahead and add two more properties:
name="checkbox-name"
role="checkbox"
5. Next, open Input.svelte in your editor, then look for this line:
id={fieldID}.
6. Add these two additional lines immediately below the line from
the previous step:
data-testid="inputId"
role="textbox"
We’ve now completed two crucial changes to help facilitate testing – let’s
move on and begin writing our tests.
242
Chapter 9 Testing Components
WRITING TESTS
With our basic testing structure in place, we can begin to create our first
tests – to do so, follow these steps:
});
const props = {
id: "Input",
class: "cobalt",
disabled: false,
placeholder: "example text",
};
243
Chapter 9 Testing Components
4. Leave a line blank after the closing bracket from the props
block, then add this assertion – this will check our component
renders as expected:
244
Chapter 9 Testing Components
});
const props = {
id: "Slider",
};
11. Leave a line blank, then add this assertion – this tests that our
component renders as expected:
12. These next two first check that the slider sets the correct value
when moved, and then it takes a snapshot for visual checks:
245
Chapter 9 Testing Components
13. Save the file and close it – the changes for this test are
complete.
15. As we did before, we’ll add the code block by block, beginning
with two imports and the describe statement container:
});
16. We need to add a props block in the same way as we’ve done
before – go ahead and add this code immediately below the
opening describe statement:
const props = {
id: "Checkbox",
class: "cobalt",
checked: false,
};
246
Chapter 9 Testing Components
17. Leave a line blank, then add this assertion – this makes sure
our component renders as expected:
19. These last two assertions test first that we show a check
when we click the checkbox; the second takes a snapshot for
visual checks:
247
Chapter 9 Testing Components
20. We have the remaining tests for our library to add in – these are
all available in the code download accompanying this book. Go
ahead and extract the contents of the remaining tests folder in
the code download, then save the files to the __test__ folder.
21. Save this file and close it – switch to a Node.js terminal
session, and make sure the prompt points to our project area.
22. At the prompt, enter npm run test and press Enter – if all is
well, we should get a result similar to this:
PASS __test__/Checkbox.spec.js
PASS __test__/Slider.spec.js
PASS __test__/Spinner.spec.js
PASS __test__/Dialog.spec.js
PASS __test__/Alert.spec.js
PASS __test__/Input.spec.js
PASS __test__/Accordion.spec.js
PASS __test__/Breadcrumbs.spec.js
...(rest trimmed for brevity)
248
Chapter 9 Testing Components
249
Chapter 9 Testing Components
Okay – a critical point before we move on: you will notice that I didn’t
go through all of the tests in this chapter in detail. Part of this is for space
reasons, but also, this book isn’t about testing. The critical factor is that
we focus on getting components in place, then testing them. I’ve worked
through a few examples to show you what’s possible; the others use similar
principles and cover the remaining components. On that note, let’s begin
with the next task: explore how we can bundle our components for use in
our projects.
Bundling the Components
With the testing complete, we can move on to the next stage: bundling our
components. Bundling, I hear you ask – what exactly is that I wonder? It is
where we prepare the components to be released in a format that makes it
easy to drop into projects – let me explain what I mean.
So far, we’ve created Svelte components with just standard CSS, HTML,
and vanilla JavaScript – they work great (except SideBar) in environments
such as Storybook.
However, one of the benefits of Svelte components is the ability to
release them as web components that we can use in other environments,
such as React. To do this, we need to bundle the code into files that we can
consume outside of our development environment – in the same way, we
might import a third-party library into our code. There are several ways to
do this, depending on our requirements; before we explore them, let’s first
set up our library, ready to bundle our components.
250
Chapter 9 Testing Components
The latter might sound a little odd, as it’s not something anyone
consuming our components would need to use, but trust me: if that file is
not present, Svelte will complain! With that thought in mind, let’s dive in
and look at what we need to do to get our library ready for bundling.
2. Next, go ahead and create a new file – add the following code:
/* Main components */
export * from './lib/Alert/Alert.svelte'
export * from './lib/Accordion/Accordion.svelte'
export * from './lib/Breadcrumbs/Breadcrumbs.svelte'
export * from './lib/Checkbox/Checkbox.svelte'
export * from './lib/Dialog/Dialog.svelte'
export * from './lib/ImageGrid/Table.svelte'
export * from './lib/Input/Input.svelte'
export * from './lib/SelectBox/SelectBox.svelte'
export * from './lib/Slider/Slider.svelte'
export * from './lib/Spinner/Spinner.svelte'
export * from './lib/Tabs/Tabs.svelte'
export * from './lib/Tooltip/Tooltip.svelte'
/* Ancillary components */
export * from './lib/Alert/Icon.svelte'
export * from './lib/Dialog/Close.svelte'
export * from './lib/ImageGrid/Cell.svelte'
export * from './lib/ImageGrid/Grid.svelte'
251
Chapter 9 Testing Components
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width,
initial-scale=1.0" />
<title>Svelte Web Components Demo</title>
</head>
<body>
</body>
</html>
252
Chapter 9 Testing Components
// https://vitejs.dev/config/
export default defineConfig({
build:{
lib:{
entry: './src/main.js',
name: 'CobaltLibrary',
}
},
plugins: [svelte({
compilerOptions:{
customElement: true
}
})]
})
svelte({
compilerOptions: {
customElement: true,
},
}),
Excellent – we’re ready to bundle! It’s at this point that we will have
some decisions to make. Do we release packages for each component
individually, in groups, or one that covers all components?
If we did the latter, does that mean people have to download the entire
library if they only want one component? That doesn’t seem sensible, but
we need to balance that against maintenance and where package versions
253
Chapter 9 Testing Components
might diverge if we update one and not the other. It’s just a few questions
we have to ask; before we do so, let’s first explore the changes we’ve made
in more detail.
254
Chapter 9 Testing Components
To round off the demo, we also modified the vite.config.js file too,
to add in the same customElement property; the significance of this will
become apparent in the next demo.
Right – let’s crack on with running the build process. We have our
configuration in place, so it should just be a matter of running a command,
right? There’s more to executing a single line of code – it all hangs around
how we want to make our code available to others. To see what this means,
let’s dive in and look at the second part of this process in more detail.
The first two options are straightforward – we can run the first now
without any further configuration, and the second only requires changing
the file we run during the process. The third option is a little more
complex; let’s dive in and look at all three to understand what this means
for us in practice.
255
Chapter 9 Testing Components
With the build process set up and ready to go, we can now run it – to do so,
follow these steps:
3. This is good, but we might get more than one JavaScript file
appearing, which is not ideal. We can use the build process to
generate a single JavaScript file to get around it. At the Node.js
terminal prompt, run this command:
256
Chapter 9 Testing Components
257
Chapter 9 Testing Components
With the build process done, it’s time to test it! We already have the
demo in place (we created it as part of running the initial npm run
build command). We need to make one change to it, though, to test
the new file we’ve just built:
8. First, crack open the index.html from the \src folder in your
editor. Change the src location for Spinner to this:
src="/dist/assets/Spinner.8cd256b1.js"
11. At the prompt, enter npm run dev and press Enter.
258
Chapter 9 Testing Components
This demo looks great, but how can we prove it’s our new component and
that it’s coming from the newly built file? For the more adventurous, feel free
to look at the compiled code from within a developer console. You should
see something similar to that shown in Figure 9-2, where we reference
the component <cobalt-spinner> and the newly generated distribution
component file:
Figure 9-2. Proof that we’re using the newly compiled component
We’ve covered quite a bit there, which has left us with plenty to think
about going forward! There is no right or wrong answer as to which process
we choose to use – it comes back to that age-old chestnut of “it depends."
In this instance, it’s most likely to depend on what people consuming
our components will ask for; we might start with something large and
split off components over time as each one matures. Let’s first review
the changes we made in the last demo – it covers some exciting features
worth learning about when bundling our components into releasable
codeSpinner component .
259
Chapter 9 Testing Components
260
Chapter 9 Testing Components
Okay – let’s continue: we’ve tested our components and bundled them
into a format suitable for release. It’s time to put these bundles to a proper
test: let’s add them to a demo to see how they perform.
To create a CodeSandbox demo that uses our newly generated files, follow
these steps:
261
Chapter 9 Testing Components
The XXXXX will be the number assigned to your files during the
bundling process.
If you want to create your own, you can use an online generator like
the one at https://favicon.io/favicon-generator. I used
the letter C (for cobalt) as the basis for our one.
262
Chapter 9 Testing Components
263
Chapter 9 Testing Components
If you get stuck, feel free to look at my version, which you can
see at https://codesandbox.io/s/condescending-
shtern-1eu87e?file=/index.html.
Phew – that was a slight relief there: I was a little apprehensive about
whether it would all work while researching for this book. However, my
fears were unfounded: it has worked better than I expected!
Even though this was a simple demo, it nevertheless revealed a few
interesting points, so let’s pause to review the code in more detail.
264
Chapter 9 Testing Components
265
Chapter 9 Testing Components
5. Click on the upward arrow to select and upload the two files
into the assets folder.
You might wonder why I’ve replicated the same folder structure
here when it isn’t entirely necessary. It’s purely to replicate what we
receive when we run the bundling process – keeping it similar helps
prove that the component works as expected outside of Svelte.
import "../dist/assets/Spinner.8cd256b1.js";
7. Next, find the line with the <h2> tag, and add this code
immediately below it, as highlighted:
266
Chapter 9 Testing Components
div.layout {
margin: 0 auto;
width: 100px;
}
10. Click on File ➤ Save to save the demo – if all is well, we should
see our spinner running, as shown in Figure 9-5.
267
Chapter 9 Testing Components
Summary
Testing is essential to creating any code, period – be it a simple one-liner,
right through to a whole website! We need to ensure it works (to the best of
our ability) and does what we expect. We’ve covered a lot of material about
testing our library in this chapter, so let’s review what we have learned.
We started by working through the steps needed to set up our testing
environment – we chose to use Svelte Testing Library, as it’s one of the few
(if indeed the only?) options that I know supports Svelte web components.
A lack of wide choice isn’t ideal, but Svelte is still relatively new, so it
should be a matter of time before other libraries offer similar support.
268
Chapter 9 Testing Components
269
CHAPTER 10
Deploying to
Production
This chapter is the most critical part: we’ve spent all this time creating our
new library, but no one can use it unless released into production!
In this chapter, we will go through releasing our library into the wild
and explore what documentation is required so that others can use the
library for the first time. Let’s start with a simple task: perform some final
checks before we release our code into production.
Leaving that aside for a moment, let’s consider what we might want to
do at this point:
• Check each file in the repository: is it still needed, or is
it one that is no longer required, and we can therefore
remove it.
• Do all of the component files have a consistent layout?
For example, I usually start each component with the
<script> block, followed by the markup, and finish
with the <style> block, but you may prefer to change it.
• Are all the file names correctly named (i.e., in title
case), where appropriate?
• Have you pushed up any final changes in your local
version?
• We created a SideBar component but, for various
reasons, couldn’t include it in Storybook: it might be
wise to remove it for now and bring it back when we
can get it working in Storybook.
• We added a .gitignore file earlier in the book: Is this
up to date, or are there any other folders or files we
need to exclude?
We might want to make more changes to tidy up, but this will
depend on your circumstances. The critical point here is that we take the
opportunity to make sure our library is as tidy as possible before we release
it into the wild.
Okay – let’s crack on: now that we’ve completed the final checks for
our site, we should look at deploying our library. Getting our library out
into the wild will require a few steps, such as pushing our code into a
repository, releasing packages, ensuring documentation is good, and
more. Before we get stuck into the various tasks, let’s first take a quick look
at what we need to do in more detail.
272
Chapter 10 Deploying to Production
273
Chapter 10 Deploying to Production
There’s plenty to do! It might seem like such, but it’s important to
remember that much of this will be a one-off; once we complete steps such
as setting up GitHub, we can switch to applying updates and new features
throughout the lifetime of the component library.
With that in mind, let’s begin the process by getting a GitHub site set
up and ready for use.
Publishing to GitHub
Although publishing content on GitHub requires quite a few steps, we can
split the process into two distinct parts – the first is to create the repository
and get it ready for use, while the second is uploading our code.
Let’s focus first on setting up the repository: if you’ve already used sites
such as GitHub, then much of what you will see shortly will be familiar to
you. Before we get stuck in, though, there are a couple of points we should
be aware of as part of setting up our repository:
Okay – with that in mind, let’s dive in and start setting up the library’s
repository.
274
Chapter 10 Deploying to Production
275
Chapter 10 Deploying to Production
6. Next, set all three options under the Initialize this repository
with… label – you should end up with a configuration similar to
that shown in Figure 10-2.
276
Chapter 10 Deploying to Production
277
Chapter 10 Deploying to Production
With our repository in place, we can now move on to the next task,
which is to upload our library code – fortunately, this is easy enough to
do, using standard Git commands. I suspect some of this will be familiar
to many of you already; for those new to Git, don’t worry – let’s dive in and
take a closer look at what’s involved.
UPLOADING TO GITHUB
If you already have Git installed for your platform, please skip the next
step, and proceed to step 3.
278
Chapter 10 Deploying to Production
3. With Git installed, fire up a Git Bash session, then change the
working folder to the same level as the cobalt-source folder
we renamed in the previous step.
6. Switch to your file manager, then copy all of the files from
cobalt-source to cobalt, except for the following:
279
Chapter 10 Deploying to Production
9. With the files ready, run this command to bundle the code into
a commit:
git commit -m "Initial release"
280
Chapter 10 Deploying to Production
11. Click on Generate a new token, log in if prompted, and enter the
name Cobalt UI for the Note field.
281
Chapter 10 Deploying to Production
15. If you do not have it, then hit Add a Windows credential and
enter the details as follows:
Entry Value
17. Switch back to your Node.js terminal; then at the prompt, enter
git push.
18. You will likely be prompted to log into GitHub – click on the
Token open when prompted, paste in your PAT token, and then
hit Enter.
282
Chapter 10 Deploying to Production
20. Switch to your GitHub repository and check the Code tab to
confirm that all files are present and correct, as shown in the
extract in Figure 10-4.
Phew – that was a mammoth exercise! However, we have now got all of
our content into GitHub, ready for release. It took a bit of doing, but much
of this last demo is a one-off, so we won’t have to do it too often.
With the content available on GitHub, we can now take a breather –
the code is ready for us to start releasing as component packages on npm
or bundling into compiled files we can download and use in demos and
projects. Before we explore that, let’s take a few moments to explore what
we covered in that last demo in more detail.
283
Chapter 10 Deploying to Production
It’s a perfectly valid question, and I’m sure you will feel a sense of
trepidation as we take that leap into the unknown! But don’t worry,
though – while there may be a few steps involved in releasing our
components, it is a straightforward process, and some of it we will only
need to do for the first time. To understand what I mean, let’s quickly
summarize the steps involved:
284
Chapter 10 Deploying to Production
The first two steps only need to be done once for each component –
step 3 is the one we will repeat each time we publish a new version of
our component(s) or library. Perfect: now that we know what is involved,
let’s get stuck in! Before we get to writing code, there are a few points of
housekeeping we need to be aware of:
If you don’t have an account, you will need to create one, which
you can do at www.npmjs.com/signup – there is plenty of
documentation online if you need assistance.
285
Chapter 10 Deploying to Production
RELEASING TO NPM
286
Chapter 10 Deploying to Production
{
"name": "@XXXXX/checkbox",
"version": "1.0.0-alpha1",
"description": "A simple checkbox component from the
Cobalt library, for Svelte",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/XXXXX/cobalt.git"
},
"keywords": [
"svelte",
"react",
"custom elements",
"web components"
],
"author": "Alex Libby",
"license": "MIT"
}
4. Next, switch to your editor, then create a new file and add
this code:
// https://vitejs.dev/config/
export default defineConfig({
build:{
rollupOptions: {
input: ['./src/lib/Checkbox/Checkbox.svelte'],
}
287
Chapter 10 Deploying to Production
},
plugins: [svelte({
compilerOptions:{
customElement: true
}
})]
})
8. Once complete, look for the \dist folder at the root of your
project.
288
Chapter 10 Deploying to Production
289
Chapter 10 Deploying to Production
14. You will also receive a confirmation email if you entered a valid
email address!
Building a Demo
Of course, though, there is one thing we should do: How about testing if it
works? We know it’s now available on npm, but (as they say) the proof is in
the pudding – we should test it in a demo.
This testing is easy enough to do, so let’s dive in and look at what we
need to do in more detail.
290
Chapter 10 Deploying to Production
Testing our component is a quick job – to see how, follow these steps:
2. Next, click inside the Add Dependency box on the left, and
start typing the name of your component – in my case, @
alexlibby/checkbox, but yours will be whatever name you
decided to use.
5. Once done, click on the App.svelte entry in the file list at the
top of the page – add a reference to the Checkbox component
as highlighted:
<main>
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<cobalt-checkbox></cobalt-checkbox>
</main>
291
Chapter 10 Deploying to Production
Yes – we have finally arrived! In the hope that this wasn’t too
premature, we now have a working component and have proven it works
in a demo.
What is interesting to note is the use of this component’s web
component reference. We’re not using it as <Checkbox /> (which we
would in a Svelte environment), but by using <cobalt-checkbox></
cobalt-checkbox>. You will also notice that I’ve used the full name, not
the shorthand; I’ve noticed instances where the latter doesn’t work well.
It is why you will see me using the longhand version when referencing
components in a web component capacity.
Okay – let’s move on: we covered a lot of practical steps in the previous
demo, so now’s a perfect opportunity to review the changes in more detail,
understand how they all work, and what we need to do when it comes to
releasing further changes.
292
Chapter 10 Deploying to Production
293
Chapter 10 Deploying to Production
Okay – what’s next? Now that we have our component on GitHub, it’s
time to make our component documentation available for others to view
online. The easiest way to do this is by publishing a static version of our
Storybook instance; let’s dive in and look at how we can do this as part of
our next demo.
294
Chapter 10 Deploying to Production
At the time of writing, some of this is still in beta – the steps are solid,
although some things may change by the time this book is in print!
It may or may not show warnings – we can deal with any later.
The critical point is that it must not show any errors, indicating a
failed build.
3. Let it churn through the process – it will finish with lines similar
to this:
295
Chapter 10 Deploying to Production
git add .
git commit -m "Add exported version of Storybook"
git push
Assuming no errors appeared, we have our files ready for the next part of the
process: publishing the content for other developers to view and use.
I recommend selecting one that hooks into GitHub to get the best
from the next exercise.
Setting Up Netlify
Although Netlify has only been around since 2014, it has quickly become
one of the most popular ways to host content. It’s perfect for hosting our
Jamstack-based site – all of the content is already on GitHub, so we need to
link it to a Netlify account and let it publish the site onto the Internet. Let’s
take a look at what we need to do in more detail.
296
Chapter 10 Deploying to Production
SETTING UP NETLIFY
297
Chapter 10 Deploying to Production
298
Chapter 10 Deploying to Production
Yay – we have published our site! Publishing is only the start, as we will
need to update it as and when we make changes to our components. That
comes later, though; for now, let’s review the code changes we made in
more detail to understand better how this fits into the broader picture.
299
Chapter 10 Deploying to Production
There are a few steps required for us to complete both tasks, which we
will do over two separate exercises; let’s dive in and take a look at the first,
which will be adding a custom domain name.
300
Chapter 10 Deploying to Production
Before we start with the purchase and configuration process, there are
a couple of assumptions we should be aware of:
Let’s crack on with setting up our custom domain as part of the next
exercise.
5. Enter your address, then hit Save. Back on the previous screen,
hit Register domain now for…, and wait for it to complete.
301
Chapter 10 Deploying to Production
At this point, Netlify will likely state that an SSL/TLS certificate can’t be
provisioned to secure the site until the domain is validated. If you scroll up
the page, you will see the primary DNS entry has changed and that it shows
“Check DNS configuration” against it.
Assuming you have waited 24 hours, follow these steps to complete the
process:
302
Chapter 10 Deploying to Production
4. Take a quick look lower down on that page – you should also
see that the SSL certificate has been successfully provisioned.
303
Chapter 10 Deploying to Production
2. On the next page, you will see a long list of license types –
scroll down until you see the entry for npm (scoped). It’s a
long list, so you might want to use the search option in your
browser!
304
Chapter 10 Deploying to Production
3. Click on either the badge or the text to the right; then in the
package name field, enter the name of your package (I will
assume cobalt, but change it if you decide to use something
different).
4. On the next page, enter your NPM account ID in the format of
@XXXXX, then enter the name in the package name field.
5. If all is well, you should see a black and orange badge appear
with the version number of your package – click on the Copy
Badge URL drop-down and select Copy Markdown.
7. Inside the file, add the following text – adjust the name of the
package and the account ID to match your own:

License: MIT
8. Save the file – we can always come back and add more later,
but this will be enough to get us started.
305
Chapter 10 Deploying to Production
10. At the prompt, enter git add . && git commit -m "Add
README", then press Enter.
306
Chapter 10 Deploying to Production
However, it is just the start of our journey: there is more we can do!
We’ll touch on some of this in the next chapter, but before we do so, let’s
take a moment to review the changes we made in this demo to understand
how they all hang together.
307
Chapter 10 Deploying to Production
Summary
Phew – this might have been a long chapter, but we’ve finally reached the
point where our Storybook site and the component library will now be live!
We’ve covered a lot of content in this chapter, so let’s relax for a moment
and review what we have learned.
We began way back when (yes, it does feel like a while ago!) with
a quick discussion around performing the final checks. Not only did
we cover some areas to consider, but we also understood that this is a
symbolic way of confirming that we are ready to sign off the results and
release them into production.
Next up, we moved on to pushing our code into GitHub; we first walked
through the process of setting up the repository before exploring the steps
required to commit our code into our new library. We then switched to
releasing a test component into the npm repository as a package – we
covered the fact that this was an alpha package and a way to explore the
process; we would do this for real once we were ready to release our code.
Moving on, we worked our way through publishing the Storybook
instance to a hosting site, using Netlify as our preferred platform. We first
set up the authorization between GitHub and Netlify before configuring
Netlify to run the build step for Storybook and create our site. As a final
touch, we explored creating a new README file with a version badge
and the steps we would need to take to release our site through a custom
domain for our customers.
And relax! We’ve done most of the hard work now – the library is
available on GitHub, Storybook is hosted on Netlify, and we released our
component’s first instance as a package. There is still more to do, but
the focus of our journey changes – it’s time now to focus on how we can
develop and expand our library. There’s plenty we can do in this respect,
so stay with me, and I’ll reveal more details in the next chapter.
308
CHAPTER 11
These are just some of the questions we should answer – I’m sure I can
think of more! To get us started, let’s first review what we’ve done so far, so
we can see where we might have any gaps that we need to fill.
Reviewing the Site
Although we completed some of this task during the release process, I can
almost guarantee that there will be things we want to add or change!
I’m not talking about adding new components, although that will
come. We must also consider areas such as tidying existing code, leveling
the number of variants for each component, or improving test coverage.
With that in mind, let’s take a quick look at a few likely candidates for
improvement in the immediate future:
310
Chapter 11 Taking Things Further
You can see a more extensive road map in the GitHub repository, in
roadmap.md, at the root of the library.
• Progressbar
311
Chapter 11 Taking Things Further
• Switch
• Tags
• Popover
• ListBox
I’m sure there will be more, but as mentioned before – let’s not get too
ahead of ourselves! Most of our focus should be on leveling up existing
components and strengthening what we’ve developed
312
Chapter 11 Taking Things Further
to create a simple component. Now, let’s dive in and look at how this
component might look if we rebuilt it in Svelte.
4. Next, crack open your editor and create a new file, saving it as
Avatar.svelte in the newly created Avatar folder.
<script>
export let src = "";
export let status = "available" || "busy" || "away" ||
"unavailable";
export let statusSize = "small" || "medium" || "large";
313
Chapter 11 Taking Things Further
7. The most important part comes next, which is the markup for
our component:
<div class="cobalt-avatar">
{#if src}
<img {src} class="avatar-img" alt="avatar" />
{/if}
<slot />
<span class={["base", statusClasses()].join(" ")} />
</div>
8. We can finish off the component with some styling – the first is
the container for our component and a common style rule for
the indicator:
<style>
.cobalt-avatar {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
vertical-align: middle;
border-radius: 800px;
width: 32px;
314
Chapter 11 Taking Things Further
height: 32px;
font-size: 12.8px;
}
.base {
border-radius: 800px;
position: absolute;
border: 1px solid #373737;
}
9. Next up, we have two styles for size – small and medium:
.small {
width: 8px;
height: 8px;
top: 25px;
right: 0px;
}
.medium {
width: 12px;
height: 12px;
top: 22px;
right: -4px;
}
.large {
width: 16px;
height: 16px;
top: 22px;
right: -4px;
}
315
Chapter 11 Taking Things Further
10. We also need some styling for availability – for this, we have
four rules for available, danger, away, and unavailable:
12. Save and close the file – we are done with the changes for now,
and we’ll do the first test of our new component shortly when
we link it into Storybook.
Dissecting the Code
In our current age of social media, avatars are probably one of the most
widely seen features you will see. It doesn’t matter if they show letters or
a fancy picture; the basic premise of identifying you as a person is still the
same. We’ve taken the opportunity to create such a component for our
library and base it on an original, built using React – let’s take a moment to
review the changes we made in more detail.
316
Chapter 11 Taking Things Further
Adding to Storybook
So far, we’ve explored how to create an equivalent Avatar component in
Svelte and seen that it’s not just a lift and shift of existing code but that it’s
better to focus on functionality rather than technical code.
We now need to test our component – as we’ve done previously, there
are two ways we can test it: writing a test case for it using Svelte Testing
Library and adding it to Storybook.
317
Chapter 11 Taking Things Further
<Meta
title="Cobalt UI Library/New Components/Avatar"
component={Avatar}
parameters={{ page: null }}
/>
4. With the template in place, we can add stories for each variant
we want to display. The first one is the default, which displays a
green status to show that the person is available:
<Story
name="Default"
args={{
318
Chapter 11 Taking Things Further
status: "available",
statusSize: "small",
src: "/public/avatar.png"
}}
parameters={{
docs: {
page: AvatarDocs
}
}}>
{Template.bind({})}
</Story>
5. Next, miss a line, then add this next Story – this takes care of
cases where the person is busy and displays a red status:
<Story
name="Busy"
args={{
status: "danger",
statusSize: "small",
src: "/public/avatar.png"
}}
parameters={{
docs: {
page: AvatarDocs
}
}}>
{Template.bind({})}
</Story>
6. For this next Story, we’ll display the Unavailable status, which
shows a white circle, but this time in a larger size:
<Story
name="Unavailable"
args={{
319
Chapter 11 Taking Things Further
status: "unavailable",
statusSize: "medium",
src: "/avatar.png"
}}
parameters={{
docs: {
page: AvatarDocs
}
}}>
{Template.bind({})}
</Story>
<Story
name="Away"
args={{
status: "away",
statusSize: "medium",
src: "/public/avatar.png"
}}
parameters={{
docs: {
page: AvatarDocs
}
}}>
{Template.bind({})}
</Story>
320
Chapter 11 Taking Things Further
At this point, we should have a handful of files to push up to our repo – to get
them committed, follow these steps:
We have one last step to perform, which is to add a test – as this is only a
status icon, we’ll keep it simple and set it to run a snapshot for now. To do this,
follow these steps:
1. Crack open a new file, saving it as Avatar.spec.js in the
__test__ folder at the root of the project folder.
2. Go ahead and add the following code into the file – we’ll begin
with the imports:
3. Next up, let’s add the opening part of the test – this contains a
props declaration for our component:
5. To close off the test, add this step – it takes a snapshot, which
we can use for visual testing:
322
Chapter 11 Taking Things Further
6. Save and close the file. Switch to your Node.js terminal session,
ensuring the working folder is still set to the project area.
7. At the prompt, enter npm run test and press Enter – if all is
well, we should see our tests pass without issue.
8. Next, switch to a Node.js terminal session, and change the
working folder to our project area.
11. Finally, enter git push to upload all of the changes to our
repo – assuming you set up Netlify earlier, this will kick in and
build the library.
323
Chapter 11 Taking Things Further
324
Chapter 11 Taking Things Further
Okay – let’s crack on. By now, I’m sure you will have thought that
we’ve added all of the components we set out to create, right?
Well, perhaps not. All of these changes, exploring possibilities and
generally figuring out what we can do, got me thinking – what if we were to
revisit one particular component for the last time as a kind of encore?
2. Next, crack open your editor, and create a new folder, saving it
as RadioButton in the src\lib folder.
325
Chapter 11 Taking Things Further
<script>
export let id;
export let options = [];
export let checkedOptions = [];
export let type = "radio";
export let legendLabel = "Radio buttons";
</script>
5. Next up, we can add in our markup – for this, leave a line blank,
then add this code:
<fieldset>
<legend class="legend">{legendLabel}</legend>
{#each options as { name, value, label }, index}
<label class="radio-label-wrap">
<input
class="radioClasses"
id="{id}-{name}-{index}"
{type}
{name}
{value}
checked={checkedOptions.includes(value)}
on:blur
on:input
on:click
on:focus
{...$$restProps}
/>
326
Chapter 11 Taking Things Further
<span class="radio-label-copy">{label}</span>
</label>
{/each}
</fieldset>
<style>
*,
*:before,
*:after {
box-sizing: border-box;
}
fieldset {
display: flex;
flex-direction: column;
line-height: 1.4;
}
.radio-group {
font-family: Arial, Helvetica, sans-serif;
}
input[type="radio"]:before {
transition: transform 0.4s cubic-bezier(0.45, 1.8,
0.5, 0.75);
327
Chapter 11 Taking Things Further
328
Chapter 11 Taking Things Further
It’s worth taking a look at the changes we made this time, but before
we do so, let’s focus first on adding our component to Storybook, so we
can test if it works as expected.
Adding to Storybook
const actionsData = {
click: action("click"),
blur: action("blur"),
change: action("change"),
input: action("input"),
focus: action("focus"),
};
3. Next up, we need to set up the data for our radio button group –
miss a line below the end of the previous step, then add
this code:
const reusableOptions = [
329
Chapter 11 Taking Things Further
{
name: "frequency",
value: "daily",
label: "Daily",
},
{
name: "frequency",
value: "weekly",
label: "Weekly",
},
{
name: "frequency",
value: "monthly",
label: "Monthly",
},
];
export default {
title: "Cobalt UI Library/New Components/RadioButton",
component: RadioButton,
};
330
Chapter 11 Taking Things Further
10. Finally, enter git push to upload all of the changes to our
repo – assuming you set up Netlify earlier, this will kick in and
rebuild the library. If all is well, we should see our component
appear when browsing to the Netlify site, as shown in
Figure 11-2.
331
Chapter 11 Taking Things Further
And we can relax…! That was sadly the last component, at least for the
book – we’ve covered some great tips throughout these pages and created
some useful features along the way. There will be more to come – but for
that, check out my website at www.cobaltui.dev. (It’s a shameless plug,
but I hope you will visit…!)
That aside, you will notice that this time around, we’ve used a different
format to host our Storybook page for RadioButton. There are some
similarities to what we’ve done before, but they are equally diverse enough
that we should take a moment to digest the changes made in that demo, in
more detail.
332
Chapter 11 Taking Things Further
The flip side to this is that we’ve had to use a different format
for Storybook – this is one of several changes I’ve implanted in this
component. We began with the usual steps of creating a folder and
placeholder component file before adding a script block that contains a
handful of exported variables, such as id and options.
Next up came the most significant part – the markup. It may look
complex, but we base the markup on a standard HTML5 fieldset, with a
label and input for each entry in our list. We use an {#each...} block to
iterate through each entry – into the <input> element, we pass values such
as type, name, and checked, along with the Svelte on: event handlers. We
round off the first part by applying some simple styling – this takes care
of positioning each element, hiding the original radio, and creating the
replacements styled for our library.
Phew! It might not look like it, but most of that first part contains a lot
of similarities to previous components – the format, exported variables,
use of keywords such as {#each...}, and Svelte event handlers.
This next part is where things get interesting – adding our component
to Storybook. It’s a different format from what we’ve been used to, but the
only way we’ll get our RadioButton component to work is to apply data. So –
how does it all hang together?
We began by creating a story for our instance of Storybook – we
imported the action function from Storybook and our component. We
also created the event and data blocks that we will use to manage events
triggered by our RadioButton components and provide data for the labels
on the screen.
Next up, we added the equivalent of the Meta tag – this time, we use
a default export, which looks different but performs the same task as the
Meta tags we’ve used earlier in the book. You will notice that I’ve used the
New Components location as we did with the Avatar component; this is
purely to keep any new additions separate from the original components,
at least for now!
333
Chapter 11 Taking Things Further
Next up, we moved to add the template and default story to our Story
file – both follow a different format but use the same principles. The
Template tells Svelte to use the RadioButton component (as before). It
also passes in the args value and details of events triggered by the user
interacting with our component. With the template in place, we bind to it a
story – this we call Default – and use the Template.bind() function to pair
it with our story.
To finish off, we committed all of the changes to GitHub and reviewed
them in the browser once Netlify had rebuilt the site.
Summary
“All good things must come to an end sometime…”
Although I can’t proclaim to know who said these wise words, their
meaning is very true – yes, sadly, we have indeed come to the end of our
adventure with Svelte web components! We’ve covered a lot over the last
few pages of this book, so let’s take a moment to review what we have
learned.
We began this chapter with a look at reviewing the site – we learned
that it’s essential to have that final check over our content to ensure we
don’t let any (at least apparent) mistakes fall through into production. At
the same time, we understood that this step acts as a way to sign off the
content – we can treat it as confirmation that development has finished
and we’re ready to move our code into production.
Next up, we then talked about setting a road map – I highlighted the
importance of basing this around two critical decisions of what you want
to see in it as the library author or what it might be used for if working in
a corporate environment. We then started converting what will be our
334
Chapter 11 Taking Things Further
335
Index
A spinner component
(see Spinner component)
Accordion component
Alert component, 121–123
AccordionItem atom, 78
building the component, 123–128
AccordionItem
in Storybook, 130–133
component, 78, 79
SVG icons, 122
code
warning variant, 133–135
documentation, 82
AlertDocs.mdx, 131, 213
files, 83
Alert.stories.mdx, 131
<Meta> tag, 82
Angular, 2, 23, 106, 265
Story block, 83
ArgsTable entry, 196, 206, 219, 229
composite component, 77
Atomic Design principles, 78
creation, 74, 75, 77
Autogeneration, 177
data, 77
Avatar component, 311–313,
data format, 78
316–318, 321, 323, 324,
designers display, 74
329, 333
folder creation, 78
import data, 78
information, 74 B
markup, 78, 79 badgesConfig object, 182
Storybook, 79–82 Basic components, 7
unitary components, 78 checkbox component
Action components, 7 (see Checkbox component)
Accordion component input field component
(see Accordions component) (see Input field component)
SelectBox component slider component (see Slider
(see SelectBox component) component)
D
C Deployment process
Cards, 311 GitHub, 273, 274
Checkbox component, 51 Developers, 2, 9, 17, 95, 271
assumptions, 19 Dialog component, 136
code CloseIcon file, 139
CheckboxDocs.mdx file, 39 showDialog, 139
folder, 34 steps, building, 136
HTML markup, 35 in Storybook, 139–142
338
INDEX
Documentation G
final checks, 272
GitHub, publishing, 274
spelling, 271
explore code, 284
Documentation files
repository for use, 277
Accordion component, 199
setting repository, 275, 276
action component, 196, 197
upload files, 283
Alert component, 215
uploading
Breadcrumb
components, 278–282
component, 220–224
GitHub repository, 311, 324, 334
Checkbox component, 185–188
.gitignore file, 272, 277
components, 184
Grid components, 7
Dialog component, 210, 212,
Cell component,
213, 216
constructing, 164–166
Grid components, 230
and Cell components, 160, 161
ImageGrid component, 230–233
coding, 163, 164
Input component, 188, 190–192
ImageGrid component, 166, 170
Navigation components, 220
placeholderImages, 166
SelectBox component, 200–202
steps, constructing, 161–163
SideBar component, 227, 228
to Storybook, 167–169
Slider component, 192–195
variants, 171–173
Spinner component, 203–205
Tabs component, 225, 226
Tooltip component, 218 H
updating, 184 HTML5, 19, 53, 61, 122, 129, 143,
variants, 206, 219 312, 333
E I
e-commerce websites, 7, 43, 53, 158 Icon.svelte, 128, 129
iconType, 129
ImageGrid component, 157–159,
F 163, 166, 167, 169, 170, 173,
Functionality, 100, 111, 312, 229, 230, 232–234
317, 335 imgHolder, 166
339
INDEX
340
INDEX
341
INDEX
342
INDEX
error message, 17 T, U, V
files, 6
TableDocs.mdx file, 168,
GitHub, 4
170, 230
installation, 11–13
Table.stories.mdx, 168, 171
interoperability, 3
Tabs component
Node commands, 5
accessibility, 111
npx sb init command, 17
adding call, 115
package.json, 6, 17
adding variation, 114
server, 14
code, exploring, 110
src folder, 5, 6
creation, 107–109
Storybook, 14, 17, 18
<div> tags, 107, 118
tools, 10
<Meta> tag, 115
version, 4
markup block, 119
Vite, 14
Story block, 115
weather-app folder, 5
Storybook, 111–113
weather component demo, 4, 5
variants, 115–118
website, 18
Tags, 312
SvelteKit, 23
Tailwind, 101
svelte:options tag, 96, 100, 110,
Test coverage, 310, 324
159–161, 163, 166, 237,
toMatchSnapshot()
313, 317
function, 249
Svelte syntax, 128
Tooltip component
Svelte Testing Library (STL),
code changes, 151
237–239, 317, 324, 335
steps, building, 143–147
preparation for testing, 242
in Storybook, 148–151
testing components, 241
values, 147
web components, 240, 241
variants, 152–154
writing tests, 241, 243, 245,
247, 248
Svelte web components, 78, 158,
163, 237, 240, 268, 334 W, X, Y, Z
Switch, 26, 37, 49, 59, 67, 93, 113, Warning message, 133, 134
129, 132, 141, 150, 153, 257, Web components, 2, 3, 100, 110,
266, 281, 283, 312, 331 157, 250, 267
343