Skip to content

Commit

Permalink
Add support for addonKnobs, rename withX to addonX to avoid name coll…
Browse files Browse the repository at this point in the history
…ision
  • Loading branch information
alexandrebodin committed Jun 16, 2017
1 parent 5784a79 commit bac1905
Show file tree
Hide file tree
Showing 14 changed files with 162 additions and 106 deletions.
7 changes: 4 additions & 3 deletions addons/knobs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,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';

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

Expand All @@ -52,14 +52,15 @@ stories.add('with a button', () => (
</button>
))

const options = {};
// Knobs as dynamic variables.
stories.add('as dynamic variables', () => {
stories.add('as dynamic variables', addonKnobs(options)(() => {
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>);
});
}));
```

You can see your Knobs in a Storybook panel as shown below.
Expand Down
3 changes: 2 additions & 1 deletion addons/knobs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
"prop-types": "^15.5.8",
"react-color": "^2.11.4",
"react-datetime": "^2.8.10",
"react-textarea-autosize": "^4.3.0"
"react-textarea-autosize": "^4.3.0",
"util-deprecate": "1.0.2"
},
"devDependencies": {
"@types/node": "^7.0.12",
Expand Down
32 changes: 0 additions & 32 deletions addons/knobs/src/KnobManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,38 +53,6 @@ export default class KnobManager {
return knobStore.get(name).value;
}

// wrapStory(channel, storyFn, context) {
// this.channel = channel;
// const key = `${context.kind}:::${context.story}`;
// let knobStore = this.knobStoreMap[key];

// if (!knobStore) {
// knobStore = this.knobStoreMap[key] = new KnobStore(); // eslint-disable-line
// }

// this.knobStore = knobStore;
// knobStore.markAllUnused();
// const initialContent = storyFn(context);
// const props = { context, storyFn, channel, knobStore, initialContent };

// if (window.STORYBOOK_ENV === 'vue') {
// channel.on('addon:knobs:knobChange', change => {
// const { name, value } = change;
// // Update the related knob and it's value.
// const knobOptions = knobStore.get(name);
// knobOptions.value = value;

// knobStore.markAllUnused();

// initialContent.$forceUpdate();
// });

// return initialContent;
// }

// return <WrapStory {...props} />;
// }

_mayCallChannel() {
// Re rendering of the story may cause changes to the knobStore. Some new knobs maybe added and
// Some knobs may go unused. So we need to update the panel accordingly. For example remove the
Expand Down
75 changes: 48 additions & 27 deletions addons/knobs/src/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
// import { window } from 'global';
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();

Expand Down Expand Up @@ -56,36 +59,54 @@ export function date(name, value = new Date()) {
return manager.knob(name, { type: 'date', value: proxyValue });
}

// export function withKnobs(storyFn, context) {
// const channel = addons.getChannel();
// return manager.wrapStory(channel, storyFn, context);
function oldKnobs(storyFn, context) {
const channel = addons.getChannel();
manager.initStore(channel);
return reactHandler(channel, manager.knobStore)(storyFn)(context);
}

// export function withKnobsOptions(options = {}) {
// return (...args) => {
// const channel = addons.getChannel();
// channel.emit('addon:knobs:setOptions', options);
function oldKnobsWithOptions(options = {}) {
return (...args) => {
const channel = addons.getChannel();
channel.emit('addon:knobs:setOptions', options);

// return withKnobs(...args);
// };
// }
return oldKnobs(...args);
};
}

export function withKnobs() {
Object.defineProperty(exports, 'withKnobs', {
configurable: true,
enumerable: true,
get: deprecate(
() => oldKnobs,
'@storybook/addon-knobs withKnobs decorator is deprecated, use addonKnobs() instead. See https://github.com/storybooks/storybook/tree/master/addons/knobs'
),
});

Object.defineProperty(exports, 'withKnobsOptions', {
configurable: true,
enumerable: true,
get: deprecate(
() => oldKnobsWithOptions,
'@storybook/addon-knobs withKnobsOptions decorator is deprecated, use addonKnobs() instead. See https://github.com/storybooks/storybook/tree/master/addons/knobs'
),
});

export function addonKnobs(options) {
const channel = addons.getChannel();
manager.initStore(channel);

return storyFn => context => ({
render(h) {
const story = storyFn(context);
return h(typeof story === 'string' ? { template: story } : story);
},
created() {
channel.on('addon:knobs:knobChange', change => {
const { name, value } = change;
// Update the related knob and it's value.
const knobOptions = manager.knobStore.get(name);
knobOptions.value = value;
this.$forceUpdate();
});
},
});
if (options) channel.emit('addon:knobs:setOptions', options);

switch (window.STORYBOOK_ENV) {
case 'vue': {
return vueHandler(channel, manager.knobStore);
}
case 'react': {
return reactHandler(channel, manager.knobStore);
}
default: {
return reactHandler(channel, manager.knobStore);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ export default class WrapStory extends React.Component {
this.props.channel.removeListener('addon:knobs:knobChange', this.knobChanged);
this.props.channel.removeListener('addon:knobs:reset', this.resetKnobs);
this.props.knobStore.unsubscribe(this.setPaneKnobs);

// cleanup before leaving
this.props.knobStore.reset();
this.setPaneKnobs(false);
}

setPaneKnobs(timestamp = +new Date()) {
Expand Down
11 changes: 11 additions & 0 deletions addons/knobs/src/react/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from 'react';
import WrapStory from './WrapStory';

/**
* Handles a react story
*/
export const reactHandler = (channel, knobStore) => getStory => context => {
const initialContent = getStory(context);
const props = { context, storyFn: getStory, channel, knobStore, initialContent };
return <WrapStory {...props} />;
};
40 changes: 40 additions & 0 deletions addons/knobs/src/vue/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
export const vueHandler = (channel, knobStore) => getStory => context => ({
render(h) {
const story = getStory(context);
return h(typeof story === 'string' ? { template: story } : story);
},

methods: {
onKnobChange(change) {
const { name, value } = change;
// Update the related knob and it's value.
const knobOptions = knobStore.get(name);
knobOptions.value = value;
this.$forceUpdate();
},

onKnobReset() {
knobStore.reset();
this.setPaneKnobs(false);
this.$forceUpdate();
},

setPaneKnobs(timestamp = +new Date()) {
channel.emit('addon:knobs:setKnobs', { knobs: knobStore.getAll(), timestamp });
},
},

created() {
channel.on('addon:knobs:reset', this.onKnobReset);
channel.on('addon:knobs:knobChange', this.onKnobChange);
knobStore.subscribe(this.setPaneKnobs);
},

beforeDestroy(){
console.log('beforeDestroy');
channel.removeListener('addon:knobs:reset', this.onKnobReset);
channel.removeListener('addon:knobs:knobChange', this.onKnobChange);
knobStore.unsubscribe(this.setPaneKnobs);
this.onKnobReset();
}
});
4 changes: 2 additions & 2 deletions addons/notes/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ Then write your stories like this:

```js
import { storiesOf } from '@storybook/react';
import { withNotes } from '@storybook/addon-notes';
import { addonNotes } from '@storybook/addon-notes';

