Storybook
For development and API documentation we make use of Storybook.
A Story is a specific scenario or state of a component that showcases its functionality, appearance, or behavior. It helps developers and designers visualize and test different variations of a component in isolation.
All stories for a single component are kept in a neighboring <component>.stories.ts
file.
INFO
The next sections describe the content of a *.stories.ts
file in more detail. Feel free to skip to the end to see the complete example and a TL;DR.
Meta section
First off we have the Meta
object. Here the component is described and documented. Using tsdoc, a general description is added above the meta
constant.
- The
title
attribute defines where in the storybook the component is placed and what its title is. Categories and the title are separated by slashes ("/
"). component
sets the Component that will be used for all Stories in this file. The documentation for all properties will be extracted from its types and their tsdoc block.- With the
argTypes
attribute the components property documentation can be extended and overwritten. Usually this is not necessary as Storybook infers the properties, their types and description from the component types. However as shown in the example below, it can be useful to overwrite Storybooks input element using thecontrol
property. - To highlight useful native DOM events, the
withNativeEventLogging
utility can be used. It enables logging and documentation for the defined DOM events.
Make sure to export meta
as default so Storybook can find the Story.
/**
* TODO: Add a general description for this component
*/
const meta: Meta<typeof OnyxComponent> = {
title: "Category/Subcategory/Component",
component: OnyxComponent,
argTypes: {
propName: { control: { type: "text" } }, // TODO: remove or add custom overwrites for properties
...withNativeEventLogging(["onInput", "onChange", "onFocusin", "onFocusout"]),
},
};
export default meta;
Actual Stories
Every non-default export represents a Story
, where the export name is also the name of the story. The first story is usually called Default
.
- The description of the story object is done via tsdoc.
- We use
StoryObj<typeof OnyxComponent>
to enable typed code completion. - A story must define
args
which represents the props that are passed to component.
To add further stories, add more named exports. Consider reusing the Default
stories args.
type Story = StoryObj<typeof OnyxComponent>;
/**
* TODO: Add a description for this Story
*/
export const Default = {
args: {
propName: "Value",
},
} satisfies Story;
/**
* TODO: Add a description for this Story
*/
export const Other = {
args: {
...Default.args,
otherPropName: true,
},
} satisfies Story;
Slots
Storybook treats Slots like props, therefore they are also configured via the args
. A slot and its content are defined using Render Functions. A Render Functions must return a VNode or an array of VNodes.
export const WithSlotContent = {
args: {
...Default.args,
slotName: () => [
// The `h` function takes the following form:
// h(componentOrHtmlTag, propertiesAndAttributes, innerTextOrSlotContent)
h(OnyxOtherComponent, { label: "Item 1", href: "/" }),
h("a", { target: "_blank", href: "https://onyx.schwarz" }, "onyx"),
],
},
} satisfies Story;
TL;DR
TL;DR
- Descriptions and types are extracted by Storybook from the components types and tsdoc
- Meta details are configured via a default export with type
Meta<typeof OnyxComponent>
- Stories are defined as Named Exports with type
StoryObj<typeof OnyxComponent>
- Props and slot content for a Story are passed with the
args
property - Slots are defined using Render Functions
Complete example:
import { withNativeEventLogging } from "@sit-onyx/storybook-utils";
import type { Meta, StoryObj } from "@storybook/vue3";
import { h } from "vue";
import OnyxComponent from "./OnyxComponent.vue";
import OnyxOtherComponent from "./OnyxOtherComponent.vue";
// #region meta
/**
* TODO: Add a general description for this component
*/
const meta: Meta<typeof OnyxComponent> = {
title: "Category/Subcategory/Component",
component: OnyxComponent,
argTypes: {
propName: { control: { type: "text" } }, // TODO: remove or add custom overwrites for properties
...withNativeEventLogging(["onInput", "onChange", "onFocusin", "onFocusout"]),
},
};
export default meta;
// #endregion meta
// #region story
type Story = StoryObj<typeof OnyxComponent>;
/**
* TODO: Add a description for this Story
*/
export const Default = {
args: {
propName: "Value",
},
} satisfies Story;
/**
* TODO: Add a description for this Story
*/
export const Other = {
args: {
...Default.args,
otherPropName: true,
},
} satisfies Story;
// #endregion story
// #region slot
export const WithSlotContent = {
args: {
...Default.args,
slotName: () => [
// The `h` function takes the following form:
// h(componentOrHtmlTag, propertiesAndAttributes, innerTextOrSlotContent)
h(OnyxOtherComponent, { label: "Item 1", href: "/" }),
h("a", { target: "_blank", href: "https://onyx.schwarz" }, "onyx"),
],
},
} satisfies Story;
// #endregion slot