Merge branch 'master' into add-events-addon

This commit is contained in:
Norbert de Langen 2017-06-06 22:29:30 +02:00 committed by GitHub
commit 925c4d704d
257 changed files with 2972 additions and 2069 deletions

View File

@ -2,7 +2,14 @@ dist
build
coverage
node_modules
**/example/**
**/demo/**
docs/public
*.bundle.js
*.js.map
!.remarkrc.js
!.eslintrc.js
!.eslintrc-markdown.js
!.jest.config.js

56
.eslintrc-markdown.js Normal file
View File

@ -0,0 +1,56 @@
const error = 2;
const warn = 1;
const ignore = 0;
module.exports = {
root: true,
extends: ['eslint-config-airbnb', 'plugin:jest/recommended', 'prettier'],
plugins: ['prettier', 'jest', 'react'],
parser: 'babel-eslint',
parserOptions: {
sourceType: 'module',
},
env: {
es6: true,
node: true,
'jest/globals': true,
},
globals: {
storiesOf: true,
addonAPI: true,
__DEV__: true,
fetch: true,
},
rules: {
strict: [error, 'never'],
'prettier/prettier': [
warn,
{
printWidth: 100,
tabWidth: 2,
bracketSpacing: true,
trailingComma: 'es5',
singleQuote: true,
},
],
'no-console': ignore,
'global-require': ignore,
quotes: [warn, 'single'],
'no-unused-vars': ignore,
'class-methods-use-this': ignore,
'arrow-parens': [warn, 'as-needed'],
'space-before-function-paren': ignore,
'import/no-unresolved': ignore,
'import/extensions': ignore,
'import/no-extraneous-dependencies': ignore,
'import/prefer-default-export': ignore,
'react/prop-types': ignore,
'react/jsx-wrap-multilines': ignore,
'react/jsx-uses-react': error,
'react/jsx-uses-vars': error,
'react/react-in-jsx-scope': ignore,
'react/jsx-filename-extension': ignore,
'jsx-a11y/accessible-emoji': ignore,
'react/no-unescaped-entities': ignore,
},
};

View File

@ -1,11 +1,11 @@
const error = 2;
const warn = 1;
const ignore = 0;
module.exports = {
root: true,
extends: [
'./node_modules/eslint-config-airbnb-base/rules/es6.js',
],
plugins: [
'prettier',
],
extends: ['eslint-config-airbnb', 'plugin:jest/recommended', 'prettier'],
plugins: ['prettier', 'jest', 'react', 'json'],
parser: 'babel-eslint',
parserOptions: {
sourceType: 'module',
@ -13,17 +13,56 @@ module.exports = {
env: {
es6: true,
node: true,
'jest/globals': true,
},
rules: {
strict: 0,
'prettier/prettier': ['warn', {
printWidth: 100,
tabWidth: 2,
bracketSpacing: true,
trailingComma: 'es5',
singleQuote: true,
}],
quotes: ['warn', 'single'],
'arrow-parens': ['warn', 'as-needed'],
strict: [error, 'never'],
'prettier/prettier': [
warn,
{
printWidth: 100,
tabWidth: 2,
bracketSpacing: true,
trailingComma: 'es5',
singleQuote: true,
},
],
quotes: [warn, 'single'],
'class-methods-use-this': ignore,
'arrow-parens': [warn, 'as-needed'],
'space-before-function-paren': ignore,
'import/no-unresolved': warn,
'import/extensions': [
warn,
{
js: 'never',
json: 'always',
},
],
'import/no-extraneous-dependencies': [
warn,
{
devDependencies: [
'**/*.test.js',
'**/scripts/*.js',
'**/stories/*.js',
'**/__tests__/*.js',
],
peerDependencies: true,
},
],
'import/prefer-default-export': ignore,
'react/jsx-wrap-multilines': ignore,
'react/jsx-uses-react': error,
'react/jsx-uses-vars': error,
'react/react-in-jsx-scope': error,
'react/jsx-filename-extension': [
warn,
{
extensions: ['.js', '.jsx'],
},
],
'jsx-a11y/accessible-emoji': ignore,
'react/no-unescaped-entities': ignore,
},
}
};

View File

@ -1,16 +0,0 @@
{
"plugins": [
"remark-preset-lint-recommended",
["remark-lint-list-item-indent", false],
["remark-lint-code", {"js": {
"module": "node_modules/remark-lint-code-eslint",
"options": {
"fix": true
}
}}],
["remark-toc", {
"tight": true,
"maxDepth": 3
}]
]
}

25
.remarkrc.js Normal file
View File

@ -0,0 +1,25 @@
module.exports = {
plugins: [
'remark-preset-lint-recommended',
['remark-lint-list-item-indent', false],
[
'remark-lint-code',
{
js: {
module: 'node_modules/remark-lint-code-eslint',
options: {
fix: true,
configFile: '.eslintrc-markdown.js',
},
},
},
],
[
'remark-toc',
{
tight: true,
maxDepth: 3,
},
],
],
};

View File

