Skip to main content

Generated Controls

Using Controls, and the Controls addon, is one of the main reasons for choosing Storybook. They really help bring your components to life! To quote from the source:

Storybook Controls gives you a graphical UI to interact with a component's arguments dynamically without needing to code. It creates an addon panel next to your component examples ('stories'), so you can edit them live.
Controls docs Storybook

Just that this (normally) comes with one caveat: You'll have to decide to either:

  • Live with the inferred defaults the addon automatically creates for you (but this is uncurated), or
  • explicitly define controls for your properties yourself (Storybook accounts for that, but it's extra work), or finally
  • just disable Controls to reduce potential confusion

Usage with kickstartDS

If you think about it, everything that should be available in your Controls is already defined by your respective component API, anyway. This is why we've written some helpers that do this for you... as long as a valid JSON Schema is found for your component, it will automatically be decorated with documentation for all its properties in Storybook, and in your Controls addon.

Features

Using our helpers provides the following conversion for your JSON Schema:

  • Add type of Control based on JSON Schema field type
  • Add title, description and meta info like required
  • Automatically flatten nested structures for Storybook Args compatibility
  • Use that information to intelligently group Controls for a better overview
Image showing the generated Controls in action

Implementation

You can find the implementation in our Github mono-repository:
https://github.com/kickstartDS/kickstartDS/tree/next/packages/components/core/source/storybook/helpers.ts

There are four functions exported to be used in your own stories for Storybook Controls:

FunctionDescription
getArgsSharedTakes a JSON Schema and returns Args and ArgTypes
packTakes a component instance (JSON object) and returns it with flattened keys
unpackTakes a flattened instance and returns the complete JSON object
unpackDecoratorDecorator you can use to add unpacking to all stories

Integration

Integration works by adding the unpackDecorator to all of your stories in .storybook/preview.js:

.storybook/preview.js

_10
import {
_10
unpackDecorator,
_10
} from "@kickstartds/core/lib/storybook";
_10
_10
[...]
_10
_10
export const decorators = [
_10
unpackDecorator,
_10
];

This ensures props passed down to our components by Storybook Args get converted back to their original, deep JSON structure by default.

And in stories themselves you'll have to use getArgsShared to get Args and ArgTypes to configure your components. See the example below, on how that would look for a Button story.

Example

Component

Let's take the Button as an example. This is the JSON Schema we've defined for an adapted version of it, as part of the "Create your Design System" guide:

button.schema.json

_41
{
_41
"$schema": "http://json-schema.org/draft-07/schema#",
_41
"$id": "http://schema.mydesignsystem.com/button.schema.json",
_41
"title": "Button",
_41
"description": "Component used for user interaction",
_41
"type": "object",
_41
"properties": {
_41
"label": {
_41
"type": "string",
_41
"title": "Label",
_41
"description": "Text content to display inside the button"
_41
},
_41
"target": {
_41
"type": "string",
_41
"title": "Target",
_41
"description": "Target that should be linked, makes the button behave like a link semantically",
_41
"format": "uri"
_41
},
_41
"variant": {
_41
"type": "string",
_41
"title": "Variant",
_41
"description": "Variant of button to be used",
_41
"enum": ["primary", "secondary", "tertiary"],
_41
"default": "secondary"
_41
},
_41
"size": {
_41
"type": "string",
_41
"title": "Size",
_41
"description": "Size of button to use",
_41
"enum": ["small", "medium", "large"],
_41
"default": "medium"
_41
},
_41
"disabled": {
_41
"type": "boolean",
_41
"title": "Disabled?",
_41
"description": "Whether the button should be disabled",
_41
"default": false
_41
}
_41
},
_41
"required": ["label"]
_41
}

Using our helpers inside your story files automatically generates the following Controls for our Button, while also generating the full documentation block for the Docs view

Story

The corresponding story for our Button might look a little like this when adding all our helpers:

Import helpers and schema

First we import all helpers we'll use, and the dereferenced version ouf our components JSON Schema.

Get Args and ArgTypes

We pull out args (as defaultArgs) and argTypes for our schema out of getArgsShared.

Pass through args in Template

We tell our Template to pass through our unpacked (by the unpackDecorator) args to the component.

Add Args and ArgTypes to story

Setting args and argTypes in the default export ensures all variants of the story we'll use will share the same, correct definition.

Pack arguments given to story instances

Finally, we'll need to pack options given to our stories / components.

src/components/button/Button.stories.jsx

_24
import { Button } from "./ButtonComponent";
_24
import { pack, getArgsShared } from "@kickstartds/core/lib/storybook";
_24
import ButtonStories from "@kickstartds/base/lib/button/button.stories";
_24
import schema from "./button.schema.dereffed.json";
_24
_24
const { defaultArgs: args, argTypes } = getArgsShared(schema);
_24
_24
const Template = (args) => <Button {...args} />;
_24
_24
export default {
_24
...ButtonStories,
_24
title: "Components/Button",
_24
args,
_24
argTypes,
_24
parameters: {
_24
jsonschema: schema,
_24
},
_24
};
_24
_24
export const NativeButton = Template.bind({});
_24
NativeButton.args = pack({
_24
label: "Click me",
_24
variant: "primary",
_24
});

Have a look at the "Adapt Button component" guide for a full version of the Button component shown here.