mirror of
https://github.com/storybookjs/storybook.git
synced 2025-03-17 05:02:23 +08:00
Merge branch 'master' of github.com:storybooks/storybook into addmarkosupport
This commit is contained in:
commit
d8e0673d31
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
@ -3,7 +3,7 @@
|
||||
|
||||
/addons/a11y/ @jbovenschen
|
||||
/addons/actions/ @rhalff
|
||||
/addons/background/ @ndelangen @hypnosphi
|
||||
/addons/backgrounds/ @ndelangen @hypnosphi
|
||||
/addons/centered/ @kazupon
|
||||
/addons/events/ @z4o4z @ndelangen
|
||||
/addons/graphql/ @mnmtanish
|
||||
|
@ -12,7 +12,7 @@ enum class StorybookApp(val appName: String, val exampleDir: String, val merged:
|
||||
ANGULAR("Angular", "angular-cli"),
|
||||
POLYMER("Polymer", "polymer-cli"),
|
||||
MITHRIL("Mithril", "mithril-kitchen-sink"),
|
||||
HTML("HTML", "html-kitchen-sink", false);
|
||||
HTML("HTML", "html-kitchen-sink");
|
||||
|
||||
val lowerName = appName.toLowerCase()
|
||||
|
@ -1,19 +1,19 @@
|
||||
## Addon / Framework Support Table
|
||||
|
||||
| |[React](app/react)|[React Native](app/react-native)|[Vue](app/vue)|[Angular](app/angular)| [Polymer](app/polymer)| [Mithril](app/mithril)| [Marko](app/marko)|
|
||||
| ----------- |:-------:|:-------:|:-------:|:-------:|:-------:|:-------:|
|
||||
|[a11y](addons/a11y) |+| | | | | | |
|
||||
|[actions](addons/actions) |+|+|+|+|+|+|+|
|
||||
|[background](addons/background) |+| | | | |+| |
|
||||
|[centered](addons/centered) |+| |+| | |+| |
|
||||
|[events](addons/events) |+| | | | | | |
|
||||
|[graphql](addons/graphql) |+| | | | | | |
|
||||
|[info](addons/info) |+| | | | | | |
|
||||
|[jest](addons/jest) |+| | | | | | |
|
||||
|[knobs](addons/knobs) |+|+|+|+|+|+|+|
|
||||
|[links](addons/links) |+|+|+|+|+|+| |
|
||||
|[notes](addons/notes) |+| |+|+|+|+| |
|
||||
|[options](addons/options) |+|+|+|+|+|+|+|
|
||||
|[storyshots](addons/storyshots) |+|+|+|+| | | |
|
||||
|[storysource](addons/storysource)|+| |+|+|+|+| |
|
||||
|[viewport](addons/viewport) |+| |+|+|+|+| |
|
||||
| |[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) |+| | | | | |+| |
|
||||
|[actions](addons/actions) |+|+|+|+|+|+|+|+|
|
||||
|[backgrounds](addons/backgrounds) |+| | | | |+|+| |
|
||||
|[centered](addons/centered) |+| |+| | |+|+| |
|
||||
|[events](addons/events) |+| | | | | |+| |
|
||||
|[graphql](addons/graphql) |+| | | | | | | |
|
||||
|[info](addons/info) |+| | | | | | | |
|
||||
|[jest](addons/jest) |+| | | | | |+| |
|
||||
|[knobs](addons/knobs) |+|+|+|+|+|+|+|+|
|
||||
|[links](addons/links) |+|+|+|+|+|+|+| |
|
||||
|[notes](addons/notes) |+| |+|+|+|+|+| |
|
||||
|[options](addons/options) |+|+|+|+|+|+|+| |
|
||||
|[storyshots](addons/storyshots) |+|+|+|+| | |+| |
|
||||
|[storysource](addons/storysource)|+| |+|+|+|+|+|+|
|
||||
|[viewport](addons/viewport) |+| |+|+|+|+|+| |
|
||||
|
10
README.md
10
README.md
@ -72,9 +72,10 @@ For additional help, join us [in our Slack](https://now-examples-slackin-rrirkqo
|
||||
- [React Native](app/react-native)
|
||||
- [Vue](app/vue)
|
||||
- [Angular](app/angular)
|
||||
- [Polymer](app/polymer) <sup>release candidate</sup>
|
||||
- [Polymer](app/polymer)
|
||||
- [Mithril](app/mithril) <sup>alpha</sup>
|
||||
- [Marko](app/marko) <sup>alpha</sup>
|
||||
- [HTML](app/html) <sup>alpha</sup>
|
||||
|
||||
### Sub Projects
|
||||
|
||||
@ -85,7 +86,7 @@ For additional help, join us [in our Slack](https://now-examples-slackin-rrirkqo
|
||||
|
||||
- [a11y](addons/a11y/) - Test components for user accessibility in Storybook
|
||||
- [actions](addons/actions/) - Log actions as users interact with components in the Storybook UI
|
||||
- [background](addons/background/) - Let users choose backgrounds in the Storybook UI
|
||||
- [backgrounds](addons/backgrounds/) - Let users choose backgrounds in the Storybook UI
|
||||
- [centered](addons/centered/) - Center the alignment of your components within the Storybook UI
|
||||
- [events](addons/events/) - Interactively fire events to components that respond to EventEmitter
|
||||
- [graphql](addons/graphql/) - Query a GraphQL server within Storybook stories
|
||||
@ -112,6 +113,7 @@ See [Addon / Framework Support Table](ADDONS_SUPPORT.md)
|
||||
- [Polymer](https://storybooks-polymer.netlify.com/)
|
||||
- [Mithril](https://storybooks-mithril.netlify.com/)
|
||||
- [Marko](https://storybooks-marko.netlify.com/)
|
||||
- [HTML](https://storybooks-html.netlify.com/)
|
||||
|
||||
### 3.4
|
||||
- [React Official](https://release-3-4--storybooks-official.netlify.com)
|
||||
@ -226,3 +228,7 @@ Become a sponsor and get your logo on our README on Github with a link to your s
|
||||
<a href="https://opencollective.com/storybook/sponsor/27/website" target="_blank"><img src="https://opencollective.com/storybook/sponsor/27/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/storybook/sponsor/28/website" target="_blank"><img src="https://opencollective.com/storybook/sponsor/28/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/storybook/sponsor/29/website" target="_blank"><img src="https://opencollective.com/storybook/sponsor/29/avatar.svg"></a>
|
||||
|
||||
## License
|
||||
|
||||
[MIT](https://github.com/storybooks/storybook/blob/master/LICENSE)
|
||||
|
1
addons/a11y/html.js
Normal file
1
addons/a11y/html.js
Normal file
@ -0,0 +1 @@
|
||||
module.exports = require('./dist/html');
|
@ -28,10 +28,12 @@
|
||||
"@storybook/addons": "4.0.0-alpha.4",
|
||||
"@storybook/client-logger": "4.0.0-alpha.4",
|
||||
"@storybook/components": "4.0.0-alpha.4",
|
||||
"@storybook/core-events": "4.0.0-alpha.4",
|
||||
"axe-core": "^3.0.2",
|
||||
"babel-runtime": "^6.26.0",
|
||||
"glamor": "^2.20.40",
|
||||
"glamorous": "^4.12.5",
|
||||
"global": "^4.3.2",
|
||||
"prop-types": "^15.6.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
@ -1,6 +1,8 @@
|
||||
import React, { Component } from 'react';
|
||||
import addons from '@storybook/addons';
|
||||
|
||||
import { CHECK_EVENT_ID } from '../shared';
|
||||
|
||||
import Tabs from './Tabs';
|
||||
import Report from './Report';
|
||||
|
||||
@ -26,11 +28,11 @@ class Panel extends Component {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.channel.on('addon:a11y:check', this.onUpdate);
|
||||
this.channel.on(CHECK_EVENT_ID, this.onUpdate);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.channel.removeListener('addon:a11y:check', this.onUpdate);
|
||||
this.channel.removeListener(CHECK_EVENT_ID, this.onUpdate);
|
||||
}
|
||||
|
||||
onUpdate({ passes, violations }) {
|
||||
|
@ -1,6 +1,9 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import addons from '@storybook/addons';
|
||||
|
||||
import { RERUN_EVENT_ID } from '../../shared';
|
||||
|
||||
import RerunButton from './RerunButton';
|
||||
import Item from './Item';
|
||||
|
||||
@ -20,7 +23,7 @@ const styles = {
|
||||
|
||||
function onRerunClick() {
|
||||
const channel = addons.getChannel();
|
||||
channel.emit('addon:a11y:rerun');
|
||||
channel.emit(RERUN_EVENT_ID);
|
||||
}
|
||||
|
||||
const Report = ({ items, empty, passes }) => (
|
||||
|
@ -4,6 +4,8 @@ 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({}),
|
||||
@ -25,13 +27,13 @@ class WrapStory extends Component {
|
||||
|
||||
componentDidMount() {
|
||||
const { channel } = this.props;
|
||||
channel.on('addon:a11y:rerun', this.runA11yCheck);
|
||||
channel.on(RERUN_EVENT_ID, this.runA11yCheck);
|
||||
this.runA11yCheck();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
const { channel } = this.props;
|
||||
channel.removeListener('addon:a11y:rerun', this.runA11yCheck);
|
||||
channel.removeListener(RERUN_EVENT_ID, this.runA11yCheck);
|
||||
}
|
||||
|
||||
/* eslint-disable react/no-find-dom-node */
|
||||
@ -42,7 +44,7 @@ class WrapStory extends Component {
|
||||
if (wrapper !== null) {
|
||||
axe.reset();
|
||||
axe.configure(axeOptions);
|
||||
axe.run(wrapper).then(results => channel.emit('addon:a11y:check', results), logger.error);
|
||||
axe.run(wrapper).then(results => channel.emit(CHECK_EVENT_ID, results), logger.error);
|
||||
}
|
||||
}
|
||||
|
||||
|
35
addons/a11y/src/html.js
Normal file
35
addons/a11y/src/html.js
Normal file
@ -0,0 +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 { 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();
|
||||
};
|
@ -1,6 +1,7 @@
|
||||
// addons, panels and events get unique names using a prefix
|
||||
const ADDON_ID = '@storybook/addon-a11y';
|
||||
const PANEL_ID = `${ADDON_ID}/panel`;
|
||||
const EVENT_ID = `${ADDON_ID}/event`;
|
||||
const CHECK_EVENT_ID = `${ADDON_ID}/check`;
|
||||
const RERUN_EVENT_ID = `${ADDON_ID}/rerun`;
|
||||
|
||||
export { ADDON_ID, PANEL_ID, EVENT_ID };
|
||||
export { ADDON_ID, PANEL_ID, CHECK_EVENT_ID, RERUN_EVENT_ID };
|
||||
|
@ -55,10 +55,10 @@ import { actions } from '@storybook/addon-actions';
|
||||
import Button from './button';
|
||||
|
||||
// This will lead to { onClick: action('onClick'), ... }
|
||||
const eventsFromNames = actions('onClick', 'onDoubleClick');
|
||||
const eventsFromNames = actions('onClick', 'onMouseOver');
|
||||
|
||||
// This will lead to { onClick: action('clicked'), ... }
|
||||
const eventsFromObject = actions({ onClick: 'clicked', onDoubleClick: 'double clicked' });
|
||||
const eventsFromObject = actions({ onClick: 'clicked', onMouseOver: 'hovered' });
|
||||
|
||||
storiesOf('Button', module)
|
||||
.add('default view', () => <Button {...eventsFromNames}>Hello World!</Button>)
|
||||
@ -123,3 +123,22 @@ action('my-action', {
|
||||
|`depth`|Number|Configures the transfered depth of any logged objects.|`10`|
|
||||
|`clearOnStoryChange`|Boolean|Flag whether to clear the action logger when switching away from the current story.|`true`|
|
||||
|`limit`|Number|Limits the number of items logged in the action logger|`50`|
|
||||
|
||||
## withActions decorator
|
||||
|
||||
You can define action handles in a declarative way using `withActions` decorators. It accepts the same arguments as [`actions`](#multiple-actions)
|
||||
Keys have `'<eventName> <selector>'` format, e.g. `'click .btn'`. Selector is optional. This can be used with any framework but is especially useful for `@storybook/html`.
|
||||
|
||||
```js
|
||||
import { storiesOf } from '@storybook/html';
|
||||
import { withActions } from '@storybook/addon-actions';
|
||||
|
||||
storiesOf('button', module)
|
||||
// Log mousovers on entire story and clicks on .btn
|
||||
.addDecorator(withActions('mouseover', 'click .btn'))
|
||||
.add('with actions', () => `
|
||||
<div>
|
||||
Clicks on this button will be logged: <button class="btn" type="button">Button</button>
|
||||
</div>
|
||||
`);
|
||||
```
|
||||
|
@ -22,11 +22,13 @@
|
||||
"dependencies": {
|
||||
"@storybook/addons": "4.0.0-alpha.4",
|
||||
"@storybook/components": "4.0.0-alpha.4",
|
||||
"@storybook/core-events": "4.0.0-alpha.4",
|
||||
"babel-runtime": "^6.26.0",
|
||||
"deep-equal": "^1.0.1",
|
||||
"glamor": "^2.20.40",
|
||||
"glamorous": "^4.12.5",
|
||||
"global": "^4.3.2",
|
||||
"lodash.isequal": "^4.5.0",
|
||||
"make-error": "^1.3.4",
|
||||
"prop-types": "^15.6.1",
|
||||
"react-inspector": "^2.3.0",
|
||||
|
@ -1,8 +1,15 @@
|
||||
import { action, actions, decorate, configureActions, decorateAction } from './preview';
|
||||
import {
|
||||
action,
|
||||
actions,
|
||||
decorate,
|
||||
configureActions,
|
||||
decorateAction,
|
||||
withActions,
|
||||
} from './preview';
|
||||
|
||||
// addons, panels and events get unique names using a prefix
|
||||
export const ADDON_ID = 'storybook/actions';
|
||||
export const PANEL_ID = `${ADDON_ID}/actions-panel`;
|
||||
export const EVENT_ID = `${ADDON_ID}/action-event`;
|
||||
|
||||
export { action, actions, decorate, configureActions, decorateAction };
|
||||
export { action, actions, decorate, configureActions, decorateAction, withActions };
|
||||
|
@ -1,5 +1,6 @@
|
||||
import action from './action';
|
||||
import actions from './actions';
|
||||
import { createDecorator } from './withActions';
|
||||
|
||||
function applyDecorators(decorators, actionCallback) {
|
||||
return (..._args) => {
|
||||
@ -17,15 +18,17 @@ export function decorateAction(decorators) {
|
||||
|
||||
export function decorate(decorators) {
|
||||
const decorated = decorateAction(decorators);
|
||||
const decoratedActions = (...args) => {
|
||||
const rawActions = actions(...args);
|
||||
const actionsObject = {};
|
||||
Object.keys(rawActions).forEach(name => {
|
||||
actionsObject[name] = applyDecorators(decorators, rawActions[name]);
|
||||
});
|
||||
return actionsObject;
|
||||
};
|
||||
return {
|
||||
action: decorated,
|
||||
actions: (...args) => {
|
||||
const rawActions = actions(...args);
|
||||
const decoratedActions = {};
|
||||
Object.keys(rawActions).forEach(name => {
|
||||
decoratedActions[name] = applyDecorators(decorators, rawActions[name]);
|
||||
});
|
||||
return decoratedActions;
|
||||
},
|
||||
actions: decoratedActions,
|
||||
withActions: createDecorator(decoratedActions),
|
||||
};
|
||||
}
|
||||
|
@ -2,3 +2,4 @@ export { default as action } from './action';
|
||||
export { default as actions } from './actions';
|
||||
export { configureActions } from './configureActions';
|
||||
export { decorateAction, decorate } from './decorateAction';
|
||||
export { default as withActions } from './withActions';
|
||||
|
64
addons/actions/src/preview/withActions.js
Normal file
64
addons/actions/src/preview/withActions.js
Normal file
@ -0,0 +1,64 @@
|
||||
// Based on http://backbonejs.org/docs/backbone.html#section-164
|
||||
import { document, Element } from 'global';
|
||||
import isEqual from 'lodash.isequal';
|
||||
import addons from '@storybook/addons';
|
||||
import Events from '@storybook/core-events';
|
||||
|
||||
import actions from './actions';
|
||||
|
||||
let lastSubscription;
|
||||
let lastArgs;
|
||||
|
||||
const delegateEventSplitter = /^(\S+)\s*(.*)$/;
|
||||
|
||||
const isIE = Element != null && !Element.prototype.matches;
|
||||
const matchesMethod = isIE ? 'msMatchesSelector' : 'matches';
|
||||
|
||||
const root = document && document.getElementById('root');
|
||||
|
||||
const hasMatchInAncestry = (element, selector) => {
|
||||
if (element[matchesMethod](selector)) {
|
||||
return true;
|
||||
}
|
||||
const parent = element.parentElement;
|
||||
if (!parent) {
|
||||
return false;
|
||||
}
|
||||
return hasMatchInAncestry(parent, selector);
|
||||
};
|
||||
|
||||
const createHandlers = (actionsFn, ...args) => {
|
||||
const actionsObject = actionsFn(...args);
|
||||
return Object.entries(actionsObject).map(([key, action]) => {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const [_, eventName, selector] = key.match(delegateEventSplitter);
|
||||
return {
|
||||
eventName,
|
||||
handler: e => {
|
||||
if (!selector || hasMatchInAncestry(e.target, selector)) {
|
||||
action(e);
|
||||
}
|
||||
},
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const actionsSubscription = (...args) => {
|
||||
if (!isEqual(args, lastArgs)) {
|
||||
lastArgs = args;
|
||||
const handlers = createHandlers(...args);
|
||||
lastSubscription = () => {
|
||||
handlers.forEach(({ eventName, handler }) => root.addEventListener(eventName, handler));
|
||||
return () =>
|
||||
handlers.forEach(({ eventName, handler }) => root.removeEventListener(eventName, handler));
|
||||
};
|
||||
}
|
||||
return lastSubscription;
|
||||
};
|
||||
|
||||
export const createDecorator = actionsFn => (...args) => story => {
|
||||
addons.getChannel().emit(Events.REGISTER_SUBSCRIPTION, actionsSubscription(actionsFn, ...args));
|
||||
return story();
|
||||
};
|
||||
|
||||
export default createDecorator(actions);
|
1
addons/backgrounds/html.js
Normal file
1
addons/backgrounds/html.js
Normal file
@ -0,0 +1 @@
|
||||
module.exports = require('./dist/html');
|
@ -25,6 +25,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/addons": "4.0.0-alpha.4",
|
||||
"@storybook/core-events": "4.0.0-alpha.4",
|
||||
"babel-runtime": "^6.26.0",
|
||||
"global": "^4.3.2",
|
||||
"prop-types": "^15.6.1",
|
@ -3,6 +3,7 @@ import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import addons from '@storybook/addons';
|
||||
|
||||
import Events from './events';
|
||||
import Swatch from './Swatch';
|
||||
|
||||
const storybookIframe = 'storybook-preview-iframe';
|
||||
@ -88,7 +89,7 @@ export default class BackgroundPanel extends Component {
|
||||
|
||||
const { api } = this.props;
|
||||
|
||||
this.channel.on('background-set', backgrounds => {
|
||||
this.channel.on(Events.SET, backgrounds => {
|
||||
this.setState({ backgrounds });
|
||||
const currentBackground = api.getQueryParam('background');
|
||||
|
||||
@ -100,7 +101,7 @@ export default class BackgroundPanel extends Component {
|
||||
}
|
||||
});
|
||||
|
||||
this.channel.on('background-unset', () => {
|
||||
this.channel.on(Events.UNSET, () => {
|
||||
this.setState({ backgrounds: [] });
|
||||
this.updateIframe('none');
|
||||
});
|
@ -3,6 +3,7 @@ import { shallow, mount } from 'enzyme';
|
||||
import EventEmitter from 'events';
|
||||
|
||||
import BackgroundPanel from '../BackgroundPanel';
|
||||
import Events from '../events';
|
||||
|
||||
const backgrounds = [
|
||||
{ name: 'black', value: '#000000' },
|
||||
@ -49,7 +50,7 @@ describe('Background Panel', () => {
|
||||
it('should set the query string', () => {
|
||||
const SpiedChannel = new EventEmitter();
|
||||
mount(<BackgroundPanel channel={SpiedChannel} api={mockedApi} />);
|
||||
SpiedChannel.emit('background-set', backgrounds);
|
||||
SpiedChannel.emit(Events.SET, backgrounds);
|
||||
|
||||
expect(mockedApi.getQueryParam).toBeCalledWith('background');
|
||||
});
|
||||
@ -57,7 +58,7 @@ describe('Background Panel', () => {
|
||||
it('should not unset the query string', () => {
|
||||
const SpiedChannel = new EventEmitter();
|
||||
mount(<BackgroundPanel channel={SpiedChannel} api={mockedApi} />);
|
||||
SpiedChannel.emit('background-unset', []);
|
||||
SpiedChannel.emit(Events.UNSET, []);
|
||||
|
||||
expect(mockedApi.setQueryParams).not.toHaveBeenCalled();
|
||||
});
|
||||
@ -65,7 +66,7 @@ describe('Background Panel', () => {
|
||||
it('should accept colors through channel and render the correct swatches with a default swatch', () => {
|
||||
const SpiedChannel = new EventEmitter();
|
||||
const backgroundPanel = mount(<BackgroundPanel channel={SpiedChannel} api={mockedApi} />);
|
||||
SpiedChannel.emit('background-set', backgrounds);
|
||||
SpiedChannel.emit(Events.SET, backgrounds);
|
||||
|
||||
expect(backgroundPanel.state('backgrounds')).toEqual(backgrounds);
|
||||
});
|
||||
@ -75,7 +76,7 @@ describe('Background Panel', () => {
|
||||
const backgroundPanel = mount(<BackgroundPanel channel={SpiedChannel} api={mockedApi} />);
|
||||
const [head, ...tail] = backgrounds;
|
||||
const localBgs = [{ ...head, default: true }, ...tail];
|
||||
SpiedChannel.emit('background-set', localBgs);
|
||||
SpiedChannel.emit(Events.SET, localBgs);
|
||||
|
||||
expect(backgroundPanel.state('backgrounds')).toEqual(localBgs);
|
||||
backgroundPanel.setState({ backgrounds: localBgs }); // force re-render
|
||||
@ -93,7 +94,7 @@ describe('Background Panel', () => {
|
||||
SpiedChannel.on('background', bg => {
|
||||
expect(bg).toBe(second.value);
|
||||
});
|
||||
SpiedChannel.emit('background-set', localBgs);
|
||||
SpiedChannel.emit(Events.SET, localBgs);
|
||||
|
||||
expect(backgroundPanel.state('backgrounds')).toEqual(localBgs);
|
||||
backgroundPanel.setState({ backgrounds: localBgs }); // force re-render
|
||||
@ -106,12 +107,12 @@ describe('Background Panel', () => {
|
||||
it('should unset all swatches on receiving the background-unset message', () => {
|
||||
const SpiedChannel = new EventEmitter();
|
||||
const backgroundPanel = mount(<BackgroundPanel channel={SpiedChannel} api={mockedApi} />);
|
||||
SpiedChannel.emit('background-set', backgrounds);
|
||||
SpiedChannel.emit(Events.SET, backgrounds);
|
||||
|
||||
expect(backgroundPanel.state('backgrounds')).toEqual(backgrounds);
|
||||
backgroundPanel.setState({ backgrounds }); // force re-render
|
||||
|
||||
SpiedChannel.emit('background-unset');
|
||||
SpiedChannel.emit(Events.UNSET);
|
||||
expect(backgroundPanel.state('backgrounds')).toHaveLength(0);
|
||||
});
|
||||
|
@ -1,9 +1,9 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import EventEmitter from 'events';
|
||||
|
||||
import { BackgroundDecorator } from '../index';
|
||||
|
||||
const EventEmitter = require('events');
|
||||
import Events from '../events';
|
||||
|
||||
const testStory = () => () => <p>Hello World!</p>;
|
||||
|
||||
@ -23,7 +23,7 @@ describe('Background Decorator', () => {
|
||||
);
|
||||
|
||||
const spy = jest.fn();
|
||||
SpiedChannel.on('background-unset', spy);
|
||||
SpiedChannel.on(Events.UNSET, spy);
|
||||
|
||||
backgroundDecorator.unmount();
|
||||
|
||||
@ -33,7 +33,7 @@ describe('Background Decorator', () => {
|
||||
it('should send background-set event when the component mounts', () => {
|
||||
const SpiedChannel = new EventEmitter();
|
||||
const spy = jest.fn();
|
||||
SpiedChannel.on('background-set', spy);
|
||||
SpiedChannel.on(Events.SET, spy);
|
||||
|
||||
shallow(<BackgroundDecorator story={testStory} channel={SpiedChannel} />);
|
||||
|
@ -1,6 +1,8 @@
|
||||
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() };
|
||||
@ -37,6 +39,6 @@ describe('Vue handler', () => {
|
||||
new Vue(vueHandler(testChannel, testBackground)(testStory, testContext)).$mount();
|
||||
|
||||
expect(testChannel.emit).toHaveBeenCalledTimes(1);
|
||||
expect(testChannel.emit).toHaveBeenCalledWith('background-set', expect.any(Array));
|
||||
expect(testChannel.emit).toHaveBeenCalledWith(Events.SET, expect.any(Array));
|
||||
});
|
||||
});
|
6
addons/backgrounds/src/events.js
Normal file
6
addons/backgrounds/src/events.js
Normal file
@ -0,0 +1,6 @@
|
||||
const ADDON_ID = 'backgrounds';
|
||||
|
||||
export default {
|
||||
SET: `${ADDON_ID}:set`,
|
||||
UNSET: `${ADDON_ID}:unset`,
|
||||
};
|
17
addons/backgrounds/src/html.js
Normal file
17
addons/backgrounds/src/html.js
Normal file
@ -0,0 +1,17 @@
|
||||
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();
|
||||
};
|
@ -4,6 +4,8 @@ import PropTypes from 'prop-types';
|
||||
|
||||
import addons from '@storybook/addons';
|
||||
|
||||
import Events from './events';
|
||||
|
||||
export class BackgroundDecorator extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
@ -21,11 +23,11 @@ export class BackgroundDecorator extends React.Component {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.channel.emit('background-set', this.props.backgrounds);
|
||||
this.channel.emit(Events.SET, this.props.backgrounds);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.channel.emit('background-unset');
|
||||
this.channel.emit(Events.UNSET);
|
||||
}
|
||||
|
||||
render() {
|
@ -5,6 +5,8 @@ import m from 'mithril';
|
||||
|
||||
import addons from '@storybook/addons';
|
||||
|
||||
import Events from './events';
|
||||
|
||||
export class BackgroundDecorator {
|
||||
constructor(vnode) {
|
||||
this.props = vnode.attrs;
|
||||
@ -22,11 +24,11 @@ export class BackgroundDecorator {
|
||||
}
|
||||
|
||||
oncreate() {
|
||||
this.channel.emit('background-set', this.props.backgrounds);
|
||||
this.channel.emit(Events.SET, this.props.backgrounds);
|
||||
}
|
||||
|
||||
onremove() {
|
||||
this.channel.emit('background-unset');
|
||||
this.channel.emit(Events.UNSET);
|
||||
}
|
||||
|
||||
view() {
|
@ -1,5 +1,7 @@
|
||||
import addons from '@storybook/addons';
|
||||
|
||||
import Events from './events';
|
||||
|
||||
export const vueHandler = (channel, backgrounds) => (getStory, context) => ({
|
||||
data() {
|
||||
return {
|
||||
@ -14,11 +16,11 @@ export const vueHandler = (channel, backgrounds) => (getStory, context) => ({
|
||||
},
|
||||
|
||||
created() {
|
||||
channel.emit('background-set', backgrounds);
|
||||
channel.emit(Events.SET, backgrounds);
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
channel.emit('background-unset');
|
||||
channel.emit(Events.UNSET);
|
||||
},
|
||||
});
|
||||
|
1
addons/centered/html.js
Normal file
1
addons/centered/html.js
Normal file
@ -0,0 +1 @@
|
||||
module.exports = require('./dist/html');
|
46
addons/centered/src/html.js
Normal file
46
addons/centered/src/html.js
Normal file
@ -0,0 +1,46 @@
|
||||
import { document, Node } from 'global';
|
||||
import styles from './styles';
|
||||
|
||||
const INNER_ID = 'sb-addon-centered-inner';
|
||||
const WRAPPER_ID = 'sb-addon-centered-wrapper';
|
||||
|
||||
function getOrCreate(id, style) {
|
||||
const elementOnDom = document.getElementById(id);
|
||||
|
||||
if (elementOnDom) {
|
||||
return elementOnDom;
|
||||
}
|
||||
|
||||
const element = document.createElement('div');
|
||||
element.setAttribute('id', id);
|
||||
Object.assign(element.style, style);
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
function getInnerDiv() {
|
||||
return getOrCreate(INNER_ID, styles.innerStyle);
|
||||
}
|
||||
|
||||
function getWrapperDiv() {
|
||||
return getOrCreate(WRAPPER_ID, styles.style);
|
||||
}
|
||||
|
||||
export default function(storyFn) {
|
||||
const inner = getInnerDiv();
|
||||
const wrapper = getWrapperDiv();
|
||||
wrapper.appendChild(inner);
|
||||
|
||||
const component = storyFn();
|
||||
|
||||
if (typeof component === 'string') {
|
||||
inner.innerHTML = component;
|
||||
} else if (component instanceof Node) {
|
||||
inner.innerHTML = '';
|
||||
inner.appendChild(component);
|
||||
} else {
|
||||
return component;
|
||||
}
|
||||
|
||||
return wrapper;
|
||||
}
|
@ -2,28 +2,13 @@
|
||||
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import m from 'mithril';
|
||||
|
||||
const style = {
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
overflow: 'auto',
|
||||
};
|
||||
|
||||
const innerStyle = {
|
||||
margin: 'auto',
|
||||
};
|
||||
import styles from './styles';
|
||||
|
||||
export default function(storyFn) {
|
||||
return {
|
||||
view: () => (
|
||||
<div style={style}>
|
||||
<div style={innerStyle}>{m(storyFn())}</div>
|
||||
<div style={styles.style}>
|
||||
<div style={styles.innerStyle}>{m(storyFn())}</div>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
|
21
addons/centered/src/react.js
vendored
21
addons/centered/src/react.js
vendored
@ -1,26 +1,11 @@
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import React from 'react';
|
||||
|
||||
const style = {
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
overflow: 'auto',
|
||||
};
|
||||
|
||||
const innerStyle = {
|
||||
margin: 'auto',
|
||||
};
|
||||
import styles from './styles';
|
||||
|
||||
export default function(storyFn) {
|
||||
return (
|
||||
<div style={style}>
|
||||
<div style={innerStyle}>{storyFn()}</div>
|
||||
<div style={styles.style}>
|
||||
<div style={styles.innerStyle}>{storyFn()}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
18
addons/centered/src/styles.js
Normal file
18
addons/centered/src/styles.js
Normal file
@ -0,0 +1,18 @@
|
||||
const styles = {
|
||||
style: {
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
overflow: 'auto',
|
||||
},
|
||||
innerStyle: {
|
||||
margin: 'auto',
|
||||
},
|
||||
};
|
||||
|
||||
export default styles;
|
@ -1,3 +1,5 @@
|
||||
import styles from './styles';
|
||||
|
||||
export default function() {
|
||||
return {
|
||||
template: `
|
||||
@ -8,22 +10,7 @@ export default function() {
|
||||
</div>
|
||||
`,
|
||||
data() {
|
||||
return {
|
||||
style: {
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
overflow: 'auto',
|
||||
},
|
||||
innerStyle: {
|
||||
margin: 'auto',
|
||||
},
|
||||
};
|
||||
return styles;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
1
addons/events/html.js
Normal file
1
addons/events/html.js
Normal file
@ -0,0 +1 @@
|
||||
module.exports = require('./dist/html');
|
@ -20,6 +20,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/addons": "4.0.0-alpha.4",
|
||||
"@storybook/core-events": "4.0.0-alpha.4",
|
||||
"babel-runtime": "^6.26.0",
|
||||
"format-json": "^1.0.3",
|
||||
"prop-types": "^15.6.1",
|
||||
|
27
addons/events/src/html.js
Normal file
27
addons/events/src/html.js
Normal file
@ -0,0 +1,27 @@
|
||||
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();
|
||||
};
|
@ -16,9 +16,9 @@ You can also use Knobs as a dynamic variable inside stories in [Storybook](https
|
||||
|
||||
This is how Knobs look like:
|
||||
|
||||
[](https://git.io/vXdhZ)
|
||||
[](https://storybooks-official.netlify.com/?knob-Dollars=12.5&knob-Name=Storyteller&knob-Years%20in%20NY=9&knob-background=%23ffff00&knob-Age=70&knob-Items%5B0%5D=Laptop&knob-Items%5B1%5D=Book&knob-Items%5B2%5D=Whiskey&knob-Other%20Fruit=lime&knob-Birthday=1484870400000&knob-Nice=true&knob-Styles=%7B%22border%22%3A%223px%20solid%20%23ff00ff%22%2C%22padding%22%3A%2210px%22%7D&knob-Fruit=apple&selectedKind=Addons%7CKnobs.withKnobs&selectedStory=tweaks%20static%20values&full=0&addons=1&stories=1&panelRight=0&addonPanel=storybooks%2Fstorybook-addon-knobs)
|
||||
|
||||
> Checkout the above [Live Storybook](https://storybooks-official.netlify.com/) or [watch this video](https://www.youtube.com/watch?v=kopW6vzs9dg&feature=youtu.be).
|
||||
> Checkout the above [Live Storybook](https://storybooks-official.netlify.com/?knob-Dollars=12.5&knob-Name=Storyteller&knob-Years%20in%20NY=9&knob-background=%23ffff00&knob-Age=70&knob-Items%5B0%5D=Laptop&knob-Items%5B1%5D=Book&knob-Items%5B2%5D=Whiskey&knob-Other%20Fruit=lime&knob-Birthday=1484870400000&knob-Nice=true&knob-Styles=%7B%22border%22%3A%223px%20solid%20%23ff00ff%22%2C%22padding%22%3A%2210px%22%7D&knob-Fruit=apple&selectedKind=Addons%7CKnobs.withKnobs&selectedStory=tweaks%20static%20values&full=0&addons=1&stories=1&panelRight=0&addonPanel=storybooks%2Fstorybook-addon-knobs) or [watch this video](https://www.youtube.com/watch?v=kopW6vzs9dg&feature=youtu.be).
|
||||
|
||||
## Getting Started
|
||||
|
||||
@ -50,7 +50,7 @@ stories.addDecorator(withKnobs);
|
||||
// Knobs for React props
|
||||
stories.add('with a button', () => (
|
||||
<button disabled={boolean('Disabled', false)} >
|
||||
{text('Label', 'Hello Button')}
|
||||
{text('Label', 'Hello Storybook')}
|
||||
</button>
|
||||
));
|
||||
|
||||
@ -77,17 +77,17 @@ const stories = storiesOf('Storybook Knobs', module);
|
||||
stories.addDecorator(withKnobs);
|
||||
|
||||
// Knobs for Angular props
|
||||
stories.add('with text', () => ({
|
||||
stories.add('with a button', () => ({
|
||||
component: Button,
|
||||
props: {
|
||||
text: text('text', 'Hello Button'), // The first param of the knob function has to be exactly the same as the component input.
|
||||
text: text('text', 'Hello Storybook'), // The first param of the knob function has to be exactly the same as the component input.
|
||||
},
|
||||
}));
|
||||
|
||||
```
|
||||
|
||||
Categorize your knobs by assigning them a `groupId`. When a `groupId` exists, tabs will appear in the knobs storybook panel to filter between the groups. Knobs without a `groupId` are automatically categorized into the `ALL` group.
|
||||
```
|
||||
```js
|
||||
// Knob assigned a groupId.
|
||||
stories.add('as dynamic variables', () => {
|
||||
const groupId = 'GROUP-ID1'
|
||||
@ -130,13 +130,6 @@ You can see your Knobs in a Storybook panel as shown below.
|
||||
|
||||

|
||||
|
||||
### Additional Links
|
||||
|
||||
- Introduction blog post.
|
||||
- Watch this video on how to use knobs
|
||||
- [Live Storybook with Knobs](https://goo.gl/uX9WLf)
|
||||
- Have a look at this [sample Storybook repo](https://github.com/kadira-samples/storybook-knobs-example).
|
||||
|
||||
## Available Knobs
|
||||
|
||||
These are the knobs available for you to use. You can import these Knobs from the `@storybook/addon-knobs` module.
|
||||
|
1
addons/knobs/html.js
Normal file
1
addons/knobs/html.js
Normal file
@ -0,0 +1 @@
|
||||
module.exports = require('./dist/html');
|
@ -15,6 +15,7 @@
|
||||
"dependencies": {
|
||||
"@storybook/addons": "4.0.0-alpha.4",
|
||||
"@storybook/components": "4.0.0-alpha.4",
|
||||
"@storybook/core-events": "4.0.0-alpha.4",
|
||||
"babel-runtime": "^6.26.0",
|
||||
"deep-equal": "^1.0.1",
|
||||
"escape-html": "^1.0.3",
|
||||
|
32
addons/knobs/src/html/index.js
Normal file
32
addons/knobs/src/html/index.js
Normal file
@ -0,0 +1,32 @@
|
||||
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 });
|
64
addons/knobs/src/html/registerKnobs.js
Normal file
64
addons/knobs/src/html/registerKnobs.js
Normal file
@ -0,0 +1,64 @@
|
||||
import addons from '@storybook/addons';
|
||||
import Events from '@storybook/core-events';
|
||||
import { manager } from '../base';
|
||||
|
||||
const { knobStore } = manager;
|
||||
|
||||
function forceReRender() {
|
||||
addons.getChannel().emit(Events.FORCE_RE_RENDER);
|
||||
}
|
||||
|
||||
function setPaneKnobs(timestamp = +new Date()) {
|
||||
const channel = addons.getChannel();
|
||||
channel.emit('addon:knobs:setKnobs', { knobs: knobStore.getAll(), timestamp });
|
||||
}
|
||||
|
||||
function knobChanged(change) {
|
||||
const { name, value } = change;
|
||||
|
||||
// Update the related knob and it's value.
|
||||
const knobOptions = knobStore.get(name);
|
||||
|
||||
knobOptions.value = value;
|
||||
knobStore.markAllUnused();
|
||||
|
||||
forceReRender();
|
||||
}
|
||||
|
||||
function knobClicked(clicked) {
|
||||
const knobOptions = knobStore.get(clicked.name);
|
||||
knobOptions.callback();
|
||||
}
|
||||
|
||||
function resetKnobs() {
|
||||
knobStore.reset();
|
||||
|
||||
forceReRender();
|
||||
|
||||
const channel = addons.getChannel();
|
||||
setPaneKnobs(channel, knobStore, false);
|
||||
}
|
||||
|
||||
function disconnectCallbacks() {
|
||||
const channel = addons.getChannel();
|
||||
channel.removeListener('addon:knobs:knobChange', knobChanged);
|
||||
channel.removeListener('addon:knobs:knobClick', knobClicked);
|
||||
channel.removeListener('addon:knobs:reset', resetKnobs);
|
||||
knobStore.unsubscribe(setPaneKnobs);
|
||||
}
|
||||
|
||||
function connectCallbacks() {
|
||||
const channel = addons.getChannel();
|
||||
channel.on('addon:knobs:knobChange', knobChanged);
|
||||
channel.on('addon:knobs:knobClick', knobClicked);
|
||||
channel.on('addon:knobs:reset', resetKnobs);
|
||||
knobStore.subscribe(setPaneKnobs);
|
||||
|
||||
return disconnectCallbacks;
|
||||
}
|
||||
|
||||
function registerKnobs() {
|
||||
addons.getChannel().emit(Events.REGISTER_SUBSCRIPTION, connectCallbacks);
|
||||
}
|
||||
|
||||
export default registerKnobs;
|
@ -95,6 +95,22 @@ storiesOf('Href', module)
|
||||
});
|
||||
```
|
||||
|
||||
## withLinks decorator
|
||||
|
||||
`withLinks` decorator enables a declarative way of defining story links, using data attributes.
|
||||
Here is an example in React, but it works with any framework:
|
||||
|
||||
```js
|
||||
import { storiesOf } from '@storybook/react'
|
||||
import { withLinks } from '@storybook/addon-links'
|
||||
|
||||
storiesOf('Button', module)
|
||||
.addDecorator(withLinks)
|
||||
.add('First', () => (
|
||||
<button data-sb-kind="OtherKind" data-sb-story="OtherStory">Go to "OtherStory"</button>
|
||||
))
|
||||
```
|
||||
|
||||
## LinkTo component (React only)
|
||||
|
||||
One possible way of using `hrefTo` is to create a component that uses native `a` element, but prevents page reloads on plain left click, so that one can still use default browser methods to open link in new tab.
|
||||
|
@ -22,6 +22,7 @@
|
||||
"dependencies": {
|
||||
"@storybook/addons": "4.0.0-alpha.4",
|
||||
"@storybook/components": "4.0.0-alpha.4",
|
||||
"@storybook/core-events": "4.0.0-alpha.4",
|
||||
"babel-runtime": "^6.26.0",
|
||||
"global": "^4.3.2",
|
||||
"prop-types": "^15.6.1"
|
||||
|
@ -3,7 +3,7 @@ export const EVENT_ID = `${ADDON_ID}/link-event`;
|
||||
export const REQUEST_HREF_EVENT_ID = `${ADDON_ID}/request-href-event`;
|
||||
export const RECEIVE_HREF_EVENT_ID = `${ADDON_ID}/receive-href-event`;
|
||||
|
||||
export { linkTo, hrefTo } from './preview';
|
||||
export { linkTo, hrefTo, withLinks } from './preview';
|
||||
|
||||
let hasWarned = false;
|
||||
|
||||
|
@ -1,4 +1,7 @@
|
||||
import { document } from 'global';
|
||||
import addons from '@storybook/addons';
|
||||
import Events from '@storybook/core-events';
|
||||
|
||||
import { EVENT_ID, REQUEST_HREF_EVENT_ID, RECEIVE_HREF_EVENT_ID } from './';
|
||||
|
||||
export const openLink = params => addons.getChannel().emit(EVENT_ID, params);
|
||||
@ -19,3 +22,21 @@ export const hrefTo = (kind, story) =>
|
||||
channel.on(RECEIVE_HREF_EVENT_ID, resolve);
|
||||
channel.emit(REQUEST_HREF_EVENT_ID, { kind, story });
|
||||
});
|
||||
|
||||
const linksListener = e => {
|
||||
const { sbKind, sbStory } = e.target.dataset;
|
||||
if (sbKind || sbStory) {
|
||||
e.preventDefault();
|
||||
linkTo(sbKind, sbStory)();
|
||||
}
|
||||
};
|
||||
|
||||
const linkSubscribtion = () => {
|
||||
document.addEventListener('click', linksListener);
|
||||
return () => document.removeEventListener('click', linksListener);
|
||||
};
|
||||
|
||||
export const withLinks = story => {
|
||||
addons.getChannel().emit(Events.REGISTER_SUBSCRIPTION, linkSubscribtion);
|
||||
return story();
|
||||
};
|
||||
|
@ -243,6 +243,18 @@ initStoryshots({suite: 'Image storyshots', test: imageSnapshot({storybookUrl: 'h
|
||||
|
||||
`getScreenshotOptions` receives an object `{ context: {kind, story}, url}`. _kind_ is the kind of the story and the _story_ its name. _url_ is the URL the browser will use to screenshot.
|
||||
|
||||
### Specifying custom Chrome executable path (puppeteer API)
|
||||
|
||||
You might use `chromeExecutablePath` to specify the path to a different version of Chrome, without downloading Chromium. Will be passed to [Runs a bundled version of Chromium](https://github.com/GoogleChrome/puppeteer#default-runtime-settings)
|
||||
|
||||
```js
|
||||
import initStoryshots, { imageSnapshot } from '@storybook/addon-storyshots';
|
||||
|
||||
const chromeExecutablePath = '/usr/local/bin/chrome';
|
||||
|
||||
initStoryshots({suite: 'Image storyshots', test: imageSnapshot({storybookUrl: 'http://localhost:6006', chromeExecutablePath})});
|
||||
```
|
||||
|
||||
### Integrate image storyshots with regular app
|
||||
|
||||
You may want to use another Jest project to run your image snapshots as they require more resources: Chrome and Storybook built/served.
|
||||
@ -422,7 +434,7 @@ Like the default, but allows you to specify a set of options for the test render
|
||||
|
||||
### `multiSnapshotWithOptions(options)`
|
||||
|
||||
Like `snapshotWithOptions`, but generate a separate snapshot file for each stories file rather than a single monolithic file (as is the convention in Jest). This makes it dramatically easier to review changes.
|
||||
Like `snapshotWithOptions`, but generate a separate snapshot file for each stories file rather than a single monolithic file (as is the convention in Jest). This makes it dramatically easier to review changes. If you'd like the benefit of separate snapshot files, but don't have custom options to pass, simply pass an empty object.
|
||||
|
||||
#### integrityOptions
|
||||
This option is useful when running test with `multiSnapshotWithOptions(options)` in order to track snapshots are matching the stories. (disabled by default).
|
||||
|
@ -2,8 +2,9 @@ import loaderReact from './react/loader';
|
||||
import loaderRn from './rn/loader';
|
||||
import loaderAngular from './angular/loader';
|
||||
import loaderVue from './vue/loader';
|
||||
import loaderHTML from './html/loader';
|
||||
|
||||
const loaders = [loaderReact, loaderAngular, loaderRn, loaderVue];
|
||||
const loaders = [loaderReact, loaderAngular, loaderRn, loaderVue, loaderHTML];
|
||||
|
||||
function loadFramework(options) {
|
||||
const loader = loaders.find(frameworkLoader => frameworkLoader.test(options));
|
||||
|
32
addons/storyshots/src/html/loader.js
Normal file
32
addons/storyshots/src/html/loader.js
Normal file
@ -0,0 +1,32 @@
|
||||
import global from 'global';
|
||||
import runWithRequireContext from '../require_context';
|
||||
import loadConfig from '../config-loader';
|
||||
|
||||
function test(options) {
|
||||
return options.framework === 'html';
|
||||
}
|
||||
|
||||
function load(options) {
|
||||
global.STORYBOOK_ENV = 'html';
|
||||
|
||||
const { content, contextOpts } = loadConfig({
|
||||
configDirPath: options.configPath,
|
||||
babelConfigPath: '@storybook/html/dist/server/config/babel',
|
||||
});
|
||||
|
||||
runWithRequireContext(content, contextOpts);
|
||||
|
||||
return {
|
||||
framework: 'html',
|
||||
renderTree: require.requireActual('./renderTree').default,
|
||||
renderShallowTree: () => {
|
||||
throw new Error('Shallow renderer is not supported for HTML');
|
||||
},
|
||||
storybook: require.requireActual('@storybook/html'),
|
||||
};
|
||||
}
|
||||
|
||||
export default {
|
||||
load,
|
||||
test,
|
||||
};
|
20
addons/storyshots/src/html/renderTree.js
Normal file
20
addons/storyshots/src/html/renderTree.js
Normal file
@ -0,0 +1,20 @@
|
||||
import { document, Node } from 'global';
|
||||
|
||||
function getRenderedTree(story, context) {
|
||||
const component = story.render(context);
|
||||
|
||||
if (component instanceof Node) {
|
||||
return component;
|
||||
}
|
||||
|
||||
const section = document.createElement('section');
|
||||
section.innerHTML = component;
|
||||
|
||||
if (section.childElementCount > 1) {
|
||||
return section;
|
||||
}
|
||||
|
||||
return section.firstChild;
|
||||
}
|
||||
|
||||
export default getRenderedTree;
|
@ -11,6 +11,7 @@ const noop = () => {};
|
||||
|
||||
const defaultConfig = {
|
||||
storybookUrl: 'http://localhost:6006',
|
||||
chromeExecutablePath: undefined,
|
||||
getMatchOptions: noop,
|
||||
getScreenshotOptions: defaultScreenshotOptions,
|
||||
beforeScreenshot: noop,
|
||||
@ -20,6 +21,7 @@ const defaultConfig = {
|
||||
export const imageSnapshot = (customConfig = {}) => {
|
||||
const {
|
||||
storybookUrl,
|
||||
chromeExecutablePath,
|
||||
getMatchOptions,
|
||||
getScreenshotOptions,
|
||||
beforeScreenshot,
|
||||
@ -70,7 +72,10 @@ export const imageSnapshot = (customConfig = {}) => {
|
||||
testFn.beforeAll = () =>
|
||||
puppeteer
|
||||
// add some options "no-sandbox" to make it work properly on some Linux systems as proposed here: https://github.com/Googlechrome/puppeteer/issues/290#issuecomment-322851507
|
||||
.launch({ args: ['--no-sandbox ', '--disable-setuid-sandbox'] })
|
||||
.launch({
|
||||
args: ['--no-sandbox ', '--disable-setuid-sandbox'],
|
||||
executablePath: chromeExecutablePath,
|
||||
})
|
||||
.then(b => {
|
||||
browser = b;
|
||||
})
|
||||
|
2
app/angular/src/client/preview/render.js
vendored
2
app/angular/src/client/preview/render.js
vendored
@ -1,6 +1,6 @@
|
||||
import { renderNgApp } from './angular/helpers';
|
||||
|
||||
export default function render({ story, showMain }) {
|
||||
renderNgApp(story);
|
||||
showMain();
|
||||
renderNgApp(story);
|
||||
}
|
||||
|
3
app/html/.npmignore
Normal file
3
app/html/.npmignore
Normal file
@ -0,0 +1,3 @@
|
||||
docs
|
||||
src
|
||||
.babelrc
|
26
app/html/README.md
Normal file
26
app/html/README.md
Normal file
@ -0,0 +1,26 @@
|
||||
# Storybook for HTML <sup>alpha</sup>
|
||||
|
||||
* * *
|
||||
|
||||
Storybook for HTML is a UI development environment for your plain HTML snippets.
|
||||
With it, you can visualize different states of your UI components and develop them interactively.
|
||||
|
||||

|
||||
|
||||
Storybook runs outside of your app.
|
||||
So you can develop UI components in isolation without worrying about app specific dependencies and requirements.
|
||||
|
||||
## Getting Started
|
||||
|
||||
```sh
|
||||
npm i -g @storybook/cli
|
||||
cd my-app
|
||||
getstorybook --html
|
||||
```
|
||||
|
||||
For more information visit: [storybook.js.org](https://storybook.js.org)
|
||||
|
||||
* * *
|
||||
|
||||
Storybook also comes with a lot of [addons](https://storybook.js.org/addons/introduction) and a great API to customize as you wish.
|
||||
You can also build a [static version](https://storybook.js.org/basics/exporting-storybook) of your storybook and deploy it anywhere you want.
|
3
app/html/bin/build.js
Executable file
3
app/html/bin/build.js
Executable file
@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
require('../dist/server/build');
|
3
app/html/bin/index.js
Executable file
3
app/html/bin/index.js
Executable file
@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
require('../dist/server');
|
BIN
app/html/docs/demo.png
Normal file
BIN
app/html/docs/demo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 232 KiB |
50
app/html/package.json
Normal file
50
app/html/package.json
Normal file
@ -0,0 +1,50 @@
|
||||
{
|
||||
"name": "@storybook/html",
|
||||
"version": "4.0.0-alpha.4",
|
||||
"description": "Storybook for HTML: View HTML snippets in isolation with Hot Reloading.",
|
||||
"homepage": "https://github.com/storybooks/storybook/tree/master/apps/html",
|
||||
"bugs": {
|
||||
"url": "https://github.com/storybooks/storybook/issues"
|
||||
},
|
||||
"license": "MIT",
|
||||
"main": "dist/client/index.js",
|
||||
"bin": {
|
||||
"build-storybook": "./bin/build.js",
|
||||
"start-storybook": "./bin/index.js",
|
||||
"storybook-server": "./bin/index.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/storybooks/storybook.git"
|
||||
},
|
||||
"scripts": {
|
||||
"prepare": "node ../../scripts/prepare.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/core": "4.0.0-alpha.4",
|
||||
"@storybook/react-dev-utils": "^5.0.0",
|
||||
"airbnb-js-shims": "^1.4.1",
|
||||
"babel-loader": "^7.1.4",
|
||||
"babel-plugin-macros": "^2.2.0",
|
||||
"babel-plugin-transform-regenerator": "^6.26.0",
|
||||
"babel-plugin-transform-runtime": "^6.23.0",
|
||||
"babel-preset-env": "^1.6.0",
|
||||
"babel-preset-minify": "^0.4.0",
|
||||
"babel-preset-stage-0": "^6.24.1",
|
||||
"babel-runtime": "^6.26.0",
|
||||
"case-sensitive-paths-webpack-plugin": "^2.1.2",
|
||||
"common-tags": "^1.4.0",
|
||||
"core-js": "^2.5.5",
|
||||
"dotenv-webpack": "^1.5.5",
|
||||
"global": "^4.3.2",
|
||||
"html-loader": "^0.5.5",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"raw-loader": "^0.5.1",
|
||||
"webpack": "^4.6.0",
|
||||
"webpack-hot-middleware": "^2.22.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"babel-core": "^6.26.0 || ^7.0.0-0",
|
||||
"babel-runtime": ">=6.0.0"
|
||||
}
|
||||
}
|
9
app/html/src/client/index.js
Normal file
9
app/html/src/client/index.js
Normal file
@ -0,0 +1,9 @@
|
||||
export {
|
||||
storiesOf,
|
||||
setAddon,
|
||||
addDecorator,
|
||||
addParameters,
|
||||
configure,
|
||||
getStorybook,
|
||||
forceReRender,
|
||||
} from './preview';
|
17
app/html/src/client/preview/index.js
Normal file
17
app/html/src/client/preview/index.js
Normal file
@ -0,0 +1,17 @@
|
||||
import { start } from '@storybook/core/client';
|
||||
|
||||
import render from './render';
|
||||
|
||||
const { clientApi, configApi, forceReRender } = start(render);
|
||||
|
||||
export const {
|
||||
storiesOf,
|
||||
setAddon,
|
||||
addDecorator,
|
||||
addParameters,
|
||||
clearDecorators,
|
||||
getStorybook,
|
||||
} = clientApi;
|
||||
|
||||
export const { configure } = configApi;
|
||||
export { forceReRender };
|
24
app/html/src/client/preview/render.js
Normal file
24
app/html/src/client/preview/render.js
Normal file
@ -0,0 +1,24 @@
|
||||
import { document, Node } from 'global';
|
||||
import { stripIndents } from 'common-tags';
|
||||
|
||||
const rootElement = document.getElementById('root');
|
||||
|
||||
export default function renderMain({ story, selectedKind, selectedStory, showMain, showError }) {
|
||||
const component = story();
|
||||
|
||||
showMain();
|
||||
if (typeof component === 'string') {
|
||||
rootElement.innerHTML = component;
|
||||
} else if (component instanceof Node) {
|
||||
rootElement.innerHTML = '';
|
||||
rootElement.appendChild(component);
|
||||
} else {
|
||||
showError({
|
||||
title: `Expecting an HTML snippet or DOM node from the story: "${selectedStory}" of "${selectedKind}".`,
|
||||
description: stripIndents`
|
||||
Did you forget to return the HTML snippet from the story?
|
||||
Use "() => <your snippet or node>" or when defining the story.
|
||||
`,
|
||||
});
|
||||
}
|
||||
}
|
12
app/html/src/server/build.js
Executable file
12
app/html/src/server/build.js
Executable file
@ -0,0 +1,12 @@
|
||||
import { buildStatic } from '@storybook/core/server';
|
||||
import path from 'path';
|
||||
import packageJson from '../../package.json';
|
||||
import getBaseConfig from './config/webpack.config.prod';
|
||||
import loadConfig from './config';
|
||||
|
||||
buildStatic({
|
||||
packageJson,
|
||||
getBaseConfig,
|
||||
loadConfig,
|
||||
defaultFavIcon: path.resolve(__dirname, 'public/favicon.ico'),
|
||||
});
|
8
app/html/src/server/config.js
Normal file
8
app/html/src/server/config.js
Normal file
@ -0,0 +1,8 @@
|
||||
import { configLoaderCreator } from '@storybook/core/server';
|
||||
import defaultConfig from './config/babel';
|
||||
|
||||
const configLoader = configLoaderCreator({
|
||||
defaultBabelConfig: defaultConfig,
|
||||
});
|
||||
|
||||
export default configLoader;
|
28
app/html/src/server/config/babel.js
Normal file
28
app/html/src/server/config/babel.js
Normal file
@ -0,0 +1,28 @@
|
||||
module.exports = {
|
||||
// Don't try to find .babelrc because we want to force this configuration.
|
||||
babelrc: false,
|
||||
presets: [
|
||||
[
|
||||
require.resolve('babel-preset-env'),
|
||||
{
|
||||
targets: {
|
||||
browsers: ['last 2 versions', 'safari >= 7'],
|
||||
},
|
||||
modules: process.env.NODE_ENV === 'test' ? 'commonjs' : false,
|
||||
},
|
||||
],
|
||||
require.resolve('babel-preset-stage-0'),
|
||||
],
|
||||
plugins: [
|
||||
require.resolve('babel-plugin-macros'),
|
||||
require.resolve('babel-plugin-transform-regenerator'),
|
||||
[
|
||||
require.resolve('babel-plugin-transform-runtime'),
|
||||
{
|
||||
helpers: true,
|
||||
polyfill: true,
|
||||
regenerator: true,
|
||||
},
|
||||
],
|
||||
],
|
||||
};
|
33
app/html/src/server/config/babel.prod.js
Normal file
33
app/html/src/server/config/babel.prod.js
Normal file
@ -0,0 +1,33 @@
|
||||
module.exports = {
|
||||
// Don't try to find .babelrc because we want to force this configuration.
|
||||
babelrc: false,
|
||||
presets: [
|
||||
[
|
||||
require.resolve('babel-preset-env'),
|
||||
{
|
||||
targets: {
|
||||
browsers: ['last 2 versions', 'safari >= 7'],
|
||||
},
|
||||
modules: false,
|
||||
},
|
||||
],
|
||||
require.resolve('babel-preset-stage-0'),
|
||||
[
|
||||
require.resolve('babel-preset-minify'),
|
||||
{
|
||||
mangle: false,
|
||||
},
|
||||
],
|
||||
],
|
||||
plugins: [
|
||||
require.resolve('babel-plugin-transform-regenerator'),
|
||||
[
|
||||
require.resolve('babel-plugin-transform-runtime'),
|
||||
{
|
||||
helpers: true,
|
||||
polyfill: true,
|
||||
regenerator: true,
|
||||
},
|
||||
],
|
||||
],
|
||||
};
|
4
app/html/src/server/config/globals.js
Normal file
4
app/html/src/server/config/globals.js
Normal file
@ -0,0 +1,4 @@
|
||||
import { window } from 'global';
|
||||
|
||||
window.STORYBOOK_REACT_CLASSES = {};
|
||||
window.STORYBOOK_ENV = 'HTML';
|
3
app/html/src/server/config/polyfills.js
Normal file
3
app/html/src/server/config/polyfills.js
Normal file
@ -0,0 +1,3 @@
|
||||
import 'core-js/es6/symbol';
|
||||
import 'core-js/fn/array/iterator';
|
||||
import 'airbnb-js-shims';
|
35
app/html/src/server/config/utils.js
Normal file
35
app/html/src/server/config/utils.js
Normal file
@ -0,0 +1,35 @@
|
||||
import path from 'path';
|
||||
|
||||
export const includePaths = [path.resolve('./')];
|
||||
|
||||
export const excludePaths = [path.resolve('node_modules')];
|
||||
|
||||
export const nodeModulesPaths = path.resolve('./node_modules');
|
||||
|
||||
export const nodePaths = (process.env.NODE_PATH || '')
|
||||
.split(process.platform === 'win32' ? ';' : ':')
|
||||
.filter(Boolean)
|
||||
.map(p => path.resolve('./', p));
|
||||
|
||||
// Load environment variables starts with STORYBOOK_ to the client side.
|
||||
export function loadEnv(options = {}) {
|
||||
const defaultNodeEnv = options.production ? 'production' : 'development';
|
||||
const env = {
|
||||
NODE_ENV: JSON.stringify(process.env.NODE_ENV || defaultNodeEnv),
|
||||
// This is to support CRA's public folder feature.
|
||||
// In production we set this to dot(.) to allow the browser to access these assests
|
||||
// even when deployed inside a subpath. (like in GitHub pages)
|
||||
// In development this is just empty as we always serves from the root.
|
||||
PUBLIC_URL: JSON.stringify(options.production ? '.' : ''),
|
||||
};
|
||||
|
||||
Object.keys(process.env)
|
||||
.filter(name => /^STORYBOOK_/.test(name))
|
||||
.forEach(name => {
|
||||
env[name] = JSON.stringify(process.env[name]);
|
||||
});
|
||||
|
||||
return {
|
||||
'process.env': env,
|
||||
};
|
||||
}
|
106
app/html/src/server/config/webpack.config.js
Normal file
106
app/html/src/server/config/webpack.config.js
Normal file
@ -0,0 +1,106 @@
|
||||
import path from 'path';
|
||||
import webpack from 'webpack';
|
||||
import Dotenv from 'dotenv-webpack';
|
||||
import InterpolateHtmlPlugin from '@storybook/react-dev-utils/InterpolateHtmlPlugin';
|
||||
import WatchMissingNodeModulesPlugin from '@storybook/react-dev-utils/WatchMissingNodeModulesPlugin';
|
||||
import CaseSensitivePathsPlugin from 'case-sensitive-paths-webpack-plugin';
|
||||
import HtmlWebpackPlugin from 'html-webpack-plugin';
|
||||
import {
|
||||
managerPath,
|
||||
getPreviewHeadHtml,
|
||||
getManagerHeadHtml,
|
||||
indexHtmlPath,
|
||||
iframeHtmlPath,
|
||||
} from '@storybook/core/server';
|
||||
|
||||
import { includePaths, excludePaths, nodeModulesPaths, loadEnv, nodePaths } from './utils';
|
||||
import babelLoaderConfig from './babel';
|
||||
import { version } from '../../../package.json';
|
||||
|
||||
export default function(configDir, quiet) {
|
||||
const config = {
|
||||
mode: 'development',
|
||||
devtool: 'cheap-module-source-map',
|
||||
entry: {
|
||||
manager: [require.resolve('./polyfills'), managerPath],
|
||||
preview: [
|
||||
require.resolve('./polyfills'),
|
||||
require.resolve('./globals'),
|
||||
`${require.resolve('webpack-hot-middleware/client')}?reload=true`,
|
||||
],
|
||||
},
|
||||
output: {
|
||||
path: path.join(__dirname, 'dist'),
|
||||
filename: 'static/[name].bundle.js',
|
||||
publicPath: '/',
|
||||
},
|
||||
plugins: [
|
||||
new HtmlWebpackPlugin({
|
||||
filename: 'index.html',
|
||||
chunks: ['manager'],
|
||||
chunksSortMode: 'none',
|
||||
data: {
|
||||
managerHead: getManagerHeadHtml(configDir),
|
||||
version,
|
||||
},
|
||||
template: indexHtmlPath,
|
||||
}),
|
||||
new HtmlWebpackPlugin({
|
||||
filename: 'iframe.html',
|
||||
excludeChunks: ['manager'],
|
||||
chunksSortMode: 'none',
|
||||
data: {
|
||||
previewHead: getPreviewHeadHtml(configDir),
|
||||
},
|
||||
template: iframeHtmlPath,
|
||||
}),
|
||||
new InterpolateHtmlPlugin(process.env),
|
||||
new webpack.DefinePlugin(loadEnv()),
|
||||
new webpack.HotModuleReplacementPlugin(),
|
||||
new CaseSensitivePathsPlugin(),
|
||||
new WatchMissingNodeModulesPlugin(nodeModulesPaths),
|
||||
quiet ? null : new webpack.ProgressPlugin(),
|
||||
new Dotenv({ silent: true }),
|
||||
].filter(Boolean),
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
loader: require.resolve('babel-loader'),
|
||||
query: babelLoaderConfig,
|
||||
include: includePaths,
|
||||
exclude: excludePaths,
|
||||
},
|
||||
{
|
||||
test: /\.html$/,
|
||||
use: [
|
||||
{
|
||||
loader: require.resolve('html-loader'),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.md$/,
|
||||
use: [
|
||||
{
|
||||
loader: require.resolve('raw-loader'),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
resolve: {
|
||||
// Since we ship with json-loader always, it's better to move extensions to here
|
||||
// from the default config.
|
||||
extensions: ['.js', '.json'],
|
||||
// Add support to NODE_PATH. With this we could avoid relative path imports.
|
||||
// Based on this CRA feature: https://github.com/facebookincubator/create-react-app/issues/253
|
||||
modules: ['node_modules'].concat(nodePaths),
|
||||
},
|
||||
performance: {
|
||||
hints: false,
|
||||
},
|
||||
};
|
||||
|
||||
return config;
|
||||
}
|
108
app/html/src/server/config/webpack.config.prod.js
Normal file
108
app/html/src/server/config/webpack.config.prod.js
Normal file
@ -0,0 +1,108 @@
|
||||
import webpack from 'webpack';
|
||||
import Dotenv from 'dotenv-webpack';
|
||||
import InterpolateHtmlPlugin from '@storybook/react-dev-utils/InterpolateHtmlPlugin';
|
||||
import HtmlWebpackPlugin from 'html-webpack-plugin';
|
||||
import {
|
||||
managerPath,
|
||||
getPreviewHeadHtml,
|
||||
getManagerHeadHtml,
|
||||
indexHtmlPath,
|
||||
iframeHtmlPath,
|
||||
} from '@storybook/core/server';
|
||||
import babelLoaderConfig from './babel.prod';
|
||||
import { includePaths, excludePaths, loadEnv, nodePaths } from './utils';
|
||||
import { version } from '../../../package.json';
|
||||
|
||||
export default function(configDir) {
|
||||
const entries = {
|
||||
preview: [require.resolve('./polyfills'), require.resolve('./globals')],
|
||||
manager: [require.resolve('./polyfills'), managerPath],
|
||||
};
|
||||
|
||||
const config = {
|
||||
mode: 'production',
|
||||
bail: true,
|
||||
devtool: '#cheap-module-source-map',
|
||||
entry: entries,
|
||||
output: {
|
||||
filename: 'static/[name].[chunkhash].bundle.js',
|
||||
// Here we set the publicPath to ''.
|
||||
// This allows us to deploy storybook into subpaths like GitHub pages.
|
||||
// This works with css and image loaders too.
|
||||
// This is working for storybook since, we don't use pushState urls and
|
||||
// relative URLs works always.
|
||||
publicPath: '',
|
||||
},
|
||||
plugins: [
|
||||
new HtmlWebpackPlugin({
|
||||
filename: 'index.html',
|
||||
chunks: ['manager', 'runtime~manager'],
|
||||
chunksSortMode: 'none',
|
||||
data: {
|
||||
managerHead: getManagerHeadHtml(configDir),
|
||||
version,
|
||||
},
|
||||
template: indexHtmlPath,
|
||||
}),
|
||||
new HtmlWebpackPlugin({
|
||||
filename: 'iframe.html',
|
||||
excludeChunks: ['manager', 'runtime~manager'],
|
||||
chunksSortMode: 'none',
|
||||
data: {
|
||||
previewHead: getPreviewHeadHtml(configDir),
|
||||
},
|
||||
template: iframeHtmlPath,
|
||||
}),
|
||||
new InterpolateHtmlPlugin(process.env),
|
||||
new webpack.DefinePlugin(loadEnv({ production: true })),
|
||||
new Dotenv({ silent: true }),
|
||||
],
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
loader: require.resolve('babel-loader'),
|
||||
query: babelLoaderConfig,
|
||||
include: includePaths,
|
||||
exclude: excludePaths,
|
||||
},
|
||||
{
|
||||
test: /\.html$/,
|
||||
use: [
|
||||
{
|
||||
loader: require.resolve('html-loader'),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.md$/,
|
||||
use: [
|
||||
{
|
||||
loader: require.resolve('raw-loader'),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
resolve: {
|
||||
// Since we ship with json-loader always, it's better to move extensions to here
|
||||
// from the default config.
|
||||
extensions: ['.js', '.json'],
|
||||
// Add support to NODE_PATH. With this we could avoid relative path imports.
|
||||
// Based on this CRA feature: https://github.com/facebookincubator/create-react-app/issues/253
|
||||
modules: ['node_modules'].concat(nodePaths),
|
||||
},
|
||||
optimization: {
|
||||
// Automatically split vendor and commons for preview bundle
|
||||
// https://twitter.com/wSokra/status/969633336732905474
|
||||
splitChunks: {
|
||||
chunks: chunk => chunk.name !== 'manager',
|
||||
},
|
||||
// Keep the runtime chunk seperated to enable long term caching
|
||||
// https://twitter.com/wSokra/status/969679223278505985
|
||||
runtimeChunk: true,
|
||||
},
|
||||
};
|
||||
|
||||
return config;
|
||||
}
|
12
app/html/src/server/index.js
Executable file
12
app/html/src/server/index.js
Executable file
@ -0,0 +1,12 @@
|
||||
import { buildDev } from '@storybook/core/server';
|
||||
import path from 'path';
|
||||
import packageJson from '../../package.json';
|
||||
import getBaseConfig from './config/webpack.config';
|
||||
import loadConfig from './config';
|
||||
|
||||
buildDev({
|
||||
packageJson,
|
||||
getBaseConfig,
|
||||
loadConfig,
|
||||
defaultFavIcon: path.resolve(__dirname, 'public/favicon.ico'),
|
||||
});
|
BIN
app/html/src/server/public/favicon.ico
Executable file
BIN
app/html/src/server/public/favicon.ico
Executable file
Binary file not shown.
After Width: | Height: | Size: 5.3 KiB |
@ -21,6 +21,6 @@ export default function renderMain({ story, selectedKind, selectedStory, showMai
|
||||
return;
|
||||
}
|
||||
|
||||
m.mount(rootEl, { view: () => m(element) });
|
||||
showMain();
|
||||
m.mount(rootEl, { view: () => m(element) });
|
||||
}
|
||||
|
@ -9,14 +9,16 @@ export default function renderMain({ story, selectedKind, selectedStory, showMai
|
||||
|
||||
if (!component) {
|
||||
showError({
|
||||
message: `Expecting a Polymer component from the story: "${selectedStory}" of "${selectedKind}".`,
|
||||
stack: stripIndents`
|
||||
title: `Expecting a Polymer component from the story: "${selectedStory}" of "${selectedKind}".`,
|
||||
description: stripIndents`
|
||||
Did you forget to return the Polymer component from the story?
|
||||
Use "() => '<your-component-name></your-component-name\>'" when defining the story.
|
||||
`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
showMain();
|
||||
if (typeof component === 'string') {
|
||||
rootElement.innerHTML = component;
|
||||
} else if (component instanceof TemplateResult) {
|
||||
@ -28,5 +30,4 @@ export default function renderMain({ story, selectedKind, selectedStory, showMai
|
||||
rootElement.innerHTML = '';
|
||||
rootElement.appendChild(component);
|
||||
}
|
||||
showMain();
|
||||
}
|
||||
|
@ -43,6 +43,6 @@ export default function renderMain({ story, selectedKind, selectedStory, showMai
|
||||
// This could leads to issues like below:
|
||||
// https://github.com/storybooks/react-storybook/issues/81
|
||||
ReactDOM.unmountComponentAtNode(rootEl);
|
||||
render(element, rootEl);
|
||||
showMain();
|
||||
render(element, rootEl);
|
||||
}
|
||||
|
@ -23,8 +23,8 @@ export default function render({
|
||||
|
||||
if (!component) {
|
||||
showError({
|
||||
message: `Expecting a Vue component from the story: "${selectedStory}" of "${selectedKind}".`,
|
||||
stack: stripIndents`
|
||||
title: `Expecting a Vue component from the story: "${selectedStory}" of "${selectedKind}".`,
|
||||
description: stripIndents`
|
||||
Did you forget to return the Vue component from the story?
|
||||
Use "() => ({ template: '<my-comp></my-comp>' })" or "() => ({ components: MyComp, template: '<my-comp></my-comp>' })" when defining the story.
|
||||
`,
|
||||
@ -32,11 +32,11 @@ export default function render({
|
||||
return;
|
||||
}
|
||||
|
||||
showMain();
|
||||
renderRoot({
|
||||
el: '#root',
|
||||
render(h) {
|
||||
return h('div', { attrs: { id: 'root' } }, [h(component)]);
|
||||
},
|
||||
});
|
||||
showMain();
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ Storyshots is a way to automatically jest-snapshot all your stories. [More info
|
||||
|
||||
Redirects console output (logs, errors, warnings) into Action Logger Panel. `withConsole` decorator notifies from what stories logs are coming.
|
||||
|
||||
### [Backgrounds](https://github.com/storybooks/storybook/tree/master/addons/background)
|
||||
### [Backgrounds](https://github.com/storybooks/storybook/tree/master/addons/backgrounds)
|
||||
|
||||
With this addon, you can switch between background colors and background images for your preview components. It is really helpful for styleguides.
|
||||
|
||||
|
@ -32,7 +32,7 @@ Now when you are writing a story it like this and add some notes:
|
||||
```js
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import { WithNotes } from '@storybook/addon-notes';
|
||||
import { withNotes } from '@storybook/addon-notes';
|
||||
|
||||
import Button from './Button';
|
||||
|
||||
|
111
docs/src/pages/basics/guide-html/index.md
Normal file
111
docs/src/pages/basics/guide-html/index.md
Normal file
@ -0,0 +1,111 @@
|
||||
---
|
||||
id: 'guide-html'
|
||||
title: 'Storybook for HTML'
|
||||
---
|
||||
|
||||
You may have tried to use our quick start guide to setup your project for Storybook. If you want to set up Storybook manually, this is the guide for you.
|
||||
|
||||
> This will also help you to understand how Storybook works.
|
||||
|
||||
## Starter Guide HTML
|
||||
|
||||
Storybook has its own Webpack setup and a dev server.
|
||||
|
||||
In this guide, we will set up Storybook for your HTML project.
|
||||
|
||||
## Table of contents
|
||||
|
||||
- [Add @storybook/html](#add-storybookhtml)
|
||||
- [Add babel-runtime and babel-core](#add-babel-runtime-and-babel-core)
|
||||
- [Create the config file](#create-the-config-file)
|
||||
- [Write your stories](#write-your-stories)
|
||||
- [Run your Storybook](#run-your-storybook)
|
||||
|
||||
## Add @storybook/html
|
||||
|
||||
First of all, you need to add `@storybook/html` to your project. To do that, simply run:
|
||||
|
||||
```sh
|
||||
npm i --save-dev @storybook/html
|
||||
```
|
||||
|
||||
If you don't have `package.json` in your project, you'll need to init it first:
|
||||
|
||||
```sh
|
||||
npm init
|
||||
```
|
||||
|
||||
## Add babel-runtime and babel-core
|
||||
|
||||
Make sure that you have `babel-runtime` and `babel-core` in your dependencies as well because we list these as a peerDependency:
|
||||
|
||||
```sh
|
||||
npm i --save-dev babel-runtime
|
||||
npm i --save-dev babel-core
|
||||
```
|
||||
|
||||
Then add the following NPM script to your package json in order to start the storybook later in this guide:
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"storybook": "start-storybook -p 9001 -c .storybook"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Create the config file
|
||||
|
||||
Storybook can be configured in several different ways.
|
||||
That’s why we need a config directory. We've added a `-c` option to the above NPM script mentioning `.storybook` as the config directory.
|
||||
|
||||
For the basic Storybook configuration file, you don't need to do much, but simply tell Storybook where to find stories.
|
||||
|
||||
To do that, simply create a file at `.storybook/config.js` with the following content:
|
||||
|
||||
```js
|
||||
import { configure } from '@storybook/html';
|
||||
|
||||
function loadStories() {
|
||||
require('../stories/index.js');
|
||||
// You can require as many stories as you need.
|
||||
}
|
||||
|
||||
configure(loadStories, module);
|
||||
```
|
||||
|
||||
That'll load stories in `../stories/index.js`.
|
||||
|
||||
## Write your stories
|
||||
|
||||
Now you can write some stories inside the `../stories/index.js` file, like this:
|
||||
|
||||
```js
|
||||
/* global document */
|
||||
import { storiesOf } from '@storybook/html';
|
||||
|
||||
storiesOf('Demo', module)
|
||||
.add('heading', () => '<h1>Hello World</h1>')
|
||||
.add('button', () => {
|
||||
const button = document.createElement('button');
|
||||
button.innerText = 'Hello Button';
|
||||
button.addEventListener('click', e => console.log(e));
|
||||
return button;
|
||||
});
|
||||
|
||||
```
|
||||
|
||||
Story is a single HTML snippet or DOM node. In the above case, there are two stories:
|
||||
|
||||
1. heading — an HTML snippet
|
||||
2. button — a DOM node with event listener
|
||||
|
||||
## Run your Storybook
|
||||
|
||||
Now everything is ready. Simply run your storybook with:
|
||||
|
||||
```sh
|
||||
npm run storybook
|
||||
```
|
||||
|
||||
Now you can change components and write stories whenever you need to.
|
@ -12,6 +12,7 @@ title: 'Live Examples'
|
||||
- [Polymer](https://storybooks-polymer.netlify.com/)
|
||||
- [Mithril](https://storybooks-mithril.netlify.com/)
|
||||
- [Marko](https://storybooks-marko.netlify.com/)
|
||||
- [HTML](https://storybooks-html.netlify.com/)
|
||||
|
||||
### 3.4
|
||||
- [React Official](https://release-3-4--storybooks-official.netlify.com)
|
||||
|
@ -13,6 +13,11 @@ getstorybook
|
||||
```
|
||||
The `-g` global install is used to run our cli tool in your project directory to generate templates for your existing projects. To avoid the global install and start your project manually, take a look at our [Slow Start Guide](/basics/slow-start-guide/).
|
||||
|
||||
To install storybook for HTML, add `--html` argument:
|
||||
```
|
||||
getstorybook --html
|
||||
```
|
||||
|
||||
This will configure your app for Storybook. After that, you can run your Storybook with:
|
||||
|
||||
```sh
|
||||
@ -23,6 +28,12 @@ Then you can access your storybook from the browser.
|
||||
|
||||
* * *
|
||||
|
||||
To learn more about what `getstorybook` command does, have a look at our [Start Guide for React](/basics/guide-react/) or [Start Guide for Vue](/basics/guide-vue/) or [Start Guide for Angular](/basics/guide-angular/) or [Start Guide for Mithril](/basics/guide-mithril/).
|
||||
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/)
|
||||
|
||||
|
||||
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).
|
||||
|
@ -10,3 +10,4 @@ Storybook supports multiple UI libraries. The manual setup for each is different
|
||||
- [Storybook for Angular](/basics/guide-angular/)
|
||||
- [Storybook for Mithril](/basics/guide-mithril/)
|
||||
- [Storybook for Marko](/basics/guide-marko/)
|
||||
- [Storybook for HTML](/basics/guide-html/)
|
||||
|
@ -4,39 +4,28 @@ import 'jasmine';
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
describe('AppComponent', () => {
|
||||
beforeEach(
|
||||
async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [AppComponent],
|
||||
}).compileComponents();
|
||||
})
|
||||
);
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [AppComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
it(
|
||||
'should create the app',
|
||||
async(() => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.debugElement.componentInstance;
|
||||
expect(app).toBeTruthy();
|
||||
})
|
||||
);
|
||||
it('should create the app', async(() => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.debugElement.componentInstance;
|
||||
expect(app).toBeTruthy();
|
||||
}));
|
||||
|
||||
it(
|
||||
`should have as title 'app'`,
|
||||
async(() => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.debugElement.componentInstance;
|
||||
expect(app.title).toEqual('app');
|
||||
})
|
||||
);
|
||||
it(`should have as title 'app'`, async(() => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.debugElement.componentInstance;
|
||||
expect(app.title).toEqual('app');
|
||||
}));
|
||||
|
||||
it(
|
||||
'should render title in a h1 tag',
|
||||
async(() => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
fixture.detectChanges();
|
||||
const compiled = fixture.debugElement.nativeElement;
|
||||
expect(compiled.querySelector('h1').textContent).toContain('Welcome to app!');
|
||||
})
|
||||
);
|
||||
it('should render title in a h1 tag', async(() => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
fixture.detectChanges();
|
||||
const compiled = fixture.debugElement.nativeElement;
|
||||
expect(compiled.querySelector('h1').textContent).toContain('Welcome to app!');
|
||||
}));
|
||||
});
|
||||
|
11
examples/html-kitchen-sink/.storybook/addons.js
Normal file
11
examples/html-kitchen-sink/.storybook/addons.js
Normal file
@ -0,0 +1,11 @@
|
||||
import '@storybook/addon-a11y/register';
|
||||
import '@storybook/addon-actions/register';
|
||||
import '@storybook/addon-backgrounds/register';
|
||||
import '@storybook/addon-events/register';
|
||||
import '@storybook/addon-jest/register';
|
||||
import '@storybook/addon-knobs/register';
|
||||
import '@storybook/addon-links/register';
|
||||
import '@storybook/addon-notes/register';
|
||||
import '@storybook/addon-options/register';
|
||||
import '@storybook/addon-storysource/register';
|
||||
import '@storybook/addon-viewport/register';
|
16
examples/html-kitchen-sink/.storybook/config.js
Normal file
16
examples/html-kitchen-sink/.storybook/config.js
Normal file
@ -0,0 +1,16 @@
|
||||
import { configure } from '@storybook/html';
|
||||
import { setOptions } from '@storybook/addon-options';
|
||||
|
||||
setOptions({
|
||||
hierarchyRootSeparator: /\|/,
|
||||
});
|
||||
|
||||
// automatically import all files ending in *.stories.js
|
||||
const req = require.context('../stories', true, /.stories.js$/);
|
||||
function loadStories() {
|
||||
// Make welcome story default
|
||||
require('../stories/index.stories');
|
||||
req.keys().forEach(filename => req(filename));
|
||||
}
|
||||
|
||||
configure(loadStories, module);
|
12
examples/html-kitchen-sink/.storybook/webpack.config.js
Normal file
12
examples/html-kitchen-sink/.storybook/webpack.config.js
Normal file
@ -0,0 +1,12 @@
|
||||
const path = require('path');
|
||||
|
||||
module.exports = (storybookBaseConfig, configType, defaultConfig) => {
|
||||
defaultConfig.module.rules.push({
|
||||
test: [/\.stories\.js$/, /index\.js$/],
|
||||
loaders: [require.resolve('@storybook/addon-storysource/loader')],
|
||||
include: [path.resolve(__dirname, '../stories')],
|
||||
enforce: 'pre',
|
||||
});
|
||||
|
||||
return defaultConfig;
|
||||
};
|
40
examples/html-kitchen-sink/package.json
Normal file
40
examples/html-kitchen-sink/package.json
Normal file
@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "html-kitchen-sink",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"generate-addon-jest-testresults": "jest --config=tests/addon-jest.config.json --json --outputFile=stories/addon-jest.testresults.json",
|
||||
"storybook": "start-storybook -p 9006",
|
||||
"build-storybook": "build-storybook"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"@storybook/addons": "^4.0.0-alpha.3",
|
||||
"@storybook/addon-a11y": "^4.0.0-alpha.3",
|
||||
"@storybook/addon-actions": "^4.0.0-alpha.3",
|
||||
"@storybook/addon-backgrounds": "^4.0.0-alpha.3",
|
||||
"@storybook/addon-centered": "^4.0.0-alpha.3",
|
||||
"@storybook/addon-events": "^4.0.0-alpha.3",
|
||||
"@storybook/addon-jest": "^4.0.0-alpha.3",
|
||||
"@storybook/addon-knobs": "^4.0.0-alpha.3",
|
||||
"@storybook/addon-links": "^4.0.0-alpha.3",
|
||||
"@storybook/addon-notes": "^4.0.0-alpha.3",
|
||||
"@storybook/addon-options": "^4.0.0-alpha.3",
|
||||
"@storybook/addon-storyshots": "^4.0.0-alpha.3",
|
||||
"@storybook/addon-storysource": "^4.0.0-alpha.3",
|
||||
"@storybook/addon-viewport": "^4.0.0-alpha.3",
|
||||
"@storybook/core": "^4.0.0-alpha.3",
|
||||
"@storybook/core-events": "^4.0.0-alpha.3",
|
||||
"@storybook/html": "^4.0.0-alpha.3",
|
||||
"babel-core": "^6.26.0",
|
||||
"babel-runtime": "^6.26.0",
|
||||
"eventemitter3": "^3.1.0",
|
||||
"format-json": "^1.0.3",
|
||||
"global": "^4.3.2",
|
||||
"jest": "^22.4.3"
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Storyshots Addons|a11y Default 1`] = `<button />`;
|
||||
|
||||
exports[`Storyshots Addons|a11y Delayed render 1`] = `<div />`;
|
||||
|
||||
exports[`Storyshots Addons|a11y Disabled 1`] = `
|
||||
<button
|
||||
disabled=""
|
||||
>
|
||||
Testing the a11y addon
|
||||
</button>
|
||||
`;
|
||||
|
||||
exports[`Storyshots Addons|a11y Invalid contrast 1`] = `
|
||||
<button
|
||||
style="color: black; background-color: brown;"
|
||||
>
|
||||
Testing the a11y addon
|
||||
</button>
|
||||
`;
|
||||
|
||||
exports[`Storyshots Addons|a11y Label 1`] = `
|
||||
<button>
|
||||
Testing the a11y addon
|
||||
</button>
|
||||
`;
|
@ -0,0 +1,62 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Storyshots Addons|Actions Decorated actions + config 1`] = `
|
||||
<button
|
||||
type="button"
|
||||
>
|
||||
Hello World
|
||||
</button>
|
||||
`;
|
||||
|
||||
exports[`Storyshots Addons|Actions Decorated actions 1`] = `
|
||||
<button
|
||||
type="button"
|
||||
>
|
||||
Hello World
|
||||
</button>
|
||||
`;
|
||||
|
||||
exports[`Storyshots Addons|Actions Hello World 1`] = `
|
||||
<button
|
||||
type="button"
|
||||
>
|
||||
Hello World
|
||||
</button>
|
||||
`;
|
||||
|
||||
exports[`Storyshots Addons|Actions Multiple actions + config 1`] = `
|
||||
<button
|
||||
type="button"
|
||||
>
|
||||
Hello World
|
||||
</button>
|
||||
`;
|
||||
|
||||
exports[`Storyshots Addons|Actions Multiple actions 1`] = `
|
||||
<button
|
||||
type="button"
|
||||
>
|
||||
Hello World
|
||||
</button>
|
||||
`;
|
||||
|
||||
exports[`Storyshots Addons|Actions Multiple actions, object + config 1`] = `
|
||||
<button
|
||||
type="button"
|
||||
>
|
||||
Hello World
|
||||
</button>
|
||||
`;
|
||||
|
||||
exports[`Storyshots Addons|Actions Multiple actions, object 1`] = `
|
||||
<button
|
||||
type="button"
|
||||
>
|
||||
Hello World
|
||||
</button>
|
||||
`;
|
||||
|
||||
exports[`Storyshots Addons|Actions Multiple actions, selector 1`] = `
|
||||
|
||||
|
||||
`;
|
@ -0,0 +1,17 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Storyshots Addons|Backgrounds story 1 1`] = `
|
||||
<span
|
||||
style="color: white"
|
||||
>
|
||||
You should be able to switch backgrounds for this story
|
||||
</span>
|
||||
`;
|
||||
|
||||
exports[`Storyshots Addons|Backgrounds story 2 1`] = `
|
||||
<span
|
||||
style="color: white"
|
||||
>
|
||||
This one too!
|
||||
</span>
|
||||
`;
|
@ -0,0 +1,17 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Storyshots Addons|Centered button in center 1`] = `
|
||||
<div
|
||||
id="sb-addon-centered-wrapper"
|
||||
style="position: fixed; top: 0px; left: 0px; bottom: 0px; right: 0px; display: flex; overflow: auto;"
|
||||
>
|
||||
<div
|
||||
id="sb-addon-centered-inner"
|
||||
style="margin: auto;"
|
||||
>
|
||||
<button>
|
||||
I am a Button !
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
@ -0,0 +1,6 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Storyshots Addons|Events Logger 1`] = `
|
||||
|
||||
|
||||
`;
|
@ -0,0 +1,3 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Storyshots Addons|jest withTests 1`] = `This story shows test results`;
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user