import Component from './Component';

storiesOf('Component', module)
.add('with some emoji', withNotes({ notes: 'A very simple component'})(() => <Component></Component>));
.add('with some emoji', addonNotes({ notes: 'A very simple component'})(() => <Component></Component>));
```
2 changes: 1 addition & 1 deletion addons/notes/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import deprecate from 'util-deprecate';
import addons from '@storybook/addons';
import { WithNotes as ReactWithNotes } from './react';

export const withNotes = ({ notes }) => {
export const addonNotes = ({ notes }) => {
const channel = addons.getChannel();

return getStory => () => {
Expand Down
1 change: 1 addition & 0 deletions app/react/src/server/config/globals.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* globals window */

window.STORYBOOK_REACT_CLASSES = {};
window.STORYBOOK_ENV = 'react';
27 changes: 3 additions & 24 deletions app/vue/src/client/preview/client_api.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* eslint no-underscore-dangle: 0 */
import Vue from 'vue';

export default class ClientApi {
constructor({ channel, storyStore }) {
// channel can be null when running in node
Expand Down Expand Up @@ -59,40 +60,18 @@ export default class ClientApi {
throw new Error(`Story of "${kind}" named "${storyName}" already exists`);
}

const parseStory = (context) => {
const element = getStory(context);
let component = element;
if (typeof component === 'string') {
component = { template: component };
} else if (typeof component === 'function') {
component = { render: component };
}

return component;
}

// Wrap the getStory function with each decorator. The first
// decorator will wrap the story function. The second will
// wrap the first decorator and so on.
const decorators = [...localDecorators, ...this._globalDecorators];

const fn = decorators.reduce(
const getDecoratedStory = decorators.reduce(
(decorated, decorator) => context => decorator(() => decorated(context), context),
getStory
);


const fnR = (context) => {
return new Vue({
render(h) {
const story = parseStory(fn(context));
return h('div', {attrs: { id: 'root' } }, [h(story)]);
},
});
}

// Add the fully decorated getStory function.
this._storyStore.addStory(kind, storyName,fnR);
this._storyStore.addStory(kind, storyName, getDecoratedStory);
return api;
};

Expand Down
12 changes: 10 additions & 2 deletions app/vue/src/client/preview/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const logger = console;
// let rootEl = null;
let previousKind = '';
let previousStory = '';
let app = null;

export function renderError(error) {
const properError = new Error(error.title);
Expand Down Expand Up @@ -90,8 +91,15 @@ export function renderMain(data, storyStore) {
// return renderError(error);
// }

element.$mount('#root');
return null;
if (app) app.$destroy();

app = new Vue({
el: '#root',
render(h) {
const story = typeof element === 'string' ? { template: element } : element;
return h('div', {attrs: { id: 'root' } }, [h(story)]);
},
});
}

export default function renderPreview({ reduxStore, storyStore }) {
Expand Down
32 changes: 27 additions & 5 deletions examples/cra-kitchen-sink/src/stories/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import EventEmiter from 'eventemitter3';

import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { withNotes, WithNotes } from '@storybook/addon-notes';
import { addonNotes, WithNotes } from '@storybook/addon-notes';
import { withKnobs, addonKnobs, text, number } from '@storybook/addon-knobs';
import { linkTo } from '@storybook/addon-links';
import WithEvents from '@storybook/addon-events';
import { withKnobs, text, number } from '@storybook/addon-knobs';
Expand Down Expand Up @@ -112,12 +113,12 @@ storiesOf('WithEvents', module)
)
.add('Logger', () => <Logger emiter={emiter} />);

storiesOf('withNotes', module)
.add('with some text', withNotes({ notes: 'Hello guys' })(() => <div>Hello copain</div>))
.add('with some emoji', withNotes({ notes: 'My notes on emojies' })(() => <p>🤔😳😯😮</p>))
storiesOf('addonNotes', module)
.add('with some text', addonNotes({ notes: 'Hello guys' })(() => <div>Hello copain</div>))
.add('with some emoji', addonNotes({ notes: 'My notes on emojies' })(() => <p>🤔😳😯😮</p>))
.add(
'with a button and some emoji',
withNotes({ notes: 'My notes on a button with emojies' })(() =>
addonNotes({ notes: 'My notes on a button with emojies' })(() =>
<Button onClick={action('clicked')}>😀 😎 👍 💯</Button>
)
)
Expand All @@ -126,3 +127,24 @@ storiesOf('withNotes', module)
<Button onClick={action('clicked')}>😀 😎 👍 💯</Button>
</WithNotes>
);

storiesOf('Addon Knobs deprecated Decorator', module)
.addDecorator(withKnobs) // test deprecated
.add('with dynamic variables deprecated', () => {
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>;
});

storiesOf('Addon Knobs', module).add(
'with dynamic variables new method',
addonKnobs()(() => {
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>;
})
);
Loading

0 comments on commit bac1905

Please sign in to comment.