Add Storybook to Vue3 project

EJEJ
3 min read

This is a continuation of this article: https://crocodayo.hashnode.dev/one-way-to-bootstrap-vue3-project

Install Storybook

Simply follow the instruction in this page: https://storybook.js.org/docs/get-started/install

Make sure to select Vue to get the right example.

$ npm create storybook@latest

And follow the prompt:

Need to install the following packages:
create-storybook@9.1.0
Ok to proceed? (y) 

...
✔ New to Storybook? › No: Skip onboarding & don't ask again

✔ What configuration should we install? › Minimal: Component dev only

...
Running Storybook

> vite-4@0.0.0 storybook
> storybook dev -p 6006 --quiet

storybook v9.1.0

Once done, Storybook will automatically open the default browser and open page http://localhost:6006

The setup will create a few configuration files (/.storybook/main.ts and /.storybook/preview.ts) and an example directory (/src/stories)

The examples will be loaded (NOTE: the examples in /src/stories directory can be safely deleted)

Add stories for our Rando component

First, add a new file called Rando.stories.ts and place it in the same directory as Rando.vue

Copy an example content from this page: https://storybook.js.org/docs/writing-stories/typescript

Update the component name and the content should look like this:

import type { Meta, StoryObj } from '@storybook/vue3-vite'

import Rando from './Rando.vue'

const meta = {
  component: Rando,
} satisfies Meta<typeof Rando>
export default meta

type Story = StoryObj<typeof meta>

export const Primary = {
  args: {
    msg: 'Some message',
  },
} satisfies Story

The above code will create a story for Rando component called Primary and we simply pass an example prop (i.e. Some Message).

Improve Rando with Vuetify

Let’s improve Rando to use Vuetify. We will use v-label, v-textfield and v-button. And when the button clicked, it will emit a save event that include the value of the textfield to the parent.

<script setup lang="ts">
  import { ref } from 'vue'

  const yourName = ref('')
  defineProps<{ msg: string }>()
  const emit = defineEmits<{
    (e: 'save', name: string): void
  }>()

  function onSave () {
    emit('save', yourName.value)
  }
</script>

<template>
  <v-container>
    <v-label>Message: {{ msg }}</v-label>
    <v-form>
      <v-text-field
        v-model="yourName"
        label="Your Name"
      />
      <v-btn @click="onSave">Save</v-btn>
    </v-form>
  </v-container>
</template>

The improved Rando component should display a label, a textfield, and a button

But Storybook does not display it nicely.

This is because Storybook has no access to Vuetify. To fix this, update the /.storybook/preview.ts file.

This is how the file before:

import type { Preview } from '@storybook/vue3-vite'

const preview: Preview = {
  parameters: {
    controls: {
      matchers: {
        color: /(background|color)$/i,
        date: /Date$/i,
      },
    },
  },
}

export default preview

And after:

import type { Preview } from '@storybook/vue3-vite'
import { setup } from '@storybook/vue3-vite'
import { vuetify } from '../src/plugins/vuetify'

setup(app => {
  app.use(vuetify)
})

const preview: Preview = {
... no changes ...
// /src/plugins/vuetify.ts
import { createVuetify } from 'vuetify'
import * as components from 'vuetify/components'
import * as directives from 'vuetify/directives'
import { mdi } from 'vuetify/iconsets/mdi-svg'
import 'vuetify/styles/main.css'

export const vuetify = createVuetify({
  components,
  directives,
  icons: {
    defaultSet: 'mdi',
    sets: { mdi },
  },
})

Refresh the Storybook and it should look like this now:

Update Rando.stories.ts to capture the event

...
import { fn } from 'storybook/test'    // <= add this
...
export const Primary = {
  args: {
    msg: 'Hello Primary',
    onSave: fn(),                      // <= and add this
  },
} satisfies Story

Type some text in Your Name textfield and click Save button.

:-) That’s All

0
Subscribe to my newsletter

Read articles from EJ directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

EJ
EJ