In the Show Code feature of Storybook, every element is being displayed for

My Vue 3 + Storybook setup is all running smoothly, except for a little hiccup when I click "Show Code". It seems to display everything instead of just the template. Can anyone help me figure out what's going wrong?

https://i.stack.imgur.com/zzBl0.png

Sharing my story below:

import Button from './Button.vue';

export default {
  title: 'Components/Button',
  component: Button
};

const Template = (args) => ({
  // Components used in your story `template` are defined in the `components` object
  components: { Button },
  // The story's `args` need to be mapped into the template through the `setup()` method
  setup() {
    return { args };
  },
  // And then the `args` are bound to your component with `v-bind="args"`
  template: '<my-button v-bind="args" />',
});

export const Primary = Template.bind({});
Primary.args = {
  primary: true,
  label: 'Button',
};

export const Secondary = Template.bind({});
Secondary.args = {
  label: 'Button',
};

export const Large = Template.bind({});
Large.args = {
  size: 'large',
  label: 'Button',
};

export const Small = Template.bind({});
Small.args = {
  size: 'small',
  label: 'Button',
};

Answer №1

Looking at the screenshot provided, it appears to be functioning as expected in Vue 2.

https://i.stack.imgur.com/HVauj.png

However, when testing with Vue 3, I am encountering the same issue.

https://i.stack.imgur.com/tRnUF.png


The straightforward explanation

The feature has not been implemented for Vue 3 yet.

Upon examining the source code of Storybook's docs add-on, it is evident that there is a specific implementation for Vue 3. However, this implementation in Vue 3 lacks the source decorator responsible for generating a rendered version of the source code.

Quick fix

If you prefer not to wait for the official update from the Storybook team, you can utilize the following code snippet to create your documentation based on specified arguments. Note that this solution may not cover all scenarios.

const stringifyArguments = (key, value) => {
    switch (typeof value) {
    case 'string':
        return `${key}="${value}"`;
    case 'boolean':
        return value ? key : '';
    default:
        return `:${key}="${value}"`;
    }
};

const generateSource = (templateSource, args) => {
    const stringifiedArguments = Object.keys(args)
    .map((key) => stringifyArguments(key, args[key]))
    .join(' ');

    return templateSource.replace('v-bind="args"', stringifiedArguments);
};

const template = '<my-button v-bind="args" />';

const Template = (args) => ({
    components: { MyButton },
    setup() {
    return { args };
    },
    template,
});

export const Primary = Template.bind({});
Primary.args = {
    primary: true,
    label: 'Button',
};

Primary.parameters = {
    docs: {
    source: { code: generateSource(template, Primary.args) },
    },
};

Another temporary workaround would be manually crafting the source code instead of relying on automatic generation.

Primary.parameters = {
  docs: {
    source: { code: '<my-button primary label="Button" />' },
  },
};

Answer №2

An issue has been identified

To address this, you can implement a workaround provided in the GitHub issue linked above.

Start by creating a file called withSource.js within the .storybook directory with the following contents:

import { addons, makeDecorator } from "@storybook/addons";
import kebabCase from "lodash.kebabcase"
import { h, onMounted } from "vue";

// The value below is not exported by addons-docs
export const SNIPPET_RENDERED = `storybook/docs/snippet-rendered`;

function templateSourceCode (
  templateSource,
  args,
  argTypes,
  replacing = 'v-bind="args"',
) {
  const componentArgs = {}
  for (const [k, t] of Object.entries(argTypes)) {
    const val = args[k]
    if (typeof val !== 'undefined' && t.table && t.table.category === 'props' && val !== t.defaultValue) {
      componentArgs[k] = val
    }
  }

  const propToSource = (key, val) => {
    const type = typeof val
    switch (type) {
      case "boolean":
        return val ? key : ""
      case "string":
        return `${key}="${val}"`
      default:
        return `:${key}="${val}"`
    }
  }

  return templateSource.replace(
    replacing,
    Object.keys(componentArgs)
      .map((key) => " " + propToSource(kebabCase(key), args[key]))
      .join(""),
  )
}

export const withSource = makeDecorator({
  name: "withSource",
  wrapper: (storyFn, context) => {
    const story = storyFn(context);

    // This creates a new component that computes the source code when mounted
    // and emits an event handled by addons-docs
    // This approach is based on Vue (2) implementation
    // Refer to https://github.com/storybookjs/storybook/blob/next/addons/docs/src/frameworks/vue/sourceDecorator.ts
    return {
      components: {
        Story: story,
      },

      setup() {
        onMounted(() => {
          try {
            // Retrieve the story source
            const src = context.originalStoryFn().template;
            
            // Generate the source code based on the current arguments
            const code = templateSourceCode(
              src,
              context.args,
              context.argTypes
            );

            const channel = addons.getChannel();

            const emitFormattedTemplate = async () => {
              const prettier = await import("prettier/standalone");
              const prettierHtml = await import("prettier/parser-html");

              
              channel.emit(
                SNIPPET_RENDERED,
                (context || {}).id,
                prettier.format(`<template>${code}</template>`, {
                  parser: "vue",
                  plugins: [prettierHtml],
                  htmlWhitespaceSensitivity: "ignore",
                })
              );
            };

            setTimeout(emitFormattedTemplate, 0);
          } catch (e) {
            console.warn("Failed to render code", e);
          }
        });

        return () => h(story);
      },
    };
  },
});

