uiowa / uids

UI Design System
http://uids.brand.uiowa.edu
7 stars 1 forks source link

4.x Storybook + Vue 3 woes #695

Open pyrello opened 2 years ago

pyrello commented 2 years ago

To start, here is a recap of some of the decisions that were made:

Issues

Default slots in Storybook

https://github.com/uiowa/uids/blob/641a6cbcc80bf9fed4b197671a542b44fbbedf3b/src/components/iowa-bar/IowaBar.stories.js#L27-L33 In Vue, when a default slot (not named, e.g. <slot></slot>) is used, it takes all top level content that is not <template> tags and renders that in the place of the default slot. In the example above, this would be the {{ args.default }} on line 30. However, Vue 3 also strictly sanitizes any variable output through the mustache syntax {{ }}, which is how we are outputting our default slot in the example. This means that if args.default contains any HTML it will be stripped out.

In a regular Vue app, this usually isn't a problem, because we can directly add the HTML content inside our our custom component tags. Let's imagine that args.default = '<h1 class="site-name">Brand</h1>'. If we did the following in a Vue app, the magic of slots would cause our default slot to be output correctly:

<template>
  <uids-brand-bar>
    <h1 class="site-name">Brand</h1>
  </uids-brand-bar>
</template>

In Storybook, this is a problem because everything that gets passed into the story component template is contained in the args variable, meaning that you have to set your props and slots alike there. So it is necessary to figure out how to output the variable. The alternative to the embedded example would be to use an extra markup element and attach the variable as its child node with the v-html directive, like so: <div v-html="args.default"></div>. That will correctly output our args.default with HTML intact, but with an extra div wrapped around it in the default slot. This becomes:

<uids-brand-bar>
  <div>
    <h1 class="site-name">Brand</h1>
  </div>
</uids-brand-bar>

For us this is problematic in part because extra layers of markup can affect whether our CSS rules apply or not. It also gets output as part of the example HTML, so if someone was using the Storybook site as reference to get HTML code snippets to use with UIDS, then they would be including extra unnecessary markup. We could potentially work around this in our CSS rules, but changing our CSS to accommodate the quirks of our development tool seems like a bad idea.

I have figured out a way around this by adding multiple "Template" objects and manipulating the template based on the variation, as shown here: https://github.com/uiowa/uids/blob/233d61a653dba35df25f7a77b8ef4560bfe71cf1/src/components/brand-bar/BrandBar.stories.js. But the side effect of this is that the controls don't work correctly for all examples.

Explicit arg binding in Storybook

Another side effect of the fact that the args variable contains both our props and slots is that we lose the ability to bind our props using v-bind. That might look something like this:

<uids-brand-bar v-bind="args">...</uids-brand-bar>

This results in the underlying component being rendered as:

<div class="iowa-bar" default="<h1 class="site-name">Brand</h1>">...</div>

Instead, it is necessary to explicitly bind each argument:

<uids-brand-bar :narrow="args.narrow">...</uids-brand-bar>

This might ultimately be better because we are able to see more clearly what is being passed into the component, but it also means that for any updates that we make to our component's props, we will have to make the same change to the story.

pyrello commented 2 years ago

Started this discussion to see if I can get any help getting pointed in the right direction: https://github.com/storybookjs/storybook/discussions/18382