Skip to content

Storybook Support

The plugin's second attribute, PartasStorybook, turns a storybook<'T> { ... } expression into a Storybook CSF module: a default-exported meta object and one named export per story. Setting up a Storybook project that picks up Fable's output is on the Storybook ecosystem page.

open Partas.Solid
open Partas.Solid.Storybook

[<PartasStorybook>]
let private meta = storybook<Button> { () }

Operations

OperationSets
args (fun c -> c.prop <- value)Args for every story, read from the assignments
render (fun props -> Comp().spread props)The render function. Spread the props, or the controls do nothing
decorator (fun story -> div () { story () })A decorator. One per story for now
cases (fun c -> match c.prop with ...)The options of a [<StringEnum>] property (Cases)
tags [\| "autodocs" \|]The meta object's tags

Put a story name first (args "Default" (fun c -> ...)) and args, render and decorator apply to that story alone. Each story name becomes a named export.

ArgTypes

The plugin infers argTypes from the component type, picking a control from each property's type:

Property typeControlTable summary
boolbooleanbool
string, char, Regextextthe type name
[<StringEnum>] with casesradio under 6 options, else select[<StringEnum>]
integer and float typesnumber, with min/max/step from the type's rangethe type name
tuples, lists, arrays, records, unions, anonymous recordsobjectthe type shape
functions, generic parametershiddenfunction, or GenericParam and the parameter's name
option, nullablethe wrapped type's control

Properties inherited from the native tag bindings get no argType, and args set on them are dropped: btn.about <- "test" in the example below does not reach args.

Cases

A [<StringEnum>] union reaches the plugin as a plain string, so without help it gets a text control. List its cases by matching on the property inside cases. The right-hand sides do not matter, and Rider can generate the match:

cases (fun btn ->
    match btn.variant with
    | Black -> failwith "todo"
    | Red -> failwith "todo"
    | Blue when btn.variant = Black -> failwith "todo"
    | _ -> ())

XML docs

XML docs on a member val steer generation. Fable does not pass plugins the XML docs of val mutable fields.

  • <summary> is the property's description.
  • <defaultValue> is the default value in the docs table.
  • <storybook /> takes attributes:
AttributeEffect
spy="true"On a function property, sets the arg to fn() from storybook/test, so calls show in the Actions panel. Function properties named on... get this anyway
controlType="..."Overrides the control type (radio, select, color, date, range). Ignored on numbers. Experimental
controlType="false"Hides the control, as does any value starting with hide

Example

open Fable.Core.TS

[<StringEnum>]
type Variant =
    | Brown2
    | Brown
    | Black

[<Erase>]
type Button() =
    interface RegularNode

    [<DefaultValue>]
    val mutable color: string

    [<DefaultValue>]
    val mutable variant: Variant

    /// <summary>A flavour.</summary>
    [<Erase>]
    member val chocolate: string = unbox null with get, set

    /// <summary>Fired when the button is chosen.</summary>
    /// <storybook spy="true"/>
    [<Erase>]
    member val choosey: Dom.Event -> unit = unbox null with get, set

    [<SolidTypeComponent>]
    member props.__ =
        props.variant <- Variant.Black

        div (
            class' =
                match props.variant with
                | Brown -> "brown"
                | Black -> "black"
                | Brown2 -> "cho"
        )

[<PartasStorybook>]
let private meta =
    storybook<Button> {
        cases (fun btn ->
            match btn.variant with
            | Brown -> failwith "todo"
            | Black when btn.variant = Brown -> failwith "todo"
            | _ -> ())

        args (fun btn ->
            btn.variant <- Variant.Black
            btn.about <- "test")

        decorator (fun story -> div () { story () })
        decorator "brown" (fun story -> div () { div () { story () } })
        args "brown" (fun btn -> btn.variant <- Variant.Brown)
        render (fun btn -> Button(about = "test").spread btn { "Test" })
        render "brown" (fun btn -> Button(about = "brown").spread btn { "brown test" })
        args "choc" (fun btn -> ())
        render "choc" (fun btn -> Button())
    }

The plugin emits (the component itself omitted):

import { fn } from "storybook/test";

const meta = {
    args: {
        variant: "black",
        choosey: fn(),
    },
    argTypes: {
        color: {
            control: { type: "text" },
            table: { type: { summary: "string" } },
        },
        variant: {
            control: { type: "radio" },
            options: ["brown", "black"],
            table: { type: { summary: "[<StringEnum>]" } },
        },
        chocolate: {
            control: { type: "text" },
            description: "A flavour.",
            table: { type: { summary: "string" } },
        },
        choosey: {
            control: { type: false },
            description: "Fired when the button is chosen.",
            table: { type: { summary: "function" } },
        },
    },
    decorators: [(story) => <div>{story()}</div>],
    render: (btn_3) => <Button about="test" {...btn_3} n$={false}>Test</Button>,
    component: Button
};

export default meta;

export const brown = {
    args: { variant: "brown" },
    render: (PARTAS_RENDER_BUILDER) => <Button about="brown" {...PARTAS_RENDER_BUILDER} n$={false}>brown test</Button>,
    decorators: [(PARTAS_DECORATOR_BUILDER) => <div><div>{PARTAS_DECORATOR_BUILDER()}</div></div>],
}
export const choc = {
    args: {},
    render: (PARTAS_RENDER_BUILDER_2) => <Button />,
}
Edit this page