-
{m(storyFn())}
+
),
};
diff --git a/addons/centered/src/react.js b/addons/centered/src/react.js
index f95d2a10fccb..58c380d940dc 100644
--- a/addons/centered/src/react.js
+++ b/addons/centered/src/react.js
@@ -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 (
-
-
{storyFn()}
+
);
}
diff --git a/addons/centered/src/styles.js b/addons/centered/src/styles.js
new file mode 100644
index 000000000000..911b33e980a6
--- /dev/null
+++ b/addons/centered/src/styles.js
@@ -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;
diff --git a/addons/centered/src/vue.js b/addons/centered/src/vue.js
index 713e7dfeb1de..177a4cdaf5a4 100644
--- a/addons/centered/src/vue.js
+++ b/addons/centered/src/vue.js
@@ -1,3 +1,5 @@
+import styles from './styles';
+
export default function() {
return {
template: `
@@ -8,22 +10,7 @@ export default function() {
`,
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;
},
};
}
diff --git a/addons/events/html.js b/addons/events/html.js
new file mode 100644
index 000000000000..4f7edc6bedba
--- /dev/null
+++ b/addons/events/html.js
@@ -0,0 +1 @@
+module.exports = require('./dist/html');
diff --git a/addons/events/package.json b/addons/events/package.json
index fc7431d3b1e2..f637c44f75da 100644
--- a/addons/events/package.json
+++ b/addons/events/package.json
@@ -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",
diff --git a/addons/events/src/html.js b/addons/events/src/html.js
new file mode 100644
index 000000000000..a7090b809c99
--- /dev/null
+++ b/addons/events/src/html.js
@@ -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();
+};
diff --git a/addons/knobs/html.js b/addons/knobs/html.js
new file mode 100644
index 000000000000..4f7edc6bedba
--- /dev/null
+++ b/addons/knobs/html.js
@@ -0,0 +1 @@
+module.exports = require('./dist/html');
diff --git a/addons/knobs/package.json b/addons/knobs/package.json
index 8d755e6e7a96..00d46a898a4a 100644
--- a/addons/knobs/package.json
+++ b/addons/knobs/package.json
@@ -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",
diff --git a/addons/knobs/src/html/index.js b/addons/knobs/src/html/index.js
new file mode 100644
index 000000000000..a032f2979ea0
--- /dev/null
+++ b/addons/knobs/src/html/index.js
@@ -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 });
diff --git a/addons/knobs/src/html/registerKnobs.js b/addons/knobs/src/html/registerKnobs.js
new file mode 100644
index 000000000000..b654e0fe4bf7
--- /dev/null
+++ b/addons/knobs/src/html/registerKnobs.js
@@ -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;
diff --git a/addons/links/README.md b/addons/links/README.md
index 4fd95ec68c50..e3e7c057b0be 100644
--- a/addons/links/README.md
+++ b/addons/links/README.md
@@ -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', () => (
+
+ ))
+```
+
## 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.
diff --git a/addons/links/package.json b/addons/links/package.json
index 4f586d31ef96..e3d81abb6c87 100644
--- a/addons/links/package.json
+++ b/addons/links/package.json
@@ -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"
diff --git a/addons/links/src/index.js b/addons/links/src/index.js
index 1f8a4f52daf6..d5c82d4b3d4f 100644
--- a/addons/links/src/index.js
+++ b/addons/links/src/index.js
@@ -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;
diff --git a/addons/links/src/preview.js b/addons/links/src/preview.js
index bdb0558293b2..547a1759316d 100644
--- a/addons/links/src/preview.js
+++ b/addons/links/src/preview.js
@@ -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();
+};
diff --git a/addons/storyshots/src/frameworkLoader.js b/addons/storyshots/src/frameworkLoader.js
index 047759ec3a98..0a8cdd51df07 100644
--- a/addons/storyshots/src/frameworkLoader.js
+++ b/addons/storyshots/src/frameworkLoader.js
@@ -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));
diff --git a/addons/storyshots/src/html/loader.js b/addons/storyshots/src/html/loader.js
new file mode 100644
index 000000000000..634cc8395762
--- /dev/null
+++ b/addons/storyshots/src/html/loader.js
@@ -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,
+};
diff --git a/addons/storyshots/src/html/renderTree.js b/addons/storyshots/src/html/renderTree.js
new file mode 100644
index 000000000000..6f02f83ef253
--- /dev/null
+++ b/addons/storyshots/src/html/renderTree.js
@@ -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;
diff --git a/app/html/.npmignore b/app/html/.npmignore
new file mode 100644
index 000000000000..329fc8d67ad9
--- /dev/null
+++ b/app/html/.npmignore
@@ -0,0 +1,3 @@
+docs
+src
+.babelrc
diff --git a/app/html/README.md b/app/html/README.md
new file mode 100644
index 000000000000..9e09fedf8c95
--- /dev/null
+++ b/app/html/README.md
@@ -0,0 +1,26 @@
+# Storybook for HTML
alpha
+
+* * *
+
+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 Screenshot](https://github.com/storybooks/storybook/blob/master/app/html/docs/demo.png)
+
+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.
diff --git a/app/html/bin/build.js b/app/html/bin/build.js
new file mode 100755
index 000000000000..780773c6cd31
--- /dev/null
+++ b/app/html/bin/build.js
@@ -0,0 +1,3 @@
+#!/usr/bin/env node
+
+require('../dist/server/build');
diff --git a/app/html/bin/index.js b/app/html/bin/index.js
new file mode 100755
index 000000000000..2e96258ce63d
--- /dev/null
+++ b/app/html/bin/index.js
@@ -0,0 +1,3 @@
+#!/usr/bin/env node
+
+require('../dist/server');
diff --git a/app/html/docs/demo.png b/app/html/docs/demo.png
new file mode 100644
index 000000000000..4bb13aa62974
Binary files /dev/null and b/app/html/docs/demo.png differ
diff --git a/app/html/package.json b/app/html/package.json
new file mode 100644
index 000000000000..5ba09f7c5f27
--- /dev/null
+++ b/app/html/package.json
@@ -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"
+ }
+}
diff --git a/app/html/src/client/index.js b/app/html/src/client/index.js
new file mode 100644
index 000000000000..88fb416f70ef
--- /dev/null
+++ b/app/html/src/client/index.js
@@ -0,0 +1,9 @@
+export {
+ storiesOf,
+ setAddon,
+ addDecorator,
+ addParameters,
+ configure,
+ getStorybook,
+ forceReRender,
+} from './preview';
diff --git a/app/html/src/client/preview/index.js b/app/html/src/client/preview/index.js
new file mode 100644
index 000000000000..b04f8439b607
--- /dev/null
+++ b/app/html/src/client/preview/index.js
@@ -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 };
diff --git a/app/html/src/client/preview/render.js b/app/html/src/client/preview/render.js
new file mode 100644
index 000000000000..75484ac511d5
--- /dev/null
+++ b/app/html/src/client/preview/render.js
@@ -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 "() =>
" or when defining the story.
+ `,
+ });
+ }
+}
diff --git a/app/html/src/server/build.js b/app/html/src/server/build.js
new file mode 100755
index 000000000000..804f258a0ba4
--- /dev/null
+++ b/app/html/src/server/build.js
@@ -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'),
+});
diff --git a/app/html/src/server/config.js b/app/html/src/server/config.js
new file mode 100644
index 000000000000..a031a5a47121
--- /dev/null
+++ b/app/html/src/server/config.js
@@ -0,0 +1,8 @@
+import { configLoaderCreator } from '@storybook/core/server';
+import defaultConfig from './config/babel';
+
+const configLoader = configLoaderCreator({
+ defaultBabelConfig: defaultConfig,
+});
+
+export default configLoader;
diff --git a/app/html/src/server/config/babel.js b/app/html/src/server/config/babel.js
new file mode 100644
index 000000000000..77a413a7cb48
--- /dev/null
+++ b/app/html/src/server/config/babel.js
@@ -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,
+ },
+ ],
+ ],
+};
diff --git a/app/html/src/server/config/babel.prod.js b/app/html/src/server/config/babel.prod.js
new file mode 100644
index 000000000000..5ceaf68bae9f
--- /dev/null
+++ b/app/html/src/server/config/babel.prod.js
@@ -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,
+ },
+ ],
+ ],
+};
diff --git a/app/html/src/server/config/globals.js b/app/html/src/server/config/globals.js
new file mode 100644
index 000000000000..d889a4529761
--- /dev/null
+++ b/app/html/src/server/config/globals.js
@@ -0,0 +1,4 @@
+import { window } from 'global';
+
+window.STORYBOOK_REACT_CLASSES = {};
+window.STORYBOOK_ENV = 'HTML';
diff --git a/app/html/src/server/config/polyfills.js b/app/html/src/server/config/polyfills.js
new file mode 100644
index 000000000000..869b6824b5ff
--- /dev/null
+++ b/app/html/src/server/config/polyfills.js
@@ -0,0 +1,3 @@
+import 'core-js/es6/symbol';
+import 'core-js/fn/array/iterator';
+import 'airbnb-js-shims';
diff --git a/app/html/src/server/config/utils.js b/app/html/src/server/config/utils.js
new file mode 100644
index 000000000000..fc73c38a37f0
--- /dev/null
+++ b/app/html/src/server/config/utils.js
@@ -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,
+ };
+}
diff --git a/app/html/src/server/config/webpack.config.js b/app/html/src/server/config/webpack.config.js
new file mode 100644
index 000000000000..c572bcba82d1
--- /dev/null
+++ b/app/html/src/server/config/webpack.config.js
@@ -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;
+}
diff --git a/app/html/src/server/config/webpack.config.prod.js b/app/html/src/server/config/webpack.config.prod.js
new file mode 100644
index 000000000000..8ae4ce9421d8
--- /dev/null
+++ b/app/html/src/server/config/webpack.config.prod.js
@@ -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;
+}
diff --git a/app/html/src/server/index.js b/app/html/src/server/index.js
new file mode 100755
index 000000000000..69df1fe59e54
--- /dev/null
+++ b/app/html/src/server/index.js
@@ -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'),
+});
diff --git a/app/html/src/server/public/favicon.ico b/app/html/src/server/public/favicon.ico
new file mode 100755
index 000000000000..e1cf7f1c59fd
Binary files /dev/null and b/app/html/src/server/public/favicon.ico differ
diff --git a/app/polymer/src/client/preview/render.js b/app/polymer/src/client/preview/render.js
index 201966de513b..6ed2e5a9bce3 100644
--- a/app/polymer/src/client/preview/render.js
+++ b/app/polymer/src/client/preview/render.js
@@ -9,8 +9,8 @@ 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.
`,
diff --git a/app/vue/src/client/preview/render.js b/app/vue/src/client/preview/render.js
index 7c865949b656..f6a4dbf09183 100644
--- a/app/vue/src/client/preview/render.js
+++ b/app/vue/src/client/preview/render.js
@@ -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: '' })" or "() => ({ components: MyComp, template: '' })" when defining the story.
`,
diff --git a/docs/src/pages/addons/addon-gallery/index.md b/docs/src/pages/addons/addon-gallery/index.md
index e6d5db59ae19..0ee37d0b6a84 100644
--- a/docs/src/pages/addons/addon-gallery/index.md
+++ b/docs/src/pages/addons/addon-gallery/index.md
@@ -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.
diff --git a/docs/src/pages/basics/guide-html/index.md b/docs/src/pages/basics/guide-html/index.md
new file mode 100644
index 000000000000..398dc67624ca
--- /dev/null
+++ b/docs/src/pages/basics/guide-html/index.md
@@ -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', () => 'Hello World
')
+ .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.
diff --git a/docs/src/pages/basics/live-examples/index.md b/docs/src/pages/basics/live-examples/index.md
index 425630295b10..f23d9e276e3a 100644
--- a/docs/src/pages/basics/live-examples/index.md
+++ b/docs/src/pages/basics/live-examples/index.md
@@ -11,6 +11,7 @@ title: 'Live Examples'
- [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)
diff --git a/docs/src/pages/basics/quick-start-guide/index.md b/docs/src/pages/basics/quick-start-guide/index.md
index c1d889380cfb..68f4b11f10ac 100644
--- a/docs/src/pages/basics/quick-start-guide/index.md
+++ b/docs/src/pages/basics/quick-start-guide/index.md
@@ -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).
diff --git a/docs/src/pages/basics/slow-start-guide/index.md b/docs/src/pages/basics/slow-start-guide/index.md
index 46370c1f2782..80b9c32946c1 100644
--- a/docs/src/pages/basics/slow-start-guide/index.md
+++ b/docs/src/pages/basics/slow-start-guide/index.md
@@ -9,3 +9,4 @@ Storybook supports multiple UI libraries. The manual setup for each is different
- [Storybook for Vue](/basics/guide-vue/)
- [Storybook for Angular](/basics/guide-angular/)
- [Storybook for Mithril](/basics/guide-mithril/)
+- [Storybook for HTML](/basics/guide-html/)
diff --git a/examples/angular-cli/src/app/app.component.spec.ts b/examples/angular-cli/src/app/app.component.spec.ts
index 32ab9b0638a1..f22ed943cf7f 100644
--- a/examples/angular-cli/src/app/app.component.spec.ts
+++ b/examples/angular-cli/src/app/app.component.spec.ts
@@ -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!');
+ }));
});
diff --git a/examples/html-kitchen-sink/.storybook/addons.js b/examples/html-kitchen-sink/.storybook/addons.js
new file mode 100644
index 000000000000..c38a31c6ea86
--- /dev/null
+++ b/examples/html-kitchen-sink/.storybook/addons.js
@@ -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';
diff --git a/examples/html-kitchen-sink/.storybook/config.js b/examples/html-kitchen-sink/.storybook/config.js
new file mode 100644
index 000000000000..d67bc8b54626
--- /dev/null
+++ b/examples/html-kitchen-sink/.storybook/config.js
@@ -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);
diff --git a/examples/html-kitchen-sink/.storybook/webpack.config.js b/examples/html-kitchen-sink/.storybook/webpack.config.js
new file mode 100644
index 000000000000..5d9aaf4e1dad
--- /dev/null
+++ b/examples/html-kitchen-sink/.storybook/webpack.config.js
@@ -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;
+};
diff --git a/examples/html-kitchen-sink/package.json b/examples/html-kitchen-sink/package.json
new file mode 100644
index 000000000000..78b303d5c0f6
--- /dev/null
+++ b/examples/html-kitchen-sink/package.json
@@ -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"
+ }
+}
diff --git a/examples/html-kitchen-sink/stories/__snapshots__/addon-a11y.stories.storyshot b/examples/html-kitchen-sink/stories/__snapshots__/addon-a11y.stories.storyshot
new file mode 100644
index 000000000000..8c979f07574a
--- /dev/null
+++ b/examples/html-kitchen-sink/stories/__snapshots__/addon-a11y.stories.storyshot
@@ -0,0 +1,27 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Storyshots Addons|a11y Default 1`] = ``;
+
+exports[`Storyshots Addons|a11y Delayed render 1`] = ``;
+
+exports[`Storyshots Addons|a11y Disabled 1`] = `
+
+`;
+
+exports[`Storyshots Addons|a11y Invalid contrast 1`] = `
+
+`;
+
+exports[`Storyshots Addons|a11y Label 1`] = `
+
+`;
diff --git a/examples/html-kitchen-sink/stories/__snapshots__/addon-actions.stories.storyshot b/examples/html-kitchen-sink/stories/__snapshots__/addon-actions.stories.storyshot
new file mode 100644
index 000000000000..b5a33580d65d
--- /dev/null
+++ b/examples/html-kitchen-sink/stories/__snapshots__/addon-actions.stories.storyshot
@@ -0,0 +1,62 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Storyshots Addons|Actions Decorated actions + config 1`] = `
+
+`;
+
+exports[`Storyshots Addons|Actions Decorated actions 1`] = `
+
+`;
+
+exports[`Storyshots Addons|Actions Hello World 1`] = `
+
+`;
+
+exports[`Storyshots Addons|Actions Multiple actions + config 1`] = `
+
+`;
+
+exports[`Storyshots Addons|Actions Multiple actions 1`] = `
+
+`;
+
+exports[`Storyshots Addons|Actions Multiple actions, object + config 1`] = `
+
+`;
+
+exports[`Storyshots Addons|Actions Multiple actions, object 1`] = `
+
+`;
+
+exports[`Storyshots Addons|Actions Multiple actions, selector 1`] = `
+
+
+`;
diff --git a/examples/html-kitchen-sink/stories/__snapshots__/addon-backgrounds.stories.storyshot b/examples/html-kitchen-sink/stories/__snapshots__/addon-backgrounds.stories.storyshot
new file mode 100644
index 000000000000..211e0b453450
--- /dev/null
+++ b/examples/html-kitchen-sink/stories/__snapshots__/addon-backgrounds.stories.storyshot
@@ -0,0 +1,17 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Storyshots Addons|Backgrounds story 1 1`] = `
+
+ You should be able to switch backgrounds for this story
+
+`;
+
+exports[`Storyshots Addons|Backgrounds story 2 1`] = `
+
+ This one too!
+
+`;
diff --git a/examples/html-kitchen-sink/stories/__snapshots__/addon-centered.stories.storyshot b/examples/html-kitchen-sink/stories/__snapshots__/addon-centered.stories.storyshot
new file mode 100644
index 000000000000..177b0ea3a16e
--- /dev/null
+++ b/examples/html-kitchen-sink/stories/__snapshots__/addon-centered.stories.storyshot
@@ -0,0 +1,17 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Storyshots Addons|Centered button in center 1`] = `
+
+
+
+
+
+`;
diff --git a/examples/html-kitchen-sink/stories/__snapshots__/addon-events.stories.storyshot b/examples/html-kitchen-sink/stories/__snapshots__/addon-events.stories.storyshot
new file mode 100644
index 000000000000..84466dd440ae
--- /dev/null
+++ b/examples/html-kitchen-sink/stories/__snapshots__/addon-events.stories.storyshot
@@ -0,0 +1,6 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Storyshots Addons|Events Logger 1`] = `
+
+
+`;
diff --git a/examples/html-kitchen-sink/stories/__snapshots__/addon-jest.stories.storyshot b/examples/html-kitchen-sink/stories/__snapshots__/addon-jest.stories.storyshot
new file mode 100644
index 000000000000..30260c566cae
--- /dev/null
+++ b/examples/html-kitchen-sink/stories/__snapshots__/addon-jest.stories.storyshot
@@ -0,0 +1,3 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Storyshots Addons|jest withTests 1`] = `This story shows test results`;
diff --git a/examples/html-kitchen-sink/stories/__snapshots__/addon-knobs.stories.storyshot b/examples/html-kitchen-sink/stories/__snapshots__/addon-knobs.stories.storyshot
new file mode 100644
index 000000000000..73f9636e4b14
--- /dev/null
+++ b/examples/html-kitchen-sink/stories/__snapshots__/addon-knobs.stories.storyshot
@@ -0,0 +1,54 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Storyshots Addons|Knobs All knobs 1`] = `
+
+
+
+
+ My name is Jane,
+
+
+
+
+ today is January 20, 2017
+
+
+
+
+ I have a stock of 20 apple, costing $2.25 each.
+
+
+
+
+ Also, I have:
+
+
+
+
+ -
+ Laptop
+
+ -
+ Book
+
+ -
+ Whiskey
+
+
+
+
+
+ Nice to meet you!
+
+
+
+
+`;
+
+exports[`Storyshots Addons|Knobs Simple 1`] = `
+
+ I am John Doe and I'm 44 years old.
+
+`;
diff --git a/examples/html-kitchen-sink/stories/__snapshots__/addon-notes.stories.storyshot b/examples/html-kitchen-sink/stories/__snapshots__/addon-notes.stories.storyshot
new file mode 100644
index 000000000000..b7b58fa7badb
--- /dev/null
+++ b/examples/html-kitchen-sink/stories/__snapshots__/addon-notes.stories.storyshot
@@ -0,0 +1,15 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Storyshots Addons|Notes Simple note 1`] = `
+
+
+
+
+
+ This is a fragment of HTML
+
+
+
+
+
+`;
diff --git a/examples/html-kitchen-sink/stories/__snapshots__/index.stories.storyshot b/examples/html-kitchen-sink/stories/__snapshots__/index.stories.storyshot
new file mode 100644
index 000000000000..9fc155940224
--- /dev/null
+++ b/examples/html-kitchen-sink/stories/__snapshots__/index.stories.storyshot
@@ -0,0 +1,130 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Storyshots Demo button 1`] = `
+
+`;
+
+exports[`Storyshots Demo heading 1`] = `
+
+ Hello World
+
+`;
+
+exports[`Storyshots Demo headings 1`] = `
+
+
+ Hello World
+
+
+ Hello World
+
+
+ Hello World
+
+
+ Hello World
+
+
+`;
+
+exports[`Storyshots Welcome Welcome 1`] = `
+
+
+
+
+ Welcome to Storybook for HTML
+
+
+
+
+ This is a UI component dev environment for your plain HTML snippets.
+
+
+
+
+
+ We've added some basic stories inside the
+
+ stories
+
+ directory.
+
+
+
+ A story is a single state of one or more UI components. You can have as many stories as you want.
+
+
+
+ (Basically a story is like a visual test case.)
+
+
+
+
+
+
+ See these sample
+
+ stories
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Just like that, you can add your own snippets as stories.
+
+
+
+ You can also edit those snippets and see changes right away.
+
+
+
+
+
+
+
+
+
+ Usually we create stories with smaller UI components in the app.
+
+
+ Have a look at the
+
+
+
+ Writing Stories
+
+
+
+ section in our documentation.
+
+
+
+
+
+`;
diff --git a/examples/html-kitchen-sink/stories/addon-a11y.stories.js b/examples/html-kitchen-sink/stories/addon-a11y.stories.js
new file mode 100644
index 000000000000..7341d03e8a6a
--- /dev/null
+++ b/examples/html-kitchen-sink/stories/addon-a11y.stories.js
@@ -0,0 +1,28 @@
+import { document, setTimeout } from 'global';
+import { storiesOf } from '@storybook/html';
+import { setOptions } from '@storybook/addon-options';
+
+import { checkA11y } from '@storybook/addon-a11y/html';
+
+const text = 'Testing the a11y addon';
+
+storiesOf('Addons|a11y', module)
+ .addDecorator(checkA11y)
+ .addDecorator(fn => {
+ setOptions({ selectedAddonPanel: '@storybook/addon-a11y/panel' });
+ return fn();
+ })
+ .add('Default', () => ``)
+ .add('Label', () => ``)
+ .add('Disabled', () => ``)
+ .add(
+ 'Invalid contrast',
+ () => ``
+ )
+ .add('Delayed render', () => {
+ const div = document.createElement('div');
+ setTimeout(() => {
+ div.innerHTML = ``;
+ }, 1000);
+ return div;
+ });
diff --git a/examples/html-kitchen-sink/stories/addon-actions.stories.js b/examples/html-kitchen-sink/stories/addon-actions.stories.js
new file mode 100644
index 000000000000..09f606d58dd4
--- /dev/null
+++ b/examples/html-kitchen-sink/stories/addon-actions.stories.js
@@ -0,0 +1,34 @@
+import { storiesOf } from '@storybook/html';
+import { withActions, decorate } from '@storybook/addon-actions';
+
+const pickTarget = decorate([args => [args[0].target]]);
+
+const button = () => ``;
+
+storiesOf('Addons|Actions', module)
+ .add('Hello World', () => withActions('click')(button))
+ .add('Multiple actions', () => withActions('click', 'contextmenu')(button))
+ .add('Multiple actions + config', () =>
+ withActions('click', 'contextmenu', { clearOnStoryChange: false })(button)
+ )
+ .add('Multiple actions, object', () =>
+ withActions({ click: 'clicked', contextmenu: 'right clicked' })(button)
+ )
+ .add('Multiple actions, selector', () =>
+ withActions({ 'click .btn': 'clicked', contextmenu: 'right clicked' })(
+ () => `
+
+ Clicks on this button will be logged:
+
+ `
+ )
+ )
+ .add('Multiple actions, object + config', () =>
+ withActions({ click: 'clicked', contextmenu: 'right clicked' }, { clearOnStoryChange: false })(
+ button
+ )
+ )
+ .add('Decorated actions', () => pickTarget.withActions('click', 'contextmenu')(button))
+ .add('Decorated actions + config', () =>
+ pickTarget.withActions('click', 'contextmenu', { clearOnStoryChange: false })(button)
+ );
diff --git a/examples/html-kitchen-sink/stories/addon-backgrounds.stories.js b/examples/html-kitchen-sink/stories/addon-backgrounds.stories.js
new file mode 100644
index 000000000000..1c8eed6ac6dc
--- /dev/null
+++ b/examples/html-kitchen-sink/stories/addon-backgrounds.stories.js
@@ -0,0 +1,17 @@
+import { storiesOf } from '@storybook/html';
+
+import backgrounds from '@storybook/addon-backgrounds/html';
+
+storiesOf('Addons|Backgrounds', module)
+ .addDecorator(
+ backgrounds([
+ { name: 'twitter', value: '#00aced' },
+ { name: 'facebook', value: '#3b5998', default: true },
+ ])
+ )
+ .add(
+ 'story 1',
+ () =>
+ 'You should be able to switch backgrounds for this story'
+ )
+ .add('story 2', () => 'This one too!');
diff --git a/examples/html-kitchen-sink/stories/addon-centered.stories.js b/examples/html-kitchen-sink/stories/addon-centered.stories.js
new file mode 100644
index 000000000000..49edcdfb4821
--- /dev/null
+++ b/examples/html-kitchen-sink/stories/addon-centered.stories.js
@@ -0,0 +1,6 @@
+import { storiesOf } from '@storybook/html';
+import centered from '@storybook/addon-centered/html';
+
+storiesOf('Addons|Centered', module)
+ .addDecorator(centered)
+ .add('button in center', () => '');
diff --git a/examples/html-kitchen-sink/stories/addon-events.css b/examples/html-kitchen-sink/stories/addon-events.css
new file mode 100644
index 000000000000..1346e8b132c3
--- /dev/null
+++ b/examples/html-kitchen-sink/stories/addon-events.css
@@ -0,0 +1,16 @@
+.wrapper {
+ padding: 20px;
+ font-family:
+ -apple-system, ".SFNSText-Regular", "San Francisco", "Roboto",
+ "Segoe UI", "Helvetica Neue", "Lucida Grande", sans-serif;
+ color: rgb(51, 51, 51);
+}
+
+.title {
+ margin: 0;
+}
+
+.item {
+ list-style: none;
+ margin-bottom: 10px;
+}
diff --git a/examples/html-kitchen-sink/stories/addon-events.stories.js b/examples/html-kitchen-sink/stories/addon-events.stories.js
new file mode 100644
index 000000000000..f0be1bee0a0e
--- /dev/null
+++ b/examples/html-kitchen-sink/stories/addon-events.stories.js
@@ -0,0 +1,115 @@
+import EventEmitter from 'eventemitter3';
+import { storiesOf } from '@storybook/html';
+import addons from '@storybook/addons';
+import CoreEvents from '@storybook/core-events';
+import json from 'format-json';
+
+import withEvents from '@storybook/addon-events/html';
+
+import './addon-events.css';
+
+const TEST_EVENTS = {
+ TEST_EVENT_1: 'test-event-1',
+ TEST_EVENT_2: 'test-event-2',
+ TEST_EVENT_3: 'test-event-3',
+ TEST_EVENT_4: 'test-event-4',
+};
+
+const emitter = new EventEmitter();
+const emit = emitter.emit.bind(emitter);
+
+const events = [];
+const eventHandlers = Object.values(TEST_EVENTS).map(name => ({
+ name,
+ handler: payload => {
+ events.push({ name, payload });
+ addons.getChannel().emit(CoreEvents.FORCE_RE_RENDER);
+ },
+}));
+
+const subscription = () => {
+ eventHandlers.forEach(({ name, handler }) => emitter.on(name, handler));
+ return () => eventHandlers.forEach(({ name, handler }) => emitter.removeListener(name, handler));
+};
+
+storiesOf('Addons|Events', module)
+ .addDecorator(
+ withEvents({
+ emit,
+ events: [
+ {
+ name: TEST_EVENTS.TEST_EVENT_1,
+ title: 'Test event 1',
+ payload: 0,
+ },
+ {
+ name: TEST_EVENTS.TEST_EVENT_2,
+ title: 'Test event 2',
+ payload: 'Test event 2',
+ },
+ {
+ name: TEST_EVENTS.TEST_EVENT_3,
+ title: 'Test event 3',
+ payload: {
+ string: 'value',
+ number: 123,
+ array: [1, 2, 3],
+ object: {
+ string: 'value',
+ number: 123,
+ array: [1, 2, 3],
+ },
+ },
+ },
+ {
+ name: TEST_EVENTS.TEST_EVENT_4,
+ title: 'Test event 4',
+ payload: [
+ {
+ string: 'value',
+ number: 123,
+ array: [1, 2, 3],
+ },
+ {
+ string: 'value',
+ number: 123,
+ array: [1, 2, 3],
+ },
+ {
+ string: 'value',
+ number: 123,
+ array: [1, 2, 3],
+ },
+ ],
+ },
+ ],
+ })
+ )
+ .addDecorator(story => {
+ addons.getChannel().emit(CoreEvents.REGISTER_SUBSCRIPTION, subscription);
+ return story();
+ })
+ .add(
+ 'Logger',
+ () => `
+
+
Logger
+
+ ${events
+ .map(
+ ({ name, payload }) => `
+
+
-
+ Event name: ${name}
+
+ -
+ Event payload: ${json.plain(payload)}
+
+
+ `
+ )
+ .join('')}
+
+
+ `
+ );
diff --git a/examples/html-kitchen-sink/stories/addon-jest.stories.js b/examples/html-kitchen-sink/stories/addon-jest.stories.js
new file mode 100644
index 000000000000..543cc5642312
--- /dev/null
+++ b/examples/html-kitchen-sink/stories/addon-jest.stories.js
@@ -0,0 +1,12 @@
+import { storiesOf } from '@storybook/html';
+
+import { withTests } from '@storybook/addon-jest';
+import results from './addon-jest.testresults.json';
+
+const withTestsFiles = withTests({
+ results,
+});
+
+storiesOf('Addons|jest', module)
+ .addDecorator(withTestsFiles('addon-jest'))
+ .add('withTests', () => 'This story shows test results');
diff --git a/examples/html-kitchen-sink/stories/addon-jest.testresults.json b/examples/html-kitchen-sink/stories/addon-jest.testresults.json
new file mode 100644
index 000000000000..14cb9a48d08f
--- /dev/null
+++ b/examples/html-kitchen-sink/stories/addon-jest.testresults.json
@@ -0,0 +1 @@
+{"numFailedTestSuites":1,"numFailedTests":4,"numPassedTestSuites":0,"numPassedTests":3,"numPendingTestSuites":0,"numPendingTests":0,"numRuntimeErrorTestSuites":0,"numTotalTestSuites":1,"numTotalTests":7,"snapshot":{"added":0,"didUpdate":false,"failure":false,"filesAdded":0,"filesRemoved":0,"filesUnmatched":0,"filesUpdated":0,"matched":0,"total":0,"unchecked":0,"uncheckedKeys":[],"unmatched":0,"updated":0},"startTime":1525471117521,"success":false,"testResults":[{"assertionResults":[{"ancestorTitles":[],"failureMessages":[],"fullName":"true should be true","location":null,"status":"passed","title":"true should be true"},{"ancestorTitles":["In a describe: "],"failureMessages":[],"fullName":"In a describe: true should still be true","location":null,"status":"passed","title":"true should still be true"},{"ancestorTitles":["In a describe: "],"failureMessages":[],"fullName":"In a describe: a list should contain 3 items","location":null,"status":"passed","title":"a list should contain 3 items"},{"ancestorTitles":["In a describe: "],"failureMessages":["Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).toEqual(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected value to equal:\n \u001b[32m\"everything is awesome\"\u001b[39m\nReceived:\n \u001b[31m\"everything is all right\"\u001b[39m\n at Object. (/Users/jetbrains/IdeaProjects/storybook/examples/official-storybook/tests/addon-jest.test.js:16:39)\n at Object.asyncFn (/Users/jetbrains/IdeaProjects/storybook/node_modules/jest-jasmine2/build/jasmine_async.js:82:37)\n at resolve (/Users/jetbrains/IdeaProjects/storybook/node_modules/jest-jasmine2/build/queue_runner.js:52:12)\n at new Promise ()\n at mapper (/Users/jetbrains/IdeaProjects/storybook/node_modules/jest-jasmine2/build/queue_runner.js:39:19)\n at promise.then (/Users/jetbrains/IdeaProjects/storybook/node_modules/jest-jasmine2/build/queue_runner.js:73:82)\n at \n at process._tickCallback (internal/process/next_tick.js:188:7)"],"fullName":"In a describe: everything is awesome","location":null,"status":"failed","title":"everything is awesome"},{"ancestorTitles":["A bunch of failing tests: "],"failureMessages":["Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).toBe(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected value to be:\n \u001b[32mfalse\u001b[39m\nReceived:\n \u001b[31mtrue\u001b[39m\n at Object. (/Users/jetbrains/IdeaProjects/storybook/examples/official-storybook/tests/addon-jest.test.js:22:18)\n at Object.asyncFn (/Users/jetbrains/IdeaProjects/storybook/node_modules/jest-jasmine2/build/jasmine_async.js:82:37)\n at resolve (/Users/jetbrains/IdeaProjects/storybook/node_modules/jest-jasmine2/build/queue_runner.js:52:12)\n at new Promise ()\n at mapper (/Users/jetbrains/IdeaProjects/storybook/node_modules/jest-jasmine2/build/queue_runner.js:39:19)\n at promise.then (/Users/jetbrains/IdeaProjects/storybook/node_modules/jest-jasmine2/build/queue_runner.js:73:82)\n at \n at process._tickCallback (internal/process/next_tick.js:188:7)"],"fullName":"A bunch of failing tests: true should still be true","location":null,"status":"failed","title":"true should still be true"},{"ancestorTitles":["A bunch of failing tests: "],"failureMessages":["Error: \u001b[2mexpect(\u001b[22m\u001b[31marray\u001b[39m\u001b[2m).toContain(\u001b[22m\u001b[32mvalue\u001b[39m\u001b[2m)\u001b[22m\n\nExpected array:\n \u001b[31m[\"a\", \"b\", \"3\"]\u001b[39m\nTo contain value:\n \u001b[32m301\u001b[39m\n at Object. (/Users/jetbrains/IdeaProjects/storybook/examples/official-storybook/tests/addon-jest.test.js:26:29)\n at Object.asyncFn (/Users/jetbrains/IdeaProjects/storybook/node_modules/jest-jasmine2/build/jasmine_async.js:82:37)\n at resolve (/Users/jetbrains/IdeaProjects/storybook/node_modules/jest-jasmine2/build/queue_runner.js:52:12)\n at new Promise ()\n at mapper (/Users/jetbrains/IdeaProjects/storybook/node_modules/jest-jasmine2/build/queue_runner.js:39:19)\n at promise.then (/Users/jetbrains/IdeaProjects/storybook/node_modules/jest-jasmine2/build/queue_runner.js:73:82)\n at \n at process._tickCallback (internal/process/next_tick.js:188:7)"],"fullName":"A bunch of failing tests: a list should contain 3 items","location":null,"status":"failed","title":"a list should contain 3 items"},{"ancestorTitles":["A bunch of failing tests: "],"failureMessages":["Error: \u001b[2mexpect(\u001b[22m\u001b[31mfunction\u001b[39m\u001b[2m).toThrow(\u001b[22m\u001b[32mundefined\u001b[39m\u001b[2m)\u001b[22m\n\nExpected the function to throw an error.\nBut it didn't throw anything.\n at Object. (/Users/jetbrains/IdeaProjects/storybook/examples/official-storybook/tests/addon-jest.test.js:30:28)\n at Object.asyncFn (/Users/jetbrains/IdeaProjects/storybook/node_modules/jest-jasmine2/build/jasmine_async.js:82:37)\n at resolve (/Users/jetbrains/IdeaProjects/storybook/node_modules/jest-jasmine2/build/queue_runner.js:52:12)\n at new Promise ()\n at mapper (/Users/jetbrains/IdeaProjects/storybook/node_modules/jest-jasmine2/build/queue_runner.js:39:19)\n at promise.then (/Users/jetbrains/IdeaProjects/storybook/node_modules/jest-jasmine2/build/queue_runner.js:73:82)\n at \n at process._tickCallback (internal/process/next_tick.js:188:7)"],"fullName":"A bunch of failing tests: should work","location":null,"status":"failed","title":"should work"}],"endTime":1525471122009,"message":"\u001b[1m\u001b[31m \u001b[1m● \u001b[1mIn a describe: › everything is awesome\u001b[39m\u001b[22m\n\n \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).toEqual(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n \n Expected value to equal:\n \u001b[32m\"everything is awesome\"\u001b[39m\n Received:\n \u001b[31m\"everything is all right\"\u001b[39m\n\u001b[2m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 14 | \u001b[39m\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 15 | \u001b[39m test(\u001b[32m'everything is awesome'\u001b[39m\u001b[33m,\u001b[39m () \u001b[33m=>\u001b[39m {\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m\u001b[31m\u001b[1m>\u001b[2m\u001b[39m\u001b[90m 16 | \u001b[39m expect(\u001b[32m'everything is all right'\u001b[39m)\u001b[33m.\u001b[39mtoEqual(\u001b[32m'everything is awesome'\u001b[39m)\u001b[33m;\u001b[39m\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 17 | \u001b[39m })\u001b[33m;\u001b[39m\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 18 | \u001b[39m})\u001b[33m;\u001b[39m\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 19 | \u001b[39m\u001b[0m\u001b[22m\n\u001b[2m \u001b[22m\n\u001b[2m \u001b[2mat Object. (\u001b[2m\u001b[0m\u001b[36mexamples/official-storybook/tests/addon-jest.test.js\u001b[39m\u001b[0m\u001b[2m:16:39)\u001b[2m\u001b[22m\n\n\u001b[1m\u001b[31m \u001b[1m● \u001b[1mA bunch of failing tests: › true should still be true\u001b[39m\u001b[22m\n\n \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).toBe(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n \n Expected value to be:\n \u001b[32mfalse\u001b[39m\n Received:\n \u001b[31mtrue\u001b[39m\n\u001b[2m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 20 | \u001b[39mdescribe(\u001b[32m'A bunch of failing tests: '\u001b[39m\u001b[33m,\u001b[39m () \u001b[33m=>\u001b[39m {\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 21 | \u001b[39m test(\u001b[32m'true should still be true'\u001b[39m\u001b[33m,\u001b[39m () \u001b[33m=>\u001b[39m {\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m\u001b[31m\u001b[1m>\u001b[2m\u001b[39m\u001b[90m 22 | \u001b[39m expect(\u001b[36mtrue\u001b[39m)\u001b[33m.\u001b[39mtoBe(\u001b[36mfalse\u001b[39m)\u001b[33m;\u001b[39m\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 23 | \u001b[39m })\u001b[33m;\u001b[39m\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 24 | \u001b[39m\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 25 | \u001b[39m test(\u001b[32m'a list should contain 3 items'\u001b[39m\u001b[33m,\u001b[39m () \u001b[33m=>\u001b[39m {\u001b[0m\u001b[22m\n\u001b[2m \u001b[22m\n\u001b[2m \u001b[2mat Object. (\u001b[2m\u001b[0m\u001b[36mexamples/official-storybook/tests/addon-jest.test.js\u001b[39m\u001b[0m\u001b[2m:22:18)\u001b[2m\u001b[22m\n\n\u001b[1m\u001b[31m \u001b[1m● \u001b[1mA bunch of failing tests: › a list should contain 3 items\u001b[39m\u001b[22m\n\n \u001b[2mexpect(\u001b[22m\u001b[31marray\u001b[39m\u001b[2m).toContain(\u001b[22m\u001b[32mvalue\u001b[39m\u001b[2m)\u001b[22m\n \n Expected array:\n \u001b[31m[\"a\", \"b\", \"3\"]\u001b[39m\n To contain value:\n \u001b[32m301\u001b[39m\n\u001b[2m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 24 | \u001b[39m\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 25 | \u001b[39m test(\u001b[32m'a list should contain 3 items'\u001b[39m\u001b[33m,\u001b[39m () \u001b[33m=>\u001b[39m {\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m\u001b[31m\u001b[1m>\u001b[2m\u001b[39m\u001b[90m 26 | \u001b[39m expect([\u001b[32m'a'\u001b[39m\u001b[33m,\u001b[39m \u001b[32m'b'\u001b[39m\u001b[33m,\u001b[39m \u001b[32m'3'\u001b[39m])\u001b[33m.\u001b[39mtoContain(\u001b[35m301\u001b[39m)\u001b[33m;\u001b[39m\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 27 | \u001b[39m })\u001b[33m;\u001b[39m\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 28 | \u001b[39m\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 29 | \u001b[39m test(\u001b[32m'should work'\u001b[39m\u001b[33m,\u001b[39m () \u001b[33m=>\u001b[39m {\u001b[0m\u001b[22m\n\u001b[2m \u001b[22m\n\u001b[2m \u001b[2mat Object. (\u001b[2m\u001b[0m\u001b[36mexamples/official-storybook/tests/addon-jest.test.js\u001b[39m\u001b[0m\u001b[2m:26:29)\u001b[2m\u001b[22m\n\n\u001b[1m\u001b[31m \u001b[1m● \u001b[1mA bunch of failing tests: › should work\u001b[39m\u001b[22m\n\n \u001b[2mexpect(\u001b[22m\u001b[31mfunction\u001b[39m\u001b[2m).toThrow(\u001b[22m\u001b[32mundefined\u001b[39m\u001b[2m)\u001b[22m\n \n Expected the function to throw an error.\n But it didn't throw anything.\n\u001b[2m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 28 | \u001b[39m\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 29 | \u001b[39m test(\u001b[32m'should work'\u001b[39m\u001b[33m,\u001b[39m () \u001b[33m=>\u001b[39m {\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m\u001b[31m\u001b[1m>\u001b[2m\u001b[39m\u001b[90m 30 | \u001b[39m expect(() \u001b[33m=>\u001b[39m {})\u001b[33m.\u001b[39mtoThrow()\u001b[33m;\u001b[39m\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 31 | \u001b[39m })\u001b[33m;\u001b[39m\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 32 | \u001b[39m})\u001b[33m;\u001b[39m\u001b[0m\u001b[22m\n\u001b[2m \u001b[0m \u001b[90m 33 | \u001b[39m\u001b[0m\u001b[22m\n\u001b[2m \u001b[22m\n\u001b[2m \u001b[2mat Object. (\u001b[2m\u001b[0m\u001b[36mexamples/official-storybook/tests/addon-jest.test.js\u001b[39m\u001b[0m\u001b[2m:30:28)\u001b[2m\u001b[22m\n","name":"/Users/jetbrains/IdeaProjects/storybook/examples/official-storybook/tests/addon-jest.test.js","startTime":1525471118647,"status":"failed","summary":""}],"wasInterrupted":false}
\ No newline at end of file
diff --git a/examples/html-kitchen-sink/stories/addon-knobs.stories.js b/examples/html-kitchen-sink/stories/addon-knobs.stories.js
new file mode 100644
index 000000000000..406ba3acf184
--- /dev/null
+++ b/examples/html-kitchen-sink/stories/addon-knobs.stories.js
@@ -0,0 +1,66 @@
+import { storiesOf } from '@storybook/html';
+import { action } from '@storybook/addon-actions';
+
+import {
+ array,
+ boolean,
+ button,
+ color,
+ date,
+ select,
+ withKnobs,
+ text,
+ number,
+} from '@storybook/addon-knobs/html';
+
+storiesOf('Addons|Knobs', module)
+ .addDecorator(withKnobs)
+ .add('Simple', () => {
+ const name = text('Name', 'John Doe');
+ const age = number('Age', 44);
+ const content = `I am ${name} and I'm ${age} years old.`;
+
+ return `${content}
`;
+ })
+ .add('All knobs', () => {
+ const name = text('Name', 'Jane');
+ const stock = number('Stock', 20, {
+ range: true,
+ min: 0,
+ max: 30,
+ step: 5,
+ });
+ const fruits = {
+ apples: 'Apple',
+ bananas: 'Banana',
+ cherries: 'Cherry',
+ };
+ const fruit = select('Fruit', fruits, 'apple');
+ const price = number('Price', 2.25);
+
+ const colour = color('Border', 'deeppink');
+ const today = date('Today', new Date('Jan 20 2017 GMT+0'));
+ const items = array('Items', ['Laptop', 'Book', 'Whiskey']);
+ const nice = boolean('Nice', true);
+
+ const stockMessage = stock
+ ? `I have a stock of ${stock} ${fruit}, costing $${price} each.`
+ : `I'm out of ${fruit}${nice ? ', Sorry!' : '.'}`;
+
+ const salutation = nice ? 'Nice to meet you!' : 'Leave me alone!';
+ const dateOptions = { year: 'numeric', month: 'long', day: 'numeric', timeZone: 'UTC' };
+
+ button('Arbitrary action', action('You clicked it!'));
+
+ const style = `border: 2px dotted ${colour}; padding: 8px 22px; border-radius: 8px`;
+
+ return `
+
My name is ${name},
+
today is ${new Date(today).toLocaleDateString('en-US', dateOptions)}
+
${stockMessage}
+
Also, I have:
+
${items.map(item => `- ${item}
`).join('')}
+
${salutation}
+
+ `;
+ });
diff --git a/examples/html-kitchen-sink/stories/addon-notes.stories.js b/examples/html-kitchen-sink/stories/addon-notes.stories.js
new file mode 100644
index 000000000000..a02ff0246d80
--- /dev/null
+++ b/examples/html-kitchen-sink/stories/addon-notes.stories.js
@@ -0,0 +1,17 @@
+import { storiesOf } from '@storybook/html';
+import { withNotes } from '@storybook/addon-notes';
+
+storiesOf('Addons|Notes', module)
+ .addDecorator(withNotes)
+ .add(
+ 'Simple note',
+ () =>
+ `
+
+ This is a fragment of HTML
+
+
`,
+ {
+ notes: 'My notes on some bold text',
+ }
+ );
diff --git a/examples/html-kitchen-sink/stories/index.stories.js b/examples/html-kitchen-sink/stories/index.stories.js
new file mode 100644
index 000000000000..1dcedbdb3522
--- /dev/null
+++ b/examples/html-kitchen-sink/stories/index.stories.js
@@ -0,0 +1,24 @@
+import { document } from 'global';
+import { storiesOf } from '@storybook/html';
+import { action } from '@storybook/addon-actions';
+import { withLinks } from '@storybook/addon-links';
+
+import './welcome.css';
+import welcome from './welcome.html';
+
+storiesOf('Welcome', module)
+ .addDecorator(withLinks)
+ .add('Welcome', () => welcome);
+
+storiesOf('Demo', module)
+ .add('heading', () => 'Hello World
')
+ .add(
+ 'headings',
+ () => 'Hello World
Hello World
Hello World
Hello World
'
+ )
+ .add('button', () => {
+ const button = document.createElement('button');
+ button.innerHTML = 'Hello Button';
+ button.addEventListener('click', action('Click'));
+ return button;
+ });
diff --git a/examples/html-kitchen-sink/stories/logo.svg b/examples/html-kitchen-sink/stories/logo.svg
new file mode 100644
index 000000000000..0171e29ca41e
--- /dev/null
+++ b/examples/html-kitchen-sink/stories/logo.svg
@@ -0,0 +1,14 @@
+
diff --git a/examples/html-kitchen-sink/stories/welcome.css b/examples/html-kitchen-sink/stories/welcome.css
new file mode 100644
index 000000000000..ea42a4983159
--- /dev/null
+++ b/examples/html-kitchen-sink/stories/welcome.css
@@ -0,0 +1,21 @@
+.main {
+ margin: 15px;
+ max-width: 600px;
+ line-height: 1.4;
+ font-family: "Helvetica Neue", Helvetica, "Segoe UI", Arial, freesans, sans-serif;
+}
+
+.logo {
+ width: 256px;
+ margin: 15px;
+}
+
+.code {
+ font-size: 15px;
+ font-weight: 600;
+ padding: 2px 5px;
+ border: 1px solid #eae9e9;
+ border-radius: 4px;
+ background-color: #f3f2f2;
+ color: #3a3a3a;
+}
diff --git a/examples/html-kitchen-sink/stories/welcome.html b/examples/html-kitchen-sink/stories/welcome.html
new file mode 100644
index 000000000000..af62ae262a1a
--- /dev/null
+++ b/examples/html-kitchen-sink/stories/welcome.html
@@ -0,0 +1,29 @@
+
+
Welcome to Storybook for HTML
+
This is a UI component dev environment for your plain HTML snippets.
+
+ We've added some basic stories inside the stories
directory.
+
+ A story is a single state of one or more UI components. You can have as many stories as you want.
+
+ (Basically a story is like a visual test case.)
+
+
+ See these sample stories
+
+
+
+ Just like that, you can add your own snippets as stories.
+
+ You can also edit those snippets and see changes right away.
+
+
+
+ Usually we create stories with smaller UI components in the app.
+ Have a look at the
+
+ Writing Stories
+
+ section in our documentation.
+
+
diff --git a/examples/html-kitchen-sink/tests/addon-jest.config.json b/examples/html-kitchen-sink/tests/addon-jest.config.json
new file mode 100644
index 000000000000..31526d712636
--- /dev/null
+++ b/examples/html-kitchen-sink/tests/addon-jest.config.json
@@ -0,0 +1,10 @@
+{
+ "rootDir": "../../..",
+ "verbose": true,
+ "notify": true,
+ "transform": {
+ ".*": "babel-jest"
+ },
+ "setupTestFrameworkScriptFile": "/scripts/jest.init.js",
+ "testMatch": ["/examples/official-storybook/tests/addon-jest.test.js"]
+}
diff --git a/examples/html-kitchen-sink/tests/addon-jest.test.js b/examples/html-kitchen-sink/tests/addon-jest.test.js
new file mode 100644
index 000000000000..8236a32e390f
--- /dev/null
+++ b/examples/html-kitchen-sink/tests/addon-jest.test.js
@@ -0,0 +1,32 @@
+// This file is excluded from the `/scripts/test.js` script. (see root `jest.config.js` file)
+test('true should be true', () => {
+ expect(true).toBe(true);
+});
+
+describe('In a describe: ', () => {
+ test('true should still be true', () => {
+ expect(true).toBe(true);
+ });
+
+ test('a list should contain 3 items', () => {
+ expect(['a', 'b', '3']).toHaveLength(3);
+ });
+
+ test('everything is awesome', () => {
+ expect('everything is all right').toEqual('everything is awesome');
+ });
+});
+
+describe('A bunch of failing tests: ', () => {
+ test('true should still be true', () => {
+ expect(true).toBe(false);
+ });
+
+ test('a list should contain 3 items', () => {
+ expect(['a', 'b', '3']).toContain(301);
+ });
+
+ test('should work', () => {
+ expect(() => {}).toThrow();
+ });
+});
diff --git a/examples/html-kitchen-sink/tests/htmlshots.test.js b/examples/html-kitchen-sink/tests/htmlshots.test.js
new file mode 100644
index 000000000000..b682c5c74b24
--- /dev/null
+++ b/examples/html-kitchen-sink/tests/htmlshots.test.js
@@ -0,0 +1,9 @@
+import path from 'path';
+import initStoryshots, { multiSnapshotWithOptions } from '@storybook/addon-storyshots';
+
+initStoryshots({
+ framework: 'html',
+ integrityOptions: { cwd: path.resolve(__dirname, '../stories') },
+ configPath: path.resolve(__dirname, '../.storybook'),
+ test: multiSnapshotWithOptions({}),
+});
diff --git a/examples/official-storybook/built-storybooks/html-kitchen-sink b/examples/official-storybook/built-storybooks/html-kitchen-sink
new file mode 120000
index 000000000000..09907fc0f899
--- /dev/null
+++ b/examples/official-storybook/built-storybooks/html-kitchen-sink
@@ -0,0 +1 @@
+../../html-kitchen-sink/storybook-static/
\ No newline at end of file
diff --git a/examples/official-storybook/stories/__snapshots__/app-acceptance.stories.storyshot b/examples/official-storybook/stories/__snapshots__/app-acceptance.stories.storyshot
index cb80d4613fb5..b21bba9770d8 100644
--- a/examples/official-storybook/stories/__snapshots__/app-acceptance.stories.storyshot
+++ b/examples/official-storybook/stories/__snapshots__/app-acceptance.stories.storyshot
@@ -16,6 +16,14 @@ exports[`Storyshots App|acceptance cra-kitchen-sink 1`] = `
/>
`;
+exports[`Storyshots App|acceptance html-kitchen-sink 1`] = `
+
+`;
+
exports[`Storyshots App|acceptance mithril-kitchen-sink 1`] = `