Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor knobs - no longer include all runtimes #1832

Merged
merged 5 commits into from
Sep 11, 2017
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 32 additions & 16 deletions addons/knobs/README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
# Storybook Addon Knobs

Storybook Addon Knobs allow you to edit React props dynamically using the Storybook UI.
You can also use Knobs as a dynamic variable inside stories in [Storybook](https://storybook.js.org).

[![Greenkeeper badge](https://badges.greenkeeper.io/storybooks/storybook.svg)](https://greenkeeper.io/)
[![Build Status](https://travis-ci.org/storybooks/storybook.svg?branch=master)](https://travis-ci.org/storybooks/storybook)
[![Build Status on CircleCI](https://circleci.com/gh/storybooks/storybook.svg?style=shield)](https://circleci.com/gh/storybooks/storybook)
[![CodeFactor](https://www.codefactor.io/repository/github/storybooks/storybook/badge)](https://www.codefactor.io/repository/github/storybooks/storybook)
[![Known Vulnerabilities](https://snyk.io/test/github/storybooks/storybook/8f36abfd6697e58cd76df3526b52e4b9dc894847/badge.svg)](https://snyk.io/test/github/storybooks/storybook/8f36abfd6697e58cd76df3526b52e4b9dc894847)
[![BCH compliance](https://bettercodehub.com/edge/badge/storybooks/storybook)](https://bettercodehub.com/results/storybooks/storybook) [![codecov](https://codecov.io/gh/storybooks/storybook/branch/master/graph/badge.svg)](https://codecov.io/gh/storybooks/storybook)
[![Storybook Slack](https://storybooks-slackin.herokuapp.com/badge.svg)](https://storybooks-slackin.herokuapp.com/)

Storybook Addon Knobs allow you to edit React props dynamically using the Storybook UI.
You can also use Knobs as a dynamic variable inside stories in [Storybook](https://storybook.js.org).
[![BCH compliance](https://bettercodehub.com/edge/badge/storybooks/storybook)](https://bettercodehub.com/results/storybooks/storybook) [![codecov](https://codecov.io/gh/storybooks/storybook/branch/master/graph/badge.svg)](https://codecov.io/gh/storybooks/storybook)
[![Storybook Slack](https://now-examples-slackin-nqnzoygycp.now.sh/badge.svg)](https://now-examples-slackin-nqnzoygycp.now.sh/)
[![Backers on Open Collective](https://opencollective.com/storybook/backers/badge.svg)](#backers) [![Sponsors on Open Collective](https://opencollective.com/storybook/sponsors/badge.svg)](#sponsors)

This addon works with Storybook for:
[React](https://github.com/storybooks/storybook/tree/master/app/react).
[React Native](https://github.com/storybooks/storybook/tree/master/app/react-native).
[Vue](https://github.com/storybooks/storybook/tree/master/app/vue).

This is how Knobs look like:

Expand All @@ -37,7 +40,7 @@ Now, write your stories with knobs.

```js
import { storiesOf } from '@storybook/react';
import { withKnobs, text, boolean, number } from '@storybook/addon-knobs';
import { addonKnobs, text, boolean, number } from '@storybook/addon-knobs/dist/react';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why did addonKnobs come back? is this a bad merge?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my bad indeed, good catch


const stories = storiesOf('Storybook Knobs', module);

Expand All @@ -50,18 +53,33 @@ stories.add('with a button', () => (
<button disabled={boolean('Disabled', false)} >
{text('Label', 'Hello Button')}
</button>
))
));

const options = {};
// Knobs as dynamic variables.
stories.add('as dynamic variables', () => {
stories.add('as dynamic variables', withKnobsOptions(options)(() => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we make this withKnobs(options)? To make it consistent with withNotes / withInfo?
OR can we make this wrapperKnobs(options) if we are not ready to migrate the addon API?

const name = text('Name', 'Arunoda Susiripala');
const age = number('Age', 89);

const content = `I am ${name} and I'm ${age} years old.`;
return (<div>{content}</div>);
});
}));
```

> In the case of Vue, use these imports:
>
> ```js
> import { storiesOf } from '@storybook/vue';
> import { addonKnobs, text, boolean, number } from '@storybook/addon-knobs/dist/vue';
> ```
>
> In the case of React-Native, use these imports:
>
> ```js
> import { storiesOf } from '@storybook/react-native';
> import { addonKnobs, text, boolean, number } from '@storybook/addon-knobs/dist/react';
> ```

You can see your Knobs in a Storybook panel as shown below.

![](docs/demo.png)
Expand Down Expand Up @@ -236,12 +254,10 @@ const value = date(label, defaultValue);
If you feel like this addon is not performing well enough there is an option to use `withKnobsOptions` instead of `withKnobs`.
Usage:

```js
story.addDecorator(withKnobsOptions({
debounce: { wait: number, leading: boolean}, // Same as lodash debounce.
timestamps: true // Doesn't emit events while user is typing.
}));
```
story.addDecorator(withKnobsOptions({
debounce: { wait: number, leading: boolean}, // Same as lodash debounce.
timestamps: true // Doesn't emit events while user is typing.
}));

## Typescript

Expand Down
55 changes: 55 additions & 0 deletions addons/knobs/src/base.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import KnobManager from './KnobManager';

export const manager = new KnobManager();

export function knob(name, options) {
return manager.knob(name, options);
}

export function text(name, value) {
return manager.knob(name, { type: 'text', value });
}

export function boolean(name, value) {
return manager.knob(name, { type: 'boolean', value });
}

export function number(name, value, options = {}) {
const defaults = {
range: false,
min: 0,
max: 10,
step: 1,
};

const mergedOptions = { ...defaults, ...options };

const finalOptions = {
...mergedOptions,
type: 'number',
value,
};

return manager.knob(name, finalOptions);
}

export function color(name, value) {
return manager.knob(name, { type: 'color', value });
}

export function object(name, value) {
return manager.knob(name, { type: 'object', value });
}

export function select(name, options, value) {
return manager.knob(name, { type: 'select', options, value });
}

export function array(name, value, separator = ',') {
return manager.knob(name, { type: 'array', value, separator });
}

export function date(name, value = new Date()) {
const proxyValue = value ? value.getTime() : null;
return manager.knob(name, { type: 'date', value: proxyValue });
}
62 changes: 9 additions & 53 deletions addons/knobs/src/index.js
Original file line number Diff line number Diff line change
@@ -1,66 +1,22 @@
import { window } from 'global';
import deprecate from 'util-deprecate';
import addons from '@storybook/addons';
import KnobManager from './KnobManager';

import { vueHandler } from './vue';
import { reactHandler } from './react';

const manager = new KnobManager();

export function knob(name, options) {
return manager.knob(name, options);
}

export function text(name, value) {
return manager.knob(name, { type: 'text', value });
}

export function boolean(name, value) {
return manager.knob(name, { type: 'boolean', value });
}

export function number(name, value, options = {}) {
const defaults = {
range: false,
min: 0,
max: 10,
step: 1,
};

const mergedOptions = { ...defaults, ...options };

const finalOptions = {
...mergedOptions,
type: 'number',
value,
};
import { knob, text, boolean, number, color, object, array, date, manager } from './base';

return manager.knob(name, finalOptions);
}

export function color(name, value) {
return manager.knob(name, { type: 'color', value });
}

export function object(name, value) {
return manager.knob(name, { type: 'object', value });
}
export { knob, text, boolean, number, color, object, array, date };

export function select(name, options, value) {
return manager.knob(name, { type: 'select', options, value });
}

export function array(name, value, separator = ',') {
return manager.knob(name, { type: 'array', value, separator });
}

export function date(name, value = new Date()) {
const proxyValue = value ? value.getTime() : null;
return manager.knob(name, { type: 'date', value: proxyValue });
}
deprecate(
() => {},
'Using @storybook/knobs directly is discouraged, please use @storybook/knobs/dist/{{framework}}'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addons currently adopt the following es5 pattern for registration /register.js:

require('./dist/register')

Can we do something analogous for frameworks?

);

// "Higher order component" / wrapper style API
// In 3.3, this will become `withKnobs`, once our decorator API supports it.
// See https://github.com/storybooks/storybook/pull/1527
// See https://github.com/storybooks/storybook/pull/1527
function wrapperKnobs(options) {
const channel = addons.getChannel();
manager.setChannel(channel);
Expand Down
23 changes: 23 additions & 0 deletions addons/knobs/src/react/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import React from 'react';
import addons from '@storybook/addons';

import WrapStory from './WrapStory';

import { knob, text, boolean, number, color, object, array, date, select, manager } from '../base';

export { knob, text, boolean, number, color, object, array, date, select };

/**
* Handles a react story
*/
Expand All @@ -9,3 +15,20 @@ export const reactHandler = (channel, knobStore) => getStory => context => {
const props = { context, storyFn: getStory, channel, knobStore, initialContent };
return <WrapStory {...props} />;
};

function wrapperKnobs(options) {
const channel = addons.getChannel();
manager.setChannel(channel);

if (options) channel.emit('addon:knobs:setOptions', options);

return reactHandler(channel, manager.knobStore);
}

export function withKnobs(storyFn, context) {
return wrapperKnobs()(storyFn)(context);
}

export function withKnobsOptions(options = {}) {
return (storyFn, context) => wrapperKnobs(options)(storyFn)(context);
}
26 changes: 26 additions & 0 deletions addons/knobs/src/vue/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
import addons from '@storybook/addons';

import { knob, text, boolean, number, color, object, array, date, select, manager } from '../base';

export { knob, text, boolean, number, color, object, array, date, select };

export const vueHandler = (channel, knobStore) => getStory => context => ({
render(h) {
return h(getStory(context));
Expand Down Expand Up @@ -35,3 +41,23 @@ export const vueHandler = (channel, knobStore) => getStory => context => ({
knobStore.unsubscribe(this.setPaneKnobs);
},
});

// "Higher order component" / wrapper style API
// In 3.3, this will become `withKnobs`, once our decorator API supports it.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is 3.3? Should we handle this API migration now?

Copy link
Member Author

@ndelangen ndelangen Sep 11, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We will delete that file with 4.0.

Let's keep this as backwards compatible as possible.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK then the comment is wrong?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I think the solution @tmeasday and I have in mind is backwards-compatible.

// See https://github.com/storybooks/storybook/pull/1527
function wrapperKnobs(options) {
const channel = addons.getChannel();
manager.setChannel(channel);

if (options) channel.emit('addon:knobs:setOptions', options);

return vueHandler(channel, manager.knobStore);
}

export function withKnobs(storyFn, context) {
return wrapperKnobs()(storyFn)(context);
}

export function withKnobsOptions(options = {}) {
return (storyFn, context) => wrapperKnobs(options)(storyFn)(context);
}
2 changes: 1 addition & 1 deletion examples/cra-kitchen-sink/src/stories/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
array,
date,
object,
} from '@storybook/addon-knobs';
} from '@storybook/addon-knobs/dist/react';
import centered from '@storybook/addon-centered';
import { withInfo } from '@storybook/addon-info';

Expand Down
11 changes: 10 additions & 1 deletion examples/crna-kitchen-sink/storybook/stories/Knobs/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
import React from 'react';
import { View, Text } from 'react-native';

import { text, number, boolean, color, select, array, date, object } from '@storybook/addon-knobs';
import {
text,
number,
boolean,
color,
select,
array,
date,
object,
} from '@storybook/addon-knobs/dist/react';

export default () => {
const name = text('Name', 'Storyteller');
Expand Down
11 changes: 10 additions & 1 deletion examples/react-native-vanilla/storybook/stories/Knobs/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
import React from 'react';
import { View, Text } from 'react-native';

import { text, number, boolean, color, select, array, date, object } from '@storybook/addon-knobs';
import {
text,
number,
boolean,
color,
select,
array,
date,
object,
} from '@storybook/addon-knobs/dist/react';

export default () => {
const name = text('Name', 'Storyteller');
Expand Down
2 changes: 1 addition & 1 deletion examples/react-native-vanilla/storybook/stories/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Text } from 'react-native';
import { storiesOf } from '@storybook/react-native';
import { action } from '@storybook/addon-actions';
import { linkTo } from '@storybook/addon-links';
import { withKnobs } from '@storybook/addon-knobs';
import { withKnobs } from '@storybook/addon-knobs/dist/react';

import knobsWrapper from './Knobs';
import Button from './Button';
Expand Down
2 changes: 1 addition & 1 deletion examples/vue-kitchen-sink/src/stories/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
select,
color,
date,
} from '@storybook/addon-knobs';
} from '@storybook/addon-knobs/dist/vue';
import Centered from '@storybook/addon-centered';

import MyButton from './Button.vue';
Expand Down