diff --git a/addons/knobs/README.md b/addons/knobs/README.md index 2da303546409..123dfbe59424 100644 --- a/addons/knobs/README.md +++ b/addons/knobs/README.md @@ -1,17 +1,20 @@ # Storybook Addon Knobs +Storybook Addon Knobs allow you to edit React props dynamically using the Storybook UI. +You can also use Knobs as a dynamic variable inside stories in [Storybook](https://storybook.js.org). + [![Greenkeeper badge](https://badges.greenkeeper.io/storybooks/storybook.svg)](https://greenkeeper.io/) -[![Build Status](https://travis-ci.org/storybooks/storybook.svg?branch=master)](https://travis-ci.org/storybooks/storybook) +[![Build Status on CircleCI](https://circleci.com/gh/storybooks/storybook.svg?style=shield)](https://circleci.com/gh/storybooks/storybook) [![CodeFactor](https://www.codefactor.io/repository/github/storybooks/storybook/badge)](https://www.codefactor.io/repository/github/storybooks/storybook) [![Known Vulnerabilities](https://snyk.io/test/github/storybooks/storybook/8f36abfd6697e58cd76df3526b52e4b9dc894847/badge.svg)](https://snyk.io/test/github/storybooks/storybook/8f36abfd6697e58cd76df3526b52e4b9dc894847) -[![BCH compliance](https://bettercodehub.com/edge/badge/storybooks/storybook)](https://bettercodehub.com/results/storybooks/storybook) [![codecov](https://codecov.io/gh/storybooks/storybook/branch/master/graph/badge.svg)](https://codecov.io/gh/storybooks/storybook) -[![Storybook Slack](https://storybooks-slackin.herokuapp.com/badge.svg)](https://storybooks-slackin.herokuapp.com/) - -Storybook Addon Knobs allow you to edit React props dynamically using the Storybook UI. -You can also use Knobs as a dynamic variable inside stories in [Storybook](https://storybook.js.org). +[![BCH compliance](https://bettercodehub.com/edge/badge/storybooks/storybook)](https://bettercodehub.com/results/storybooks/storybook) [![codecov](https://codecov.io/gh/storybooks/storybook/branch/master/graph/badge.svg)](https://codecov.io/gh/storybooks/storybook) +[![Storybook Slack](https://now-examples-slackin-nqnzoygycp.now.sh/badge.svg)](https://now-examples-slackin-nqnzoygycp.now.sh/) +[![Backers on Open Collective](https://opencollective.com/storybook/backers/badge.svg)](#backers) [![Sponsors on Open Collective](https://opencollective.com/storybook/sponsors/badge.svg)](#sponsors) This addon works with Storybook for: [React](https://github.com/storybooks/storybook/tree/master/app/react). +[React Native](https://github.com/storybooks/storybook/tree/master/app/react-native). +[Vue](https://github.com/storybooks/storybook/tree/master/app/vue). This is how Knobs look like: @@ -37,7 +40,7 @@ Now, write your stories with knobs. ```js import { storiesOf } from '@storybook/react'; -import { withKnobs, text, boolean, number } from '@storybook/addon-knobs'; +import { withKnobs, text, boolean, number } from '@storybook/addon-knobs/react'; const stories = storiesOf('Storybook Knobs', module); @@ -50,7 +53,7 @@ stories.add('with a button', () => ( -)) +)); // Knobs as dynamic variables. stories.add('as dynamic variables', () => { @@ -62,6 +65,20 @@ 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'; +> ``` + You can see your Knobs in a Storybook panel as shown below. ![](docs/demo.png) @@ -89,7 +106,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'; +import { text } from '@storybook/addon-knobs/react'; const label = 'Your Name'; const defaultValue = 'Arunoda Susiripala'; @@ -102,7 +119,7 @@ const value = text(label, defaultValue); Allows you to get a boolean value from the user. ```js -import { boolean } from '@storybook/addon-knobs'; +import { boolean } from '@storybook/addon-knobs/react'; const label = 'Agree?'; const defaultValue = false; @@ -115,7 +132,7 @@ const value = boolean(label, defaultValue); Allows you to get a number from the user. ```js -import { number } from '@storybook/addon-knobs'; +import { number } from '@storybook/addon-knobs/react'; const label = 'Age'; const defaultValue = 78; @@ -128,7 +145,7 @@ const value = number(label, defaultValue); Allows you to get a number from the user using a range slider. ```js -import { number } from '@storybook/addon-knobs'; +import { number } from '@storybook/addon-knobs/react'; const label = 'Temperature'; const defaultValue = 73; @@ -147,7 +164,7 @@ const value = number(label, defaultValue, options); Allows you to get a colour from the user. ```js -import { color } from '@storybook/addon-knobs'; +import { color } from '@storybook/addon-knobs/react'; const label = 'Color'; const defaultValue = '#ff00ff'; @@ -160,7 +177,7 @@ const value = color(label, defaultValue); Allows you to get a JSON object or array from the user. ```js -import { object } from '@storybook/addon-knobs'; +import { object } from '@storybook/addon-knobs/react'; const label = 'Styles'; const defaultValue = { @@ -177,7 +194,7 @@ const value = object(label, defaultValue); Allows you to get an array of strings from the user. ```js -import { array } from '@storybook/addon-knobs'; +import { array } from '@storybook/addon-knobs/react'; const label = 'Styles'; const defaultValue = ['Red'] @@ -189,7 +206,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'; +> import { array } from '@storybook/addon-knobs/react'; > > const label = 'Styles'; > const defaultValue = ['Red']; @@ -202,7 +219,7 @@ const value = array(label, defaultValue); Allows you to get a value from a select box from the user. ```js -import { select } from '@storybook/addon-knobs'; +import { select } from '@storybook/addon-knobs/react'; const label = 'Colors'; const options = { @@ -222,7 +239,7 @@ const value = select(label, options, defaultValue); Allow you to get date (and time) from the user. ```js -import { date } from '@storybook/addon-knobs'; +import { date } from '@storybook/addon-knobs/react'; const label = 'Event Date'; const defaultValue = new Date('Jan 20 2017'); @@ -237,7 +254,11 @@ If you feel like this addon is not performing well enough there is an option to Usage: ```js -story.addDecorator(withKnobsOptions({ +import { storiesOf } from '@storybook/react'; + +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. })); diff --git a/addons/knobs/react.js b/addons/knobs/react.js new file mode 100644 index 000000000000..70e1111ae070 --- /dev/null +++ b/addons/knobs/react.js @@ -0,0 +1 @@ +module.exports = require('./dist/react'); diff --git a/addons/knobs/src/base.js b/addons/knobs/src/base.js new file mode 100644 index 000000000000..8441c2f20fc9 --- /dev/null +++ b/addons/knobs/src/base.js @@ -0,0 +1,55 @@ +import KnobManager from './KnobManager'; + +export const manager = new KnobManager(); + +export function knob(name, options) { + return manager.knob(name, options); +} + +export function text(name, value) { + return manager.knob(name, { type: 'text', value }); +} + +export function boolean(name, value) { + return manager.knob(name, { type: 'boolean', value }); +} + +export function number(name, value, options = {}) { + const defaults = { + range: false, + min: 0, + max: 10, + step: 1, + }; + + const mergedOptions = { ...defaults, ...options }; + + const finalOptions = { + ...mergedOptions, + type: 'number', + value, + }; + + return manager.knob(name, finalOptions); +} + +export function color(name, value) { + return manager.knob(name, { type: 'color', value }); +} + +export function object(name, value) { + return manager.knob(name, { type: 'object', value }); +} + +export function select(name, options, value) { + return manager.knob(name, { type: 'select', options, value }); +} + +export function array(name, value, separator = ',') { + return manager.knob(name, { type: 'array', value, separator }); +} + +export function date(name, value = new Date()) { + const proxyValue = value ? value.getTime() : null; + return manager.knob(name, { type: 'date', value: proxyValue }); +} diff --git a/addons/knobs/src/index.js b/addons/knobs/src/index.js index 2f4c16dcddca..2681f3860fb2 100644 --- a/addons/knobs/src/index.js +++ b/addons/knobs/src/index.js @@ -1,66 +1,21 @@ import { window } from 'global'; +import deprecate from 'util-deprecate'; import addons from '@storybook/addons'; -import KnobManager from './KnobManager'; + import { vueHandler } from './vue'; import { reactHandler } from './react'; -const manager = new KnobManager(); - -export function knob(name, options) { - return manager.knob(name, options); -} - -export function text(name, value) { - return manager.knob(name, { type: 'text', value }); -} - -export function boolean(name, value) { - return manager.knob(name, { type: 'boolean', value }); -} - -export function number(name, value, options = {}) { - const defaults = { - range: false, - min: 0, - max: 10, - step: 1, - }; - - const mergedOptions = { ...defaults, ...options }; - - const finalOptions = { - ...mergedOptions, - type: 'number', - value, - }; +import { knob, text, boolean, number, color, object, array, date, manager } from './base'; - return manager.knob(name, finalOptions); -} - -export function color(name, value) { - return manager.knob(name, { type: 'color', value }); -} - -export function object(name, value) { - return manager.knob(name, { type: 'object', value }); -} +export { knob, text, boolean, number, color, object, array, date }; -export function select(name, options, value) { - return manager.knob(name, { type: 'select', options, value }); -} - -export function array(name, value, separator = ',') { - return manager.knob(name, { type: 'array', value, separator }); -} - -export function date(name, value = new Date()) { - const proxyValue = value ? value.getTime() : null; - return manager.knob(name, { type: 'date', value: proxyValue }); -} +deprecate( + () => {}, + 'Using @storybook/addon-knobs directly is discouraged, please use @storybook/addon-knobs/{{framework}}' +); -// "Higher order component" / wrapper style API -// In 3.3, this will become `withKnobs`, once our decorator API supports it. -// See https://github.com/storybooks/storybook/pull/1527 +// 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); diff --git a/addons/knobs/src/react/index.js b/addons/knobs/src/react/index.js index 0b093d446ddc..e1c1b5d736d5 100644 --- a/addons/knobs/src/react/index.js +++ b/addons/knobs/src/react/index.js @@ -1,11 +1,31 @@ import React from 'react'; +import addons from '@storybook/addons'; + import WrapStory from './WrapStory'; -/** - * Handles a react story - */ +import { knob, text, boolean, number, color, object, array, date, select, manager } from '../base'; + +export { knob, text, boolean, number, color, object, array, date, select }; + export const reactHandler = (channel, knobStore) => getStory => context => { const initialContent = getStory(context); const props = { context, storyFn: getStory, channel, knobStore, initialContent }; return ; }; + +function wrapperKnobs(options) { + const channel = addons.getChannel(); + manager.setChannel(channel); + + if (options) channel.emit('addon:knobs:setOptions', options); + + return reactHandler(channel, manager.knobStore); +} + +export function withKnobs(storyFn, context) { + return wrapperKnobs()(storyFn)(context); +} + +export function withKnobsOptions(options = {}) { + return (storyFn, context) => wrapperKnobs(options)(storyFn)(context); +} diff --git a/addons/knobs/src/vue/index.js b/addons/knobs/src/vue/index.js index ae99e44f1c1a..9cc2590c7fc5 100644 --- a/addons/knobs/src/vue/index.js +++ b/addons/knobs/src/vue/index.js @@ -1,3 +1,9 @@ +import addons from '@storybook/addons'; + +import { knob, text, boolean, number, color, object, array, date, select, manager } from '../base'; + +export { knob, text, boolean, number, color, object, array, date, select }; + export const vueHandler = (channel, knobStore) => getStory => context => ({ render(h) { return h(getStory(context)); @@ -35,3 +41,20 @@ export const vueHandler = (channel, knobStore) => getStory => context => ({ knobStore.unsubscribe(this.setPaneKnobs); }, }); + +function wrapperKnobs(options) { + const channel = addons.getChannel(); + manager.setChannel(channel); + + if (options) channel.emit('addon:knobs:setOptions', options); + + return vueHandler(channel, manager.knobStore); +} + +export function withKnobs(storyFn, context) { + return wrapperKnobs()(storyFn)(context); +} + +export function withKnobsOptions(options = {}) { + return (storyFn, context) => wrapperKnobs(options)(storyFn)(context); +} diff --git a/addons/knobs/vue.js b/addons/knobs/vue.js new file mode 100644 index 000000000000..42311b17563f --- /dev/null +++ b/addons/knobs/vue.js @@ -0,0 +1 @@ +module.exports = require('./dist/vue'); diff --git a/examples/cra-kitchen-sink/src/stories/index.js b/examples/cra-kitchen-sink/src/stories/index.js index 6a6e91d58df2..1865aff8c037 100644 --- a/examples/cra-kitchen-sink/src/stories/index.js +++ b/examples/cra-kitchen-sink/src/stories/index.js @@ -17,7 +17,7 @@ import { array, date, object, -} from '@storybook/addon-knobs'; +} from '@storybook/addon-knobs/react'; import centered from '@storybook/addon-centered'; import { withInfo } from '@storybook/addon-info'; diff --git a/examples/crna-kitchen-sink/storybook/stories/Knobs/index.js b/examples/crna-kitchen-sink/storybook/stories/Knobs/index.js index 8ca5af5a4440..776fb1eb5729 100644 --- a/examples/crna-kitchen-sink/storybook/stories/Knobs/index.js +++ b/examples/crna-kitchen-sink/storybook/stories/Knobs/index.js @@ -1,7 +1,16 @@ import React from 'react'; import { View, Text } from 'react-native'; -import { text, number, boolean, color, select, array, date, object } from '@storybook/addon-knobs'; +import { + text, + number, + boolean, + color, + select, + array, + date, + object, +} from '@storybook/addon-knobs/react'; export default () => { const name = text('Name', 'Storyteller'); diff --git a/examples/react-native-vanilla/storybook/stories/Knobs/index.js b/examples/react-native-vanilla/storybook/stories/Knobs/index.js index 8ca5af5a4440..776fb1eb5729 100644 --- a/examples/react-native-vanilla/storybook/stories/Knobs/index.js +++ b/examples/react-native-vanilla/storybook/stories/Knobs/index.js @@ -1,7 +1,16 @@ import React from 'react'; import { View, Text } from 'react-native'; -import { text, number, boolean, color, select, array, date, object } from '@storybook/addon-knobs'; +import { + text, + number, + boolean, + color, + select, + array, + date, + object, +} from '@storybook/addon-knobs/react'; export default () => { const name = text('Name', 'Storyteller'); diff --git a/examples/react-native-vanilla/storybook/stories/index.js b/examples/react-native-vanilla/storybook/stories/index.js index 2eea2d4a4e29..2aab33712874 100644 --- a/examples/react-native-vanilla/storybook/stories/index.js +++ b/examples/react-native-vanilla/storybook/stories/index.js @@ -4,7 +4,7 @@ import { Text } from 'react-native'; import { storiesOf } from '@storybook/react-native'; import { action } from '@storybook/addon-actions'; import { linkTo } from '@storybook/addon-links'; -import { withKnobs } from '@storybook/addon-knobs'; +import { withKnobs } from '@storybook/addon-knobs/react'; import knobsWrapper from './Knobs'; import Button from './Button'; diff --git a/examples/vue-kitchen-sink/src/stories/index.js b/examples/vue-kitchen-sink/src/stories/index.js index cbaea1df5144..666cc4d19474 100644 --- a/examples/vue-kitchen-sink/src/stories/index.js +++ b/examples/vue-kitchen-sink/src/stories/index.js @@ -12,7 +12,7 @@ import { select, color, date, -} from '@storybook/addon-knobs'; +} from '@storybook/addon-knobs/vue'; import Centered from '@storybook/addon-centered'; import MyButton from './Button.vue';