@ -1,3 +1,33 @@
# 3.0.1
Minor bug fixes and documentation updates post 3.0.0 release.
2017-June-06
#### Bug Fixes
- Added error message for `addon-options` [#1194](https://github.com/storybooks/storybook/pull/1194)
- Fix(react-native) add missing `ws` dependency [#1174](https://github.com/storybooks/storybook/pull/1174)
- Fix terminal colors by reset console colors explicitly [#1184](https://github.com/storybooks/storybook/pull/1184)
- Fix addon panel layout styling [#1170](https://github.com/storybooks/storybook/pull/1170)
- ADD https import & remove tracking code remains [#1176](https://github.com/storybooks/storybook/pull/1176)
- Fix incorrect babel config file reading [#1156](https://github.com/storybooks/storybook/pull/1156)
- Fixed withKnobs definition. [#1164](https://github.com/storybooks/storybook/pull/1164)
#### Documentation
- Fixed typo in react-native browser instructions [#1189](https://github.com/storybooks/storybook/pull/1189)
- Add instruction for npm install with -D for development dependency [#1168](https://github.com/storybooks/storybook/pull/1168)
- Fix broken link for [addons] in README [#1167](https://github.com/storybooks/storybook/pull/1167)
- Refreshed logo in docs [#1149](https://github.com/storybooks/storybook/pull/1149)
- fix addon broken links in documentation [#1165](https://github.com/storybooks/storybook/pull/1165)
- start-storybook cli - expand commands descriptions [#1161](https://github.com/storybooks/storybook/pull/1161)
- Fix typo in codemod readme [#1158](https://github.com/storybooks/storybook/pull/1158)
#### Dependency Upgrades
- Replaced deprecated `markdown-to-react-components` with `marksy` [#1188](https://github.com/storybooks/storybook/pull/1188)
# 3.0.0
Storybook 3.0 is our first fully community-driven release! Notable changes:
@ -27,7 +57,7 @@ Storybook 3.0 is our first fully community-driven release! Notable changes:
- FIX addon info and addon storyshots incompatibility [#1129](https://github.com/storybooks/storybook/pull/1129)
- FIX postcss options missing in default webpack config && UPDATE dependencies [#1087](https://github.com/storybooks/storybook/pull/1087)
- Fix CLI had a package version from storybook hardcoded - now queries npm registry [#1079](https://github.com/storybooks/storybook/pull/1079)
- Fix semi broken __docgenInfo integration in addon info [#1030](https://github.com/storybooks/storybook/pull/1030)
- Fix semi broken \_\_docgenInfo integration in addon info [#1030](https://github.com/storybooks/storybook/pull/1030)
- Fix: build-storybook no longer supports relative paths [#1058](https://github.com/storybooks/storybook/pull/1058)
- Fix for types `number` for addon knobs [#1001](https://github.com/storybooks/storybook/pull/1001)
- Fix webpack overriding && Add an example with local file dependencies [#965](https://github.com/storybooks/storybook/pull/965)
@ -86,12 +116,12 @@ Storybook 3.0 is our first fully community-driven release! Notable changes:
- Added an upgrade mode to getstorybook [#1146](https://github.com/storybooks/storybook/pull/1146)
- Update link to Storyshots addon [#1074](https://github.com/storybooks/storybook/pull/1074)
- Added error message for missing or invalid storyName [#747](https://github.com/storybooks/storybook/pull/747)
- Opened an Open Collective Account https://opencollective.com/storybook [#1065](https://github.com/storybooks/storybook/pull/1065)
- Opened an Open Collective Account <https://opencollective.com/storybook> [#1065](https://github.com/storybooks/storybook/pull/1065)
- Add propTablesExclude option [#924](https://github.com/storybooks/storybook/pull/924)
- addon-info: make the info overlay be fixed [#914](https://github.com/storybooks/storybook/pull/914)
- Handle null elements in getData [#926](https://github.com/storybooks/storybook/pull/926)
- add description field from __docgenInfo for prop table for info plugin [#929](https://github.com/storybooks/storybook/pull/929)
- #959 add a max-height and center element with alignItems: center [#961](https://github.com/storybooks/storybook/pull/961)
- add description field from \_\_docgenInfo for prop table for info plugin [#929](https://github.com/storybooks/storybook/pull/929)
- \#959 add a max-height and center element with alignItems: center [#961](https://github.com/storybooks/storybook/pull/961)
- Switch to the only prepublish script [#903](https://github.com/storybooks/storybook/pull/903)
- PR review policy [#923](https://github.com/storybooks/storybook/pull/923)
- Add typescript definitions for getStorybook() [#753](https://github.com/storybooks/storybook/pull/753)

View File

@ -93,10 +93,10 @@ We welcome contributions to Storybook!
> boolean check if code conforms to linting rules - uses remark & eslint
- `npm run lint:js` - will check js
- `npm run lint:markdown` - will check markdown + code samples
- `npm run lint:md` - will check markdown + code samples
- `npm run lint:js -- --fix` - will automatically fix js
- `npm run lint:markdown -- -o` - will automatically fix markdown
- `npm run lint:md -- -o` - will automatically fix markdown
#### `npm run test`

View File

@ -18,6 +18,7 @@ This addon works with Storybook for:
## Getting Started
Install:
```sh
npm i -D @storybook/addon-actions
```
@ -27,8 +28,10 @@ Import the `action` function and use it to create actions handlers. When creatin
> _Note: Make sure NOT to use reserved words as function names. [issues#29](https://github.com/storybooks/storybook-addon-actions/issues/29#issuecomment-288274794)_
```js
import { storiesOf } from '@storybook/react'
import { action } from '@storybook/addon-actions'
import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import Button from './button';
storiesOf('Button', module)
.add('default view', () => (
@ -47,6 +50,8 @@ If you wish to process action data before sending them over to the logger, you c
```js
import { action, decorateAction } from '@storybook/addon-actions'
import Button from './button';
const firstArgAction = decorateAction([
args => args.slice(0, 1)
]);

View File

@ -1,31 +1,25 @@
{
"name": "@storybook/addon-actions",
"version": "3.0.0",
"version": "3.0.1",
"description": "Action Logger addon for storybook",
"keywords": [
"storybook"
],
"homepage": "https://github.com/storybooks/storybook/tree/master/addons/actions",
"bugs": {
"url": "https://github.com/storybooks/storybook/issues"
},
"license": "MIT",
"main": "dist/index.js",
"repository": {
"type": "git",
"url": "https://github.com/storybooks/storybook.git"
},
"scripts": {
"deploy-storybook": "storybook-to-ghpages",
"prepublish": "node ../../scripts/prepublish.js",
"storybook": "start-storybook -p 9001"
},
"repository": {
"type": "git",
"url": "git+https://github.com/storybooks/storybook.git"
},
"keywords": [
"storybook"
],
"license": "MIT",
"bugs": {
"url": "https://github.com/storybooks/storybook/issues"
},
"homepage": "https://github.com/storybooks/storybook/tree/master/addons/actions",
"devDependencies": {
"react": "^15.5.4",
"react-dom": "^15.5.4",
"react-test-renderer": "^15.5.4",
"shelljs": "^0.7.7"
},
"dependencies": {
"@storybook/addons": "^3.0.0",
"deep-equal": "^1.0.1",
@ -33,6 +27,12 @@
"prop-types": "^15.5.8",
"react-inspector": "^2.0.0"
},
"devDependencies": {
"react": "^15.5.4",
"react-dom": "^15.5.4",
"react-test-renderer": "^15.5.4",
"shelljs": "^0.7.7"
},
"peerDependencies": {
"react": "*",
"react-dom": "*"

View File

@ -5,7 +5,7 @@ import style from './style';
class ActionLogger extends Component {
componentDidUpdate() {
const latest = this.refs.latest;
const latest = this.ref.latest;
if (latest) {
const borderLeft = style.action.borderLeft;
latest.style.borderLeft = 'solid 5px #aaa';
@ -15,8 +15,12 @@ class ActionLogger extends Component {
}
}
getActionData() {
return this.props.actions.map((action, i) => this.renderAction(action, i));
}
renderAction(action, i) {
const ref = i ? '' : 'latest';
const ref = () => (this.ref = i ? '' : 'latest');
const counter = <div style={style.counter}>{action.count}</div>;
return (
<div ref={ref} key={action.id} style={style.action}>
@ -34,10 +38,6 @@ class ActionLogger extends Component {
);
}
getActionData() {
return this.props.actions.map((action, i) => this.renderAction(action, i));
}
render() {
return (
<div style={style.wrapper}>
@ -50,7 +50,11 @@ class ActionLogger extends Component {
ActionLogger.propTypes = {
onClear: PropTypes.func,
actions: PropTypes.array,
actions: PropTypes.array, // eslint-disable-line react/forbid-prop-types
};
ActionLogger.defaultProps = {
onClear: () => {},
actions: [],
};
export default ActionLogger;

View File

@ -1,5 +1,9 @@
/* eslint-disable no-underscore-dangle */
import React from 'react';
import PropTypes from 'prop-types';
import deepEqual from 'deep-equal';
import ActionLoggerComponent from '../../components/ActionLogger/';
import { EVENT_ID } from '../../';
@ -10,14 +14,22 @@ export default class ActionLogger extends React.Component {
this._actionListener = action => this.addAction(action);
}
componentDidMount() {
this.props.channel.on(EVENT_ID, this._actionListener);
}
componentWillUnmount() {
this.props.channel.removeListener(EVENT_ID, this._actionListener);
}
addAction(action) {
action.data.args = action.data.args.map(arg => JSON.parse(arg));
action.data.args = action.data.args.map(arg => JSON.parse(arg)); // eslint-disable-line
const actions = [...this.state.actions];
const previous = actions.length && actions[0];
if (previous && deepEqual(previous.data, action.data, { strict: true })) {
previous.count++;
previous.count++; // eslint-disable-line
} else {
action.count = 1;
action.count = 1; // eslint-disable-line
actions.unshift(action);
}
this.setState({ actions });
@ -27,14 +39,6 @@ export default class ActionLogger extends React.Component {
this.setState({ actions: [] });
}
componentDidMount() {
this.props.channel.on(EVENT_ID, this._actionListener);
}
componentWillUnmount() {
this.props.channel.removeListener(EVENT_ID, this._actionListener);
}
render() {
const props = {
actions: this.state.actions,
@ -43,3 +47,10 @@ export default class ActionLogger extends React.Component {
return <ActionLoggerComponent {...props} />;
}
}
ActionLogger.propTypes = {
channel: PropTypes.object, // eslint-disable-line react/forbid-prop-types
};
ActionLogger.defaultProps = {
channel: {},
};

View File

@ -1,5 +1,5 @@
// addons, panels and events get unique names using a prefix
export const ADDON_ID = 'storybook/addon-actions';
export const ADDON_ID = 'storybook/actions';
export const PANEL_ID = `${ADDON_ID}/actions-panel`;
export const EVENT_ID = `${ADDON_ID}/action-event`;

View File

@ -4,7 +4,7 @@ import ActionLogger from './containers/ActionLogger';
import { ADDON_ID, PANEL_ID } from './';
export function register() {
addons.register(ADDON_ID, api => {
addons.register(ADDON_ID, () => {
const channel = addons.getChannel();
addons.addPanel(PANEL_ID, {
title: 'Action Logger',

View File

@ -1,3 +1,5 @@
/* eslint-disable no-underscore-dangle */
import addons from '@storybook/addons';
import stringify from 'json-stringify-safe';
import { EVENT_ID } from './';
@ -10,6 +12,7 @@ function _format(arg) {
}
export function action(name) {
// eslint-disable-next-line no-unused-vars, func-names
const handler = function(..._args) {
const args = Array.from(_args).map(_format);
const channel = addons.getChannel();
@ -27,13 +30,16 @@ export function action(name) {
//
// Ref: https://bocoup.com/weblog/whats-in-a-function-name
const fnName = name ? name.replace(/\W+/g, '_') : 'action';
// eslint-disable-next-line no-eval
const named = eval(`(function ${fnName}() { return handler.apply(this, arguments) })`);
return named;
}
export function decorateAction(decorators) {
// eslint-disable-next-line no-unused-vars, func-names
return function(name) {
const callAction = action(name);
// eslint-disable-next-line no-unused-vars, func-names
return function(..._args) {
const decorated = decorators.reduce((args, fn) => fn(args), _args);
callAction(...decorated);

View File

@ -15,7 +15,7 @@ This addon works with Storybook for:
### Usage
```sh
npm i @storybook/addon-centered
npm install @storybook/addon-centered --save-dev
```
#### As a decorator
@ -23,11 +23,11 @@ npm i @storybook/addon-centered
You can set the decorator locally:
```js
import React from 'react';
import { storiesOf } from '@storybook/react';
import MyComponent from '../Component';
import centered from '@storybook/addon-centered';
import MyComponent from '../Component';
storiesOf('MyComponent', module)
.addDecorator(centered)
.add('without props', () => (<MyComponent />))
@ -52,7 +52,6 @@ configure(function () {
1 - Configure the extension
```js
import React from 'react';
import { configure, setAddon } from '@storybook/react';
import centered from '@storybook/addon-centered';
@ -61,7 +60,7 @@ setAddon({
this.add(storyName, (context) => (
centered.call(context, storyFn)
));
}
},
});
configure(function () {
@ -72,8 +71,8 @@ configure(function () {
2 - Use it in your story
```js
import React from 'react';
import { storiesOf } from '@storybook/react';
import Component from '../Component';
storiesOf('Component', module)

View File

@ -2,10 +2,13 @@
"name": "@storybook/addon-centered",
"version": "3.0.0",
"description": "Storybook decorator to center components",
"license": "MIT",
"author": "Muhammed Thanish <mnmtanish@gmail.com>",
"main": "dist/index.js",
"scripts": {
"prepublish": "node ../../scripts/prepublish.js"
},
"author": "Muhammed Thanish <mnmtanish@gmail.com>",
"license": "MIT"
"peerDependencies": {
"react": "*"
}
}

View File

@ -1,2 +1,3 @@
const manager = require('./dist/manager');
manager.init();

View File

@ -13,7 +13,7 @@
"main": "preview.js",
"repository": {
"type": "git",
"url": "git+https://github.com/storybooks/storybook.git"
"url": "https://github.com/storybooks/storybook.git"
},
"scripts": {
"prepublish": "node ../../scripts/prepublish.js",
@ -27,6 +27,7 @@
"babel-runtime": "^6.23.0",
"deep-equal": "^1.0.1",
"events": "^1.1.1",
"global": "^4.3.2",
"insert-css": "^1.0.0",
"marked": "^0.3.6",
"moment": "^2.18.1",

View File

@ -1,2 +1,3 @@
const preview = require('./dist/preview');
preview.init();

View File

@ -10,16 +10,20 @@ const buttonStyles = {
padding: '3px 10px',
};
const Button = ({ children, onClick, style = {} }) => (
const Button = ({ children, onClick, style = {} }) =>
<button style={{ ...buttonStyles, ...style }} onClick={onClick}>
{children}
</button>
);
</button>;
Button.defaultProps = {
onClick: () => {},
style: {},
};
Button.propTypes = {
children: PropTypes.string.isRequired,
onClick: PropTypes.func,
style: PropTypes.object,
style: PropTypes.object, // eslint-disable-line react/forbid-prop-types
};
export default Button;

View File

@ -1,13 +1,12 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { window } from 'global';
import Textarea from 'react-textarea-autosize';
import marked from 'marked';
import style from './style';
const renderer = new marked.Renderer();
renderer.heading = function(text) {
return text;
};
renderer.heading = text => text;
marked.setOptions({
renderer,
@ -43,8 +42,10 @@ export default class CommentForm extends Component {
}
openLogin() {
const signInUrl = `https://hub.getstorybook.io/sign-in?redirectUrl=${encodeURIComponent(location.href)}`;
location.href = signInUrl;
const signInUrl = `https://hub.getstorybook.io/sign-in?redirectUrl=${encodeURIComponent(
window.location.href
)}`;
window.location.href = signInUrl;
}
handleKeyDown(e) {
@ -58,7 +59,7 @@ export default class CommentForm extends Component {
if (!this.props.user) {
return (
<div style={style.wrapper}>
<Textarea style={style.input} disabled={true} />
<Textarea style={style.input} disabled />
<button style={style.submitButton} onClick={() => this.openLogin()}>
Sign in with Storybook Hub
</button>
@ -84,6 +85,11 @@ export default class CommentForm extends Component {
}
}
CommentForm.defaultProps = {
user: null,
addComment: () => {},
};
CommentForm.propTypes = {
user: PropTypes.object, // eslint-disable-line react/forbid-prop-types
addComment: PropTypes.func,
};

View File

@ -1,5 +1,6 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { window } from 'global';
import moment from 'moment';
import renderHTML from 'react-render-html';
import insertCss from 'insert-css';
@ -26,7 +27,7 @@ export default class CommentItem extends Component {
}
deleteComment() {
const confirmDelete = confirm('Are you sure you want to delete this comment?');
const confirmDelete = window.confirm('Are you sure you want to delete this comment?');
if (confirmDelete === true) {
this.props.deleteComment();
}
@ -34,7 +35,7 @@ export default class CommentItem extends Component {
renderDelete() {
return (
<a href="#" style={style.commentDelete} onClick={this.deleteComment}>
<a style={style.commentDelete} onClick={this.deleteComment} role="button" tabIndex="0">
delete
</a>
);
@ -69,8 +70,19 @@ export default class CommentItem extends Component {
}
}
CommentItem.defaultProps = {
comment: {},
deleteComment: () => {},
ownComment: false,
};
CommentItem.propTypes = {
deleteComment: PropTypes.func,
comment: PropTypes.object,
comment: PropTypes.shape({
user: PropTypes.object,
text: PropTypes.string,
time: PropTypes.oneOfType([PropTypes.string, PropTypes.instanceOf(Date)]),
loading: PropTypes.bool,
}),
ownComment: PropTypes.bool,
};

View File

@ -37,21 +37,29 @@ export default class CommentList extends Component {
}}
style={style.wrapper}
>
{comments.map((comment, idx) => (
{comments.map(comment =>
<CommentItem
key={`comment_${idx}`}
key={comment.id}
comment={comment}
ownComment={comment.userId === (this.props.user && this.props.user.id)}
deleteComment={() => this.props.deleteComment(comment.id)}
/>
))}
)}
</div>
);
}
}
CommentList.defaultProps = {
comments: [],
user: null,
deleteComment: () => {},
};
CommentList.propTypes = {
comments: PropTypes.array,
user: PropTypes.object,
comments: PropTypes.arrayOf(PropTypes.object),
user: PropTypes.shape({
id: PropTypes.number,
}),
deleteComment: PropTypes.func,
};

View File

@ -1,41 +1,43 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import React from 'react';
import CommentList from '../CommentList';
import CommentForm from '../CommentForm';
import style from './style';
export default class CommentsPanel extends Component {
render() {
if (this.props.loading) {
return (
<div style={style.wrapper}>
<div style={style.message}>loading...</div>
</div>
);
}
if (this.props.appNotAvailable) {
const appsUrl = 'https://hub.getstorybook.io/apps';
return (
<div style={style.wrapper}>
<div style={style.message}>
<a style={style.button} href={appsUrl}>Create an app for this repo on Storybook Hub</a>
</div>
</div>
);
}
export default function CommentsPanel(props) {
if (props.loading) {
return (
<div style={style.wrapper}>
<CommentList key="list" {...this.props} />
<CommentForm key="form" {...this.props} />
<div style={style.message}>loading...</div>
</div>
);
}
if (props.appNotAvailable) {
const appsUrl = 'https://hub.getstorybook.io/apps';
return (
<div style={style.wrapper}>
<div style={style.message}>
<a style={style.button} href={appsUrl}>Create an app for this repo on Storybook Hub</a>
</div>
</div>
);
}
return (
<div style={style.wrapper}>
<CommentList key="list" {...props} />
<CommentForm key="form" {...props} />
</div>
);
}
CommentsPanel.defaultProps = {
loading: false,
appNotAvailable: false,
};
CommentsPanel.propTypes = {
loading: PropTypes.bool,
user: PropTypes.object,
appNotAvailable: PropTypes.bool,
};

View File

@ -13,16 +13,16 @@ export default class DataStore {
this.eventStore = new EventEmitter();
}
_addToCache(currentStory, comments) {
const key = this._getStoryKey(currentStory);
addToCache(currentStory, comments) {
const key = this.getStoryKey(currentStory);
this.cache[key] = {
comments,
addedAt: Date.now(),
};
}
_getFromCache(currentStory) {
const key = this._getStoryKey(currentStory);
getFromCache(currentStory) {
const key = this.getStoryKey(currentStory);
const item = this.cache[key];
if (!item) {
@ -44,14 +44,14 @@ export default class DataStore {
return { comments, invalidated };
}
_reloadCurrentComments() {
if (this._stopReloading) {
clearInterval(this._stopReloading);
reloadCurrentComments() {
if (this.stopReloading) {
clearInterval(this.stopReloading);
}
this._stopReloading = setInterval(
this.stopReloading = setInterval(
() => {
this._loadUsers().then(() => this._loadComments());
this.loadUsers().then(() => this.loadComments());
},
1000 * 60 // Reload for every minute
);
@ -63,14 +63,14 @@ export default class DataStore {
// We don't need to do anything if the there's no loggedIn user.
// if (!this.user) return;
this._reloadCurrentComments();
const item = this._getFromCache(this.currentStory);
this.reloadCurrentComments();
const item = this.getFromCache(this.currentStory);
if (item) {
this._fireComments(item.comments);
this.fireComments(item.comments);
// if the cache invalidated we need to load comments again.
if (item.invalidated) {
return this._loadUsers().then(() => this._loadComments());
return this.loadUsers().then(() => this.loadComments());
}
return Promise.resolve(null);
}
@ -78,21 +78,23 @@ export default class DataStore {
// load comments for the first time.
// TODO: send a null and handle the loading part in the UI side.
this.eventStore.emit('loading', true);
this._fireComments([]);
this._loadUsers().then(() => this._loadComments()).then(() => {
this.fireComments([]);
this.loadUsers().then(() => this.loadComments()).then(() => {
this.eventStore.emit('loading', false);
return Promise.resolve(null);
});
return this.currentStory;
}
setCurrentUser(user) {
this.user = user;
}
_loadUsers() {
loadUsers() {
const query = {};
const options = { limit: 1e6 };
return this.db.persister._getAppInfo().then(info => {
return this.db.persister.getAppInfo().then(info => {
if (!info) {
return null;
}
@ -108,38 +110,37 @@ export default class DataStore {
});
}
_loadComments() {
loadComments() {
const currentStory = { ...this.currentStory };
const query = currentStory;
const options = { limit: 1e6 };
return this.db.persister._getAppInfo().then(info => {
return this.db.persister.getAppInfo().then(info => {
if (!info) {
return null;
}
return this.db.getCollection('comments').get(query, options).then(comments => {
// add to cache
this._addToCache(currentStory, comments);
this.addToCache(currentStory, comments);
// set comments only if we are on the relavant story
if (deepEquals(currentStory, this.currentStory)) {
this._fireComments(comments);
this.fireComments(comments);
}
});
});
}
_getStoryKey(currentStory) {
getStoryKey(currentStory) {
return `${currentStory.sbKind}:::${currentStory.sbStory}`;
}
_fireComments(comments) {
fireComments(comments) {
this.callbacks.forEach(callback => {
// link user to the comment directly
comments.forEach(comment => {
comment.user = this.users[comment.userId];
});
callback(comments);
const commentsWithUser = comments.map(comment =>
Object.assign({}, comment, { user: this.users[comment.userId] })
);
callback(commentsWithUser);
});
}
@ -153,27 +154,27 @@ export default class DataStore {
return stop;
}
_addPendingComment(comment) {
addPendingComment(comment) {
// Add the pending comment.
const pendingComment = { ...comment, loading: true };
const { comments: existingComments } = this._getFromCache(this.currentStory);
const { comments: existingComments } = this.getFromCache(this.currentStory);
const updatedComments = existingComments.concat(pendingComment);
this._fireComments(updatedComments);
this.fireComments(updatedComments);
return Promise.resolve(null);
}
_setDeletedComment(commentId) {
const { comments } = this._getFromCache(this.currentStory);
setDeletedComment(commentId) {
const { comments } = this.getFromCache(this.currentStory);
const deleted = comments.find(c => c.id === commentId);
if (deleted) {
deleted.loading = true;
}
this._fireComments(comments);
this.fireComments(comments);
return Promise.resolve(null);
}
_addAuthorToTheDatabase() {
addAuthorToTheDatabase() {
if (this.users[this.user.id]) {
// user exists in the DB.
return Promise.resolve(null);
@ -188,33 +189,34 @@ export default class DataStore {
// NOTE the "sbProtected" makes sure only the author can modify
// or delete a comment after its saved on the cloud database.
_addCommentToDatabase(comment) {
addCommentToDatabase(comment) {
const doc = {
...comment,
...this.currentStory,
...this.currentStory,
sbProtected: true,
};
return this.db.getCollection('comments').set(doc);
}
_deleteCommentOnDatabase(commentId) {
deleteCommentOnDatabase(commentId) {
const query = { id: commentId };
return this.db.getCollection('comments').del(query);
}
addComment(comment) {
return this._addAuthorToTheDatabase()
.then(() => this._addPendingComment(comment))
.then(() => this._addCommentToDatabase(comment))
.then(() => this._loadUsers())
.then(() => this._loadComments());
return this.addAuthorToTheDatabase()
.then(() => this.addPendingComment(comment))
.then(() => this.addCommentToDatabase(comment))
.then(() => this.loadUsers())
.then(() => this.loadComments());
}
deleteComment(commentId) {
return this._setDeletedComment(commentId)
.then(() => this._deleteCommentOnDatabase(commentId))
.then(() => this._loadComments());
return this.setDeletedComment(commentId)
.then(() => this.deleteCommentOnDatabase(commentId))
.then(() => this.loadComments());
}
onLoading(cb) {

View File

@ -5,7 +5,7 @@ const user = {
name: 'user-name',
};
const dbGetUsers = jest.fn(a => Promise.resolve([user]));
const dbGetUsers = jest.fn(() => Promise.resolve([user]));
const dbSetUsers = jest.fn(a => Promise.resolve(a));
const dbGetComments = jest.fn(a => Promise.resolve(a));
const dbSetComments = jest.fn(a => Promise.resolve(a));
@ -16,7 +16,7 @@ const db = {
case 'users': {
return {
get: dbGetUsers,
set: dbGetUsers,
set: dbSetUsers,
};
}
case 'comments': {
@ -25,10 +25,13 @@ const db = {
set: dbSetComments,
};
}
default: {
return {};
}
}
},
persister: {
_getAppInfo() {
getAppInfo() {
return Promise.resolve(true);
},
},

View File

@ -41,23 +41,23 @@ export default class Container extends Component {
this.stopListingStoreLoading();
}
_getAppInfo(persister) {
getAppInfo(persister) {
return persister
._getAppInfo()
.then(appInfo => Promise.resolve(appInfo), err => Promise.resolve(null));
.getAppInfo()
.then(appInfo => Promise.resolve(appInfo), () => Promise.resolve(null));
}
init() {
const db = addons.getDatabase();
if (typeof db.persister._getUser !== 'function') {
if (typeof db.persister.getUser !== 'function') {
throw new Error('unable to get user info');
}
this.setState({ loading: true });
db.persister
._getUser()
.then(u => Promise.resolve(u), err => Promise.resolve(null))
.getUser()
.then(u => Promise.resolve(u), () => Promise.resolve(null))
.then(user => {
if (user) {
this.store.setCurrentUser(user);
@ -65,7 +65,7 @@ export default class Container extends Component {
} else {
this.setState({ user: null });
}
return this._getAppInfo(db.persister);
return this.getAppInfo(db.persister);
})
.then(appInfo => {
const updatedState = { loading: false };
@ -108,5 +108,7 @@ export default class Container extends Component {
}
Container.propTypes = {
api: PropTypes.object,
api: PropTypes.shape({
onStory: PropTypes.func.isRequired,
}).isRequired,
};

View File

@ -1,6 +1,9 @@
/* eslint-disable import/no-extraneous-dependencies, import/no-unresolved */
import React from 'react';
import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import Button from '../index';
import CommentForm from '../manager/components/CommentForm';
import CommentList from '../manager/components/CommentList';
@ -28,7 +31,8 @@ const commentsList = [
name: 'User B',
},
time: 'Wed Oct 12 2016 13:38:46 GMT+0530 (IST)',
text: 'Vivamus tortor nisi, <b>efficitur</b> in rutrum <em>ac</em>, tempor <code>et mauris</code>. In et rutrum enim',
text:
'Vivamus tortor nisi, <b>efficitur</b> in rutrum <em>ac</em>, tempor <code>et mauris</code>. In et rutrum enim',
},
{
loading: true,
@ -56,14 +60,14 @@ storiesOf('Button', module)
storiesOf('Components', module)
.add('CommentForm', () => <CommentForm addComment={action('addComment')} />)
.add('CommentList - No Comments', () => <CommentList comments={[]} />)
.add('CommentList - with comments', () => (
.add('CommentList - with comments', () =>
<CommentList user={userObj} comments={commentsList} deleteComment={action('deleteComment')} />
))
)
.add('CommentPanel - not loggedIn', () => <CommentsPanel />)
.add('CommentPanel - app not available', () => (
.add('CommentPanel - app not available', () =>
<CommentsPanel user={userObj} appNotAvailable={{}} />
))
.add('CommentPanel - loggedIn with no comments', () => (
)
.add('CommentPanel - loggedIn with no comments', () =>
<CommentsPanel
user={userObj}
loading={false}
@ -71,8 +75,8 @@ storiesOf('Components', module)
addComment={action('addComment')}
deleteComment={action('deleteComment')}
/>
))
.add('CommentPanel - loggedIn with has comments', () => (
)
.add('CommentPanel - loggedIn with has comments', () =>
<CommentsPanel
user={userObj}
loading={false}
@ -80,4 +84,4 @@ storiesOf('Components', module)
addComment={action('addComment')}
deleteComment={action('deleteComment')}
/>
));
);

View File

@ -49,7 +49,9 @@ The `setupGraphiQL` function also accepts a fetcher parameter which can be used
import { storiesOf } from '@storybook/react'
import { setupGraphiQL } from '@storybook/addon-graphql'
const fetcher = function (params) {
import { url } from './settings';
const fetcher = params => {
const options = {
method: 'post',
headers: { 'Content-Type': 'application/json' },

View File

@ -2,33 +2,35 @@
"name": "@storybook/addon-graphql",
"version": "3.0.0",
"description": "Storybook addon to display the GraphiQL IDE",
"keywords": [
"storybook"
],
"homepage": "https://github.com/storybooks/storybook/tree/master/addons/graphql",
"bugs": {
"url": "https://github.com/storybooks/storybook/issues"
},
"license": "MIT",
"main": "dist/index.js",
"repository": {
"type": "git",
"url": "https://github.com/storybooks/storybook.git"
},
"scripts": {
"deploy-storybook": "storybook-to-ghpages",
"prepublish": "node ../../scripts/prepublish.js",
"storybook": "start-storybook -p 9001"
},
"repository": {
"type": "git",
"url": "git+https://github.com/storybooks/storybook.git"
"dependencies": {
"global": "^4.3.2",
"graphiql": "^0.7.8",
"graphql": "^0.7.0",
"prop-types": "^15.5.10"
},
"keywords": [
"storybook"
],
"license": "MIT",
"bugs": {
"url": "https://github.com/storybooks/storybook/issues"
},
"homepage": "https://github.com/storybooks/storybook/tree/master/addons/graphql",
"devDependencies": {
"react": "^15.5.4",
"react-dom": "^15.5.4",
"shelljs": "^0.7.7"
},
"dependencies": {
"graphiql": "^0.7.8",
"graphql": "^0.7.0"
},
"peerDependencies": {
"react": "*"
}

View File

@ -1,12 +1,14 @@
import React, { Component } from 'react';
import React from 'react';
import PropTypes from 'prop-types';
import style from './style';
export default class FullScreen extends Component {
render() {
return (
<div style={style.wrapper}>
{this.props.children}
</div>
);
}
export default function FullScreen(props) {
return (
<div style={style.wrapper}>
{props.children}
</div>
);
}
FullScreen.defaultProps = { children: null };
FullScreen.propTypes = { children: PropTypes.node };

View File

@ -1,15 +1,17 @@
import React from 'react';
import GraphiQL from 'graphiql';
import FullScreen from './components/FullScreen';
import { fetch } from 'global';
import 'graphiql/graphiql.css';
import FullScreen from './components/FullScreen';
const FETCH_OPTIONS = {
method: 'post',
headers: { 'Content-Type': 'application/json' },
};
function getDefautlFetcher(url) {
return function(params) {
return params => {
const body = JSON.stringify(params);
const options = Object.assign({ body }, FETCH_OPTIONS);
return fetch(url, options).then(res => res.json());
@ -23,13 +25,12 @@ function reIndentQuery(query) {
}
export function setupGraphiQL(config) {
return function(_query, variables = '{}') {
return (_query, variables = '{}') => {
const query = reIndentQuery(_query);
const fetcher = config.fetcher || getDefautlFetcher(config.url);
return () => (
return () =>
<FullScreen>
<GraphiQL query={query} variables={variables} fetcher={fetcher} />
</FullScreen>
);
</FullScreen>;
};
}

View File

@ -40,8 +40,8 @@ Then create your stories with the `.addWithInfo` API.
```js
import React from 'react';
import Component from './Component';
import { storiesOf } from '@storybook/react';
import Component from './Component';
storiesOf('Component')
.addWithInfo(

View File

@ -1,16 +1,25 @@
{
"name": "@storybook/addon-info",
"version": "3.0.0",
"version": "3.0.1",
"description": "A Storybook addon to show additional information for your stories.",
"license": "MIT",
"main": "dist/index.js",
"repository": {
"type": "git",
"url": "git+https://github.com/storybooks/storybook.git"
"url": "https://github.com/storybooks/storybook.git"
},
"license": "MIT",
"scripts": {
"prepublish": "node ../../scripts/prepublish.js",
"storybook": "start-storybook -p 9010",
"publish-storybook": "bash .scripts/publish_storybook.sh"
"publish-storybook": "bash .scripts/publish_storybook.sh",
"storybook": "start-storybook -p 9010"
},
"dependencies": {
"@storybook/addons": "^3.0.0",
"babel-runtime": "^6.23.0",
"global": "^4.3.2",
"marksy": "^2.0.0",
"prop-types": "^15.5.8",
"react-addons-create-fragment": "^15.5.3"
},
"devDependencies": {
"git-url-parse": "^6.2.2",
@ -21,13 +30,5 @@
},
"peerDependencies": {
"react": "*"
},
"dependencies": {
"@storybook/addons": "^3.0.0",
"babel-runtime": "^6.23.0",
"markdown-to-react-components": "^0.2.1",
"prop-types": "^15.5.8",
"react-addons-create-fragment": "^15.5.3"
},
"main": "dist/index.js"
}
}

View File

@ -1,4 +1,5 @@
import React from 'react';
import PropTypes from 'prop-types';
import Props from './Props';
const stylesheet = {
@ -8,62 +9,6 @@ const stylesheet = {
},
};
export default class Node extends React.Component {
render() {
const { node, depth } = this.props;
const { tagStyle, containerStyle } = stylesheet;
const leftPad = {
paddingLeft: 3 + (depth + 1) * 15,
paddingRight: 3,
};
Object.assign(containerStyle, leftPad);
const { name, text, children } = getData(node);
// Just text
if (!name) {
return (
<div style={containerStyle}>
<span style={tagStyle}>{text}</span>
</div>
);
}
// Single-line tag
if (!children) {
return (
<div style={containerStyle}>
<span style={tagStyle}>&lt;{name}</span>
<Props node={node} singleLine />
<span style={tagStyle}>/&gt;</span>
</div>
);
}
// Keep a copy so that further mutations to containerStyle don't impact us:
const containerStyleCopy = Object.assign({}, containerStyle);
// tag with children
return (
<div>
<div style={containerStyleCopy}>
<span style={tagStyle}>&lt;{name}</span>
<Props node={node} />
<span style={tagStyle}>&gt;</span>
</div>
{React.Children.map(children, childElement => (
<Node node={childElement} depth={depth + 1} />
))}
<div style={containerStyleCopy}>
<span style={tagStyle}>&lt;/{name}&gt;</span>
</div>
</div>
);
}
}
function getData(element) {
const data = {
name: null,
@ -71,11 +16,11 @@ function getData(element) {
children: null,
};
if (typeof element === 'null') {
if (element === null) {
return data;
}
if (typeof element == 'string') {
if (typeof element === 'string') {
data.text = element;
return data;
}
@ -96,3 +41,65 @@ function getData(element) {
return data;
}
export default function Node(props) {
const { node, depth } = props;
const { tagStyle, containerStyle } = stylesheet;
const leftPad = {
paddingLeft: 3 + (depth + 1) * 15,
paddingRight: 3,
};
Object.assign(containerStyle, leftPad);
const { name, text, children } = getData(node);
// Just text
if (!name) {
return (
<div style={containerStyle}>
<span style={tagStyle}>{text}</span>
</div>
);
}
// Single-line tag
if (!children) {
return (
<div style={containerStyle}>
<span style={tagStyle}>&lt;{name}</span>
<Props node={node} singleLine />
<span style={tagStyle}>/&gt;</span>
</div>
);
}
// Keep a copy so that further mutations to containerStyle don't impact us:
const containerStyleCopy = Object.assign({}, containerStyle);
// tag with children
return (
<div>
<div style={containerStyleCopy}>
<span style={tagStyle}>&lt;{name}</span>
<Props node={node} />
<span style={tagStyle}>&gt;</span>
</div>
{React.Children.map(children, childElement => <Node node={childElement} depth={depth + 1} />)}
<div style={containerStyleCopy}>
<span style={tagStyle}>&lt;/{name}&gt;</span>
</div>
</div>
);
}
Node.defaultProps = {
node: null,
depth: 0,
};
Node.propTypes = {
node: PropTypes.node,
depth: PropTypes.number,
};

View File

@ -1,11 +1,13 @@
/* eslint-disable no-underscore-dangle */
import PropTypes from 'prop-types';
import React from 'react';
import PropVal from './PropVal';
const PropTypesMap = new Map();
for (const typeName in PropTypes) {
if (!PropTypes.hasOwnProperty(typeName)) {
continue;
for (const typeName in PropTypes) { // eslint-disable-line
if (!PropTypes.hasOwnProperty(typeName)) { // eslint-disable-line
continue; // eslint-disable-line
}
const type = PropTypes[typeName];
PropTypesMap.set(type, typeName);
@ -20,93 +22,92 @@ const stylesheet = {
},
};
export default class PropTable extends React.Component {
render() {
const type = this.props.type;
if (!type) {
return null;
}
const props = {};
if (type.propTypes) {
for (const property in type.propTypes) {
if (!type.propTypes.hasOwnProperty(property)) {
continue;
}
const typeInfo = type.propTypes[property];
let propType = PropTypesMap.get(typeInfo) || 'other';
const required = typeInfo.isRequired === undefined ? 'yes' : 'no';
const description = type.__docgenInfo &&
type.__docgenInfo.props &&
type.__docgenInfo.props[property]
? type.__docgenInfo.props[property].description
: null;
if (propType === 'other') {
if (
type.__docgenInfo &&
type.__docgenInfo.props &&
type.__docgenInfo.props[property] &&
type.__docgenInfo.props[property].type
) {
propType = type.__docgenInfo.props[property].type.name;
}
}
props[property] = { property, propType, required, description };
}
}
if (type.defaultProps) {
for (const property in type.defaultProps) {
if (!type.defaultProps.hasOwnProperty(property)) {
continue;
}
const value = type.defaultProps[property];
if (value === undefined) {
continue;
}
if (!props[property]) {
props[property] = { property };
}
props[property].defaultValue = value;
}
}
const array = Object.values(props);
if (!array.length) {
return <small>No propTypes defined!</small>;
}
array.sort((a, b) => a.property > b.property);
return (
<table style={stylesheet.propTable}>
<thead>
<tr>
<th>property</th>
<th>propType</th>
<th>required</th>
<th>default</th>
<th>description</th>
</tr>
</thead>
<tbody>
{array.map(row => (
<tr key={row.property}>
<td>{row.property}</td>
<td>{row.propType}</td>
<td>{row.required}</td>
<td>{row.defaultValue === undefined ? '-' : <PropVal val={row.defaultValue} />}</td>
<td>{row.description}</td>
</tr>
))}
</tbody>
</table>
);
const PropTable = ({ type }) => {
if (!type) {
return null;
}
}
const props = {};
if (type.propTypes) {
for (const property in type.propTypes) { // eslint-disable-line
if (!type.propTypes.hasOwnProperty(property)) { // eslint-disable-line
continue; // eslint-disable-line
}
const typeInfo = type.propTypes[property];
let propType = PropTypesMap.get(typeInfo) || 'other';
const required = typeInfo.isRequired === undefined ? 'yes' : 'no';
const description = type.__docgenInfo &&
type.__docgenInfo.props &&
type.__docgenInfo.props[property]
? type.__docgenInfo.props[property].description
: null;
if (propType === 'other') {
if (
type.__docgenInfo &&
type.__docgenInfo.props &&
type.__docgenInfo.props[property] &&
type.__docgenInfo.props[property].type
) {
propType = type.__docgenInfo.props[property].type.name;
}
}
props[property] = { property, propType, required, description };
}
}
if (type.defaultProps) {
for (const property in type.defaultProps) { // eslint-disable-line
if (!type.defaultProps.hasOwnProperty(property)) { // eslint-disable-line
continue; // eslint-disable-line
}
const value = type.defaultProps[property];
if (value === undefined) {
continue; // eslint-disable-line
}
if (!props[property]) {
props[property] = { property };
}
props[property].defaultValue = value;
}
}
const array = Object.values(props);
if (!array.length) {
return <small>No propTypes defined!</small>;
}
array.sort((a, b) => a.property > b.property);
return (
<table style={stylesheet.propTable}>
<thead>
<tr>
<th>property</th>
<th>propType</th>
<th>required</th>
<th>default</th>
<th>description</th>
</tr>
</thead>
<tbody>
{array.map(row =>
<tr key={row.property}>
<td>{row.property}</td>
<td>{row.propType}</td>
<td>{row.required}</td>
<td>{row.defaultValue === undefined ? '-' : <PropVal val={row.defaultValue} />}</td>
<td>{row.description}</td>
</tr>
)}
</tbody>
</table>
);
};
PropTable.displayName = 'PropTable';
PropTable.defaultProps = {
type: null,
};
PropTable.propTypes = {
type: PropTypes.func,
};

View File

@ -82,7 +82,7 @@ function previewProp(val) {
content = <span style={valueStyles.number}>{val}</span>;
} else if (typeof val === 'string') {
if (val.length > 50) {
val = `${val.slice(0, 50)}`;
val = `${val.slice(0, 50)}`; // eslint-disable-line
}
content = <span style={valueStyles.string}>"{val}"</span>;
braceWrap = false;
@ -112,7 +112,7 @@ function previewProp(val) {
export default class PropVal extends React.Component {
render() {
return previewProp(this.props.val);
return previewProp(this.props.val); // eslint-disable-line react/prop-types
}
}

View File

@ -1,4 +1,5 @@
import React from 'react';
import PropTypes from 'prop-types';
import PropVal from './PropVal';
const stylesheet = {
@ -7,48 +8,54 @@ const stylesheet = {
propValueStyle: {},
};
export default class Props extends React.Component {
render() {
const props = this.props.node.props;
const defaultProps = this.props.node.type.defaultProps;
if (!props || typeof props !== 'object') {
return <span />;
}
const { propStyle, propValueStyle, propNameStyle } = stylesheet;
const names = Object.keys(props).filter(
name =>
name[0] !== '_' &&
name !== 'children' &&
(!defaultProps || props[name] != defaultProps[name])
);
const breakIntoNewLines = names.length > 3;
const endingSpace = this.props.singleLine ? ' ' : '';
const items = [];
names.forEach((name, i) => {
items.push(
<span key={name}>
{breakIntoNewLines
? <span>
<br />&nbsp;&nbsp;
</span>
: ' '}
<span style={propNameStyle}>{name}</span>
{/* Use implicit true: */}
{(!props[name] || typeof props[name] !== 'boolean') &&
<span>
=
<span style={propValueStyle}><PropVal val={props[name]} /></span>
</span>}
{i === names.length - 1 && (breakIntoNewLines ? <br /> : endingSpace)}
</span>
);
});
return <span>{items}</span>;
export default function Props(props) {
const nodeProps = props.node.props;
const defaultProps = props.node.type.defaultProps;
if (!nodeProps || typeof nodeProps !== 'object') {
return <span />;
}
const { propValueStyle, propNameStyle } = stylesheet;
const names = Object.keys(props).filter(
name =>
name[0] !== '_' &&
name !== 'children' &&
(!defaultProps || props[name] !== defaultProps[name])
);
const breakIntoNewLines = names.length > 3;
const endingSpace = props.singleLine ? ' ' : '';
const items = [];
names.forEach((name, i) => {
items.push(
<span key={name}>
{breakIntoNewLines ? <span><br />&nbsp;&nbsp;</span> : ' '}
<span style={propNameStyle}>{name}</span>
{/* Use implicit true: */}
{(!nodeProps[name] || typeof nodeProps[name] !== 'boolean') &&
<span>
=
<span style={propValueStyle}><PropVal val={nodeProps[name]} /></span>
</span>}
{i === names.length - 1 && (breakIntoNewLines ? <br /> : endingSpace)}
</span>
);
});
return <span>{items}</span>;
}
Props.defaultProps = {
singleLine: false,
};
Props.propTypes = {
node: PropTypes.shape({
props: PropTypes.object,
type: PropTypes.object.isRequired,
}).isRequired,
singleLine: PropTypes.bool,
};

View File

@ -1,11 +1,19 @@
import PropTypes from 'prop-types';
/* eslint no-underscore-dangle: 0 */
import React from 'react';
import MTRC from 'markdown-to-react-components';
import PropTypes from 'prop-types';
import global from 'global';
import marksy from 'marksy';
import PropTable from './PropTable';
import Node from './Node';
import { baseFonts } from './theme';
import { Pre } from './markdown';
global.STORYBOOK_REACT_CLASSES = global.STORYBOOK_REACT_CLASSES || [];
const STORYBOOK_REACT_CLASSES = global.STORYBOOK_REACT_CLASSES;
const stylesheet = {
link: {
base: {
@ -90,7 +98,7 @@ export default class Story extends React.Component {
open: false,
stylesheet: this.props.styles(JSON.parse(JSON.stringify(stylesheet))),
};
MTRC.configure(this.props.mtrcConf);
this.marksy = marksy(this.props.marksyConf);
}
componentWillReceiveProps(nextProps) {
@ -156,9 +164,9 @@ export default class Story extends React.Component {
<div style={this.state.stylesheet.children}>
{this.props.children}
</div>
<a style={linkStyle} onClick={openOverlay}>Show Info</a>
<a style={linkStyle} onClick={openOverlay} role="button" tabIndex="0">Show Info</a>
<div style={infoStyle}>
<a style={linkStyle} onClick={closeOverlay}>×</a>
<a style={linkStyle} onClick={closeOverlay} role="button" tabIndex="0">×</a>
<div style={this.state.stylesheet.infoPage}>
<div style={this.state.stylesheet.infoBody}>
{this._getInfoHeader()}
@ -211,7 +219,7 @@ export default class Story extends React.Component {
const source = lines.map(s => s.slice(padding)).join('\n');
return (
<div style={this.state.stylesheet.infoContent}>
{MTRC(source).tree}
{this.marksy(source).tree}
</div>
);
}
@ -220,7 +228,7 @@ export default class Story extends React.Component {
let retDiv = null;
if (Object.keys(STORYBOOK_REACT_CLASSES).length) {
Object.keys(STORYBOOK_REACT_CLASSES).forEach((key, index) => {
Object.keys(STORYBOOK_REACT_CLASSES).forEach(key => {
if (STORYBOOK_REACT_CLASSES[key].name === this.props.context.kind) {
retDiv = (
<div>
@ -243,9 +251,9 @@ export default class Story extends React.Component {
<div>
<h1 style={this.state.stylesheet.source.h1}>Story Source</h1>
<Pre>
{React.Children.map(this.props.children, (root, idx) => (
{React.Children.map(this.props.children, (root, idx) =>
<Node key={idx} depth={0} node={root} />
))}
)}
</Pre>
</div>
);
@ -284,7 +292,7 @@ export default class Story extends React.Component {
typeof children === 'string' ||
typeof children.type === 'string' ||
(Array.isArray(this.props.propTablesExclude) && // also ignore excluded types
~this.props.propTablesExclude.indexOf(children.type))
~this.props.propTablesExclude.indexOf(children.type)) // eslint-disable-line no-bitwise
) {
return;
}
@ -299,14 +307,14 @@ export default class Story extends React.Component {
const array = Array.from(types.keys());
array.sort((a, b) => (a.displayName || a.name) > (b.displayName || b.name));
const propTables = array.map((type, idx) => (
<div key={idx}>
const propTables = array.map(type =>
<div key={type.name}>
<h2 style={this.state.stylesheet.propTableHead}>
"{type.displayName || type.name}" Component
</h2>
<PropTable type={type} />
</div>
));
);
if (!propTables || propTables.length === 0) {
return null;
@ -330,8 +338,12 @@ export default class Story extends React.Component {
}
Story.displayName = 'Story';
Story.propTypes = {
context: PropTypes.object,
context: PropTypes.shape({
kind: PropTypes.string,
story: PropTypes.string,
}),
info: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
propTables: PropTypes.arrayOf(PropTypes.func),
propTablesExclude: PropTypes.arrayOf(PropTypes.func),
@ -340,12 +352,16 @@ Story.propTypes = {
showSource: PropTypes.bool,
styles: PropTypes.func.isRequired,
children: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
mtrcConf: PropTypes.object,
marksyConf: PropTypes.object, // eslint-disable-line react/forbid-prop-types
};
Story.defaultProps = {
context: null,
info: '',
children: null,
propTables: null,
propTablesExclude: [],
showInline: false,
showHeader: true,
showSource: true,
mtrcConf: {},
marksyConf: {},
};

View File

@ -1,4 +1,6 @@
import { Prism } from 'global';
import React from 'react';
import PropTypes from 'prop-types';
export class Code extends React.Component {
componentDidMount() {
@ -41,30 +43,39 @@ export class Code extends React.Component {
}
}
export class Pre extends React.Component {
render() {
const style = {
fontSize: '.88em',
fontFamily: 'Menlo, Monaco, "Courier New", monospace',
backgroundColor: '#fafafa',
padding: '.5rem',
lineHeight: 1.5,
overflowX: 'scroll',
};
Code.propTypes = {
language: PropTypes.string,
code: PropTypes.node,
};
Code.defaultProps = {
language: null,
code: null,
};
return <pre style={style}>{this.props.children}</pre>;
}
export function Pre(props) {
const style = {
fontSize: '.88em',
fontFamily: 'Menlo, Monaco, "Courier New", monospace',
backgroundColor: '#fafafa',
padding: '.5rem',
lineHeight: 1.5,
overflowX: 'scroll',
};
return <pre style={style}>{props.children}</pre>;
}
export class Blockquote extends React.Component {
render() {
const style = {
fontSize: '1.88em',
fontFamily: 'Menlo, Monaco, "Courier New", monospace',
borderLeft: '8px solid #fafafa',
padding: '1rem',
};
Pre.propTypes = { children: PropTypes.node };
Pre.defaultProps = { children: null };
return <blockquote style={style}>{this.props.children}</blockquote>;
}
export function Blockquote(props) {
const style = {
fontSize: '1.88em',
fontFamily: 'Menlo, Monaco, "Courier New", monospace',
borderLeft: '8px solid #fafafa',
padding: '1rem',
};
return <blockquote style={style}>{props.children}</blockquote>;
}
Blockquote.propTypes = { children: PropTypes.node };
Blockquote.defaultProps = { children: null };

View File

@ -1,88 +1,98 @@
import React from 'react';
import PropTypes from 'prop-types';
import { baseFonts } from '../theme';
export class H1 extends React.Component {
render() {
const styles = {
...baseFonts,
borderBottom: '1px solid #eee',
fontWeight: 600,
margin: 0,
padding: 0,
fontSize: '40px',
};
const defaultProps = {
children: null,
id: null,
};
const propTypes = {
children: PropTypes.node,
id: PropTypes.string,
};
return <h1 id={this.props.id} style={styles}>{this.props.children}</h1>;
}
export function H1(props) {
const styles = {
...baseFonts,
borderBottom: '1px solid #eee',
fontWeight: 600,
margin: 0,
padding: 0,
fontSize: '40px',
};
return <h1 id={props.id} style={styles}>{props.children}</h1>;
}
export class H2 extends React.Component {
render() {
const styles = {
...baseFonts,
fontWeight: 600,
margin: 0,
padding: 0,
fontSize: '30px',
};
H1.defaultProps = defaultProps;
H1.propTypes = propTypes;
return <h2 id={this.props.id} style={styles}>{this.props.children}</h2>;
}
export function H2(props) {
const styles = {
...baseFonts,
fontWeight: 600,
margin: 0,
padding: 0,
fontSize: '30px',
};
return <h2 id={props.id} style={styles}>{props.children}</h2>;
}
export class H3 extends React.Component {
render() {
const styles = {
...baseFonts,
fontWeight: 600,
margin: 0,
padding: 0,
fontSize: '22px',
textTransform: 'uppercase',
};
H2.defaultProps = defaultProps;
H2.propTypes = propTypes;
return <h3 id={this.props.id} style={styles}>{this.props.children}</h3>;
}
export function H3(props) {
const styles = {
...baseFonts,
fontWeight: 600,
margin: 0,
padding: 0,
fontSize: '22px',
textTransform: 'uppercase',
};
return <h3 id={props.id} style={styles}>{props.children}</h3>;
}
export class H4 extends React.Component {
render() {
const styles = {
...baseFonts,
fontWeight: 600,
margin: 0,
padding: 0,
fontSize: '20px',
};
H3.defaultProps = defaultProps;
H3.propTypes = propTypes;
return <h4 id={this.props.id} style={styles}>{this.props.children}</h4>;
}
export function H4(props) {
const styles = {
...baseFonts,
fontWeight: 600,
margin: 0,
padding: 0,
fontSize: '20px',
};
return <h4 id={props.id} style={styles}>{props.children}</h4>;
}
export class H5 extends React.Component {
render() {
const styles = {
...baseFonts,
fontWeight: 600,
margin: 0,
padding: 0,
fontSize: '18px',
};
H4.defaultProps = defaultProps;
H4.propTypes = propTypes;
return <h5 id={this.props.id} style={styles}>{this.props.children}</h5>;
}
export function H5(props) {
const styles = {
...baseFonts,
fontWeight: 600,
margin: 0,
padding: 0,
fontSize: '18px',
};
return <h5 id={props.id} style={styles}>{props.children}</h5>;
}
export class H6 extends React.Component {
render() {
const styles = {
...baseFonts,
fontWeight: 400,
margin: 0,
padding: 0,
fontSize: '18px',
};
H5.defaultProps = defaultProps;
H5.propTypes = propTypes;
return <h6 id={this.props.id} style={styles}>{this.props.children}</h6>;
}
export function H6(props) {
const styles = {
...baseFonts,
fontWeight: 400,
margin: 0,
padding: 0,
fontSize: '18px',
};
return <h6 id={props.id} style={styles}>{props.children}</h6>;
}
H6.defaultProps = defaultProps;
H6.propTypes = propTypes;

View File

@ -1,43 +1,49 @@
import React from 'react';
import PropTypes from 'prop-types';
import { baseFonts } from '../theme';
export class P extends React.Component {
render() {
const style = {
...baseFonts,
fontSize: '15px',
};
return <p style={style}>{this.props.children}</p>;
}
const defaultProps = { children: null };
const propTypes = { children: PropTypes.node };
export function P(props) {
const style = {
...baseFonts,
fontSize: '15px',
};
return <p style={style}>{props.children}</p>;
}
export class LI extends React.Component {
render() {
const style = {
...baseFonts,
fontSize: '15px',
};
return <li style={style}>{this.props.children}</li>;
}
P.defaultProps = defaultProps;
P.propTypes = propTypes;
export function LI(props) {
const style = {
...baseFonts,
fontSize: '15px',
};
return <li style={style}>{props.children}</li>;
}
export class UL extends React.Component {
render() {
const style = {
...baseFonts,
fontSize: '15px',
};
LI.defaultProps = defaultProps;
LI.propTypes = propTypes;
return <ul style={style}>{this.props.children}</ul>;
}
export function UL(props) {
const style = {
...baseFonts,
fontSize: '15px',
};
return <ul style={style}>{props.children}</ul>;
}
export class A extends React.Component {
render() {
const style = {
color: '#3498db',
};
UL.defaultProps = defaultProps;
UL.propTypes = propTypes;
return <a href={this.props.href} style={style}>{this.props.children}</a>;
}
export function A(props) {
const style = {
color: '#3498db',
};
return <a href={this.props.href} style={style}>{props.children}</a>;
}
A.defaultProps = defaultProps;
A.propTypes = propTypes;

View File

@ -1,6 +1,7 @@
import React from 'react';
import _Story from './components/Story';
import { H1, H2, H3, H4, H5, H6, Code, P, UL, A, LI } from './components/markdown';
export const Story = _Story;
const defaultOptions = {
@ -10,7 +11,7 @@ const defaultOptions = {
propTables: [],
};
const defaultMtrcConf = {
const defaultMarksyConf = {
h1: H1,
h2: H2,
h3: H3,
@ -28,9 +29,9 @@ export default {
addWithInfo(storyName, info, storyFn, _options) {
if (typeof storyFn !== 'function') {
if (typeof info === 'function') {
_options = storyFn;
storyFn = info;
info = '';
_options = storyFn; // eslint-disable-line
storyFn = info; // eslint-disable-line
info = ''; // eslint-disable-line
} else {
throw new Error('No story defining function has been specified');
}
@ -48,9 +49,9 @@ export default {
options.propTables = null;
}
const mtrcConf = { ...defaultMtrcConf };
if (options && options.mtrcConf) {
Object.assign(mtrcConf, options.mtrcConf);
const marksyConf = { ...defaultMarksyConf };
if (options && options.marksyConf) {
Object.assign(marksyConf, options.marksyConf);
}
return this.add(storyName, context => {
@ -63,7 +64,7 @@ export default {
propTables: options.propTables,
propTablesExclude: options.propTablesExclude,
styles: typeof options.styles === 'function' ? options.styles : s => s,
mtrcConf,
marksyConf,
};
return (

View File

@ -24,7 +24,7 @@ This is how Knobs look like:
First of all, you need to install knobs into your project as a dev dependency.
```sh
npm i -D @storybook/addon-knobs
npm install @storybook/addon-knobs --save-dev
```
Then, configure it as an addon by adding it to your `addons.js` file (located in the Storybook config directory).
@ -36,7 +36,6 @@ import '@storybook/addon-knobs/register'
Now, write your stories with knobs.
```js
import React from 'react';
import { storiesOf } from '@storybook/react';
import { withKnobs, text, boolean, number } from '@storybook/addon-knobs';
@ -48,9 +47,7 @@ stories.addDecorator(withKnobs);
// Knobs for React props
stories.add('with a button', () => (
<button
disabled={boolean('Disabled', false)}
>
<button disabled={boolean('Disabled', false)} >
{text('Label', 'Hello Button')}
</button>
))
@ -92,6 +89,8 @@ Just like that, you can import any other following Knobs:
Allows you to get some text from the user.
```js
import { text } from '@storybook/addon-knobs';
const label = 'Your Name';
const defaultValue = 'Arunoda Susiripala';
@ -103,6 +102,8 @@ const value = text(label, defaultValue);
Allows you to get a boolean value from the user.
```js
import { boolean } from '@storybook/addon-knobs';
const label = 'Agree?';
const defaultValue = false;
@ -114,6 +115,8 @@ const value = boolean(label, defaultValue);
Allows you to get a number from the user.
```js
import { number } from '@storybook/addon-knobs';
const label = 'Age';
const defaultValue = 78;
@ -125,6 +128,8 @@ const value = number(label, defaultValue);
Allows you to get a number from the user using a range slider.
```js
import { number } from '@storybook/addon-knobs';
const label = 'Temperature';
const defaultValue = 73;
const options = {
@ -137,11 +142,13 @@ const options = {
const value = number(label, defaultValue, options);
```
### colour
### color
Allows you to get a colour from the user.
```js
import { color } from '@storybook/addon-knobs';
const label = 'Color';
const defaultValue = '#ff00ff';
@ -153,6 +160,8 @@ const value = color(label, defaultValue);
Allows you to get a JSON object from the user.
```js
import { object } from '@storybook/addon-knobs';
const label = 'Styles';
const defaultValue = {
backgroundColor: 'red'
@ -168,6 +177,8 @@ const value = object(label, defaultValue);
Allows you to get an array from the user.
```js
import { array } from '@storybook/addon-knobs';
const label = 'Styles';
const defaultValue = ['Red']
@ -178,6 +189,10 @@ const value = array(label, defaultValue);
> By default it's a comma, but this can be override by passing a separator variable.
>
> ```js
> import { array } from '@storybook/addon-knobs';
>
> const label = 'Styles';
> const defaultValue = ['Red'];
> const separator = ':';
> const value = array(label, defaultValue, separator);
> ```
@ -187,6 +202,8 @@ const value = array(label, defaultValue);
Allows you to get a value from a select box from the user.
```js
import { select } from '@storybook/addon-knobs';
const label = 'Colors';
const options = {
red: 'Red',
@ -205,6 +222,8 @@ const value = select(label, options, defaultValue);
Allow you to get date (and time) from the user.
```js
import { date } from '@storybook/addon-knobs';
const label = 'Event Date';
const defaultValue = new Date('Jan 20 2017');
const value = date(label, defaultValue);
@ -217,4 +236,4 @@ If you are using typescript, make sure you have the type definitions installed f
- node
- react
You can install them using `npm i -S @types/node @types/react`, assuming you are using Typescript >2.0.
You can install them using `npm install -save @types/node @types/react`, assuming you are using Typescript >2.0.

View File

@ -1,17 +1,32 @@
{
"name": "@storybook/addon-knobs",
"version": "3.0.0",
"version": "3.0.1",
"description": "Storybook Addon Prop Editor Component",
"license": "MIT",
"main": "dist/index.js",
"typings": "./storybook-addon-knobs.d.ts",
"repository": {
"type": "git",
"url": "git+https://github.com/storybooks/storybook.git"
"url": "https://github.com/storybooks/storybook.git"
},
"license": "MIT",
"scripts": {
"start": "./example/prepublish.sh",
"prepublish": "node ../../scripts/prepublish.js",
"storybook": "start-storybook -p 9010",
"publish-storybook": "bash .scripts/publish_storybook.sh"
"publish-storybook": "bash .scripts/publish_storybook.sh",
"start": "./example/prepublish.sh",
"storybook": "start-storybook -p 9010"
},
"dependencies": {
"@storybook/addons": "*",
"babel-runtime": "^6.23.0",
"deep-equal": "^1.0.1",
"global": "^4.3.2",
"insert-css": "^1.0.0",
"lodash.debounce": "^4.0.8",
"moment": "^2.18.1",
"prop-types": "^15.5.8",
"react-color": "^2.11.4",
"react-datetime": "^2.8.10",
"react-textarea-autosize": "^4.3.0"
},
"devDependencies": {
"@types/node": "^7.0.12",
@ -27,19 +42,5 @@
"peerDependencies": {
"react": "*",
"react-dom": "*"
},
"dependencies": {
"@storybook/addons": "*",
"babel-runtime": "^6.23.0",
"deep-equal": "^1.0.1",
"insert-css": "^1.0.0",
"lodash.debounce": "^4.0.8",
"moment": "^2.18.1",
"prop-types": "^15.5.8",
"react-color": "^2.11.4",
"react-datetime": "^2.8.10",
"react-textarea-autosize": "^4.3.0"
},
"main": "dist/index.js",
"typings": "./storybook-addon-knobs.d.ts"
}
}

View File

@ -1,7 +1,9 @@
/* eslint no-underscore-dangle: 0 */
import React from 'react';
import deepEqual from 'deep-equal';
import WrapStory from './components/WrapStory';
import KnobStore from './KnobStore';
import deepEqual from 'deep-equal';
// This is used by _mayCallChannel to determine how long to wait to before triggering a panel update
const PANEL_UPDATE_INTERVAL = 400;
@ -41,7 +43,7 @@ export default class KnobManager {
let knobStore = this.knobStoreMap[key];
if (!knobStore) {
knobStore = this.knobStoreMap[key] = new KnobStore();
knobStore = this.knobStoreMap[key] = new KnobStore(); // eslint-disable-line
}
this.knobStore = knobStore;

View File

@ -1,5 +1,5 @@
import React from 'react';
import { shallow } from 'enzyme';
import { shallow } from 'enzyme'; // eslint-disable-line
import KnobManager from './KnobManager';
describe('KnobManager', () => {

View File

@ -1,6 +1,7 @@
import PropTypes from 'prop-types';
import React from 'react';
import PropTypes from 'prop-types';
import debounce from 'lodash.debounce';
import PropForm from './PropForm';
import Types from './types';
@ -147,7 +148,14 @@ export default class Panel extends React.Component {
}
Panel.propTypes = {
channel: PropTypes.object,
onReset: PropTypes.object,
api: PropTypes.object,
channel: PropTypes.shape({
emit: PropTypes.func,
on: PropTypes.func,
removeListener: PropTypes.func,
}).isRequired,
onReset: PropTypes.object, // eslint-disable-line
api: PropTypes.shape({
getQueryParam: PropTypes.func,
setQueryParams: PropTypes.func,
}).isRequired,
};

View File

@ -1,3 +1,5 @@
/* eslint-disable no-underscore-dangle */
import PropTypes from 'prop-types';
import React from 'react';
import TypeMap from './types';
@ -60,6 +62,9 @@ export default class PropField extends React.Component {
}
PropField.propTypes = {
knob: PropTypes.shape({
name: PropTypes.string,
value: PropTypes.string,
}),
onChange: PropTypes.func.isRequired,
knob: PropTypes.object,
};

View File

@ -1,5 +1,7 @@
import PropTypes from 'prop-types';
/* eslint no-underscore-dangle: 0 */
import React from 'react';
import PropTypes from 'prop-types';
import PropField from './PropField';
@ -33,16 +35,16 @@ export default class propForm extends React.Component {
return (
<form style={stylesheet.propForm}>
{knobs.map(knob => (
{knobs.map(knob =>
<PropField
key={knob.name}
name={knob.name}
type={knob.type}
value={knob.value}
knob={knob}
onChange={this._onFieldChange.bind(null, knob.name, knob.type)}
onChange={() => this._onFieldChange(knob.name, knob.type)}
/>
))}
)}
</form>
);
}
@ -50,7 +52,16 @@ export default class propForm extends React.Component {
propForm.displayName = 'propForm';
propForm.defaultProps = {
knobs: [],
};
propForm.propTypes = {
knobs: PropTypes.array.isRequired,
knobs: PropTypes.arrayOf(
PropTypes.shape({
name: PropTypes.string,
value: PropTypes.string,
})
),
onFieldChange: PropTypes.func.isRequired,
};

View File

@ -1,3 +1,5 @@
/* eslint no-underscore-dangle: 0 */
import PropTypes from 'prop-types';
import React from 'react';
@ -60,10 +62,27 @@ export default class WrapStory extends React.Component {
}
}
WrapStory.propTypes = {
context: PropTypes.object,
storyFn: PropTypes.func,
channel: PropTypes.object,
knobStore: PropTypes.object,
initialContent: PropTypes.object,
WrapStory.defaultProps = {
context: {},
initialContent: {},
storyFn: context => context,
};
WrapStory.propTypes = {
context: PropTypes.object, // eslint-disable-line react/forbid-prop-types
storyFn: PropTypes.func,
channel: React.PropTypes.shape({
on: PropTypes.func,
removeListener: PropTypes.func,
}).isRequired,
knobStore: PropTypes.shape({
channel: PropTypes.func,
get: PropTypes.func,
getAll: PropTypes.func,
markAllUnused: PropTypes.func,
reset: PropTypes.func,
subscribe: PropTypes.func,
unsubscribe: PropTypes.func,
}).isRequired,
initialContent: PropTypes.object, // eslint-disable-line react/forbid-prop-types
};

View File

@ -1,5 +1,5 @@
import React from 'react';
import { shallow } from 'enzyme';
import { shallow } from 'enzyme'; // eslint-disable-line
import Array from '../types/Array';
describe('Array', () => {

View File

@ -1,5 +1,5 @@
import React from 'react';
import { shallow } from 'enzyme';
import { shallow } from 'enzyme'; // eslint-disable-line
import Panel from '../Panel';
describe('Panel', () => {

View File

@ -22,7 +22,9 @@ class ArrayType extends React.Component {
return (
<Textarea
id={knob.name}
ref="input"
ref={c => {
this.input = c;
}}
style={styles}
value={knob.value.join(knob.separator)}
onChange={e => onChange(e.target.value.split(knob.separator))}
@ -31,17 +33,20 @@ class ArrayType extends React.Component {
}
}
ArrayType.defaultProps = {
knob: {},
onChange: value => value,
};
ArrayType.propTypes = {
knob: PropTypes.object,
knob: PropTypes.shape({
name: PropTypes.string,
value: PropTypes.string,
}),
onChange: PropTypes.func,
};
ArrayType.serialize = function(value) {
return value;
};
ArrayType.deserialize = function(value) {
return value;
};
ArrayType.serialize = value => value;
ArrayType.deserialize = value => value;
export default ArrayType;

View File

@ -19,28 +19,32 @@ class BooleanType extends React.Component {
return (
<input
id={knob.name}
ref="input"
ref={c => {
this.input = c;
}}
style={styles}
type="checkbox"
onChange={() => onChange(this.refs.input.checked)}
onChange={() => onChange(this.input.checked)}
checked={knob.value}
/>
);
}
}
BooleanType.defaultProps = {
knob: {},
onChange: value => value,
};
BooleanType.propTypes = {
knob: PropTypes.object,
knob: PropTypes.shape({
name: PropTypes.string,
value: PropTypes.string,
}),
onChange: PropTypes.func,
};
BooleanType.serialize = function(value) {
return String(value);
};
BooleanType.deserialize = function(value) {
if (!value) return false;
return value.trim() === 'true';
};
BooleanType.serialize = value => String(value);
BooleanType.deserialize = value => (typeof value === 'string' ? value.match('true') : false);
export default BooleanType;

View File

@ -1,7 +1,10 @@
import { document } from 'global';
import PropTypes from 'prop-types';
import React from 'react';
import { SketchPicker } from 'react-color';
const conditionalRender = (condition, positive, negative) => (condition ? positive() : negative());
const styles = {
swatch: {
background: '#fff',
@ -58,6 +61,7 @@ class ColorType extends React.Component {
render() {
const { knob, onChange } = this.props;
const { displayColorPicker } = this.state;
const colorStyle = {
width: 'auto',
height: '20px',
@ -67,35 +71,40 @@ class ColorType extends React.Component {
};
return (
<div id={knob.name}>
<div style={styles.swatch} onClick={this.handleClick}>
<div style={styles.swatch} onClick={this.handleClick} role="button" tabIndex="0">
<div style={colorStyle} />
</div>
{this.state.displayColorPicker
? <div
{conditionalRender(
displayColorPicker,
() =>
<div
style={styles.popover}
ref={e => {
this.popover = e;
}}
>
<SketchPicker color={knob.value} onChange={color => onChange(color.hex)} />
</div>
: null}
</div>,
() => null
)}
</div>
);
}
}
ColorType.propTypes = {
knob: PropTypes.object,
knob: PropTypes.shape({
name: PropTypes.string,
value: PropTypes.string,
}),
onChange: PropTypes.func,
};
ColorType.serialize = function(value) {
return value;
ColorType.defaultProps = {
knob: {},
onChange: value => value,
};
ColorType.deserialize = function(value) {
return value;
};
ColorType.serialize = value => value;
ColorType.deserialize = value => value;
export default ColorType;

View File

@ -21,33 +21,30 @@ const customStyle = `
insertCss(style);
insertCss(customStyle);
class DateType extends React.Component {
render() {
const { knob, onChange } = this.props;
return (
<div>
<Datetime
id={knob.name}
value={knob.value ? new Date(knob.value) : null}
type="date"
onChange={date => onChange(date.valueOf())}
/>
</div>
);
}
}
const DateType = ({ knob, onChange }) =>
<div>
<Datetime
id={knob.name}
value={knob.value ? new Date(knob.value) : null}
type="date"
onChange={date => onChange(date.valueOf())}
/>
</div>;
DateType.defaultProps = {
knob: {},
onChange: value => value,
};
DateType.propTypes = {
knob: PropTypes.object,
knob: PropTypes.shape({
name: PropTypes.string,
value: PropTypes.string,
}),
onChange: PropTypes.func,
};
DateType.serialize = function(value) {
return String(value);
};
DateType.deserialize = function(value) {
return parseFloat(value);
};
DateType.serialize = value => String(value);
DateType.deserialize = value => parseFloat(value);
export default DateType;

View File

@ -28,11 +28,13 @@ class NumberType extends React.Component {
return (
<input
id={knob.name}
ref="input"
ref={c => {
this.input = c;
}}
style={styles}
value={knob.value}
type="number"
onChange={() => onChange(parseFloat(this.refs.input.value))}
onChange={() => onChange(parseFloat(this.input.value))}
/>
);
}
@ -43,14 +45,16 @@ class NumberType extends React.Component {
return (
<input
id={knob.name}
ref="input"
ref={c => {
this.input = c;
}}
style={styles}
value={knob.value}
type="range"
min={knob.min}
max={knob.max}
step={knob.step}
onChange={() => onChange(parseFloat(this.refs.input.value))}
onChange={() => onChange(parseFloat(this.input.value))}
/>
);
}
@ -62,17 +66,20 @@ class NumberType extends React.Component {
}
}
NumberType.defaultProps = {
knob: {},
onChange: value => value,
};
NumberType.propTypes = {
knob: PropTypes.object,
knob: PropTypes.shape({
name: PropTypes.string,
value: PropTypes.string,
}),
onChange: PropTypes.func,
};
NumberType.serialize = function(value) {
return String(value);
};
NumberType.deserialize = function(value) {
return parseFloat(value);
};
NumberType.serialize = value => String(value);
NumberType.deserialize = value => parseFloat(value);
export default NumberType;

View File

@ -69,7 +69,9 @@ class ObjectType extends React.Component {
return (
<Textarea
id={knob.name}
ref="input"
ref={c => {
this.input = c;
}}
style={{ ...styles, ...extraStyle }}
value={jsonString}
onChange={e => this.handleChange(e)}
@ -78,18 +80,20 @@ class ObjectType extends React.Component {
}
}
ObjectType.defaultProps = {
knob: {},
onChange: value => value,
};
ObjectType.propTypes = {
knob: PropTypes.object,
knob: PropTypes.shape({
name: PropTypes.string,
value: PropTypes.string,
}),
onChange: PropTypes.func,
};
ObjectType.serialize = function(object) {
return JSON.stringify(object);
};
ObjectType.deserialize = function(value) {
if (!value) return {};
return JSON.parse(value);
};
ObjectType.serialize = object => JSON.stringify(object);
ObjectType.deserialize = value => (value ? JSON.parse(value) : {});
export default ObjectType;

View File

@ -1,3 +1,5 @@
/* eslint no-underscore-dangle: 0 */
import PropTypes from 'prop-types';
import React from 'react';
@ -40,7 +42,9 @@ class SelectType extends React.Component {
return (
<select
id={knob.name}
ref="input"
ref={c => {
this.input = c;
}}
style={styles}
value={knob.value}
onChange={e => onChange(e.target.value)}
@ -51,17 +55,20 @@ class SelectType extends React.Component {
}
}
SelectType.defaultProps = {
knob: {},
onChange: value => value,
};
SelectType.propTypes = {
knob: PropTypes.object,
knob: PropTypes.shape({
name: PropTypes.string,
value: PropTypes.string,
}),
onChange: PropTypes.func,
};
SelectType.serialize = function(value) {
return value;
};
SelectType.deserialize = function(value) {
return value;
};
SelectType.serialize = value => value;
SelectType.deserialize = value => value;
export default SelectType;

View File

@ -23,7 +23,9 @@ class TextType extends React.Component {
return (
<Textarea
id={knob.name}
ref="input"
ref={c => {
this.input = c;
}}
style={styles}
value={knob.value}
onChange={e => onChange(e.target.value)}
@ -32,17 +34,20 @@ class TextType extends React.Component {
}
}
TextType.defaultProps = {
knob: {},
onChange: value => value,
};
TextType.propTypes = {
knob: PropTypes.object,
knob: PropTypes.shape({
name: PropTypes.string,
value: PropTypes.string,
}),
onChange: PropTypes.func,
};
TextType.serialize = function(value) {
return value;
};
TextType.deserialize = function(value) {
return value;
};
TextType.serialize = value => value;
TextType.deserialize = value => value;
export default TextType;

View File

@ -33,6 +33,8 @@ storiesOf('Button', module)
Have a look at the linkTo function:
```js
import { linkTo } from '@storybook/addon-links'
linkTo('Toggle', 'off')
```

View File

@ -2,35 +2,35 @@
"name": "@storybook/addon-links",
"version": "3.0.0",
"description": "Story Links addon for storybook",
"keywords": [
"storybook"
],
"homepage": "https://github.com/storybooks/storybook/tree/master/addons/links",
"bugs": {
"url": "https://github.com/storybooks/storybook/issues"
},
"license": "MIT",
"main": "dist/index.js",
"typings": "./storybook-addon-links.d.ts",
"repository": {
"type": "git",
"url": "https://github.com/storybooks/storybook.git"
},
"scripts": {
"deploy-storybook": "storybook-to-ghpages",
"prepublish": "node ../../scripts/prepublish.js",
"storybook": "start-storybook -p 9001"
},
"repository": {
"type": "git",
"url": "git+https://github.com/storybooks/storybook.git"
"dependencies": {
"@storybook/addons": "^3.0.0"
},
"keywords": [
"storybook"
],
"license": "MIT",
"bugs": {
"url": "https://github.com/storybooks/storybook/issues"
},
"homepage": "https://github.com/storybooks/storybook/tree/master/addons/links",
"devDependencies": {
"react": "^15.5.4",
"react-dom": "^15.5.4",
"shelljs": "^0.7.7"
},
"dependencies": {
"@storybook/addons": "^3.0.0"
},
"peerDependencies": {
"react": "*",
"react-dom": "*"
},
"typings": "./storybook-addon-links.d.ts"
}
}

View File

@ -1,4 +1,4 @@
export const ADDON_ID = 'kadirahq/storybook-addon-links';
export const ADDON_ID = 'storybook/links';
export const EVENT_ID = `${ADDON_ID}/link-event`;
export { register } from './manager';

View File

@ -2,7 +2,7 @@ import addons from '@storybook/addons';
import { EVENT_ID } from './';
export function linkTo(kind, story) {
return function(...args) {
return (...args) => {
const resolvedKind = typeof kind === 'function' ? kind(...args) : kind;
const resolvedStory = typeof story === 'function' ? story(...args) : story;

View File

@ -2,15 +2,26 @@
"name": "@storybook/addon-notes",
"version": "3.0.0",
"description": "Write notes for your Storybook stories.",
"keywords": [
"addon",
"react",
"storybook"
],
"license": "MIT",
"main": "dist/index.js",
"repository": {
"type": "git",
"url": "git+https://github.com/storybooks/storybook.git"
"url": "https://github.com/storybooks/storybook.git"
},
"license": "MIT",
"scripts": {
"prepublish": "node ../../scripts/prepublish.js",
"storybook": "start-storybook -p 9010",
"publish-storybook": "bash .scripts/publish_storybook.sh"
"publish-storybook": "bash .scripts/publish_storybook.sh",
"storybook": "start-storybook -p 9010"
},
"dependencies": {
"@storybook/addons": "*",
"babel-runtime": "^6.23.0",
"prop-types": "^15.5.10"
},
"devDependencies": {
"git-url-parse": "^6.2.2",
@ -21,18 +32,7 @@
"peerDependencies": {
"react": "*"
},
"dependencies": {
"@storybook/addons": "*",
"babel-runtime": "^6.23.0",
"prop-types": "^15.5.10"
},
"optionalDependencies": {
"@types/react": "^15.0.24"
},
"main": "dist/index.js",
"keywords": [
"react",
"storybook",
"addon"
]
}
}

View File

@ -18,3 +18,7 @@ WithNotes.propTypes = {
children: PropTypes.node,
notes: PropTypes.string,
};
WithNotes.defaultProps = {
children: null,
notes: '',
};

View File

@ -1,3 +1,5 @@
/* eslint-disable import/no-extraneous-dependencies, import/no-unresolved, import/extensions */
import React from 'react';
import PropTypes from 'prop-types';
import addons from '@storybook/addons';
@ -59,8 +61,12 @@ export class Notes extends React.Component {
}
Notes.propTypes = {
channel: PropTypes.object,
api: PropTypes.object,
channel: PropTypes.object, // eslint-disable-line react/forbid-prop-types
api: PropTypes.object, // eslint-disable-line react/forbid-prop-types
};
Notes.defaultProps = {
channel: {},
api: {},
};
// Register the addon with a unique name.

View File

@ -1,2 +1,3 @@
const manager = require('./dist/manager');
manager.init();

View File

@ -1,36 +1,36 @@
{
"name": "@storybook/addon-options",
"version": "3.0.0",
"version": "3.0.1",
"description": "Options addon for storybook",
"keywords": [
"storybook"
],
"homepage": "https://github.com/storybooks/storybook/tree/master/addons/options",
"bugs": {
"url": "https://github.com/storybooks/storybook/issues"
},
"license": "MIT",
"main": "preview.js",
"typings": "./storybook-addon-options.d.ts",
"repository": {
"type": "git",
"url": "https://github.com/storybooks/storybook.git"
},
"scripts": {
"prepublish": "node ../../scripts/prepublish.js",
"storybook": "start-storybook -p 9001"
},
"repository": {
"type": "git",
"url": "git+https://github.com/storybooks/storybook.git"
"dependencies": {
"@storybook/addons": "^3.0.0"
},
"keywords": [
"storybook"
],
"license": "MIT",
"bugs": {
"url": "https://github.com/storybooks/storybook/issues"
},
"homepage": "https://github.com/storybooks/storybook/tree/master/addons/options",
"devDependencies": {
"react": "^15.5.4",
"react-dom": "^15.5.4",
"react-test-renderer": "^15.5.4",
"shelljs": "^0.7.7"
},
"dependencies": {
"@storybook/addons": "^3.0.0"
},
"peerDependencies": {
"react": "*",
"react-dom": "*"
},
"typings": "./storybook-addon-options.d.ts"
}
}

View File

@ -1,3 +1,4 @@
const preview = require('./dist/preview');
exports.setOptions = preview.setOptions;
preview.init();

View File

@ -1,4 +1,3 @@
import React from 'react';
import addons from '@storybook/addons';
import { ADDON_ID, EVENT_ID } from '../shared';

View File

@ -11,5 +11,10 @@ export function init() {
// ready. If called before, options will be cached until it can be sent.
export function setOptions(options) {
const channel = addons.getChannel();
if (!channel) {
throw new Error(
'Failed to find addon channel. This may be due to https://github.com/storybooks/storybook/issues/1192.'
);
}
channel.emit(EVENT_ID, { options });
}

View File

@ -61,6 +61,8 @@ By default, Storyshots assumes the config directory path for your project as bel
If you are using a different config directory path, you could change it like this:
```js
import initStoryshots from '@storybook/addon-storyshots';
initStoryshots({
configPath: '.my-storybook-config-dir'
});
@ -71,6 +73,8 @@ initStoryshots({
By default, Storyshots groups stories inside a Jest test suit called "Storyshots". You could change it like this:
```js
import initStoryshots from '@storybook/addon-storyshots';
initStoryshots({
suit: 'MyStoryshots'
});
@ -81,6 +85,8 @@ initStoryshots({
If you'd like to only run a subset of the stories for your snapshot tests based on the story's kind:
```js
import initStoryshots from '@storybook/addon-storyshots';
initStoryshots({
storyKindRegex: /^MyComponent$/
});
@ -93,6 +99,8 @@ This can be useful if you want to separate the snapshots in directories next to
If you'd like to only run a subset of the stories for your snapshot tests based on the story's name:
```js
import initStoryshots from '@storybook/addon-storyshots';
initStoryshots({
storyNameRegex: /buttons/
});

View File

@ -2,16 +2,22 @@
"name": "@storybook/addon-storyshots",
"version": "3.0.0",
"description": "StoryShots is a Jest Snapshot Testing Addon for Storybook.",
"license": "MIT",
"main": "dist/index.js",
"repository": {
"type": "git",
"url": "https://github.com/storybooks/storybook.git"
},
"license": "MIT",
"main": "dist/index.js",
"scripts": {
"build-storybook": "build-storybook",
"prepublish": "babel ./src --out-dir ./dist",
"storybook": "start-storybook -p 6006",
"build-storybook": "build-storybook"
"storybook": "start-storybook -p 6006"
},
"dependencies": {
"babel-runtime": "^6.23.0",
"global": "^4.3.2",
"prop-types": "^15.5.8",
"read-pkg-up": "^2.0.0"
},
"devDependencies": {
"babel-cli": "^6.24.1",
@ -21,13 +27,10 @@
"react": "^15.5.4",
"react-dom": "^15.5.4"
},
"dependencies": {
"babel-runtime": "^6.23.0",
"global": "^4.3.2",
"prop-types": "^15.5.8",
"read-pkg-up": "^2.0.0"
},
"peerDependencies": {
"@storybook/addons": "^3.0.0",
"@storybook/channels": "^3.0.0",
"babel-core": "^6.24.1",
"react": "*",
"react-test-renderer": "*"
}

View File

@ -1,11 +1,11 @@
import path from 'path';
import global from 'global';
import global, { describe, it } from 'global';
import readPkgUp from 'read-pkg-up';
import addons from '@storybook/addons';
import runWithRequireContext from './require_context';
import createChannel from './storybook-channel-mock';
import { snapshot } from './test-bodies';
const { describe, it, expect } = global;
export { snapshotWithOptions, snapshot, renderOnly } from './test-bodies';
@ -17,12 +17,9 @@ const babel = require('babel-core');
const pkg = readPkgUp.sync().pkg;
const hasDependency = function(name) {
return (
(pkg.devDependencies && pkg.devDependencies[name]) ||
(pkg.dependencies && pkg.dependencies[name])
);
};
const hasDependency = name =>
(pkg.devDependencies && pkg.devDependencies[name]) ||
(pkg.dependencies && pkg.dependencies[name]);
export default function testStorySnapshots(options = {}) {
addons.setChannel(createChannel());
@ -33,16 +30,17 @@ export default function testStorySnapshots(options = {}) {
if (isStorybook) {
storybook = require.requireActual('@storybook/react');
// eslint-disable-next-line
const loadBabelConfig = require('@storybook/react/dist/server/babel_config').default;
const configDirPath = path.resolve(options.configPath || '.storybook');
configPath = path.join(configDirPath, 'config.js');
const babelConfig = loadBabelConfig(configDirPath);
const content = babel.transformFileSync(configPath, babelConfig).code;
const contextOpts = {
filename: configPath,
dirname: configDirPath,
};
const babelConfig = loadBabelConfig(configDirPath);
runWithRequireContext(content, contextOpts);
} else if (isRNStorybook) {
@ -61,19 +59,24 @@ export default function testStorySnapshots(options = {}) {
const stories = storybook.getStorybook();
// Added not to break existing storyshots configs (can be removed in a future major release)
// eslint-disable-next-line
options.storyNameRegex = options.storyNameRegex || options.storyRegex;
// eslint-disable-next-line
options.test = options.test || snapshot;
// eslint-disable-next-line
for (const group of stories) {
if (options.storyKindRegex && !group.kind.match(options.storyKindRegex)) {
// eslint-disable-next-line
continue;
}
describe(suit, () => {
describe(group.kind, () => {
// eslint-disable-next-line
for (const story of group.stories) {
if (options.storyNameRegex && !story.name.match(options.storyNameRegex)) {
// eslint-disable-next-line
continue;
}

View File

@ -11,7 +11,7 @@ function requireModules(keys, root, directory, regExp, recursive) {
// TODO: Check this in windows
const entryKey = `./${path.join(directory, filename)}`;
if (regExp.test(entryKey)) {
keys[entryKey] = require(path.join(root, directory, filename));
keys[entryKey] = require(path.join(root, directory, filename)); // eslint-disable-line
return;
}
@ -45,10 +45,10 @@ export default function runWithRequireContext(content, options) {
const newRequire = request => {
if (isRelativeRequest(request)) {
return require(path.resolve(dirname, request));
return require(path.resolve(dirname, request)); // eslint-disable-line
}
return require(request);
return require(request); // eslint-disable-line
};
newRequire.resolve = require.resolve;

View File

@ -10,5 +10,5 @@ export const snapshot = snapshotWithOptions({});
export function renderOnly({ story, context }) {
const storyElement = story.render(context);
const tree = renderer.create(storyElement);
renderer.create(storyElement);
}

View File

@ -11,11 +11,14 @@ const buttonStyles = {
margin: 10,
};
const Button = ({ children, onClick }) => (
const Button = ({ children, onClick }) =>
<button style={buttonStyles} onClick={onClick}>
{children}
</button>
);
</button>;
Button.defaultProps = {
onClick: null,
};
Button.propTypes = {
children: PropTypes.string.isRequired,

View File

@ -1,6 +1,6 @@
import React from 'react';
import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { storiesOf } from '@storybook/react'; // eslint-disable-line
import { action } from '@storybook/addon-actions'; // eslint-disable-line
import Button from './Button';
storiesOf('Another Button', module)

View File

@ -11,15 +11,17 @@ const buttonStyles = {
margin: 10,
};
const Button = ({ children, onClick }) => (
const Button = ({ children, onClick }) =>
<button style={buttonStyles} onClick={onClick}>
{children}
</button>
);
</button>;
Button.propTypes = {
children: PropTypes.string.isRequired,
onClick: PropTypes.func,
};
Button.defaultProps = {
onClick: null,
};
export default Button;

View File

@ -1,11 +1,9 @@
import React from 'react';
import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { linkTo } from '@storybook/addon-links';
import { storiesOf } from '@storybook/react'; // eslint-disable-line
import { action } from '@storybook/addon-actions'; // eslint-disable-line
import Button from './Button';
import Welcome from './Welcome';
storiesOf('Button', module)
.add('with text', () => <Button onClick={action('clicked')}>Hello Button</Button>)

View File

@ -1,4 +1,7 @@
/* eslint-disable import/no-extraneous-dependencies, import/no-unresolved, import/extensions */
import React from 'react';
import PropTypes from 'prop-types';
const styles = {
main: {
@ -28,12 +31,24 @@ const styles = {
backgroundColor: '#f3f2f2',
color: '#3a3a3a',
},
note: {
opacity: 0.5,
},
};
// eslint-disable-next-line no-console
const log = () => console.log('Welcome to storybook!');
export default class Welcome extends React.Component {
showApp(e) {
e.preventDefault();
if (this.props.showApp) this.props.showApp();
constructor(props) {
super(props);
this.clickHandler = event => {
event.preventDefault();
const { showApp } = this.props;
showApp();
};
}
render() {
@ -50,14 +65,15 @@ export default class Welcome extends React.Component {
{' '}
directory.
<br />
A story is a single state of one or more UI components. You can have as many stories as you want.
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
{' '}
<a style={styles.link} href="#" onClick={this.showApp.bind(this)}>stories</a>
<a style={styles.link} onClick={this.clickHandler} role="button" tabIndex="0">stories</a>
{' '}
for a component called
{' '}
@ -70,20 +86,42 @@ export default class Welcome extends React.Component {
You can also edit those components and see changes right away.
<br />
(Try editing the <code style={styles.code}>Button</code> component
located at <code style={styles.code}>stories/Button.js</code>.)
located at <code style={styles.code}>src/stories/Button.js</code>.)
</p>
<p>
This is just one thing you can do with Storybook.
<br />
Have a look at the
{' '}
<a style={styles.link} href="https://github.com/storybooks/storybook" target="_blank">
Storybook for React
<a
style={styles.link}
href="https://github.com/storybooks/storybook"
target="_blank"
rel="noopener noreferrer"
>
Storybook
</a>
{' '}
repo for more information.
</p>
<p style={styles.note}>
<b>NOTE:</b>
<br />
Have a look at the
{' '}
<code style={styles.code}>.storybook/webpack.config.js</code>
{' '}
to add webpack
loaders and plugins you are using in this project.
</p>
</div>
);
}
}
Welcome.propTypes = {
showApp: PropTypes.function,
};
Welcome.defaultProps = {
showApp: log,
};

View File

@ -1,7 +1,7 @@
import React from 'react';
import { storiesOf } from '@storybook/react';
import { linkTo } from '@storybook/addon-links';
import { storiesOf } from '@storybook/react'; // eslint-disable-line
import { linkTo } from '@storybook/addon-links'; // eslint-disable-line
import Welcome from './Welcome';

View File

@ -2,8 +2,8 @@
First, install the `@storybook/react-native` module
```shell
npm i -D @storybook/react-native
```sh
npm install @storybook/react-native --save-dev
```
Create a new directory called `storybook` in your project root and create an entry file (index.ios.js or index.android.js) as given below. (Don't forget to replace "MyApplicationName" with your app name).
@ -18,7 +18,10 @@ configure(function() {
require('./stories');
}, module);
const StorybookUI = getStorybookUI({port: 7007, host: 'localhost'});
const StorybookUI = getStorybookUI({
port: 7007,
host: 'localhost',
});
AppRegistry.registerComponent('MyApplicationName', () => StorybookUI);
```
@ -32,9 +35,6 @@ import '@storybook/addon-links';
Then write your first story in the `stories` directory like this:
```js
// index.js
import React from 'react';
import { storiesOf } from '@storybook/react-native';
import { View, Text } from 'react-native';
@ -44,10 +44,9 @@ const style = {
alignItems: 'center',
backgroundColor: '#F5FCFF'
};
const CenteredView = (props) => (
const CenteredView = ({ children }) => (
<View style={style}>
{props.children}
{children}
</View>
);

View File

@ -1,44 +1,34 @@
{
"name": "@storybook/react-native",
"version": "3.0.0",
"version": "3.0.1",
"description": "A better way to develop React Native Components for your app",
"main": "dist/index.js",
"bin": {
"storybook": "dist/bin/storybook.js"
},
"scripts": {
"prepublish": "node ../../scripts/prepublish.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/storybooks/storybook.git"
},
"keywords": [
"react",
"react-native",
"storybook"
],
"license": "MIT",
"homepage": "https://github.com/storybooks/storybook/tree/master/app/react-native",
"bugs": {
"url": "https://github.com/storybooks/storybook/issues"
},
"homepage": "https://github.com/storybooks/storybook/tree/master/app/react-native",
"peerDependencies": {
"react": "*",
"react-native": "0.27.0 - 0.43.x"
"license": "MIT",
"main": "dist/index.js",
"bin": {
"storybook": "dist/bin/storybook.js"
},
"devDependencies": {
"babel-cli": "^6.24.1",
"react": "^15.5.4",
"react-dom": "^15.5.4",
"react-native": "^0.43.3"
"repository": {
"type": "git",
"url": "https://github.com/storybooks/storybook.git"
},
"scripts": {
"prepublish": "node ../../scripts/prepublish.js"
},
"dependencies": {
"@storybook/addon-actions": "^3.0.0",
"@storybook/addon-actions": "^3.0.1",
"@storybook/addon-links": "^3.0.0",
"@storybook/addons": "^3.0.0",
"@storybook/channel-websocket": "^3.0.0",
"@storybook/ui": "^3.0.0",
"@storybook/ui": "^3.0.1",
"autoprefixer": "^7.0.1",
"babel-core": "^6.24.1",
"babel-loader": "^7.0.0",
@ -61,6 +51,7 @@
"events": "^1.1.1",
"express": "^4.15.2",
"file-loader": "^0.11.1",
"global": "^4.3.2",
"json-loader": "^0.5.4",
"json5": "^0.5.1",
"postcss-loader": "^2.0.3",
@ -71,6 +62,17 @@
"uuid": "^3.0.1",
"webpack": "^2.4.1",
"webpack-dev-middleware": "^1.10.1",
"webpack-hot-middleware": "^2.18.0"
"webpack-hot-middleware": "^2.18.0",
"ws": "^3.0.0"
},
"devDependencies": {
"babel-cli": "^6.24.1",
"react": "^15.5.4",
"react-dom": "^15.5.4",
"react-native": "^0.43.3"
},
"peerDependencies": {
"react": "*",
"react-native": "0.27.0 - 0.43.x"
}
}

View File

@ -40,6 +40,9 @@ There are multiple options here. for example, you can export conditionally:
```js
import StorybookUI from './storybook';
import App from './app';
module.exports = __DEV__ ? StorybookUI : App;
```

View File

@ -36,7 +36,7 @@ server.listen(...listenAddr, err => {
throw err;
}
const address = `http://${program.host || 'localhost'}:${program.port}/`;
console.info(`\nReact Native Storybook started on => ${address}\n`);
console.info(`\nReact Native Storybook started on => ${address}\n`); // eslint-disable-line no-console
});
if (!program.skipPackager) {

View File

@ -1,4 +1,9 @@
import deprecate from 'util-deprecate';
// NOTE export these to keep backwards compatibility
import { action as deprecatedAction } from '@storybook/addon-actions';
import { linkTo as deprecatedLinkTo } from '@storybook/addon-links';
import Preview from './preview';
const preview = new Preview();
@ -10,10 +15,6 @@ export const configure = preview.configure.bind(preview);
export const getStorybook = preview.getStorybook.bind(preview);
export const getStorybookUI = preview.getStorybookUI.bind(preview);
// NOTE export these to keep backwards compatibility
import { action as deprecatedAction } from '@storybook/addon-actions';
import { linkTo as deprecatedLinkTo } from '@storybook/addon-links';
export const action = deprecate(
deprecatedAction,
'@storybook/react action is deprecated. See: https://github.com/storybooks/storybook/tree/master/addon/actions'

View File

@ -1,46 +1,4 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
class PreviewHelp extends Component {
render() {
return (
<div style={styles.main}>
<h1>Welcome to STORYBOOK</h1>
<p>
This is a UI component dev environment for your app.
</p>
<p>
We've added some basic stories inside the
{' '}
<span style={styles.code}>storybook/stories</span>
{' '}
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.
</p>
<p>
To see your Storybook stories on the device, you should start your mobile app for the
{' '}
<span style={styles.code}>&lt;platform&gt;</span>
{' '}
of your choice (typically ios or android).
</p>
<p>
For <span style={styles.code}>create-react-native-app</span> apps:
</p>
<div style={styles.codeBlock}>
<pre style={styles.instructionsCode}>npm run &lt;platform&gt;</pre>
</div>
<p>
For <span style={styles.code}>react-native init</span> apps:
</p>
<div style={styles.codeBlock}>
<pre style={styles.instructionsCode}>npm run &lt;platform&gt;</pre>
</div>
</div>
);
}
}
import React from 'react';
const styles = {
main: {
@ -67,4 +25,41 @@ const styles = {
},
};
export default PreviewHelp;
const PreviewHelp = () =>
<div style={styles.main}>
<h1>Welcome to storybook</h1>
<p>
This is a UI component dev environment for your app.
</p>
<p>
We've added some basic stories inside the
{' '}
<span style={styles.code}>storybook/stories</span>
{' '}
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.
</p>
<p>
To see your Storybook stories on the device, you should start your mobile app for the
{' '}
<span style={styles.code}>&lt;platform&gt;</span>
{' '}
of your choice (typically ios or android).
</p>
<p>
For <span style={styles.code}>create-react-native-app</span> apps:
</p>
<div style={styles.codeBlock}>
<pre style={styles.instructionsCode}>npm run &lt;platform&gt;</pre>
</div>
<p>
For <span style={styles.code}>react-native init</span> apps:
</p>
<div style={styles.codeBlock}>
<pre style={styles.instructionsCode}>npm run &lt;platform&gt;</pre>
</div>
</div>;
export { PreviewHelp as default };

View File

@ -1,3 +1,4 @@
import { window, location, document } from 'global';
import renderStorybookUI from '@storybook/ui';
import Provider from './provider';

View File

@ -1,4 +1,4 @@
import React, { Component } from 'react';
import React, { Component, PropTypes } from 'react';
import { View, Text } from 'react-native';
import style from './style';
@ -49,3 +49,11 @@ export default class StoryView extends Component {
);
}
}
StoryView.propTypes = {
events: PropTypes.shape({
on: PropTypes.func.isRequired,
removeListener: PropTypes.func.isRequired,
}).isRequired,
url: PropTypes.string.isRequired,
};

View File

@ -1,3 +1,5 @@
/* eslint no-underscore-dangle: 0 */
import React from 'react';
import addons from '@storybook/addons';
import createChannel from '@storybook/channel-websocket';
@ -66,10 +68,11 @@ export default class Preview {
channel = createChannel({ url });
addons.setChannel(channel);
}
channel.on('getStories', d => this._sendSetStories());
channel.on('getStories', () => this._sendSetStories());
channel.on('setCurrentStory', d => this._selectStory(d));
this._sendSetStories();
this._sendGetCurrentStory();
// finally return the preview component
return <StoryView url={webUrl} events={this._events} />;
};

View File

@ -1,3 +1,5 @@
/* eslint no-underscore-dangle: 0 */
export default class StoryKindApi {
constructor(stories, addons, decorators, kind) {
this.kind = kind;

View File

@ -1,4 +1,5 @@
let cnt = 0;
/* eslint no-underscore-dangle: 0 */
let count = 0;
export default class StoryStore {
constructor() {
@ -6,17 +7,18 @@ export default class StoryStore {
}
addStory(kind, name, fn) {
count += 1;
if (!this._data[kind]) {
this._data[kind] = {
kind,
index: cnt++,
index: count,
stories: {},
};
}
this._data[kind].stories[name] = {
name,
index: cnt++,
index: count,
fn,
};
}

View File

@ -101,7 +101,7 @@ export default function(configType, baseConfig, projectDir, configDir) {
customConfigPath = path.resolve(__dirname, './config/defaults/webpack.config.js');
}
const customConfig = require(customConfigPath);
const customConfig = require(customConfigPath); // eslint-disable-line
if (typeof customConfig === 'function') {
logger.info('=> Loading custom webpack config (full-control mode).');

View File

@ -23,7 +23,7 @@ const config = {
{
test: /\.jsx?$/,
loader: require.resolve('babel-loader'),
query: require('./babel.js'),
query: require('./babel.js'), // eslint-disable-line
include: includePaths,
exclude: excludePaths,
},

View File

@ -43,7 +43,7 @@ const config = {
{
test: /\.jsx?$/,
loader: require.resolve('babel-loader'),
query: require('./babel.prod.js'),
query: require('./babel.prod.js'), // eslint-disable-line
include: includePaths,
exclude: excludePaths,
},

View File

@ -11,7 +11,7 @@ export default class Server {
this.expressApp = express();
this.expressApp.use(storybook(options));
this.httpServer.on('request', this.expressApp);
this.wsServer = ws.Server({ server: this.httpServer });
this.wsServer = new ws.Server({ server: this.httpServer });
this.wsServer.on('connection', s => this.handleWS(s));
}
@ -22,14 +22,14 @@ export default class Server {
: {};
if (params.pairedId) {
socket.pairedId = params.pairedId;
socket.pairedId = params.pairedId; // eslint-disable-line
}
}
socket.on('message', data => {
this.wsServer.clients.forEach(c => {
if (!this.options.manualId || (socket.pairedId && socket.pairedId === c.pairedId)) {
return c.send(data);
c.send(data);
}
});
});

View File

@ -12,13 +12,13 @@ import getIndexHtml from './index.html';
function getMiddleware(configDir) {
const middlewarePath = path.resolve(configDir, 'middleware.js');
if (fs.existsSync(middlewarePath)) {
let middlewareModule = require(middlewarePath);
if (middlewareModule.__esModule) {
let middlewareModule = require(middlewarePath); // eslint-disable-line
if (middlewareModule.__esModule) { // eslint-disable-line
middlewareModule = middlewareModule.default;
}
return middlewareModule;
}
return function() {};
return () => {};
}
export default function({ projectDir, configDir, ...options }) {

Some files were not shown because too many files have changed in this diff Show More