mirror of
https://github.com/storybookjs/storybook.git
synced 2025-03-21 05:02:39 +08:00
Merge branch 'master' into update-dev-deps
This commit is contained in:
commit
5058250b4e
@ -35,6 +35,7 @@ jobs:
|
||||
- node_modules
|
||||
- examples/angular-cli/node_modules
|
||||
- examples/cra-kitchen-sink/node_modules
|
||||
- examples/mithril-kitchen-sink/node_modules
|
||||
- examples/official-storybook/node_modules
|
||||
- examples/polymer-cli/node_modules
|
||||
- examples/vue-kitchen-sink/node_modules
|
||||
@ -100,6 +101,11 @@ jobs:
|
||||
command: |
|
||||
cd examples/official-storybook
|
||||
yarn build-storybook
|
||||
- run:
|
||||
name: "Build mithril kitchen-sink"
|
||||
command: |
|
||||
cd examples/mithril-kitchen-sink
|
||||
yarn build-storybook
|
||||
- run:
|
||||
name: "Visually test storybook"
|
||||
command: |
|
||||
@ -148,6 +154,11 @@ jobs:
|
||||
command: |
|
||||
cd examples/official-storybook
|
||||
yarn storybook --smoke-test
|
||||
- run:
|
||||
name: "Run mithril kitchen-sink (smoke test)"
|
||||
command: |
|
||||
cd examples/mithril-kitchen-sink
|
||||
yarn storybook --smoke-test
|
||||
react-native:
|
||||
<<: *defaults
|
||||
steps:
|
||||
|
1
.github/autolabeler.yml
vendored
1
.github/autolabeler.yml
vendored
@ -17,6 +17,7 @@
|
||||
'app: react-native': ["app/react-native/**"]
|
||||
'app: react': ["app/react/**"]
|
||||
'app: vue': ["app/vue/**"]
|
||||
'app: mithril': ["app/mithril/**"]
|
||||
'babel / webpack': ["webpack", "babel"]
|
||||
'cli': ["lib/cli/**"]
|
||||
'compatibility with other tools': []
|
||||
|
@ -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)|
|
||||
| ----------- |:-------:|:-------:|:-------:|:-------:|:-------:|
|
||||
|[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)|
|
||||
| ----------- |:-------:|:-------:|:-------:|:-------:|:-------:|:-------:|
|
||||
|[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) |+| |+|+|+|+|
|
||||
|
@ -72,6 +72,7 @@ For additional help, join us [in our Slack](https://now-examples-slackin-rrirkqo
|
||||
- [Vue](app/vue)
|
||||
- [Angular](app/angular)
|
||||
- [Polymer](app/polymer) <sup>alpha</sup>
|
||||
- [Mithril](app/mithril) <sup>alpha</sup>
|
||||
|
||||
### Sub Projects
|
||||
|
||||
@ -107,6 +108,7 @@ See [Addon / Framework Support Table](ADDONS_SUPPORT.md)
|
||||
- [Vue](https://storybooks-vue.netlify.com/)
|
||||
- [Angular](https://storybooks-angular.netlify.com/)
|
||||
- [Polymer](https://storybooks-polymer.netlify.com/)
|
||||
- [Mithril](https://storybooks-mithril.netlify.com/)
|
||||
|
||||
### 3.4
|
||||
- [React Official](https://release-3-4--storybooks-official.netlify.com)
|
||||
|
@ -69,3 +69,10 @@ storiesOf("Button", module)
|
||||
.addDecorator(backgrounds)
|
||||
.add("with text", () => <button>Click me</button>);
|
||||
```
|
||||
|
||||
> In the case of Mithril, use these imports:
|
||||
>
|
||||
> ```js
|
||||
> import { storiesOf } from '@storybook/mithril';
|
||||
> import backgrounds from "@storybook/addon-backgrounds/mithril";
|
||||
> ```
|
||||
|
1
addons/background/mithril.js
Normal file
1
addons/background/mithril.js
Normal file
@ -0,0 +1 @@
|
||||
module.exports = require('./dist/mithril');
|
39
addons/background/src/mithril.js
Normal file
39
addons/background/src/mithril.js
Normal file
@ -0,0 +1,39 @@
|
||||
/** @jsx m */
|
||||
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import m from 'mithril';
|
||||
|
||||
import addons from '@storybook/addons';
|
||||
|
||||
export class BackgroundDecorator {
|
||||
constructor(vnode) {
|
||||
this.props = vnode.attrs;
|
||||
|
||||
const { channel, story } = vnode.attrs;
|
||||
|
||||
// A channel is explicitly passed in for testing
|
||||
if (channel) {
|
||||
this.channel = channel;
|
||||
} else {
|
||||
this.channel = addons.getChannel();
|
||||
}
|
||||
|
||||
this.story = story();
|
||||
}
|
||||
|
||||
oncreate() {
|
||||
this.channel.emit('background-set', this.props.backgrounds);
|
||||
}
|
||||
|
||||
onremove() {
|
||||
this.channel.emit('background-unset');
|
||||
}
|
||||
|
||||
view() {
|
||||
return m(this.story);
|
||||
}
|
||||
}
|
||||
|
||||
export default backgrounds => story => ({
|
||||
view: () => <BackgroundDecorator story={story} backgrounds={backgrounds} />,
|
||||
});
|
@ -49,11 +49,29 @@ storiesOf('MyComponent', module)
|
||||
.add('without props', () => ({
|
||||
components: { MyComponent },
|
||||
template: '<my-component />'
|
||||
})
|
||||
}))
|
||||
.add('with some props', () => ({
|
||||
components: { MyComponent },
|
||||
template: '<my-component text="The Comp"/>'
|
||||
});
|
||||
}));
|
||||
```
|
||||
|
||||
example for Mithril:
|
||||
|
||||
```js
|
||||
import { storiesOf } from '@storybook/mithril';
|
||||
import centered from '@storybook/addon-centered/mithril';
|
||||
|
||||
import MyComponent from '../Component';
|
||||
|
||||
storiesOf('MyComponent', module)
|
||||
.addDecorator(centered)
|
||||
.add('without props', () => ({
|
||||
view: () => <MyComponent />
|
||||
}))
|
||||
.add('with some props', () => ({
|
||||
view: () => <MyComponent text="The Comp"/>
|
||||
}));
|
||||
```
|
||||
|
||||
Also, you can also add this decorator globally
|
||||
@ -84,6 +102,19 @@ configure(function () {
|
||||
}, module);
|
||||
```
|
||||
|
||||
example for Mithril:
|
||||
|
||||
```js
|
||||
import { configure, addDecorator } from '@storybook/mithril';
|
||||
import centered from '@storybook/addon-centered/mithril';
|
||||
|
||||
addDecorator(centered);
|
||||
|
||||
configure(function () {
|
||||
//...
|
||||
}, module);
|
||||
```
|
||||
|
||||
#### As an extension
|
||||
|
||||
##### 1 - Configure the extension
|
||||
|
1
addons/centered/mithril.js
Normal file
1
addons/centered/mithril.js
Normal file
@ -0,0 +1 @@
|
||||
module.exports = require('./dist/mithril');
|
30
addons/centered/src/mithril.js
Normal file
30
addons/centered/src/mithril.js
Normal file
@ -0,0 +1,30 @@
|
||||
/** @jsx m */
|
||||
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import m from 'mithril';
|
||||
|
||||
const style = {
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
overflow: 'auto',
|
||||
};
|
||||
|
||||
const innerStyle = {
|
||||
margin: 'auto',
|
||||
};
|
||||
|
||||
export default function(storyFn) {
|
||||
return {
|
||||
view: () => (
|
||||
<div style={style}>
|
||||
<div style={innerStyle}>{m(storyFn())}</div>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
}
|
@ -118,6 +118,13 @@ stories.add('as dynamic variables', () => {
|
||||
> import { storiesOf } from '@storybook/angular';
|
||||
> import { withKnobs, text, boolean, number } from '@storybook/addon-knobs/angular';
|
||||
> ```
|
||||
>
|
||||
> In the case of Mithril, use these imports:
|
||||
>
|
||||
> ```js
|
||||
> import { storiesOf } from '@storybook/mithril';
|
||||
> import { withKnobs, text, boolean, number } from '@storybook/addon-knobs/mithril';
|
||||
> ```
|
||||
|
||||
You can see your Knobs in a Storybook panel as shown below.
|
||||
|
||||
|
1
addons/knobs/mithril.js
Normal file
1
addons/knobs/mithril.js
Normal file
@ -0,0 +1 @@
|
||||
module.exports = require('./dist/mithril');
|
68
addons/knobs/src/mithril/WrapStory.js
Normal file
68
addons/knobs/src/mithril/WrapStory.js
Normal file
@ -0,0 +1,68 @@
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import m from 'mithril';
|
||||
|
||||
export default class WrapStory {
|
||||
constructor(vnode) {
|
||||
this.knobChanged = this.knobChanged.bind(this);
|
||||
this.knobClicked = this.knobClicked.bind(this);
|
||||
this.resetKnobs = this.resetKnobs.bind(this);
|
||||
this.setPaneKnobs = this.setPaneKnobs.bind(this);
|
||||
this.props = vnode.attrs;
|
||||
this.storyContent = vnode.attrs.initialContent;
|
||||
}
|
||||
|
||||
oncreate() {
|
||||
// Watch for changes in knob editor.
|
||||
this.props.channel.on('addon:knobs:knobChange', this.knobChanged);
|
||||
// Watch for clicks in knob editor.
|
||||
this.props.channel.on('addon:knobs:knobClick', this.knobClicked);
|
||||
// Watch for the reset event and reset knobs.
|
||||
this.props.channel.on('addon:knobs:reset', this.resetKnobs);
|
||||
// Watch for any change in the knobStore and set the panel again for those
|
||||
// changes.
|
||||
this.props.knobStore.subscribe(this.setPaneKnobs);
|
||||
// Set knobs in the panel for the first time.
|
||||
this.setPaneKnobs();
|
||||
}
|
||||
|
||||
onremove() {
|
||||
this.props.channel.removeListener('addon:knobs:knobChange', this.knobChanged);
|
||||
this.props.channel.removeListener('addon:knobs:knobClick', this.knobClicked);
|
||||
this.props.channel.removeListener('addon:knobs:reset', this.resetKnobs);
|
||||
this.props.knobStore.unsubscribe(this.setPaneKnobs);
|
||||
}
|
||||
|
||||
setPaneKnobs(timestamp = +new Date()) {
|
||||
const { channel, knobStore } = this.props;
|
||||
channel.emit('addon:knobs:setKnobs', { knobs: knobStore.getAll(), timestamp });
|
||||
}
|
||||
|
||||
knobChanged(change) {
|
||||
const { name, value } = change;
|
||||
const { knobStore, storyFn, context } = this.props;
|
||||
// Update the related knob and it's value.
|
||||
const knobOptions = knobStore.get(name);
|
||||
|
||||
knobOptions.value = value;
|
||||
knobStore.markAllUnused();
|
||||
this.storyContent = storyFn(context);
|
||||
m.redraw();
|
||||
}
|
||||
|
||||
knobClicked(clicked) {
|
||||
const knobOptions = this.props.knobStore.get(clicked.name);
|
||||
knobOptions.callback();
|
||||
}
|
||||
|
||||
resetKnobs() {
|
||||
const { knobStore, storyFn, context } = this.props;
|
||||
knobStore.reset();
|
||||
this.storyContent = storyFn(context);
|
||||
m.redraw();
|
||||
this.setPaneKnobs(false);
|
||||
}
|
||||
|
||||
view() {
|
||||
return m(this.storyContent);
|
||||
}
|
||||
}
|
49
addons/knobs/src/mithril/index.js
Normal file
49
addons/knobs/src/mithril/index.js
Normal file
@ -0,0 +1,49 @@
|
||||
/** @jsx m */
|
||||
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import m from 'mithril';
|
||||
import addons from '@storybook/addons';
|
||||
|
||||
import WrapStory from './WrapStory';
|
||||
|
||||
import {
|
||||
knob,
|
||||
text,
|
||||
boolean,
|
||||
number,
|
||||
color,
|
||||
object,
|
||||
array,
|
||||
date,
|
||||
select,
|
||||
selectV2,
|
||||
button,
|
||||
manager,
|
||||
} from '../base';
|
||||
|
||||
export { knob, text, boolean, number, color, object, array, date, select, selectV2, button };
|
||||
|
||||
export const mithrilHandler = (channel, knobStore) => getStory => context => {
|
||||
const initialContent = getStory(context);
|
||||
const props = { context, storyFn: getStory, channel, knobStore, initialContent };
|
||||
return {
|
||||
view: () => <WrapStory {...props} />,
|
||||
};
|
||||
};
|
||||
|
||||
function wrapperKnobs(options) {
|
||||
const channel = addons.getChannel();
|
||||
manager.setChannel(channel);
|
||||
|
||||
if (options) channel.emit('addon:knobs:setOptions', options);
|
||||
|
||||
return mithrilHandler(channel, manager.knobStore);
|
||||
}
|
||||
|
||||
export function withKnobs(storyFn, context) {
|
||||
return wrapperKnobs()(storyFn)(context);
|
||||
}
|
||||
|
||||
export function withKnobsOptions(options = {}) {
|
||||
return (storyFn, context) => wrapperKnobs(options)(storyFn)(context);
|
||||
}
|
2
app/mithril/.npmignore
Normal file
2
app/mithril/.npmignore
Normal file
@ -0,0 +1,2 @@
|
||||
docs
|
||||
.babelrc
|
33
app/mithril/README.md
Normal file
33
app/mithril/README.md
Normal file
@ -0,0 +1,33 @@
|
||||
# Storybook for Mithril
|
||||
|
||||
[](https://circleci.com/gh/storybooks/storybook)
|
||||
[](https://www.codefactor.io/repository/github/storybooks/storybook)
|
||||
[](https://snyk.io/test/github/storybooks/storybook/8f36abfd6697e58cd76df3526b52e4b9dc894847)
|
||||
[](https://bettercodehub.com/results/storybooks/storybook) [](https://codecov.io/gh/storybooks/storybook)
|
||||
[](https://now-examples-slackin-rrirkqohko.now.sh/)
|
||||
[](#backers) [](#sponsors)
|
||||
|
||||
* * *
|
||||
|
||||
Storybook for Mithril is a UI development environment for your Mithril components.
|
||||
With it, you can visualize different states of your UI components and develop them interactively.
|
||||
|
||||

|
||||
|
||||
Storybook runs outside of your app.
|
||||
So you can develop UI components in isolation without worrying about app specific dependencies and requirements.
|
||||
|
||||
## Getting Started
|
||||
|
||||
```sh
|
||||
npm i -g @storybook/cli
|
||||
cd my-mithril-app
|
||||
getstorybook
|
||||
```
|
||||
|
||||
For more information visit: [storybook.js.org](https://storybook.js.org)
|
||||
|
||||
* * *
|
||||
|
||||
Storybook also comes with a lot of [addons](https://storybook.js.org/addons/introduction) and a great API to customize as you wish.
|
||||
You can also build a [static version](https://storybook.js.org/basics/exporting-storybook) of your storybook and deploy it anywhere you want.
|
3
app/mithril/bin/build.js
Executable file
3
app/mithril/bin/build.js
Executable file
@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
require('../dist/server/build');
|
3
app/mithril/bin/index.js
Executable file
3
app/mithril/bin/index.js
Executable file
@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
require('../dist/server');
|
BIN
app/mithril/docs/demo.gif
Normal file
BIN
app/mithril/docs/demo.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.1 MiB |
BIN
app/mithril/docs/react_storybook_screenshot.png
Normal file
BIN
app/mithril/docs/react_storybook_screenshot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 245 KiB |
BIN
app/mithril/docs/storybooks_io_logo.png
Normal file
BIN
app/mithril/docs/storybooks_io_logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 38 KiB |
67
app/mithril/package.json
Normal file
67
app/mithril/package.json
Normal file
@ -0,0 +1,67 @@
|
||||
{
|
||||
"name": "@storybook/mithril",
|
||||
"version": "3.4.0-rc.3",
|
||||
"description": "Storybook for Mithril: Develop Mithril Component in isolation.",
|
||||
"homepage": "https://github.com/storybooks/storybook/tree/master/app/mithril",
|
||||
"bugs": {
|
||||
"url": "https://github.com/storybooks/storybook/issues"
|
||||
},
|
||||
"license": "MIT",
|
||||
"main": "dist/client/index.js",
|
||||
"jsnext:main": "src/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": {
|
||||
"dev": "cross-env DEV_BUILD=1 nodemon --watch ./src --exec \"yarn prepare\"",
|
||||
"prepare": "node ../../scripts/prepare.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/addons": "3.4.0-rc.3",
|
||||
"@storybook/channel-postmessage": "3.4.0-rc.3",
|
||||
"@storybook/client-logger": "3.4.0-rc.3",
|
||||
"@storybook/core": "3.4.0-rc.3",
|
||||
"@storybook/node-logger": "3.4.0-rc.3",
|
||||
"@storybook/ui": "3.4.0-rc.3",
|
||||
"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.1",
|
||||
"babel-preset-minify": "^0.3.0",
|
||||
"babel-preset-stage-0": "^6.24.1",
|
||||
"babel-runtime": "^6.26.0",
|
||||
"case-sensitive-paths-webpack-plugin": "^2.1.2",
|
||||
"common-tags": "^1.7.2",
|
||||
"core-js": "^2.5.4",
|
||||
"dotenv-webpack": "^1.5.5",
|
||||
"find-cache-dir": "^1.0.0",
|
||||
"global": "^4.3.2",
|
||||
"html-loader": "^0.5.5",
|
||||
"html-webpack-plugin": "^3.1.0",
|
||||
"json5": "^0.5.1",
|
||||
"markdown-loader": "^2.0.2",
|
||||
"react": "^16.2.0",
|
||||
"react-dev-utils": "^5.0.0",
|
||||
"react-dom": "^16.2.0",
|
||||
"util-deprecate": "^1.0.2",
|
||||
"webpack": "^4.3.0",
|
||||
"webpack-hot-middleware": "^2.21.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"mithril": "^1.1.6",
|
||||
"nodemon": "^1.17.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"babel-core": "^6.26.0 || ^7.0.0-0",
|
||||
"babel-runtime": ">=6.0.0",
|
||||
"mithril": "^1.1.6"
|
||||
}
|
||||
}
|
8
app/mithril/src/client/index.js
Normal file
8
app/mithril/src/client/index.js
Normal file
@ -0,0 +1,8 @@
|
||||
export {
|
||||
storiesOf,
|
||||
setAddon,
|
||||
addDecorator,
|
||||
configure,
|
||||
getStorybook,
|
||||
forceReRender,
|
||||
} from './preview';
|
45
app/mithril/src/client/preview/error_display.js
Normal file
45
app/mithril/src/client/preview/error_display.js
Normal file
@ -0,0 +1,45 @@
|
||||
/** @jsx m */
|
||||
|
||||
import m from 'mithril';
|
||||
|
||||
const mainStyle = {
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
padding: 20,
|
||||
backgroundColor: 'rgb(187, 49, 49)',
|
||||
color: '#FFF',
|
||||
WebkitFontSmoothing: 'antialiased',
|
||||
};
|
||||
|
||||
const headingStyle = {
|
||||
fontSize: 20,
|
||||
fontWeight: 600,
|
||||
letterSpacing: 0.2,
|
||||
margin: '10px 0',
|
||||
fontFamily: `
|
||||
-apple-system, ".SFNSText-Regular", "San Francisco", Roboto, "Segoe UI",
|
||||
"Helvetica Neue", "Lucida Grande", sans-serif
|
||||
`,
|
||||
};
|
||||
|
||||
const codeStyle = {
|
||||
fontSize: 14,
|
||||
width: '100vw',
|
||||
overflow: 'auto',
|
||||
};
|
||||
|
||||
const ErrorDisplay = {
|
||||
view: vnode => (
|
||||
<div style={mainStyle}>
|
||||
<div style={headingStyle}>{vnode.attrs.error.message}</div>
|
||||
<pre style={codeStyle}>
|
||||
<code>{vnode.attrs.error.stack}</code>
|
||||
</pre>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
|
||||
export default ErrorDisplay;
|
17
app/mithril/src/client/preview/index.js
Normal file
17
app/mithril/src/client/preview/index.js
Normal file
@ -0,0 +1,17 @@
|
||||
import { start } from '@storybook/core/client';
|
||||
|
||||
import render from './render';
|
||||
|
||||
const { clientApi, configApi, forceReRender } = start(render);
|
||||
|
||||
export const {
|
||||
storiesOf,
|
||||
setAddon,
|
||||
addDecorator,
|
||||
addParameters,
|
||||
clearDecorators,
|
||||
getStorybook,
|
||||
} = clientApi;
|
||||
|
||||
export const { configure } = configApi;
|
||||
export { forceReRender };
|
93
app/mithril/src/client/preview/render.js
Normal file
93
app/mithril/src/client/preview/render.js
Normal file
@ -0,0 +1,93 @@
|
||||
/* global document */
|
||||
/** @jsx m */
|
||||
|
||||
import m from 'mithril';
|
||||
import { stripIndents } from 'common-tags';
|
||||
import { logger } from '@storybook/client-logger';
|
||||
import ErrorDisplay from './error_display';
|
||||
|
||||
// check whether we're running on node/browser
|
||||
const isBrowser = typeof window !== 'undefined';
|
||||
|
||||
let rootEl = null;
|
||||
let previousKind = '';
|
||||
let previousStory = '';
|
||||
|
||||
if (isBrowser) {
|
||||
rootEl = document.getElementById('root');
|
||||
}
|
||||
|
||||
export function renderError(error) {
|
||||
const properError = new Error(error.title);
|
||||
properError.stack = error.description;
|
||||
|
||||
const redBox = <ErrorDisplay error={properError} />;
|
||||
m.mount(rootEl, { view: () => redBox });
|
||||
}
|
||||
|
||||
export function renderException(error) {
|
||||
// We always need to render redbox in the mainPage if we get an error.
|
||||
// Since this is an error, this affects to the main page as well.
|
||||
const realError = new Error(error.message);
|
||||
realError.stack = error.stack;
|
||||
const redBox = <ErrorDisplay error={realError} />;
|
||||
m.mount(rootEl, { view: () => redBox });
|
||||
|
||||
// Log the stack to the console. So, user could check the source code.
|
||||
logger.error(error.stack);
|
||||
}
|
||||
|
||||
export function renderMain(data, storyStore, forceRender) {
|
||||
if (storyStore.size() === 0) return;
|
||||
|
||||
const NoPreview = { view: () => <p>No Preview Available!</p> };
|
||||
const noPreview = <NoPreview />;
|
||||
const { selectedKind, selectedStory } = data;
|
||||
|
||||
const story = storyStore.getStory(selectedKind, selectedStory);
|
||||
if (!story) {
|
||||
m.mount(rootEl, { view: () => noPreview });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!forceRender && selectedKind === previousKind && previousStory === selectedStory) {
|
||||
return;
|
||||
}
|
||||
|
||||
previousKind = selectedKind;
|
||||
previousStory = selectedStory;
|
||||
|
||||
const context = {
|
||||
kind: selectedKind,
|
||||
story: selectedStory,
|
||||
};
|
||||
|
||||
const element = story(context);
|
||||
|
||||
if (!element) {
|
||||
const error = {
|
||||
title: `Expecting a Mithril element from the story: "${selectedStory}" of "${selectedKind}".`,
|
||||
description: stripIndents`
|
||||
Did you forget to return the Mithril element from the story?
|
||||
Use "() => MyComp" or "() => { return MyComp; }" when defining the story.
|
||||
`,
|
||||
};
|
||||
renderError(error);
|
||||
return;
|
||||
}
|
||||
|
||||
m.mount(rootEl, { view: () => m(element) });
|
||||
}
|
||||
|
||||
export default function renderPreview({ reduxStore, storyStore }, forceRender = false) {
|
||||
const state = reduxStore.getState();
|
||||
if (state.error) {
|
||||
return renderException(state.error);
|
||||
}
|
||||
|
||||
try {
|
||||
return renderMain(state, storyStore, forceRender);
|
||||
} catch (ex) {
|
||||
return renderException(ex);
|
||||
}
|
||||
}
|
68
app/mithril/src/server/babel_config.js
Normal file
68
app/mithril/src/server/babel_config.js
Normal file
@ -0,0 +1,68 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import JSON5 from 'json5';
|
||||
import { logger } from '@storybook/node-logger';
|
||||
import defaultConfig from './config/babel';
|
||||
|
||||
function removeReactHmre(presets) {
|
||||
const index = presets.indexOf('react-hmre');
|
||||
if (index > -1) {
|
||||
presets.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Tries to load a .babelrc and returns the parsed object if successful
|
||||
function loadFromPath(babelConfigPath) {
|
||||
let config;
|
||||
if (fs.existsSync(babelConfigPath)) {
|
||||
const content = fs.readFileSync(babelConfigPath, 'utf-8');
|
||||
try {
|
||||
config = JSON5.parse(content);
|
||||
config.babelrc = false;
|
||||
logger.info('=> Loading custom .babelrc');
|
||||
} catch (e) {
|
||||
logger.error(`=> Error parsing .babelrc file: ${e.message}`);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
if (!config) return null;
|
||||
|
||||
// Remove react-hmre preset.
|
||||
// It causes issues with react-storybook.
|
||||
// We don't really need it.
|
||||
// Earlier, we fix this by running storybook in the production mode.
|
||||
// But, that hide some useful debug messages.
|
||||
if (config.presets) {
|
||||
removeReactHmre(config.presets);
|
||||
}
|
||||
|
||||
if (config.env && config.env.development && config.env.development.presets) {
|
||||
removeReactHmre(config.env.development.presets);
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
export default function(configDir) {
|
||||
let babelConfig = loadFromPath(path.resolve(configDir, '.babelrc'));
|
||||
let inConfigDir = true;
|
||||
|
||||
if (!babelConfig) {
|
||||
babelConfig = loadFromPath('.babelrc');
|
||||
inConfigDir = false;
|
||||
}
|
||||
|
||||
if (babelConfig) {
|
||||
// If the custom config uses babel's `extends` clause, then replace it with
|
||||
// an absolute path. `extends` will not work unless we do this.
|
||||
if (babelConfig.extends) {
|
||||
babelConfig.extends = inConfigDir
|
||||
? path.resolve(configDir, babelConfig.extends)
|
||||
: path.resolve(babelConfig.extends);
|
||||
}
|
||||
}
|
||||
|
||||
const finalConfig = babelConfig || defaultConfig;
|
||||
return finalConfig;
|
||||
}
|
84
app/mithril/src/server/babel_config.test.js
Normal file
84
app/mithril/src/server/babel_config.test.js
Normal file
@ -0,0 +1,84 @@
|
||||
import loadBabelConfig from './babel_config';
|
||||
|
||||
// eslint-disable-next-line global-require
|
||||
jest.mock('fs', () => require('../../../../__mocks__/fs'));
|
||||
jest.mock('path', () => ({
|
||||
resolve: () => '.babelrc',
|
||||
}));
|
||||
|
||||
const setup = ({ files }) => {
|
||||
// eslint-disable-next-line no-underscore-dangle, global-require
|
||||
require('fs').__setMockFiles(files);
|
||||
};
|
||||
|
||||
describe('babel_config', () => {
|
||||
// As the 'fs' is going to be mocked, let's call require.resolve
|
||||
// so the require.cache has the correct route to the file.
|
||||
// In fact let's use it in the tests :)
|
||||
it('should return the config with the extra plugins when `plugins` is an array.', () => {
|
||||
setup({
|
||||
files: {
|
||||
'.babelrc': `{
|
||||
"presets": [
|
||||
"env",
|
||||
"foo-preset"
|
||||
],
|
||||
"plugins": [
|
||||
"foo-plugin"
|
||||
]
|
||||
}`,
|
||||
},
|
||||
});
|
||||
|
||||
const config = loadBabelConfig('.foo');
|
||||
|
||||
expect(config).toEqual({
|
||||
babelrc: false,
|
||||
plugins: ['foo-plugin'],
|
||||
presets: ['env', 'foo-preset'],
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the config with the extra plugins when `plugins` is not an array.', () => {
|
||||
setup({
|
||||
files: {
|
||||
'.babelrc': `{
|
||||
"presets": [
|
||||
"env",
|
||||
"foo-preset"
|
||||
],
|
||||
"plugins": "bar-plugin"
|
||||
}`,
|
||||
},
|
||||
});
|
||||
|
||||
const config = loadBabelConfig('.bar');
|
||||
|
||||
expect(config).toEqual({
|
||||
babelrc: false,
|
||||
plugins: 'bar-plugin',
|
||||
presets: ['env', 'foo-preset'],
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the config only with the extra plugins when `plugins` is not present.', () => {
|
||||
// Mock a `.babelrc` config file with no plugins key.
|
||||
setup({
|
||||
files: {
|
||||
'.babelrc': `{
|
||||
"presets": [
|
||||
"env",
|
||||
"foo-preset"
|
||||
]
|
||||
}`,
|
||||
},
|
||||
});
|
||||
|
||||
const config = loadBabelConfig('.biz');
|
||||
|
||||
expect(config).toEqual({
|
||||
babelrc: false,
|
||||
presets: ['env', 'foo-preset'],
|
||||
});
|
||||
});
|
||||
});
|
12
app/mithril/src/server/build.js
Executable file
12
app/mithril/src/server/build.js
Executable file
@ -0,0 +1,12 @@
|
||||
import { buildStatic } from '@storybook/core/server';
|
||||
import path from 'path';
|
||||
import packageJson from '../../package.json';
|
||||
import getBaseConfig from './config/webpack.config.prod';
|
||||
import loadConfig from './config';
|
||||
|
||||
buildStatic({
|
||||
packageJson,
|
||||
getBaseConfig,
|
||||
loadConfig,
|
||||
defaultFavIcon: path.resolve(__dirname, 'public/favicon.ico'),
|
||||
});
|
86
app/mithril/src/server/config.js
Normal file
86
app/mithril/src/server/config.js
Normal file
@ -0,0 +1,86 @@
|
||||
/* eslint-disable global-require, import/no-dynamic-require */
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import findCacheDir from 'find-cache-dir';
|
||||
import { logger } from '@storybook/node-logger';
|
||||
import { createDefaultWebpackConfig } from '@storybook/core/server';
|
||||
import loadBabelConfig from './babel_config';
|
||||
|
||||
// `baseConfig` is a webpack configuration bundled with storybook.
|
||||
// Storybook will look in the `configDir` directory
|
||||
// (inside working directory) if a config path is not provided.
|
||||
export default function(configType, baseConfig, configDir) {
|
||||
const config = baseConfig;
|
||||
|
||||
const babelConfig = loadBabelConfig(configDir);
|
||||
config.module.rules[0].query = {
|
||||
// This is a feature of `babel-loader` for webpack (not Babel itself).
|
||||
// It enables a cache directory for faster-rebuilds
|
||||
// `find-cache-dir` will create the cache directory under the node_modules directory.
|
||||
cacheDirectory: findCacheDir({ name: 'react-storybook' }),
|
||||
...babelConfig,
|
||||
};
|
||||
|
||||
// Check whether a config.js file exists inside the storybook
|
||||
// config directory and throw an error if it's not.
|
||||
const storybookConfigPath = path.resolve(configDir, 'config.js');
|
||||
if (!fs.existsSync(storybookConfigPath)) {
|
||||
const err = new Error(`=> Create a storybook config file in "${configDir}/config.js".`);
|
||||
throw err;
|
||||
}
|
||||
config.entry.preview.push(require.resolve(storybookConfigPath));
|
||||
|
||||
// Check whether addons.js file exists inside the storybook.
|
||||
// Load the default addons.js file if it's missing.
|
||||
// Insert it after polyfills.js, but before client/manager.
|
||||
const storybookCustomAddonsPath = path.resolve(configDir, 'addons.js');
|
||||
if (fs.existsSync(storybookCustomAddonsPath)) {
|
||||
logger.info('=> Loading custom addons config.');
|
||||
config.entry.manager.splice(1, 0, storybookCustomAddonsPath);
|
||||
}
|
||||
|
||||
const defaultConfig = createDefaultWebpackConfig(config);
|
||||
|
||||
// Check whether user has a custom webpack config file and
|
||||
// return the (extended) base configuration if it's not available.
|
||||
const customConfigPath = path.resolve(configDir, 'webpack.config.js');
|
||||
|
||||
if (!fs.existsSync(customConfigPath)) {
|
||||
logger.info('=> Using default webpack setup.');
|
||||
return defaultConfig;
|
||||
}
|
||||
const customConfig = require(customConfigPath);
|
||||
|
||||
if (typeof customConfig === 'function') {
|
||||
logger.info('=> Loading custom webpack config (full-control mode).');
|
||||
return customConfig(config, configType, defaultConfig);
|
||||
}
|
||||
logger.info('=> Loading custom webpack config (extending mode).');
|
||||
return {
|
||||
...customConfig,
|
||||
// We'll always load our configurations after the custom config.
|
||||
// So, we'll always load the stuff we need.
|
||||
...config,
|
||||
// Override with custom devtool if provided
|
||||
devtool: customConfig.devtool || config.devtool,
|
||||
// We need to use our and custom plugins.
|
||||
plugins: [...config.plugins, ...(customConfig.plugins || [])],
|
||||
module: {
|
||||
...config.module,
|
||||
// We need to use our and custom rules.
|
||||
...customConfig.module,
|
||||
rules: [
|
||||
...config.module.rules,
|
||||
...((customConfig.module && customConfig.module.rules) || []),
|
||||
],
|
||||
},
|
||||
resolve: {
|
||||
...config.resolve,
|
||||
...customConfig.resolve,
|
||||
alias: {
|
||||
...config.alias,
|
||||
...(customConfig.resolve && customConfig.resolve.alias),
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
28
app/mithril/src/server/config/babel.js
Normal file
28
app/mithril/src/server/config/babel.js
Normal file
@ -0,0 +1,28 @@
|
||||
module.exports = {
|
||||
// Don't try to find .babelrc because we want to force this configuration.
|
||||
babelrc: false,
|
||||
presets: [
|
||||
[
|
||||
require.resolve('babel-preset-env'),
|
||||
{
|
||||
targets: {
|
||||
browsers: ['last 2 versions', 'safari >= 7'],
|
||||
},
|
||||
modules: process.env.NODE_ENV === 'test' ? 'commonjs' : false,
|
||||
},
|
||||
],
|
||||
require.resolve('babel-preset-stage-0'),
|
||||
],
|
||||
plugins: [
|
||||
require.resolve('babel-plugin-macros'),
|
||||
require.resolve('babel-plugin-transform-regenerator'),
|
||||
[
|
||||
require.resolve('babel-plugin-transform-runtime'),
|
||||
{
|
||||
helpers: true,
|
||||
polyfill: true,
|
||||
regenerator: true,
|
||||
},
|
||||
],
|
||||
],
|
||||
};
|
28
app/mithril/src/server/config/babel.prod.js
Normal file
28
app/mithril/src/server/config/babel.prod.js
Normal file
@ -0,0 +1,28 @@
|
||||
module.exports = {
|
||||
// Don't try to find .babelrc because we want to force this configuration.
|
||||
babelrc: false,
|
||||
presets: [
|
||||
[
|
||||
require.resolve('babel-preset-env'),
|
||||
{
|
||||
targets: {
|
||||
browsers: ['last 2 versions', 'safari >= 7'],
|
||||
},
|
||||
modules: false,
|
||||
},
|
||||
],
|
||||
require.resolve('babel-preset-stage-0'),
|
||||
require.resolve('babel-preset-minify'),
|
||||
],
|
||||
plugins: [
|
||||
require.resolve('babel-plugin-transform-regenerator'),
|
||||
[
|
||||
require.resolve('babel-plugin-transform-runtime'),
|
||||
{
|
||||
helpers: true,
|
||||
polyfill: true,
|
||||
regenerator: true,
|
||||
},
|
||||
],
|
||||
],
|
||||
};
|
4
app/mithril/src/server/config/globals.js
Normal file
4
app/mithril/src/server/config/globals.js
Normal file
@ -0,0 +1,4 @@
|
||||
/* globals window */
|
||||
|
||||
window.STORYBOOK_REACT_CLASSES = {};
|
||||
window.STORYBOOK_ENV = 'mithril';
|
3
app/mithril/src/server/config/polyfills.js
Normal file
3
app/mithril/src/server/config/polyfills.js
Normal file
@ -0,0 +1,3 @@
|
||||
import 'core-js/es6/symbol';
|
||||
import 'core-js/fn/array/iterator';
|
||||
import 'airbnb-js-shims';
|
35
app/mithril/src/server/config/utils.js
Normal file
35
app/mithril/src/server/config/utils.js
Normal file
@ -0,0 +1,35 @@
|
||||
import path from 'path';
|
||||
|
||||
export const includePaths = [path.resolve('./')];
|
||||
|
||||
export const excludePaths = [path.resolve('node_modules')];
|
||||
|
||||
export const nodeModulesPaths = path.resolve('./node_modules');
|
||||
|
||||
export const nodePaths = (process.env.NODE_PATH || '')
|
||||
.split(process.platform === 'win32' ? ';' : ':')
|
||||
.filter(Boolean)
|
||||
.map(p => path.resolve('./', p));
|
||||
|
||||
// Load environment variables starts with STORYBOOK_ to the client side.
|
||||
export function loadEnv(options = {}) {
|
||||
const defaultNodeEnv = options.production ? 'production' : 'development';
|
||||
const env = {
|
||||
NODE_ENV: JSON.stringify(process.env.NODE_ENV || defaultNodeEnv),
|
||||
// This is to support CRA's public folder feature.
|
||||
// In production we set this to dot(.) to allow the browser to access these assests
|
||||
// even when deployed inside a subpath. (like in GitHub pages)
|
||||
// In development this is just empty as we always serves from the root.
|
||||
PUBLIC_URL: JSON.stringify(options.production ? '.' : ''),
|
||||
};
|
||||
|
||||
Object.keys(process.env)
|
||||
.filter(name => /^STORYBOOK_/.test(name))
|
||||
.forEach(name => {
|
||||
env[name] = JSON.stringify(process.env[name]);
|
||||
});
|
||||
|
||||
return {
|
||||
'process.env': env,
|
||||
};
|
||||
}
|
94
app/mithril/src/server/config/webpack.config.js
Normal file
94
app/mithril/src/server/config/webpack.config.js
Normal file
@ -0,0 +1,94 @@
|
||||
import path from 'path';
|
||||
import webpack from 'webpack';
|
||||
import Dotenv from 'dotenv-webpack';
|
||||
import InterpolateHtmlPlugin from 'react-dev-utils/InterpolateHtmlPlugin';
|
||||
import WatchMissingNodeModulesPlugin from 'react-dev-utils/WatchMissingNodeModulesPlugin';
|
||||
import CaseSensitivePathsPlugin from 'case-sensitive-paths-webpack-plugin';
|
||||
import HtmlWebpackPlugin from 'html-webpack-plugin';
|
||||
import { managerPath } from '@storybook/core/server';
|
||||
|
||||
import { includePaths, excludePaths, nodeModulesPaths, loadEnv, nodePaths } from './utils';
|
||||
import babelLoaderConfig from './babel';
|
||||
import { getPreviewHeadHtml, getManagerHeadHtml } from '../utils';
|
||||
import { version } from '../../../package.json';
|
||||
|
||||
export default function(configDir) {
|
||||
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'],
|
||||
data: {
|
||||
managerHead: getManagerHeadHtml(configDir),
|
||||
version,
|
||||
},
|
||||
template: require.resolve('../index.html.ejs'),
|
||||
}),
|
||||
new HtmlWebpackPlugin({
|
||||
filename: 'iframe.html',
|
||||
excludeChunks: ['manager'],
|
||||
data: {
|
||||
previewHead: getPreviewHeadHtml(configDir),
|
||||
},
|
||||
template: require.resolve('../iframe.html.ejs'),
|
||||
}),
|
||||
new InterpolateHtmlPlugin(process.env),
|
||||
new webpack.DefinePlugin(loadEnv()),
|
||||
new webpack.HotModuleReplacementPlugin(),
|
||||
new CaseSensitivePathsPlugin(),
|
||||
new WatchMissingNodeModulesPlugin(nodeModulesPaths),
|
||||
new webpack.ProgressPlugin(),
|
||||
new Dotenv({ silent: true }),
|
||||
],
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.jsx?$/,
|
||||
loader: require.resolve('babel-loader'),
|
||||
query: babelLoaderConfig,
|
||||
include: includePaths,
|
||||
exclude: excludePaths,
|
||||
},
|
||||
{
|
||||
test: /\.md$/,
|
||||
use: [
|
||||
{
|
||||
loader: require.resolve('html-loader'),
|
||||
},
|
||||
{
|
||||
loader: require.resolve('markdown-loader'),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
resolve: {
|
||||
// Since we ship with json-loader always, it's better to move extensions to here
|
||||
// from the default config.
|
||||
extensions: ['.js', '.json', '.jsx'],
|
||||
// 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;
|
||||
}
|
86
app/mithril/src/server/config/webpack.config.prod.js
Normal file
86
app/mithril/src/server/config/webpack.config.prod.js
Normal file
@ -0,0 +1,86 @@
|
||||
import webpack from 'webpack';
|
||||
import Dotenv from 'dotenv-webpack';
|
||||
import InterpolateHtmlPlugin from 'react-dev-utils/InterpolateHtmlPlugin';
|
||||
import HtmlWebpackPlugin from 'html-webpack-plugin';
|
||||
import { managerPath } from '@storybook/core/server';
|
||||
import babelLoaderConfig from './babel.prod';
|
||||
import { includePaths, excludePaths, loadEnv, nodePaths } from './utils';
|
||||
import { getPreviewHeadHtml, getManagerHeadHtml } 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'],
|
||||
data: {
|
||||
managerHead: getManagerHeadHtml(configDir),
|
||||
version,
|
||||
},
|
||||
template: require.resolve('../index.html.ejs'),
|
||||
}),
|
||||
new HtmlWebpackPlugin({
|
||||
filename: 'iframe.html',
|
||||
excludeChunks: ['manager'],
|
||||
data: {
|
||||
previewHead: getPreviewHeadHtml(configDir),
|
||||
},
|
||||
template: require.resolve('../iframe.html.ejs'),
|
||||
}),
|
||||
new InterpolateHtmlPlugin(process.env),
|
||||
new webpack.DefinePlugin(loadEnv({ production: true })),
|
||||
new Dotenv({ silent: true }),
|
||||
],
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.jsx?$/,
|
||||
loader: require.resolve('babel-loader'),
|
||||
query: babelLoaderConfig,
|
||||
include: includePaths,
|
||||
exclude: excludePaths,
|
||||
},
|
||||
{
|
||||
test: /\.md$/,
|
||||
use: [
|
||||
{
|
||||
loader: require.resolve('html-loader'),
|
||||
},
|
||||
{
|
||||
loader: require.resolve('markdown-loader'),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
resolve: {
|
||||
// Since we ship with json-loader always, it's better to move extensions to here
|
||||
// from the default config.
|
||||
extensions: ['.js', '.json', '.jsx'],
|
||||
// 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),
|
||||
},
|
||||
};
|
||||
|
||||
return config;
|
||||
}
|
15
app/mithril/src/server/iframe.html.ejs
Normal file
15
app/mithril/src/server/iframe.html.ejs
Normal file
@ -0,0 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta content="IE=edge" http-equiv="X-UA-Compatible" />
|
||||
<base target="_parent">
|
||||
<title>Storybook</title>
|
||||
<%= htmlWebpackPlugin.options.data.previewHead %>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<div id="error-display"></div>
|
||||
</body>
|
||||
</html>
|
44
app/mithril/src/server/index.html.ejs
Normal file
44
app/mithril/src/server/index.html.ejs
Normal file
@ -0,0 +1,44 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="storybook-version" content="<%= htmlWebpackPlugin.options.data.version %>">
|
||||
<meta content="IE=edge" http-equiv="X-UA-Compatible" />
|
||||
<title>Storybook</title>
|
||||
<style>
|
||||
/*
|
||||
When resizing panels, the drag event breaks if the cursor
|
||||
moves over the iframe. Add the 'dragging' class to the body
|
||||
at drag start and remove it when the drag ends.
|
||||
*/
|
||||
.dragging iframe {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Styling the fuzzy search box placeholders */
|
||||
.searchBox::-webkit-input-placeholder { /* Chrome/Opera/Safari */
|
||||
color: #ddd;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.searchBox::-moz-placeholder { /* Firefox 19+ */
|
||||
color: #ddd;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.searchBox:focus{
|
||||
border-color: #EEE !important;
|
||||
}
|
||||
|
||||
.btn:hover{
|
||||
background-color: #eee
|
||||
}
|
||||
</style>
|
||||
<%= htmlWebpackPlugin.options.data.managerHead %>
|
||||
|
||||
</head>
|
||||
<body style="margin: 0;">
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
12
app/mithril/src/server/index.js
Executable file
12
app/mithril/src/server/index.js
Executable file
@ -0,0 +1,12 @@
|
||||
import { buildDev } from '@storybook/core/server';
|
||||
import path from 'path';
|
||||
import packageJson from '../../package.json';
|
||||
import getBaseConfig from './config/webpack.config';
|
||||
import loadConfig from './config';
|
||||
|
||||
buildDev({
|
||||
packageJson,
|
||||
getBaseConfig,
|
||||
loadConfig,
|
||||
defaultFavIcon: path.resolve(__dirname, 'public/favicon.ico'),
|
||||
});
|
BIN
app/mithril/src/server/public/favicon.ico
Executable file
BIN
app/mithril/src/server/public/favicon.ico
Executable file
Binary file not shown.
After Width: | Height: | Size: 5.3 KiB |
30
app/mithril/src/server/utils.js
Normal file
30
app/mithril/src/server/utils.js
Normal file
@ -0,0 +1,30 @@
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import deprecate from 'util-deprecate';
|
||||
|
||||
const fallbackHeadUsage = deprecate(() => {},
|
||||
'Usage of head.html has been deprecated. Please rename head.html to preview-head.html');
|
||||
|
||||
export function getPreviewHeadHtml(configDirPath) {
|
||||
const headHtmlPath = path.resolve(configDirPath, 'preview-head.html');
|
||||
const fallbackHtmlPath = path.resolve(configDirPath, 'head.html');
|
||||
let headHtml = '';
|
||||
if (fs.existsSync(headHtmlPath)) {
|
||||
headHtml = fs.readFileSync(headHtmlPath, 'utf8');
|
||||
} else if (fs.existsSync(fallbackHtmlPath)) {
|
||||
headHtml = fs.readFileSync(fallbackHtmlPath, 'utf8');
|
||||
fallbackHeadUsage();
|
||||
}
|
||||
|
||||
return headHtml;
|
||||
}
|
||||
|
||||
export function getManagerHeadHtml(configDirPath) {
|
||||
const scriptPath = path.resolve(configDirPath, 'manager-head.html');
|
||||
let scriptHtml = '';
|
||||
if (fs.existsSync(scriptPath)) {
|
||||
scriptHtml = fs.readFileSync(scriptPath, 'utf8');
|
||||
}
|
||||
|
||||
return scriptHtml;
|
||||
}
|
69
app/mithril/src/server/utils.test.js
Normal file
69
app/mithril/src/server/utils.test.js
Normal file
@ -0,0 +1,69 @@
|
||||
import { getPreviewHeadHtml, getManagerHeadHtml } from './utils';
|
||||
|
||||
// eslint-disable-next-line global-require
|
||||
jest.mock('fs', () => require('../../../../__mocks__/fs'));
|
||||
jest.mock('path', () => ({
|
||||
resolve: (a, p) => p,
|
||||
}));
|
||||
|
||||
const setup = ({ files }) => {
|
||||
// eslint-disable-next-line no-underscore-dangle, global-require
|
||||
require('fs').__setMockFiles(files);
|
||||
};
|
||||
|
||||
const HEAD_HTML_CONTENTS = 'UNITTEST_HEAD_HTML_CONTENTS';
|
||||
|
||||
describe('getPreviewHeadHtml', () => {
|
||||
it('returns an empty string without head.html present', () => {
|
||||
setup({
|
||||
files: {},
|
||||
});
|
||||
|
||||
const result = getPreviewHeadHtml('first');
|
||||
expect(result).toEqual('');
|
||||
});
|
||||
|
||||
it('return contents of head.html when present', () => {
|
||||
setup({
|
||||
files: {
|
||||
'head.html': HEAD_HTML_CONTENTS,
|
||||
},
|
||||
});
|
||||
|
||||
const result = getPreviewHeadHtml('second');
|
||||
expect(result).toEqual(HEAD_HTML_CONTENTS);
|
||||
});
|
||||
|
||||
it('returns contents of preview-head.html when present', () => {
|
||||
setup({
|
||||
files: {
|
||||
'preview-head.html': HEAD_HTML_CONTENTS,
|
||||
},
|
||||
});
|
||||
|
||||
const result = getPreviewHeadHtml('second');
|
||||
expect(result).toEqual(HEAD_HTML_CONTENTS);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getManagerHeadHtml', () => {
|
||||
it('returns an empty string without manager-head.html present', () => {
|
||||
setup({
|
||||
files: {},
|
||||
});
|
||||
|
||||
const result = getManagerHeadHtml('first');
|
||||
expect(result).toEqual('');
|
||||
});
|
||||
|
||||
it('returns contents of manager-head.html when present', () => {
|
||||
setup({
|
||||
files: {
|
||||
'manager-head.html': HEAD_HTML_CONTENTS,
|
||||
},
|
||||
});
|
||||
|
||||
const result = getManagerHeadHtml('second');
|
||||
expect(result).toEqual(HEAD_HTML_CONTENTS);
|
||||
});
|
||||
});
|
@ -12,6 +12,7 @@ module.exports = {
|
||||
'/basics/guide-react/',
|
||||
'/basics/guide-vue/',
|
||||
'/basics/guide-angular/',
|
||||
'/basics/guide-mithril/',
|
||||
'/basics/writing-stories/',
|
||||
'/basics/exporting-storybook/',
|
||||
'/basics/faq/',
|
||||
|
108
docs/src/pages/basics/guide-mithril/index.md
Normal file
108
docs/src/pages/basics/guide-mithril/index.md
Normal file
@ -0,0 +1,108 @@
|
||||
---
|
||||
id: 'guide-mithril'
|
||||
title: 'Storybook for Mithril'
|
||||
---
|
||||
|
||||
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 Mithril
|
||||
|
||||
Storybook has its own Webpack setup and a dev server.
|
||||
|
||||
In this guide, we will set up Storybook for your Mithril project.
|
||||
|
||||
## Table of contents
|
||||
|
||||
- [Add @storybook/mithril](#add-storybookmithril)
|
||||
- [Add mithril and babel-core](#add-mithril-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/mithril
|
||||
|
||||
First of all, you need to add `@storybook/mithril` to your project. To do that, simply run:
|
||||
|
||||
```sh
|
||||
npm i --save-dev @storybook/mithril
|
||||
```
|
||||
|
||||
## Add mithril and babel-core
|
||||
|
||||
Make sure that you have `mithril` and `babel-core` in your dependencies as well because we list these as a peerDependency:
|
||||
|
||||
```sh
|
||||
npm i --save mithril
|
||||
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/mithril';
|
||||
|
||||
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
|
||||
/** @jsx m */
|
||||
|
||||
import m from 'mithril';
|
||||
|
||||
import { storiesOf } from '@storybook/mithril';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import Button from '../components/Button';
|
||||
|
||||
storiesOf('Button', module)
|
||||
.add('with text', () => ({
|
||||
view: () => <Button onclick={action('clicked')}>Hello Button</Button>
|
||||
}))
|
||||
.add('with some emoji', () => ({
|
||||
view: () => <Button onclick={action('clicked')}><span role="img" aria-label="so cool">😀 😎 👍 💯</span></Button>
|
||||
}));
|
||||
```
|
||||
|
||||
Story is a single state of your component. In the above case, there are two stories for the native button component:
|
||||
|
||||
1. with text
|
||||
2. with some emoji
|
||||
|
||||
## 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.
|
@ -10,6 +10,7 @@ title: 'Live Examples'
|
||||
- [Vue](https://storybooks-vue.netlify.com/)
|
||||
- [Angular](https://storybooks-angular.netlify.com/)
|
||||
- [Polymer](https://storybooks-polymer.netlify.com/)
|
||||
- [Mithril](https://storybooks-mithril.netlify.com/)
|
||||
|
||||
### 3.4
|
||||
- [React Official](https://release-3-4--storybooks-official.netlify.com)
|
||||
|
@ -3,7 +3,7 @@ id: 'quick-start-guide'
|
||||
title: 'Quick Start Guide'
|
||||
---
|
||||
|
||||
Storybook is very easy to use. You can use it with any kind of React or Vue or Angular project.
|
||||
Storybook is very easy to use. You can use it with any kind of React or Vue or Angular or Mithril project.
|
||||
Follow these steps to get started with Storybook.
|
||||
|
||||
```sh
|
||||
@ -23,4 +23,4 @@ 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/).
|
||||
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/).
|
||||
|
@ -8,3 +8,4 @@ Storybook supports multiple UI libraries, the manual setup for each is different
|
||||
- [Storybook for React](/basics/guide-react/)
|
||||
- [Storybook for Vue](/basics/guide-vue/)
|
||||
- [Storybook for Angular](/basics/guide-angular/)
|
||||
- [Storybook for Mithril](/basics/guide-mithril/)
|
||||
|
5
examples/mithril-kitchen-sink/.babelrc
Normal file
5
examples/mithril-kitchen-sink/.babelrc
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"plugins": [
|
||||
"transform-react-jsx"
|
||||
]
|
||||
}
|
15
examples/mithril-kitchen-sink/.gitignore
vendored
Normal file
15
examples/mithril-kitchen-sink/.gitignore
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
node_modules
|
||||
|
||||
# testing
|
||||
coverage
|
||||
|
||||
# production
|
||||
build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env
|
||||
npm-debug.log
|
8
examples/mithril-kitchen-sink/.storybook/addons.js
Normal file
8
examples/mithril-kitchen-sink/.storybook/addons.js
Normal file
@ -0,0 +1,8 @@
|
||||
import '@storybook/addon-storysource/register';
|
||||
import '@storybook/addon-actions/register';
|
||||
import '@storybook/addon-links/register';
|
||||
import '@storybook/addon-notes/register';
|
||||
import '@storybook/addon-knobs/register';
|
||||
import '@storybook/addon-viewport/register';
|
||||
import '@storybook/addon-options/register';
|
||||
import '@storybook/addon-backgrounds/register';
|
13
examples/mithril-kitchen-sink/.storybook/config.js
Normal file
13
examples/mithril-kitchen-sink/.storybook/config.js
Normal file
@ -0,0 +1,13 @@
|
||||
import { configure } from '@storybook/mithril';
|
||||
import { setOptions } from '@storybook/addon-options';
|
||||
|
||||
setOptions({
|
||||
hierarchyRootSeparator: /\|/,
|
||||
});
|
||||
|
||||
function loadStories() {
|
||||
const req = require.context('../src/stories', true, /\.stories\.js$/);
|
||||
req.keys().forEach(filename => req(filename));
|
||||
}
|
||||
|
||||
configure(loadStories, module);
|
12
examples/mithril-kitchen-sink/.storybook/webpack.config.js
Normal file
12
examples/mithril-kitchen-sink/.storybook/webpack.config.js
Normal file
@ -0,0 +1,12 @@
|
||||
const path = require('path');
|
||||
|
||||
module.exports = (storybookBaseConfig, configType, defaultConfig) => {
|
||||
defaultConfig.module.rules.push({
|
||||
test: [/\.stories\.js$/],
|
||||
loaders: [require.resolve('@storybook/addon-storysource/loader')],
|
||||
include: [path.resolve(__dirname, '../src')],
|
||||
enforce: 'pre',
|
||||
});
|
||||
|
||||
return defaultConfig;
|
||||
};
|
3
examples/mithril-kitchen-sink/README.md
Normal file
3
examples/mithril-kitchen-sink/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# Storybook Demo
|
||||
|
||||
This is a demo app to test Mithril integration with Storybook. Run `npm install` or `yarn install` to sync Storybook module with the source code and run `npm run storybook` or `yarn storybook` to start the Storybook.
|
29
examples/mithril-kitchen-sink/package.json
Normal file
29
examples/mithril-kitchen-sink/package.json
Normal file
@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "mithril-example",
|
||||
"version": "3.4.0-rc.3",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build-storybook": "build-storybook",
|
||||
"storybook": "start-storybook -p 9007"
|
||||
},
|
||||
"dependencies": {
|
||||
"mithril": "^1.1.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@storybook/addon-actions": "3.4.0-rc.3",
|
||||
"@storybook/addon-backgrounds": "3.4.0-rc.3",
|
||||
"@storybook/addon-centered": "3.4.0-rc.3",
|
||||
"@storybook/addon-knobs": "3.4.0-rc.3",
|
||||
"@storybook/addon-links": "3.4.0-rc.3",
|
||||
"@storybook/addon-notes": "3.4.0-rc.3",
|
||||
"@storybook/addon-options": "3.4.0-rc.3",
|
||||
"@storybook/addon-storyshots": "3.4.0-rc.3",
|
||||
"@storybook/addon-storysource": "3.4.0-rc.3",
|
||||
"@storybook/addon-viewport": "3.4.0-rc.3",
|
||||
"@storybook/addons": "3.4.0-rc.3",
|
||||
"@storybook/mithril": "3.4.0-rc.3",
|
||||
"babel-core": "^6.26.0",
|
||||
"babel-plugin-transform-react-jsx": "^6.24.1",
|
||||
"webpack": "^4.3.0"
|
||||
}
|
||||
}
|
12
examples/mithril-kitchen-sink/src/BaseButton.js
Normal file
12
examples/mithril-kitchen-sink/src/BaseButton.js
Normal file
@ -0,0 +1,12 @@
|
||||
import m from 'mithril';
|
||||
|
||||
const BaseButton = {
|
||||
view: ({ attrs }) =>
|
||||
m(
|
||||
'button',
|
||||
{ disabled: attrs.disabled, onclick: attrs.onclick, style: attrs.style },
|
||||
attrs.label
|
||||
),
|
||||
};
|
||||
|
||||
export default BaseButton;
|
23
examples/mithril-kitchen-sink/src/Button.js
Normal file
23
examples/mithril-kitchen-sink/src/Button.js
Normal file
@ -0,0 +1,23 @@
|
||||
/** @jsx m */
|
||||
|
||||
import m from 'mithril';
|
||||
|
||||
const style = {
|
||||
border: '1px solid #eee',
|
||||
borderRadius: '3px',
|
||||
backgroundColor: '#FFFFFF',
|
||||
cursor: 'pointer',
|
||||
fontSize: '15px',
|
||||
padding: '3px 10px',
|
||||
margin: '10px',
|
||||
};
|
||||
|
||||
const Button = {
|
||||
view: vnode => (
|
||||
<button style={style} {...vnode.attrs}>
|
||||
{vnode.children}
|
||||
</button>
|
||||
),
|
||||
};
|
||||
|
||||
export default Button;
|
137
examples/mithril-kitchen-sink/src/Welcome.js
Normal file
137
examples/mithril-kitchen-sink/src/Welcome.js
Normal file
@ -0,0 +1,137 @@
|
||||
/** @jsx m */
|
||||
|
||||
import m from 'mithril';
|
||||
|
||||
const Main = {
|
||||
view: vnode => (
|
||||
<article
|
||||
style={{
|
||||
margin: '15px',
|
||||
maxWidth: '600px',
|
||||
lineHeight: 1.4,
|
||||
fontFamily: '"Helvetica Neue", Helvetica, "Segoe UI", Arial, freesans, sans-serif',
|
||||
}}
|
||||
>
|
||||
{vnode.children}
|
||||
</article>
|
||||
),
|
||||
};
|
||||
|
||||
const Title = {
|
||||
view: vnode => <h1>{vnode.children}</h1>,
|
||||
};
|
||||
|
||||
const Note = {
|
||||
view: vnode => (
|
||||
<p
|
||||
style={{
|
||||
opacity: 0.5,
|
||||
}}
|
||||
>
|
||||
{vnode.children}
|
||||
</p>
|
||||
),
|
||||
};
|
||||
|
||||
const InlineCode = {
|
||||
view: vnode => (
|
||||
<code
|
||||
style={{
|
||||
fontSize: '15px',
|
||||
fontWeight: 600,
|
||||
padding: '2px 5px',
|
||||
border: '1px solid #eae9e9',
|
||||
borderRadius: '4px',
|
||||
backgroundColor: '#f3f2f2',
|
||||
color: '#3a3a3a',
|
||||
}}
|
||||
>
|
||||
{vnode.children}
|
||||
</code>
|
||||
),
|
||||
};
|
||||
|
||||
const Link = {
|
||||
view: vnode => (
|
||||
<a
|
||||
style={{
|
||||
color: '#1474f3',
|
||||
textDecoration: 'none',
|
||||
borderBottom: '1px solid #1474f3',
|
||||
paddingBottom: '2px',
|
||||
}}
|
||||
{...vnode.attrs}
|
||||
>
|
||||
{vnode.children}
|
||||
</a>
|
||||
),
|
||||
};
|
||||
|
||||
const NavButton = {
|
||||
view: vnode => (
|
||||
<button
|
||||
style={{
|
||||
borderTop: 'none',
|
||||
borderRight: 'none',
|
||||
borderLeft: 'none',
|
||||
backgroundColor: 'transparent',
|
||||
padding: 0,
|
||||
cursor: 'pointer',
|
||||
font: 'inherit',
|
||||
}}
|
||||
{...vnode.attrs}
|
||||
>
|
||||
{vnode.children}
|
||||
</button>
|
||||
),
|
||||
};
|
||||
|
||||
const Welcome = {
|
||||
view: vnode => (
|
||||
<Main>
|
||||
<Title>Welcome to storybook</Title>
|
||||
<p>This is a UI component dev environment for your app.</p>
|
||||
<p>
|
||||
We've added some basic stories inside the <InlineCode>src/stories</InlineCode> directory.
|
||||
<br />
|
||||
A story is a single state of one or more UI components. You can have as many stories as you
|
||||
want.
|
||||
<br />
|
||||
(Basically a story is like a visual test case.)
|
||||
</p>
|
||||
<p>
|
||||
See these sample <NavButton onclick={vnode.attrs.showApp}>stories</NavButton> for a
|
||||
component called <InlineCode>Button</InlineCode>
|
||||
.
|
||||
</p>
|
||||
<p>
|
||||
Just like that, you can add your own components as stories.
|
||||
<br />
|
||||
You can also edit those components and see changes right away.
|
||||
<br />
|
||||
(Try editing the <InlineCode>Button</InlineCode> stories located at{' '}
|
||||
<InlineCode>src/stories/index.js</InlineCode>.)
|
||||
</p>
|
||||
<p>
|
||||
Usually we create stories with smaller UI components in the app.<br />
|
||||
Have a look at the{' '}
|
||||
<Link
|
||||
href="https://storybook.js.org/basics/writing-stories"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Writing Stories
|
||||
</Link>{' '}
|
||||
section in our documentation.
|
||||
</p>
|
||||
<Note>
|
||||
<b>NOTE:</b>
|
||||
<br />
|
||||
Have a look at the <InlineCode>.storybook/webpack.config.js</InlineCode> to add webpack
|
||||
loaders and plugins you are using in this project.
|
||||
</Note>
|
||||
</Main>
|
||||
),
|
||||
};
|
||||
|
||||
export default Welcome;
|
@ -0,0 +1,24 @@
|
||||
/** @jsx m */
|
||||
|
||||
import m from 'mithril';
|
||||
|
||||
import { storiesOf } from '@storybook/mithril';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import Button from '../Button';
|
||||
|
||||
storiesOf('Addons|Actions', module)
|
||||
.add('Action only', () => ({
|
||||
view: () => <Button onclick={action('logo1')}>Click me to log the action</Button>,
|
||||
}))
|
||||
.add('Action and method', () => ({
|
||||
view: () => (
|
||||
<Button
|
||||
onclick={e => {
|
||||
e.preventDefault();
|
||||
action('log2')(e.target);
|
||||
}}
|
||||
>
|
||||
Click me to log the action
|
||||
</Button>
|
||||
),
|
||||
}));
|
@ -0,0 +1,22 @@
|
||||
/** @jsx m */
|
||||
|
||||
import m from 'mithril';
|
||||
|
||||
import { storiesOf } from '@storybook/mithril';
|
||||
|
||||
import backgrounds from '@storybook/addon-backgrounds/mithril';
|
||||
import BaseButton from '../BaseButton';
|
||||
|
||||
storiesOf('Addons|Backgrounds', module)
|
||||
.addDecorator(
|
||||
backgrounds([
|
||||
{ name: 'twitter', value: '#00aced' },
|
||||
{ name: 'facebook', value: '#3b5998', default: true },
|
||||
])
|
||||
)
|
||||
.add('story 1', () => ({
|
||||
view: () => <BaseButton label="You should be able to switch backgrounds for this story" />,
|
||||
}))
|
||||
.add('story 2', () => ({
|
||||
view: () => <BaseButton label="This one too!" />,
|
||||
}));
|
@ -0,0 +1,13 @@
|
||||
/** @jsx m */
|
||||
|
||||
import m from 'mithril';
|
||||
|
||||
import { storiesOf } from '@storybook/mithril';
|
||||
import Centered from '@storybook/addon-centered/mithril';
|
||||
import Button from '../Button';
|
||||
|
||||
storiesOf('Addons|Centered', module)
|
||||
.addDecorator(Centered)
|
||||
.add('button', () => ({
|
||||
view: () => <Button>A button</Button>,
|
||||
}));
|
@ -0,0 +1,71 @@
|
||||
/** @jsx m */
|
||||
|
||||
import m from 'mithril';
|
||||
|
||||
import { storiesOf } from '@storybook/mithril';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import {
|
||||
withKnobs,
|
||||
text,
|
||||
number,
|
||||
boolean,
|
||||
array,
|
||||
select,
|
||||
color,
|
||||
date,
|
||||
button,
|
||||
} from '@storybook/addon-knobs/mithril';
|
||||
|
||||
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 {
|
||||
view: () => <div>{content}</div>,
|
||||
};
|
||||
})
|
||||
.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!'));
|
||||
|
||||
return {
|
||||
view: () => (
|
||||
<div style={`border:2px dotted ${colour}; padding: 8px 22px; border-radius: 8px`}>
|
||||
<h1>My name is {name},</h1>
|
||||
<h3>today is {new Date(today).toLocaleDateString('en-US', dateOptions)}</h3>
|
||||
<p>{stockMessage}</p>
|
||||
<p>Also, I have:</p>
|
||||
<ul>{items.map(item => `<li key=${item}>${item}</li>`).join('')}</ul>
|
||||
<p>{salutation}</p>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
});
|
@ -0,0 +1,11 @@
|
||||
/** @jsx m */
|
||||
|
||||
import m from 'mithril';
|
||||
|
||||
import { storiesOf } from '@storybook/mithril';
|
||||
import { linkTo } from '@storybook/addon-links';
|
||||
import Button from '../Button';
|
||||
|
||||
storiesOf('Addons|Links', module).add('Go to welcome', () => ({
|
||||
view: () => <Button onclick={linkTo('Welcome')}>This buttons links to Welcome</Button>,
|
||||
}));
|
@ -0,0 +1,48 @@
|
||||
/** @jsx m */
|
||||
/* eslint-disable jsx-a11y/accessible-emoji */
|
||||
|
||||
import m from 'mithril';
|
||||
|
||||
import { storiesOf } from '@storybook/mithril';
|
||||
import { withNotes } from '@storybook/addon-notes';
|
||||
|
||||
storiesOf('Addons|Notes', module)
|
||||
.add(
|
||||
'Simple note',
|
||||
withNotes({ text: 'My notes on some bold text' })(() => ({
|
||||
view: () => (
|
||||
<p>
|
||||
<strong>
|
||||
Etiam vulputate elit eu venenatis eleifend. Duis nec lectus augue. Morbi egestas diam
|
||||
sed vulputate mollis. Fusce egestas pretium vehicula. Integer sed neque diam. Donec
|
||||
consectetur velit vitae enim varius, ut placerat arcu imperdiet. Praesent sed faucibus
|
||||
arcu. Nullam sit amet nibh a enim eleifend rhoncus. Donec pretium elementum leo at
|
||||
fermentum. Nulla sollicitudin, mauris quis semper tempus, sem metus tristique diam,
|
||||
efficitur pulvinar mi urna id urna.
|
||||
</strong>
|
||||
</p>
|
||||
),
|
||||
}))
|
||||
)
|
||||
.add(
|
||||
'Note with HTML',
|
||||
withNotes({
|
||||
text: `
|
||||
<h2>My notes on emojies</h2>
|
||||
|
||||
<em>It's not all that important to be honest, but..</em>
|
||||
|
||||
Emojis are great, I love emojis, in fact I like using them in my Component notes too! 😇
|
||||
`,
|
||||
})(() => ({
|
||||
view: () => (
|
||||
<p>
|
||||
<span>🤔😳😯😮</span>
|
||||
<br />
|
||||
<span>😄😩😓😱</span>
|
||||
<br />
|
||||
<span>🤓😑😶😊</span>
|
||||
</p>
|
||||
),
|
||||
}))
|
||||
);
|
25
examples/mithril-kitchen-sink/src/stories/index.stories.js
Normal file
25
examples/mithril-kitchen-sink/src/stories/index.stories.js
Normal file
@ -0,0 +1,25 @@
|
||||
import m from 'mithril';
|
||||
|
||||
import { storiesOf } from '@storybook/mithril';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import { linkTo } from '@storybook/addon-links';
|
||||
|
||||
import Button from '../Button';
|
||||
import Welcome from '../Welcome';
|
||||
|
||||
storiesOf('Welcome', module).add('to Storybook', () => ({
|
||||
view: () => m(Welcome, { showApp: linkTo('Button') }),
|
||||
}));
|
||||
|
||||
storiesOf('Button', module)
|
||||
.add('with text', () => ({
|
||||
view: () => m(Button, { onclick: action('clicked') }, 'Hello Button'),
|
||||
}))
|
||||
.add('with some emoji', () => ({
|
||||
view: () =>
|
||||
m(
|
||||
Button,
|
||||
{ onclick: action('clicked') },
|
||||
m('span', { role: 'img', ariaLabel: 'so cool' }, '😀 😎 👍 💯')
|
||||
),
|
||||
}));
|
@ -0,0 +1 @@
|
||||
../../mithril-kitchen-sink/storybook-static
|
@ -16,6 +16,14 @@ exports[`Storyshots App|acceptance cra-kitchen-sink 1`] = `
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`Storyshots App|acceptance mithril-kitchen-sink 1`] = `
|
||||
<iframe
|
||||
src="/mithril-kitchen-sink/index.html"
|
||||
style="border:0;position:absolute;top:0;left:0;width:100vw;height:100vh"
|
||||
title="mithril-kitchen-sink"
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`Storyshots App|acceptance polymer-cli 1`] = `
|
||||
<iframe
|
||||
src="/polymer-cli/index.html"
|
||||
|
@ -16,7 +16,13 @@ const style = {
|
||||
height: '100vh',
|
||||
};
|
||||
|
||||
['cra-kitchen-sink', 'vue-kitchen-sink', 'angular-cli', 'polymer-cli'].forEach(name => {
|
||||
[
|
||||
'cra-kitchen-sink',
|
||||
'vue-kitchen-sink',
|
||||
'angular-cli',
|
||||
'polymer-cli',
|
||||
'mithril-kitchen-sink',
|
||||
].forEach(name => {
|
||||
chapter.add(
|
||||
name,
|
||||
withNotes(`You must build the storybook for the ${name} example for this story to work.`)(
|
||||
|
@ -17,6 +17,7 @@ import updateOrganisationsGenerator from '../generators/UPDATE_PACKAGE_ORGANIZAT
|
||||
import vueGenerator from '../generators/VUE';
|
||||
import polymerGenerator from '../generators/POLYMER';
|
||||
import webpackReactGenerator from '../generators/WEBPACK_REACT';
|
||||
import mithrilGenerator from '../generators/MITHRIL';
|
||||
|
||||
const logger = console;
|
||||
|
||||
@ -157,6 +158,11 @@ const runGenerator = () => {
|
||||
.then(commandLog('Adding storybook support to your "Polymer" app'))
|
||||
.then(end);
|
||||
|
||||
case types.MITHRIL:
|
||||
return mithrilGenerator()
|
||||
.then(commandLog('Adding storybook support to your "Mithril" app'))
|
||||
.then(end);
|
||||
|
||||
default:
|
||||
paddedLog(`We couldn't detect your project type. (code: ${projectType})`);
|
||||
paddedLog(
|
||||
|
66
lib/cli/generators/MITHRIL/index.js
Normal file
66
lib/cli/generators/MITHRIL/index.js
Normal file
@ -0,0 +1,66 @@
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import JSON5 from 'json5';
|
||||
import mergeDirs from 'merge-dirs';
|
||||
import { getVersions, getPackageJson, writePackageJson } from '../../lib/helpers';
|
||||
|
||||
export default async () => {
|
||||
const [
|
||||
storybookVersion,
|
||||
actionsVersion,
|
||||
linksVersion,
|
||||
addonsVersion,
|
||||
babelCoreVersion,
|
||||
babelPluginTransformReactJsxVersion,
|
||||
] = await getVersions(
|
||||
'@storybook/mithril',
|
||||
'@storybook/addon-actions',
|
||||
'@storybook/addon-links',
|
||||
'@storybook/addons',
|
||||
'babel-core',
|
||||
'babel-plugin-transform-react-jsx'
|
||||
);
|
||||
|
||||
mergeDirs(path.resolve(__dirname, 'template/'), '.', 'overwrite');
|
||||
|
||||
const packageJson = getPackageJson();
|
||||
|
||||
packageJson.dependencies = packageJson.dependencies || {};
|
||||
packageJson.devDependencies = packageJson.devDependencies || {};
|
||||
packageJson.devDependencies['@storybook/addon-actions'] = actionsVersion;
|
||||
packageJson.devDependencies['@storybook/addon-links'] = linksVersion;
|
||||
packageJson.devDependencies['@storybook/addons'] = addonsVersion;
|
||||
packageJson.devDependencies['@storybook/mithril'] = storybookVersion;
|
||||
|
||||
// create or update .babelrc
|
||||
let babelrc = null;
|
||||
if (fs.existsSync('.babelrc')) {
|
||||
const babelrcContent = fs.readFileSync('.babelrc', 'utf8');
|
||||
babelrc = JSON5.parse(babelrcContent);
|
||||
babelrc.plugins = babelrc.plugins || [];
|
||||
|
||||
if (babelrc.plugins.indexOf('babel-plugin-transform-react-jsx') < 0) {
|
||||
babelrc.plugins.push('transform-react-jsx');
|
||||
packageJson.devDependencies[
|
||||
'babel-plugin-transform-react-jsx'
|
||||
] = babelPluginTransformReactJsxVersion;
|
||||
}
|
||||
} else {
|
||||
babelrc = {
|
||||
plugins: ['transform-react-jsx'],
|
||||
};
|
||||
|
||||
packageJson.devDependencies['babel-core'] = babelCoreVersion;
|
||||
packageJson.devDependencies[
|
||||
'babel-plugin-transform-react-jsx'
|
||||
] = babelPluginTransformReactJsxVersion;
|
||||
}
|
||||
|
||||
fs.writeFileSync('.babelrc', JSON.stringify(babelrc, null, 2), 'utf8');
|
||||
|
||||
packageJson.scripts = packageJson.scripts || {};
|
||||
packageJson.scripts.storybook = 'start-storybook -p 6006';
|
||||
packageJson.scripts['build-storybook'] = 'build-storybook';
|
||||
|
||||
writePackageJson(packageJson);
|
||||
};
|
2
lib/cli/generators/MITHRIL/template/.storybook/addons.js
Normal file
2
lib/cli/generators/MITHRIL/template/.storybook/addons.js
Normal file
@ -0,0 +1,2 @@
|
||||
import '@storybook/addon-actions/register';
|
||||
import '@storybook/addon-links/register';
|
9
lib/cli/generators/MITHRIL/template/.storybook/config.js
Normal file
9
lib/cli/generators/MITHRIL/template/.storybook/config.js
Normal file
@ -0,0 +1,9 @@
|
||||
import { configure } from '@storybook/mithril';
|
||||
|
||||
// automatically import all files ending in *.stories.js
|
||||
const req = require.context('../stories', true, /.stories.js$/);
|
||||
function loadStories() {
|
||||
req.keys().forEach(filename => req(filename));
|
||||
}
|
||||
|
||||
configure(loadStories, module);
|
23
lib/cli/generators/MITHRIL/template/stories/Button.js
Normal file
23
lib/cli/generators/MITHRIL/template/stories/Button.js
Normal file
@ -0,0 +1,23 @@
|
||||
/** @jsx m */
|
||||
|
||||
import m from 'mithril';
|
||||
|
||||
const style = {
|
||||
border: '1px solid #eee',
|
||||
borderRadius: '3px',
|
||||
backgroundColor: '#FFFFFF',
|
||||
cursor: 'pointer',
|
||||
fontSize: '15px',
|
||||
padding: '3px 10px',
|
||||
margin: '10px',
|
||||
};
|
||||
|
||||
const Button = {
|
||||
view: vnode => (
|
||||
<button style={style} {...vnode.attrs}>
|
||||
{vnode.children}
|
||||
</button>
|
||||
),
|
||||
};
|
||||
|
||||
export default Button;
|
178
lib/cli/generators/MITHRIL/template/stories/Welcome.js
Normal file
178
lib/cli/generators/MITHRIL/template/stories/Welcome.js
Normal file
@ -0,0 +1,178 @@
|
||||
/** @jsx m */
|
||||
|
||||
import m from 'mithril';
|
||||
import { linkTo, hrefTo } from '@storybook/addon-links';
|
||||
|
||||
const Main = {
|
||||
view: vnode => (
|
||||
<article
|
||||
style={{
|
||||
margin: '15px',
|
||||
maxWidth: '600px',
|
||||
lineHeight: 1.4,
|
||||
fontFamily: '"Helvetica Neue", Helvetica, "Segoe UI", Arial, freesans, sans-serif',
|
||||
}}
|
||||
>
|
||||
{vnode.children}
|
||||
</article>
|
||||
),
|
||||
};
|
||||
|
||||
const Title = {
|
||||
view: vnode => <h1>{vnode.children}</h1>,
|
||||
};
|
||||
|
||||
const Note = {
|
||||
view: vnode => (
|
||||
<p
|
||||
style={{
|
||||
opacity: 0.5,
|
||||
}}
|
||||
>
|
||||
{vnode.children}
|
||||
</p>
|
||||
),
|
||||
};
|
||||
|
||||
const InlineCode = {
|
||||
view: vnode => (
|
||||
<code
|
||||
style={{
|
||||
fontSize: '15px',
|
||||
fontWeight: 600,
|
||||
padding: '2px 5px',
|
||||
border: '1px solid #eae9e9',
|
||||
borderRadius: '4px',
|
||||
backgroundColor: '#f3f2f2',
|
||||
color: '#3a3a3a',
|
||||
}}
|
||||
>
|
||||
{vnode.children}
|
||||
</code>
|
||||
),
|
||||
};
|
||||
|
||||
const Link = {
|
||||
view: vnode => (
|
||||
<a
|
||||
style={{
|
||||
color: '#1474f3',
|
||||
textDecoration: 'none',
|
||||
borderBottom: '1px solid #1474f3',
|
||||
paddingBottom: '2px',
|
||||
}}
|
||||
{...vnode.attrs}
|
||||
>
|
||||
{vnode.children}
|
||||
</a>
|
||||
),
|
||||
};
|
||||
|
||||
const NavButton = {
|
||||
view: vnode => (
|
||||
<button
|
||||
style={{
|
||||
borderTop: 'none',
|
||||
borderRight: 'none',
|
||||
borderLeft: 'none',
|
||||
backgroundColor: 'transparent',
|
||||
padding: 0,
|
||||
cursor: 'pointer',
|
||||
font: 'inherit',
|
||||
}}
|
||||
{...vnode.attrs}
|
||||
>
|
||||
{vnode.children}
|
||||
</button>
|
||||
),
|
||||
};
|
||||
|
||||
const StoryLink = {
|
||||
oninit: vnode => {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
vnode.state.href = '/';
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
vnode.state.onclick = () => {
|
||||
linkTo(vnode.attrs.kind, vnode.attrs.story)();
|
||||
return false;
|
||||
};
|
||||
StoryLink.updateHref(vnode);
|
||||
},
|
||||
updateHref: async vnode => {
|
||||
const href = await hrefTo(vnode.attrs.kind, vnode.attrs.story);
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
vnode.state.href = href;
|
||||
m.redraw();
|
||||
},
|
||||
view: vnode => (
|
||||
<a
|
||||
href={vnode.state.href}
|
||||
style={{
|
||||
color: '#1474f3',
|
||||
textDecoration: 'none',
|
||||
borderBottom: '1px solid #1474f3',
|
||||
paddingBottom: '2px',
|
||||
}}
|
||||
onClick={vnode.state.onclick}
|
||||
>
|
||||
{vnode.children}
|
||||
</a>
|
||||
),
|
||||
};
|
||||
|
||||
const Welcome = {
|
||||
view: vnode => (
|
||||
<Main>
|
||||
<Title>Welcome to storybook</Title>
|
||||
<p>This is a UI component dev environment for your app.</p>
|
||||
<p>
|
||||
We've added some basic stories inside the <InlineCode>src/stories</InlineCode> directory.
|
||||
<br />
|
||||
A story is a single state of one or more UI components. You can have as many stories as you
|
||||
want.
|
||||
<br />
|
||||
(Basically a story is like a visual test case.)
|
||||
</p>
|
||||
<p>
|
||||
See these sample{' '}
|
||||
{vnode.attrs.showApp ? (
|
||||
<NavButton onclick={vnode.attrs.showApp}>stories</NavButton>
|
||||
) : (
|
||||
<StoryLink kind={vnode.attrs.showKind} story={vnode.attrs.showStory}>
|
||||
stories
|
||||
</StoryLink>
|
||||
)}{' '}
|
||||
for a component called <InlineCode>Button</InlineCode>
|
||||
.
|
||||
</p>
|
||||
<p>
|
||||
Just like that, you can add your own components as stories.
|
||||
<br />
|
||||
You can also edit those components and see changes right away.
|
||||
<br />
|
||||
(Try editing the <InlineCode>Button</InlineCode> stories located at{' '}
|
||||
<InlineCode>src/stories/index.js</InlineCode>.)
|
||||
</p>
|
||||
<p>
|
||||
Usually we create stories with smaller UI components in the app.<br />
|
||||
Have a look at the{' '}
|
||||
<Link
|
||||
href="https://storybook.js.org/basics/writing-stories"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Writing Stories
|
||||
</Link>{' '}
|
||||
section in our documentation.
|
||||
</p>
|
||||
<Note>
|
||||
<b>NOTE:</b>
|
||||
<br />
|
||||
Have a look at the <InlineCode>.storybook/webpack.config.js</InlineCode> to add webpack
|
||||
loaders and plugins you are using in this project.
|
||||
</Note>
|
||||
</Main>
|
||||
),
|
||||
};
|
||||
|
||||
export default Welcome;
|
25
lib/cli/generators/MITHRIL/template/stories/index.stories.js
Normal file
25
lib/cli/generators/MITHRIL/template/stories/index.stories.js
Normal file
@ -0,0 +1,25 @@
|
||||
import m from 'mithril';
|
||||
|
||||
import { storiesOf } from '@storybook/mithril';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import { linkTo } from '@storybook/addon-links';
|
||||
|
||||
import Button from './Button';
|
||||
import Welcome from './Welcome';
|
||||
|
||||
storiesOf('Welcome', module).add('to Storybook', () => ({
|
||||
view: () => m(Welcome, { showApp: linkTo('Button') }),
|
||||
}));
|
||||
|
||||
storiesOf('Button', module)
|
||||
.add('with text', () => ({
|
||||
view: () => m(Button, { onclick: action('clicked') }, 'Hello Button'),
|
||||
}))
|
||||
.add('with some emoji', () => ({
|
||||
view: () =>
|
||||
m(
|
||||
Button,
|
||||
{ onclick: action('clicked') },
|
||||
m('span', { role: 'img', ariaLabel: 'so cool' }, '😀 😎 👍 💯')
|
||||
),
|
||||
}));
|
@ -76,6 +76,14 @@ function detectFramework(dependencies) {
|
||||
) {
|
||||
return types.POLYMER;
|
||||
}
|
||||
|
||||
if (
|
||||
(dependencies.dependencies && dependencies.dependencies.mithril) ||
|
||||
(dependencies.devDependencies && dependencies.devDependencies.mithril)
|
||||
) {
|
||||
return types.MITHRIL;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -85,7 +93,7 @@ function isStorybookInstalled(dependencies, force) {
|
||||
}
|
||||
|
||||
if (!force && dependencies.devDependencies) {
|
||||
const supportedFrameworks = ['react', 'react-native', 'vue', 'angular', 'polymer'];
|
||||
const supportedFrameworks = ['react', 'react-native', 'vue', 'angular', 'polymer', 'mithril'];
|
||||
if (
|
||||
supportedFrameworks.reduce(
|
||||
(storybookPresent, framework) =>
|
||||
|
@ -13,4 +13,5 @@ export default {
|
||||
ALREADY_HAS_STORYBOOK: 'ALREADY_HAS_STORYBOOK',
|
||||
UPDATE_PACKAGE_ORGANIZATIONS: 'UPDATE_PACKAGE_ORGANIZATIONS',
|
||||
POLYMER: 'POLYMER',
|
||||
MITHRIL: 'MITHRIL',
|
||||
};
|
||||
|
@ -56,6 +56,7 @@
|
||||
"@storybook/channel-postmessage": "3.4.0-rc.3",
|
||||
"@storybook/channel-websocket": "3.4.0-rc.3",
|
||||
"@storybook/channels": "3.4.0-rc.3",
|
||||
"@storybook/mithril": "3.4.0-rc.3",
|
||||
"@storybook/polymer": "3.4.0-rc.3",
|
||||
"@storybook/react": "3.4.0-rc.3",
|
||||
"@storybook/react-native": "3.4.0-rc.3",
|
||||
|
14
lib/cli/test/fixtures/mithril/package.json
vendored
Normal file
14
lib/cli/test/fixtures/mithril/package.json
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "mithril-fixture",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build": "webpack -p dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"mithril": "^1.1.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
}
|
||||
}
|
5
lib/cli/test/snapshots/mithril/.babelrc
Normal file
5
lib/cli/test/snapshots/mithril/.babelrc
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"plugins": [
|
||||
"transform-react-jsx"
|
||||
]
|
||||
}
|
2
lib/cli/test/snapshots/mithril/.storybook/addons.js
Normal file
2
lib/cli/test/snapshots/mithril/.storybook/addons.js
Normal file
@ -0,0 +1,2 @@
|
||||
import '@storybook/addon-actions/register';
|
||||
import '@storybook/addon-links/register';
|
9
lib/cli/test/snapshots/mithril/.storybook/config.js
Normal file
9
lib/cli/test/snapshots/mithril/.storybook/config.js
Normal file
@ -0,0 +1,9 @@
|
||||
import { configure } from '@storybook/mithril';
|
||||
|
||||
// automatically import all files ending in *.stories.js
|
||||
const req = require.context('../stories', true, /.stories.js$/);
|
||||
function loadStories() {
|
||||
req.keys().forEach(filename => req(filename));
|
||||
}
|
||||
|
||||
configure(loadStories, module);
|
22
lib/cli/test/snapshots/mithril/package.json
Normal file
22
lib/cli/test/snapshots/mithril/package.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "mithril-fixture",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build": "webpack -p dist",
|
||||
"storybook": "start-storybook -p 6006",
|
||||
"build-storybook": "build-storybook"
|
||||
},
|
||||
"dependencies": {
|
||||
"mithril": "^1.1.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@storybook/addon-actions": "^3.4.0-rc.3",
|
||||
"@storybook/addon-links": "^3.4.0-rc.3",
|
||||
"@storybook/addons": "^3.4.0-rc.3",
|
||||
"@storybook/mithril": "3.4.0-rc.3",
|
||||
"babel-core": "^6.26.0",
|
||||
"babel-plugin-transform-react-jsx": "^6.24.1"
|
||||
}
|
||||
}
|
23
lib/cli/test/snapshots/mithril/stories/Button.js
Normal file
23
lib/cli/test/snapshots/mithril/stories/Button.js
Normal file
@ -0,0 +1,23 @@
|
||||
/** @jsx m */
|
||||
|
||||
import m from 'mithril';
|
||||
|
||||
const style = {
|
||||
border: '1px solid #eee',
|
||||
borderRadius: '3px',
|
||||
backgroundColor: '#FFFFFF',
|
||||
cursor: 'pointer',
|
||||
fontSize: '15px',
|
||||
padding: '3px 10px',
|
||||
margin: '10px',
|
||||
};
|
||||
|
||||
const Button = {
|
||||
view: vnode => (
|
||||
<button style={style} {...vnode.attrs}>
|
||||
{vnode.children}
|
||||
</button>
|
||||
),
|
||||
};
|
||||
|
||||
export default Button;
|
178
lib/cli/test/snapshots/mithril/stories/Welcome.js
Normal file
178
lib/cli/test/snapshots/mithril/stories/Welcome.js
Normal file
@ -0,0 +1,178 @@
|
||||
/** @jsx m */
|
||||
|
||||
import m from 'mithril';
|
||||
import { linkTo, hrefTo } from '@storybook/addon-links';
|
||||
|
||||
const Main = {
|
||||
view: vnode => (
|
||||
<article
|
||||
style={{
|
||||
margin: '15px',
|
||||
maxWidth: '600px',
|
||||
lineHeight: 1.4,
|
||||
fontFamily: '"Helvetica Neue", Helvetica, "Segoe UI", Arial, freesans, sans-serif',
|
||||
}}
|
||||
>
|
||||
{vnode.children}
|
||||
</article>
|
||||
),
|
||||
};
|
||||
|
||||
const Title = {
|
||||
view: vnode => <h1>{vnode.children}</h1>,
|
||||
};
|
||||
|
||||
const Note = {
|
||||
view: vnode => (
|
||||
<p
|
||||
style={{
|
||||
opacity: 0.5,
|
||||
}}
|
||||
>
|
||||
{vnode.children}
|
||||
</p>
|
||||
),
|
||||
};
|
||||
|
||||
const InlineCode = {
|
||||
view: vnode => (
|
||||
<code
|
||||
style={{
|
||||
fontSize: '15px',
|
||||
fontWeight: 600,
|
||||
padding: '2px 5px',
|
||||
border: '1px solid #eae9e9',
|
||||
borderRadius: '4px',
|
||||
backgroundColor: '#f3f2f2',
|
||||
color: '#3a3a3a',
|
||||
}}
|
||||
>
|
||||
{vnode.children}
|
||||
</code>
|
||||
),
|
||||
};
|
||||
|
||||
const Link = {
|
||||
view: vnode => (
|
||||
<a
|
||||
style={{
|
||||
color: '#1474f3',
|
||||
textDecoration: 'none',
|
||||
borderBottom: '1px solid #1474f3',
|
||||
paddingBottom: '2px',
|
||||
}}
|
||||
{...vnode.attrs}
|
||||
>
|
||||
{vnode.children}
|
||||
</a>
|
||||
),
|
||||
};
|
||||
|
||||
const NavButton = {
|
||||
view: vnode => (
|
||||
<button
|
||||
style={{
|
||||
borderTop: 'none',
|
||||
borderRight: 'none',
|
||||
borderLeft: 'none',
|
||||
backgroundColor: 'transparent',
|
||||
padding: 0,
|
||||
cursor: 'pointer',
|
||||
font: 'inherit',
|
||||
}}
|
||||
{...vnode.attrs}
|
||||
>
|
||||
{vnode.children}
|
||||
</button>
|
||||
),
|
||||
};
|
||||
|
||||
const StoryLink = {
|
||||
oninit: vnode => {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
vnode.state.href = '/';
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
vnode.state.onclick = () => {
|
||||
linkTo(vnode.attrs.kind, vnode.attrs.story)();
|
||||
return false;
|
||||
};
|
||||
StoryLink.updateHref(vnode);
|
||||
},
|
||||
updateHref: async vnode => {
|
||||
const href = await hrefTo(vnode.attrs.kind, vnode.attrs.story);
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
vnode.state.href = href;
|
||||
m.redraw();
|
||||
},
|
||||
view: vnode => (
|
||||
<a
|
||||
href={vnode.state.href}
|
||||
style={{
|
||||
color: '#1474f3',
|
||||
textDecoration: 'none',
|
||||
borderBottom: '1px solid #1474f3',
|
||||
paddingBottom: '2px',
|
||||
}}
|
||||
onClick={vnode.state.onclick}
|
||||
>
|
||||
{vnode.children}
|
||||
</a>
|
||||
),
|
||||
};
|
||||
|
||||
const Welcome = {
|
||||
view: vnode => (
|
||||
<Main>
|
||||
<Title>Welcome to storybook</Title>
|
||||
<p>This is a UI component dev environment for your app.</p>
|
||||
<p>
|
||||
We've added some basic stories inside the <InlineCode>src/stories</InlineCode> directory.
|
||||
<br />
|
||||
A story is a single state of one or more UI components. You can have as many stories as you
|
||||
want.
|
||||
<br />
|
||||
(Basically a story is like a visual test case.)
|
||||
</p>
|
||||
<p>
|
||||
See these sample{' '}
|
||||
{vnode.attrs.showApp ? (
|
||||
<NavButton onclick={vnode.attrs.showApp}>stories</NavButton>
|
||||
) : (
|
||||
<StoryLink kind={vnode.attrs.showKind} story={vnode.attrs.showStory}>
|
||||
stories
|
||||
</StoryLink>
|
||||
)}{' '}
|
||||
for a component called <InlineCode>Button</InlineCode>
|
||||
.
|
||||
</p>
|
||||
<p>
|
||||
Just like that, you can add your own components as stories.
|
||||
<br />
|
||||
You can also edit those components and see changes right away.
|
||||
<br />
|
||||
(Try editing the <InlineCode>Button</InlineCode> stories located at{' '}
|
||||
<InlineCode>src/stories/index.js</InlineCode>.)
|
||||
</p>
|
||||
<p>
|
||||
Usually we create stories with smaller UI components in the app.<br />
|
||||
Have a look at the{' '}
|
||||
<Link
|
||||
href="https://storybook.js.org/basics/writing-stories"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Writing Stories
|
||||
</Link>{' '}
|
||||
section in our documentation.
|
||||
</p>
|
||||
<Note>
|
||||
<b>NOTE:</b>
|
||||
<br />
|
||||
Have a look at the <InlineCode>.storybook/webpack.config.js</InlineCode> to add webpack
|
||||
loaders and plugins you are using in this project.
|
||||
</Note>
|
||||
</Main>
|
||||
),
|
||||
};
|
||||
|
||||
export default Welcome;
|
25
lib/cli/test/snapshots/mithril/stories/index.stories.js
Normal file
25
lib/cli/test/snapshots/mithril/stories/index.stories.js
Normal file
@ -0,0 +1,25 @@
|
||||
import m from 'mithril';
|
||||
|
||||
import { storiesOf } from '@storybook/mithril';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import { linkTo } from '@storybook/addon-links';
|
||||
|
||||
import Button from './Button';
|
||||
import Welcome from './Welcome';
|
||||
|
||||
storiesOf('Welcome', module).add('to Storybook', () => ({
|
||||
view: () => m(Welcome, { showApp: linkTo('Button') }),
|
||||
}));
|
||||
|
||||
storiesOf('Button', module)
|
||||
.add('with text', () => ({
|
||||
view: () => m(Button, { onclick: action('clicked') }, 'Hello Button'),
|
||||
}))
|
||||
.add('with some emoji', () => ({
|
||||
view: () =>
|
||||
m(
|
||||
Button,
|
||||
{ onclick: action('clicked') },
|
||||
m('span', { role: 'img', ariaLabel: 'so cool' }, '😀 😎 👍 💯')
|
||||
),
|
||||
}));
|
@ -147,6 +147,7 @@
|
||||
"lib/*",
|
||||
"examples/angular-cli",
|
||||
"examples/cra-kitchen-sink",
|
||||
"examples/mithril-kitchen-sink",
|
||||
"examples/polymer-cli",
|
||||
"examples/vue-kitchen-sink",
|
||||
"examples/official-storybook",
|
||||
|
@ -42,6 +42,13 @@ elif [ "$BUILD_CONTEXT" = "POLYMER" ]; then
|
||||
yarn build-storybook
|
||||
mv storybook-static ../../netlify-build
|
||||
popd
|
||||
elif [ "$BUILD_CONTEXT" = "MITHRIL" ]; then
|
||||
echo "netlify-build Mithril examples"
|
||||
pushd examples/mithril-kitchen-sink
|
||||
yarn
|
||||
yarn build-storybook
|
||||
mv storybook-static ../../netlify-build
|
||||
popd
|
||||
elif [ "$BUILD_CONTEXT" = "OFFICIAL" ]; then
|
||||
echo "netlify-build official examples"
|
||||
pushd examples/official-storybook
|
||||
|
@ -10618,6 +10618,10 @@ mississippi@^2.0.0:
|
||||
stream-each "^1.1.0"
|
||||
through2 "^2.0.0"
|
||||
|
||||
mithril@^1.1.6:
|
||||
version "1.1.6"
|
||||
resolved "https://registry.yarnpkg.com/mithril/-/mithril-1.1.6.tgz#bd2cc0de3d3c86076a6a7a30367a601a1bd108f3"
|
||||
|
||||
mixin-deep@^1.2.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.0.tgz#47a8732ba97799457c8c1eca28f95132d7e8150a"
|
||||
|
Loading…
x
Reference in New Issue
Block a user