Skip to content

Commit

Permalink
Merge pull request #3475 from storybooks/html
Browse files Browse the repository at this point in the history
Storybook for HTML snippets
  • Loading branch information
Hypnosphi committed May 7, 2018
2 parents 417ccd6 + 10fa1dc commit e9d423d
Show file tree
Hide file tree
Showing 134 changed files with 2,299 additions and 162 deletions.
2 changes: 1 addition & 1 deletion .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down
34 changes: 17 additions & 17 deletions ADDONS_SUPPORT.md
Original file line number Diff line number Diff line change
@@ -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)|
| ----------- |:-------:|:-------:|:-------:|:-------:|:-------:|:-------:|
|[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)|
| ----------- |:-------:|:-------:|:-------:|:-------:|:-------:|:-------:|:-------:|
|[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) |+| |+|+|+|+|+|
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,9 @@ 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>
- [HTML](app/html) <sup>alpha</sup>

### Sub Projects

Expand All @@ -84,7 +85,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
Expand All @@ -110,6 +111,7 @@ See [Addon / Framework Support Table](ADDONS_SUPPORT.md)
- [Angular](https://storybooks-angular.netlify.com/)
- [Polymer](https://storybooks-polymer.netlify.com/)
- [Mithril](https://storybooks-mithril.netlify.com/)
- [HTML](https://storybooks-html.netlify.com/)

### 3.4
- [React Official](https://release-3-4--storybooks-official.netlify.com)
Expand Down
1 change: 1 addition & 0 deletions addons/a11y/html.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('./dist/html');
2 changes: 2 additions & 0 deletions addons/a11y/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
6 changes: 4 additions & 2 deletions addons/a11y/src/components/Panel.js
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -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 }) {
Expand Down
5 changes: 4 additions & 1 deletion addons/a11y/src/components/Report/index.js
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -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 }) => (
Expand Down
8 changes: 5 additions & 3 deletions addons/a11y/src/components/WrapStory.js
Original file line number Diff line number Diff line change
Expand Up @@ -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({}),
Expand All @@ -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 */
Expand All @@ -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);
}
}

Expand Down
35 changes: 35 additions & 0 deletions addons/a11y/src/html.js
Original file line number Diff line number Diff line change
@@ -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();
};
5 changes: 3 additions & 2 deletions addons/a11y/src/shared/index.js
Original file line number Diff line number Diff line change
@@ -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 };
23 changes: 21 additions & 2 deletions addons/actions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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>)
Expand Down Expand Up @@ -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>
`);
```
2 changes: 2 additions & 0 deletions addons/actions/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
11 changes: 9 additions & 2 deletions addons/actions/src/index.js
Original file line number Diff line number Diff line change
@@ -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 };
19 changes: 11 additions & 8 deletions addons/actions/src/preview/decorateAction.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import action from './action';
import actions from './actions';
import { createDecorator } from './withActions';

function applyDecorators(decorators, actionCallback) {
return (..._args) => {
Expand All @@ -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),
};
}
1 change: 1 addition & 0 deletions addons/actions/src/preview/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 64 additions & 0 deletions addons/actions/src/preview/withActions.js
Original file line number Diff line number Diff line change
@@ -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);
File renamed without changes.
1 change: 1 addition & 0 deletions addons/backgrounds/html.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('./dist/html');
File renamed without changes.

0 comments on commit e9d423d

Please sign in to comment.