From f3300115ad5ba6ae0279693b8bd6aef69c774c7a Mon Sep 17 00:00:00 2001 From: Hypnosphi Date: Tue, 8 May 2018 01:32:29 +0300 Subject: [PATCH 01/17] Generic addon-a11y decorator --- addons/a11y/html.js | 1 - addons/a11y/src/A11yManager.js | 14 ----- addons/a11y/src/components/WrapStory.js | 58 ------------------- addons/a11y/src/html.js | 35 ----------- addons/a11y/src/index.js | 37 ++++++++---- .../stories/addon-a11y.stories.js | 2 +- 6 files changed, 28 insertions(+), 119 deletions(-) delete mode 100644 addons/a11y/html.js delete mode 100644 addons/a11y/src/A11yManager.js delete mode 100644 addons/a11y/src/components/WrapStory.js delete mode 100644 addons/a11y/src/html.js diff --git a/addons/a11y/html.js b/addons/a11y/html.js deleted file mode 100644 index 4f7edc6bedba..000000000000 --- a/addons/a11y/html.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('./dist/html'); diff --git a/addons/a11y/src/A11yManager.js b/addons/a11y/src/A11yManager.js deleted file mode 100644 index fe48aaef6662..000000000000 --- a/addons/a11y/src/A11yManager.js +++ /dev/null @@ -1,14 +0,0 @@ -import React from 'react'; - -import WrapStory from './components/WrapStory'; - -// Run all a11y checks inside -class A11yManager { - wrapStory(channel, storyFn, context, axeOptions) { - const props = { context, storyFn, channel, axeOptions }; - - return ; - } -} - -export default A11yManager; diff --git a/addons/a11y/src/components/WrapStory.js b/addons/a11y/src/components/WrapStory.js deleted file mode 100644 index b36e8c15c862..000000000000 --- a/addons/a11y/src/components/WrapStory.js +++ /dev/null @@ -1,58 +0,0 @@ -import { Component } from 'react'; -import { findDOMNode } from 'react-dom'; -import PropTypes from 'prop-types'; -import axe from 'axe-core'; -import { logger } from '@storybook/client-logger'; - -import { CHECK_EVENT_ID, RERUN_EVENT_ID } from '../shared'; - -class WrapStory extends Component { - static propTypes = { - context: PropTypes.shape({}), - storyFn: PropTypes.func, - channel: PropTypes.shape({}), - axeOptions: PropTypes.shape({}), - }; - static defaultProps = { - context: {}, - storyFn: () => {}, - channel: {}, - axeOptions: {}, - }; - - constructor(props) { - super(props); - this.runA11yCheck = this.runA11yCheck.bind(this); - } - - componentDidMount() { - const { channel } = this.props; - channel.on(RERUN_EVENT_ID, this.runA11yCheck); - this.runA11yCheck(); - } - - componentWillUnmount() { - const { channel } = this.props; - channel.removeListener(RERUN_EVENT_ID, this.runA11yCheck); - } - - /* eslint-disable react/no-find-dom-node */ - runA11yCheck() { - const { channel, axeOptions } = this.props; - const wrapper = findDOMNode(this); - - if (wrapper !== null) { - axe.reset(); - axe.configure(axeOptions); - axe.run(wrapper).then(results => channel.emit(CHECK_EVENT_ID, results), logger.error); - } - } - - render() { - const { storyFn, context } = this.props; - - return storyFn(context); - } -} - -export default WrapStory; diff --git a/addons/a11y/src/html.js b/addons/a11y/src/html.js deleted file mode 100644 index f41765366cce..000000000000 --- a/addons/a11y/src/html.js +++ /dev/null @@ -1,35 +0,0 @@ -import { document, setTimeout } from 'global'; -import axe from 'axe-core'; -import addons from '@storybook/addons'; -import Events from '@storybook/core-events'; -import { logger } from '@storybook/client-logger'; - -import { CHECK_EVENT_ID, RERUN_EVENT_ID } from './shared'; - -let axeOptions = {}; - -export const configureA11y = (options = {}) => { - axeOptions = options; -}; - -const runA11yCheck = () => { - const channel = addons.getChannel(); - const wrapper = document.getElementById('root'); - - axe.reset(); - axe.configure(axeOptions); - axe.run(wrapper).then(results => channel.emit(CHECK_EVENT_ID, results), logger.error); -}; - -const a11ySubscription = () => { - const channel = addons.getChannel(); - channel.on(RERUN_EVENT_ID, runA11yCheck); - return () => channel.removeListener(RERUN_EVENT_ID, runA11yCheck); -}; - -export const checkA11y = story => { - addons.getChannel().emit(Events.REGISTER_SUBSCRIPTION, a11ySubscription); - // We need to wait for rendering - setTimeout(runA11yCheck, 0); - return story(); -}; diff --git a/addons/a11y/src/index.js b/addons/a11y/src/index.js index b8e4f0ba447e..f41765366cce 100644 --- a/addons/a11y/src/index.js +++ b/addons/a11y/src/index.js @@ -1,18 +1,35 @@ +import { document, setTimeout } from 'global'; +import axe from 'axe-core'; import addons from '@storybook/addons'; +import Events from '@storybook/core-events'; +import { logger } from '@storybook/client-logger'; -import A11yManager from './A11yManager'; -import * as shared from './shared'; +import { CHECK_EVENT_ID, RERUN_EVENT_ID } from './shared'; -const manager = new A11yManager(); let axeOptions = {}; -function checkA11y(storyFn, context) { +export const configureA11y = (options = {}) => { + axeOptions = options; +}; + +const runA11yCheck = () => { const channel = addons.getChannel(); - return manager.wrapStory(channel, storyFn, context, axeOptions); -} + const wrapper = document.getElementById('root'); -function configureA11y(options = {}) { - axeOptions = options; -} + axe.reset(); + axe.configure(axeOptions); + axe.run(wrapper).then(results => channel.emit(CHECK_EVENT_ID, results), logger.error); +}; + +const a11ySubscription = () => { + const channel = addons.getChannel(); + channel.on(RERUN_EVENT_ID, runA11yCheck); + return () => channel.removeListener(RERUN_EVENT_ID, runA11yCheck); +}; -export { checkA11y, shared, configureA11y }; +export const checkA11y = story => { + addons.getChannel().emit(Events.REGISTER_SUBSCRIPTION, a11ySubscription); + // We need to wait for rendering + setTimeout(runA11yCheck, 0); + return story(); +}; diff --git a/examples/html-kitchen-sink/stories/addon-a11y.stories.js b/examples/html-kitchen-sink/stories/addon-a11y.stories.js index 7341d03e8a6a..96e90a23ae1b 100644 --- a/examples/html-kitchen-sink/stories/addon-a11y.stories.js +++ b/examples/html-kitchen-sink/stories/addon-a11y.stories.js @@ -2,7 +2,7 @@ import { document, setTimeout } from 'global'; import { storiesOf } from '@storybook/html'; import { setOptions } from '@storybook/addon-options'; -import { checkA11y } from '@storybook/addon-a11y/html'; +import { checkA11y } from '@storybook/addon-a11y'; const text = 'Testing the a11y addon'; From 6f78c1261532f648d06410129072c53654ec9766 Mon Sep 17 00:00:00 2001 From: Hypnosphi Date: Tue, 8 May 2018 02:12:52 +0300 Subject: [PATCH 02/17] Generic addon-backgrounds decorator --- ADDONS_SUPPORT.md | 4 +- addons/backgrounds/html.js | 2 +- addons/backgrounds/mithril.js | 2 +- addons/backgrounds/package.json | 2 +- addons/backgrounds/src/__tests__/index.js | 64 -------------------- addons/backgrounds/src/__tests__/vue.js | 44 -------------- addons/backgrounds/src/deprecated.js | 8 +++ addons/backgrounds/src/html.js | 17 ------ addons/backgrounds/src/index.js | 72 ++++------------------- addons/backgrounds/src/mithril.js | 41 ------------- addons/backgrounds/src/vue.js | 30 ---------- addons/backgrounds/vue.js | 2 +- 12 files changed, 26 insertions(+), 262 deletions(-) delete mode 100644 addons/backgrounds/src/__tests__/index.js delete mode 100644 addons/backgrounds/src/__tests__/vue.js create mode 100644 addons/backgrounds/src/deprecated.js delete mode 100644 addons/backgrounds/src/html.js delete mode 100644 addons/backgrounds/src/mithril.js delete mode 100644 addons/backgrounds/src/vue.js diff --git a/ADDONS_SUPPORT.md b/ADDONS_SUPPORT.md index 74caad618361..86aba7bb261f 100644 --- a/ADDONS_SUPPORT.md +++ b/ADDONS_SUPPORT.md @@ -2,9 +2,9 @@ | |[React](app/react)|[React Native](app/react-native)|[Vue](app/vue)|[Angular](app/angular)| [Polymer](app/polymer)| [Mithril](app/mithril)| [HTML](app/html)| [Marko](app/marko)| | ----------- |:-------:|:-------:|:-------:|:-------:|:-------:|:-------:|:-------:| -|[a11y](addons/a11y) |+| | | | | |+| | +|[a11y](addons/a11y) |+|+|+|+|+|+|+|+| |[actions](addons/actions) |+|+|+|+|+|+|+|+| -|[backgrounds](addons/backgrounds) |+| | | | |+|+| | +|[backgrounds](addons/backgrounds) |+|+|+|+|+|+|+|+| |[centered](addons/centered) |+| |+| | |+|+| | |[events](addons/events) |+| | | | | |+| | |[graphql](addons/graphql) |+| | | | | | | | diff --git a/addons/backgrounds/html.js b/addons/backgrounds/html.js index 4f7edc6bedba..c22c26b6732d 100644 --- a/addons/backgrounds/html.js +++ b/addons/backgrounds/html.js @@ -1 +1 @@ -module.exports = require('./dist/html'); +module.exports = require('./dist/deprecated'); diff --git a/addons/backgrounds/mithril.js b/addons/backgrounds/mithril.js index 884a541476ec..c22c26b6732d 100644 --- a/addons/backgrounds/mithril.js +++ b/addons/backgrounds/mithril.js @@ -1 +1 @@ -module.exports = require('./dist/mithril'); +module.exports = require('./dist/deprecated'); diff --git a/addons/backgrounds/package.json b/addons/backgrounds/package.json index f429c870b992..7d56230d1aec 100644 --- a/addons/backgrounds/package.json +++ b/addons/backgrounds/package.json @@ -29,7 +29,7 @@ "babel-runtime": "^6.26.0", "global": "^4.3.2", "prop-types": "^15.6.1", - "react-lifecycles-compat": "^3.0.2" + "util-deprecate": "^1.0.2" }, "devDependencies": { "vue": "^2.5.16" diff --git a/addons/backgrounds/src/__tests__/index.js b/addons/backgrounds/src/__tests__/index.js deleted file mode 100644 index 8da89469a59c..000000000000 --- a/addons/backgrounds/src/__tests__/index.js +++ /dev/null @@ -1,64 +0,0 @@ -import React from 'react'; -import { shallow } from 'enzyme'; -import EventEmitter from 'events'; - -import { BackgroundDecorator } from '../index'; -import Events from '../events'; - -const testStory = () => () =>

Hello World!

