Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add params-based API to options addon #3965

Merged
merged 8 commits into from
Sep 8, 2018
194 changes: 100 additions & 94 deletions addons/options/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,114 +20,120 @@ Add this line to your `addons.js` file (create this file inside your storybook c
import '@storybook/addon-options/register';
```

Import and use the `setOptions` function in your config.js file.
Import and use the `withOptions` decorator in your config.js file.

```js
import * as storybook from '@storybook/react';
import { setOptions } from '@storybook/addon-options';
import { addDecorator, configure } from '@storybook/react';
import { withOptions } from '@storybook/addon-options';

// Option defaults:
setOptions({
/**
* name to display in the top left corner
* @type {String}
*/
name: 'Storybook',
/**
* URL for name in top left corner to link to
* @type {String}
*/
url: '#',
/**
* show story component as full screen
* @type {Boolean}
*/
goFullScreen: false,
/**
* display panel that shows a list of stories
* @type {Boolean}
*/
showStoriesPanel: true,
/**
* display panel that shows addon configurations
* @type {Boolean}
*/
showAddonPanel: true,
/**
* display floating search box to search through stories
* @type {Boolean}
*/
showSearchBox: false,
/**
* show addon panel as a vertical panel on the right
* @type {Boolean}
*/
addonPanelInRight: false,
/**
* sorts stories
* @type {Boolean}
*/
sortStoriesByKind: false,
/**
* regex for finding the hierarchy separator
* @example:
* null - turn off hierarchy
* /\// - split by `/`
* /\./ - split by `.`
* /\/|\./ - split by `/` or `.`
* @type {Regex}
*/
hierarchySeparator: null,
/**
* regex for finding the hierarchy root separator
* @example:
* null - turn off multiple hierarchy roots
* /\|/ - split by `|`
* @type {Regex}
*/
hierarchyRootSeparator: null,
/**
* sidebar tree animations
* @type {Boolean}
*/
sidebarAnimations: true,
/**
* id to select an addon panel
* @type {String}
*/
selectedAddonPanel: undefined, // The order of addons in the "Addon panel" is the same as you import them in 'addons.js'. The first panel will be opened by default as you run Storybook
/**
* enable/disable shortcuts
* @type {Boolean}
*/
enableShortcuts: false, // true by default
});

storybook.configure(() => require('./stories'), module);
addDecorator(
withOptions({
/**
* name to display in the top left corner
* @type {String}
*/
name: 'Storybook',
/**
* URL for name in top left corner to link to
* @type {String}
*/
url: '#',
/**
* show story component as full screen
* @type {Boolean}
*/
goFullScreen: false,
/**
* display panel that shows a list of stories
* @type {Boolean}
*/
showStoriesPanel: true,
/**
* display panel that shows addon configurations
* @type {Boolean}
*/
showAddonPanel: true,
/**
* display floating search box to search through stories
* @type {Boolean}
*/
showSearchBox: false,
/**
* show addon panel as a vertical panel on the right
* @type {Boolean}
*/
addonPanelInRight: false,
/**
* sorts stories
* @type {Boolean}
*/
sortStoriesByKind: false,
/**
* regex for finding the hierarchy separator
* @example:
* null - turn off hierarchy
* /\// - split by `/`
* /\./ - split by `.`
* /\/|\./ - split by `/` or `.`
* @type {Regex}
*/
hierarchySeparator: null,
/**
* regex for finding the hierarchy root separator
* @example:
* null - turn off multiple hierarchy roots
* /\|/ - split by `|`
* @type {Regex}
*/
hierarchyRootSeparator: null,
/**
* sidebar tree animations
* @type {Boolean}
*/
sidebarAnimations: true,
/**
* id to select an addon panel
* @type {String}
*/
selectedAddonPanel: undefined, // The order of addons in the "Addon panel" is the same as you import them in 'addons.js'. The first panel will be opened by default as you run Storybook
/**
* enable/disable shortcuts
* @type {Boolean}
*/
enableShortcuts: false, // true by default
})
);

configure(() => require('./stories'), module);
```

### using withOptions options or parameters
### Using per-story options

The options-addon accepts story paramters to set options (as shown below).
The options-addon accepts story parameters on the `options` key:

```js
import { storiesOf } from '@storybook/marko';
import { withKnobs, text, number } from '@storybook/addon-knobs';
import { withOptions } from '@storybook/addon-options';
import Hello from '../components/hello/index.marko';

storiesOf('Addons|Knobs/Hello', module)
.addDecorator(withOptions)
.addDecorator(withKnobs)
// If you want to set the option for all stories in of this kind
.addParameters({ options: { addonPanelInRight: true } })
.add('Simple', () => {
const name = text('Name', 'John Doe');
const age = number('Age', 44);
return Hello.renderSync({
name,
age,
});
});
.addDecorator(withKnobs)
.add(
'Simple',
() => {
const name = text('Name', 'John Doe');
const age = number('Age', 44);
return Hello.renderSync({
name,
age,
});
},
// If you want to set the options for a specific story
{ options: { addonPanelInRight: false } }
);
```

It is also possible to call `setOptions()` inside individual stories. Note that this will bring impact story render performance significantly.
_NOTE_ that you must attach `withOptions` as a decorator (at the top-level) for this to work.
3 changes: 2 additions & 1 deletion addons/options/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "4.0.0-alpha.21"
"@storybook/addons": "4.0.0-alpha.21",
"util-deprecate": "^1.0.2"
},
"peerDependencies": {
"react": "*"
Expand Down
1 change: 0 additions & 1 deletion addons/options/preview.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,4 @@ const preview = require('./dist/preview');

exports.setOptions = preview.setOptions;
exports.withOptions = preview.withOptions;

preview.init();
53 changes: 23 additions & 30 deletions addons/options/src/preview/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import deprecate from 'util-deprecate';
import addons, { makeDecorator } from '@storybook/addons';
import { EVENT_ID } from '../shared';

Expand All @@ -21,9 +22,7 @@ function withRegexProp(object, propName) {
return hasOwnProp(object, propName) ? { [propName]: regExpStringify(object[propName]) } : {};
}

// setOptions function will send Storybook UI options when the channel is
// ready. If called before, options will be cached until it can be sent.
export function setOptions(newOptions) {
function emitOptions(options) {
const channel = addons.getChannel();
if (!channel) {
throw new Error(
Expand All @@ -33,39 +32,33 @@ export function setOptions(newOptions) {

// since 'undefined' and 'null' are the valid values we don't want to
// override the hierarchySeparator or hierarchyRootSeparator if the prop is missing
const options = {
...newOptions,
...withRegexProp(newOptions, 'hierarchySeparator'),
...withRegexProp(newOptions, 'hierarchyRootSeparator'),
};

channel.emit(EVENT_ID, { options });
channel.emit(EVENT_ID, {
options: {
...options,
...withRegexProp(options, 'hierarchySeparator'),
...withRegexProp(options, 'hierarchyRootSeparator'),
},
});
}

// setOptions function will send Storybook UI options when the channel is
// ready. If called before, options will be cached until it can be sent.
let globalOptions = {};
export const setOptions = deprecate(options => {
globalOptions = options;
emitOptions(options);
}, '`setOptions(options)` is deprecated. Please use the `withOptions(options)` decorator globally.');

export const withOptions = makeDecorator({
name: 'withOptions',
parameterName: 'options',
skipIfNoParametersOrOptions: false,
allowDeprecatedUsage: true,
wrapper: (getStory, context, { newOptions, parameters }) => {
const optionsIn = parameters || newOptions || {};

const channel = addons.getChannel();
if (!channel) {
throw new Error(
'Failed to find addon channel. This may be due to https://github.com/storybooks/storybook/issues/1192.'
);
}

// since 'undefined' and 'null' are the valid values we don't want to
// override the hierarchySeparator or hierarchyRootSeparator if the prop is missing
const options = {
...optionsIn,
...withRegexProp(optionsIn, 'hierarchySeparator'),
...withRegexProp(optionsIn, 'hierarchyRootSeparator'),
};

channel.emit(EVENT_ID, { options });
wrapper: (getStory, context, { options: inputOptions, parameters }) => {
emitOptions({
...globalOptions,
...inputOptions,
...parameters,
});

return getStory(context);
},
Expand Down
13 changes: 2 additions & 11 deletions examples/marko-cli/src/stories/index.stories.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { storiesOf } from '@storybook/marko';
import { withOptions } from '@storybook/addon-options';
import Hello from '../components/hello/index.marko';
import ClickCount from '../components/click-count/index.marko';
import StopWatch from '../components/stop-watch/index.marko';
Expand All @@ -8,17 +7,9 @@ import Welcome from '../components/welcome/index.marko';
storiesOf('Welcome', module).add('welcome', () => Welcome.renderSync({}));

storiesOf('Hello', module)
.addDecorator(withOptions)
.addParameters({ options: { addonPanelInRight: false } })
.add('Simple', () => Hello.renderSync({ name: 'abc', age: 20 }))
.add('with ERROR!', () => 'NOT A MARKO RENDER_RESULT');

storiesOf('ClickCount', module)
.addDecorator(withOptions)
.addParameters({ options: { addonPanelInRight: true } })
.add('Simple', () => ClickCount.renderSync({}), { hierarchyRootSeparator: /\|/ });
storiesOf('ClickCount', module).add('Simple', () => ClickCount.renderSync({}));

storiesOf('StopWatch', module)
.addDecorator(withOptions)
.addParameters({ options: { addonPanelInRight: false } })
.add('Simple', () => StopWatch.renderSync({}));
storiesOf('StopWatch', module).add('Simple', () => StopWatch.renderSync({}));
14 changes: 8 additions & 6 deletions examples/official-storybook/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react';
import ThemeProvider from '@emotion/provider';
import { configure, addDecorator } from '@storybook/react';
import { themes } from '@storybook/components';
import { setOptions } from '@storybook/addon-options';
import { withOptions } from '@storybook/addon-options';
import { configureViewport, INITIAL_VIEWPORTS } from '@storybook/addon-viewport';

import 'react-chromatic/storybook-addon';
Expand All @@ -12,11 +12,13 @@ import extraViewports from './extra-viewports.json';
addHeadWarning('Preview head not loaded', 'preview-head-not-loaded');
addHeadWarning('Dotenv file not loaded', 'dotenv-file-not-loaded');

setOptions({
hierarchySeparator: /\/|\./,
hierarchyRootSeparator: /\|/,
theme: themes.dark,
});
addDecorator(
withOptions({
hierarchySeparator: /\/|\./,
hierarchyRootSeparator: /\|/,
theme: themes.dark,
})
);

addDecorator(
(story, { kind }) =>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Storyshots Addons|Options withOptions hiding addon panel 1`] = `
<div>
This story should have changed hidden the addons panel
</div>
`;

exports[`Storyshots Addons|Options withOptions setting name 1`] = `
<div>
This story should have changed the name of the storybook
</div>
`;
22 changes: 22 additions & 0 deletions examples/official-storybook/stories/addon-options.stories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from 'react';
import { storiesOf } from '@storybook/react';

storiesOf('Addons|Options', module)
.add(
'withOptions setting name',
() => <div>This story should have changed the name of the storybook</div>,
{
options: {
name: 'Custom Storybook',
},
}
)
.add(
'withOptions hiding addon panel',
() => <div>This story should have changed hidden the addons panel</div>,
{
options: {
showAddonPanel: false,
},
}
);
Loading