Merge branch 'master' into a11y#3641

This commit is contained in:
Jason Miazga 2018-07-20 08:10:15 -04:00
commit 5d0ed30854
255 changed files with 5429 additions and 3734 deletions

View File

@ -1,3 +1,50 @@
# 4.0.0-alpha.14
2018-July-11
#### Bug Fixes
- Upgrade universal-dotenv to fix core-js dependency [#3874](https://github.com/storybooks/storybook/pull/3874)
# 4.0.0-alpha.13
2018-July-09
#### Features
- Refactor addon-jest to use a parameter-based pattern [#3678](https://github.com/storybooks/storybook/pull/3678)
#### Bug Fixes
- Upgrade universal-dotenv to fix babel-runtime [#3863](https://github.com/storybooks/storybook/pull/3863)
#### Maintenance
- Added a test for parameter combination [#3844](https://github.com/storybooks/storybook/pull/3844)
# 4.0.0-alpha.12
2018-July-03
#### Bug Fixes
- Fix non-polyfilled themed UI components [#3829](https://github.com/storybooks/storybook/pull/3829)
# 4.0.0-alpha.11
2018-July-02
#### Features
- Storybook UI theming [#3628](https://github.com/storybooks/storybook/pull/3628)
- Replaced 'dotenv-webpack' with 'universal-dotenv' to support multiple dot env files (like CRA) [#3744](https://github.com/storybooks/storybook/pull/3744)
- Support other type of webpack configs [#3785](https://github.com/storybooks/storybook/pull/3785)
#### Bug Fixes
- Marko: fix welcome component [#3796](https://github.com/storybooks/storybook/pull/3796)
- Addon-a11y: Run analysis on demand [#3690](https://github.com/storybooks/storybook/pull/3690)
# 4.0.0-alpha.10
2018-June-21

View File

@ -331,7 +331,7 @@ yarn bootstrap --reset --core
```sh
# publish and tag the release
npm run publish -- --concurrency 1 --npm-tag=alpha --force-publish=*
npm run publish:alpha
# update the release page
open https://github.com/storybooks/storybook/releases
@ -355,7 +355,7 @@ git commit -m "Changelog for vX.Y"
yarn bootstrap --reset --core
# publish and tag the release
npm run publish -- --concurrency 1 --force-publish=*
npm run publish
# update the release page
open https://github.com/storybooks/storybook/releases

View File

@ -67,6 +67,24 @@ storiesOf('button', module)
));
```
If you want to add a11y globally to your stories, you can use the global Storybook decorator in your *.storybook/config.js* file:
```js
import { configure, addDecorator } from '@storybook/react';
import { checkA11y } from '@storybook/addon-a11y';
// pick all stories.js files within the src/ folder
const req = require.context('../src', true, /stories\.js$/);
addDecorator(checkA11y);
function loadStories() {
req.keys().forEach(filename => req(filename));
}
configure(loadStories, module);
```
## Roadmap
* Make UI accessibile

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-a11y",
"version": "4.0.0-alpha.10",
"version": "4.0.0-alpha.14",
"description": "a11y addon for storybook",
"keywords": [
"a11y",
@ -25,13 +25,12 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "4.0.0-alpha.10",
"@storybook/client-logger": "4.0.0-alpha.10",
"@storybook/components": "4.0.0-alpha.10",
"@storybook/core-events": "4.0.0-alpha.10",
"@storybook/addons": "4.0.0-alpha.14",
"@storybook/client-logger": "4.0.0-alpha.14",
"@storybook/components": "4.0.0-alpha.14",
"@storybook/core-events": "4.0.0-alpha.14",
"axe-core": "^3.0.3",
"babel-runtime": "^6.26.0",
"emotion": "^9.1.3",
"global": "^4.3.2",
"prop-types": "^15.6.1",
"react-emotion": "^9.1.3",

View File

@ -1,65 +1,93 @@
import React, { Component } from 'react';
import addons from '@storybook/addons';
import PropTypes from 'prop-types';
import styled from 'react-emotion';
import { CHECK_EVENT_ID } from '../shared';
import { STORY_RENDERED } from '@storybook/core-events';
import { ActionBar, ActionButton } from '@storybook/components';
import { CHECK_EVENT_ID, RERUN_EVENT_ID, REQUEST_CHECK_EVENT_ID } from '../shared';
import Tabs from './Tabs';
import Report from './Report';
const Passes = styled('span')({
color: '#0D6731',
});
const Passes = styled('span')(({ theme }) => ({
color: theme.successColor,
}));
const Violations = styled('span')({
color: '#AC2300',
});
const Violations = styled('span')(({ theme }) => ({
color: theme.failColor,
}));
class Panel extends Component {
constructor(props, ...args) {
super(props, ...args);
this.state = {
passes: [],
violations: [],
};
this.channel = addons.getChannel();
static propTypes = {
active: PropTypes.bool.isRequired,
channel: PropTypes.shape({
on: PropTypes.func,
emit: PropTypes.func,
removeListener: PropTypes.func,
}).isRequired,
};
this.onUpdate = this.onUpdate.bind(this);
}
state = {
passes: [],
violations: [],
};
componentDidMount() {
this.channel.on(CHECK_EVENT_ID, this.onUpdate);
this.props.channel.on(CHECK_EVENT_ID, this.onUpdate);
this.props.channel.on(STORY_RENDERED, this.requestCheck);
this.props.channel.on(RERUN_EVENT_ID, this.requestCheck);
}
componentDidUpdate(prevProps) {
if (!prevProps.active && this.props.active) {
this.requestCheck();
}
}
componentWillUnmount() {
this.channel.removeListener(CHECK_EVENT_ID, this.onUpdate);
this.props.channel.removeListener(CHECK_EVENT_ID, this.onUpdate);
this.props.channel.removeListener(STORY_RENDERED, this.requestCheck);
this.props.channel.removeListener(RERUN_EVENT_ID, this.requestCheck);
}
onUpdate({ passes, violations }) {
onUpdate = ({ passes, violations }) => {
this.setState({
passes,
violations,
});
}
};
requestCheck = () => {
if (this.props.active) {
this.props.channel.emit(REQUEST_CHECK_EVENT_ID);
}
};
render() {
const { passes, violations } = this.state;
const { active } = this.props;
return (
<Tabs
tabs={[
{
label: <Violations>{violations.length} Violations</Violations>,
panel: <Report passes={false} items={violations} empty="No a11y violations found." />,
},
{
label: <Passes>{passes.length} Passes</Passes>,
panel: <Report passes items={passes} empty="No a11y check passed" />,
},
]}
/>
);
return active ? (
<div>
<Tabs
tabs={[
{
label: <Violations>{violations.length} Violations</Violations>,
panel: <Report passes={false} items={violations} empty="No a11y violations found." />,
},
{
label: <Passes>{passes.length} Passes</Passes>,
panel: <Report passes items={passes} empty="No a11y check passed" />,
},
]}
/>
<ActionBar>
<ActionButton onClick={this.requestCheck}>RERUN TEST</ActionButton>
</ActionBar>
</div>
) : null;
}
}

View File

@ -3,18 +3,18 @@ import PropTypes from 'prop-types';
import styled from 'react-emotion';
const Wrapper = styled('div')({
backgroundColor: 'rgb(234, 234, 234)',
const Wrapper = styled('div')(({ theme }) => ({
backgroundColor: theme.barFill,
padding: '12px',
marginBottom: '10px',
});
}));
const Help = styled('p')({
margin: '0 0 12px',
});
const Link = styled('a')({
marginTop: '12px',
textDecoration: 'underline',
color: 'rgb(130, 130, 130)',
color: 'inherit',
display: 'block',
});

View File

@ -8,19 +8,28 @@ import Info from './Info';
import Tags from './Tags';
import Elements from './Elements';
const Wrapper = styled('div')({
const Wrapper = styled('div')(({ theme }) => ({
padding: '0 14px',
cursor: 'pointer',
borderBottom: '1px solid rgb(234, 234, 234)',
});
borderBottom: theme.mainBorder,
}));
const HeaderBar = styled('button')({
const HeaderBar = styled('button')(({ theme }) => ({
padding: '12px 0px',
display: 'block',
width: '100%',
border: 0,
background: 'none',
});
color: 'inherit',
borderTop: '3px solid transparent',
borderBottom: '3px solid transparent',
'&:focus': {
outline: '0 none',
borderBottom: `3px solid ${theme.highlightColor}`,
},
}));
class Item extends Component {
static propTypes = {

View File

@ -1,17 +0,0 @@
import styled from 'react-emotion';
const RerunButton = styled('button')({
position: 'absolute',
bottom: 0,
right: 0,
border: 'none',
borderTop: 'solid 1px rgba(0, 0, 0, 0.2)',
borderLeft: 'solid 1px rgba(0, 0, 0, 0.2)',
background: 'rgba(255, 255, 255, 0.5)',
padding: '5px 10px',
borderRadius: '4px 0 0 0',
color: 'rgba(0, 0, 0, 0.5)',
textTransform: 'uppercase',
});
export default RerunButton;

View File

@ -9,14 +9,12 @@ const Wrapper = styled('div')({
margin: '12px 0',
});
const Item = styled('div')({
const Item = styled('div')(({ theme }) => ({
margin: '0 6px',
padding: '5px',
border: '1px solid rgb(234, 234, 234)',
borderRadius: '2px',
color: 'rgb(130, 130, 130)',
fontSize: '12px',
});
border: theme.mainBorder,
borderRadius: theme.mainBorderRadius,
}));
function Tags({ tags }) {
return <Wrapper>{tags.map(tag => <Item key={tag}>{tag}</Item>)}</Wrapper>;

View File

@ -1,18 +1,9 @@
import React from 'react';
import PropTypes from 'prop-types';
import addons from '@storybook/addons';
import { Placeholder } from '@storybook/components';
import { RERUN_EVENT_ID } from '../../shared';
import RerunButton from './RerunButton';
import Item from './Item';
function onRerunClick() {
const channel = addons.getChannel();
channel.emit(RERUN_EVENT_ID);
}
const Report = ({ items, empty, passes }) => (
<div>
{items.length ? (
@ -20,7 +11,6 @@ const Report = ({ items, empty, passes }) => (
) : (
<Placeholder>{empty}</Placeholder>
)}
<RerunButton onClick={onRerunClick}>Re-run tests</RerunButton>
</div>
);

View File

@ -6,13 +6,14 @@ import styled from 'react-emotion';
const Container = styled('div')({
width: '100%',
position: 'relative',
minHeight: '100%',
});
const List = styled('div')({
borderBottom: '1px solid rgb(234, 234, 234)',
const List = styled('div')(({ theme }) => ({
borderBottom: theme.mainBorder,
flexWrap: 'wrap',
display: 'flex',
});
}));
const Item = styled('button')(
({ active }) =>
@ -22,9 +23,7 @@ const Item = styled('button')(
fontWeight: 600,
}
: {},
{
color: 'rgb(68, 68, 68)',
fontSize: '11px',
({ theme }) => ({
textDecoration: 'none',
textTransform: 'uppercase',
padding: '10px 15px',
@ -33,9 +32,16 @@ const Item = styled('button')(
fontWeight: 500,
opacity: 0.7,
border: 'none',
borderTop: '3px solid transparent',
borderBottom: '3px solid transparent',
background: 'none',
flex: 1,
}
'&:focus': {
outline: '0 none',
borderBottom: `3px solid ${theme.highlightColor}`,
},
})
);
class Tabs extends Component {

View File

@ -4,7 +4,7 @@ import addons from '@storybook/addons';
import Events from '@storybook/core-events';
import { logger } from '@storybook/client-logger';
import { CHECK_EVENT_ID, RERUN_EVENT_ID } from './shared';
import { CHECK_EVENT_ID, REQUEST_CHECK_EVENT_ID } from './shared';
let axeOptions = {};
@ -23,11 +23,10 @@ const runA11yCheck = () => {
const a11ySubscription = () => {
const channel = addons.getChannel();
channel.on(Events.STORY_RENDERED, runA11yCheck);
channel.on(RERUN_EVENT_ID, runA11yCheck);
channel.on(REQUEST_CHECK_EVENT_ID, runA11yCheck);
return () => {
channel.removeListener(Events.STORY_RENDERED, runA11yCheck);
channel.removeListener(RERUN_EVENT_ID, runA11yCheck);
channel.removeListener(REQUEST_CHECK_EVENT_ID, runA11yCheck);
};
};

View File

@ -5,12 +5,12 @@ import Panel from './components/Panel';
import { ADDON_ID, PANEL_ID } from './shared';
function init() {
addons.register(ADDON_ID, () => {
addons.register(ADDON_ID, api => {
const channel = addons.getChannel();
addons.addPanel(PANEL_ID, {
title: 'Accessibility',
render() {
return <Panel />;
},
// eslint-disable-next-line react/prop-types
render: ({ active }) => <Panel channel={channel} api={api} active={active} />,
});
});
}

View File

@ -3,5 +3,6 @@ const ADDON_ID = '@storybook/addon-a11y';
const PANEL_ID = `${ADDON_ID}/panel`;
const CHECK_EVENT_ID = `${ADDON_ID}/check`;
const RERUN_EVENT_ID = `${ADDON_ID}/rerun`;
const REQUEST_CHECK_EVENT_ID = `${ADDON_ID}/request-check`;
export { ADDON_ID, PANEL_ID, CHECK_EVENT_ID, RERUN_EVENT_ID };
export { ADDON_ID, PANEL_ID, CHECK_EVENT_ID, RERUN_EVENT_ID, REQUEST_CHECK_EVENT_ID };

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-actions",
"version": "4.0.0-alpha.10",
"version": "4.0.0-alpha.14",
"description": "Action Logger addon for storybook",
"keywords": [
"storybook"
@ -20,12 +20,12 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "4.0.0-alpha.10",
"@storybook/components": "4.0.0-alpha.10",
"@storybook/core-events": "4.0.0-alpha.10",
"@storybook/addons": "4.0.0-alpha.14",
"@storybook/components": "4.0.0-alpha.14",
"@storybook/core-events": "4.0.0-alpha.14",
"babel-runtime": "^6.26.0",
"deep-equal": "^1.0.1",
"emotion": "^9.1.3",
"emotion-theming": "^9.1.2",
"global": "^4.3.2",
"lodash.isequal": "^4.5.0",
"make-error": "^1.3.4",

View File

@ -1,48 +1,48 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import React from 'react';
import Inspector from 'react-inspector';
import { Actions, Action, Button, Wrapper, InspectorContainer, Countwrap, Counter } from './style';
import { withTheme } from 'emotion-theming';
class ActionLogger extends Component {
getActionData() {
return this.props.actions.map(action => this.renderAction(action));
}
import { ActionBar, ActionButton } from '@storybook/components';
renderAction(action) {
const counter = <Counter>{action.count}</Counter>;
return (
<Action key={action.id}>
<Countwrap>{action.count > 1 && counter}</Countwrap>
<InspectorContainer>
<Inspector
sortObjectKeys
showNonenumerable={false}
name={action.data.name}
data={action.data.args || action.data}
/>
</InspectorContainer>
</Action>
);
}
import { Actions, Action, Wrapper, InspectorContainer, Countwrap, Counter } from './style';
render() {
return (
<Wrapper>
<Actions>{this.getActionData()}</Actions>
<Button onClick={this.props.onClear}>Clear</Button>
</Wrapper>
);
}
}
const ActionLogger = withTheme(({ actions, onClear, theme }) => (
<Wrapper>
<Actions>
{actions.map(action => (
<Action key={action.id}>
<Countwrap>{action.count > 1 && <Counter>{action.count}</Counter>}</Countwrap>
<InspectorContainer>
<Inspector
theme={theme.addonActionsTheme || 'chromeLight'}
sortObjectKeys
showNonenumerable={false}
name={action.data.name}
data={action.data.args || action.data}
/>
</InspectorContainer>
</Action>
))}
</Actions>
<ActionBar>
<ActionButton onClick={onClear}>CLEAR</ActionButton>
</ActionBar>
</Wrapper>
));
ActionLogger.propTypes = {
onClear: PropTypes.func,
// eslint-disable-next-line react/forbid-prop-types
actions: PropTypes.array,
};
ActionLogger.defaultProps = {
onClear: () => {},
actions: [],
onClear: PropTypes.func.isRequired,
actions: PropTypes.arrayOf(
PropTypes.shape({
count: PropTypes.node,
data: PropTypes.shape({
name: PropTypes.node.isRequired,
args: PropTypes.any,
}),
})
).isRequired,
};
export default ActionLogger;

View File

@ -1,5 +1,4 @@
import styled from 'react-emotion';
import { Button as BaseButton } from '@storybook/components';
export const Actions = styled('pre')({
flex: 1,
@ -12,24 +11,12 @@ export const Actions = styled('pre')({
export const Action = styled('div')({
display: 'flex',
padding: '3px 3px 3px 0',
borderLeft: '5px solid white',
borderBottom: '1px solid #fafafa',
borderLeft: '5px solid transparent',
borderBottom: '1px solid transparent',
transition: 'all 0.1s',
alignItems: 'start',
});
export const Button = styled(BaseButton)({
position: 'absolute',
bottom: 0,
right: 0,
borderRadius: '4px 0 0 0',
textTransform: 'uppercase',
letterSpacing: 1,
paddingTop: 5,
paddingBottom: 5,
border: '0 none',
});
export const Counter = styled('div')({
margin: '0 5px 0 5px',
backgroundColor: '#777777',
@ -51,4 +38,5 @@ export const Wrapper = styled('div')({
flex: 1,
display: 'flex',
position: 'relative',
minHeight: '100%',
});

View File

@ -56,21 +56,25 @@ export default class ActionLogger extends React.Component {
}
render() {
const { active } = this.props;
const props = {
actions: this.state.actions,
onClear: () => this.clearActions(),
};
return <ActionLoggerComponent {...props} />;
return active ? <ActionLoggerComponent {...props} /> : null;
}
}
ActionLogger.propTypes = {
// eslint-disable-next-line react/forbid-prop-types
channel: PropTypes.object,
active: PropTypes.bool.isRequired,
channel: PropTypes.shape({
emit: PropTypes.func,
on: PropTypes.func,
removeListener: PropTypes.func,
}).isRequired,
api: PropTypes.shape({
onStory: PropTypes.func.isRequired,
onStory: PropTypes.func,
getQueryParam: PropTypes.func,
setQueryParams: PropTypes.func,
}).isRequired,
};
ActionLogger.defaultProps = {
channel: {},
};

View File

@ -8,7 +8,8 @@ export function register() {
const channel = addons.getChannel();
addons.addPanel(PANEL_ID, {
title: 'Action Logger',
render: () => <ActionLogger channel={channel} api={api} />,
// eslint-disable-next-line react/prop-types
render: ({ active }) => <ActionLogger channel={channel} api={api} active={active} />,
});
});
}

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-backgrounds",
"version": "4.0.0-alpha.10",
"version": "4.0.0-alpha.14",
"description": "A storybook addon to show different backgrounds for your preview",
"keywords": [
"addon",
@ -24,10 +24,9 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "4.0.0-alpha.10",
"@storybook/core-events": "4.0.0-alpha.10",
"@storybook/addons": "4.0.0-alpha.14",
"@storybook/core-events": "4.0.0-alpha.14",
"babel-runtime": "^6.26.0",
"emotion": "^9.1.3",
"global": "^4.3.2",
"prop-types": "^15.6.1",
"react-emotion": "^9.1.3",

View File

@ -16,14 +16,13 @@ const Title = styled('h5')({
fontSize: 16,
});
const Pre = styled('pre')({
const Pre = styled('pre')(({ theme }) => ({
padding: '30px',
display: 'block',
background: 'rgba(19,19,19,0.9)',
color: 'rgba(255,255,255,0.95)',
background: theme.fillColor,
marginTop: '15px',
lineHeight: '1.75em',
});
}));
const List = styled('div')({
display: 'inline-block',
@ -47,13 +46,13 @@ const defaultBackground = {
};
const instructionsHtml = `
import { storiesOf } from "@storybook/react";
import { withBackgrounds } from "@storybook/addon-backgrounds";
import { storiesOf } from '@storybook/react';
import { withBackgrounds } from '@storybook/addon-backgrounds';
storiesOf("First Component", module)
storiesOf('First Component', module)
.addDecorator(withBackgrounds([
{ name: "twitter", value: "#00aced" },
{ name: "facebook", value: "#3b5998" },
{ name: 'twitter', value: '#00aced' },
{ name: 'facebook', value: '#3b5998" },
]))
.add("First Button", () => <button>Click me</button>);
`.trim();
@ -130,8 +129,11 @@ export default class BackgroundPanel extends Component {
}
render() {
const { active } = this.props;
const backgrounds = [...this.state.backgrounds];
if (!active) {
return null;
}
if (!backgrounds.length) return <Instructions />;
const hasDefault = backgrounds.filter(x => x.default).length;
@ -149,6 +151,7 @@ export default class BackgroundPanel extends Component {
}
}
BackgroundPanel.propTypes = {
active: PropTypes.bool.isRequired,
api: PropTypes.shape({
getQueryParam: PropTypes.func,
setQueryParams: PropTypes.func,

View File

@ -3,24 +3,26 @@ import PropTypes from 'prop-types';
import styled from 'react-emotion';
const Button = styled('button')({
const Button = styled('button')(({ theme }) => ({
listStyle: 'none',
backgroundColor: '#fff',
backgroundColor: theme.barFill,
textAlign: 'center',
border: '1px solid rgba(0,0,0,0.1)',
borderRadius: 4,
border: theme.mainBorder,
borderRadius: theme.mainBorderRadius,
color: 'inherit',
cursor: 'pointer',
display: 'inline-block',
width: 175,
verticalAlign: 'top',
wordWrap: 'break-word',
padding: 0,
});
const Block = styled('div')(({ bg }) => ({
overflow: 'hidden',
}));
const Block = styled('div')(({ bg, theme }) => ({
height: 80,
borderRadius: '4px 4px 0 0',
transition: 'opacity 0.25s ease-in-out',
borderBottom: '1px solid rgba(0,0,0,0.1)',
borderBottom: theme.mainBorder,
background: bg,
backgroundSize: 'cover',
backgroundPosition: 'center',

View File

@ -30,26 +30,26 @@ jest.mock('global', () => ({
describe('Background Panel', () => {
it('should exist', () => {
const backgroundPanel = shallow(<BackgroundPanel channel={channel} api={mockedApi} />);
const backgroundPanel = shallow(<BackgroundPanel channel={channel} api={mockedApi} active />);
expect(backgroundPanel).toBeDefined();
});
it('should have a default background value of transparent', () => {
const backgroundPanel = shallow(<BackgroundPanel channel={channel} api={mockedApi} />);
const backgroundPanel = shallow(<BackgroundPanel channel={channel} api={mockedApi} active />);
expect(backgroundPanel.state().backgrounds).toHaveLength(0);
});
it('should show setup instructions if no colors provided', () => {
const backgroundPanel = shallow(<BackgroundPanel channel={channel} api={mockedApi} />);
const backgroundPanel = shallow(<BackgroundPanel channel={channel} api={mockedApi} active />);
expect(backgroundPanel.html().match(/Setup Instructions/gim).length).toBeGreaterThan(0);
});
it('should set the query string', () => {
const SpiedChannel = new EventEmitter();
mount(<BackgroundPanel channel={SpiedChannel} api={mockedApi} />);
mount(<BackgroundPanel channel={SpiedChannel} api={mockedApi} active />);
SpiedChannel.emit(Events.SET, backgrounds);
expect(mockedApi.getQueryParam).toBeCalledWith('background');
@ -57,7 +57,7 @@ describe('Background Panel', () => {
it('should not unset the query string', () => {
const SpiedChannel = new EventEmitter();
mount(<BackgroundPanel channel={SpiedChannel} api={mockedApi} />);
mount(<BackgroundPanel channel={SpiedChannel} api={mockedApi} active />);
SpiedChannel.emit(Events.UNSET, []);
expect(mockedApi.setQueryParams).not.toHaveBeenCalled();
@ -65,7 +65,9 @@ describe('Background Panel', () => {
it('should accept colors through channel and render the correct swatches with a default swatch', () => {
const SpiedChannel = new EventEmitter();
const backgroundPanel = mount(<BackgroundPanel channel={SpiedChannel} api={mockedApi} />);
const backgroundPanel = mount(
<BackgroundPanel channel={SpiedChannel} api={mockedApi} active />
);
SpiedChannel.emit(Events.SET, backgrounds);
expect(backgroundPanel.state('backgrounds')).toEqual(backgrounds);
@ -73,7 +75,9 @@ describe('Background Panel', () => {
it('should allow setting a default swatch', () => {
const SpiedChannel = new EventEmitter();
const backgroundPanel = mount(<BackgroundPanel channel={SpiedChannel} api={mockedApi} />);
const backgroundPanel = mount(
<BackgroundPanel channel={SpiedChannel} api={mockedApi} active />
);
const [head, ...tail] = backgrounds;
const localBgs = [{ ...head, default: true }, ...tail];
SpiedChannel.emit(Events.SET, localBgs);
@ -88,7 +92,9 @@ describe('Background Panel', () => {
it('should allow the default swatch become the background color', () => {
const SpiedChannel = new EventEmitter();
const backgroundPanel = mount(<BackgroundPanel channel={SpiedChannel} api={mockedApi} />);
const backgroundPanel = mount(
<BackgroundPanel channel={SpiedChannel} api={mockedApi} active />
);
const [head, second, ...tail] = backgrounds;
const localBgs = [head, { ...second, default: true }, ...tail];
SpiedChannel.on('background', bg => {
@ -106,7 +112,9 @@ describe('Background Panel', () => {
it('should unset all swatches on receiving the background-unset message', () => {
const SpiedChannel = new EventEmitter();
const backgroundPanel = mount(<BackgroundPanel channel={SpiedChannel} api={mockedApi} />);
const backgroundPanel = mount(
<BackgroundPanel channel={SpiedChannel} api={mockedApi} active />
);
SpiedChannel.emit(Events.SET, backgrounds);
expect(backgroundPanel.state('backgrounds')).toEqual(backgrounds);
@ -118,7 +126,9 @@ describe('Background Panel', () => {
it('should set iframe background', () => {
const SpiedChannel = new EventEmitter();
const backgroundPanel = mount(<BackgroundPanel channel={SpiedChannel} api={mockedApi} />);
const backgroundPanel = mount(
<BackgroundPanel channel={SpiedChannel} api={mockedApi} active />
);
backgroundPanel.setState({ backgrounds }); // force re-render
backgroundPanel

View File

@ -15,6 +15,7 @@ export const withBackgrounds = makeDecorator({
name: 'backgrounds',
parameterName: 'backgrounds',
skipIfNoParametersOrOptions: true,
allowDeprecatedUsage: true,
wrapper: (getStory, context, { options, parameters }) => {
const backgrounds = parameters || options;

View File

@ -10,6 +10,7 @@ addons.register(ADDON_ID, api => {
const channel = addons.getChannel();
addons.addPanel(PANEL_ID, {
title: 'Backgrounds',
render: () => <BackgroundPanel channel={channel} api={api} />,
// eslint-disable-next-line react/prop-types
render: ({ active }) => <BackgroundPanel channel={channel} api={api} active={active} />,
});
});

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-centered",
"version": "4.0.0-alpha.10",
"version": "4.0.0-alpha.14",
"description": "Storybook decorator to center components",
"license": "MIT",
"author": "Muhammed Thanish <mnmtanish@gmail.com>",

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-events",
"version": "4.0.0-alpha.10",
"version": "4.0.0-alpha.14",
"description": "Add events to your Storybook stories.",
"keywords": [
"addon",
@ -19,10 +19,9 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "4.0.0-alpha.10",
"@storybook/core-events": "4.0.0-alpha.10",
"@storybook/addons": "4.0.0-alpha.14",
"@storybook/core-events": "4.0.0-alpha.14",
"babel-runtime": "^6.26.0",
"emotion": "^9.1.3",
"format-json": "^1.0.3",
"prop-types": "^15.6.1",
"react-emotion": "^9.1.3",

View File

@ -10,10 +10,12 @@ const Wrapper = styled('div')({
width: '100%',
boxSizing: 'border-box',
padding: '10px',
minHeight: '100%',
});
export default class Events extends Component {
static propTypes = {
active: PropTypes.bool.isRequired,
channel: PropTypes.shape({
on: PropTypes.func,
emit: PropTypes.func,
@ -43,10 +45,11 @@ export default class Events extends Component {
render() {
const { events } = this.state;
return (
const { active } = this.props;
return active ? (
<Wrapper>
{events.map(event => <Event key={event.name} {...event} onEmit={this.onEmit} />)}
</Wrapper>
);
) : null;
}
}

View File

@ -6,9 +6,11 @@ import { ADDON_ID, PANEL_ID } from './constants';
export function register() {
addons.register(ADDON_ID, () => {
const channel = addons.getChannel();
addons.addPanel(PANEL_ID, {
title: 'Events',
render: () => <Panel channel={addons.getChannel()} />,
// eslint-disable-next-line react/prop-types
render: ({ active }) => <Panel channel={channel} active={active} />,
});
});
}

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-graphql",
"version": "4.0.0-alpha.10",
"version": "4.0.0-alpha.14",
"description": "Storybook addon to display the GraphiQL IDE",
"keywords": [
"storybook"

View File

@ -7,7 +7,7 @@
[![Storybook Slack](https://now-examples-slackin-rrirkqohko.now.sh/badge.svg)](https://now-examples-slackin-rrirkqohko.now.sh/)
[![Backers on Open Collective](https://opencollective.com/storybook/backers/badge.svg)](#backers) [![Sponsors on Open Collective](https://opencollective.com/storybook/sponsors/badge.svg)](#sponsors)
* * *
---
Storybook Info Addon will show additional information for your stories in [Storybook](https://storybook.js.org).
Useful when you want to display usage or other types of documentation alongside your story.
@ -25,7 +25,8 @@ npm i -D @storybook/addon-info
```
## Basic usage
Then, add `withInfo` as a decarator to your book of stories.
Then, add `withInfo` as a decarator to your book of stories.
It is possible to add `info` by default to all or a subsection of stories by using a global or story decorator.
It is important to declare this decorator as **the first decorator**, otherwise it won't work well.
@ -33,7 +34,9 @@ It is important to declare this decorator as **the first decorator**, otherwise
```js
addDecorator(withInfo); // Globally in your .storybook/config.js.
```
or
```js
storiesOf('Component', module)
.addDecorator(withInfo) // At your stories directly.
@ -53,12 +56,13 @@ storiesOf('Component', module)
.addParameters({
info: {
// Your settings
}
},
})
.add('with some emoji', () => <Component/>);
.add('with some emoji', () => <Component />);
```
...or for each story individually:
```js
import { storiesOf } from '@storybook/react';
@ -67,12 +71,12 @@ import Component from './Component';
storiesOf('Component', module)
.add(
'with some emoji',
() => <Component emoji/>,
{ info : { inline: false, header: false } } // Make your component render inline with the additional info
() => <Component emoji />,
{ info: { inline: true, header: false } } // Make your component render inline with the additional info
)
.add(
'with no emoji',
() => <Component/>,
() => <Component />,
{ info: '☹️ no emojis' } // Add additional info text directly
);
```
@ -86,41 +90,36 @@ import Component from './Component';
storiesOf('Component', module)
.addParameters({
info: { // Make a default for all stories in this book,
inline: true, // where the components are inlined
info: {
// Make a default for all stories in this book,
inline: true, // where the components are inlined
styles: {
header: {
h1: {
color: 'red' // and the headers of the sections are red.
}
}
color: 'red', // and the headers of the sections are red.
},
},
},
}
},
})
.add(
'green version',
() => <Component green/>,
{
info: {
styles: stylesheet => ({ // Setting the style with a function
...stylesheet,
header: {
...stylesheet.header,
h1: {
...stylesheet.header.h1,
color: 'green' // Still inlined but with green headers!
}
}
})
}
})
.add(
'something else',
() => <Component different/>,
{
info: "This story has additional text added to the info!" // Still inlined and with red headers!
}
);
.add('green version', () => <Component green />, {
info: {
styles: stylesheet => ({
// Setting the style with a function
...stylesheet,
header: {
...stylesheet.header,
h1: {
...stylesheet.header.h1,
color: 'green', // Still inlined but with green headers!
},
},
}),
},
})
.add('something else', () => <Component different />, {
info: 'This story has additional text added to the info!', // Still inlined and with red headers!
});
```
It is also possible to disable the `info` addon entirely.
@ -135,27 +134,22 @@ Depending on the scope at which you want to disable the addon, pass the followin
```
## Markdown
The `info` addon also supports markdown.
To use markdown as additional textual documentation for your stories, either pass it directly as a String to the `info` parameters, or use the `text` option.
```js
storiesOf('Button', module)
.add(
'Button Component',
() => <Button />,
{
info: {
text: `
storiesOf('Button', module).add('Button Component', () => <Button />, {
info: {
text: `
description or documentation about my component, supports markdown
~~~js
<Button>Click Here</Button>
~~~
`
}
}
);
`,
},
});
```
## Setting Global Options
@ -166,9 +160,11 @@ To configure default options for all usage of the info addon, pass a option obje
// config.js
import { withInfo } from '@storybook/addon-info';
addDecorator(withInfo({
header: false, // Global configuration for the info addon across all of your stories.
}));
addDecorator(
withInfo({
header: false, // Global configuration for the info addon across all of your stories.
})
);
```
Configuration parameters can be set at 3 different locations: passed as default options along the `addDecorator` call, passed as an object of parameters to a book of stories to the `addParameters` call, and passed as direct parameters to each individual story.
@ -274,40 +270,40 @@ Example:
```js
// button.js
// @flow
import React from 'react'
import React from 'react';
const paddingStyles = {
small: '4px 8px',
medium: '8px 16px'
}
medium: '8px 16px',
};
const Button = ({
size,
...rest
}: {
/** The size of the button */
size: 'small' | 'medium'
size: 'small' | 'medium',
}) => {
const style = {
padding: paddingStyles[size] || ''
}
return <button style={style} {...rest} />
}
padding: paddingStyles[size] || '',
};
return <button style={style} {...rest} />;
};
Button.defaultProps = {
size: 'medium'
}
size: 'medium',
};
export default Button
export default Button;
```
```js
// stories.js
import React from "react";
import React from 'react';
import { storiesOf } from "@storybook/react";
import { withInfo } from "@storybook/addon-info";
import Button from "./button";
import { storiesOf } from '@storybook/react';
import Button from './button';
const Red = props => <span style={{ color: "red" }} {...props} />;
const Red = props => <span style={{ color: 'red' }} {...props} />;
const TableComponent = ({ propDefinitions }) => {
const props = propDefinitions.map(
@ -341,12 +337,11 @@ const TableComponent = ({ propDefinitions }) => {
);
};
storiesOf("Button", module).add(
"with text",
withInfo({
TableComponent
})(() => <Button>Hello Button</Button>)
);
storiesOf('Button', module).add('with text', () => <Button>Hello Button</Button>, {
info: {
TableComponent,
},
});
```
### React Docgen Integration
@ -359,10 +354,11 @@ import React from 'react';
import PropTypes from 'prop-types';
/** Button component description */
const DocgenButton = ({ disabled, label, style, onClick }) =>
const DocgenButton = ({ disabled, label, style, onClick }) => (
<button disabled={disabled} style={style} onClick={onClick}>
{label}
</button>;
</button>
);
DocgenButton.defaultProps = {
disabled: false,

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-info",
"version": "4.0.0-alpha.10",
"version": "4.0.0-alpha.14",
"description": "A Storybook addon to show additional information for your stories.",
"repository": {
"type": "git",
@ -13,12 +13,11 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "4.0.0-alpha.10",
"@storybook/client-logger": "4.0.0-alpha.10",
"@storybook/components": "4.0.0-alpha.10",
"@storybook/addons": "4.0.0-alpha.14",
"@storybook/client-logger": "4.0.0-alpha.14",
"@storybook/components": "4.0.0-alpha.14",
"babel-runtime": "^6.26.0",
"core-js": "2.5.7",
"emotion": "^9.1.3",
"global": "^4.3.2",
"marksy": "^6.0.3",
"nested-object-assign": "^1.0.1",

View File

@ -33,6 +33,9 @@ exports[`addon Info should render <Info /> and external markdown 1`] = `
-webkit-align-self: flex-start;
-ms-flex-item-align: start;
align-self: flex-start;
-webkit-flex-shrink: 0;
-ms-flex-negative: 0;
flex-shrink: 0;
}
.emotion-1:hover {
@ -1335,6 +1338,9 @@ exports[`addon Info should render <Info /> and markdown 1`] = `
-webkit-align-self: flex-start;
-ms-flex-item-align: start;
align-self: flex-start;
-webkit-flex-shrink: 0;
-ms-flex-negative: 0;
flex-shrink: 0;
}
.emotion-1:hover {

View File

@ -12,6 +12,7 @@ const Button = styled('button')(
fontSize: 13,
padding: '3px 10px',
alignSelf: 'flex-start',
flexShrink: 0,
':hover': {
backgroundColor: '#f4f7fa',

View File

@ -87,6 +87,7 @@ function addInfo(storyFn, context, infoOptions) {
export const withInfo = makeDecorator({
name: 'withInfo',
parameterName: 'info',
allowDeprecatedUsage: true,
wrapper: (getStory, context, { options, parameters }) => {
const storyOptions = parameters || options;
const infoOptions = typeof storyOptions === 'string' ? { text: storyOptions } : storyOptions;

View File

@ -31,17 +31,19 @@ When running **Jest**, be sure to save the results in a json file:
```
You may want to add it the result file to `.gitignore`, since it's a generated file:
```
jest-test-results.json
```
But much like lockfiles and snapshots checking-in generated files can have certain advantages as well. It's up to you.
We recommend to **do** check in the test results file so starting storybook from an clean git clone doesn't require running all tests first,
but this can mean you'll experience merge conflicts on this file in the future. (*re-generating this file is super easy though, just like lockfiles and snapshots*)
We recommend to **do** check in the test results file so starting storybook from an clean git clone doesn't require running all tests first,
but this can mean you'll experience merge conflicts on this file in the future. (_re-generating this file is super easy though, just like lockfiles and snapshots_)
## Generating the test results
You need to make sure the generated test-results file exists before you start storybook.
During development you will likely start jest in watch-mode
You need to make sure the generated test-restuls file exists before you start storybook.
During development you will likely start jest in watch-mode
and so the json file will be re-generated every time code or tests change.
```sh
@ -50,9 +52,10 @@ npm run test:generate-output -- --watch
This change will then be HMR (hot module reloaded) using webpack and displayed by this addon.
If you want to pre-run jest automaticly during development or a static build,
If you want to pre-run jest automaticly during development or a static build,
you may need to consider that if your tests fail, the script receives a non-0 exit code and will exit.
You could create a `prebuild:storybook` npm script, which will never fail by appending `|| true`:
```json
"scripts": {
"test:generate-output": "jest --json --outputFile=.jest-test-results.json || true",
@ -83,40 +86,57 @@ import results from '../.jest-test-results.json';
import { withTests } from '@storybook/addon-jest';
storiesOf('MyComponent', module)
.addDecorator(withTests({ results })('MyComponent', 'MyOtherComponent'))
.add('This story shows test results from MyComponent.test.js and MyOtherComponent.test.js', () => (
<div>Jest results in storybook</div>
));
.addDecorator(withTests({ results }))
.add(
'This story shows test results from MyComponent.test.js and MyOtherComponent.test.js',
() => <div>Jest results in storybook</div>,
{
jest: ['MyComponent.test.js', 'MyOtherComponent.test.js'],
}
);
```
Or in order to avoid importing `.jest-test-results.json` in each story, you can create a simple file `withTests.js`:
Or in order to avoid importing `.jest-test-results.json` in each story, simply add the decorator in your `.storybook/config.js` and results will display for stories that you have set the `jest` parameter on:
```js
import results from '../.jest-test-results.json';
import { addDecorator } from '@storybook/react'; // <- or your view layer
import { withTests } from '@storybook/addon-jest';
export default withTests({
results,
});
import results from '../.jest-test-results.json';
addDecorator(
withTests({
results,
})
);
```
Then in your story:
```js
// import your file
import withTests from '.withTests';
storiesOf('MyComponent', module)
.addDecorator(withTests('MyComponent', 'MyOtherComponent'))
.add('This story shows test results from MyComponent.test.js and MyOtherComponent.test.js', () => (
<div>Jest results in storybook</div>
));
// Use .addParameters if you want the same tests displayed for all stories of the component
.addParameters({ jest: ['MyComponent', 'MyOtherComponent'] })
.add(
'This story shows test results from MyComponent.test.js and MyOtherComponent.test.js',
() => <div>Jest results in storybook</div>
);
```
### Disabling
You can disable the addon for a single story by setting the `jest` parameter to `{disable: true}`:
```js
storiesOf('MyComponent', module).add('Story', () => <div>Jest results disabled here</div>, {
jest: { disable: true },
});
```
### withTests(options)
- **options.results**: OBJECT jest output results. *mandatory*
- **filesExt**: STRING test file extention. *optional*. This allow you to write "MyComponent" and not "MyComponent.test.js". It will be used as regex to find your file results. Default value is `((\\.specs?)|(\\.tests?))?(\\.js)?$`. That mean it will match: MyComponent.js, MyComponent.test.js, MyComponent.tests.js, MyComponent.spec.js, MyComponent.specs.js...
- **options.results**: OBJECT jest output results. _mandatory_
- **filesExt**: STRING test file extention. _optional_. This allow you to write "MyComponent" and not "MyComponent.test.js". It will be used as regex to find your file results. Default value is `((\\.specs?)|(\\.tests?))?(\\.js)?$`. That mean it will match: MyComponent.js, MyComponent.test.js, MyComponent.tests.js, MyComponent.spec.js, MyComponent.specs.js...
## Usage with Angular
@ -124,7 +144,7 @@ Assuming that you have created a test files `my.component.spec.ts` and `my-other
Configure Jest with [jest-preset-angular](https://www.npmjs.com/package/jest-preset-angular)
In project`s `typings.d.ts` add
In project`s`typings.d.ts` add
```ts
declare module '*.json' {
@ -133,29 +153,31 @@ declare module '*.json' {
}
```
Create a simple file `withTests.ts`:
In your `.storybook/config.ts`:
```ts
import * as results from '../.jest-test-results.json';
import { addDecorator } from '@storybook/angular';
import { withTests } from '@storybook/addon-jest';
export const wTests = withTests({
results,
filesExt: '((\\.specs?)|(\\.tests?))?(\\.ts)?$'
});
import * as results from '../.jest-test-results.json';
addDecorator(
withTests({
results,
filesExt: '((\\.specs?)|(\\.tests?))?(\\.ts)?$',
})
);
```
Then in your story:
```js
// import your file
import wTests from '.withTests';
storiesOf('MyComponent', module)
.addDecorator(wTests('my.component', 'my-other.component'))
.add('This story shows test results from my.component.spec.ts and my-other.component.spec.ts', () => (
<div>Jest results in storybook</div>
));
.addParameters({ jest: ['my.component', 'my-other.component'] })
.add(
'This story shows test results from my.component.spec.ts and my-other.component.spec.ts',
() => <div>Jest results in storybook</div>
);
```
##### Example [here](https://github.com/storybooks/storybook/tree/master/examples/angular-cli)

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-jest",
"version": "4.0.0-alpha.10",
"version": "4.0.0-alpha.14",
"description": "React storybook addon that show component jest report",
"keywords": [
"addon",
@ -25,13 +25,13 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "4.0.0-alpha.10",
"@storybook/components": "4.0.0-alpha.10",
"@storybook/addons": "4.0.0-alpha.14",
"@storybook/components": "4.0.0-alpha.14",
"babel-runtime": "^6.26.0",
"emotion": "^9.1.3",
"global": "^4.3.2",
"prop-types": "^15.6.1",
"react-emotion": "^9.1.3"
"react-emotion": "^9.1.3",
"util-deprecate": "^1.0.2"
},
"peerDependencies": {
"react": "*"

View File

@ -2,8 +2,6 @@ import React from 'react';
import PropTypes from 'prop-types';
import styled from 'react-emotion';
import { baseFonts } from '@storybook/components';
import Indicator from './Indicator';
import Result, { FailedResult } from './Result';
import provideJestResult from '../hoc/provideJestResult';
@ -25,7 +23,6 @@ const Item = styled('li')({
const NoTests = styled('div')({
padding: '10px 20px',
flex: 1,
...baseFonts,
});
const FileTitle = styled('h2')({
@ -139,7 +136,6 @@ const Content = styled(({ tests, className }) => (
))({
padding: '10px 20px',
flex: '1 1 0%',
...baseFonts,
});
const Panel = ({ tests }) =>

View File

@ -1,7 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
const provideTests = Component => {
const provideTests = Component =>
class TestProvider extends React.Component {
static propTypes = {
channel: PropTypes.shape({
@ -11,21 +11,22 @@ const provideTests = Component => {
api: PropTypes.shape({
onStory: PropTypes.func,
}).isRequired,
active: PropTypes.bool,
};
static defaultProps = {
active: true,
};
constructor(props) {
super(props);
this.state = {};
this.onAddTests = this.onAddTests.bind(this);
}
state = {};
componentDidMount() {
this.stopListeningOnStory = this.props.api.onStory(() => {
const { channel, api } = this.props;
this.stopListeningOnStory = api.onStory(() => {
this.onAddTests({});
});
this.props.channel.on('storybook/tests/add_tests', this.onAddTests);
channel.on('storybook/tests/add_tests', this.onAddTests);
}
componentWillUnmount() {
@ -35,16 +36,14 @@ const provideTests = Component => {
this.props.channel.removeListener('storybook/tests/add_tests', this.onAddTests);
}
onAddTests({ kind, storyName, tests }) {
onAddTests = ({ kind, storyName, tests }) => {
this.setState({ kind, storyName, tests });
}
};
render() {
return <Component {...this.state} />;
const { active } = this.props;
return active ? <Component {...this.state} /> : null;
}
}
return TestProvider;
};
};
export default provideTests;

View File

@ -1,7 +1,8 @@
import addons from '@storybook/addons';
import deprecate from 'util-deprecate';
const findTestResults = (testFiles, jestTestResults, jestTestFilesExt) =>
testFiles.map(name => {
Array.from(testFiles).map(name => {
if (jestTestResults && jestTestResults.testResults) {
return {
name,
@ -27,9 +28,27 @@ export const withTests = userOptions => {
};
const options = Object.assign({}, defaultOptions, userOptions);
return (...testFiles) => (storyFn, { kind, story }) => {
emitAddTests({ kind, story, testFiles, options });
return (...args) => {
if (typeof args[0] === 'string') {
return deprecate((story, { kind }) => {
emitAddTests({ kind, story, testFiles: args, options });
return storyFn();
return story();
}, 'Passing component filenames to the `@storybook/addon-jest` via `withTests` is deprecated. Instead, use the `jest` story parameter');
}
const [
story,
{
kind,
parameters: { jest: testFiles },
},
] = args;
if (testFiles && !testFiles.disable) {
emitAddTests({ kind, story, testFiles, options });
}
return story();
};
};

View File

@ -4,11 +4,11 @@ import addons from '@storybook/addons';
import PanelTitle from './components/PanelTitle';
import Panel from './components/Panel';
// Register the addon with a unique name.
addons.register('storybook/tests', api => {
// Also need to set a unique name to the panel.
const channel = addons.getChannel();
addons.addPanel('storybook/tests/panel', {
title: <PanelTitle channel={addons.getChannel()} api={api} />,
render: () => <Panel channel={addons.getChannel()} api={api} />,
// eslint-disable-next-line react/prop-types
render: ({ active }) => <Panel channel={channel} api={api} active={active} />,
});
});

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-knobs",
"version": "4.0.0-alpha.10",
"version": "4.0.0-alpha.14",
"description": "Storybook Addon Prop Editor Component",
"repository": {
"type": "git",
@ -13,22 +13,20 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "4.0.0-alpha.10",
"@storybook/components": "4.0.0-alpha.10",
"@storybook/core-events": "4.0.0-alpha.10",
"@storybook/addons": "4.0.0-alpha.14",
"@storybook/components": "4.0.0-alpha.14",
"@storybook/core-events": "4.0.0-alpha.14",
"babel-runtime": "^6.26.0",
"deep-equal": "^1.0.1",
"copy-to-clipboard": "^3.0.8",
"escape-html": "^1.0.3",
"fast-deep-equal": "^2.0.1",
"global": "^4.3.2",
"insert-css": "^2.0.0",
"lodash.debounce": "^4.0.8",
"moment": "^2.22.2",
"prop-types": "^15.6.1",
"qs": "^6.5.2",
"react-color": "^2.14.1",
"react-datetime": "^2.14.0",
"react-emotion": "^9.1.3",
"react-lifecycles-compat": "^3.0.4",
"react-textarea-autosize": "^6.1.0",
"util-deprecate": "^1.0.2"
},
"devDependencies": {

View File

@ -1,5 +1,5 @@
/* eslint no-underscore-dangle: 0 */
import deepEqual from 'deep-equal';
import deepEqual from 'fast-deep-equal';
import escape from 'escape-html';
import KnobStore from './KnobStore';

View File

@ -1,3 +1,6 @@
const callArg = fn => fn();
const callAll = fns => fns.forEach(callArg);
export default class KnobStore {
constructor() {
this.store = {};
@ -12,7 +15,12 @@ export default class KnobStore {
this.store[key] = value;
this.store[key].used = true;
this.store[key].groupId = value.groupId;
this.callbacks.forEach(cb => cb());
// debounce the execution of the callbacks for 50 milliseconds
if (this.timer) {
clearTimeout(this.timer);
}
this.timer = setTimeout(callAll, 50, this.callbacks);
}
get(key) {

View File

@ -1,110 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import styled from 'react-emotion';
import { Placeholder } from '@storybook/components';
const Wrapper = styled('div')({
flex: '1 1 auto',
display: 'flex',
flexDirection: 'column',
background: 'white',
borderRadius: 4,
border: 'solid 1px rgb(236, 236, 236)',
width: '100%',
});
const Bar = styled('div')({
display: 'flex',
flexWrap: 'wrap',
flexDirection: 'row',
justifyContent: 'flex-start',
alignItems: 'center',
borderBottom: 'solid 1px #eaeaea',
});
const Content = styled('div')({
flex: '1 1 0',
display: 'flex',
overflow: 'auto',
});
const Tab = styled('button')(({ active }) => ({
fontSize: 11,
letterSpacing: '1px',
padding: '10px 15px',
textTransform: 'uppercase',
transition: 'opacity 0.3s',
opacity: active ? 1 : 0.5,
maxHeight: 60,
overflow: 'hidden',
cursor: 'pointer',
background: 'transparent',
border: 'none',
}));
class GroupTabs extends Component {
renderTab(name, group) {
const onClick = e => {
e.preventDefault();
this.props.onGroupSelect(name);
};
let { title } = group;
if (typeof title === 'function') {
title = title();
}
return (
<Tab
type="button"
key={name}
onClick={onClick}
role="tab"
active={this.props.selectedGroup === name}
>
{title}
</Tab>
);
}
render() {
const entries = this.props.groups ? Object.entries(this.props.groups) : null;
return entries && entries.length ? (
<Wrapper>
<Bar role="tablist">{entries.map(([key, value]) => this.renderTab(key, value))}</Bar>
<Content>
{entries.map(([key, value]) => {
const groupStyle = { display: 'none' };
if (key === this.props.selectedGroup) {
Object.assign(groupStyle, { flex: 1, display: 'flex' });
}
return (
<div key={key} style={groupStyle}>
{value.render()}
</div>
);
})}
</Content>
</Wrapper>
) : (
<Placeholder>no groups available</Placeholder>
);
}
}
GroupTabs.defaultProps = {
groups: {},
onGroupSelect: () => {},
selectedGroup: null,
};
GroupTabs.propTypes = {
// eslint-disable-next-line react/forbid-prop-types
groups: PropTypes.object,
onGroupSelect: PropTypes.func,
selectedGroup: PropTypes.string,
};
export default GroupTabs;

View File

@ -1,12 +1,14 @@
import React from 'react';
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import qs from 'qs';
import { document } from 'global';
import styled from 'react-emotion';
import copy from 'copy-to-clipboard';
import { Placeholder, TabWrapper, TabsState, ActionBar, ActionButton } from '@storybook/components';
import { Placeholder } from '@storybook/components';
import GroupTabs from './GroupTabs';
import PropForm from './PropForm';
import Types from './types';
import PropForm from './PropForm';
const getTimestamp = () => +new Date();
@ -16,49 +18,21 @@ const PanelWrapper = styled('div')({
width: '100%',
});
const PanelInner = styled('div')({
padding: '5px',
width: 'auto',
position: 'relative',
});
const ResetButton = styled('button')({
position: 'absolute',
bottom: 11,
right: 10,
border: 'none',
borderTop: 'solid 1px rgba(0, 0, 0, 0.2)',
borderLeft: 'solid 1px rgba(0, 0, 0, 0.2)',
background: 'rgba(255, 255, 255, 0.5)',
padding: '5px 10px',
borderRadius: '4px 0 0 0',
color: 'rgba(0, 0, 0, 0.5)',
outline: 'none',
});
export default class Panel extends React.Component {
export default class Panel extends PureComponent {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.handleClick = this.handleClick.bind(this);
this.setKnobs = this.setKnobs.bind(this);
this.reset = this.reset.bind(this);
this.setOptions = this.setOptions.bind(this);
this.onGroupSelect = this.onGroupSelect.bind(this);
this.state = { knobs: {}, groupId: DEFAULT_GROUP_ID };
this.state = { knobs: {} };
this.options = {};
this.lastEdit = getTimestamp();
this.loadedFromUrl = false;
}
componentDidMount() {
this.props.channel.on('addon:knobs:setKnobs', this.setKnobs);
this.props.channel.on('addon:knobs:setOptions', this.setOptions);
this.stopListeningOnStory = this.props.api.onStory(() => {
this.setState({ knobs: [], groupId: DEFAULT_GROUP_ID });
this.setState({ knobs: {} });
this.props.channel.emit('addon:knobs:reset');
});
}
@ -68,15 +42,11 @@ export default class Panel extends React.Component {
this.stopListeningOnStory();
}
onGroupSelect(name) {
this.setState({ groupId: name });
}
setOptions(options = { timestamps: false }) {
setOptions = (options = { timestamps: false }) => {
this.options = options;
}
};
setKnobs({ knobs, timestamp }) {
setKnobs = ({ knobs, timestamp }) => {
const queryParams = {};
const { api, channel } = this.props;
@ -86,7 +56,6 @@ export default class Panel extends React.Component {
// For the first time, get values from the URL and set them.
if (!this.loadedFromUrl) {
const urlValue = api.getQueryParam(`knob-${name}`);
if (urlValue !== undefined) {
// If the knob value present in url
knob.value = Types[knob.type].deserialize(urlValue);
@ -94,48 +63,63 @@ export default class Panel extends React.Component {
}
}
queryParams[`knob-${name}`] = Types[knob.type].serialize(knob.value);
// set all knobsquery params to be deleted from URL
queryParams[`knob-${name}`] = null;
});
this.loadedFromUrl = true;
api.setQueryParams(queryParams);
this.setState({ knobs });
this.loadedFromUrl = true;
}
}
};
reset() {
reset = () => {
this.props.channel.emit('addon:knobs:reset');
}
};
emitChange(changedKnob) {
this.props.channel.emit('addon:knobs:knobChange', changedKnob);
}
handleChange(changedKnob) {
this.lastEdit = getTimestamp();
const { api } = this.props;
copy = () => {
const { location } = document;
const query = qs.parse(location.search.replace('?', ''));
const { knobs } = this.state;
const { name, type, value } = changedKnob;
Object.entries(knobs).forEach(([name, knob]) => {
query[`knob-${name}`] = Types[knob.type].serialize(knob.value);
});
copy(`${location.origin + location.pathname}?${qs.stringify(query)}`);
// TODO: show some notification of this
};
emitChange = changedKnob => {
this.props.channel.emit('addon:knobs:knobChange', changedKnob);
};
handleChange = changedKnob => {
this.lastEdit = getTimestamp();
const { knobs } = this.state;
const { name } = changedKnob;
const newKnobs = { ...knobs };
newKnobs[name] = {
...newKnobs[name],
...changedKnob,
};
this.setState({ knobs: newKnobs });
const queryParams = {};
queryParams[`knob-${name}`] = Types[type].serialize(value);
api.setQueryParams(queryParams);
this.setState({ knobs: newKnobs }, this.emitChange(changedKnob));
}
};
handleClick(knob) {
handleClick = knob => {
this.props.channel.emit('addon:knobs:knobClick', knob);
}
};
render() {
const { knobs, groupId } = this.state;
const { knobs } = this.state;
const { active } = this.props;
if (!active) {
return null;
}
const groups = {};
const groupIds = [];
@ -146,20 +130,23 @@ export default class Panel extends React.Component {
const knobKeyGroupId = knobs[key].groupId;
groupIds.push(knobKeyGroupId);
groups[knobKeyGroupId] = {
render: () => <div id={knobKeyGroupId}>{knobKeyGroupId}</div>,
render: ({ active: groupActive, selected }) => (
<TabWrapper active={groupActive || selected === DEFAULT_GROUP_ID}>
<PropForm
knobs={knobsArray.filter(knob => knob.groupId === knobKeyGroupId)}
onFieldChange={this.handleChange}
onFieldClick={this.handleClick}
/>
</TabWrapper>
),
title: knobKeyGroupId,
};
});
if (groupIds.length > 0) {
groups[DEFAULT_GROUP_ID] = {
render: () => <div id={DEFAULT_GROUP_ID}>{DEFAULT_GROUP_ID}</div>,
title: DEFAULT_GROUP_ID,
};
if (groupId !== DEFAULT_GROUP_ID) {
knobsArray = knobsArray.filter(key => knobs[key].groupId === groupId);
}
}
groups[DEFAULT_GROUP_ID] = {
render: () => null,
title: DEFAULT_GROUP_ID,
};
knobsArray = knobsArray.map(key => knobs[key]);
@ -169,33 +156,38 @@ export default class Panel extends React.Component {
return (
<PanelWrapper>
{groupIds.length > 0 && (
<GroupTabs
groups={groups}
onGroupSelect={this.onGroupSelect}
selectedGroup={this.state.groupId}
/>
)}
<PanelInner>
{groupIds.length > 0 ? (
<TabsState>
{Object.entries(groups).map(([k, v]) => (
<div id={k} title={v.title}>
{v.render}
</div>
))}
</TabsState>
) : (
<PropForm
knobs={knobsArray}
onFieldChange={this.handleChange}
onFieldClick={this.handleClick}
/>
</PanelInner>
<ResetButton onClick={this.reset}>RESET</ResetButton>
)}
<ActionBar>
<ActionButton onClick={this.copy}>COPY</ActionButton>
<ActionButton onClick={this.reset}>RESET</ActionButton>
</ActionBar>
</PanelWrapper>
);
}
}
Panel.propTypes = {
active: PropTypes.bool.isRequired,
onReset: PropTypes.object, // eslint-disable-line
channel: PropTypes.shape({
emit: PropTypes.func,
on: PropTypes.func,
removeListener: PropTypes.func,
}).isRequired,
onReset: PropTypes.object, // eslint-disable-line
api: PropTypes.shape({
onStory: PropTypes.func,
getQueryParam: PropTypes.func,

View File

@ -1,43 +0,0 @@
import PropTypes from 'prop-types';
import React from 'react';
import styled from 'react-emotion';
import TypeMap from './types';
const InvalidType = () => <span>Invalid Type</span>;
const Field = styled('div')({
display: 'table-row',
padding: '5px',
});
const Label = styled('label')({
display: 'table-cell',
boxSizing: 'border-box',
verticalAlign: 'top',
paddingRight: 5,
paddingTop: 5,
textAlign: 'right',
width: 80,
fontSize: 12,
color: 'rgb(68, 68, 68)',
fontWeight: 600,
});
export default function PropField({ onChange, onClick, knob }) {
const InputType = TypeMap[knob.type] || InvalidType;
return (
<Field>
<Label htmlFor={knob.name}>{!knob.hideLabel && `${knob.name}`}</Label>
<InputType knob={knob} onChange={onChange} onClick={onClick} />
</Field>
);
}
PropField.propTypes = {
knob: PropTypes.shape({
name: PropTypes.string,
value: PropTypes.any,
}).isRequired,
onChange: PropTypes.func.isRequired,
onClick: PropTypes.func.isRequired,
};

View File

@ -1,19 +1,19 @@
import React from 'react';
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import styled from 'react-emotion';
import PropField from './PropField';
import { Field } from '@storybook/components';
import TypeMap from './types';
const Form = styled('form')({
display: 'table',
boxSizing: 'border-box',
width: '100%',
borderCollapse: 'separate',
borderSpacing: '5px',
});
export default class propForm extends React.Component {
const InvalidType = () => <span>Invalid Type</span>;
export default class PropForm extends Component {
makeChangeHandler(name, type) {
return value => {
const change = { name, type, value };
@ -28,16 +28,12 @@ export default class propForm extends React.Component {
<Form>
{knobs.map(knob => {
const changeHandler = this.makeChangeHandler(knob.name, knob.type);
const InputType = TypeMap[knob.type] || InvalidType;
return (
<PropField
key={knob.name}
name={knob.name}
type={knob.type}
value={knob.value}
knob={knob}
onChange={changeHandler}
onClick={this.props.onFieldClick}
/>
<Field key={knob.name} label={!knob.hideLabel && `${knob.name}`}>
<InputType knob={knob} onChange={changeHandler} onClick={this.props.onFieldClick} />
</Field>
);
})}
</Form>
@ -45,19 +41,15 @@ export default class propForm extends React.Component {
}
}
propForm.displayName = 'propForm';
PropForm.displayName = 'PropForm';
propForm.defaultProps = {
knobs: [],
};
propForm.propTypes = {
PropForm.propTypes = {
knobs: PropTypes.arrayOf(
PropTypes.shape({
name: PropTypes.string,
value: PropTypes.any,
})
),
).isRequired,
onFieldChange: PropTypes.func.isRequired,
onFieldClick: PropTypes.func.isRequired,
};

View File

@ -1,62 +0,0 @@
import React from 'react';
import { shallow } from 'enzyme';
import GroupTabs from '../GroupTabs';
describe('GroupTabs', () => {
test('should render only the selected group with display set other than "none"', () => {
const groups = {
test1: {
render() {
return <div id="test1">TEST 1</div>;
},
},
test2: {
render() {
return <div id="test2">TEST 2</div>;
},
},
};
const onGroupSelect = () => 'onGroupSelect';
const wrapper = shallow(
<GroupTabs groups={groups} onGroupSelect={onGroupSelect} selectedGroup="test2" />
);
expect(wrapper.find('#test1').parent()).toHaveStyle('display', 'none');
expect(wrapper.find('#test2').parent()).not.toHaveStyle('display', 'none');
});
test('should set onGroupSelected as onClick handlers of tabs', () => {
const groups = {
test1: {
title: 'test 1',
render() {
return <div>TEST 1</div>;
},
},
};
const onGroupSelect = jest.fn();
const preventDefault = jest.fn();
const wrapper = shallow(
<GroupTabs groups={groups} onGroupSelect={onGroupSelect} selectedGroup="test1" />
);
wrapper
.find('Styled(button)')
.dive()
.simulate('click', { preventDefault });
expect(onGroupSelect).toHaveBeenCalled();
expect(preventDefault).toHaveBeenCalled();
});
describe('when no groups are given', () => {
test('should render "no groups available"', () => {
const groups = {};
const onGroupSelect = () => 'onGroupSelect';
const wrapper = shallow(<GroupTabs groups={groups} onGroupSelect={onGroupSelect} />);
expect(wrapper.contains('no groups available')).toBe(true);
});
});
});

View File

@ -6,14 +6,14 @@ describe('Panel', () => {
it('should subscribe to setKnobs event of channel', () => {
const testChannel = { on: jest.fn() };
const testApi = { onStory: jest.fn() };
shallow(<Panel channel={testChannel} api={testApi} />);
shallow(<Panel channel={testChannel} api={testApi} active />);
expect(testChannel.on).toHaveBeenCalledWith('addon:knobs:setKnobs', jasmine.any(Function));
});
it('should subscribe to onStory event', () => {
const testChannel = { on: jest.fn() };
const testApi = { onStory: jest.fn() };
shallow(<Panel channel={testChannel} api={testApi} />);
shallow(<Panel channel={testChannel} api={testApi} active />);
expect(testApi.onStory).toHaveBeenCalled();
expect(testChannel.on).toHaveBeenCalledWith('addon:knobs:setKnobs', jasmine.any(Function));
@ -41,7 +41,7 @@ describe('Panel', () => {
onStory: jest.fn(),
};
shallow(<Panel channel={testChannel} api={testApi} />);
shallow(<Panel channel={testChannel} api={testApi} active />);
const setKnobsHandler = handlers['addon:knobs:setKnobs'];
const knobs = {
@ -67,7 +67,7 @@ describe('Panel', () => {
expect(testChannel.emit).toHaveBeenCalledWith(e, knobFromUrl);
});
it('should set query params when url params are already read', () => {
it('should remove query params when url params are already read', () => {
const handlers = {};
const testChannel = {
@ -88,7 +88,7 @@ describe('Panel', () => {
onStory: jest.fn(),
};
const wrapper = shallow(<Panel channel={testChannel} api={testApi} />);
const wrapper = shallow(<Panel channel={testChannel} api={testApi} active />);
const setKnobsHandler = handlers['addon:knobs:setKnobs'];
const knobs = {
@ -109,8 +109,8 @@ describe('Panel', () => {
setKnobsHandler({ knobs, timestamp: +new Date() });
const knobFromStory = {
'knob-foo': knobs.foo.value,
'knob-baz': knobs.baz.value,
'knob-foo': null,
'knob-baz': null,
};
expect(testApi.setQueryParams).toHaveBeenCalledWith(knobFromStory);
@ -130,7 +130,7 @@ describe('Panel', () => {
onStory: jest.fn(),
};
const wrapper = shallow(<Panel channel={testChannel} api={testApi} />);
const wrapper = shallow(<Panel channel={testChannel} api={testApi} active />);
const testChangedKnob = {
name: 'foo',
@ -140,8 +140,8 @@ describe('Panel', () => {
wrapper.instance().handleChange(testChangedKnob);
expect(testChannel.emit).toHaveBeenCalledWith('addon:knobs:knobChange', testChangedKnob);
const paramsChange = { 'knob-foo': 'changed text' };
expect(testApi.setQueryParams).toHaveBeenCalledWith(paramsChange);
// const paramsChange = { 'knob-foo': 'changed text' };
// expect(testApi.setQueryParams).toHaveBeenCalledWith(paramsChange);
});
});
});

View File

@ -1,24 +1,7 @@
import PropTypes from 'prop-types';
import React from 'react';
import styled from 'react-emotion';
import Textarea from 'react-textarea-autosize';
import debounce from 'lodash.debounce';
const StyledTextarea = styled(Textarea)({
display: 'table-cell',
boxSizing: 'border-box',
verticalAlign: 'middle',
height: '26px',
width: '100%',
maxWidth: '100%',
outline: 'none',
border: '1px solid #f7f4f4',
borderRadius: 2,
fontSize: 11,
padding: '5px',
color: '#555',
});
import { Textarea } from '@storybook/components';
function formatArray(value, separator) {
if (value === '') {
@ -28,34 +11,21 @@ function formatArray(value, separator) {
}
class ArrayType extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
value: props.knob.value.join(props.knob.separator),
};
this.onChange = debounce(this.props.onChange, 200);
}
componentWillUnmount() {
this.onChange.cancel();
shouldComponentUpdate(nextProps) {
return nextProps.knob.value !== this.props.knob.value;
}
handleChange = e => {
const { knob } = this.props;
const { value } = e.target;
const newVal = formatArray(value, knob.separator);
this.setState({ value });
this.onChange(newVal);
this.props.onChange(newVal);
};
render() {
const { knob } = this.props;
const { value } = this.state;
return <StyledTextarea id={knob.name} value={value} onChange={this.handleChange} />;
return <Textarea id={knob.name} value={knob.value} onChange={this.handleChange} size="flex" />;
}
}

View File

@ -1,26 +1,18 @@
import PropTypes from 'prop-types';
import React from 'react';
import styled from 'react-emotion';
const Button = styled('button')({
height: '26px',
});
import { Button } from '@storybook/components';
const ButtonType = ({ knob, onClick }) => (
<Button type="button" id={knob.name} onClick={() => onClick(knob)}>
<Button type="button" onClick={() => onClick(knob)}>
{knob.name}
</Button>
);
ButtonType.defaultProps = {
knob: {},
};
ButtonType.propTypes = {
knob: PropTypes.shape({
name: PropTypes.string,
}),
}).isRequired,
onClick: PropTypes.func.isRequired,
};

View File

@ -4,22 +4,15 @@ import React from 'react';
import { SketchPicker } from 'react-color';
import styled from 'react-emotion';
import debounce from 'lodash.debounce';
const SwatchButton = styled('button')({
background: '#fff',
borderRadius: '1px',
border: '1px solid rgb(247, 244, 244)',
display: 'inline-block',
cursor: 'pointer',
width: '100%',
padding: 0,
});
import { Button } from '@storybook/components';
const Swatch = styled('div')({
width: 'auto',
height: '20px',
borderRadius: '2px',
margin: 5,
position: 'absolute',
top: 0,
bottom: 0,
right: 3,
width: 28,
});
const Popover = styled('div')({
position: 'absolute',
@ -27,23 +20,18 @@ const Popover = styled('div')({
});
class ColorType extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
displayColorPicker: false,
value: props.knob.value,
};
this.onChange = debounce(props.onChange, 200);
}
state = {
displayColorPicker: false,
};
componentDidMount() {
document.addEventListener('mousedown', this.handleWindowMouseDown);
}
shouldComponentUpdate(nextProps) {
return nextProps.knob.value !== this.props.knob.value;
}
componentWillUnmount() {
document.removeEventListener('mousedown', this.handleWindowMouseDown);
this.onChange.cancel();
}
handleWindowMouseDown = e => {
@ -62,35 +50,30 @@ class ColorType extends React.Component {
};
handleChange = color => {
this.setState({
value: color,
});
this.onChange(`rgba(${color.rgb.r},${color.rgb.g},${color.rgb.b},${color.rgb.a})`);
this.props.onChange(`rgba(${color.rgb.r},${color.rgb.g},${color.rgb.b},${color.rgb.a})`);
};
render() {
const { knob } = this.props;
const { displayColorPicker, value } = this.state;
const { displayColorPicker } = this.state;
const colorStyle = {
background: knob.value,
};
return (
<div id={knob.name}>
<SwatchButton type="button" onClick={this.handleClick}>
<Swatch style={colorStyle} />
</SwatchButton>
<Button type="button" onClick={this.handleClick} size="flex">
{knob.value}
<Swatch style={colorStyle} />
{displayColorPicker ? (
<Popover
innerRef={e => {
this.popover = e;
}}
>
<SketchPicker color={value} onChange={this.handleChange} />
<SketchPicker color={knob.value} onChange={this.handleChange} />
</Popover>
) : null}
</div>
</Button>
);
}
}

View File

@ -1,73 +1,43 @@
import PropTypes from 'prop-types';
import React from 'react';
import styled from 'react-emotion';
import Datetime from 'react-datetime';
import insertCss from 'insert-css';
import debounce from 'lodash.debounce';
import style from './styles';
const customStyle = `
.rdt input {
outline: 0;
width: 100%;
border: 1px solid #f7f4f4;
border-radius: 2px;
font-size: 11px;
padding: 5px;
color: #555;
display: table-cell;
box-sizing: border-box;
}
`;
insertCss(style);
insertCss(customStyle);
const DateInput = styled(Datetime)(style);
class DateType extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
value: props.knob.value,
};
this.onChange = debounce(props.onChange, 200);
shouldComponentUpdate(nextProps) {
return nextProps.knob.value !== this.props.knob.value;
}
handleChange = date => {
const value = date.valueOf();
this.setState({ value });
this.onChange(value);
this.props.onChange(value);
};
render() {
const { knob } = this.props;
const { value } = this.state;
return (
<div>
<Datetime
id={knob.name}
value={value ? new Date(value) : null}
type="date"
onChange={this.handleChange}
/>
</div>
<DateInput
value={knob.value ? new Date(knob.value) : null}
type="date"
onChange={this.handleChange}
size="flex"
/>
);
}
}
DateType.defaultProps = {
knob: {},
onChange: value => value,
};
DateType.propTypes = {
knob: PropTypes.shape({
name: PropTypes.string,
value: PropTypes.number,
}),
onChange: PropTypes.func,
}).isRequired,
onChange: PropTypes.func.isRequired,
};
DateType.serialize = value => String(value);

View File

@ -1,219 +1,202 @@
export default `
.rdt {
position: relative;
}
.rdtPicker {
display: none;
position: absolute;
width: 200px;
padding: 4px;
margin-top: 1px;
z-index: 99999 !important;
background: #fff;
box-shadow: 0 1px 3px rgba(0,0,0,.1);
border: 1px solid #f9f9f9;
}
.rdtOpen .rdtPicker {
display: block;
}
.rdtStatic .rdtPicker {
box-shadow: none;
position: static;
}
import { Input } from '@storybook/components';
.rdtPicker .rdtTimeToggle {
text-align: center;
font-size:11px;
}
export default ({ theme, size }) => ({
...Input.sizes({ size, theme }),
'&.rdt': {
position: 'relative',
},
'& > input': {
width: '100%',
height: 32,
...Input.styles({ theme }),
...Input.alignment({ theme }),
},
'& .rdtPicker': {
display: 'none',
position: 'absolute',
width: 200,
padding: 4,
marginTop: 1,
zIndex: 99999,
background: theme.barFill,
},
'&.rdtOpen .rdtPicker': {
display: 'block',
},
'&.rdt .rdtPicker': {
boxShadow: 'none',
position: 'static',
},
.rdtPicker table {
width: 100%;
margin: 0;
}
.rdtPicker td,
.rdtPicker th {
text-align: center;
height: 28px;
}
.rdtPicker td {
cursor: pointer;
}
.rdtPicker td.rdtDay:hover,
.rdtPicker td.rdtHour:hover,
.rdtPicker td.rdtMinute:hover,
.rdtPicker td.rdtSecond:hover,
.rdtPicker .rdtTimeToggle:hover {
background: #eeeeee;
cursor: pointer;
}
.rdtPicker td.rdtOld,
.rdtPicker td.rdtNew {
color: #999999;
}
.rdtPicker td.rdtToday {
position: relative;
}
.rdtPicker td.rdtToday:before {
content: '';
display: inline-block;
border-left: 7px solid transparent;
border-bottom: 7px solid #428bca;
border-top-color: rgba(0, 0, 0, 0.2);
position: absolute;
bottom: 4px;
right: 4px;
}
.rdtPicker td.rdtActive,
.rdtPicker td.rdtActive:hover {
background-color: #428bca;
color: #fff;
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
}
.rdtPicker td.rdtActive.rdtToday:before {
border-bottom-color: #fff;
}
.rdtPicker td.rdtDisabled,
.rdtPicker td.rdtDisabled:hover {
background: none;
color: #999999;
cursor: not-allowed;
}
'& .rdtPicker .rdtTimeToggle': {
textAlign: 'center',
fontSize: 11,
},
.rdtPicker td span.rdtOld {
color: #999999;
}
.rdtPicker td span.rdtDisabled,
.rdtPicker td span.rdtDisabled:hover {
background: none;
color: #999999;
cursor: not-allowed;
}
.rdtPicker th {
border-bottom: 1px solid #f9f9f9;
}
.rdtPicker .dow {
width: 14.2857%;
font-size: 11px;
border-bottom: none;
}
.rdtPicker th.rdtSwitch {
width: 100px;
font-size: 11px;
}
.rdtPicker th.rdtNext,
.rdtPicker th.rdtPrev {
font-size: 11px;
vertical-align: top;
}
'& .rdtPicker table': {
width: '100%',
margin: 0,
},
'& .rdtPicker td, & .rdtPicker th': {
textAlign: 'center',
height: 32,
boxSizing: 'border-box',
},
'& .rdtPicker td': {
cursor: 'pointer',
},
'& .rdtPicker td.rdtDay:hover, & .rdtPicker td.rdtHour:hover, & .rdtPicker td.rdtMinute:hover, & .rdtPicker td.rdtSecond:hover, & .rdtPicker .rdtTimeToggle:hover': {
color: theme.highlightColor,
textDecoration: 'underline',
cursor: 'pointer',
},
'& .rdtPicker td.rdtOld, & .rdtPicker td.rdtNew': {
color: '#999999',
},
'& .rdtPicker td.rdtToday': {
position: 'relative',
},
'& .rdtPicker td.rdtToday:before': {
content: '""',
display: 'inline-block',
borderLeft: '7px solid transparent',
borderBottom: `7px solid ${theme.highlightColor}`,
borderTopColor: 'rgba(0, 0, 0, 0.2)',
position: 'absolute',
bottom: 4,
right: 4,
},
'& .rdtPicker td.rdtActive, & .rdtPicker td.rdtActive:hover': {
backgroundColor: theme.highlightColor,
color: '#fff',
textShadow:
'0 -1px 0 rgba(0,0,0,0.25), 0 1px 0 rgba(0,0,0,0.25), -1px 0 0 rgba(0,0,0,0.25), 1px 0 0 rgba(0,0,0,0.25)',
},
'& .rdtPicker td.rdtActive.rdtToday:before': {
borderBottomColor: '#fff',
},
'& .rdtPicker td.rdtDisabled, & .rdtPicker td.rdtDisabled:hover': {
background: 'none',
color: '#999999',
cursor: 'not-allowed',
},
.rdtPrev span,
.rdtNext span {
display: block;
-webkit-touch-callout: none; /* iOS Safari */
-webkit-user-select: none; /* Chrome/Safari/Opera */
-khtml-user-select: none; /* Konqueror */
-moz-user-select: none; /* Firefox */
-ms-user-select: none; /* Internet Explorer/Edge */
user-select: none;
}
'& .rdtPicker td span.rdtOld': {
color: '#999999',
},
'& .rdtPicker td span.rdtDisabled, & .rdtPicker td span.rdtDisabled:hover': {
background: 'none',
color: '#999999',
cursor: 'not-allowed',
},
'& .rdtPicker th': {
borderBottom: `1px solid ${theme.highlightColor}`,
},
'& .rdtPicker .dow': {
width: '14.2857%',
fontSize: 11,
borderBottom: 'none',
},
'& .rdtPicker th.rdtSwitch': {
width: 100,
fontSize: 11,
},
'& .rdtPicker th.rdtNext, & .rdtPicker th.rdtPrev': {
fontSize: 11,
verticalAlign: 'middle',
},
.rdtPicker th.rdtDisabled,
.rdtPicker th.rdtDisabled:hover {
background: none;
color: #999999;
cursor: not-allowed;
}
.rdtPicker thead tr:first-child th {
cursor: pointer;
}
.rdtPicker thead tr:first-child th:hover {
background: #eeeeee;
}
'& .rdtPrev span, & .rdtNext span': {
display: 'block',
userSelect: 'none',
},
.rdtPicker tfoot {
border-top: 1px solid #f9f9f9;
}
'& .rdtPicker th.rdtDisabled, & .rdtPicker th.rdtDisabled:hover': {
background: 'none',
color: '#999999',
cursor: 'not-allowed',
},
'& .rdtPicker thead tr:first-child th': {
cursor: 'pointer',
},
'& .rdtPicker thead tr:first-child th:hover': {
color: theme.highlightColor,
},
.rdtPicker button {
border: none;
background: none;
cursor: pointer;
}
.rdtPicker button:hover {
background-color: #eee;
}
'& .rdtPicker tfoot': {
borderTop: '1px solid #f9f9f9',
},
.rdtPicker thead button {
width: 100%;
height: 100%;
}
'& .rdtPicker button': {
border: 'none',
background: 'none',
cursor: 'pointer',
},
'& .rdtPicker button:hover': {
color: theme.highlightColor,
},
td.rdtMonth,
td.rdtYear {
height: 50px;
width: 25%;
cursor: pointer;
}
td.rdtMonth:hover,
td.rdtYear:hover {
background: #eee;
}
'& .rdtPicker thead button': {
width: '100%',
height: '100%',
},
td.rdtDay {
font-size: 11px
}
'& td.rdtMonth, & td.rdtYear': {
height: 50,
width: '25%',
cursor: 'pointer',
},
'& td.rdtMonth:hover, & td.rdtYear:hover': {
color: theme.highlightColor,
},
.rdtCounters {
display: inline-block;
}
'& td.rdtDay': {
fontSize: 11,
},
.rdtCounters > div {
float: left;
}
'& .rdtCounters': {
display: 'inline-block',
},
.rdtCounter {
height: 100px;
}
'& .rdtCounters > div': {
float: 'left',
},
.rdtCounter {
width: 40px;
}
'& .rdtCounter': {
height: 100,
width: 40,
},
.rdtCounterSeparator {
line-height: 100px;
}
'& .rdtCounterSeparator': {
lineHeight: '100px',
},
.rdtCounter .rdtBtn {
height: 40%;
line-height: 40px;
cursor: pointer;
display: block;
font-size: 11px;
'& .rdtCounter .rdtBtn': {
height: '40%',
lineHeight: '40px',
cursor: 'pointer',
display: 'block',
fontSize: 11,
-webkit-touch-callout: none; /* iOS Safari */
-webkit-user-select: none; /* Chrome/Safari/Opera */
-khtml-user-select: none; /* Konqueror */
-moz-user-select: none; /* Firefox */
-ms-user-select: none; /* Internet Explorer/Edge */
user-select: none;
}
.rdtCounter .rdtBtn:hover {
background: #eee;
}
.rdtCounter .rdtCount {
height: 20%;
font-size: 11px;
}
userSelect: 'none',
},
'& .rdtCounter .rdtBtn:hover': {
color: theme.highlightColor,
},
'& .rdtCounter .rdtCount': {
height: '20%',
fontSize: 11,
},
.rdtMilli {
vertical-align: middle;
padding-left: 8px;
width: 48px;
}
'& .rdtMilli': {
verticalSlign: 'middle',
paddingLeft: 8,
width: 48,
},
.rdtMilli input {
width: 100%;
font-size: 11px;
margin-top: 37px;
}
`;
'& .rdtMilli input': {
width: '100%',
fontSize: 11,
marginTop: 37,
},
});

View File

@ -1,22 +1,12 @@
import { FileReader } from 'global';
import PropTypes from 'prop-types';
import React from 'react';
import styled from 'react-emotion';
const Input = styled('input')({
display: 'table-cell',
boxSizing: 'border-box',
verticalAlign: 'middle',
height: '26px',
width: '100%',
maxWidth: '100%',
outline: 'none',
border: '1px solid #f7f4f4',
borderRadius: 2,
fontSize: 11,
padding: '5px',
color: '#555',
import { Input } from '@storybook/components';
const FileInput = styled(Input)({
paddingTop: 4,
});
function fileReaderPromise(file) {
@ -28,12 +18,12 @@ function fileReaderPromise(file) {
}
const FilesType = ({ knob, onChange }) => (
<Input
id={knob.name}
<FileInput
type="file"
multiple
onChange={e => Promise.all(Array.from(e.target.files).map(fileReaderPromise)).then(onChange)}
accept={knob.accept}
size="flex"
/>
);

View File

@ -2,7 +2,8 @@ import PropTypes from 'prop-types';
import React from 'react';
import styled from 'react-emotion';
import debounce from 'lodash.debounce';
import { Input } from '@storybook/components';
const base = {
boxSizing: 'border-box',
@ -15,11 +16,6 @@ const base = {
color: '#444',
};
const TextInput = styled('input')(base, {
display: 'table-cell',
width: '100%',
verticalAlign: 'middle',
});
const RangeInput = styled('input')(base, {
display: 'table-cell',
flexGrow: 1,
@ -37,78 +33,62 @@ const RangeWrapper = styled('div')({
});
class NumberType extends React.Component {
constructor(props) {
super(props);
let { value } = props.knob;
if (value === null || value === undefined) {
value = '';
}
this.state = { value };
this.onChange = debounce(props.onChange, 400);
}
componentWillUnmount() {
this.onChange.cancel();
shouldComponentUpdate(nextProps) {
return nextProps.knob.value !== this.props.knob.value;
}
handleChange = event => {
const { value } = event.target;
this.setState({ value });
let parsedValue = Number(value);
if (Number.isNaN(parsedValue) || value === '') {
parsedValue = null;
}
this.onChange(parsedValue);
this.props.onChange(parsedValue);
};
render() {
const { knob } = this.props;
const { value } = this.state;
return knob.range ? (
<RangeWrapper>
<RangeLabel>{knob.min}</RangeLabel>
<RangeInput
id={knob.name}
value={value}
value={knob.value}
type="range"
min={knob.min}
max={knob.max}
step={knob.step}
onChange={this.handleChange}
/>
<RangeLabel>{`${value} / ${knob.max}`}</RangeLabel>
<RangeLabel>{`${knob.value} / ${knob.max}`}</RangeLabel>
</RangeWrapper>
) : (
<TextInput
id={knob.name}
value={value}
<Input
value={knob.value}
type="number"
min={knob.min}
max={knob.max}
step={knob.step}
onChange={this.handleChange}
size="flex"
/>
);
}
}
NumberType.defaultProps = {
knob: {},
onChange: value => value,
};
NumberType.propTypes = {
knob: PropTypes.shape({
name: PropTypes.string,
value: PropTypes.number,
}),
onChange: PropTypes.func,
range: PropTypes.bool,
min: PropTypes.number,
max: PropTypes.number,
step: PropTypes.number,
}).isRequired,
onChange: PropTypes.func.isRequired,
};
NumberType.serialize = value => (value === null || value === undefined ? '' : String(value));

View File

@ -1,58 +1,38 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import styled from 'react-emotion';
import Textarea from 'react-textarea-autosize';
import debounce from 'lodash.debounce';
const StyledTextarea = styled(Textarea)({
display: 'table-cell',
boxSizing: 'border-box',
verticalAlign: 'middle',
width: '100%',
outline: 'none',
border: '1px solid #f7f4f4',
borderRadius: 2,
fontSize: 11,
padding: '5px',
color: '#555',
fontFamily: 'monospace',
});
import PropTypes from 'prop-types';
import deepEqual from 'fast-deep-equal';
import { polyfill } from 'react-lifecycles-compat';
import { Textarea } from '@storybook/components';
class ObjectType extends Component {
constructor(props, context) {
super(props, context);
try {
this.state = {
value: JSON.stringify(props.knob.value, null, 2),
failed: false,
};
} catch (e) {
this.state = {
// if it can't be JSON stringified, it's probably some weird stuff
value: 'Default object cannot not be JSON stringified',
failed: true,
};
static getDerivedStateFromProps(props, state) {
if (!state || !deepEqual(props.knob.value, state.json)) {
try {
return {
value: JSON.stringify(props.knob.value, null, 2),
failed: false,
json: props.knob.value,
};
} catch (e) {
return { value: 'Object cannot be stringified', failed: true };
}
}
this.onChange = debounce(props.onChange, 200);
}
componentWillUnmount() {
this.onChange.cancel();
return null;
}
handleChange = e => {
const { value } = e.target;
try {
const json = JSON.parse(e.target.value.trim());
this.onChange(json);
const json = JSON.parse(value.trim());
this.setState({
value,
json,
failed: false,
});
if (deepEqual(this.props.knob.value, this.state.json)) {
this.props.onChange(json);
}
} catch (err) {
this.setState({
value,
@ -62,40 +42,30 @@ class ObjectType extends Component {
};
render() {
const { knob } = this.props;
const { value, failed } = this.state;
const extraStyle = {};
if (failed) {
extraStyle.border = '1px solid #fadddd';
extraStyle.backgroundColor = '#fff5f5';
}
return (
<StyledTextarea
id={knob.name}
style={extraStyle}
<Textarea
valid={failed ? 'error' : null}
value={value}
onChange={this.handleChange}
size="flex"
/>
);
}
}
ObjectType.defaultProps = {
knob: {},
onChange: value => value,
};
ObjectType.propTypes = {
knob: PropTypes.shape({
name: PropTypes.string,
value: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
}),
onChange: PropTypes.func,
}).isRequired,
onChange: PropTypes.func.isRequired,
};
ObjectType.serialize = object => JSON.stringify(object);
ObjectType.deserialize = value => (value ? JSON.parse(value) : {});
polyfill(ObjectType);
export default ObjectType;

View File

@ -1,22 +1,9 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import React from 'react';
import styled from 'react-emotion';
const Select = styled('select')({
display: 'table-cell',
boxSizing: 'border-box',
verticalAlign: 'middle',
height: '26px',
width: '100%',
outline: 'none',
border: '1px solid #f7f4f4',
borderRadius: 2,
fontSize: 11,
padding: '5px',
color: '#555',
});
import { Select } from '@storybook/components';
class SelectType extends React.Component {
class SelectType extends Component {
renderOptionList({ options }) {
if (Array.isArray(options)) {
return options.map(val => this.renderOption(val, val));
@ -34,7 +21,7 @@ class SelectType extends React.Component {
const { knob, onChange } = this.props;
return (
<Select id={knob.name} value={knob.value} onChange={e => onChange(e.target.value)}>
<Select value={knob.value} onChange={e => onChange(e.target.value)} size="flex">
{this.renderOptionList(knob)}
</Select>
);

View File

@ -1,53 +1,22 @@
import PropTypes from 'prop-types';
import React from 'react';
import styled from 'react-emotion';
import Textarea from 'react-textarea-autosize';
import debounce from 'lodash.debounce';
const StyledTextarea = styled(Textarea)({
display: 'table-cell',
boxSizing: 'border-box',
verticalAlign: 'middle',
height: '26px',
width: '100%',
maxWidth: '100%',
outline: 'none',
border: '1px solid #f7f4f4',
borderRadius: 2,
fontSize: 11,
padding: '5px',
color: '#555',
});
import { Textarea } from '@storybook/components';
class TextType extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
value: props.knob.value,
};
this.onChange = debounce(props.onChange, 200);
}
componentWillUnmount() {
this.onChange.cancel();
shouldComponentUpdate(nextProps) {
return nextProps.knob.value !== this.props.knob.value;
}
handleChange = event => {
const { value } = event.target;
this.setState({ value });
this.onChange(value);
this.props.onChange(value);
};
render() {
const { knob } = this.props;
const { value } = this.state;
return <StyledTextarea id={knob.name} value={value} onChange={this.handleChange} />;
return <Textarea id={knob.name} value={knob.value} onChange={this.handleChange} size="flex" />;
}
}

View File

@ -77,6 +77,7 @@ export const withKnobs = makeDecorator({
name: 'withKnobs',
parameterName: 'knobs',
skipIfNoParametersOrOptions: false,
allowDeprecatedUsage: true,
wrapper: (getStory, context, { options, parameters }) => {
const storyOptions = parameters || options;
const allOptions = { ...defaultOptions, ...storyOptions };

View File

@ -4,9 +4,9 @@ import Panel from './components/Panel';
addons.register('storybooks/storybook-addon-knobs', api => {
const channel = addons.getChannel();
addons.addPanel('storybooks/storybook-addon-knobs', {
title: 'Knobs',
render: () => <Panel channel={channel} api={api} key="knobs-panel" />,
// eslint-disable-next-line react/prop-types
render: ({ active }) => <Panel channel={channel} api={api} key="knobs-panel" active={active} />,
});
});

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-links",
"version": "4.0.0-alpha.10",
"version": "4.0.0-alpha.14",
"description": "Story Links addon for storybook",
"keywords": [
"storybook"
@ -20,9 +20,9 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "4.0.0-alpha.10",
"@storybook/components": "4.0.0-alpha.10",
"@storybook/core-events": "4.0.0-alpha.10",
"@storybook/addons": "4.0.0-alpha.14",
"@storybook/components": "4.0.0-alpha.14",
"@storybook/core-events": "4.0.0-alpha.14",
"babel-runtime": "^6.26.0",
"global": "^4.3.2",
"prop-types": "^15.6.1"

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-notes",
"version": "4.0.0-alpha.10",
"version": "4.0.0-alpha.14",
"description": "Write notes for your Storybook stories.",
"keywords": [
"addon",
@ -18,13 +18,11 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "4.0.0-alpha.10",
"@storybook/addons": "4.0.0-alpha.14",
"babel-runtime": "^6.26.0",
"emotion": "^9.1.3",
"marked": "^0.4.0",
"prop-types": "^15.6.1",
"react-emotion": "^9.1.3",
"util-deprecate": "^1.0.2"
"react-emotion": "^9.1.3"
},
"peerDependencies": {
"react": "*"

View File

@ -10,6 +10,7 @@ export const withNotes = makeDecorator({
name: 'withNotes',
parameterName: 'notes',
skipIfNoParametersOrOptions: true,
allowDeprecatedUsage: true,
wrapper: (getStory, context, { options, parameters }) => {
const channel = addons.getChannel();

View File

@ -44,6 +44,7 @@ export class Notes extends React.Component {
}
render() {
const { active } = this.props;
const { text } = this.state;
const textAfterFormatted = text
? text
@ -52,31 +53,34 @@ export class Notes extends React.Component {
.replace(/\n/g, '<br />')
: '';
return (
return active ? (
<Panel
className="addon-notes-container"
dangerouslySetInnerHTML={{ __html: textAfterFormatted }}
/>
);
) : null;
}
}
Notes.propTypes = {
// eslint-disable-next-line react/forbid-prop-types
channel: PropTypes.object,
// eslint-disable-next-line react/forbid-prop-types
api: PropTypes.object,
};
Notes.defaultProps = {
channel: {},
api: {},
active: PropTypes.bool.isRequired,
channel: PropTypes.shape({
on: PropTypes.func,
emit: PropTypes.func,
removeListener: PropTypes.func,
}).isRequired,
api: PropTypes.shape({
onStory: PropTypes.func,
getQueryParam: PropTypes.func,
setQueryParams: PropTypes.func,
}).isRequired,
};
// Register the addon with a unique name.
addons.register('storybook/notes', api => {
// Also need to set a unique name to the panel.
const channel = addons.getChannel();
addons.addPanel('storybook/notes/panel', {
title: 'Notes',
render: () => <Notes channel={addons.getChannel()} api={api} />,
// eslint-disable-next-line react/prop-types
render: ({ active }) => <Notes channel={channel} api={api} active={active} />,
});
});

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-options",
"version": "4.0.0-alpha.10",
"version": "4.0.0-alpha.14",
"description": "Options addon for storybook",
"keywords": [
"storybook"
@ -19,7 +19,7 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "4.0.0-alpha.10",
"@storybook/addons": "4.0.0-alpha.14",
"babel-runtime": "^6.26.0"
},
"peerDependencies": {

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-storyshots",
"version": "4.0.0-alpha.10",
"version": "4.0.0-alpha.14",
"description": "StoryShots is a Jest Snapshot Testing Addon for Storybook.",
"repository": {
"type": "git",
@ -16,7 +16,7 @@
"storybook": "start-storybook -p 6006"
},
"dependencies": {
"@storybook/addons": "4.0.0-alpha.10",
"@storybook/addons": "4.0.0-alpha.14",
"babel-runtime": "^6.26.0",
"glob": "^7.1.2",
"global": "^4.3.2",
@ -24,10 +24,10 @@
"read-pkg-up": "^3.0.0"
},
"devDependencies": {
"@storybook/addon-actions": "4.0.0-alpha.10",
"@storybook/addon-links": "4.0.0-alpha.10",
"@storybook/addons": "4.0.0-alpha.10",
"@storybook/react": "4.0.0-alpha.10",
"@storybook/addon-actions": "4.0.0-alpha.14",
"@storybook/addon-links": "4.0.0-alpha.14",
"@storybook/addons": "4.0.0-alpha.14",
"@storybook/react": "4.0.0-alpha.14",
"enzyme-to-json": "^3.3.4",
"react": "^16.4.0"
}

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-storyshots-puppeteer",
"version": "4.0.0-alpha.10",
"version": "4.0.0-alpha.14",
"description": "Image snappshots addition to StoryShots base on puppeteer",
"repository": {
"type": "git",
@ -13,7 +13,7 @@
"prepare": "node ../../../scripts/prepare.js"
},
"dependencies": {
"@storybook/node-logger": "4.0.0-alpha.10",
"@storybook/node-logger": "4.0.0-alpha.14",
"jest-image-snapshot": "^2.4.2",
"puppeteer": "^1.4.0"
},

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-storysource",
"version": "4.0.0-alpha.10",
"version": "4.0.0-alpha.14",
"description": "Stories addon for storybook",
"keywords": [
"storybook"
@ -20,8 +20,8 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "4.0.0-alpha.10",
"@storybook/components": "4.0.0-alpha.10",
"@storybook/addons": "4.0.0-alpha.14",
"@storybook/components": "4.0.0-alpha.14",
"babel-runtime": "^6.26.0",
"estraverse": "^4.2.0",
"loader-utils": "^1.1.0",

View File

@ -7,6 +7,7 @@ import SyntaxHighlighter, { registerLanguage } from 'react-syntax-highlighter/pr
import { createElement } from 'react-syntax-highlighter';
import { EVENT_ID } from './';
// TODO: take from theme
const highlighterTheme = {
...darcula,
'pre[class*="language-"]': {
@ -175,7 +176,8 @@ export default class StoryPanel extends Component {
};
render() {
return (
const { active } = this.props;
return active ? (
<SyntaxHighlighter
language="jsx"
showLineNumbers="true"
@ -185,11 +187,12 @@ export default class StoryPanel extends Component {
>
{this.state.source}
</SyntaxHighlighter>
);
) : null;
}
}
StoryPanel.propTypes = {
active: PropTypes.bool.isRequired,
api: PropTypes.shape({
selectStory: PropTypes.func.isRequired,
}).isRequired,

View File

@ -8,7 +8,8 @@ export function register() {
const channel = addons.getChannel();
addons.addPanel(PANEL_ID, {
title: 'Story',
render: () => <StoryPanel channel={channel} api={api} />,
// eslint-disable-next-line react/prop-types
render: ({ active }) => <StoryPanel channel={channel} api={api} active={active} />,
});
});
}

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/addon-viewport",
"version": "4.0.0-alpha.10",
"version": "4.0.0-alpha.14",
"description": "Storybook addon to change the viewport size to mobile",
"keywords": [
"storybook"
@ -11,13 +11,11 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "4.0.0-alpha.10",
"@storybook/components": "4.0.0-alpha.10",
"@storybook/core-events": "4.0.0-alpha.10",
"@storybook/addons": "4.0.0-alpha.14",
"@storybook/components": "4.0.0-alpha.14",
"@storybook/core-events": "4.0.0-alpha.14",
"babel-runtime": "^6.26.0",
"emotion": "^9.1.3",
"global": "^4.3.2",
"lodash.debounce": "^4.0.8",
"prop-types": "^15.6.1",
"react-emotion": "^9.1.3",
"util-deprecate": "^1.0.2"

View File

@ -1,13 +1,11 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { document } from 'global';
import debounce from 'lodash.debounce';
import styled from 'react-emotion';
import { ActionBar, ActionButton, Button, Select, Field } from '@storybook/components';
import { resetViewport, viewportsTransformer } from './viewportInfo';
import { SelectViewport } from './SelectViewport';
import { RotateViewport } from './RotateViewport';
import {
SET_STORY_DEFAULT_VIEWPORT_EVENT_ID,
CONFIGURE_VIEWPORT_EVENT_ID,
@ -17,14 +15,13 @@ import {
DEFAULT_VIEWPORT,
} from '../../shared';
import { Button } from './styles';
const storybookIframe = 'storybook-preview-iframe';
const Container = styled('div')({
padding: 15,
width: '100%',
boxSizing: 'border-box',
});
Container.displayName = 'Container';
const getDefaultViewport = (viewports, candidateViewport) =>
candidateViewport in viewports ? candidateViewport : Object.keys(viewports)[0];
@ -32,35 +29,29 @@ const getDefaultViewport = (viewports, candidateViewport) =>
const getViewports = viewports =>
Object.keys(viewports).length > 0 ? viewports : INITIAL_VIEWPORTS;
const setStoryDefaultViewportWait = 100;
export class Panel extends Component {
static defaultOptions = {
viewports: INITIAL_VIEWPORTS,
defaultViewport: DEFAULT_VIEWPORT,
};
static propTypes = {
channel: PropTypes.shape({}).isRequired,
api: PropTypes.shape({}).isRequired,
active: PropTypes.bool.isRequired,
api: PropTypes.shape({
selectStory: PropTypes.func.isRequired,
}).isRequired,
channel: PropTypes.shape({
on: PropTypes.func,
emit: PropTypes.func,
removeListener: PropTypes.func,
}).isRequired,
};
constructor(props, context) {
super(props, context);
this.state = {
viewport: DEFAULT_VIEWPORT,
defaultViewport: DEFAULT_VIEWPORT,
viewports: viewportsTransformer(INITIAL_VIEWPORTS),
isLandscape: false,
};
this.previousViewport = DEFAULT_VIEWPORT;
this.setStoryDefaultViewport = debounce(
this.setStoryDefaultViewport,
setStoryDefaultViewportWait
);
}
state = {
viewport: DEFAULT_VIEWPORT,
defaultViewport: DEFAULT_VIEWPORT,
viewports: viewportsTransformer(INITIAL_VIEWPORTS),
isLandscape: false,
};
componentDidMount() {
const { channel, api } = this.props;
@ -113,6 +104,7 @@ export class Panel extends Component {
};
iframe = undefined;
previousViewport = DEFAULT_VIEWPORT;
changeViewport = viewport => {
const { viewport: previousViewport } = this.state;
@ -180,28 +172,39 @@ export class Panel extends Component {
viewport,
viewports,
} = this.state;
const { active } = this.props;
const disableDefault = viewport === storyDefaultViewport;
const isResponsive = viewport === storyDefaultViewport;
return (
return active ? (
<Container>
<SelectViewport
viewports={viewports}
defaultViewport={storyDefaultViewport}
activeViewport={viewport}
onChange={e => this.changeViewport(e.target.value)}
/>
<Field label="Device">
<Select value={viewport} onChange={e => this.changeViewport(e.target.value)} size="flex">
{Object.entries(viewports).map(([key, { name }]) => (
<option value={key} key={key}>
{key === defaultViewport ? `${name} (Default)` : name}
</option>
))}
</Select>
</Field>
<RotateViewport
onClick={this.toggleLandscape}
disabled={disableDefault}
active={isLandscape}
/>
{!isResponsive ? (
<Field label="Rotate">
<Button onClick={this.toggleLandscape} active={isLandscape} size="flex">
{isLandscape ? 'rotate to portrait' : 'rotate to landscape'}
</Button>
</Field>
) : null}
<Button onClick={() => this.changeViewport(storyDefaultViewport)} disabled={disableDefault}>
Reset Viewport
</Button>
<ActionBar>
<ActionButton
onClick={() => this.changeViewport(storyDefaultViewport)}
disabled={isResponsive}
>
RESET
</ActionButton>
</ActionBar>
</Container>
);
) : null;
}
}

View File

@ -1,24 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Label, Row, Button } from './styles';
export const RotateViewport = ({ active, ...props }) => (
<Row>
<Label htmlFor="rotate">Rotate</Label>
<Button id="rotate" {...props}>
{active ? 'Vertical' : 'Landscape'}
</Button>
</Row>
);
RotateViewport.propTypes = {
disabled: PropTypes.bool,
onClick: PropTypes.func.isRequired,
active: PropTypes.bool,
};
RotateViewport.defaultProps = {
disabled: true,
active: false,
};

View File

@ -1,26 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Label, Row, Select } from './styles';
export function SelectViewport({ viewports, defaultViewport, activeViewport, onChange }) {
return (
<Row>
<Label htmlFor="device">Device</Label>
<Select id="device" value={activeViewport} onChange={onChange}>
{Object.entries(viewports).map(([key, { name }]) => (
<option value={key} key={key}>
{key === defaultViewport ? `(Default) ${name}` : name}
</option>
))}
</Select>
</Row>
);
}
SelectViewport.propTypes = {
onChange: PropTypes.func.isRequired,
activeViewport: PropTypes.string.isRequired,
viewports: PropTypes.shape({}).isRequired,
defaultViewport: PropTypes.string.isRequired,
};

View File

@ -1,36 +0,0 @@
import styled from 'react-emotion';
export const Row = styled('div')({
width: '100%',
display: 'flex',
marginBottom: 15,
});
export const Label = styled('label')({
width: 80,
marginRight: 15,
});
const actionColor = 'rgb(247, 247, 247)';
const basebutton = {
color: 'rgb(85, 85, 85)',
width: '100%',
border: `1px solid ${actionColor}`,
backgroundColor: actionColor,
borderRadius: 4,
padding: 10,
};
export const Button = styled('button')(
basebutton,
({ disabled }) =>
disabled
? {
opacity: '0.5',
cursor: 'not-allowed',
}
: {}
);
export const Select = styled('select')(basebutton);

View File

@ -2,6 +2,8 @@ import React from 'react';
import { shallow } from 'enzyme';
import { document } from 'global';
import { ActionButton, Select } from '@storybook/components';
import { Panel } from '../Panel';
import { resetViewport, viewportsTransformer } from '../viewportInfo';
import { DEFAULT_VIEWPORT, INITIAL_VIEWPORTS } from '../../../shared';
@ -9,8 +11,6 @@ import { DEFAULT_VIEWPORT, INITIAL_VIEWPORTS } from '../../../shared';
const initialViewportAt = index => Object.keys(INITIAL_VIEWPORTS)[index];
const transformedInitialViewports = viewportsTransformer(INITIAL_VIEWPORTS);
jest.mock('lodash.debounce', () => jest.fn(fn => fn));
describe('Viewport/Panel', () => {
const props = {
channel: {
@ -20,7 +20,9 @@ describe('Viewport/Panel', () => {
},
api: {
onStory: jest.fn(),
selectStory: jest.fn(),
},
active: true,
};
let subject;
@ -324,16 +326,18 @@ describe('Viewport/Panel', () => {
beforeEach(() => {
subject.instance().changeViewport = jest.fn();
resetBtn = subject.find('Styled(button)');
resetBtn = subject.find(ActionButton);
});
it('enables the reset button if not default', () => {
subject.setState({ viewport: 'responsive' });
resetBtn = subject.find('Styled(button)');
resetBtn = subject.find(ActionButton);
expect(resetBtn).toHaveProp('disabled', true);
subject.setState({ viewport: 'iphone6' });
resetBtn = subject.find('Styled(button)');
resetBtn = subject.find(ActionButton);
expect(resetBtn).toHaveProp('disabled', false);
});
@ -347,32 +351,16 @@ describe('Viewport/Panel', () => {
let select;
beforeEach(() => {
select = subject.find('SelectViewport');
select = subject.find(Select);
subject.instance().changeViewport = jest.fn();
});
it('passes the activeViewport', () => {
expect(select.props()).toEqual(
expect.objectContaining({
activeViewport: DEFAULT_VIEWPORT,
})
);
it('passes the value', () => {
expect(select.props().value).toEqual('responsive');
});
it('passes the defaultViewport', () => {
expect(select.props()).toEqual(
expect.objectContaining({
defaultViewport: DEFAULT_VIEWPORT,
})
);
});
it('passes the INITIAL_VIEWPORTS', () => {
expect(select.props()).toEqual(
expect.objectContaining({
viewports: transformedInitialViewports,
})
);
it('passes the children', () => {
expect(select.props().children).toHaveLength(8);
});
it('onChange it updates the viewport', () => {
@ -386,34 +374,21 @@ describe('Viewport/Panel', () => {
let toggle;
beforeEach(() => {
toggle = subject.find('RotateViewport');
toggle = subject.find('Field[label="Rotate"]');
jest.spyOn(subject.instance(), 'toggleLandscape');
subject.instance().forceUpdate();
});
it('passes the active prop based on the state of the panel', () => {
expect(toggle.props().active).toEqual(subject.state('isLandscape'));
it('renders viewport is not default', () => {
subject.setState({ viewport: 'iphone6' });
toggle = subject.find({ label: 'Rotate' });
expect(toggle).toExist();
});
describe('is on the default viewport', () => {
beforeEach(() => {
subject.setState({ viewport: DEFAULT_VIEWPORT });
});
it('sets the disabled property', () => {
expect(toggle.props().disabled).toEqual(true);
});
});
describe('is on a non-default viewport', () => {
beforeEach(() => {
subject.setState({ viewport: 'iphone6' });
toggle = subject.find('RotateViewport');
});
it('the disabled property is false', () => {
expect(toggle.props().disabled).toEqual(false);
});
it('hidden the viewport is default', () => {
subject.setState({ viewport: DEFAULT_VIEWPORT });
toggle = subject.find({ label: 'Rotate' });
expect(toggle).not.toExist();
});
});
});

View File

@ -1,51 +0,0 @@
import React from 'react';
import { shallow } from 'enzyme';
import { RotateViewport } from '../RotateViewport';
const setup = addition => {
const props = {
onClick: jest.fn(),
disabled: false,
...addition,
};
const result = shallow(<RotateViewport {...props} />);
return { result, props };
};
describe('Viewport/RotateViewport', () => {
it('renders correctly', () => {
const { result } = setup();
expect(result).toMatchSnapshot();
});
it('has a click handler set via props', () => {
const { result, props } = setup();
const button = result.find('Styled(button)');
expect(button).toHaveProp('onClick');
button.simulate('click');
expect(props.onClick).toHaveBeenCalled();
});
it('renders the correctly if not-disabled', () => {
const { result } = setup({ disabled: false });
expect(result.find('Styled(button)')).toHaveProp('disabled', false);
});
it('renders the correctly if disabled', () => {
const { result } = setup({ disabled: true });
expect(result.find('Styled(button)')).toHaveProp('disabled', true);
});
it('renders the correctly if not-active', () => {
const { result } = setup({ active: false });
expect(result.html()).toContain('Landscape');
});
it('renders the correctly if active', () => {
const { result } = setup({ active: true });
expect(result.html()).toContain('Vertical');
});
});

View File

@ -1,44 +0,0 @@
import React from 'react';
import { shallow } from 'enzyme';
import { SelectViewport } from '../SelectViewport';
import { INITIAL_VIEWPORTS, DEFAULT_VIEWPORT } from '../../../shared';
const setup = () => {
const props = {
onChange: jest.fn(),
activeViewport: DEFAULT_VIEWPORT,
viewports: INITIAL_VIEWPORTS,
defaultViewport: DEFAULT_VIEWPORT,
};
return { props, result: shallow(<SelectViewport {...props} />) };
};
describe('Viewport/SelectViewport', () => {
it('is correctly rendered', () => {
const { result } = setup();
expect(result).toMatchSnapshot();
});
it('has a default option first', () => {
const { result } = setup();
expect(result.find('Styled(select)').props().value).toEqual(DEFAULT_VIEWPORT);
});
it('has at least 1 option', () => {
const viewportKeys = Object.keys(INITIAL_VIEWPORTS);
expect(viewportKeys.length).toBeGreaterThan(0);
});
const viewportKeys = Object.keys(INITIAL_VIEWPORTS);
const { result } = setup();
viewportKeys.forEach(key => {
const { name } = INITIAL_VIEWPORTS[key];
const expectedText = key === DEFAULT_VIEWPORT ? `(Default) ${name}` : name;
it(`renders an option for ${name}`, () => {
const option = result.find(`option[value="${key}"]`);
expect(option.text()).toEqual(expectedText);
});
});
});

View File

@ -1,18 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Viewport/RotateViewport renders correctly 1`] = `
<Styled(div)>
<Styled(label)
htmlFor="rotate"
>
Rotate
</Styled(label)>
<Styled(button)
disabled={false}
id="rotate"
onClick={[MockFunction]}
>
Landscape
</Styled(button)>
</Styled(div)>
`;

View File

@ -1,65 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Viewport/SelectViewport is correctly rendered 1`] = `
<Styled(div)>
<Styled(label)
htmlFor="device"
>
Device
</Styled(label)>
<Styled(select)
id="device"
onChange={[MockFunction]}
value="responsive"
>
<option
key="responsive"
value="responsive"
>
(Default) Responsive
</option>
<option
key="iphone5"
value="iphone5"
>
iPhone 5
</option>
<option
key="iphone6"
value="iphone6"
>
iPhone 6
</option>
<option
key="iphone6p"
value="iphone6p"
>
iPhone 6 Plus
</option>
<option
key="ipad"
value="ipad"
>
iPad
</option>
<option
key="galaxys5"
value="galaxys5"
>
Galaxy S5
</option>
<option
key="nexus5x"
value="nexus5x"
>
Nexus 5X
</option>
<option
key="nexus6p"
value="nexus6p"
>
Nexus 6P
</option>
</Styled(select)>
</Styled(div)>
`;

View File

@ -7,12 +7,10 @@ import { ADDON_ID, PANEL_ID } from '../shared';
const addChannel = api => {
const channel = addons.getChannel();
addons.addPanel(PANEL_ID, {
title: 'Viewport',
render() {
return <Panel channel={channel} api={api} />;
},
// eslint-disable-next-line react/prop-types
render: ({ active }) => <Panel channel={channel} api={api} active={active} />,
});
};

View File

@ -33,6 +33,7 @@ const applyViewportOptions = (options = {}) => {
const withViewport = makeDecorator({
name: 'withViewport',
parameterName: 'viewport',
allowDeprecatedUsage: true,
wrapper: (getStory, context, { options, parameters }) => {
const storyOptions = parameters || options;
const viewportOptions =

View File

@ -1,3 +1,4 @@
#!/usr/bin/env node
process.env.NODE_ENV = process.env.NODE_ENV || 'production';
require('../dist/server/build');

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/angular",
"version": "4.0.0-alpha.10",
"version": "4.0.0-alpha.14",
"description": "Storybook for Angular: Develop Angular Components in isolation with Hot Reloading.",
"homepage": "https://github.com/storybooks/storybook/tree/master/apps/angular",
"bugs": {
@ -22,8 +22,8 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/core": "4.0.0-alpha.10",
"@storybook/node-logger": "4.0.0-alpha.10",
"@storybook/core": "4.0.0-alpha.14",
"@storybook/node-logger": "4.0.0-alpha.14",
"angular2-template-loader": "^0.6.2",
"babel-runtime": "^6.23.0",
"core-js": "^2.5.7",

View File

@ -7,16 +7,16 @@ import { Component, Input, Output, EventEmitter } from '@angular/core';
`,
styles: [
`
button {
border: 1px solid #eee;
border-radius: 3px;
background-color: #FFFFFF;
cursor: pointer;
font-size: 15px;
padding: 3px 10px;
margin: 10px;
}
`,
button {
border: 1px solid #eee;
border-radius: 3px;
background-color: #ffffff;
cursor: pointer;
font-size: 15px;
padding: 3px 10px;
margin: 10px;
}
`,
],
})
export default class ButtonComponent {

View File

@ -46,34 +46,34 @@ import { Component, Output, EventEmitter } from '@angular/core';
`,
styles: [
`
main {
margin: 15px;
max-width: 600;
lineHeight: 1.4;
fontFamily: "Helvetica Neue", Helvetica, "Segoe UI", Arial, freesans, sans-serif;
}
main {
margin: 15px;
max-width: 600;
line-height: 1.4;
fontfamily: 'Helvetica Neue', Helvetica, 'Segoe UI', Arial, freesans, sans-serif;
}
.note {
opacity: 0.5,
}
.note {
opacity: 0.5;
}
.inline-code {
font-size: 15px;
fontWeight: 600;
padding: 2px 5px;
border: 1px solid #eae9e9;
border-radius: 4px;
background-color: #f3f2f2;
color: #3a3a3a;
}
.inline-code {
font-size: 15px;
font-weight: 600;
padding: 2px 5px;
border: 1px solid #eae9e9;
border-radius: 4px;
background-color: #f3f2f2;
color: #3a3a3a;
}
a {
color: #1474f3;
text-decoration: none;
border-bottom: 1px solid #1474f3;
padding-bottom: 2px;
}
`,
a {
color: #1474f3;
text-decoration: none;
border-bottom: 1px solid #1474f3;
padding-bottom: 2px;
}
`,
],
})
export default class WelcomeComponent {

View File

@ -1,5 +1,4 @@
import { buildStatic } from '@storybook/core/server';
import options from './options';
buildStatic(options);

View File

@ -1,3 +1,4 @@
#!/usr/bin/env node
process.env.NODE_ENV = process.env.NODE_ENV || 'production';
require('../dist/server/build');

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/html",
"version": "4.0.0-alpha.10",
"version": "4.0.0-alpha.14",
"description": "Storybook for HTML: View HTML snippets in isolation with Hot Reloading.",
"homepage": "https://github.com/storybooks/storybook/tree/master/apps/html",
"bugs": {
@ -21,7 +21,7 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/core": "4.0.0-alpha.10",
"@storybook/core": "4.0.0-alpha.14",
"common-tags": "^1.8.0",
"global": "^4.3.2",
"html-loader": "^0.5.5",

View File

@ -1,5 +1,4 @@
import { buildStatic } from '@storybook/core/server';
import options from './options';
buildStatic(options);

View File

@ -1,3 +1,4 @@
#!/usr/bin/env node
process.env.NODE_ENV = process.env.NODE_ENV || 'production';
require('../dist/server/build');

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/marko",
"version": "4.0.0-alpha.10",
"version": "4.0.0-alpha.14",
"description": "Storybook for Marko: Develop Marko Component in isolation with Hot Reloading.",
"homepage": "https://github.com/storybooks/storybook/tree/master/app/marko",
"bugs": {
@ -22,7 +22,7 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/core": "4.0.0-alpha.10",
"@storybook/core": "4.0.0-alpha.14",
"common-tags": "^1.8.0",
"global": "^4.3.2",
"marko-loader": "^1.3.3",
@ -31,7 +31,7 @@
"react-dom": "^16.4.0"
},
"peerDependencies": {
"marko": "^4",
"marko": "^4.10.0",
"marko-widgets": "^7.0.1"
}
}

View File

@ -1,5 +1,4 @@
import { buildStatic } from '@storybook/core/server';
import options from './options';
buildStatic(options);

View File

@ -1,3 +1,4 @@
#!/usr/bin/env node
process.env.NODE_ENV = process.env.NODE_ENV || 'production';
require('../dist/server/build');

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/mithril",
"version": "4.0.0-alpha.10",
"version": "4.0.0-alpha.14",
"description": "Storybook for Mithril: Develop Mithril Component in isolation.",
"homepage": "https://github.com/storybooks/storybook/tree/master/app/mithril",
"bugs": {
@ -22,7 +22,7 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/core": "4.0.0-alpha.10",
"@storybook/core": "4.0.0-alpha.14",
"common-tags": "^1.8.0",
"global": "^4.3.2",
"react": "^16.4.0",

View File

@ -1,5 +1,4 @@
import { buildStatic } from '@storybook/core/server';
import options from './options';
buildStatic(options);

View File

@ -1,3 +1,4 @@
#!/usr/bin/env node
process.env.NODE_ENV = process.env.NODE_ENV || 'production';
require('../dist/server/build');

View File

@ -1,6 +1,6 @@
{
"name": "@storybook/polymer",
"version": "4.0.0-alpha.10",
"version": "4.0.0-alpha.14",
"description": "Storybook for Polymer: Develop Polymer components in isolation with Hot Reloading.",
"homepage": "https://github.com/storybooks/storybook/tree/master/apps/polymer",
"bugs": {
@ -21,7 +21,7 @@
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/core": "4.0.0-alpha.10",
"@storybook/core": "4.0.0-alpha.14",
"@webcomponents/webcomponentsjs": "^1.2.0",
"babel-polyfill": "^6.26.0",
"common-tags": "^1.8.0",

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