Merge branch 'release/3.2' into update-global-hook

This commit is contained in:
Norbert de Langen 2017-07-18 12:05:56 +02:00 committed by GitHub
commit 45ada3515e
72 changed files with 818 additions and 282 deletions

View File

@ -53,6 +53,7 @@ module.exports = {
],
'import/prefer-default-export': ignore,
'react/jsx-wrap-multilines': ignore,
'react/jsx-indent': ignore,
'react/jsx-indent-props': ignore,
'react/jsx-closing-bracket-location': ignore,
'react/jsx-uses-react': error,

View File

@ -1,3 +1,22 @@
# 3.1.8
2017-July-06
#### Documentation
- Updated addon knob readme. [#1406](https://github.com/storybooks/storybook/pull/1406)
- Add a FAQ entry for shared config with next [#1390](https://github.com/storybooks/storybook/pull/1390)
- Documented webpack customization example for typescript [#1386](https://github.com/storybooks/storybook/pull/1386)
#### Maintenance
- Removed empty array, since webpack 2 doesn't support them anymore. [#1381](https://github.com/storybooks/storybook/pull/1381)
#### Dependency Upgrades
- Support webpack 3.0.0 [#1410](https://github.com/storybooks/storybook/pull/1410)
- Update react inspector to fix #1385 [#1408](https://github.com/storybooks/storybook/pull/1408)
# 3.1.7
2017-June-28

View File

@ -124,5 +124,5 @@ If you **are** using these addons, migrating is simple:
import React from 'react';
import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { link } from '@storybook/addon-links';
import { linkTo } from '@storybook/addon-links';
```

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-actions",
"version": "3.1.6",
"version": "3.2.0-alpha.7",
"description": "Action Logger addon for storybook",
"keywords": [
"storybook"

View File

@ -9,7 +9,11 @@ class ActionLogger extends Component {
}
renderAction(action) {
const counter = <div style={style.counter}>{action.count}</div>;
const counter = (
<div style={style.counter}>
{action.count}
</div>
);
return (
<div key={action.id} style={style.action}>
<div style={style.countwrap}>
@ -29,8 +33,12 @@ class ActionLogger extends Component {
render() {
return (
<div style={style.wrapper}>
<pre style={style.actions}>{this.getActionData()}</pre>
<button style={style.button} onClick={this.props.onClear}>CLEAR</button>
<pre style={style.actions}>
{this.getActionData()}
</pre>
<button style={style.button} onClick={this.props.onClear}>
CLEAR
</button>
</div>
);
}

View File

@ -30,7 +30,7 @@ export function action(name) {
// the same.
//
// Ref: https://bocoup.com/weblog/whats-in-a-function-name
const fnName = name ? name.replace(/\W+/g, '_') : 'action';
const fnName = name && typeof name === 'string' ? name.replace(/\W+/g, '_') : 'action';
// eslint-disable-next-line no-eval
const named = eval(`(function ${fnName}() { return handler.apply(this, arguments) })`);
return named;

View File

@ -9,8 +9,19 @@ const style = {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
overflow: 'auto',
};
const innerStyle = {
margin: 'auto',
};
export default function(storyFn) {
return <div style={style}>{storyFn()}</div>;
return (
<div style={style}>
<div style={innerStyle}>
{storyFn()}
</div>
</div>
);
}

View File

@ -59,10 +59,16 @@ export default class CommentItem extends Component {
</div>
<div className="comment-content" style={style.commentContent}>
<div style={style.commentHead}>
<span style={style.commentUser}>{comment.user.name}</span>
<span style={style.commentTime}>{time}</span>
<span style={style.commentUser}>
{comment.user.name}
</span>
<span style={style.commentTime}>
{time}
</span>
</div>
<span style={style.commentText}>{body}</span>
<span style={style.commentText}>
{body}
</span>
{showDelete ? this.renderDelete() : null}
</div>
</div>

View File

@ -18,7 +18,9 @@ export default function CommentsPanel(props) {
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>
<a style={style.button} href={appsUrl}>
Create an app for this repo on Storybook Hub
</a>
</div>
</div>
);

View File

@ -136,7 +136,9 @@ export default class Item extends Component {
return (
<div style={styles.item}>
<label htmlFor={`addon-event-${name}`} style={styles.label}>{title}</label>
<label htmlFor={`addon-event-${name}`} style={styles.label}>
{title}
</label>
<button
style={styles.button}
onClick={this.onEmitClick}

View File

@ -19,7 +19,8 @@
"global": "^4.3.2",
"marksy": "^2.0.0",
"prop-types": "^15.5.8",
"react-addons-create-fragment": "^15.5.3"
"react-addons-create-fragment": "^15.5.3",
"util-deprecate": "^1.0.2"
},
"devDependencies": {
"git-url-parse": "^6.2.2",

View File

@ -66,7 +66,9 @@ export default function Node(props) {
if (!name) {
return (
<div style={containerStyle}>
<span style={tagStyle}>{text}</span>
<span style={tagStyle}>
{text}
</span>
</div>
);
}
@ -75,7 +77,9 @@ export default function Node(props) {
if (!children) {
return (
<div style={containerStyle}>
<span style={tagStyle}>&lt;{name}</span>
<span style={tagStyle}>
&lt;{name}
</span>
<Props
node={node}
singleLine
@ -96,7 +100,9 @@ export default function Node(props) {
return (
<div>
<div style={containerStyleCopy}>
<span style={tagStyle}>&lt;{name}</span>
<span style={tagStyle}>
&lt;{name}
</span>
<Props
node={node}
maxPropsIntoLine={maxPropsIntoLine}
@ -117,7 +123,9 @@ export default function Node(props) {
/>
)}
<div style={containerStyleCopy}>
<span style={tagStyle}>&lt;/{name}&gt;</span>
<span style={tagStyle}>
&lt;/{name}&gt;
</span>
</div>
</div>
);

View File

@ -34,11 +34,10 @@ export default function PropTable(props) {
Object.keys(type.propTypes).forEach(property => {
const typeInfo = type.propTypes[property];
const required = typeInfo.isRequired === undefined ? 'yes' : 'no';
const description = type.__docgenInfo &&
type.__docgenInfo.props &&
type.__docgenInfo.props[property]
? type.__docgenInfo.props[property].description
: null;
const description =
type.__docgenInfo && type.__docgenInfo.props && type.__docgenInfo.props[property]
? type.__docgenInfo.props[property].description
: null;
let propType = PropTypesMap.get(typeInfo) || 'other';
if (propType === 'other') {
@ -100,15 +99,23 @@ export default function PropTable(props) {
<tbody>
{array.map(row =>
<tr key={row.property}>
<td>{row.property}</td>
<td>{row.propType}</td>
<td>{row.required}</td>
<td>
{row.property}
</td>
<td>
{row.propType}
</td>
<td>
{row.required}
</td>
<td>
{row.defaultValue === undefined
? '-'
: <PropVal val={row.defaultValue} {...propValProps} />}
</td>
<td>{row.description}</td>
<td>
{row.description}
</td>
</tr>
)}
</tbody>

View File

@ -59,7 +59,11 @@ function previewObject(val, maxPropObjectKeys) {
const names = Object.keys(val);
const items = {};
names.slice(0, maxPropObjectKeys).forEach((name, i) => {
items[`k${i}`] = <span style={valueStyles.attr}>{name}</span>;
items[`k${i}`] = (
<span style={valueStyles.attr}>
{name}
</span>
);
items[`c${i}`] = ': ';
items[`v${i}`] = <PropVal val={val[name]} />;
items[`m${i}`] = ', ';
@ -71,7 +75,9 @@ function previewObject(val, maxPropObjectKeys) {
}
return (
<span style={valueStyles.object}>
{'{'}{createFragment(items)}{'}'}
{'{'}
{createFragment(items)}
{'}'}
</span>
);
}
@ -83,19 +89,31 @@ export default function PropVal(props) {
let content = null;
if (typeof val === 'number') {
content = <span style={valueStyles.number}>{val}</span>;
content = (
<span style={valueStyles.number}>
{val}
</span>
);
} else if (typeof val === 'string') {
if (val.length > maxPropStringLength) {
val = `${val.slice(0, maxPropStringLength)}`;
}
content = <span style={valueStyles.string}>"{val}"</span>;
content = (
<span style={valueStyles.string}>
"{val}"
</span>
);
braceWrap = false;
} else if (typeof val === 'boolean') {
content = <span style={valueStyles.bool}>{`${val}`}</span>;
} else if (Array.isArray(val)) {
content = previewArray(val, maxPropArrayLength);
} else if (typeof val === 'function') {
content = <span style={valueStyles.func}>{val.name ? `${val.name}()` : 'anonymous()'}</span>;
content = (
<span style={valueStyles.func}>
{val.name ? `${val.name}()` : 'anonymous()'}
</span>
);
} else if (!val) {
content = <span style={valueStyles.empty}>{`${val}`}</span>;
} else if (typeof val !== 'object') {
@ -112,12 +130,23 @@ export default function PropVal(props) {
if (!braceWrap) return content;
return <span>{content}</span>;
return (
<span>
{content}
</span>
);
}
PropVal.propTypes = {
val: PropTypes.any.isRequired, // eslint-disable-line
maxPropObjectKeys: PropTypes.number.isRequired,
maxPropArrayLength: PropTypes.number.isRequired,
maxPropStringLength: PropTypes.number.isRequired,
PropVal.defaultProps = {
val: null,
maxPropObjectKeys: 3,
maxPropArrayLength: 3,
maxPropStringLength: 50,
};
PropVal.propTypes = {
val: PropTypes.any, // eslint-disable-line
maxPropObjectKeys: PropTypes.number,
maxPropArrayLength: PropTypes.number,
maxPropStringLength: PropTypes.number,
};

View File

@ -32,8 +32,14 @@ export default function Props(props) {
names.forEach((name, i) => {
items.push(
<span key={name}>
{breakIntoNewLines ? <span><br />&nbsp;&nbsp;</span> : ' '}
<span style={propNameStyle}>{name}</span>
{breakIntoNewLines
? <span>
<br />&nbsp;&nbsp;
</span>
: ' '}
<span style={propNameStyle}>
{name}
</span>
{/* Use implicit true: */}
{(!nodeProps[name] || typeof nodeProps[name] !== 'boolean') &&
<span>
@ -53,7 +59,11 @@ export default function Props(props) {
);
});
return <span>{items}</span>;
return (
<span>
{items}
</span>
);
}
Props.defaultProps = {

View File

@ -180,9 +180,13 @@ export default class Story extends React.Component {
<div style={this.state.stylesheet.children}>
{this.props.children}
</div>
<a style={linkStyle} onClick={openOverlay} role="button" tabIndex="0">Show Info</a>
<a style={linkStyle} onClick={openOverlay} role="button" tabIndex="0">
Show Info
</a>
<div style={infoStyle}>
<a style={linkStyle} onClick={closeOverlay} role="button" tabIndex="0">×</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()}
@ -204,8 +208,12 @@ export default class Story extends React.Component {
return (
<div style={this.state.stylesheet.header.body}>
<h1 style={this.state.stylesheet.header.h1}>{this.props.context.kind}</h1>
<h2 style={this.state.stylesheet.header.h2}>{this.props.context.story}</h2>
<h1 style={this.state.stylesheet.header.h1}>
{this.props.context.kind}
</h1>
<h2 style={this.state.stylesheet.header.h2}>
{this.props.context.story}
</h2>
</div>
);
}

View File

@ -61,7 +61,11 @@ export function Pre(props) {
lineHeight: 1.5,
overflowX: 'scroll',
};
return <pre style={style}>{props.children}</pre>;
return (
<pre style={style}>
{props.children}
</pre>
);
}
Pre.propTypes = { children: PropTypes.node };
@ -74,7 +78,11 @@ export function Blockquote(props) {
borderLeft: '8px solid #fafafa',
padding: '1rem',
};
return <blockquote style={style}>{props.children}</blockquote>;
return (
<blockquote style={style}>
{props.children}
</blockquote>
);
}
Blockquote.propTypes = { children: PropTypes.node };

View File

@ -20,7 +20,11 @@ export function H1(props) {
padding: 0,
fontSize: '40px',
};
return <h1 id={props.id} style={styles}>{props.children}</h1>;
return (
<h1 id={props.id} style={styles}>
{props.children}
</h1>
);
}
H1.defaultProps = defaultProps;
@ -34,7 +38,11 @@ export function H2(props) {
padding: 0,
fontSize: '30px',
};
return <h2 id={props.id} style={styles}>{props.children}</h2>;
return (
<h2 id={props.id} style={styles}>
{props.children}
</h2>
);
}
H2.defaultProps = defaultProps;
@ -49,7 +57,11 @@ export function H3(props) {
fontSize: '22px',
textTransform: 'uppercase',
};
return <h3 id={props.id} style={styles}>{props.children}</h3>;
return (
<h3 id={props.id} style={styles}>
{props.children}
</h3>
);
}
H3.defaultProps = defaultProps;
@ -63,7 +75,11 @@ export function H4(props) {
padding: 0,
fontSize: '20px',
};
return <h4 id={props.id} style={styles}>{props.children}</h4>;
return (
<h4 id={props.id} style={styles}>
{props.children}
</h4>
);
}
H4.defaultProps = defaultProps;
@ -77,7 +93,11 @@ export function H5(props) {
padding: 0,
fontSize: '18px',
};
return <h5 id={props.id} style={styles}>{props.children}</h5>;
return (
<h5 id={props.id} style={styles}>
{props.children}
</h5>
);
}
H5.defaultProps = defaultProps;
@ -91,7 +111,11 @@ export function H6(props) {
padding: 0,
fontSize: '18px',
};
return <h6 id={props.id} style={styles}>{props.children}</h6>;
return (
<h6 id={props.id} style={styles}>
{props.children}
</h6>
);
}
H6.defaultProps = defaultProps;

View File

@ -10,7 +10,11 @@ export function P(props) {
...baseFonts,
fontSize: '15px',
};
return <p style={style}>{props.children}</p>;
return (
<p style={style}>
{props.children}
</p>
);
}
P.defaultProps = defaultProps;
@ -21,7 +25,11 @@ export function LI(props) {
...baseFonts,
fontSize: '15px',
};
return <li style={style}>{props.children}</li>;
return (
<li style={style}>
{props.children}
</li>
);
}
LI.defaultProps = defaultProps;
@ -32,7 +40,11 @@ export function UL(props) {
...baseFonts,
fontSize: '15px',
};
return <ul style={style}>{props.children}</ul>;
return (
<ul style={style}>
{props.children}
</ul>
);
}
UL.defaultProps = defaultProps;

View File

@ -1,7 +1,12 @@
import React from 'react';
import deprecate from 'util-deprecate';
import _Story from './components/Story';
import { H1, H2, H3, H4, H5, H6, Code, P, UL, A, LI } from './components/markdown';
function addonCompose(addonFn) {
return storyFn => context => addonFn(storyFn, context);
}
export const Story = _Story;
const defaultOptions = {
@ -29,59 +34,62 @@ const defaultMarksyConf = {
ul: UL,
};
export default {
addWithInfo(storyName, info, storyFn, _options) {
if (typeof storyFn !== 'function') {
if (typeof info === 'function') {
export function addInfo(storyFn, context, info, _options) {
if (typeof storyFn !== 'function') {
if (typeof info === 'function') {
_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');
}
} else {
throw new Error('No story defining function has been specified');
}
}
const options = {
...defaultOptions,
..._options,
};
const options = {
...defaultOptions,
..._options,
};
// props.propTables can only be either an array of components or null
// propTables option is allowed to be set to 'false' (a boolean)
// if the option is false, replace it with null to avoid react warnings
if (!options.propTables) {
options.propTables = null;
}
// props.propTables can only be either an array of components or null
// propTables option is allowed to be set to 'false' (a boolean)
// if the option is false, replace it with null to avoid react warnings
if (!options.propTables) {
options.propTables = null;
}
const marksyConf = { ...defaultMarksyConf };
if (options && options.marksyConf) {
Object.assign(marksyConf, options.marksyConf);
}
const marksyConf = { ...defaultMarksyConf };
if (options && options.marksyConf) {
Object.assign(marksyConf, options.marksyConf);
}
const props = {
info,
context,
showInline: Boolean(options.inline),
showHeader: Boolean(options.header),
showSource: Boolean(options.source),
propTables: options.propTables,
propTablesExclude: options.propTablesExclude,
styles: typeof options.styles === 'function' ? options.styles : s => s,
marksyConf,
maxPropObjectKeys: options.maxPropObjectKeys,
maxPropArrayLength: options.maxPropArrayLength,
maxPropsIntoLine: options.maxPropsIntoLine,
maxPropStringLength: options.maxPropStringLength,
};
return (
<Story {...props}>
{storyFn(context)}
</Story>
);
}
return this.add(storyName, context => {
const props = {
info,
context,
showInline: Boolean(options.inline),
showHeader: Boolean(options.header),
showSource: Boolean(options.source),
propTables: options.propTables,
propTablesExclude: options.propTablesExclude,
styles: typeof options.styles === 'function' ? options.styles : s => s,
marksyConf,
maxPropObjectKeys: options.maxPropObjectKeys,
maxPropArrayLength: options.maxPropArrayLength,
maxPropsIntoLine: options.maxPropsIntoLine,
maxPropStringLength: options.maxPropStringLength,
};
export const withInfo = (info, _options) =>
addonCompose((storyFn, context) => addInfo(storyFn, context, info, _options));
return (
<Story {...props}>
{storyFn(context)}
</Story>
);
});
},
export default {
addWithInfo: deprecate(function addWithInfo(storyName, info, storyFn, _options) {
return this.add(storyName, withInfo(info, _options)(storyFn));
}, '@storybook/addon-info .addWithInfo() addon is deprecated, use withInfo() from the same package instead. \nSee https://github.com/storybooks/storybook/tree/master/addons/info'),
};
export function setDefaults(newDefaults) {

View File

@ -0,0 +1,59 @@
/* global document */
import React from 'react';
import ReactDOM from 'react-dom';
import AddonInfo, { withInfo, setDefaults, addInfo } from './';
/* eslint-disable */
const TestComponent = ({ func, obj, array, number, string, bool, empty }) =>
<div>
<h1>{func}</h1>
<h2>{obj.toString()}</h2>
<h3>{array}</h3>
<h4>{number}</h4>
<h5>{string}</h5>
<h6>{bool}</h6>
<p>{empty}</p>
<a href="#">test</a>
<code>storiesOf</code>
<ui>
<li>1</li>
<li>2</li>
</ui>
</div>;
/* eslint-enable */
const testContext = { kind: 'addon_info', story: 'jest_test' };
const testOptions = { propTables: false };
describe('addon Info', () => {
const story = context =>
<div>
It's a {context.story} story:
<TestComponent
func={x => x + 1}
obj={{ a: 'a', b: 'b' }}
array={[1, 2, 3]}
number={7}
string={'seven'}
bool
/>
</div>;
const api = {
add: (name, fn) => fn(testContext),
};
it('should render <Info /> and markdown', () => {
const Info = withInfo(
'# Test story \n## with markdown info \ncontaing **bold**, *cursive* text and `code`'
)(story);
ReactDOM.render(<Info />, document.createElement('div'));
});
it('should render with missed info', () => {
setDefaults(testOptions);
addInfo(null, testContext, story, testOptions);
});
it('should show deprecation warning', () => {
const addWithInfo = AddonInfo.addWithInfo.bind(api);
addWithInfo('jest', story);
});
});

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-knobs",
"version": "3.2.0-alpha.5",
"version": "3.2.0-alpha.7",
"description": "Storybook Addon Prop Editor Component",
"license": "MIT",
"main": "dist/index.js",

View File

@ -147,7 +147,9 @@ export default class Panel extends React.Component {
<div style={styles.panel}>
<PropForm knobs={knobsArray} onFieldChange={this.handleChange} />
</div>
<button style={styles.resetButton} onClick={this.reset}>RESET</button>
<button style={styles.resetButton} onClick={this.reset}>
RESET
</button>
</div>
);
}

View File

@ -24,7 +24,11 @@ class SelectType extends React.Component {
value: key,
};
return <option {...opts}>{val}</option>;
return (
<option {...opts}>
{val}
</option>
);
}
_options(values) {
let data = [];

View File

@ -5,10 +5,10 @@ import { WithNotes as ReactWithNotes } from './react';
export const addonNotes = ({ notes }) => {
const channel = addons.getChannel();
return getStory => () => {
return getStory => (context) => {
// send the notes to the channel before the story is rendered
channel.emit('storybook/notes/add_notes', notes);
return getStory();
return getStory(context);
};
};

View File

@ -44,6 +44,7 @@ setOptions({
showSearchBox: false,
downPanelInRight: false,
sortStoriesByKind: false,
hierarchySeparator: /\//,
});
storybook.configure(() => require('./stories'), module);

View File

@ -68,15 +68,15 @@ initStoryshots({
});
```
### `suit`
### `suite`
By default, Storyshots groups stories inside a Jest test suit called "Storyshots". You could change it like this:
By default, Storyshots groups stories inside a Jest test suite called "Storyshots". You could change it like this:
```js
import initStoryshots from '@storybook/addon-storyshots';
initStoryshots({
suit: 'MyStoryshots'
suite: 'MyStoryshots'
});
```

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-storyshots",
"version": "3.2.0-alpha.5",
"version": "3.2.0-alpha.7",
"description": "StoryShots is a Jest Snapshot Testing Addon for Storybook.",
"license": "MIT",
"main": "dist/index.js",
@ -22,7 +22,7 @@
"devDependencies": {
"@storybook/addons": "^3.1.6",
"@storybook/channels": "^3.1.6",
"@storybook/react": "^3.2.0-alpha.5",
"@storybook/react": "^3.2.0-alpha.7",
"babel-cli": "^6.24.1",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-preset-es2015": "^6.24.1",
@ -33,7 +33,7 @@
"peerDependencies": {
"@storybook/addons": "^3.1.6",
"@storybook/channels": "^3.1.6",
"@storybook/react": "^3.2.0-alpha.5",
"@storybook/react": "^3.2.0-alpha.7",
"babel-core": "^6.24.1",
"react": "*",
"react-test-renderer": "*"

View File

@ -56,7 +56,8 @@ export default function testStorySnapshots(options = {}) {
throw new Error('testStorySnapshots is intended only to be used inside jest');
}
const suit = options.suit || 'Storyshots';
// NOTE: keep `suit` typo for backwards compatibility
const suite = options.suite || options.suit || 'Storyshots';
const stories = storybook.getStorybook();
// Added not to break existing storyshots configs (can be removed in a future major release)
@ -72,7 +73,7 @@ export default function testStorySnapshots(options = {}) {
continue;
}
describe(suit, () => {
describe(suite, () => {
describe(group.kind, () => {
// eslint-disable-next-line
for (const story of group.stories) {

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/react-native",
"version": "3.2.0-alpha.5",
"version": "3.2.0-alpha.7",
"description": "A better way to develop React Native Components for your app",
"keywords": [
"react",
@ -24,11 +24,11 @@
"prepublish": "node ../../scripts/prepublish.js"
},
"dependencies": {
"@storybook/addon-actions": "^3.1.6",
"@storybook/addon-actions": "^3.2.0-alpha.7",
"@storybook/addon-links": "^3.2.0-alpha.5",
"@storybook/addons": "^3.1.6",
"@storybook/channel-websocket": "^3.1.6",
"@storybook/ui": "^3.2.0-alpha.5",
"@storybook/ui": "^3.2.0-alpha.7",
"autoprefixer": "^7.0.1",
"babel-core": "^6.24.1",
"babel-loader": "^7.0.0",

View File

@ -28,27 +28,19 @@ const styles = {
const PreviewHelp = () =>
<div style={styles.main}>
<h1>Welcome to storybook</h1>
<p>This is a UI component dev environment for your app.</p>
<p>
This is a UI component dev environment for your app.
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>
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). (Note that due to an implementation detail, your
stories will only show up in the left-pane after your device has connected to this storybook
server.)
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).
(Note that due to an implementation detail, your stories will only show up in the left-pane
after your device has connected to this storybook server.)
</p>
<p>
For <span style={styles.code}>create-react-native-app</span> apps:

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/react",
"version": "3.2.0-alpha.5",
"version": "3.2.0-alpha.7",
"description": "Storybook for React: Develop React Component in isolation with Hot Reloading.",
"homepage": "https://github.com/storybooks/storybook/tree/master/apps/react",
"bugs": {
@ -22,11 +22,11 @@
"prepublish": "node ../../scripts/prepublish.js"
},
"dependencies": {
"@storybook/addon-actions": "^3.1.6",
"@storybook/addon-actions": "^3.2.0-alpha.7",
"@storybook/addon-links": "^3.2.0-alpha.5",
"@storybook/addons": "^3.1.6",
"@storybook/channel-postmessage": "^3.1.6",
"@storybook/ui": "^3.2.0-alpha.5",
"@storybook/ui": "^3.2.0-alpha.7",
"airbnb-js-shims": "^1.1.1",
"autoprefixer": "^7.1.1",
"babel-core": "^6.24.1",

View File

@ -32,7 +32,9 @@ const codeStyle = {
const ErrorDisplay = ({ error }) =>
<div style={mainStyle}>
<div style={headingStyle}>{error.message}</div>
<div style={headingStyle}>
{error.message}
</div>
<pre style={codeStyle}>
<code>
{error.stack}

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/vue",
"version": "3.2.0-alpha.6",
"version": "3.2.0-alpha.7",
"description": "Storybook for Vue: Develop Vue Component in isolation with Hot Reloading.",
"homepage": "https://github.com/storybooks/storybook/tree/master/apps/vue",
"bugs": {

View File

@ -8,7 +8,9 @@ import './style.css';
const DocsContent = ({ title, content, editUrl }) =>
<div id="docs-content">
<div className="content">
<h2 className="title">{title}</h2>
<h2 className="title">
{title}
</h2>
<p>
<a className="edit-link" href={editUrl} target="_blank" rel="noopener noreferrer">
Edit this page

View File

@ -24,11 +24,19 @@ class Nav extends React.Component {
}
renderNavOpts(nav) {
return <option value={nav.id} key={nav.id}>{nav.title}</option>;
return (
<option value={nav.id} key={nav.id}>
{nav.title}
</option>
);
}
renderHeadingOpts(section) {
return <option value={section.id} key={section.id}>{section.heading}</option>;
return (
<option value={section.id} key={section.id}>
{section.heading}
</option>
);
}
render() {
const { sections, selectedSection, selectedItem } = this.props;

View File

@ -7,18 +7,21 @@ const Nav = ({ sections, selectedSectionId, selectedItemId }) =>
<div id="nav">
{sections.map(section =>
<div key={section.id}>
<h3>{section.heading}</h3>
<h3>
{section.heading}
</h3>
<ul>
{section.items.map(item => {
const cssClass = section.id === selectedSectionId && item.id === selectedItemId
? 'selected'
: '';
const cssClass =
section.id === selectedSectionId && item.id === selectedItemId ? 'selected' : '';
const url = `/${section.id}/${item.id}/`;
return (
<li key={item.id}>
<Link className={cssClass} to={url}>{item.title}</Link>
<Link className={cssClass} to={url}>
{item.title}
</Link>
</li>
);
})}

View File

@ -10,11 +10,7 @@ const Footer = () =>
<div className="row logos">
<div className="col-xs-12">
<center>
Maintained by the
{' '}
<Link to="/basics/community/">
Storybook Community
</Link>
Maintained by the <Link to="/basics/community/">Storybook Community</Link>
.
</center>
<center>

View File

@ -14,11 +14,23 @@ const GridItem = ({ title, description, source, demo, thumbnail }) =>
<div className="overlay" />
</div>
<div className="text">
<h2>{title}</h2>
<p className="desc">{description}</p>
<h2>
{title}
</h2>
<p className="desc">
{description}
</p>
<div className="button-row">
{demo ? <a href={demo} {...linkProps}>Demo</a> : null}
{source ? <a href={source} {...linkProps}>Source</a> : null}
{demo
? <a href={demo} {...linkProps}>
Demo
</a>
: null}
{source
? <a href={source} {...linkProps}>
Source
</a>
: null}
</div>
</div>
</div>;

View File

@ -17,7 +17,9 @@ const Item = ({ storybook, owner, source }) =>
{storybook.name}
</a>
</p>
<a href={source} target="_blank" rel="noopener noreferrer">source</a>
<a href={source} target="_blank" rel="noopener noreferrer">
source
</a>
</div>
</div>;
Item.propTypes = {

View File

@ -46,23 +46,37 @@ const MainLinks = () =>
id="search"
placeholder="type to search"
/>
<span className="form-control-feedback" aria-hidden="true">🔍</span>
<span className="form-control-feedback" aria-hidden="true">
🔍
</span>
</div>
</div>
<div className="col-sm-4 read-docs">
<Link to="/basics/introduction/"><h3>Basics</h3></Link>
<Link to="/basics/introduction/">
<h3>Basics</h3>
</Link>
<ul>
<li><Link to="/basics/quick-start-guide/">Quick setup</Link></li>
<li><Link to="/basics/slow-start-guide/">Adding to existing project</Link></li>
<li><Link to="/basics/writing-stories/">Writing stories</Link></li>
<li>
<Link to="/basics/quick-start-guide/">Quick setup</Link>
</li>
<li>
<Link to="/basics/slow-start-guide/">Adding to existing project</Link>
</li>
<li>
<Link to="/basics/writing-stories/">Writing stories</Link>
</li>
</ul>
</div>
<div className="col-sm-4 read-docs">
<Link to="/configurations/default-config/"><h3>Configuration</h3></Link>
<Link to="/configurations/default-config/">
<h3>Configuration</h3>
</Link>
<ul>
<li><Link to="/configurations/custom-babel-config/">Babel configurations</Link></li>
<li>
<Link to="/configurations/custom-babel-config/">Babel configurations</Link>
</li>
<li>
<Link to="/configurations/custom-webpack-config/">Webpack configurations</Link>
</li>
@ -76,9 +90,13 @@ const MainLinks = () =>
</div>
<div className="col-sm-4 read-docs">
<Link to="/configurations/default-config/"><h3>Addons</h3></Link>
<Link to="/configurations/default-config/">
<h3>Addons</h3>
</Link>
<ul>
<li><Link to="/addons/introduction/">Intro to Addons</Link></li>
<li>
<Link to="/addons/introduction/">Intro to Addons</Link>
</li>
<li>
<Link to="/addons/using-addons/">Using Addons</Link>
</li>

View File

@ -6,17 +6,14 @@ const Platform = () =>
<div className="col-md-12">
<h3 className="built-for">Built for</h3>
<p className="platforms">
<a
href="https://github.com/storybooks/storybook/tree/master/app/react"
target="_blank"
rel="noopener noreferrer"
>
React
</a>
{' '}
&
{' '}
</a>{' '}
&{' '}
<a
href="https://github.com/storybooks/storybook/tree/master/app/react-native"
target="_blank"

View File

@ -67,9 +67,9 @@ const UsedBy = ({ users }) =>
</div>
</div>
</div>
<Link to="/examples/" className="used-by-more-examples">
See more examples
</Link>
<div className="used-by-more-examples">
<Link to="/examples/">See more examples</Link>
</div>
</div>;
UsedBy.propTypes = {
users: PropTypes.array, // eslint-disable-line

View File

@ -63,5 +63,13 @@
.used-by-more-examples {
text-align: center;
display: block;
margin-top: 10px;
margin-top: 50px;
font-size: 2em;
}
.used-by-more-examples a {
background-color: #e4004f;
color: #fff;
padding: 20px;
border-radius: 6px;
}

View File

@ -41,7 +41,9 @@ const HTML = props => {
<meta charSet="utf-8" />
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{title}</title>
<title>
{title}
</title>
<link rel="icon" href={favicon} type="image/x-icon" />
{css}
</head>

View File

@ -33,8 +33,8 @@
"color-pairs-picker": "^1.3.5",
"docsearch.js": "^2.3.3",
"front-matter": "^2.1.2",
"gatsby": "^0.12.45",
"gh-pages": "^0.12.0",
"gatsby": "0.12.48",
"gh-pages": "^1.0.0",
"global": "^4.3.2",
"highlight.js": "^9.12.0",
"loader-utils": "^1.1.0",

View File

@ -65,6 +65,8 @@ configure(loadStories, module);
Here we use Webpack's [require.context](https://webpack.github.io/docs/context.html#require-context) to load modules dynamically. Have a look at the relevant Webpack [docs](https://webpack.github.io/docs/context.html#require-context) to learn more about how to use require.context.
The **React Native** packager resolves all the imports at build-time, so it's not possible to load modules dynamically. If you don't want to import all your stories manually you can use [react-native-storybook-loader](https://github.com/elderfo/react-native-storybook-loader) to automatically create the import statements for all of your stories.
## Using Decorators
A decorator is a way to wrap a story with a common set of component(s). Let's say you want to center all your stories. Here is how we can do this with a decorator:

View File

@ -25,16 +25,23 @@ algolia:
description: Lightning-fast, hyper-configurable search.
source: https://github.com/algolia/react-instantsearch/
demo: https://community.algolia.com/react-instantsearch/storybook/
rebass:
thumbnail: ./thumbnails/rebass.png
title: Rebass
description: Functional React UI component library
demo: http://jxnblk.com/rebass/stories/
source: https://github.com/jxnblk/rebass
site: http://jxnblk.com/rebass
coursera:
thumbnail: ./thumbnails/coursera-ui.png
title: Coursera
description: Coursera UI component library
demo: https://building.coursera.org/ui/
demo: https://webedx-spark.github.io/coursera-ui/
artsy:
thumbnail: ./thumbnails/artsy.png
title: Artsy Force
description: Artsy's "Force" component library
demo: https://artsy.github.io/reaction-force/
demo: https://artsy.github.io/reaction/
source: https://github.com/artsy/reaction-force
site: https://artsy.net
necolas:

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

View File

@ -34,31 +34,28 @@ Then, install StoryShots into your app with:
npm i -D @storybook/addon-storyshots
```
Then, add the following NPM script into your package.json:
Then, assuming you are using Jest for testing, you can create a test file `storyshots.test.js` that contains the following:
```json
{
"scripts": {
"test-storybook": "storyshots"
}
}
```js
import initStoryshots from '@storybook/addon-storyshots';
initStoryshots({ /* configuration options */ });
```
Now you can run the above command with:
Now you can snapshot test all of your stories with:
```sh
npm run test-storybook
npm test
```
This will save the initial set of snapshots inside your Storybook config directory.
![StoryShots First](../static/storyshots-first-run.png)
After you complete any changes, you can run the above NPM script again and find our structural changes.
After you complete any changes, you can run the test again and find all structural changes.
![StoryShots Diff View](../static/storyshots-diff-view.png)
* * *
StoryShots also comes with a few important [productive features](https://github.com/storybooks/storybook/tree/master/addons/storyshots#key-features) that can be customized.
Have a look at the StoryShots [repo](https://github.com/storybooks/storybook/tree/master/addons/storyshots) for more information.
StoryShots also comes with a variety of customization options. Have a look at the StoryShots [repo](https://github.com/storybooks/storybook/tree/master/addons/storyshots) for more information.

View File

@ -10,7 +10,9 @@ const Markdown = ({ route }) => {
return (
<DocumentTitle title={`${post.title} | ${config.siteTitle}`}>
<div className="markdown">
<h1>{post.title}</h1>
<h1>
{post.title}
</h1>
<p>
<a className="edit-link" href={editUrl} target="_blank" rel="noopener noreferrer">
Edit this page

View File

@ -19,17 +19,17 @@
"uuid": "^3.0.1"
},
"devDependencies": {
"@storybook/addon-actions": "^3.0.0",
"@storybook/addon-actions": "3.2.0-alpha.7",
"@storybook/addon-centered": "^3.0.0",
"@storybook/addon-events": "^3.0.0",
"@storybook/addon-knobs": "3.2.0-alpha.5",
"@storybook/addon-knobs": "3.2.0-alpha.7",
"@storybook/addon-info": "^3.0.0",
"@storybook/addon-links": "3.2.0-alpha.5",
"@storybook/addon-notes": "3.2.0-alpha.5",
"@storybook/addon-options": "3.2.0-alpha.5",
"@storybook/addon-storyshots": "3.2.0-alpha.5",
"@storybook/addon-storyshots": "3.2.0-alpha.7",
"@storybook/addons": "^3.0.0",
"@storybook/react": "3.2.0-alpha.5",
"@storybook/react": "3.2.0-alpha.7",
"react-scripts": "1.0.1"
},
"private": true

View File

@ -60,8 +60,12 @@ export default class Logger extends Component {
<dl>
{events.map(({ id, name, payload }) =>
<div style={styles.item} key={id}>
<dt><b>Event name:</b> {name}</dt>
<dd><b>Event payload:</b> {json.plain(payload)}</dd>
<dt>
<b>Event name:</b> {name}
</dt>
<dd>
<b>Event payload:</b> {json.plain(payload)}
</dd>
</div>
)}
</dl>

View File

@ -19,6 +19,7 @@ import {
object,
} from '@storybook/addon-knobs';
import centered from '@storybook/addon-centered';
import { withInfo } from '@storybook/addon-info';
import { Button, Welcome } from '@storybook/react/demo';
@ -37,6 +38,22 @@ const emit = emiter.emit.bind(emiter);
storiesOf('Welcome', module).add('to Storybook', () => <Welcome showApp={linkTo('Button')} />);
const InfoButton = () =>
<span
style={{
fontFamily: 'sans-serif',
fontSize: 12,
textDecoration: 'none',
background: 'rgb(34, 136, 204)',
color: 'rgb(255, 255, 255)',
padding: '5px 15px',
margin: 10,
borderRadius: '0px 0px 0px 5px',
}}
>
{' '}Show Info{' '}
</span>;
storiesOf('Button', module)
.addDecorator(withKnobs)
.add('with text', () => <Button onClick={action('clicked')}>Hello Button</Button>)
@ -75,21 +92,56 @@ storiesOf('Button', module)
return (
<div style={style}>
<p>{intro}</p>
<p>My birthday is: {new Date(birthday).toLocaleDateString()}</p>
<p>My wallet contains: ${dollars.toFixed(2)}</p>
<p>
{intro}
</p>
<p>
My birthday is: {new Date(birthday).toLocaleDateString()}
</p>
<p>
My wallet contains: ${dollars.toFixed(2)}
</p>
<p>In my backpack, I have:</p>
<ul>
{items.map(item => <li key={item}>{item}</li>)}
{items.map(item =>
<li key={item}>
{item}
</li>
)}
</ul>
<p>{salutation}</p>
<p>
{salutation}
</p>
</div>
);
})
.addWithInfo(
'with some info',
'Use the [info addon](https://github.com/storybooks/storybook/tree/master/addons/info) with its painful API.',
() => <Button>click the "?" in top right for info</Button>
context =>
<div>
click the <InfoButton /> label in top right for info about "{context.story}"
</div>
)
.add(
'with new info',
withInfo(
'Use the [info addon](https://github.com/storybooks/storybook/tree/master/addons/info) with its new painless API.'
)(context =>
<div>
click the <InfoButton /> label in top right for info about "{context.story}"
</div>
)
)
.add(
'addons composition',
withInfo('see Notes panel for composition info')(
addonNotes({ notes: 'Composition: Info(Notes())' })(context =>
<div>
click the <InfoButton /> label in top right for info about "{context.story}"
</div>
)
)
);
storiesOf('App', module).add('full app', () => <App />);
@ -177,7 +229,11 @@ storiesOf('Addon Knobs deprecated Decorator', module)
const age = number('Age', 120);
const content = `I am ${name} and I'm ${age} years old.`;
return <div>{content}</div>;
return (
<div>
{content}
</div>
);
});
storiesOf('Addon Knobs', module).add(
@ -187,14 +243,26 @@ storiesOf('Addon Knobs', module).add(
const age = number('Age', 89);
const content = `I am ${name} and I'm ${age} years old.`;
return <div>{content}</div>;
return (
<div>
{content}
</div>
);
})
);
storiesOf('component.base.Link')
.addDecorator(withKnobs)
.add('first', () => <a>{text('firstLink', 'first link')}</a>)
.add('second', () => <a>{text('secondLink', 'second link')}</a>);
.add('first', () =>
<a>
{text('firstLink', 'first link')}
</a>
)
.add('second', () =>
<a>
{text('secondLink', 'second link')}
</a>
);
storiesOf('component.base.Span')
.add('first', () => <span>first span</span>)
@ -205,8 +273,20 @@ storiesOf('component.common.Div')
.add('second', () => <div>second div</div>);
storiesOf('component.common.Table')
.add('first', () => <table><tr><td>first table</td></tr></table>)
.add('second', () => <table><tr><td>first table</td></tr></table>);
.add('first', () =>
<table>
<tr>
<td>first table</td>
</tr>
</table>
)
.add('second', () =>
<table>
<tr>
<td>first table</td>
</tr>
</table>
);
storiesOf('component.Button')
.add('first', () => <button>first button</button>)
@ -216,7 +296,11 @@ storiesOf('component.Button')
storiesOf('Cells¯\\_(ツ)_/¯Molecules.Atoms/simple', module)
.addDecorator(withKnobs)
.add('with text', () => <Button>{text('buttonText', 'Hello Button')}</Button>)
.add('with text', () =>
<Button>
{text('buttonText', 'Hello Button')}
</Button>
)
.add('with some emoji', () => <Button>😀 😎 👍 💯</Button>);
storiesOf('Cells/Molecules/Atoms.more', module)
@ -230,4 +314,3 @@ storiesOf('Cells/Molecules', module)
storiesOf('Cells.Molecules.Atoms', module)
.add('with text2', () => <Button>Hello Button</Button>)
.add('with some emoji2', () => <Button>😀 😎 👍 💯</Button>);

View File

@ -12,7 +12,11 @@ import Welcome from './Welcome';
storiesOf('Welcome', module).add('to Storybook', () => <Welcome showApp={linkTo('Button')} />);
storiesOf('Button', module)
.addDecorator(getStory => <CenterView>{getStory()}</CenterView>)
.addDecorator(getStory =>
<CenterView>
{getStory()}
</CenterView>
)
.add('with text', () =>
<Button onPress={action('clicked-text')}>
<Text>Hello Button</Text>

View File

@ -13,7 +13,8 @@
"test-cra",
"react-native-vanilla",
"vue-example",
"@storybook/components"
"@storybook/components",
"@storybook/vue"
]
}
},
@ -24,5 +25,5 @@
"examples/*"
],
"concurrency": 1,
"version": "3.2.0-alpha.5"
"version": "3.2.0-alpha.7"
}

View File

@ -14,7 +14,11 @@ import Welcome from './Welcome';
storiesOf('Welcome', module).add('to Storybook', () => <Welcome showApp={linkTo('Button')} />);
storiesOf('Button', module)
.addDecorator(getStory => <CenterView>{getStory()}</CenterView>)
.addDecorator(getStory =>
<CenterView>
{getStory()}
</CenterView>
)
.add('with text', () =>
<Button onPress={action('clicked-text')}>
<Text>Hello Button</Text>

View File

@ -12,7 +12,11 @@ import Welcome from './Welcome';
storiesOf('Welcome', module).add('to Storybook', () => <Welcome showApp={linkTo('Button')} />);
storiesOf('Button', module)
.addDecorator(getStory => <CenterView>{getStory()}</CenterView>)
.addDecorator(getStory =>
<CenterView>
{getStory()}
</CenterView>
)
.add('with text', () =>
<Button onPress={action('clicked-text')}>
<Text>Hello Button</Text>

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/ui",
"version": "3.2.0-alpha.5",
"version": "3.2.0-alpha.7",
"description": "Core Storybook UI",
"license": "MIT",
"main": "dist/index.js",

View File

@ -40,16 +40,16 @@ class DownPanel extends Component {
if (name === this.props.selectedPanel) {
Object.assign(panelStyle, { flex: 1, display: 'flex' });
}
return <div key={name} style={panelStyle} role="tabpanel">{panel.render()}</div>;
return (
<div key={name} style={panelStyle} role="tabpanel">
{panel.render()}
</div>
);
});
}
renderEmpty() {
return (
<div style={style.empty}>
no panels available
</div>
);
return <div style={style.empty}>no panels available</div>;
}
render() {
@ -58,8 +58,12 @@ class DownPanel extends Component {
}
return (
<div style={style.wrapper}>
<div style={style.tabbar} role="tablist">{this.renderTabs()}</div>
<div style={style.content}>{this.renderPanels()}</div>
<div style={style.tabbar} role="tablist">
{this.renderTabs()}
</div>
<div style={style.content}>
{this.renderPanels()}
</div>
</div>
);
}

View File

@ -204,7 +204,9 @@ class Layout extends React.Component {
showLeftPanel,
() =>
<div style={leftPanelStyle(leftPanelOnTop)}>
<div style={{ flexGrow: 1, height: '100%' }}>{leftPanel()}</div>
<div style={{ flexGrow: 1, height: '100%', width: '100%' }}>
{leftPanel()}
</div>
<USplit shift={5} split={storiesSplit} />
</div>,
() => <span />

View File

@ -50,9 +50,13 @@ const linkStyle = {
const Header = ({ openShortcutsHelp, name, url }) =>
<div style={wrapperStyle}>
<button style={shortcutIconStyle} onClick={openShortcutsHelp}></button>
<button style={shortcutIconStyle} onClick={openShortcutsHelp}>
</button>
<a style={linkStyle} href={url} target="_blank" rel="noopener noreferrer">
<h3 style={headingStyle}>{name}</h3>
<h3 style={headingStyle}>
{name}
</h3>
</a>
</div>;

View File

@ -8,7 +8,7 @@ import TextFilter from './text_filter';
const scrollStyle = {
height: 'calc(100vh - 105px)',
marginTop: 10,
overflowY: 'auto',
overflow: 'auto',
};
const mainStyle = {

View File

@ -3,10 +3,11 @@ import PropTypes from 'prop-types';
import React from 'react';
import deepEqual from 'deep-equal';
import treeNodeTypes from './tree_node_type';
import treeDecorators from './tree_decorators';
import createTreeDecorators from './tree_decorators';
import treeStyle from './tree_style';
const namespaceSeparator = '@';
const keyCodeEnter = 13;
function createNodeKey({ namespaces, type }) {
return [...namespaces, [type]].join(namespaceSeparator);
@ -39,12 +40,14 @@ class Stories extends React.Component {
constructor(...args) {
super(...args);
this.onToggle = this.onToggle.bind(this);
this.onKeyDown = this.onKeyDown.bind(this);
const { selectedHierarchy } = this.props;
this.state = {
nodes: getSelectedNodes(selectedHierarchy),
};
this.treeDecorators = createTreeDecorators(this);
}
componentWillReceiveProps(nextProps) {
@ -82,6 +85,12 @@ class Stories extends React.Component {
}));
}
onKeyDown(event, node) {
if (event.keyCode === keyCodeEnter) {
this.onToggle(node, !node.toggled);
}
}
fireOnKind(kind) {
const { onSelectStory } = this.props;
if (onSelectStory) onSelectStory(kind, null);
@ -144,7 +153,7 @@ class Stories extends React.Component {
style={treeStyle}
data={data}
onToggle={this.onToggle}
decorators={treeDecorators}
decorators={this.treeDecorators}
/>
);
}

View File

@ -313,5 +313,46 @@ describe('manager.ui.components.left_panel.stories', () => {
expect(onSelectStory).toHaveBeenCalledWith('another.space.20', 'b2');
});
test('should call the onSelectStory prop when a story is selected with enter key', () => {
const data = createHierarchy(
[
{ kind: 'some.name.item1', stories: ['a1', 'a2'] },
{ kind: 'another.space.20', stories: ['b1', 'b2'] },
],
'\\.'
);
const onSelectStory = jest.fn();
const wrap = mount(
<Stories
storiesHierarchy={data}
selectedKind="some.name.item1"
selectedStory="a2"
selectedHierarchy={['some', 'name', 'item1']}
onSelectStory={onSelectStory}
/>
);
wrap
.find('a')
.filterWhere(el => el.text() === 'another')
.last()
.simulate('keyDown', { keyCode: 13 });
wrap
.find('a')
.filterWhere(el => el.text() === 'space')
.last()
.simulate('keyDown', { keyCode: 13 });
wrap
.find('a')
.filterWhere(el => el.text() === '20')
.last()
.simulate('keyDown', { keyCode: 13 });
expect(onSelectStory).toHaveBeenCalledWith('another.space.20', null);
});
});
});

View File

@ -34,41 +34,61 @@ ContainerDecorator.propTypes = {
}).isRequired,
};
function HeaderDecorator(props) {
const { style, node } = props;
function createHeaderDecoratorScope(parent) {
class HeaderDecorator extends React.Component {
constructor(...args) {
super(...args);
this.onKeyDown = this.onKeyDown.bind(this);
}
const newStyleTitle = {
...style.title,
};
onKeyDown(event) {
const { onKeyDown } = parent;
const { node } = this.props;
const Icon = iconsMap[node.type];
onKeyDown(event, node);
}
if (!node.children || !node.children.length) {
newStyleTitle.fontSize = '13px';
render() {
const { style, node } = this.props;
const newStyleTitle = {
...style.title,
};
const Icon = iconsMap[node.type];
if (!node.children || !node.children.length) {
newStyleTitle.fontSize = '13px';
}
return (
<div style={style.base} role="menuitem" tabIndex="0" onKeyDown={this.onKeyDown}>
{Icon && <Icon color={iconsColor} />}
<a style={newStyleTitle}>
{node.name}
</a>
</div>
);
}
}
return (
<div style={style.base}>
{Icon && <Icon color={iconsColor} />}
<a style={newStyleTitle}>
{node.name}
</a>
</div>
);
HeaderDecorator.propTypes = {
style: PropTypes.shape({
title: PropTypes.object.isRequired,
base: PropTypes.object.isRequired,
}).isRequired,
node: PropTypes.shape({
name: PropTypes.string.isRequired,
}).isRequired,
};
return HeaderDecorator;
}
HeaderDecorator.propTypes = {
style: PropTypes.shape({
title: PropTypes.object,
base: PropTypes.object,
}).isRequired,
node: PropTypes.shape({
name: PropTypes.string,
}).isRequired,
};
export default {
...decorators,
Header: HeaderDecorator,
Container: ContainerDecorator,
};
export default function(parent) {
return {
...decorators,
Header: createHeaderDecoratorScope(parent),
Container: ContainerDecorator,
};
}

View File

@ -1,5 +1,7 @@
import { baseFonts } from '../../theme';
const toggleWidth = '24px';
export default {
tree: {
base: {
@ -8,6 +10,7 @@ export default {
padding: 0,
fontFamily: baseFonts.fontFamily,
fontSize: '15px',
minWidth: '200px',
},
node: {
base: {
@ -30,7 +33,7 @@ export default {
verticalAlign: 'top',
marginLeft: '-5px',
height: '24px',
width: '24px',
width: toggleWidth,
},
wrapper: {
position: 'absolute',
@ -49,6 +52,7 @@ export default {
base: {
display: 'inline-block',
verticalAlign: 'top',
maxWidth: `calc(100% - ${toggleWidth})`,
},
connector: {
width: '2px',

View File

@ -62,19 +62,31 @@ export function getShortcuts(platform) {
export const Keys = ({ shortcutKeys }) => {
// if we have only one key combination for a shortcut
if (shortcutKeys.length === 1) {
return <span><b style={commandStyle}>{shortcutKeys[0]}</b></span>;
return (
<span>
<b style={commandStyle}>
{shortcutKeys[0]}
</b>
</span>
);
}
// if we have multiple key combinations for a shortcut
const keys = shortcutKeys.map((key, index, arr) =>
<span key={key}>
<b style={commandStyle}>{key}</b>
<b style={commandStyle}>
{key}
</b>
{/* add / & space if it is not a last key combination */}
{arr.length - 1 !== index ? <span>/ &nbsp;</span> : ''}
</span>
);
return <span>{keys}</span>;
return (
<span>
{keys}
</span>
);
};
Keys.propTypes = {

View File

@ -8,9 +8,14 @@ export const mapper = (state, props, { actions }) => {
const actionMap = actions();
const { stories, selectedKind, selectedStory, uiOptions, storyFilter } = state;
const { name, url, sortStoriesByKind, hierarchySeparator } = uiOptions;
const filteredStores = filters.storyFilter(stories, storyFilter, selectedKind, sortStoriesByKind);
const filteredStories = filters.storyFilter(
stories,
storyFilter,
selectedKind,
sortStoriesByKind
);
const storiesHierarchy = createHierarchy(filteredStores, hierarchySeparator);
const storiesHierarchy = createHierarchy(filteredStories, hierarchySeparator);
const selectedHierarchy = resolveStoryHierarchy(selectedKind, hierarchySeparator);
const data = {

View File

@ -11,11 +11,27 @@ export function storyFilter(stories, filter, selectedKind, sortStoriesByKind) {
if (!stories) return null;
const sorted = sort(stories, sortStoriesByKind);
if (!filter) return sorted;
return sorted.filter(kindInfo => {
if (kindInfo.kind === selectedKind) return true;
return sorted.reduce((acc, kindInfo) => {
// Don't filter out currently selected filter
if (kindInfo.kind === selectedKind) return acc.concat(kindInfo);
const needle = filter.toLocaleLowerCase();
const hstack = kindInfo.kind.toLocaleLowerCase();
return fuzzysearch(needle, hstack);
});
// If a match is found in the story hierachy structure return kindInfo
if (fuzzysearch(needle, hstack)) return acc.concat(kindInfo);
// Now search at individual story level and filter results
const matchedStories = kindInfo.stories.filter(story => {
const storyHstack = story.toLocaleLowerCase();
return fuzzysearch(needle, storyHstack);
});
if (matchedStories.length)
return acc.concat({
kind: kindInfo.kind,
stories: matchedStories,
});
return acc;
}, []);
}

View File

@ -54,5 +54,45 @@ describe('manager.ui.libs.filters', () => {
expect(res).toEqual([stories[1], stories[2], stories[0]]);
});
test('should filter on story level', () => {
const stories = [
{ kind: 'aa', stories: ['bb'] },
{ kind: 'cc', stories: ['dd'] },
{ kind: 'ee', stories: ['ff'] },
];
const selectedKind = 'aa';
const res = storyFilter(stories, 'ff', selectedKind);
expect(res).toEqual([stories[0], stories[2]]);
});
test('should filter out unmatched stories at lowest level', () => {
const stories = [
{ kind: 'aa', stories: ['bb'] },
{ kind: 'cc', stories: ['dd'] },
{ kind: 'ee', stories: ['ff', 'gg'] },
];
const selectedKind = 'aa';
const res = storyFilter(stories, 'ff', selectedKind);
expect(res).toEqual([stories[0], { kind: 'ee', stories: ['ff'] }]);
});
test('should be case insensitive at tree level', () => {
const stories = [{ kind: 'aA', stories: ['bb'] }, { kind: 'cc', stories: ['dd'] }];
const selectedKind = 'aA';
const res = storyFilter(stories, 'aa', selectedKind);
expect(res).toEqual([stories[0]]);
});
test('should be case insensitive at story level', () => {
const stories = [{ kind: 'aa', stories: ['bb'] }, { kind: 'cc', stories: ['dd', 'eE'] }];
const selectedKind = 'aa';
const res = storyFilter(stories, 'ee', selectedKind);
expect(res).toEqual([stories[0], { kind: 'cc', stories: ['eE'] }]);
});
});
});

View File

@ -39,9 +39,9 @@
"babel-preset-env": "^1.5.1",
"babel-preset-react": "^6.24.1",
"babel-preset-stage-0": "^6.24.1",
"chalk": "^1.1.3",
"chalk": "^2.0.1",
"codecov": "^2.2.0",
"danger": "^0.21.0",
"danger": "^1.0.0",
"enzyme": "^2.8.2",
"eslint": "^3.19.0",
"eslint-config-airbnb": "^15.0.1",
@ -54,16 +54,14 @@
"eslint-plugin-react": "^7.0.1",
"gh-pages": "^1.0.0",
"github-release-from-changelog": "^1.2.1",
"husky": "^0.13.4",
"husky": "^0.14.3",
"jest": "^20.0.4",
"jest-enzyme": "^3.2.0",
"lerna": "2.0.0-rc.5",
"lerna": "2.0.0",
"lint-staged": "^4.0.0",
"markdown-it-anchor": "^4.0.0",
"markdownlint-cli": "^0.3.1",
"nodemon": "^1.11.0",
"npmc": "^5.0.3-canary.12",
"prettier": "^1.3.1",
"npmc": "^5.1.0-canary.2",
"prettier": "^1.5.2",
"react": "^15.5.4",
"react-dom": "^15.5.4",
"react-test-renderer": "^15.5.4",