; - -describe('Background Decorator', () => { - it('should exist', () => { - const SpiedChannel = new EventEmitter(); - const backgroundDecorator = shallow( - - ); - expect(backgroundDecorator).toBeDefined(); - }); - - it('should send background-unset event when the component unmounts', () => { - const SpiedChannel = new EventEmitter(); - const backgroundDecorator = shallow( - - ); - - const spy = jest.fn(); - SpiedChannel.on(Events.UNSET, spy); - - backgroundDecorator.unmount(); - - expect(spy).toBeCalled(); - }); - - it('should send background-set event when the component mounts', () => { - const SpiedChannel = new EventEmitter(); - const spy = jest.fn(); - SpiedChannel.on(Events.SET, spy); - - shallow(); - - expect(spy).toBeCalled(); - }); - - it('should update story on change', () => { - const SpiedChannel = new EventEmitter(); - const nextStory = jest.fn(() =>

I am next story!

); - const backgroundDecorator = shallow( - - ); - - backgroundDecorator.setProps({ story: nextStory }); - expect(nextStory).toBeCalled(); - }); - - it('should not update story on other props change', () => { - const SpiedChannel = new EventEmitter(); - const story = jest.fn(() =>

I am the only one!

); - const backgroundDecorator = shallow( - - ); - - backgroundDecorator.setProps({ randomProp: true }); - expect(story.mock.calls).toHaveLength(1); - }); -}); diff --git a/addons/backgrounds/src/__tests__/vue.js b/addons/backgrounds/src/__tests__/vue.js deleted file mode 100644 index 3893473dad61..000000000000 --- a/addons/backgrounds/src/__tests__/vue.js +++ /dev/null @@ -1,44 +0,0 @@ -import Vue from 'vue'; -import { vueHandler } from '../vue'; - -import Events from '../events'; - -describe('Vue handler', () => { - it('Returns a component with a created function', () => { - const testChannel = { emit: jest.fn() }; - const testStory = () => ({ template: '
testStory
' }); - const testContext = { - kind: 'Foo', - story: 'bar baz', - }; - const testBackground = [ - { name: 'twitter', value: '#00aced' }, - { name: 'facebook', value: '#3b5998', default: true }, - ]; - const component = vueHandler(testChannel, testBackground)(testStory, testContext); - - expect(component).toMatchObject({ - created: expect.any(Function), - beforeDestroy: expect.any(Function), - render: expect.any(Function), - }); - }); - - it('Subscribes to the channel on creation', () => { - const testChannel = { emit: jest.fn() }; - const testStory = () => ({ render: h => h('div', ['testStory']) }); - const testContext = { - kind: 'Foo', - story: 'bar baz', - }; - const testBackground = [ - { name: 'twitter', value: '#00aced' }, - { name: 'facebook', value: '#3b5998', default: true }, - ]; - - new Vue(vueHandler(testChannel, testBackground)(testStory, testContext)).$mount(); - - expect(testChannel.emit).toHaveBeenCalledTimes(1); - expect(testChannel.emit).toHaveBeenCalledWith(Events.SET, expect.any(Array)); - }); -}); diff --git a/addons/backgrounds/src/deprecated.js b/addons/backgrounds/src/deprecated.js new file mode 100644 index 000000000000..f03a1a290186 --- /dev/null +++ b/addons/backgrounds/src/deprecated.js @@ -0,0 +1,8 @@ +import deprecate from 'util-deprecate'; + +import backgrounds from '.'; + +export default deprecate( + backgrounds, + "addon-backgrounds: framework-specific imports are deprecated, just use `import backgrounds from '@storybook/addon-backgrounds`" +); diff --git a/addons/backgrounds/src/html.js b/addons/backgrounds/src/html.js deleted file mode 100644 index 20eeb3d1c6a7..000000000000 --- a/addons/backgrounds/src/html.js +++ /dev/null @@ -1,17 +0,0 @@ -import addons from '@storybook/addons'; -import CoreEvents from '@storybook/core-events'; - -import Events from './events'; - -const subscription = () => () => addons.getChannel().emit(Events.UNSET); - -let prevBackgrounds; - -export default backgrounds => story => { - if (prevBackgrounds !== backgrounds) { - addons.getChannel().emit(Events.SET, backgrounds); - prevBackgrounds = backgrounds; - } - addons.getChannel().emit(CoreEvents.REGISTER_SUBSCRIPTION, subscription); - return story(); -}; diff --git a/addons/backgrounds/src/index.js b/addons/backgrounds/src/index.js index 64142e74b8b0..b6a1f857fd94 100644 --- a/addons/backgrounds/src/index.js +++ b/addons/backgrounds/src/index.js @@ -1,68 +1,20 @@ -import React from 'react'; -import { polyfill } from 'react-lifecycles-compat'; -import PropTypes from 'prop-types'; - import addons from '@storybook/addons'; +import CoreEvents from '@storybook/core-events'; import Events from './events'; -export class BackgroundDecorator extends React.Component { - constructor(props) { - super(props); - - const { channel } = props; - - // A channel is explicitly passed in for testing - if (channel) { - this.channel = channel; - } else { - this.channel = addons.getChannel(); - } - - this.state = {}; - } - - componentDidMount() { - this.channel.emit(Events.SET, this.props.backgrounds); - } - - componentWillUnmount() { - this.channel.emit(Events.UNSET); - } +let prevBackgrounds; - render() { - return this.state.story; - } -} - -BackgroundDecorator.getDerivedStateFromProps = ({ story }, { prevStory }) => { - if (story !== prevStory) { - return { - story: story(), - prevStory: story, - }; - } - return null; -}; - -BackgroundDecorator.propTypes = { - backgrounds: PropTypes.arrayOf(PropTypes.object), - channel: PropTypes.shape({ - emit: PropTypes.func, - on: PropTypes.func, - removeListener: PropTypes.func, - }), - // eslint-disable-next-line react/no-unused-prop-types - story: PropTypes.func.isRequired, +const subscription = () => () => { + prevBackgrounds = null; + addons.getChannel().emit(Events.UNSET); }; -BackgroundDecorator.defaultProps = { - backgrounds: [], - channel: undefined, +export default backgrounds => story => { + if (prevBackgrounds !== backgrounds) { + addons.getChannel().emit(Events.SET, backgrounds); + prevBackgrounds = backgrounds; + } + addons.getChannel().emit(CoreEvents.REGISTER_SUBSCRIPTION, subscription); + return story(); }; - -polyfill(BackgroundDecorator); - -export default backgrounds => story => ( - -); diff --git a/addons/backgrounds/src/mithril.js b/addons/backgrounds/src/mithril.js deleted file mode 100644 index 8c27f1390dac..000000000000 --- a/addons/backgrounds/src/mithril.js +++ /dev/null @@ -1,41 +0,0 @@ -/** @jsx m */ - -// eslint-disable-next-line import/no-extraneous-dependencies -import m from 'mithril'; - -import addons from '@storybook/addons'; - -import Events from './events'; - -export class BackgroundDecorator { - constructor(vnode) { - this.props = vnode.attrs; - - const { channel, story } = vnode.attrs; - - // A channel is explicitly passed in for testing - if (channel) { - this.channel = channel; - } else { - this.channel = addons.getChannel(); - } - - this.story = story(); - } - - oncreate() { - this.channel.emit(Events.SET, this.props.backgrounds); - } - - onremove() { - this.channel.emit(Events.UNSET); - } - - view() { - return m(this.story); - } -} - -export default backgrounds => story => ({ - view: () => , -}); diff --git a/addons/backgrounds/src/vue.js b/addons/backgrounds/src/vue.js deleted file mode 100644 index 5ff182695a70..000000000000 --- a/addons/backgrounds/src/vue.js +++ /dev/null @@ -1,30 +0,0 @@ -import addons from '@storybook/addons'; - -import Events from './events'; - -export const vueHandler = (channel, backgrounds) => (getStory, context) => ({ - data() { - return { - context, - getStory, - story: getStory(context), - }; - }, - - render(h) { - return h(this.story); - }, - - created() { - channel.emit(Events.SET, backgrounds); - }, - - beforeDestroy() { - channel.emit(Events.UNSET); - }, -}); - -export default function makeDecorator(backgrounds) { - const channel = addons.getChannel(); - return vueHandler(channel, backgrounds); -} diff --git a/addons/backgrounds/vue.js b/addons/backgrounds/vue.js index 42311b17563f..c22c26b6732d 100644 --- a/addons/backgrounds/vue.js +++ b/addons/backgrounds/vue.js @@ -1 +1 @@ -module.exports = require('./dist/vue'); +module.exports = require('./dist/deprecated'); From 0ecf66e5295fbdc0577d07613ef18d5a9509027f Mon Sep 17 00:00:00 2001 From: Hypnosphi Date: Tue, 8 May 2018 02:16:38 +0300 Subject: [PATCH 03/17] Update examples --- addons/backgrounds/html.js | 1 - examples/html-kitchen-sink/stories/addon-backgrounds.stories.js | 2 +- .../src/stories/addon-backgrounds.stories.js | 2 +- .../vue-kitchen-sink/src/stories/addon-backgrounds.stories.js | 2 +- 4 files changed, 3 insertions(+), 4 deletions(-) delete mode 100644 addons/backgrounds/html.js diff --git a/addons/backgrounds/html.js b/addons/backgrounds/html.js deleted file mode 100644 index c22c26b6732d..000000000000 --- a/addons/backgrounds/html.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('./dist/deprecated'); diff --git a/examples/html-kitchen-sink/stories/addon-backgrounds.stories.js b/examples/html-kitchen-sink/stories/addon-backgrounds.stories.js index 1c8eed6ac6dc..1a5cc547cf28 100644 --- a/examples/html-kitchen-sink/stories/addon-backgrounds.stories.js +++ b/examples/html-kitchen-sink/stories/addon-backgrounds.stories.js @@ -1,6 +1,6 @@ import { storiesOf } from '@storybook/html'; -import backgrounds from '@storybook/addon-backgrounds/html'; +import backgrounds from '@storybook/addon-backgrounds'; storiesOf('Addons|Backgrounds', module) .addDecorator( diff --git a/examples/mithril-kitchen-sink/src/stories/addon-backgrounds.stories.js b/examples/mithril-kitchen-sink/src/stories/addon-backgrounds.stories.js index 373a25de5bb6..307765b93697 100644 --- a/examples/mithril-kitchen-sink/src/stories/addon-backgrounds.stories.js +++ b/examples/mithril-kitchen-sink/src/stories/addon-backgrounds.stories.js @@ -4,7 +4,7 @@ import m from 'mithril'; import { storiesOf } from '@storybook/mithril'; -import backgrounds from '@storybook/addon-backgrounds/mithril'; +import backgrounds from '@storybook/addon-backgrounds'; import BaseButton from '../BaseButton'; storiesOf('Addons|Backgrounds', module) diff --git a/examples/vue-kitchen-sink/src/stories/addon-backgrounds.stories.js b/examples/vue-kitchen-sink/src/stories/addon-backgrounds.stories.js index aacee49f3522..3621cf9d65d7 100644 --- a/examples/vue-kitchen-sink/src/stories/addon-backgrounds.stories.js +++ b/examples/vue-kitchen-sink/src/stories/addon-backgrounds.stories.js @@ -1,5 +1,5 @@ import { storiesOf } from '@storybook/vue'; -import backgrounds from '@storybook/addon-backgrounds/vue'; +import backgrounds from '@storybook/addon-backgrounds'; storiesOf('Addon|Backgrounds', module) .addDecorator( From f9e946e92227902634be99a2cb08e50f4c7b58f5 Mon Sep 17 00:00:00 2001 From: Hypnosphi Date: Tue, 8 May 2018 02:29:18 +0300 Subject: [PATCH 04/17] Update readme --- addons/backgrounds/README.md | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/addons/backgrounds/README.md b/addons/backgrounds/README.md index cecda21cfecd..0dae593a512e 100644 --- a/addons/backgrounds/README.md +++ b/addons/backgrounds/README.md @@ -69,17 +69,3 @@ storiesOf("Button", module) .addDecorator(backgrounds) .add("with text", () => ); ``` - -> In the case of Mithril, use these imports: -> -> ```js -> import { storiesOf } from '@storybook/mithril'; -> import backgrounds from "@storybook/addon-backgrounds/mithril"; -> ``` - -> In the case of Vue, use these imports: -> -> ```js -> import { storiesOf } from '@storybook/vue'; -> import backgrounds from "@storybook/addon-backgrounds/vue"; -> ``` From 09225c28dd662b7f792b457bd1355815ef921090 Mon Sep 17 00:00:00 2001 From: Hypnosphi Date: Tue, 8 May 2018 02:33:11 +0300 Subject: [PATCH 05/17] Generic addon-events decorator --- addons/events/README.md | 18 ++++--- addons/events/html.js | 1 - addons/events/src/html.js | 27 ----------- addons/events/src/index.js | 28 ++++++++++- addons/events/src/preview.js | 47 ------------------- .../stories/addon-events.stories.js | 2 +- .../stories/addon-events.stories.js | 18 ++++--- 7 files changed, 44 insertions(+), 97 deletions(-) delete mode 100644 addons/events/html.js delete mode 100644 addons/events/src/html.js delete mode 100644 addons/events/src/preview.js diff --git a/addons/events/README.md b/addons/events/README.md index 81636b354b0d..cb2ffb89effc 100644 --- a/addons/events/README.md +++ b/addons/events/README.md @@ -36,7 +36,7 @@ Then write your stories like this: ```js import { storiesOf } from '@storybook/react'; -import WithEvents from '@storybook/addon-events'; +import withEvents from '@storybook/addon-events'; import EventEmiter from 'event-emiter'; import Logger from './Logger'; @@ -47,10 +47,10 @@ const emit = emiter.emit.bind(emiter); storiesOf('WithEvents', module) - .addDecorator(getStory => ( - - {getStory()} - - )) + ] + }) + ) .add('Logger', () => ); ``` diff --git a/addons/events/html.js b/addons/events/html.js deleted file mode 100644 index 4f7edc6bedba..000000000000 --- a/addons/events/html.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('./dist/html'); diff --git a/addons/events/src/html.js b/addons/events/src/html.js deleted file mode 100644 index a7090b809c99..000000000000 --- a/addons/events/src/html.js +++ /dev/null @@ -1,27 +0,0 @@ -import addons from '@storybook/addons'; -import CoreEvents from '@storybook/core-events'; - -import { EVENTS } from './constants'; - -let prevEvents; -let currentEmit; - -const onEmit = event => { - currentEmit(event.name, event.payload); -}; - -const subscription = () => { - const channel = addons.getChannel(); - channel.on(EVENTS.EMIT, onEmit); - return () => channel.removeListener(EVENTS.EMIT, onEmit); -}; - -export default ({ emit, events }) => story => { - if (prevEvents !== events) { - addons.getChannel().emit(EVENTS.ADD, events); - prevEvents = events; - } - currentEmit = emit; - addons.getChannel().emit(CoreEvents.REGISTER_SUBSCRIPTION, subscription); - return story(); -}; diff --git a/addons/events/src/index.js b/addons/events/src/index.js index 48358fbf1386..a7090b809c99 100644 --- a/addons/events/src/index.js +++ b/addons/events/src/index.js @@ -1 +1,27 @@ -export default from './preview'; +import addons from '@storybook/addons'; +import CoreEvents from '@storybook/core-events'; + +import { EVENTS } from './constants'; + +let prevEvents; +let currentEmit; + +const onEmit = event => { + currentEmit(event.name, event.payload); +}; + +const subscription = () => { + const channel = addons.getChannel(); + channel.on(EVENTS.EMIT, onEmit); + return () => channel.removeListener(EVENTS.EMIT, onEmit); +}; + +export default ({ emit, events }) => story => { + if (prevEvents !== events) { + addons.getChannel().emit(EVENTS.ADD, events); + prevEvents = events; + } + currentEmit = emit; + addons.getChannel().emit(CoreEvents.REGISTER_SUBSCRIPTION, subscription); + return story(); +}; diff --git a/addons/events/src/preview.js b/addons/events/src/preview.js deleted file mode 100644 index d5fb7b7db5eb..000000000000 --- a/addons/events/src/preview.js +++ /dev/null @@ -1,47 +0,0 @@ -import { Component } from 'react'; -import addons from '@storybook/addons'; -import PropTypes from 'prop-types'; - -import { EVENTS } from './constants'; - -export default class WithEvents extends Component { - static propTypes = { - emit: PropTypes.func.isRequired, - events: PropTypes.arrayOf( - PropTypes.shape({ - name: PropTypes.string, - title: PropTypes.string, - payload: PropTypes.any, - }) - ).isRequired, - children: PropTypes.oneOfType([PropTypes.element, PropTypes.array]).isRequired, - }; - - componentDidMount() { - const { events } = this.props; - - this.channel = addons.getChannel(); - - this.channel.on(EVENTS.EMIT, this.onEmit); - - this.channel.emit(EVENTS.ADD, events); - } - - componentDidUpdate() { - const { events } = this.props; - - this.channel.emit(EVENTS.ADD, events); - } - - componentWillUnmount() { - this.channel.removeListener(EVENTS.EMIT, this.onEmit); - } - - onEmit = event => { - this.props.emit(event.name, event.payload); - }; - - render() { - return this.props.children; - } -} diff --git a/examples/html-kitchen-sink/stories/addon-events.stories.js b/examples/html-kitchen-sink/stories/addon-events.stories.js index f0be1bee0a0e..954f7eeea87a 100644 --- a/examples/html-kitchen-sink/stories/addon-events.stories.js +++ b/examples/html-kitchen-sink/stories/addon-events.stories.js @@ -4,7 +4,7 @@ import addons from '@storybook/addons'; import CoreEvents from '@storybook/core-events'; import json from 'format-json'; -import withEvents from '@storybook/addon-events/html'; +import withEvents from '@storybook/addon-events'; import './addon-events.css'; diff --git a/examples/official-storybook/stories/addon-events.stories.js b/examples/official-storybook/stories/addon-events.stories.js index 456aa4c88713..2379d0755afd 100644 --- a/examples/official-storybook/stories/addon-events.stories.js +++ b/examples/official-storybook/stories/addon-events.stories.js @@ -2,7 +2,7 @@ import React from 'react'; import EventEmitter from 'eventemitter3'; import { storiesOf } from '@storybook/react'; -import WithEvents from '@storybook/addon-events'; +import withEvents from '@storybook/addon-events'; import Logger from './Logger'; const EVENTS = { @@ -20,10 +20,10 @@ const eventHandler = name => payload => emit(Logger.LOG_EVENT, { name, payload } Object.keys(EVENTS).forEach(event => emitter.on(EVENTS[event], eventHandler(EVENTS[event]))); storiesOf('Addons|Events', module) - .addDecorator(getStory => ( - - {getStory()} - - )) + ], + }) + ) .add('Logger', () => ); From bb5c62fce892f59fdad71db09b48a790559ad543 Mon Sep 17 00:00:00 2001 From: Hypnosphi Date: Wed, 9 May 2018 02:35:41 +0300 Subject: [PATCH 06/17] Introduce "story rendered" event --- addons/a11y/src/index.js | 10 ++++++---- lib/core-events/index.js | 1 + lib/core/src/client/preview/start.js | 4 ++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/addons/a11y/src/index.js b/addons/a11y/src/index.js index f41765366cce..e330e75f4a95 100644 --- a/addons/a11y/src/index.js +++ b/addons/a11y/src/index.js @@ -1,4 +1,4 @@ -import { document, setTimeout } from 'global'; +import { document } from 'global'; import axe from 'axe-core'; import addons from '@storybook/addons'; import Events from '@storybook/core-events'; @@ -23,13 +23,15 @@ const runA11yCheck = () => { const a11ySubscription = () => { const channel = addons.getChannel(); + channel.on(Events.STORY_RENDERED, runA11yCheck); channel.on(RERUN_EVENT_ID, runA11yCheck); - return () => channel.removeListener(RERUN_EVENT_ID, runA11yCheck); + return () => { + channel.removeListener(Events.STORY_RENDERED, runA11yCheck); + channel.removeListener(RERUN_EVENT_ID, runA11yCheck); + }; }; export const checkA11y = story => { addons.getChannel().emit(Events.REGISTER_SUBSCRIPTION, a11ySubscription); - // We need to wait for rendering - setTimeout(runA11yCheck, 0); return story(); }; diff --git a/lib/core-events/index.js b/lib/core-events/index.js index d2c34524a251..c1cbe2ea8819 100644 --- a/lib/core-events/index.js +++ b/lib/core-events/index.js @@ -9,4 +9,5 @@ module.exports = { STORY_ADDED: 'storyAdded', FORCE_RE_RENDER: 'forceReRender', REGISTER_SUBSCRIPTION: 'registerSubscription', + STORY_RENDERED: 'storyRendered', }; diff --git a/lib/core/src/client/preview/start.js b/lib/core/src/client/preview/start.js index a5b88361de08..cf34147fdb1c 100644 --- a/lib/core/src/client/preview/start.js +++ b/lib/core/src/client/preview/start.js @@ -21,8 +21,6 @@ const classes = { }; function showMain() { - subscriptionsStore.clearUnused(); - document.body.classList.remove(classes.NOPREVIEW); document.body.classList.remove(classes.ERROR); @@ -168,6 +166,8 @@ export default function start(render, { decorateStory } = {}) { } try { renderMain(forceRender); + subscriptionsStore.clearUnused(); + addons.getChannel().emit(Events.STORY_RENDERED); } catch (ex) { showException(ex); } From 2e30aeb675b2211589f97ca99d18eb24f61d2cc6 Mon Sep 17 00:00:00 2001 From: Hypnosphi Date: Wed, 9 May 2018 18:14:04 +0300 Subject: [PATCH 07/17] Generic addon-knobs decorator --- ADDONS_SUPPORT.md | 6 +- addons/knobs/README.md | 83 +++-------- addons/knobs/angular.js | 2 +- addons/knobs/html.js | 2 +- addons/knobs/marko.js | 2 +- addons/knobs/mithril.js | 2 +- addons/knobs/polymer.js | 2 +- addons/knobs/react.js | 2 +- addons/knobs/src/angular/helpers.js | 136 ------------------ addons/knobs/src/angular/index.js | 24 ---- addons/knobs/src/angular/utils.js | 38 ----- addons/knobs/src/base.js | 99 ------------- addons/knobs/src/components/Panel.js | 9 +- .../knobs/src/components/__tests__/Select.js | 14 +- addons/knobs/src/components/types/Select.js | 29 +--- addons/knobs/src/deprecated.js | 31 ++++ addons/knobs/src/html/index.js | 32 ----- addons/knobs/src/index.js | 124 ++++++++++------ addons/knobs/src/marko/WrapStory.marko | 70 --------- addons/knobs/src/marko/index.js | 43 ------ addons/knobs/src/mithril/WrapStory.js | 68 --------- addons/knobs/src/mithril/index.js | 33 ----- addons/knobs/src/polymer/WrapStory.html | 94 ------------ addons/knobs/src/polymer/index.js | 33 ----- addons/knobs/src/react/WrapStory.js | 109 -------------- addons/knobs/src/react/index.js | 29 ---- addons/knobs/src/react/index.test.js | 27 ---- addons/knobs/src/{html => }/registerKnobs.js | 7 +- addons/knobs/src/vue/index.js | 75 ---------- addons/knobs/src/vue/index.test.js | 40 ------ addons/knobs/vue.js | 2 +- app/polymer/src/client/preview/render.js | 14 +- app/react/src/client/preview/render.js | 14 +- .../pages/basics/quick-start-guide/index.md | 11 +- .../angular-cli/addon-jest.testresults.json | 2 +- .../addon-knobs.stories.storyshot | 2 +- .../custom-styles.stories.storyshot | 9 +- .../src/stories/addon-knobs.stories.ts | 10 +- .../addon-knobs.stories.storyshot | 4 +- .../stories/addon-knobs.stories.js | 13 +- examples/marko-cli/package.json | 2 +- .../src/stories/addon-actions.stories.js | 2 +- .../src/stories/addon-knobs.stories.js | 2 +- .../src/stories/addon-knobs.stories.js | 12 +- .../addon-knobs.stories.storyshot | 63 +------- .../stories/addon-knobs.stories.js | 98 ++----------- .../src/stories/addon-knobs.stories.js | 10 +- .../addon-knobs.stories.storyshot | 2 +- .../src/stories/addon-knobs.stories.js | 10 +- lib/core/src/client/preview/start.js | 1 + 50 files changed, 233 insertions(+), 1315 deletions(-) delete mode 100644 addons/knobs/src/angular/helpers.js delete mode 100644 addons/knobs/src/angular/index.js delete mode 100644 addons/knobs/src/angular/utils.js delete mode 100644 addons/knobs/src/base.js create mode 100644 addons/knobs/src/deprecated.js delete mode 100644 addons/knobs/src/html/index.js delete mode 100644 addons/knobs/src/marko/WrapStory.marko delete mode 100644 addons/knobs/src/marko/index.js delete mode 100644 addons/knobs/src/mithril/WrapStory.js delete mode 100644 addons/knobs/src/mithril/index.js delete mode 100644 addons/knobs/src/polymer/WrapStory.html delete mode 100644 addons/knobs/src/polymer/index.js delete mode 100644 addons/knobs/src/react/WrapStory.js delete mode 100644 addons/knobs/src/react/index.js delete mode 100644 addons/knobs/src/react/index.test.js rename addons/knobs/src/{html => }/registerKnobs.js (92%) delete mode 100644 addons/knobs/src/vue/index.js delete mode 100644 addons/knobs/src/vue/index.test.js diff --git a/ADDONS_SUPPORT.md b/ADDONS_SUPPORT.md index 86aba7bb261f..667b9b71c193 100644 --- a/ADDONS_SUPPORT.md +++ b/ADDONS_SUPPORT.md @@ -2,11 +2,11 @@ | |[React](app/react)|[React Native](app/react-native)|[Vue](app/vue)|[Angular](app/angular)| [Polymer](app/polymer)| [Mithril](app/mithril)| [HTML](app/html)| [Marko](app/marko)| | ----------- |:-------:|:-------:|:-------:|:-------:|:-------:|:-------:|:-------:| -|[a11y](addons/a11y) |+|+|+|+|+|+|+|+| +|[a11y](addons/a11y) |+| |+|+|+|+|+|+| |[actions](addons/actions) |+|+|+|+|+|+|+|+| -|[backgrounds](addons/backgrounds) |+|+|+|+|+|+|+|+| +|[backgrounds](addons/backgrounds) |+| |+|+|+|+|+|+| |[centered](addons/centered) |+| |+| | |+|+| | -|[events](addons/events) |+| | | | | |+| | +|[events](addons/events) |+| |+|+|+|+|+|+| |[graphql](addons/graphql) |+| | | | | | | | |[info](addons/info) |+| | | | | | | | |[jest](addons/jest) |+| | |+| | |+| | diff --git a/addons/knobs/README.md b/addons/knobs/README.md index ca18c2472b2a..5311e52dc95c 100644 --- a/addons/knobs/README.md +++ b/addons/knobs/README.md @@ -39,7 +39,7 @@ Now, write your stories with knobs. ### With React ```js import { storiesOf } from '@storybook/react'; -import { withKnobs, text, boolean, number } from '@storybook/addon-knobs/react'; +import { withKnobs, text, boolean, number } from '@storybook/addon-knobs'; const stories = storiesOf('Storybook Knobs', module); @@ -67,7 +67,7 @@ stories.add('as dynamic variables', () => { ### With Angular ```js import { storiesOf } from '@storybook/angular'; -import { boolean, number, text, withKnobs } from '@storybook/addon-knobs/angular'; +import { boolean, number, text, withKnobs } from '@storybook/addon-knobs'; import { Button } from '@storybook/angular/demo'; @@ -98,34 +98,6 @@ stories.add('as dynamic variables', () => { }); ``` -> In the case of Vue, use these imports: -> -> ```js -> import { storiesOf } from '@storybook/vue'; -> import { withKnobs, text, boolean, number } from '@storybook/addon-knobs/vue'; -> ``` -> -> In the case of React-Native, use these imports: -> -> ```js -> import { storiesOf } from '@storybook/react-native'; -> import { withKnobs, text, boolean, number } from '@storybook/addon-knobs/react'; -> ``` -> -> In the case of Angular, use these imports: -> -> ```js -> import { storiesOf } from '@storybook/angular'; -> import { withKnobs, text, boolean, number } from '@storybook/addon-knobs/angular'; -> ``` -> -> In the case of Mithril, use these imports: -> -> ```js -> import { storiesOf } from '@storybook/mithril'; -> import { withKnobs, text, boolean, number } from '@storybook/addon-knobs/mithril'; -> ``` - You can see your Knobs in a Storybook panel as shown below. ![](docs/demo.png) @@ -146,7 +118,7 @@ Just like that, you can import any other following Knobs: Allows you to get some text from the user. ```js -import { text } from '@storybook/addon-knobs/react'; +import { text } from '@storybook/addon-knobs'; const label = 'Your Name'; const defaultValue = 'Arunoda Susiripala'; @@ -160,7 +132,7 @@ const value = text(label, defaultValue, groupId); Allows you to get a boolean value from the user. ```js -import { boolean } from '@storybook/addon-knobs/react'; +import { boolean } from '@storybook/addon-knobs'; const label = 'Agree?'; const defaultValue = false; @@ -173,7 +145,7 @@ const value = boolean(label, defaultValue, groupId); Allows you to get a number from the user. ```js -import { number } from '@storybook/addon-knobs/react'; +import { number } from '@storybook/addon-knobs'; const label = 'Age'; const defaultValue = 78; @@ -191,7 +163,7 @@ const value = number(label, defaultValue, {}, groupId); Allows you to get a number from the user using a range slider. ```js -import { number } from '@storybook/addon-knobs/react'; +import { number } from '@storybook/addon-knobs'; const label = 'Temperature'; const defaultValue = 73; @@ -211,7 +183,7 @@ const value = number(label, defaultValue, options, groupId); Allows you to get a colour from the user. ```js -import { color } from '@storybook/addon-knobs/react'; +import { color } from '@storybook/addon-knobs'; const label = 'Color'; const defaultValue = '#ff00ff'; @@ -225,7 +197,7 @@ const value = color(label, defaultValue, groupId); Allows you to get a JSON object or array from the user. ```js -import { object } from '@storybook/addon-knobs/react'; +import { object } from '@storybook/addon-knobs'; const label = 'Styles'; const defaultValue = { @@ -243,7 +215,7 @@ const value = object(label, defaultValue, groupId); Allows you to get an array of strings from the user. ```js -import { array } from '@storybook/addon-knobs/react'; +import { array } from '@storybook/addon-knobs'; const label = 'Styles'; const defaultValue = ['Red']; @@ -256,7 +228,7 @@ const value = array(label, defaultValue); > By default it's a comma, but this can be override by passing a separator variable. > > ```js -> import { array } from '@storybook/addon-knobs/react'; +> import { array } from '@storybook/addon-knobs'; > > const label = 'Styles'; > const defaultValue = ['Red']; @@ -274,28 +246,7 @@ const value = array(label, defaultValue, ',', groupId); Allows you to get a value from a select box from the user. ```js -import { select } from '@storybook/addon-knobs/react'; - -const label = 'Colors'; -const options = { - red: 'Red', - blue: 'Blue', - yellow: 'Yellow', -}; -const defaultValue = 'red'; -const groupId = 'GROUP-ID1'; - -const value = select(label, options, defaultValue, groupId); -``` - -> You can also provide options as an array like this: `['red', 'blue', 'yellow']` - -### selectV2 - -In v4 this will replace `select`. The value from the select now uses the values from the `options` object. - -```js -import { selectV2 } from '@storybook/addon-knobs'; +import { select } from '@storybook/addon-knobs'; const label = 'Colors'; const options = { @@ -308,15 +259,17 @@ const options = { const defaultValue = 'red'; const groupId = 'GROUP-ID1'; -const value = selectV2(label, options, defaultValue, groupId); +const value = select(label, options, defaultValue, groupId); ``` +> You can also provide options as an array like this: `['red', 'blue', 'yellow']` + ### files Allows you to get a value from a file input from the user. ```js -import { files } from '@storybook/addon-knobs/react'; +import { files } from '@storybook/addon-knobs'; const label = 'Images'; const defaultValue = []; @@ -331,7 +284,7 @@ const value = files(label, accept, defaultValue); Allow you to get date (and time) from the user. ```js -import { date } from '@storybook/addon-knobs/react'; +import { date } from '@storybook/addon-knobs'; const label = 'Event Date'; const defaultValue = new Date('Jan 20 2017'); @@ -373,12 +326,12 @@ Usage: ```js import { storiesOf } from '@storybook/react'; +import { withKnobsOptions } from '@storybook/addon-knobs'; const stories = storiesOf('Storybook Knobs', module); stories.addDecorator(withKnobsOptions({ - debounce: { wait: number, leading: boolean}, // Same as lodash debounce. - timestamps: true // Doesn't emit events while user is typing. + timestamps: true, // Doesn't emit events while user is typing. escapeHTML: true // Escapes strings to be safe for inserting as innerHTML. This option is true by default in storybook for Vue, Angular, and Polymer, because those frameworks allow rendering plain HTML. // You can still set it to false, but it's strongly unrecommendend in cases when you host your storybook on some route of your main site or web app. diff --git a/addons/knobs/angular.js b/addons/knobs/angular.js index e40620072b57..c22c26b6732d 100644 --- a/addons/knobs/angular.js +++ b/addons/knobs/angular.js @@ -1 +1 @@ -module.exports = require('./dist/angular'); +module.exports = require('./dist/deprecated'); diff --git a/addons/knobs/html.js b/addons/knobs/html.js index 4f7edc6bedba..c22c26b6732d 100644 --- a/addons/knobs/html.js +++ b/addons/knobs/html.js @@ -1 +1 @@ -module.exports = require('./dist/html'); +module.exports = require('./dist/deprecated'); diff --git a/addons/knobs/marko.js b/addons/knobs/marko.js index 239559b30c53..c22c26b6732d 100644 --- a/addons/knobs/marko.js +++ b/addons/knobs/marko.js @@ -1 +1 @@ -module.exports = require('./dist/marko'); +module.exports = require('./dist/deprecated'); diff --git a/addons/knobs/mithril.js b/addons/knobs/mithril.js index 884a541476ec..c22c26b6732d 100644 --- a/addons/knobs/mithril.js +++ b/addons/knobs/mithril.js @@ -1 +1 @@ -module.exports = require('./dist/mithril'); +module.exports = require('./dist/deprecated'); diff --git a/addons/knobs/polymer.js b/addons/knobs/polymer.js index b3cfb782c794..c22c26b6732d 100644 --- a/addons/knobs/polymer.js +++ b/addons/knobs/polymer.js @@ -1 +1 @@ -module.exports = require('./dist/polymer'); +module.exports = require('./dist/deprecated'); diff --git a/addons/knobs/react.js b/addons/knobs/react.js index 70e1111ae070..c22c26b6732d 100644 --- a/addons/knobs/react.js +++ b/addons/knobs/react.js @@ -1 +1 @@ -module.exports = require('./dist/react'); +module.exports = require('./dist/deprecated'); diff --git a/addons/knobs/src/angular/helpers.js b/addons/knobs/src/angular/helpers.js deleted file mode 100644 index 3b05496d41d9..000000000000 --- a/addons/knobs/src/angular/helpers.js +++ /dev/null @@ -1,136 +0,0 @@ -/* eslint no-underscore-dangle: 0 */ -// eslint-disable-next-line import/no-extraneous-dependencies -import { Component, SimpleChange, ChangeDetectorRef } from '@angular/core'; -import { getParameters, getAnnotations } from './utils'; - -const getComponentMetadata = ({ component, props = {}, moduleMetadata = {} }) => { - if (!component || typeof component !== 'function') throw new Error('No valid component provided'); - - const componentMeta = getAnnotations(component)[0] || {}; - const paramsMetadata = getParameters(component); - - return { - component, - props, - componentMeta, - moduleMetadata, - params: paramsMetadata, - }; -}; - -const getAnnotatedComponent = ({ componentMeta, component, params, knobStore, channel }) => { - const KnobWrapperComponent = function KnobWrapperComponent(cd, ...args) { - component.call(this, ...args); - this.cd = cd; - this.knobChanged = this.knobChanged.bind(this); - this.setPaneKnobs = this.setPaneKnobs.bind(this); - }; - - KnobWrapperComponent.prototype = Object.create(component.prototype); - KnobWrapperComponent.annotations = [new Component(componentMeta)]; - KnobWrapperComponent.parameters = [[ChangeDetectorRef], ...params]; - - KnobWrapperComponent.prototype.constructor = KnobWrapperComponent; - KnobWrapperComponent.prototype.ngOnInit = function onInit() { - if (component.prototype.ngOnInit) { - component.prototype.ngOnInit.call(this); - } - - channel.on('addon:knobs:knobChange', this.knobChanged); - channel.on('addon:knobs:knobClick', this.knobClicked); - knobStore.subscribe(this.setPaneKnobs); - this.setPaneKnobs(); - }; - - KnobWrapperComponent.prototype.ngOnDestroy = function onDestroy() { - if (component.prototype.ngOnDestroy) { - component.prototype.ngOnDestroy.call(this); - } - - channel.removeListener('addon:knobs:knobChange', this.knobChanged); - channel.removeListener('addon:knobs:knobClick', this.knobClicked); - knobStore.unsubscribe(this.setPaneKnobs); - }; - - KnobWrapperComponent.prototype.ngOnChanges = function onChanges(changes) { - if (component.prototype.ngOnChanges) { - component.prototype.ngOnChanges.call(this, changes); - } - }; - - KnobWrapperComponent.prototype.setPaneKnobs = function setPaneKnobs(timestamp = +new Date()) { - channel.emit('addon:knobs:setKnobs', { - knobs: knobStore.getAll(), - timestamp, - }); - }; - - KnobWrapperComponent.prototype.knobChanged = function knobChanged(change) { - const { name, value } = change; - const knobOptions = knobStore.get(name); - const oldValue = knobOptions.value; - knobOptions.value = value; - knobStore.markAllUnused(); - this[name] = value; - this.cd.detectChanges(); - this.ngOnChanges({ - [name]: new SimpleChange(oldValue, value, false), - }); - }; - - KnobWrapperComponent.prototype.knobClicked = function knobClicked(clicked) { - const knobOptions = knobStore.get(clicked.name); - knobOptions.callback(); - }; - - return KnobWrapperComponent; -}; - -const createComponentFromTemplate = (template, styles) => { - const componentClass = class DynamicComponent {}; - - return Component({ - template, - styles, - })(componentClass); -}; - -const resetKnobs = (knobStore, channel) => { - knobStore.reset(); - channel.emit('addon:knobs:setKnobs', { - knobs: knobStore.getAll(), - timestamp: false, - }); -}; - -export function prepareComponent({ getStory, context, channel, knobStore }) { - resetKnobs(knobStore, channel); - const story = getStory(context); - let { component } = story; - const { template, styles } = story; - - if (!component) { - component = createComponentFromTemplate(template, styles); - } - - const { componentMeta, props, params, moduleMetadata } = getComponentMetadata({ - ...story, - component, - }); - - if (!componentMeta && component) throw new Error('No component metadata available'); - - const AnnotatedComponent = getAnnotatedComponent({ - componentMeta, - component, - params, - knobStore, - channel, - }); - - return { - component: AnnotatedComponent, - props, - moduleMetadata, - }; -} diff --git a/addons/knobs/src/angular/index.js b/addons/knobs/src/angular/index.js deleted file mode 100644 index 688740ad83eb..000000000000 --- a/addons/knobs/src/angular/index.js +++ /dev/null @@ -1,24 +0,0 @@ -import { prepareComponent } from './helpers'; - -import { - knob, - text, - boolean, - number, - color, - object, - array, - date, - select, - selectV2, - button, - files, - makeDecorators, -} from '../base'; - -export { knob, text, boolean, number, color, object, array, date, select, selectV2, button, files }; - -export const angularHandler = (channel, knobStore) => getStory => context => - prepareComponent({ getStory, context, channel, knobStore }); - -export const { withKnobs, withKnobsOptions } = makeDecorators(angularHandler, { escapeHTML: true }); diff --git a/addons/knobs/src/angular/utils.js b/addons/knobs/src/angular/utils.js deleted file mode 100644 index ddb2abe543cb..000000000000 --- a/addons/knobs/src/angular/utils.js +++ /dev/null @@ -1,38 +0,0 @@ -/* globals window */ -/* eslint-disable no-param-reassign */ -// eslint-disable-next-line import/no-extraneous-dependencies -import { ɵReflectionCapabilities } from '@angular/core'; - -// eslint-disable-next-line new-cap -const reflectionCapabilities = new ɵReflectionCapabilities(); - -function getMeta(component, [name1, name2], defaultValue) { - if (!name2) { - name2 = name1; - name1 = `__${name1}__`; - } - - if (component[name1]) { - return component[name1]; - } - - if (component[name2]) { - return component[name2]; - } - - return window.Reflect.getMetadata(name2, component) || defaultValue; -} - -export function getAnnotations(component) { - return getMeta(component, ['annotations'], []); -} - -export function getParameters(component) { - const params = reflectionCapabilities.parameters(component); - - if (!params || !params[0]) { - return getMeta(component, ['parameters'], []); - } - - return params; -} diff --git a/addons/knobs/src/base.js b/addons/knobs/src/base.js deleted file mode 100644 index 1343fc15b10f..000000000000 --- a/addons/knobs/src/base.js +++ /dev/null @@ -1,99 +0,0 @@ -import deprecate from 'util-deprecate'; -import addons from '@storybook/addons'; - -import KnobManager from './KnobManager'; - -export const manager = new KnobManager(); - -export function knob(name, options) { - return manager.knob(name, options); -} - -export function text(name, value, groupId) { - return manager.knob(name, { type: 'text', value, groupId }); -} - -export function boolean(name, value, groupId) { - return manager.knob(name, { type: 'boolean', value, groupId }); -} - -export function number(name, value, options = {}, groupId) { - const rangeDefaults = { - min: 0, - max: 10, - step: 1, - }; - - const mergedOptions = options.range - ? { - ...rangeDefaults, - ...options, - } - : options; - - const finalOptions = { - ...mergedOptions, - type: 'number', - value, - groupId, - }; - - return manager.knob(name, finalOptions); -} - -export function color(name, value, groupId) { - return manager.knob(name, { type: 'color', value, groupId }); -} - -export function object(name, value, groupId) { - return manager.knob(name, { type: 'object', value, groupId }); -} - -export const select = deprecate( - (name, options, value, groupId) => - manager.knob(name, { type: 'select', options, value, groupId }), - 'in v4 keys/values of the options argument are reversed' -); - -export function selectV2(name, options, value, groupId) { - return manager.knob(name, { type: 'select', selectV2: true, options, value, groupId }); -} - -export function array(name, value, separator = ',', groupId) { - return manager.knob(name, { type: 'array', value, separator, groupId }); -} - -export function date(name, value = new Date(), groupId) { - const proxyValue = value ? value.getTime() : null; - return manager.knob(name, { type: 'date', value: proxyValue, groupId }); -} - -export function button(name, callback, groupId) { - return manager.knob(name, { type: 'button', callback, hideLabel: true, groupId }); -} - -export function files(name, accept, value = []) { - return manager.knob(name, { type: 'files', accept, value }); -} - -export function makeDecorators(handler, defaultOptions = {}) { - function wrapperKnobs(options) { - const allOptions = { ...defaultOptions, ...options }; - - manager.setOptions(allOptions); - const channel = addons.getChannel(); - manager.setChannel(channel); - channel.emit('addon:knobs:setOptions', allOptions); - - return handler(channel, manager.knobStore); - } - - return { - withKnobs(storyFn, context) { - return wrapperKnobs()(storyFn)(context); - }, - withKnobsOptions(options = {}) { - return (storyFn, context) => wrapperKnobs(options)(storyFn)(context); - }, - }; -} diff --git a/addons/knobs/src/components/Panel.js b/addons/knobs/src/components/Panel.js index c0f66faf5790..b64d96494577 100644 --- a/addons/knobs/src/components/Panel.js +++ b/addons/knobs/src/components/Panel.js @@ -1,6 +1,5 @@ import React from 'react'; import PropTypes from 'prop-types'; -import debounce from 'lodash.debounce'; import GroupTabs from './GroupTabs'; import PropForm from './PropForm'; @@ -81,14 +80,8 @@ export default class Panel extends React.Component { this.setState({ groupId: name }); } - setOptions(options = { debounce: false, timestamps: false }) { + setOptions(options = { timestamps: false }) { this.options = options; - - if (options.debounce) { - this.emitChange = debounce(this.emitChange, options.debounce.wait, { - leading: options.debounce.leading, - }); - } } setKnobs({ knobs, timestamp }) { diff --git a/addons/knobs/src/components/__tests__/Select.js b/addons/knobs/src/components/__tests__/Select.js index 71dd7825196b..464d43f66bd2 100644 --- a/addons/knobs/src/components/__tests__/Select.js +++ b/addons/knobs/src/components/__tests__/Select.js @@ -16,19 +16,7 @@ describe('Select', () => { }; }); - it('displays value', () => { - const wrapper = shallow(); - - const green = wrapper.find('option').first(); - expect(green.text()).toEqual('#00ff00'); - expect(green.prop('value')).toEqual('Green'); - }); - - describe('selectV2', () => { - beforeEach(() => { - knob.selectV2 = true; - }); - + describe('displays value', () => { it('correctly maps option keys and values', () => { const wrapper = shallow(); diff --git a/addons/knobs/src/components/types/Select.js b/addons/knobs/src/components/types/Select.js index 33a445e4b60a..f091f8f83fa6 100644 --- a/addons/knobs/src/components/types/Select.js +++ b/addons/knobs/src/components/types/Select.js @@ -18,35 +18,17 @@ const styles = { }; class SelectType extends React.Component { - constructor(props, context) { - super(props, context); - - if (!props.knob.selectV2) { - console.info('Select Knob V1 will be deprecated, please upgrade to V2 of Select Knob'); // eslint-disable-line no-console - } - } - - renderOptionList({ options, selectV2 }) { + renderOptionList({ options }) { if (Array.isArray(options)) { return options.map(val => this.renderOption(val, val)); } - return Object.keys(options).map(key => this.renderOption(key, options[key], selectV2)); + return Object.keys(options).map(key => this.renderOption(key, options[key])); } - renderOption(key, value, selectV2) { - const opts = { - key, - value: key, - }; - - let display = value; - - if (selectV2) { - opts.value = value; - display = key; - } + renderOption(key, value) { + const opts = { key, value }; - return ; + return ; } render() { @@ -75,7 +57,6 @@ SelectType.propTypes = { name: PropTypes.string, value: PropTypes.string, options: PropTypes.oneOfType([PropTypes.array, PropTypes.object]), - selectV2: PropTypes.bool, }), onChange: PropTypes.func, }; diff --git a/addons/knobs/src/deprecated.js b/addons/knobs/src/deprecated.js new file mode 100644 index 000000000000..7822c6bf60ce --- /dev/null +++ b/addons/knobs/src/deprecated.js @@ -0,0 +1,31 @@ +import deprecate from 'util-deprecate'; + +import { + knob, + text, + boolean, + number, + color, + object, + array, + date, + select, + files, + button, + withKnobs as commonWithKnobs, + withKnobsOptions as commonWithKnobsOptions, +} from '.'; + +export { knob, text, boolean, number, color, object, array, date, select, files, button }; + +export const selectV2 = deprecate(select, 'selectV2 has been renamed to select'); + +export const withKnobs = deprecate( + commonWithKnobs, + "addon-knobs: framework-specific imports are deprecated, just use `import {withKnobs} from '@storybook/addon-knobs`" +); + +export const withKnobsOptions = deprecate( + commonWithKnobsOptions, + "addon-knobs: framework-specific imports are deprecated, just use `import {withKnobsOptions} from '@storybook/addon-knobs`" +); diff --git a/addons/knobs/src/html/index.js b/addons/knobs/src/html/index.js deleted file mode 100644 index a032f2979ea0..000000000000 --- a/addons/knobs/src/html/index.js +++ /dev/null @@ -1,32 +0,0 @@ -import registerKnobs from './registerKnobs'; - -import { - knob, - text, - boolean, - number, - color, - object, - array, - date, - select, - files, - manager, - makeDecorators, -} from '../base'; - -export { knob, text, boolean, number, color, object, array, date, select, files }; - -export function button(name, callback) { - return manager.knob(name, { type: 'button', value: Date.now(), callback, hideLabel: true }); -} - -function prepareComponent({ getStory, context }) { - registerKnobs(); - - return getStory(context); -} - -export const htmlHandler = () => getStory => context => prepareComponent({ getStory, context }); - -export const { withKnobs, withKnobsOptions } = makeDecorators(htmlHandler, { escapeHTML: true }); diff --git a/addons/knobs/src/index.js b/addons/knobs/src/index.js index 5c264102cb25..d0bc02d4916f 100644 --- a/addons/knobs/src/index.js +++ b/addons/knobs/src/index.js @@ -1,56 +1,86 @@ -import { window } from 'global'; -import deprecate from 'util-deprecate'; import addons from '@storybook/addons'; -import { vueHandler } from './vue'; -import { reactHandler } from './react'; - -import { - array, - boolean, - button, - files, - color, - date, - knob, - manager, - number, - object, - select, - selectV2, - text, -} from './base'; - -export { knob, text, boolean, number, color, object, array, date, button, select, selectV2, files }; - -deprecate(() => {}, -'Using @storybook/addon-knobs directly is discouraged, please use @storybook/addon-knobs/{{framework}}'); - -// generic higher-order component decorator for all platforms - usage is discouraged -// This file Should be removed with 4.0 release -function wrapperKnobs(options) { - const channel = addons.getChannel(); - manager.setChannel(channel); +import { manager, registerKnobs } from './registerKnobs'; + +export function knob(name, options) { + return manager.knob(name, options); +} + +export function text(name, value, groupId) { + return manager.knob(name, { type: 'text', value, groupId }); +} + +export function boolean(name, value, groupId) { + return manager.knob(name, { type: 'boolean', value, groupId }); +} + +export function number(name, value, options = {}, groupId) { + const rangeDefaults = { + min: 0, + max: 10, + step: 1, + }; + + const mergedOptions = options.range + ? { + ...rangeDefaults, + ...options, + } + : options; + + const finalOptions = { + ...mergedOptions, + type: 'number', + value, + groupId, + }; - if (options) channel.emit('addon:knobs:setOptions', options); + return manager.knob(name, finalOptions); +} + +export function color(name, value, groupId) { + return manager.knob(name, { type: 'color', value, groupId }); +} - switch (window.STORYBOOK_ENV) { - case 'vue': { - return vueHandler(channel, manager.knobStore); - } - case 'react': { - return reactHandler(channel, manager.knobStore); - } - default: { - return reactHandler(channel, manager.knobStore); - } - } +export function object(name, value, groupId) { + return manager.knob(name, { type: 'object', value, groupId }); } -export function withKnobs(storyFn, context) { - return wrapperKnobs()(storyFn)(context); +export function select(name, options, value, groupId) { + return manager.knob(name, { type: 'select', selectV2: true, options, value, groupId }); } -export function withKnobsOptions(options = {}) { - return (storyFn, context) => wrapperKnobs(options)(storyFn)(context); +export function array(name, value, separator = ',', groupId) { + return manager.knob(name, { type: 'array', value, separator, groupId }); } + +export function date(name, value = new Date(), groupId) { + const proxyValue = value ? value.getTime() : null; + return manager.knob(name, { type: 'date', value: proxyValue, groupId }); +} + +export function button(name, callback, groupId) { + return manager.knob(name, { type: 'button', callback, hideLabel: true, groupId }); +} + +export function files(name, accept, value = []) { + return manager.knob(name, { type: 'files', accept, value }); +} + +const defaultOptions = { + escapeHTML: true, +}; + +export const withKnobsOptions = (options = {}) => storyFn => { + const allOptions = { ...defaultOptions, ...options }; + + manager.setOptions(allOptions); + const channel = addons.getChannel(); + manager.setChannel(channel); + channel.emit('addon:knobs:setOptions', allOptions); + + registerKnobs(); + return storyFn(); +}; + +export const withKnobs = withKnobsOptions(); diff --git a/addons/knobs/src/marko/WrapStory.marko b/addons/knobs/src/marko/WrapStory.marko deleted file mode 100644 index 790abc65f552..000000000000 --- a/addons/knobs/src/marko/WrapStory.marko +++ /dev/null @@ -1,70 +0,0 @@ -class { - - onCreate(input) { - this.props = input.props; - this.knobChanged = this.knobChanged.bind(this); - this.knobClicked = this.knobClicked.bind(this); - this.resetKnobs = this.resetKnobs.bind(this); - this.setPaneKnobs = this.setPaneKnobs.bind(this); - } - - onMount() { - // Watch for changes in knob editor. - this.props.channel.on('addon:knobs:knobChange', this.knobChanged); - // Watch for clicks in knob editor. - this.props.channel.on('addon:knobs:knobClick', this.knobClicked); - // Watch for the reset event and reset knobs. - this.props.channel.on('addon:knobs:reset', this.resetKnobs); - // Watch for any change in the knobStore and set the panel again for those changes. - this.props.knobStore.subscribe(this.setPaneKnobs); - // Set knobs in the panel for the first time. - this.setPaneKnobs(); - } - - onDestroy() { - this.props.channel.removeListener('addon:knobs:knobChange', this.knobChanged); - this.props.channel.removeListener('addon:knobs:knobClick', this.knobClicked); - this.props.channel.removeListener('addon:knobs:reset', this.resetKnobs); - this.props.knobStore.unsubscribe(this.setPaneKnobs); - } - - setPaneKnobs(timestamp = +new Date()) { - const { channel, knobStore } = this.props; - channel.emit('addon:knobs:setKnobs', { knobs: knobStore.getAll(), timestamp }); - } - - knobChanged(change) { - const { name, value } = change; - const { knobStore, storyFn, context } = this.props; - - // Update the related knob and it's value. - var knobOptions = knobStore.get(name); - knobOptions.value = value; - knobStore.markAllUnused(); - - this.renderElement(storyFn(context)); - } - - knobClicked(clicked) { - let knobOptions = this.props.knobStore.get(clicked.name); - knobOptions.callback(); - } - - resetKnobs() { - const { knobStore, storyFn, context } = this.props; - knobStore.reset(); - this.renderElement(storyFn(context)); - this.setPaneKnobs(false); - } - - renderElement(storyContent) { - var WrapperElm = document.getElementById('Wrapper'); - if(this.currLoadedComponent) { - this.currLoadedComponent.destroy(); - this.currLoadedComponent = null; - } - this.currLoadedComponent = storyContent.appendTo(WrapperElm).getComponent(); - } -} - -
\ No newline at end of file diff --git a/addons/knobs/src/marko/index.js b/addons/knobs/src/marko/index.js deleted file mode 100644 index 66ec565b5d9f..000000000000 --- a/addons/knobs/src/marko/index.js +++ /dev/null @@ -1,43 +0,0 @@ -import addons from '@storybook/addons'; -import WrapStory from './WrapStory.marko'; - -import { - knob, - text, - boolean, - number, - color, - object, - array, - date, - select, - selectV2, - button, - manager, -} from '../base'; - -export { knob, text, boolean, number, color, object, array, date, select, selectV2, button }; - -export const markoHandler = (channel, knobStore) => getStory => context => { - const initialContent = getStory(context); - const props = { context, storyFn: getStory, channel, knobStore, initialContent }; - - return WrapStory.renderSync({ props }); -}; - -function wrapperKnobs(options) { - const channel = addons.getChannel(); - manager.setChannel(channel); - - if (options) channel.emit('addon:knobs:setOptions', options); - - return markoHandler(channel, manager.knobStore); -} - -export function withKnobs(storyFn, context) { - return wrapperKnobs()(storyFn)(context); -} - -export function withKnobsOptions(options = {}) { - return (storyFn, context) => wrapperKnobs(options)(storyFn)(context); -} diff --git a/addons/knobs/src/mithril/WrapStory.js b/addons/knobs/src/mithril/WrapStory.js deleted file mode 100644 index dd0ff093e754..000000000000 --- a/addons/knobs/src/mithril/WrapStory.js +++ /dev/null @@ -1,68 +0,0 @@ -// eslint-disable-next-line import/no-extraneous-dependencies -import m from 'mithril'; - -export default class WrapStory { - constructor(vnode) { - this.knobChanged = this.knobChanged.bind(this); - this.knobClicked = this.knobClicked.bind(this); - this.resetKnobs = this.resetKnobs.bind(this); - this.setPaneKnobs = this.setPaneKnobs.bind(this); - this.props = vnode.attrs; - this.storyContent = vnode.attrs.initialContent; - } - - oncreate() { - // Watch for changes in knob editor. - this.props.channel.on('addon:knobs:knobChange', this.knobChanged); - // Watch for clicks in knob editor. - this.props.channel.on('addon:knobs:knobClick', this.knobClicked); - // Watch for the reset event and reset knobs. - this.props.channel.on('addon:knobs:reset', this.resetKnobs); - // Watch for any change in the knobStore and set the panel again for those - // changes. - this.props.knobStore.subscribe(this.setPaneKnobs); - // Set knobs in the panel for the first time. - this.setPaneKnobs(); - } - - onremove() { - this.props.channel.removeListener('addon:knobs:knobChange', this.knobChanged); - this.props.channel.removeListener('addon:knobs:knobClick', this.knobClicked); - this.props.channel.removeListener('addon:knobs:reset', this.resetKnobs); - this.props.knobStore.unsubscribe(this.setPaneKnobs); - } - - setPaneKnobs(timestamp = +new Date()) { - const { channel, knobStore } = this.props; - channel.emit('addon:knobs:setKnobs', { knobs: knobStore.getAll(), timestamp }); - } - - knobChanged(change) { - const { name, value } = change; - const { knobStore, storyFn, context } = this.props; - // Update the related knob and it's value. - const knobOptions = knobStore.get(name); - - knobOptions.value = value; - knobStore.markAllUnused(); - this.storyContent = storyFn(context); - m.redraw(); - } - - knobClicked(clicked) { - const knobOptions = this.props.knobStore.get(clicked.name); - knobOptions.callback(); - } - - resetKnobs() { - const { knobStore, storyFn, context } = this.props; - knobStore.reset(); - this.storyContent = storyFn(context); - m.redraw(); - this.setPaneKnobs(false); - } - - view() { - return m(this.storyContent); - } -} diff --git a/addons/knobs/src/mithril/index.js b/addons/knobs/src/mithril/index.js deleted file mode 100644 index 216049d1694c..000000000000 --- a/addons/knobs/src/mithril/index.js +++ /dev/null @@ -1,33 +0,0 @@ -/** @jsx m */ - -// eslint-disable-next-line import/no-extraneous-dependencies -import m from 'mithril'; - -import WrapStory from './WrapStory'; - -import { - knob, - text, - boolean, - number, - color, - object, - array, - date, - select, - selectV2, - button, - makeDecorators, -} from '../base'; - -export { knob, text, boolean, number, color, object, array, date, select, selectV2, button }; - -export const mithrilHandler = (channel, knobStore) => getStory => context => { - const initialContent = getStory(context); - const props = { context, storyFn: getStory, channel, knobStore, initialContent }; - return { - view: () => , - }; -}; - -export const { withKnobs, withKnobsOptions } = makeDecorators(mithrilHandler); diff --git a/addons/knobs/src/polymer/WrapStory.html b/addons/knobs/src/polymer/WrapStory.html deleted file mode 100644 index 60bf2e35a6f4..000000000000 --- a/addons/knobs/src/polymer/WrapStory.html +++ /dev/null @@ -1,94 +0,0 @@ - - - diff --git a/addons/knobs/src/polymer/index.js b/addons/knobs/src/polymer/index.js deleted file mode 100644 index 32df8b36a678..000000000000 --- a/addons/knobs/src/polymer/index.js +++ /dev/null @@ -1,33 +0,0 @@ -import window from 'global'; -import './WrapStory.html'; - -import { - knob, - text, - boolean, - number, - color, - object, - array, - date, - select, - files, - manager, - makeDecorators, -} from '../base'; - -export { knob, text, boolean, number, color, object, array, date, select, files }; - -export function button(name, callback) { - return manager.knob(name, { type: 'button', value: Date.now(), callback, hideLabel: true }); -} - -function prepareComponent({ getStory, context, channel, knobStore }) { - const WrapStory = window.customElements.get('wrap-story'); - return new WrapStory(getStory(context), channel, context, getStory, knobStore); -} - -export const polymerHandler = (channel, knobStore) => getStory => context => - prepareComponent({ getStory, context, channel, knobStore }); - -export const { withKnobs, withKnobsOptions } = makeDecorators(polymerHandler, { escapeHTML: true }); diff --git a/addons/knobs/src/react/WrapStory.js b/addons/knobs/src/react/WrapStory.js deleted file mode 100644 index 148ba186cd1f..000000000000 --- a/addons/knobs/src/react/WrapStory.js +++ /dev/null @@ -1,109 +0,0 @@ -/* eslint no-underscore-dangle: 0 */ - -import PropTypes from 'prop-types'; -import React from 'react'; -import { polyfill } from 'react-lifecycles-compat'; - -class WrapStory extends React.Component { - constructor(props) { - super(props); - this.knobChanged = this.knobChanged.bind(this); - this.knobClicked = this.knobClicked.bind(this); - this.resetKnobs = this.resetKnobs.bind(this); - this.setPaneKnobs = this.setPaneKnobs.bind(this); - this._knobsAreReset = false; - this.state = {}; - } - - componentDidMount() { - // Watch for changes in knob editor. - this.props.channel.on('addon:knobs:knobChange', this.knobChanged); - // Watch for clicks in knob editor. - this.props.channel.on('addon:knobs:knobClick', this.knobClicked); - // Watch for the reset event and reset knobs. - this.props.channel.on('addon:knobs:reset', this.resetKnobs); - // Watch for any change in the knobStore and set the panel again for those - // changes. - this.props.knobStore.subscribe(this.setPaneKnobs); - // Set knobs in the panel for the first time. - this.setPaneKnobs(); - } - - componentWillUnmount() { - this.props.channel.removeListener('addon:knobs:knobChange', this.knobChanged); - this.props.channel.removeListener('addon:knobs:knobClick', this.knobClicked); - this.props.channel.removeListener('addon:knobs:reset', this.resetKnobs); - this.props.knobStore.unsubscribe(this.setPaneKnobs); - } - - setPaneKnobs(timestamp = +new Date()) { - const { channel, knobStore } = this.props; - channel.emit('addon:knobs:setKnobs', { knobs: knobStore.getAll(), timestamp }); - } - - knobChanged(change) { - const { name, value } = change; - const { knobStore, storyFn, context } = this.props; - // Update the related knob and it's value. - const knobOptions = knobStore.get(name); - - knobOptions.value = value; - knobStore.markAllUnused(); - this.setState({ storyContent: storyFn(context) }); - } - - knobClicked(clicked) { - const knobOptions = this.props.knobStore.get(clicked.name); - knobOptions.callback(); - } - - resetKnobs() { - const { knobStore, storyFn, context } = this.props; - knobStore.reset(); - this.setState({ storyContent: storyFn(context) }); - this.setPaneKnobs(false); - } - - render() { - return this.state.storyContent; - } -} - -WrapStory.getDerivedStateFromProps = ({ initialContent }, { prevContent }) => { - if (initialContent !== prevContent) { - return { - storyContent: initialContent, - prevContent: initialContent, - }; - } - return null; -}; - -WrapStory.defaultProps = { - context: {}, - initialContent: {}, - storyFn: context => context, -}; - -WrapStory.propTypes = { - context: PropTypes.object, // eslint-disable-line react/forbid-prop-types - storyFn: PropTypes.func, - channel: PropTypes.shape({ - on: PropTypes.func, - removeListener: PropTypes.func, - }).isRequired, - knobStore: PropTypes.shape({ - channel: PropTypes.func, - get: PropTypes.func, - getAll: PropTypes.func, - markAllUnused: PropTypes.func, - reset: PropTypes.func, - subscribe: PropTypes.func, - unsubscribe: PropTypes.func, - }).isRequired, - initialContent: PropTypes.node, // eslint-disable-line react/no-unused-prop-types -}; - -polyfill(WrapStory); - -export default WrapStory; diff --git a/addons/knobs/src/react/index.js b/addons/knobs/src/react/index.js deleted file mode 100644 index 109411497257..000000000000 --- a/addons/knobs/src/react/index.js +++ /dev/null @@ -1,29 +0,0 @@ -import React from 'react'; - -import WrapStory from './WrapStory'; - -import { - knob, - text, - boolean, - number, - color, - object, - array, - date, - select, - selectV2, - button, - files, - makeDecorators, -} from '../base'; - -export { knob, text, boolean, number, color, object, array, date, select, selectV2, button, files }; - -export const reactHandler = (channel, knobStore) => getStory => context => { - const initialContent = getStory(context); - const props = { context, storyFn: getStory, channel, knobStore, initialContent }; - return ; -}; - -export const { withKnobs, withKnobsOptions } = makeDecorators(reactHandler); diff --git a/addons/knobs/src/react/index.test.js b/addons/knobs/src/react/index.test.js deleted file mode 100644 index 75b1a08e47f1..000000000000 --- a/addons/knobs/src/react/index.test.js +++ /dev/null @@ -1,27 +0,0 @@ -import React from 'react'; -import { reactHandler } from './index'; -import { shallow } from 'enzyme'; // eslint-disable-line -import KnobStore from '../KnobStore'; - -describe('React Handler', () => { - describe('wrapStory', () => { - it('should contain the story and add correct props', () => { - const testChannel = { emit: jest.fn(), on: jest.fn() }; - const testStory = () =>
Test Content
; - const testContext = { - kind: 'Foo', - story: 'bar baz', - }; - - const testStore = new KnobStore(); - - const wrappedStory = reactHandler(testChannel, testStore)(testStory)(testContext); - const wrapper = shallow(wrappedStory); - expect(wrapper.find('#test-story')).toHaveLength(1); - - const storyWrapperProps = wrappedStory.props; - expect(storyWrapperProps.channel).toEqual(testChannel); - expect(storyWrapperProps.context).toEqual(testContext); - }); - }); -}); diff --git a/addons/knobs/src/html/registerKnobs.js b/addons/knobs/src/registerKnobs.js similarity index 92% rename from addons/knobs/src/html/registerKnobs.js rename to addons/knobs/src/registerKnobs.js index b654e0fe4bf7..bffd55bb0514 100644 --- a/addons/knobs/src/html/registerKnobs.js +++ b/addons/knobs/src/registerKnobs.js @@ -1,7 +1,8 @@ import addons from '@storybook/addons'; import Events from '@storybook/core-events'; -import { manager } from '../base'; +import KnobManager from './KnobManager'; +export const manager = new KnobManager(); const { knobStore } = manager; function forceReRender() { @@ -57,8 +58,6 @@ function connectCallbacks() { return disconnectCallbacks; } -function registerKnobs() { +export function registerKnobs() { addons.getChannel().emit(Events.REGISTER_SUBSCRIPTION, connectCallbacks); } - -export default registerKnobs; diff --git a/addons/knobs/src/vue/index.js b/addons/knobs/src/vue/index.js deleted file mode 100644 index df0049eee272..000000000000 --- a/addons/knobs/src/vue/index.js +++ /dev/null @@ -1,75 +0,0 @@ -import { - knob, - text, - boolean, - number, - color, - object, - array, - date, - select, - selectV2, - button, - files, - makeDecorators, -} from '../base'; - -export { knob, text, boolean, number, color, object, array, date, select, selectV2, button, files }; - -export const vueHandler = (channel, knobStore) => getStory => context => ({ - data() { - return { - context, - getStory, - story: getStory(context), - }; - }, - - render(h) { - return h(this.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.story = this.getStory(this.context); - this.$forceUpdate(); - }, - - onKnobClick(clicked) { - const knobOptions = knobStore.get(clicked.name); - knobOptions.callback(); - }, - - onKnobReset() { - knobStore.reset(); - this.setPaneKnobs(false); - this.story = this.getStory(this.context); - 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); - channel.on('addon:knobs:knobClick', this.onKnobClick); - knobStore.subscribe(this.setPaneKnobs); - }, - - beforeDestroy() { - channel.removeListener('addon:knobs:reset', this.onKnobReset); - channel.removeListener('addon:knobs:knobChange', this.onKnobChange); - channel.removeListener('addon:knobs:knobClick', this.onKnobClick); - knobStore.unsubscribe(this.setPaneKnobs); - }, -}); - -export const { withKnobs, withKnobsOptions } = makeDecorators(vueHandler, { escapeHTML: true }); diff --git a/addons/knobs/src/vue/index.test.js b/addons/knobs/src/vue/index.test.js deleted file mode 100644 index 9e2b4ca7a3d3..000000000000 --- a/addons/knobs/src/vue/index.test.js +++ /dev/null @@ -1,40 +0,0 @@ -import Vue from 'vue'; -import { vueHandler } from './index'; -import KnobStore from '../KnobStore'; - -describe('Vue handler', () => { - it('Returns a component with a created function', () => { - const testChannel = { emit: jest.fn(), on: jest.fn() }; - const testStory = () => ({ template: '
testStory
' }); - const testContext = { - kind: 'Foo', - story: 'bar baz', - }; - - const testStore = new KnobStore(); - const component = vueHandler(testChannel, testStore)(testStory)(testContext); - - expect(component).toMatchObject({ - created: expect.any(Function), - beforeDestroy: expect.any(Function), - render: expect.any(Function), - }); - }); - - it('Subscribes to the channel on creation', () => { - const testChannel = { emit: () => {}, on: jest.fn() }; - const testStory = () => ({ render: h => h('div', ['testStory']) }); - const testContext = { - kind: 'Foo', - story: 'bar baz', - }; - - const testStore = new KnobStore(); - new Vue(vueHandler(testChannel, testStore)(testStory)(testContext)).$mount(); - - expect(testChannel.on).toHaveBeenCalledTimes(3); - expect(testChannel.on).toHaveBeenCalledWith('addon:knobs:reset', expect.any(Function)); - expect(testChannel.on).toHaveBeenCalledWith('addon:knobs:knobChange', expect.any(Function)); - expect(testChannel.on).toHaveBeenCalledWith('addon:knobs:knobClick', expect.any(Function)); - }); -}); diff --git a/addons/knobs/vue.js b/addons/knobs/vue.js index 42311b17563f..c22c26b6732d 100644 --- a/addons/knobs/vue.js +++ b/addons/knobs/vue.js @@ -1 +1 @@ -module.exports = require('./dist/vue'); +module.exports = require('./dist/deprecated'); diff --git a/app/polymer/src/client/preview/render.js b/app/polymer/src/client/preview/render.js index 6ed2e5a9bce3..6fbbe908c21f 100644 --- a/app/polymer/src/client/preview/render.js +++ b/app/polymer/src/client/preview/render.js @@ -4,7 +4,14 @@ import { html, render, TemplateResult } from 'lit-html'; const rootElement = document.getElementById('root'); -export default function renderMain({ story, selectedKind, selectedStory, showMain, showError }) { +export default function renderMain({ + story, + selectedKind, + selectedStory, + showMain, + showError, + forceRender, +}) { const component = story(); if (!component) { @@ -24,7 +31,10 @@ export default function renderMain({ story, selectedKind, selectedStory, showMai } else if (component instanceof TemplateResult) { // `render` stores the TemplateInstance in the Node and tries to update based on that. // Since we reuse `rootElement` for all stories, remove the stored instance first. - render(html``, rootElement); + // But forceRender means that it's the same story, so we want too keep the state in that case. + if (!forceRender) { + render(html``, rootElement); + } render(component, rootElement); } else { rootElement.innerHTML = ''; diff --git a/app/react/src/client/preview/render.js b/app/react/src/client/preview/render.js index 2094cc207b91..507e58685636 100644 --- a/app/react/src/client/preview/render.js +++ b/app/react/src/client/preview/render.js @@ -13,7 +13,14 @@ function render(node, el) { ); } -export default function renderMain({ story, selectedKind, selectedStory, showMain, showError }) { +export default function renderMain({ + story, + selectedKind, + selectedStory, + showMain, + showError, + forceRender, +}) { const element = story(); if (!element) { @@ -42,7 +49,10 @@ export default function renderMain({ story, selectedKind, selectedStory, showMai // Otherwise, React may not recrease instances for every story run. // This could leads to issues like below: // https://github.com/storybooks/react-storybook/issues/81 - ReactDOM.unmountComponentAtNode(rootEl); + // But forceRender means that it's the same story, so we want too keep the state in that case. + if (!forceRender) { + ReactDOM.unmountComponentAtNode(rootEl); + } showMain(); render(element, rootEl); } diff --git a/docs/src/pages/basics/quick-start-guide/index.md b/docs/src/pages/basics/quick-start-guide/index.md index 68f4b11f10ac..52d0b49c8d46 100644 --- a/docs/src/pages/basics/quick-start-guide/index.md +++ b/docs/src/pages/basics/quick-start-guide/index.md @@ -29,11 +29,12 @@ Then you can access your storybook from the browser. * * * To learn more about what `getstorybook` command does, have a look at our slow start guides: - * [React](/basics/guide-react/) - * [Vue](/basics/guide-vue/) - * [Angular](/basics/guide-angular/) - * [Mithril](/basics/guide-mithril/) - * [HTML](/basics/guide-html/) +* [React](/basics/guide-react/) +* [Vue](/basics/guide-vue/) +* [Angular](/basics/guide-angular/) +* [Mithril](/basics/guide-mithril/) +* [Marko](/basics/guide-marko/) +* [HTML](/basics/guide-html/) If you prefer a guided tutorial to reading docs, head to [Learn Storybook](https://www.learnstorybook.com) for a step-by-step guide (currently React-only). diff --git a/examples/angular-cli/addon-jest.testresults.json b/examples/angular-cli/addon-jest.testresults.json index 62fbfd2c65ce..08bff9cecbaf 100644 --- a/examples/angular-cli/addon-jest.testresults.json +++ b/examples/angular-cli/addon-jest.testresults.json @@ -1 +1 @@ -{"numFailedTestSuites":0,"numFailedTests":0,"numPassedTestSuites":1,"numPassedTests":3,"numPendingTestSuites":0,"numPendingTests":0,"numRuntimeErrorTestSuites":0,"numTotalTestSuites":1,"numTotalTests":3,"snapshot":{"added":0,"didUpdate":false,"failure":false,"filesAdded":0,"filesRemoved":0,"filesUnmatched":0,"filesUpdated":0,"matched":0,"total":0,"unchecked":0,"uncheckedKeys":[],"unmatched":0,"updated":0},"startTime":1525677901357,"success":true,"testResults":[{"assertionResults":[{"ancestorTitles":["AppComponent"],"failureMessages":[],"fullName":"AppComponent should create the app","location":null,"status":"passed","title":"should create the app"},{"ancestorTitles":["AppComponent"],"failureMessages":[],"fullName":"AppComponent should have as title 'app'","location":null,"status":"passed","title":"should have as title 'app'"},{"ancestorTitles":["AppComponent"],"failureMessages":[],"fullName":"AppComponent should render title in a h1 tag","location":null,"status":"passed","title":"should render title in a h1 tag"}],"endTime":1525677905915,"message":"","name":"C:\\tmp\\storybook\\examples\\angular-cli\\src\\app\\app.component.spec.ts","startTime":1525677902829,"status":"passed","summary":""}],"wasInterrupted":false} \ No newline at end of file +{"numFailedTestSuites":0,"numFailedTests":0,"numPassedTestSuites":2,"numPassedTests":6,"numPendingTestSuites":0,"numPendingTests":0,"numRuntimeErrorTestSuites":0,"numTotalTestSuites":2,"numTotalTests":6,"snapshot":{"added":0,"didUpdate":false,"failure":false,"filesAdded":0,"filesRemoved":0,"filesUnmatched":0,"filesUpdated":0,"matched":0,"total":0,"unchecked":0,"uncheckedKeys":[],"unmatched":0,"updated":0},"startTime":1525873343397,"success":true,"testResults":[{"assertionResults":[{"ancestorTitles":["AppComponent"],"failureMessages":[],"fullName":"AppComponent should create the app","location":null,"status":"passed","title":"should create the app"},{"ancestorTitles":["AppComponent"],"failureMessages":[],"fullName":"AppComponent should have as title 'app'","location":null,"status":"passed","title":"should have as title 'app'"},{"ancestorTitles":["AppComponent"],"failureMessages":[],"fullName":"AppComponent should render title in a h1 tag","location":null,"status":"passed","title":"should render title in a h1 tag"}],"endTime":1525873348302,"message":"","name":"/Users/jetbrains/IdeaProjects/storybook/examples/angular-cli/dist/app/app.component.spec.ts","startTime":1525873345081,"status":"passed","summary":""},{"assertionResults":[{"ancestorTitles":["AppComponent"],"failureMessages":[],"fullName":"AppComponent should create the app","location":null,"status":"passed","title":"should create the app"},{"ancestorTitles":["AppComponent"],"failureMessages":[],"fullName":"AppComponent should have as title 'app'","location":null,"status":"passed","title":"should have as title 'app'"},{"ancestorTitles":["AppComponent"],"failureMessages":[],"fullName":"AppComponent should render title in a h1 tag","location":null,"status":"passed","title":"should render title in a h1 tag"}],"endTime":1525873348352,"message":"","name":"/Users/jetbrains/IdeaProjects/storybook/examples/angular-cli/src/app/app.component.spec.ts","startTime":1525873345086,"status":"passed","summary":""}],"wasInterrupted":false} \ No newline at end of file diff --git a/examples/angular-cli/src/stories/__snapshots__/addon-knobs.stories.storyshot b/examples/angular-cli/src/stories/__snapshots__/addon-knobs.stories.storyshot index 7b714f4052b2..e6705c07ae8c 100644 --- a/examples/angular-cli/src/stories/__snapshots__/addon-knobs.stories.storyshot +++ b/examples/angular-cli/src/stories/__snapshots__/addon-knobs.stories.storyshot @@ -27,7 +27,7 @@ exports[`Storyshots Addon|Knobs All knobs 1`] = `

- I have a stock of 20 apple, costing $ 2.25 each. + I have a stock of 20 apples, costing $ 2.25 each.

diff --git a/examples/angular-cli/src/stories/__snapshots__/custom-styles.stories.storyshot b/examples/angular-cli/src/stories/__snapshots__/custom-styles.stories.storyshot index 9999b7af6024..cdf3c67078c3 100644 --- a/examples/angular-cli/src/stories/__snapshots__/custom-styles.stories.storyshot +++ b/examples/angular-cli/src/stories/__snapshots__/custom-styles.stories.storyshot @@ -31,18 +31,15 @@ exports[`Storyshots Custom|Style With Knobs 1`] = ` data={[Function Object]} target={[Function ViewContainerRef_]} > - + diff --git a/examples/angular-cli/src/stories/addon-knobs.stories.ts b/examples/angular-cli/src/stories/addon-knobs.stories.ts index ff4a661486f4..ea71f6d90c58 100644 --- a/examples/angular-cli/src/stories/addon-knobs.stories.ts +++ b/examples/angular-cli/src/stories/addon-knobs.stories.ts @@ -11,7 +11,7 @@ import { color, date, button, -} from '@storybook/addon-knobs/angular'; +} from '@storybook/addon-knobs'; import { SimpleKnobsComponent } from './knobs.component'; import { AllKnobsComponent } from './all-knobs.component'; @@ -53,11 +53,11 @@ storiesOf('Addon|Knobs', module) step: 5, }); const fruits = { - apples: 'Apple', - bananas: 'Banana', - cherries: 'Cherry', + Apple: 'apples', + Banana: 'bananas', + Cherry: 'cherries', }; - const fruit = select('fruit', fruits, 'apple'); + const fruit = select('fruit', fruits, 'apples'); const price = number('price', 2.25); const border = color('border', 'deeppink'); diff --git a/examples/html-kitchen-sink/stories/__snapshots__/addon-knobs.stories.storyshot b/examples/html-kitchen-sink/stories/__snapshots__/addon-knobs.stories.storyshot index 73f9636e4b14..1517b1b70843 100644 --- a/examples/html-kitchen-sink/stories/__snapshots__/addon-knobs.stories.storyshot +++ b/examples/html-kitchen-sink/stories/__snapshots__/addon-knobs.stories.storyshot @@ -17,7 +17,7 @@ exports[`Storyshots Addons|Knobs All knobs 1`] = `

- I have a stock of 20 apple, costing $2.25 each. + I have a stock of 20 apples, costing $2.25 each.

@@ -52,3 +52,5 @@ exports[`Storyshots Addons|Knobs Simple 1`] = ` I am John Doe and I'm 44 years old. `; + +exports[`Storyshots Addons|Knobs XSS safety 1`] = `<img src=x onerror="alert('XSS Attack')" >`; diff --git a/examples/html-kitchen-sink/stories/addon-knobs.stories.js b/examples/html-kitchen-sink/stories/addon-knobs.stories.js index 406ba3acf184..04fd7d9c3d3a 100644 --- a/examples/html-kitchen-sink/stories/addon-knobs.stories.js +++ b/examples/html-kitchen-sink/stories/addon-knobs.stories.js @@ -11,7 +11,7 @@ import { withKnobs, text, number, -} from '@storybook/addon-knobs/html'; +} from '@storybook/addon-knobs'; storiesOf('Addons|Knobs', module) .addDecorator(withKnobs) @@ -31,11 +31,11 @@ storiesOf('Addons|Knobs', module) step: 5, }); const fruits = { - apples: 'Apple', - bananas: 'Banana', - cherries: 'Cherry', + Apple: 'apples', + Banana: 'bananas', + Cherry: 'cherries', }; - const fruit = select('Fruit', fruits, 'apple'); + const fruit = select('Fruit', fruits, 'apples'); const price = number('Price', 2.25); const colour = color('Border', 'deeppink'); @@ -63,4 +63,5 @@ storiesOf('Addons|Knobs', module)

${salutation}

`; - }); + }) + .add('XSS safety', () => text('Rendered string', '')); diff --git a/examples/marko-cli/package.json b/examples/marko-cli/package.json index 3083ee070110..056ac2e7c740 100644 --- a/examples/marko-cli/package.json +++ b/examples/marko-cli/package.json @@ -28,7 +28,7 @@ "scripts": { "start": "marko-starter server", "build-storybook": "build-storybook", - "storybook": "start-storybook -p 9010", + "storybook": "start-storybook -p 9005", "build": "NODE_ENV=production marko-starter build", "serve-static": "NODE_ENV=production marko-starter serve-static", "lint": "eslint src/", diff --git a/examples/marko-cli/src/stories/addon-actions.stories.js b/examples/marko-cli/src/stories/addon-actions.stories.js index ec3aa83f09a0..73d25b05d3f6 100644 --- a/examples/marko-cli/src/stories/addon-actions.stories.js +++ b/examples/marko-cli/src/stories/addon-actions.stories.js @@ -2,6 +2,6 @@ import { storiesOf } from '@storybook/marko'; import { action } from '@storybook/addon-actions'; import Button from '../components/action-button/index.marko'; -storiesOf('Addons|Actions/Button').add('Simple', () => +storiesOf('Addons|Actions/Button', module).add('Simple', () => Button.renderSync({ click: action('action logged!') }) ); diff --git a/examples/marko-cli/src/stories/addon-knobs.stories.js b/examples/marko-cli/src/stories/addon-knobs.stories.js index 35287848196d..74b6898dbb4c 100644 --- a/examples/marko-cli/src/stories/addon-knobs.stories.js +++ b/examples/marko-cli/src/stories/addon-knobs.stories.js @@ -1,5 +1,5 @@ import { storiesOf } from '@storybook/marko'; -import { withKnobs, text, number } from '@storybook/addon-knobs/marko'; +import { withKnobs, text, number } from '@storybook/addon-knobs'; import Hello from '../components/hello/index.marko'; storiesOf('Addons|Knobs/Hello', module) diff --git a/examples/mithril-kitchen-sink/src/stories/addon-knobs.stories.js b/examples/mithril-kitchen-sink/src/stories/addon-knobs.stories.js index d518f313e546..cd9836b357cb 100644 --- a/examples/mithril-kitchen-sink/src/stories/addon-knobs.stories.js +++ b/examples/mithril-kitchen-sink/src/stories/addon-knobs.stories.js @@ -14,7 +14,7 @@ import { color, date, button, -} from '@storybook/addon-knobs/mithril'; +} from '@storybook/addon-knobs'; storiesOf('Addons|Knobs', module) .addDecorator(withKnobs) @@ -36,11 +36,11 @@ storiesOf('Addons|Knobs', module) step: 5, }); const fruits = { - apples: 'Apple', - bananas: 'Banana', - cherries: 'Cherry', + Apple: 'apples', + Banana: 'bananas', + Cherry: 'cherries', }; - const fruit = select('Fruit', fruits, 'apple'); + const fruit = select('Fruit', fruits, 'apples'); const price = number('Price', 2.25); const colour = color('Border', 'deeppink'); @@ -63,7 +63,7 @@ storiesOf('Addons|Knobs', module)

today is {new Date(today).toLocaleDateString('en-US', dateOptions)}

{stockMessage}

Also, I have:

-
    {items.map(item => `
  • ${item}
  • `).join('')}
+
    {items.map(item =>
  • {item}
  • )}

{salutation}

), diff --git a/examples/official-storybook/stories/__snapshots__/addon-knobs.stories.storyshot b/examples/official-storybook/stories/__snapshots__/addon-knobs.stories.storyshot index 1facf226a0c3..4ac266e2867b 100644 --- a/examples/official-storybook/stories/__snapshots__/addon-knobs.stories.storyshot +++ b/examples/official-storybook/stories/__snapshots__/addon-knobs.stories.storyshot @@ -2,7 +2,7 @@ exports[`Storyshots Addons|Knobs.withKnobs XSS safety 1`] = `
- <img src=x onerror="alert('XSS Attack')" > + <img src=x onerror="alert('XSS Attack')" >
`; @@ -33,7 +33,7 @@ exports[`Storyshots Addons|Knobs.withKnobs tweaks static values 1`] = ` style="background-color:#ffff00;border:3px solid #ff00ff;padding:10px" >

- My name is Storyteller, I'm 70 years old, and my favorite fruit is apple. I also enjoy lime. + My name is Storyteller, I'm 70 years old, and my favorite fruit is apple.

My birthday is: January 20, 2017 @@ -107,9 +107,6 @@ exports[`Storyshots Addons|Knobs.withKnobs tweaks static values organized in gro

Fruit: apple

-

- Second Fruit: lime -

Items:

@@ -127,60 +124,8 @@ exports[`Storyshots Addons|Knobs.withKnobs tweaks static values organized in gro `; -exports[`Storyshots Addons|Knobs.withKnobsOptions triggers actions via button with debounce delay 1`] = ` +exports[`Storyshots Addons|Knobs.withKnobsOptions displays HTML code 1`] = `
-

- Hit the knob load button and it should trigger an async load after a short delay -

-

- No items loaded -

-
-`; - -exports[`Storyshots Addons|Knobs.withKnobsOptions tweaks static values with debounce delay 1`] = ` -
-

- If you are encountering performance issues with state updating too often, use withKnobsOptions to debounce changes. -

-

- My name is Storyteller, I'm 70 years old, and my favorite fruit is apple. I also enjoy lime. -

-

- My birthday is: January 20, 2017 -

-

- My wallet contains: $12.50 -

-

- In my backpack, I have: -

-
    -
  • - Laptop -
  • -
  • - Book -
  • -
  • - Whiskey -
  • -
-

- Nice to meet you! -

-

- When I am happy I look like this: - happy -

-
-

- PS. My shirt pocket contains: -

+ <h1>Hello</h1>
`; diff --git a/examples/official-storybook/stories/addon-knobs.stories.js b/examples/official-storybook/stories/addon-knobs.stories.js index 3406b6407205..40fa2a7865a3 100644 --- a/examples/official-storybook/stories/addon-knobs.stories.js +++ b/examples/official-storybook/stories/addon-knobs.stories.js @@ -9,13 +9,12 @@ import { boolean, color, select, - selectV2, array, date, button, object, files, -} from '@storybook/addon-knobs/react'; +} from '@storybook/addon-knobs'; class AsyncItemLoader extends React.Component { constructor() { @@ -53,17 +52,11 @@ storiesOf('Addons|Knobs.withKnobs', module) const name = text('Name', 'Storyteller'); const age = number('Age', 70, { range: true, min: 0, max: 90, step: 5 }); const fruits = { - apple: 'Apple', - banana: 'Banana', - cherry: 'Cherry', + Apple: 'apple', + Banana: 'banana', + Cherry: 'cherry', }; const fruit = select('Fruit', fruits, 'apple'); - const otherFruits = { - Lime: 'lime', - Coconut: 'coconut', - Tomato: 'tomato', - }; - const otherFruit = selectV2('Other Fruit', otherFruits, 'lime'); const dollars = number('Dollars', 12.5, { min: 0, max: 100, step: 0.01 }); const years = number('Years in NY', 9); @@ -82,7 +75,7 @@ storiesOf('Addons|Knobs.withKnobs', module) const defaultBirthday = new Date('Jan 20 2017 GMT+0'); const birthday = date('Birthday', defaultBirthday); - const intro = `My name is ${name}, I'm ${age} years old, and my favorite fruit is ${fruit}. I also enjoy ${otherFruit}.`; + const intro = `My name is ${name}, I'm ${age} years old, and my favorite fruit is ${fruit}.`; const style = { backgroundColor, ...otherStyles }; const salutation = nice ? 'Nice to meet you!' : 'Leave me alone!'; const dateOptions = { year: 'numeric', month: 'long', day: 'numeric', timeZone: 'UTC' }; @@ -112,15 +105,9 @@ storiesOf('Addons|Knobs.withKnobs', module) }; const fruits = { - apple: 'Apple', - banana: 'Banana', - cherry: 'Cherry', - }; - - const otherFruits = { - Lime: 'lime', - Coconut: 'coconut', - Tomato: 'tomato', + Apple: 'apple', + Banana: 'banana', + Cherry: 'cherry', }; // NOTE: the default value must not change - e.g., do not do date('Label', new Date()) or date('Label') @@ -141,7 +128,6 @@ storiesOf('Addons|Knobs.withKnobs', module) // Favorites const nice = boolean('Nice', true, GROUP_IDS.FAVORITES); const fruit = select('Fruit', fruits, 'apple', GROUP_IDS.FAVORITES); - const otherFruit = selectV2('Other Fruit', otherFruits, 'lime', GROUP_IDS.FAVORITES); const items = array('Items', ['Laptop', 'Book', 'Whiskey'], ',', GROUP_IDS.FAVORITES); // Display @@ -169,7 +155,6 @@ storiesOf('Addons|Knobs.withKnobs', module)

Favorites

Catchphrase: {salutation}

Fruit: {fruit}

-

Second Fruit: {otherFruit}

Items:

    {items.map(item =>
  • {item}
  • )}
@@ -197,70 +182,7 @@ storiesOf('Addons|Knobs.withKnobs', module) storiesOf('Addons|Knobs.withKnobsOptions', module) .addDecorator( withKnobsOptions({ - debounce: { wait: 100, leading: boolean }, // Same as lodash debounce. - timestamps: true, // Doesn't emit events while user is typing. + escapeHTML: false, }) ) - .add('tweaks static values with debounce delay', () => { - const name = text('Name', 'Storyteller'); - const age = number('Age', 70, { range: true, min: 0, max: 90, step: 5 }); - const fruits = { - apple: 'Apple', - banana: 'Banana', - cherry: 'Cherry', - }; - const fruit = select('Fruit', fruits, 'apple'); - const otherFruits = { - Lime: 'lime', - Coconut: 'coconut', - Tomato: 'tomato', - }; - const otherFruit = selectV2('Other Fruit', otherFruits, 'lime'); - const dollars = number('Dollars', 12.5, { min: 0, max: 100, step: 0.01 }); - - const backgroundColor = color('background', '#ffff00'); - const items = array('Items', ['Laptop', 'Book', 'Whiskey']); - const otherStyles = object('Styles', { - border: '3px solid #ff00ff', - padding: '10px', - }); - const nice = boolean('Nice', true); - const images = files('Happy Picture', 'image/*', [ - 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QA/4ePzL8AAAAHdElNRQfiARwMCyEWcOFPAAAAP0lEQVQoz8WQMQoAIAwDL/7/z3GwghSp4KDZyiUpBMCYUgd8rehtH16/l3XewgU2KAzapjXBbNFaPS6lDMlKB6OiDv3iAH1OAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE4LTAxLTI4VDEyOjExOjMzLTA3OjAwlAHQBgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxOC0wMS0yOFQxMjoxMTozMy0wNzowMOVcaLoAAAAASUVORK5CYII=', - ]); - - // NOTE: the default value must not change - e.g., do not do date('Label', new Date()) or date('Label') - const defaultBirthday = new Date('Jan 20 2017 GMT+0'); - const birthday = date('Birthday', defaultBirthday); - - const intro = `My name is ${name}, I'm ${age} years old, and my favorite fruit is ${fruit}. I also enjoy ${otherFruit}.`; - const style = { backgroundColor, ...otherStyles }; - const salutation = nice ? 'Nice to meet you!' : 'Leave me alone!'; - const dateOptions = { year: 'numeric', month: 'long', day: 'numeric', timeZone: 'UTC' }; - - return ( -
-

- If you are encountering performance issues with state updating too often, use - withKnobsOptions to debounce changes. -

-

{intro}

-

My birthday is: {new Date(birthday).toLocaleDateString('en-US', dateOptions)}

-

My wallet contains: ${dollars.toFixed(2)}

-

In my backpack, I have:

-
    {items.map(item =>
  • {item}
  • )}
-

{salutation}

-

- When I am happy I look like this: happy -

-
-

PS. My shirt pocket contains:

-
- ); - }) - .add('triggers actions via button with debounce delay', () => ( -
-

Hit the knob load button and it should trigger an async load after a short delay

- -
- )); + .add('displays HTML code', () =>
{text('Rendered string', '

Hello

')}
); diff --git a/examples/polymer-cli/src/stories/addon-knobs.stories.js b/examples/polymer-cli/src/stories/addon-knobs.stories.js index b1bfd8dff47a..8015a235ff64 100644 --- a/examples/polymer-cli/src/stories/addon-knobs.stories.js +++ b/examples/polymer-cli/src/stories/addon-knobs.stories.js @@ -13,7 +13,7 @@ import { color, array, boolean, -} from '@storybook/addon-knobs/polymer'; +} from '@storybook/addon-knobs'; storiesOf('Addon|Knobs', module) .addDecorator(withKnobs) @@ -31,8 +31,12 @@ storiesOf('Addon|Knobs', module) .add('complex', () => { const name = text('Name', 'Jane'); const stock = number('Stock', 20, { range: true, min: 0, max: 30, step: 5 }); - const fruits = { apples: 'Apple', bananas: 'Banana', cherries: 'Cherry' }; - const fruit = select('Fruit', fruits, 'apple'); + const fruits = { + Apple: 'apples', + Banana: 'bananas', + Cherry: 'cherries', + }; + const fruit = select('Fruit', fruits, 'apples'); const price = number('Price', 2.25); const colour = color('Border', 'deeppink'); const today = date('Today', new Date('Jan 20 2017 GMT+0')); diff --git a/examples/vue-kitchen-sink/src/stories/__snapshots__/addon-knobs.stories.storyshot b/examples/vue-kitchen-sink/src/stories/__snapshots__/addon-knobs.stories.storyshot index f54e6f19d0e5..13bf0ff02a61 100644 --- a/examples/vue-kitchen-sink/src/stories/__snapshots__/addon-knobs.stories.storyshot +++ b/examples/vue-kitchen-sink/src/stories/__snapshots__/addon-knobs.stories.storyshot @@ -13,7 +13,7 @@ exports[`Storyshots Addon|Knobs All knobs 1`] = `

- I have a stock of 20 apple, costing $2.25 each. + I have a stock of 20 apples, costing $2.25 each.

diff --git a/examples/vue-kitchen-sink/src/stories/addon-knobs.stories.js b/examples/vue-kitchen-sink/src/stories/addon-knobs.stories.js index 26015c311b76..cf8a0a7d74eb 100644 --- a/examples/vue-kitchen-sink/src/stories/addon-knobs.stories.js +++ b/examples/vue-kitchen-sink/src/stories/addon-knobs.stories.js @@ -10,7 +10,7 @@ import { color, date, button, -} from '@storybook/addon-knobs/vue'; +} from '@storybook/addon-knobs'; storiesOf('Addon|Knobs', module) .addDecorator(withKnobs) @@ -32,11 +32,11 @@ storiesOf('Addon|Knobs', module) step: 5, }); const fruits = { - apples: 'Apple', - bananas: 'Banana', - cherries: 'Cherry', + Apple: 'apples', + Banana: 'bananas', + Cherry: 'cherries', }; - const fruit = select('Fruit', fruits, 'apple'); + const fruit = select('Fruit', fruits, 'apples'); const price = number('Price', 2.25); const colour = color('Border', 'deeppink'); diff --git a/lib/core/src/client/preview/start.js b/lib/core/src/client/preview/start.js index cf34147fdb1c..b435f84b29ea 100644 --- a/lib/core/src/client/preview/start.js +++ b/lib/core/src/client/preview/start.js @@ -153,6 +153,7 @@ export default function start(render, { decorateStory } = {}) { story, selectedKind, selectedStory, + forceRender, }); }; From f7795984c12bfa0c08237749fcc785177f5f4f87 Mon Sep 17 00:00:00 2001 From: Hypnosphi Date: Sat, 12 May 2018 03:28:39 +0300 Subject: [PATCH 08/17] Support supscriptions & force render in RN --- .../src/preview/components/StoryView/index.js | 3 +++ app/react-native/src/preview/index.js | 5 +++++ .../storybook/stories/Knobs/index.js | 6 +++--- lib/channel-postmessage/src/index.js | 1 - lib/channels/src/index.js | 11 ++++++----- lib/core/src/client/preview/client_api.js | 19 ++++++++++++++++++- lib/core/src/client/preview/start.js | 4 ---- 7 files changed, 35 insertions(+), 14 deletions(-) diff --git a/app/react-native/src/preview/components/StoryView/index.js b/app/react-native/src/preview/components/StoryView/index.js index dc056e14adbd..44abf486f288 100644 --- a/app/react-native/src/preview/components/StoryView/index.js +++ b/app/react-native/src/preview/components/StoryView/index.js @@ -10,12 +10,15 @@ export default class StoryView extends Component { this.state = { storyFn: null, selection: {} }; this.storyHandler = this.selectStory.bind(this); + this.forceRender = this.forceUpdate.bind(this); this.props.events.on(Events.SELECT_STORY, this.storyHandler); + this.props.events.on(Events.FORCE_RE_RENDER, this.forceRender); } componentWillUnmount() { this.props.events.removeListener(Events.SELECT_STORY, this.storyHandler); + this.props.events.removeListener(Events.FORCE_RE_RENDER, this.forceRender); } selectStory(storyFn, selection) { diff --git a/app/react-native/src/preview/index.js b/app/react-native/src/preview/index.js index 6e7a58d80e7b..eaf857f9372a 100644 --- a/app/react-native/src/preview/index.js +++ b/app/react-native/src/preview/index.js @@ -69,6 +69,7 @@ export default class Preview { } channel.on(Events.GET_STORIES, () => this._sendSetStories()); channel.on(Events.SET_CURRENT_STORY, d => this._selectStory(d)); + channel.on(Events.FORCE_RE_RENDER, () => this._forceRender()); this._events.on(Events.SET_CURRENT_STORY, d => this._selectStory(d)); this._sendSetStories(); this._sendGetCurrentStory(); @@ -98,4 +99,8 @@ export default class Preview { const storyFn = this._stories.getStoryWithContext(kind, story); this._events.emit(Events.SELECT_STORY, storyFn, selection); } + + _forceRender() { + this._events.emit(Events.FORCE_RE_RENDER); + } } diff --git a/examples/crna-kitchen-sink/storybook/stories/Knobs/index.js b/examples/crna-kitchen-sink/storybook/stories/Knobs/index.js index 776fb1eb5729..7ca828cadd8d 100644 --- a/examples/crna-kitchen-sink/storybook/stories/Knobs/index.js +++ b/examples/crna-kitchen-sink/storybook/stories/Knobs/index.js @@ -16,9 +16,9 @@ export default () => { const name = text('Name', 'Storyteller'); const age = number('Age', 70, { range: true, min: 0, max: 90, step: 5 }); const fruits = { - apple: 'Apple', - banana: 'Banana', - cherry: 'Cherry', + Apple: 'apple', + Banana: 'banana', + Cherry: 'cherry', }; const fruit = select('Fruit', fruits, 'apple'); const dollars = number('Dollars', 12.5); diff --git a/lib/channel-postmessage/src/index.js b/lib/channel-postmessage/src/index.js index e8497bd90133..2395164de92b 100644 --- a/lib/channel-postmessage/src/index.js +++ b/lib/channel-postmessage/src/index.js @@ -32,7 +32,6 @@ export class PostmsgTransport { } const data = stringify({ key: KEY, event }); iframeWindow.postMessage(data, '*'); - this._handler(event); return Promise.resolve(null); } diff --git a/lib/channels/src/index.js b/lib/channels/src/index.js index ca07dc95898f..bae6bd48854a 100644 --- a/lib/channels/src/index.js +++ b/lib/channels/src/index.js @@ -4,7 +4,7 @@ export default class Channel { constructor({ transport }) { this._sender = this._randomId(); this._transport = transport; - this._transport.setHandler(this._handleEvent.bind(this)); + this._transport.setHandler(event => this._handleEvent(event)); this._listeners = {}; } @@ -14,13 +14,14 @@ export default class Channel { addPeerListener(type, listener) { const peerListener = listener; - peerListener.isPeer = from => from === this._sender; + peerListener.ignorePeer = true; this.on(type, peerListener); } emit(type, ...args) { const event = { type, args, from: this._sender }; this._transport.send(event); + this._handleEvent(event, true); } eventNames() { @@ -78,10 +79,10 @@ export default class Channel { .slice(2); } - _handleEvent(event) { + _handleEvent(event, isPeer) { const listeners = this._listeners[event.type]; - if (listeners) { - listeners.forEach(fn => !(fn.isPeer && fn.isPeer(event.from)) && fn(...event.args)); + if (listeners && (isPeer || event.from !== this._sender)) { + listeners.forEach(fn => !(isPeer && fn.ignorePeer) && fn(...event.args)); } } diff --git a/lib/core/src/client/preview/client_api.js b/lib/core/src/client/preview/client_api.js index c63cbfd4e0af..4c0a75998ed0 100644 --- a/lib/core/src/client/preview/client_api.js +++ b/lib/core/src/client/preview/client_api.js @@ -1,8 +1,11 @@ /* eslint no-underscore-dangle: 0 */ import { logger } from '@storybook/client-logger'; +import addons from '@storybook/addons'; +import Events from '@storybook/core-events'; import StoryStore from './story_store'; +import subscriptionsStore from './subscriptions_store'; const defaultDecorateStory = (getStory, decorators) => decorators.reduce( @@ -10,6 +13,20 @@ const defaultDecorateStory = (getStory, decorators) => getStory ); +const metaSubscription = () => { + addons.getChannel().on(Events.REGISTER_SUBSCRIPTION, subscriptionsStore.register); + return () => + addons.getChannel().removeListener(Events.REGISTER_SUBSCRIPTION, subscriptionsStore.register); +}; + +const withSubscriptionTracking = storyFn => { + subscriptionsStore.markAllAsUnused(); + subscriptionsStore.register(metaSubscription); + const result = storyFn(); + subscriptionsStore.clearUnused(); + return result; +}; + export default class ClientApi { constructor({ storyStore = new StoryStore(), decorateStory = defaultDecorateStory } = {}) { this._storyStore = storyStore; @@ -83,7 +100,7 @@ export default class ClientApi { // 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 decorators = [...localDecorators, ...this._globalDecorators, withSubscriptionTracking]; const fileName = m ? m.filename : null; diff --git a/lib/core/src/client/preview/start.js b/lib/core/src/client/preview/start.js index b435f84b29ea..eadc2f00e7a9 100644 --- a/lib/core/src/client/preview/start.js +++ b/lib/core/src/client/preview/start.js @@ -12,7 +12,6 @@ import ConfigApi from './config_api'; import reducer from './reducer'; import * as Actions from './actions'; import syncUrlWithStore from './syncUrlWithStore'; -import subscriptionsStore from './subscriptions_store'; const classes = { MAIN: 'sb-show-main', @@ -87,7 +86,6 @@ export default function start(render, { decorateStory } = {}) { channel.on(Events.SET_CURRENT_STORY, data => { reduxStore.dispatch(Actions.selectStory(data.kind, data.story)); }); - channel.on(Events.REGISTER_SUBSCRIPTION, subscriptionsStore.register); addons.setChannel(channel); Object.assign(context, { channel }); @@ -147,7 +145,6 @@ export default function start(render, { decorateStory } = {}) { previousKind = selectedKind; previousStory = selectedStory; - subscriptionsStore.markAllAsUnused(); render({ ...context, story, @@ -167,7 +164,6 @@ export default function start(render, { decorateStory } = {}) { } try { renderMain(forceRender); - subscriptionsStore.clearUnused(); addons.getChannel().emit(Events.STORY_RENDERED); } catch (ex) { showException(ex); From b94155ecd4a05793cb5a1bf4d302f90aba31941b Mon Sep 17 00:00:00 2001 From: Hypnosphi Date: Sat, 12 May 2018 18:17:00 +0300 Subject: [PATCH 09/17] Fix tests --- addons/storyshots/src/index.js | 5 +++- .../addon-centered.stories.storyshot | 18 +++++++---- .../custom-decorators.stories.storyshot | 30 ++++++++++++------- lib/addons/src/index.js | 4 +++ lib/channels/src/index.test.js | 11 ++++++- lib/core/src/client/preview/client_api.js | 1 + 6 files changed, 51 insertions(+), 18 deletions(-) diff --git a/addons/storyshots/src/index.js b/addons/storyshots/src/index.js index 6d04b800d26a..1abbf1309241 100644 --- a/addons/storyshots/src/index.js +++ b/addons/storyshots/src/index.js @@ -1,6 +1,6 @@ import fs from 'fs'; import glob from 'glob'; -import global, { describe, it } from 'global'; +import global, { document, describe, it } from 'global'; import addons, { mockChannel } from '@storybook/addons'; import loadFramework from './frameworkLoader'; import getIntegrityOptions from './getIntegrityOptions'; @@ -35,6 +35,9 @@ export default function testStorySnapshots(options = {}) { } addons.setChannel(mockChannel()); + const rootEl = document.createElement('div'); + rootEl.setAttribute('id', 'root'); + document.body.appendChild(rootEl); const { storybook, framework, renderTree, renderShallowTree } = loadFramework(options); const stories = storybook.getStorybook(); diff --git a/examples/vue-kitchen-sink/src/stories/__snapshots__/addon-centered.stories.storyshot b/examples/vue-kitchen-sink/src/stories/__snapshots__/addon-centered.stories.storyshot index 4838600b62f3..47ac3c0b5436 100644 --- a/examples/vue-kitchen-sink/src/stories/__snapshots__/addon-centered.stories.storyshot +++ b/examples/vue-kitchen-sink/src/stories/__snapshots__/addon-centered.stories.storyshot @@ -7,12 +7,20 @@ exports[`Storyshots Addon|Centered rounded 1`] = `

- +
+ +
+
`; diff --git a/examples/vue-kitchen-sink/src/stories/__snapshots__/custom-decorators.stories.storyshot b/examples/vue-kitchen-sink/src/stories/__snapshots__/custom-decorators.stories.storyshot index eb88673228af..d0e4a58cc4f5 100644 --- a/examples/vue-kitchen-sink/src/stories/__snapshots__/custom-decorators.stories.storyshot +++ b/examples/vue-kitchen-sink/src/stories/__snapshots__/custom-decorators.stories.storyshot @@ -5,13 +5,17 @@ exports[`Storyshots Custom|Decorator for Vue render 1`] = ` style="border: medium solid blue;" >
- + +
`; @@ -21,14 +25,18 @@ exports[`Storyshots Custom|Decorator for Vue template 1`] = ` style="border: medium solid blue;" >
- + +
`; diff --git a/lib/addons/src/index.js b/lib/addons/src/index.js index 161a58ed0067..883633ee9fbc 100644 --- a/lib/addons/src/index.js +++ b/lib/addons/src/index.js @@ -22,6 +22,10 @@ export class AddonStore { return this.channel; } + hasChannel() { + return Boolean(this.channel); + } + setChannel(channel) { this.channel = channel; } diff --git a/lib/channels/src/index.test.js b/lib/channels/src/index.test.js index 722a26e01396..7497f7e9cba7 100644 --- a/lib/channels/src/index.test.js +++ b/lib/channels/src/index.test.js @@ -195,11 +195,19 @@ describe('Channel', () => { }); describe('_miscellaneous', () => { - it('should not ignore if event came from itself', () => { + it('should ignore if event came from own sender', () => { const received = []; channel.on('type-1', n => received.push(n)); channel._handleEvent({ type: 'type-1', args: [11] }); channel._handleEvent({ type: 'type-1', args: [12], from: channel._sender }); + expect(received).toEqual([11]); + }); + + it('should not ignore peer event', () => { + const received = []; + channel.on('type-1', n => received.push(n)); + channel._handleEvent({ type: 'type-1', args: [11] }); + channel._handleEvent({ type: 'type-1', args: [12] }, true); expect(received).toEqual([11, 12]); }); @@ -208,6 +216,7 @@ describe('Channel', () => { channel.addPeerListener('type-1', n => received.push(n)); channel._handleEvent({ type: 'type-1', args: [11], from: channel._sender }); channel._handleEvent({ type: 'type-1', args: [12], from: '_' }); + channel._handleEvent({ type: 'type-1', args: [13] }, true); expect(received).toEqual([12]); }); }); diff --git a/lib/core/src/client/preview/client_api.js b/lib/core/src/client/preview/client_api.js index 4c0a75998ed0..3e8337d3be41 100644 --- a/lib/core/src/client/preview/client_api.js +++ b/lib/core/src/client/preview/client_api.js @@ -20,6 +20,7 @@ const metaSubscription = () => { }; const withSubscriptionTracking = storyFn => { + if (!addons.hasChannel()) return storyFn(); subscriptionsStore.markAllAsUnused(); subscriptionsStore.register(metaSubscription); const result = storyFn(); From 2889076bf24872da50bd2563d9a819bc1ddfa846 Mon Sep 17 00:00:00 2001 From: Hypnosphi Date: Sat, 12 May 2018 20:16:21 +0300 Subject: [PATCH 10/17] RN: Sync selected story between manager and device --- addons/knobs/src/registerKnobs.js | 3 +-- app/react-native/src/manager/provider.js | 3 +++ app/react-native/src/preview/index.js | 8 ++++++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/addons/knobs/src/registerKnobs.js b/addons/knobs/src/registerKnobs.js index bffd55bb0514..ab16295cc5cd 100644 --- a/addons/knobs/src/registerKnobs.js +++ b/addons/knobs/src/registerKnobs.js @@ -36,8 +36,7 @@ function resetKnobs() { forceReRender(); - const channel = addons.getChannel(); - setPaneKnobs(channel, knobStore, false); + setPaneKnobs(false); } function disconnectCallbacks() { diff --git a/app/react-native/src/manager/provider.js b/app/react-native/src/manager/provider.js index 510b5635b172..820fb3bc9439 100644 --- a/app/react-native/src/manager/provider.js +++ b/app/react-native/src/manager/provider.js @@ -56,6 +56,9 @@ export default class ReactProvider extends Provider { this.selection = { kind, story }; this.channel.emit(Events.SET_CURRENT_STORY, this.selection); }); + this.channel.on(Events.SELECT_STORY, ({ kind, story }) => { + api.selectStory(kind, story); + }); this.channel.on(Events.SET_STORIES, data => { api.setStories(data.stories); }); diff --git a/app/react-native/src/preview/index.js b/app/react-native/src/preview/index.js index eaf857f9372a..3947b60cbfc1 100644 --- a/app/react-native/src/preview/index.js +++ b/app/react-native/src/preview/index.js @@ -70,7 +70,7 @@ export default class Preview { channel.on(Events.GET_STORIES, () => this._sendSetStories()); channel.on(Events.SET_CURRENT_STORY, d => this._selectStory(d)); channel.on(Events.FORCE_RE_RENDER, () => this._forceRender()); - this._events.on(Events.SET_CURRENT_STORY, d => this._selectStory(d)); + this._events.on(Events.SET_CURRENT_STORY, d => this._selectStory(d, true)); this._sendSetStories(); this._sendGetCurrentStory(); @@ -94,9 +94,13 @@ export default class Preview { channel.emit(Events.GET_CURRENT_STORY); } - _selectStory(selection) { + _selectStory(selection, fromPreview) { const { kind, story } = selection; const storyFn = this._stories.getStoryWithContext(kind, story); + if (fromPreview) { + const channel = addons.getChannel(); + channel.emit(Events.SELECT_STORY, selection); + } this._events.emit(Events.SELECT_STORY, storyFn, selection); } From 56f30e75f23ac8b193beecdb6615c6a487986b60 Mon Sep 17 00:00:00 2001 From: Hypnosphi Date: Sat, 12 May 2018 20:31:07 +0300 Subject: [PATCH 11/17] RN: Sync selected story between manager and device --- addons/actions/src/preview/withActions.js | 4 +++- addons/storyshots/src/index.js | 5 +---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/addons/actions/src/preview/withActions.js b/addons/actions/src/preview/withActions.js index 35b78f48d62c..52dd7095ccba 100644 --- a/addons/actions/src/preview/withActions.js +++ b/addons/actions/src/preview/withActions.js @@ -57,7 +57,9 @@ const actionsSubscription = (...args) => { }; export const createDecorator = actionsFn => (...args) => story => { - addons.getChannel().emit(Events.REGISTER_SUBSCRIPTION, actionsSubscription(actionsFn, ...args)); + if (root != null) { + addons.getChannel().emit(Events.REGISTER_SUBSCRIPTION, actionsSubscription(actionsFn, ...args)); + } return story(); }; diff --git a/addons/storyshots/src/index.js b/addons/storyshots/src/index.js index 1abbf1309241..6d04b800d26a 100644 --- a/addons/storyshots/src/index.js +++ b/addons/storyshots/src/index.js @@ -1,6 +1,6 @@ import fs from 'fs'; import glob from 'glob'; -import global, { document, describe, it } from 'global'; +import global, { describe, it } from 'global'; import addons, { mockChannel } from '@storybook/addons'; import loadFramework from './frameworkLoader'; import getIntegrityOptions from './getIntegrityOptions'; @@ -35,9 +35,6 @@ export default function testStorySnapshots(options = {}) { } addons.setChannel(mockChannel()); - const rootEl = document.createElement('div'); - rootEl.setAttribute('id', 'root'); - document.body.appendChild(rootEl); const { storybook, framework, renderTree, renderShallowTree } = loadFramework(options); const stories = storybook.getStorybook(); From 24fe256e29116d7c2b96e0123c445ff4a396c992 Mon Sep 17 00:00:00 2001 From: Hypnosphi Date: Sat, 12 May 2018 20:53:41 +0300 Subject: [PATCH 12/17] Backgrounds: add overriding example --- addons/backgrounds/src/BackgroundPanel.js | 2 +- .../__snapshots__/addon-backgrounds.stories.storyshot | 6 ++++++ .../stories/addon-backgrounds.stories.js | 8 +++++++- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/addons/backgrounds/src/BackgroundPanel.js b/addons/backgrounds/src/BackgroundPanel.js index 14dbd9fa1539..96bbfbbf4a4f 100644 --- a/addons/backgrounds/src/BackgroundPanel.js +++ b/addons/backgrounds/src/BackgroundPanel.js @@ -93,7 +93,7 @@ export default class BackgroundPanel extends Component { this.setState({ backgrounds }); const currentBackground = api.getQueryParam('background'); - if (currentBackground) { + if (currentBackground && backgrounds.some(bg => bg.value === currentBackground)) { this.updateIframe(currentBackground); } else if (backgrounds.filter(x => x.default).length) { const defaultBgs = backgrounds.filter(x => x.default); diff --git a/examples/official-storybook/stories/__snapshots__/addon-backgrounds.stories.storyshot b/examples/official-storybook/stories/__snapshots__/addon-backgrounds.stories.storyshot index 59ddd8f533da..856f3abbf94c 100644 --- a/examples/official-storybook/stories/__snapshots__/addon-backgrounds.stories.storyshot +++ b/examples/official-storybook/stories/__snapshots__/addon-backgrounds.stories.storyshot @@ -1,5 +1,11 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`Storyshots Addons|Backgrounds overriden 1`] = ` + +`; + exports[`Storyshots Addons|Backgrounds story 1 1`] = ` @@ -33,13 +33,13 @@ exports[`Storyshots Custom|Style With Knobs 1`] = ` >