Next, include this decorator in your preview.js file:

import { withSource } from './withSource'

...

export const decorators = [
  withSource
]

The solution was proposed by this author

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

The process of mounting a Vue component involves removing the surrounding div element

I am facing an issue while mounting a component on a function. Everything seems to be working fine initially. However, I have configured it to destroy the div after a certain number of seconds. The problem arises when I attempt to add the component again; ...

Passing data from an API in Vue.js to a different page

I'm a beginner with Vue Js and I'm looking for guidance on how to send data between two components. Currently, I am working on a Vue app that fetches a list of users from an API and displays them. My goal is to transfer data between these compone ...

Enhancing the default vue-cli webpack app with additional components

To ensure reproducibility, I began by setting up a basic project using vue-cli: npm install -g vue-cli vue init webpack spa cd spa npm install npm run dev Afterwards, I decided to swap out the Vue logo with an app-header component. Here's how I impl ...

Feeling lost when it comes to understanding how vue3 handles reactivity

Within vue3's reactive framework, there exists a stack known as the effectStack. I'm puzzled by the necessity of it being a stack if the effect is immediately removed with pop() after push(). Is there a scenario where the length of effectStack co ...

Transfer information from an array to a Vue function

Having some difficulties passing data to the function myChart within the mounted section. As a beginner in vuejs, I'm struggling with identifying the issue. I am trying to pass data in labels and datasets, which are called from my function. Can anyone ...

Exploring Firebase's Collection Retrieval through Vue.js

Trying to retrieve a specific collection from Firebase Firestore is giving me an error that I haven't been able to resolve yet. Below is the code snippet from my boot file: import { initializeApp } from "firebase/app"; import { getFirestore ...

Identifying the specific filter used in Vue-tables-2

Trying to configure a basic server-side vue-tables-2 with dual filters - one dropdown and the other a search field. The challenge here is identifying which filter was applied within the requestFunction() in order to send a server request. My current strate ...

Change the Vue3 PrimeVue theme or CSS file with a simple click or upon page load

One way to incorporate themes is by importing them in the main.js file at the root of the app: import 'primevue/resources/themes/arya-orange/theme.css'; However, I am exploring ways to dynamically switch CSS files based on the user's system ...

Verify for any empty values in a quiz

After completing a quiz using vue.js, I am facing a challenge in checking if a value is null before logging it into console.log. I have been unable to find a solution for this issue. Although I have numerous questions to include in the quiz, some of them ...

Executing functions from one component to another in Vue.js

Is there a way to trigger a method from one component to another in this situation? I want to fetch additional data from an API when a button is clicked on Component 1 and update Component 2 with this information. Thank you Below are the two components s ...

how to switch page direction seamlessly in vue without triggering a page refresh

I'm looking to update the page direction in Pinia whenever a value changes, and while my code is functioning correctly, it reloads the page which I want to avoid. Take a look at my App.vue <script setup> import { useaCountdownStore } from " ...

Unable to display nested JSON data from API in Vue.js

Having trouble accessing nested properties from API JSON data. The Vue component I'm working on: var profileComponent = { data : function() { return { isError : false, loading : true, users : null, ...

What is the process for dynamically inserting a new object into an array of data objects in Vue.js?

I am a beginner with vue.js and currently working on a form. I have an add button in my form, so when the user clicks on it, the same form field will be added to the form. Users can add as many times as they want. Here is my initial data: data () { ret ...

Vue.js dynamic HTML elements are constantly changing and evolving

Is it possible to dynamically add elements to the content? See example below: <template> {{{ message | hashTags }}} </template> <script> export default { ... filters: { hashTags: function(value) { ...

Can you point me to the source of definition for Vue 2's ComponentDefinition and ComponentConstructor types?

I am struggling to add a dynamic Vue 2 component with correct typing in TypeScript. The documentation clearly mentions that the is attribute accepts values of type string | ComponentDefinition | ComponentConstructor, but I cannot locate these custom types ...

What could be causing my POST request to fail in this basic VueJS form?

I am currently working on a vueJS form that involves submitting nested values under "medications." When I select a medication from the dropdown and fill out the remaining fields before submitting, I encounter an error indicating that not all values are bei ...

I keep encountering an attach() error every time I try to close a modal that contains a vee-validated form

Every time I try to close a bootstrap modal (using bootstrap-vue) that includes a vee-validated "update" form with vue.js, I encounter the following error: main.js:477686 Uncaught (in promise) Error: [vee-validate] Validating a non-existent field: "#35". ...

How can I make text wrap instead of overflowing to ellipsis in a list when using vue-material?

Question: <md-list-item> <md-icon v-bind:style="{color: transcript.color}">account_circle</md-icon> <div class="md-list-item-text"> <p>{{ transcript.text }}</p> </di ...

Ways to automatically assign a unique div id to every item in a v-for loop?

Here is my Vue <div class="drag"> <h2>List 1 Draggable</h2> <ul> <li v-for="category in data"> <draggable :key="category.id" v-bind:id="categ ...

Tips for using rspec to test front end functionality?

In my Rails project, I have incorporated Vue.js using only the core library. Currently, most forms in the project are developed with Vue.js. When testing front-end features like form filling or validations using feature tests in RSpec, I found it to be qui ...