diff --git a/addons/knobs/README.md b/addons/knobs/README.md
index f4d24e43eb00..9d5d53f8cd7c 100644
--- a/addons/knobs/README.md
+++ b/addons/knobs/README.md
@@ -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);
@@ -52,14 +52,15 @@ stories.add('with a 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 (
{content}
);
-});
+}));
```
You can see your Knobs in a Storybook panel as shown below.
diff --git a/addons/knobs/package.json b/addons/knobs/package.json
index dd1036fcf75e..0471dfe49344 100644
--- a/addons/knobs/package.json
+++ b/addons/knobs/package.json
@@ -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",
diff --git a/addons/knobs/src/KnobManager.js b/addons/knobs/src/KnobManager.js
index 8604493d0ad2..2ec08c6de14b 100644
--- a/addons/knobs/src/KnobManager.js
+++ b/addons/knobs/src/KnobManager.js
@@ -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 ;
- // }
-
_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
diff --git a/addons/knobs/src/index.js b/addons/knobs/src/index.js
index 7b7b59755ea5..eebec91913a9 100644
--- a/addons/knobs/src/index.js
+++ b/addons/knobs/src/index.js
@@ -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();
@@ -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);
+ }
+ }
}
diff --git a/addons/knobs/src/components/WrapStory.js b/addons/knobs/src/react/WrapStory.js
similarity index 96%
rename from addons/knobs/src/components/WrapStory.js
rename to addons/knobs/src/react/WrapStory.js
index 29568e2950ad..a23227978022 100644
--- a/addons/knobs/src/components/WrapStory.js
+++ b/addons/knobs/src/react/WrapStory.js
@@ -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()) {
diff --git a/addons/knobs/src/react/index.js b/addons/knobs/src/react/index.js
new file mode 100644
index 000000000000..0b093d446ddc
--- /dev/null
+++ b/addons/knobs/src/react/index.js
@@ -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 ;
+};
diff --git a/addons/knobs/src/vue/index.js b/addons/knobs/src/vue/index.js
new file mode 100644
index 000000000000..0b653f7c71c1
--- /dev/null
+++ b/addons/knobs/src/vue/index.js
@@ -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();
+ }
+});
\ No newline at end of file
diff --git a/addons/notes/README.md b/addons/notes/README.md
index dffefb10732a..fb8d896c28f0 100644
--- a/addons/notes/README.md
+++ b/addons/notes/README.md
@@ -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'})(() => ));
+ .add('with some emoji', addonNotes({ notes: 'A very simple component'})(() => ));
```
diff --git a/addons/notes/src/index.js b/addons/notes/src/index.js
index 7aff6cb5cbf4..f825eac410d6 100644
--- a/addons/notes/src/index.js
+++ b/addons/notes/src/index.js
@@ -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 => () => {
diff --git a/app/react/src/server/config/globals.js b/app/react/src/server/config/globals.js
index ee4d9597bf2a..e95a663712b5 100644
--- a/app/react/src/server/config/globals.js
+++ b/app/react/src/server/config/globals.js
@@ -1,3 +1,4 @@
/* globals window */
window.STORYBOOK_REACT_CLASSES = {};
+window.STORYBOOK_ENV = 'react';
diff --git a/app/vue/src/client/preview/client_api.js b/app/vue/src/client/preview/client_api.js
index 6cae429ada55..75d787d92e87 100644
--- a/app/vue/src/client/preview/client_api.js
+++ b/app/vue/src/client/preview/client_api.js
@@ -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
@@ -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;
};
diff --git a/app/vue/src/client/preview/render.js b/app/vue/src/client/preview/render.js
index 8047f6dff2bf..653cf9ea362d 100644
--- a/app/vue/src/client/preview/render.js
+++ b/app/vue/src/client/preview/render.js
@@ -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);
@@ -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 }) {
diff --git a/examples/cra-kitchen-sink/src/stories/index.js b/examples/cra-kitchen-sink/src/stories/index.js
index cda162e0773e..2c63eaccfd64 100644
--- a/examples/cra-kitchen-sink/src/stories/index.js
+++ b/examples/cra-kitchen-sink/src/stories/index.js
@@ -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';
@@ -112,12 +113,12 @@ storiesOf('WithEvents', module)
)
.add('Logger', () => );
-storiesOf('withNotes', module)
- .add('with some text', withNotes({ notes: 'Hello guys' })(() => Hello copain
))
- .add('with some emoji', withNotes({ notes: 'My notes on emojies' })(() => 🤔😳😯😮
))
+storiesOf('addonNotes', module)
+ .add('with some text', addonNotes({ notes: 'Hello guys' })(() => Hello copain
))
+ .add('with some emoji', addonNotes({ notes: 'My notes on emojies' })(() => 🤔😳😯😮
))
.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' })(() =>
😀 😎 👍 💯
)
)
@@ -126,3 +127,24 @@ storiesOf('withNotes', module)
😀 😎 👍 💯
);
+
+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 {content}
;
+ });
+
+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 {content}
;
+ })
+);
diff --git a/examples/vue/src/stories/index.js b/examples/vue/src/stories/index.js
index 4a2bb37a86b2..d44bd989ad96 100644
--- a/examples/vue/src/stories/index.js
+++ b/examples/vue/src/stories/index.js
@@ -5,9 +5,9 @@ import { storiesOf } from '@storybook/vue';
import { action } from '@storybook/addon-actions';
import { linkTo } from '@storybook/addon-links';
-import { withNotes } from '@storybook/addon-notes';
+import { addonNotes } from '@storybook/addon-notes';
-import { withKnobs, text, boolean, number } from '@storybook/addon-knobs';
+import { addonKnobs, text, boolean, number } from '@storybook/addon-knobs';
import MyButton from './Button.vue';
@@ -78,30 +78,30 @@ storiesOf('Other', module)
storiesOf('Addon Notes', module)
- .add('with some emoji', withNotes({notes: 'My notes on emojies'})(() => '🤔😳😯😮
'))
- .add('with some button', withNotes({ notes: 'My notes on some button' })(() => ({
+ .add('with some emoji', addonNotes({notes: 'My notes on emojies'})(() => '🤔😳😯😮
'))
+ .add('with some button', addonNotes({ notes: 'My notes on some button' })(() => ({
components: { MyButton },
template: 'rounded '
})))
- .add('with some color', withNotes({ notes: 'Some notes on some colored component' })(() => ({
+ .add('with some color', addonNotes({ notes: 'Some notes on some colored component' })(() => ({
render(h) {
return h(MyButton, { props: { color: 'pink' } }, ['colorful']);
}
})))
- .add('with some text', withNotes({ notes: 'My notes on some text' })(() => ({
+ .add('with some text', addonNotes({ notes: 'My notes on some text' })(() => ({
template: 'Text
'
})
))
- .add('with some long text', withNotes({ notes: 'My notes on some long text' })(
+ .add('with some long text', addonNotes({ notes: 'My notes on some long text' })(
() => 'A looooooooonnnnnnnggggggggggggg text
'
))
- .add('with some bold text', withNotes({ notes: 'My notes on some bold text' })(() => ({
+ .add('with some bold text', addonNotes({ notes: 'My notes on some bold text' })(() => ({
render: h => h('div', [h('strong', ['A very long text to display'])])
})));
storiesOf('Addon Knobs', module)
- .add('With some name', withKnobs()(() => {
+ .add('With some name', addonKnobs()(() => {
const name = text('Name', 'Arunoda Susiripala');
const age = number('Age', 89);