Skip to content

Commit 4575cb8

Browse files
authored
Merge pull request #10 from kgoggin/component-story-format
CSF Bindings
2 parents a53b055 + c286cb0 commit 4575cb8

File tree

6 files changed

+239
-9
lines changed

6 files changed

+239
-9
lines changed

README.md

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ BuckleScript bindings for **[Storybook](https://storybook.js.org/)**.
44

55
The goal of this project is to provide bindings for the main Storybook API, as well as the official add-ons. Currently it supports:
66

7-
* [actions](https://github.com/storybooks/storybook/tree/master/addons/actions)
8-
* [knobs](https://github.com/storybooks/storybook/tree/master/addons/knobs)
9-
* [addons API](https://storybook.js.org/addons/writing-addons/)
7+
- [actions](https://github.com/storybooks/storybook/tree/master/addons/actions)
8+
- [knobs](https://github.com/storybooks/storybook/tree/master/addons/knobs)
9+
- [addons API](https://storybook.js.org/addons/writing-addons/)
1010

1111
## Getting Started
1212

@@ -26,9 +26,9 @@ In your `/.storybook/config.js`, import your stories from wherever your compiled
2626
For example, if you're writing your stories inside a `__stories__` directory, and `bsb` is configured for a standard build, you might do something like:
2727

2828
```javascript
29-
const req = require.context('../lib/js', true, /\__stories__\/.*.js$/);
29+
const req = require.context("../lib/js", true, /\__stories__\/.*.js$/);
3030
configure(() => {
31-
req.keys().forEach(module => {
31+
req.keys().forEach((module) => {
3232
req(module).default();
3333
});
3434
}, module);
@@ -39,8 +39,12 @@ or if you are using Storybook v6.
3939
```javascript
4040
/* .storybook/main.js */
4141
module.exports = {
42-
stories: ['../stories/**/*.js'],
43-
addons: ['@storybook/addon-actions', '@storybook/addon-links', '@storybook/addon-knobs/register'],
42+
stories: ["../stories/**/*.js"],
43+
addons: [
44+
"@storybook/addon-actions",
45+
"@storybook/addon-links",
46+
"@storybook/addon-knobs/register",
47+
],
4448
};
4549
```
4650

@@ -61,9 +65,24 @@ storiesOf("My First Reason Story", _module)
6165

6266
Storybook uses a reference to the `module` global provided by webpack to facilitate hot-reloading. We'll access that via the `[%bs.raw]` decorator.
6367

68+
## Writing a CSF Story
69+
70+
If you'd prefer to use the newer [Component Story Format](https://storybook.js.org/docs/formats/component-story-format/), you can do that as well:
71+
72+
```reason
73+
open BsStorybook;
74+
75+
let default = CSF.make(~title="My CSF Story", ());
76+
77+
let button = () => <MyButton />;
78+
79+
button->CSF.addMeta(~name="Plain Button", ());
80+
```
81+
6482
## The Actions Addon
6583

6684
The action addon's API is essentially unchanged from its JS implementation:
85+
6786
> Make sure that you have `@storybook/addon-actions` in the config
6887
6988
```reason
@@ -75,6 +94,7 @@ let clickAction = Action.action("I Clicked The Button!");
7594
## The Knobs Addon
7695

7796
To use [knobs](https://github.com/storybooks/storybook/tree/master/addons/knobs) you have twoo ways:
97+
7898
> Make sure that you have @storybook/addon-knobs/register in the config
7999
80100
#### As a decorator
@@ -190,6 +210,7 @@ let obj = Knobs.object_(~label="User", ~defaultValue={"color": "grey"}, ());
190210
```
191211

192212
### Js.Dict
213+
193214
> https://bucklescript.github.io/bucklescript/api/Js.Dict.html
194215
195216
```reason

example/bindings/Storybook.bs.js

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11

22

33
import * as List from "bs-platform/lib/es6/list.js";
4+
import * as Belt_Option from "bs-platform/lib/es6/belt_Option.js";
45
import * as React from "@storybook/react";
56
import * as Js_null_undefined from "bs-platform/lib/es6/js_null_undefined.js";
67
import * as React$1 from "@storybook/addon-knobs/react";
@@ -31,8 +32,8 @@ function text(label, defaultValue, param) {
3132
return React$1.text(label, Js_null_undefined.fromOption(defaultValue));
3233
}
3334

34-
function $$boolean(label, $staropt$star, param) {
35-
var defaultValue = $staropt$star !== undefined ? $staropt$star : false;
35+
function $$boolean(label, defaultValueOpt, param) {
36+
var defaultValue = defaultValueOpt !== undefined ? defaultValueOpt : false;
3637
return React$1.boolean(label, defaultValue);
3738
}
3839

@@ -55,13 +56,44 @@ var Addons = { };
5556

5657
var Action = { };
5758

59+
var bsExports = ["$$default"];
60+
61+
function make(title, component, decorators, parameters, includeStories, excludeStories, param) {
62+
return {
63+
title: title,
64+
component: Js_null_undefined.fromOption(component),
65+
decorators: Js_null_undefined.fromOption(decorators),
66+
parameters: Js_null_undefined.fromOption(parameters),
67+
includeStories: Js_null_undefined.fromOption(includeStories),
68+
excludeStories: Belt_Option.mapWithDefault(excludeStories, bsExports, (function (es) {
69+
return es.concat(bsExports);
70+
}))
71+
};
72+
}
73+
74+
function addMeta(csfStory, name, decorators, parameters, param) {
75+
csfStory.story = {
76+
name: Js_null_undefined.fromOption(name),
77+
decorators: Js_null_undefined.fromOption(decorators),
78+
parameters: Js_null_undefined.fromOption(parameters)
79+
};
80+
return /* () */0;
81+
}
82+
83+
var CSF = {
84+
bsExports: bsExports,
85+
make: make,
86+
addMeta: addMeta
87+
};
88+
5889
export {
5990
Story ,
6091
Main ,
6192
Notes ,
6293
Knobs ,
6394
Addons ,
6495
Action ,
96+
CSF ,
6597

6698
}
6799
/* @storybook/react Not a pure module */

example/bindings/Storybook.re

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,3 +182,61 @@ module Action = {
182182
[@bs.val] [@bs.module "@storybook/addon-actions"]
183183
external action: string => actionHandler('a);
184184
};
185+
186+
module CSF = {
187+
type csfStory = unit => React.element;
188+
type decorator = csfStory => React.element;
189+
190+
type csfMetaData('params) = {
191+
title: string,
192+
component: Js.Nullable.t(unit => React.element),
193+
decorators: Js.Nullable.t(array(decorator)),
194+
parameters: Js.Nullable.t('params),
195+
includeStories: Js.Nullable.t(array(string)),
196+
excludeStories: Js.Nullable.t(array(string)),
197+
};
198+
199+
type storyMetadata('params) = {
200+
name: Js.Nullable.t(string),
201+
decorators: Js.Nullable.t(array(decorator)),
202+
parameters: Js.Nullable.t('params),
203+
};
204+
205+
/**
206+
* Bucklescript renames a variables called `default` to `$$default` and exports that as well.
207+
*/
208+
let bsExports = [|"$$default"|];
209+
210+
[@bs.set]
211+
external story: (csfStory, storyMetadata('params)) => unit = "story";
212+
213+
let make =
214+
(
215+
~title,
216+
~component=?,
217+
~decorators=?,
218+
~parameters=?,
219+
~includeStories=?,
220+
~excludeStories=?,
221+
(),
222+
) => {
223+
title,
224+
component: component->Js.Nullable.fromOption,
225+
decorators: decorators->Js.Nullable.fromOption,
226+
parameters: parameters->Js.Nullable.fromOption,
227+
includeStories: includeStories->Js.Nullable.fromOption,
228+
excludeStories:
229+
excludeStories
230+
->Belt.Option.mapWithDefault(bsExports, es =>
231+
es->Js.Array2.concat(bsExports)
232+
)
233+
->Js.Nullable.return,
234+
};
235+
236+
let addMeta = (csfStory, ~name=?, ~decorators=?, ~parameters=?, ()) =>
237+
csfStory->story({
238+
name: name->Js.Nullable.fromOption,
239+
decorators: decorators->Js.Nullable.fromOption,
240+
parameters: parameters->Js.Nullable.fromOption,
241+
});
242+
};
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
2+
3+
import * as React from "react";
4+
import * as MyButton from "../src/MyButton.bs.js";
5+
import * as Storybook from "../bindings/Storybook.bs.js";
6+
import * as AddonActions from "@storybook/addon-actions";
7+
import * as React$1 from "@storybook/addon-knobs/react";
8+
9+
var $$default = Storybook.CSF.make("Reason CSF Story", undefined, [React$1.withKnobs], undefined, undefined, undefined, /* () */0);
10+
11+
function renderButton(param) {
12+
return React.createElement(MyButton.make, { });
13+
}
14+
15+
Storybook.CSF.addMeta(renderButton, "render plain button", undefined, undefined, /* () */0);
16+
17+
function knobTest(param) {
18+
var name = Storybook.Knobs.text("Name", "Kyle", /* () */0);
19+
var age = React$1.number("Age", 37, {
20+
range: false,
21+
min: 19,
22+
max: 50,
23+
step: 1
24+
});
25+
var content = "I am " + (name + (" and I am " + (String(age | 0) + " old")));
26+
return React.createElement("span", {
27+
onClick: AddonActions.action("Test")
28+
}, content);
29+
}
30+
31+
export {
32+
$$default ,
33+
$$default as default,
34+
renderButton ,
35+
knobTest ,
36+
37+
}
38+
/* default Not a pure module */
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
open Storybook;
2+
3+
let default =
4+
CSF.make(~title="Reason CSF Story", ~decorators=[|Knobs.withKnobs|], ());
5+
6+
let renderButton = () => <MyButton />;
7+
renderButton->CSF.addMeta(~name="render plain button", ());
8+
9+
let knobTest = () => {
10+
let name = Knobs.text(~label="Name", ~defaultValue="Kyle", ());
11+
let age =
12+
Knobs.number(
13+
~label="Age",
14+
~defaultValue=37.,
15+
~range={range: false, min: 19., max: 50., step: 1.},
16+
(),
17+
);
18+
19+
let content =
20+
"I am "
21+
++ name
22+
++ " and I am "
23+
++ age->Belt.Float.toInt->Belt.Int.toString
24+
++ " old";
25+
<span onClick={Action.action("Test")}> content->React.string </span>;
26+
};

src/CSF.re

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
type csfStory = unit => React.element;
2+
type decorator = csfStory => React.element;
3+
4+
type csfMetadata('params) = {
5+
title: string,
6+
component: Js.Nullable.t(unit => React.element),
7+
decorators: Js.Nullable.t(array(decorator)),
8+
parameters: Js.Nullable.t('params),
9+
includeStories: Js.Nullable.t(array(string)),
10+
excludeStories: Js.Nullable.t(array(string)),
11+
};
12+
13+
type storyMetadata('params) = {
14+
name: Js.Nullable.t(string),
15+
decorators: Js.Nullable.t(array(decorator)),
16+
parameters: Js.Nullable.t('params),
17+
};
18+
19+
/**
20+
* Bucklescript renames a variables called `default` to `$$default` and exports that as well.
21+
*/
22+
let bsExports = [|"$$default"|];
23+
24+
[@bs.set]
25+
external story: (csfStory, storyMetadata('params)) => unit = "story";
26+
27+
let make =
28+
(
29+
~title,
30+
~component=?,
31+
~decorators=?,
32+
~parameters=?,
33+
~includeStories=?,
34+
~excludeStories=?,
35+
(),
36+
) => {
37+
title,
38+
component: component->Js.Nullable.fromOption,
39+
decorators: decorators->Js.Nullable.fromOption,
40+
parameters: parameters->Js.Nullable.fromOption,
41+
includeStories: includeStories->Js.Nullable.fromOption,
42+
excludeStories:
43+
excludeStories
44+
->Belt.Option.mapWithDefault(bsExports, es =>
45+
es->Js.Array2.concat(bsExports)
46+
)
47+
->Js.Nullable.return,
48+
};
49+
50+
let addMeta = (csfStory, ~name=?, ~decorators=?, ~parameters=?, ()) =>
51+
csfStory->story({
52+
name: name->Js.Nullable.fromOption,
53+
decorators: decorators->Js.Nullable.fromOption,
54+
parameters: parameters->Js.Nullable.fromOption,
55+
});

0 commit comments

Comments
 (0)