mirror of
https://github.com/storybookjs/storybook.git
synced 2025-04-07 07:21:17 +08:00
Merge branch 'tech/overhaul-ui' of https://github.com/storybooks/storybook into tech/overhaul-ui
This commit is contained in:
commit
8674a24484
164
addons/a11y/src/components/ColorBlindness.js
Normal file
164
addons/a11y/src/components/ColorBlindness.js
Normal file
@ -0,0 +1,164 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import { document } from 'global';
|
||||
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { Popout, Item, Icons, Icon, IconButton, Title, List } from '@storybook/components';
|
||||
|
||||
const storybookIframe = 'storybook-preview-iframe';
|
||||
|
||||
const ColorIcon = styled.span(
|
||||
{
|
||||
background: 'linear-gradient(to right, #F44336, #FF9800, #FFEB3B, #8BC34A, #2196F3, #9C27B0)',
|
||||
},
|
||||
({ filter }) => ({
|
||||
filter: filter === 'mono' ? 'grayscale(100%)' : `url('#${filter}')`,
|
||||
})
|
||||
);
|
||||
|
||||
const Hidden = styled.div(() => ({
|
||||
display: 'none',
|
||||
}));
|
||||
|
||||
class ColorBlindness extends Component {
|
||||
state = {
|
||||
filter: false,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.iframe = document.getElementById(storybookIframe);
|
||||
}
|
||||
|
||||
setFilter = filter => {
|
||||
if (!this.iframe) {
|
||||
throw new Error('Cannot find Storybook iframe');
|
||||
}
|
||||
|
||||
this.iframe.style.filter = filter === 'mono' ? 'grayscale(100%)' : `url('#${filter}')`;
|
||||
|
||||
this.setState({
|
||||
filter,
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { filter } = this.state;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Hidden>
|
||||
<svg key="svg">
|
||||
<defs>
|
||||
<filter id="protanopia">
|
||||
<feColorMatrix
|
||||
in="SourceGraphic"
|
||||
type="matrix"
|
||||
values="0.567, 0.433, 0, 0, 0 0.558, 0.442, 0, 0, 0 0, 0.242, 0.758, 0, 0 0, 0, 0, 1, 0"
|
||||
/>
|
||||
</filter>
|
||||
<filter id="protanomaly">
|
||||
<feColorMatrix
|
||||
in="SourceGraphic"
|
||||
type="matrix"
|
||||
values="0.817, 0.183, 0, 0, 0 0.333, 0.667, 0, 0, 0 0, 0.125, 0.875, 0, 0 0, 0, 0, 1, 0"
|
||||
/>
|
||||
</filter>
|
||||
<filter id="deuteranopia">
|
||||
<feColorMatrix
|
||||
in="SourceGraphic"
|
||||
type="matrix"
|
||||
values="0.625, 0.375, 0, 0, 0 0.7, 0.3, 0, 0, 0 0, 0.3, 0.7, 0, 0 0, 0, 0, 1, 0"
|
||||
/>
|
||||
</filter>
|
||||
<filter id="deuteranomaly">
|
||||
<feColorMatrix
|
||||
in="SourceGraphic"
|
||||
type="matrix"
|
||||
values="0.8, 0.2, 0, 0, 0 0.258, 0.742, 0, 0, 0 0, 0.142, 0.858, 0, 0 0, 0, 0, 1, 0"
|
||||
/>
|
||||
</filter>
|
||||
<filter id="tritanopia">
|
||||
<feColorMatrix
|
||||
in="SourceGraphic"
|
||||
type="matrix"
|
||||
values="0.95, 0.05, 0, 0, 0 0, 0.433, 0.567, 0, 0 0, 0.475, 0.525, 0, 0 0, 0, 0, 1, 0"
|
||||
/>
|
||||
</filter>
|
||||
<filter id="tritanomaly">
|
||||
<feColorMatrix
|
||||
in="SourceGraphic"
|
||||
type="matrix"
|
||||
values="0.967, 0.033, 0, 0, 0 0, 0.733, 0.267, 0, 0 0, 0.183, 0.817, 0, 0 0, 0, 0, 1, 0"
|
||||
/>
|
||||
</filter>
|
||||
<filter id="achromatopsia">
|
||||
<feColorMatrix
|
||||
in="SourceGraphic"
|
||||
type="matrix"
|
||||
values="0.299, 0.587, 0.114, 0, 0 0.299, 0.587, 0.114, 0, 0 0.299, 0.587, 0.114, 0, 0 0, 0, 0, 1, 0"
|
||||
/>
|
||||
</filter>
|
||||
<filter id="achromatomaly">
|
||||
<feColorMatrix
|
||||
in="SourceGraphic"
|
||||
type="matrix"
|
||||
values="0.618, 0.320, 0.062, 0, 0 0.163, 0.775, 0.062, 0, 0 0.163, 0.320, 0.516, 0, 0 0, 0, 0, 1, 0"
|
||||
/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
</Hidden>
|
||||
<Popout key="filters">
|
||||
<IconButton key="filter" active={!!filter} title="Color Blindness Emulation">
|
||||
<Icons icon="mirror" />
|
||||
</IconButton>
|
||||
{({ hide }) => (
|
||||
<List>
|
||||
{[
|
||||
'protanopia',
|
||||
'protanomaly',
|
||||
'deuteranopia',
|
||||
'deuteranomaly',
|
||||
'tritanopia',
|
||||
'tritanomaly',
|
||||
'achromatopsia',
|
||||
'achromatomaly',
|
||||
].map(i => (
|
||||
<Item
|
||||
key={i}
|
||||
onClick={() => {
|
||||
this.setFilter(filter === i ? null : i);
|
||||
hide();
|
||||
}}
|
||||
>
|
||||
<Icon type={<ColorIcon filter={i} />} />
|
||||
<Title>{i}</Title>
|
||||
</Item>
|
||||
))}
|
||||
<Item
|
||||
onClick={() => {
|
||||
this.setFilter(filter === 'mono' ? null : 'mono');
|
||||
hide();
|
||||
}}
|
||||
>
|
||||
<Icon type={<ColorIcon filter="mono" />} />
|
||||
<Title>mono</Title>
|
||||
</Item>
|
||||
<Item
|
||||
onClick={() => {
|
||||
this.setFilter(null);
|
||||
hide();
|
||||
}}
|
||||
>
|
||||
<Icon type={<ColorIcon />} />
|
||||
<Title>Off</Title>
|
||||
</Item>
|
||||
</List>
|
||||
)}
|
||||
</Popout>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ColorBlindness;
|
@ -1,13 +1,22 @@
|
||||
import React from 'react';
|
||||
import addons from '@storybook/addons';
|
||||
import addons, { types } from '@storybook/addons';
|
||||
|
||||
import Panel from './components/Panel';
|
||||
import ColorBlindness from './components/ColorBlindness';
|
||||
|
||||
import { ADDON_ID, PANEL_ID } from './shared';
|
||||
|
||||
function init() {
|
||||
addons.register(ADDON_ID, api => {
|
||||
const channel = addons.getChannel();
|
||||
addons.addPanel(PANEL_ID, {
|
||||
|
||||
addons.add(PANEL_ID, {
|
||||
type: types.TOOL,
|
||||
render: () => <ColorBlindness />,
|
||||
});
|
||||
|
||||
addons.add(PANEL_ID, {
|
||||
type: types.PANEL,
|
||||
title: 'Accessibility',
|
||||
// eslint-disable-next-line react/prop-types
|
||||
render: ({ active }) => <Panel channel={channel} api={api} active={active} />,
|
||||
|
@ -60,7 +60,7 @@ const Instructions = () => (
|
||||
</Wrapper>
|
||||
);
|
||||
|
||||
export default class BackgroundPanel extends Component {
|
||||
export default class Panel extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
@ -138,7 +138,7 @@ export default class BackgroundPanel extends Component {
|
||||
);
|
||||
}
|
||||
}
|
||||
BackgroundPanel.propTypes = {
|
||||
Panel.propTypes = {
|
||||
active: PropTypes.bool.isRequired,
|
||||
api: PropTypes.shape({
|
||||
getQueryParam: PropTypes.func,
|
||||
@ -150,6 +150,6 @@ BackgroundPanel.propTypes = {
|
||||
removeListener: PropTypes.func,
|
||||
}),
|
||||
};
|
||||
BackgroundPanel.defaultProps = {
|
||||
Panel.defaultProps = {
|
||||
channel: undefined,
|
||||
};
|
132
addons/backgrounds/src/Tool.js
Normal file
132
addons/backgrounds/src/Tool.js
Normal file
@ -0,0 +1,132 @@
|
||||
import { document } from 'global';
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { Popout, Item, Icons, Icon, IconButton, Title, List } from '@storybook/components';
|
||||
|
||||
import Events from './constants';
|
||||
|
||||
const storybookIframe = 'storybook-preview-background';
|
||||
const style = {
|
||||
iframe: {
|
||||
transition: 'background 0.25s ease-in-out',
|
||||
},
|
||||
};
|
||||
|
||||
const ColorIcon = styled.span(({ background }) => ({
|
||||
background,
|
||||
}));
|
||||
|
||||
const defaultBackground = {
|
||||
name: 'default',
|
||||
value: 'transparent',
|
||||
};
|
||||
|
||||
export default class Tool extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = { backgrounds: [] };
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { api, channel } = this.props;
|
||||
channel.on(Events.SET, data => {
|
||||
this.iframe = document.getElementById(storybookIframe);
|
||||
|
||||
if (!this.iframe) {
|
||||
return;
|
||||
// throw new Error('Cannot find Storybook iframe');
|
||||
}
|
||||
|
||||
Object.keys(style.iframe).forEach(prop => {
|
||||
this.iframe.style[prop] = style.iframe[prop];
|
||||
});
|
||||
|
||||
const backgrounds = [...data];
|
||||
|
||||
this.setState({ backgrounds });
|
||||
const current = api.getQueryParam('background');
|
||||
const defaultOrFirst = backgrounds.find(x => x.default) || backgrounds[0];
|
||||
|
||||
if (current && backgrounds.find(bg => bg.value === current)) {
|
||||
this.updateIframe(current);
|
||||
} else if (defaultOrFirst) {
|
||||
this.updateIframe(defaultOrFirst.value);
|
||||
api.setQueryParams({ background: defaultOrFirst.value });
|
||||
}
|
||||
});
|
||||
|
||||
channel.on(Events.UNSET, () => {
|
||||
if (!this.iframe) {
|
||||
return;
|
||||
// throw new Error('Cannot find Storybook iframe');
|
||||
}
|
||||
this.setState({ backgrounds: [] });
|
||||
this.updateIframe('none');
|
||||
});
|
||||
}
|
||||
|
||||
setBackgroundFromSwatch = background => {
|
||||
const { api } = this.props;
|
||||
this.updateIframe(background);
|
||||
api.setQueryParams({ background });
|
||||
};
|
||||
|
||||
updateIframe(background) {
|
||||
this.iframe.style.background = background;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { backgrounds = [] } = this.state;
|
||||
|
||||
if (!backgrounds.length) {
|
||||
// we should just disable the button
|
||||
}
|
||||
|
||||
const hasDefault = backgrounds.filter(x => x.default).length;
|
||||
if (!hasDefault) backgrounds.push(defaultBackground);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Popout key="backgrounds">
|
||||
<IconButton key="background" title="Backgrounds">
|
||||
<Icons icon="photo" />
|
||||
</IconButton>
|
||||
{({ hide }) => (
|
||||
<List>
|
||||
{backgrounds.map(({ value, name }) => (
|
||||
<Item
|
||||
key={`${name} ${value}`}
|
||||
onClick={() => {
|
||||
this.setBackgroundFromSwatch(value);
|
||||
|
||||
hide();
|
||||
}}
|
||||
>
|
||||
<Icon type={<ColorIcon background={value} />} />
|
||||
<Title>{value}</Title>
|
||||
</Item>
|
||||
))}
|
||||
</List>
|
||||
)}
|
||||
</Popout>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
Tool.propTypes = {
|
||||
api: PropTypes.shape({
|
||||
getQueryParam: PropTypes.func,
|
||||
setQueryParams: PropTypes.func,
|
||||
}).isRequired,
|
||||
channel: PropTypes.shape({
|
||||
emit: PropTypes.func,
|
||||
on: PropTypes.func,
|
||||
removeListener: PropTypes.func,
|
||||
}),
|
||||
};
|
||||
Tool.defaultProps = {
|
||||
channel: undefined,
|
||||
};
|
@ -2,7 +2,7 @@ import React from 'react';
|
||||
import { shallow, mount } from 'enzyme';
|
||||
import EventEmitter from 'events';
|
||||
|
||||
import BackgroundPanel from '../BackgroundPanel';
|
||||
import Panel from '../Panel';
|
||||
import Events from '../constants';
|
||||
|
||||
const backgrounds = [
|
||||
@ -30,26 +30,26 @@ jest.mock('global', () => ({
|
||||
|
||||
describe('Background Panel', () => {
|
||||
it('should exist', () => {
|
||||
const backgroundPanel = shallow(<BackgroundPanel channel={channel} api={mockedApi} active />);
|
||||
const BackgroundPanel = shallow(<Panel channel={channel} api={mockedApi} active />);
|
||||
|
||||
expect(backgroundPanel).toBeDefined();
|
||||
expect(BackgroundPanel).toBeDefined();
|
||||
});
|
||||
|
||||
it('should have a default background value of transparent', () => {
|
||||
const backgroundPanel = shallow(<BackgroundPanel channel={channel} api={mockedApi} active />);
|
||||
const BackgroundPanel = shallow(<Panel channel={channel} api={mockedApi} active />);
|
||||
|
||||
expect(backgroundPanel.state().backgrounds).toHaveLength(0);
|
||||
expect(BackgroundPanel.state().backgrounds).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should show setup instructions if no colors provided', () => {
|
||||
const backgroundPanel = shallow(<BackgroundPanel channel={channel} api={mockedApi} active />);
|
||||
const BackgroundPanel = shallow(<Panel channel={channel} api={mockedApi} active />);
|
||||
|
||||
expect(backgroundPanel.html().match(/Setup Instructions/gim).length).toBeGreaterThan(0);
|
||||
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} active />);
|
||||
mount(<Panel 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} active />);
|
||||
mount(<Panel channel={SpiedChannel} api={mockedApi} active />);
|
||||
SpiedChannel.emit(Events.UNSET, []);
|
||||
|
||||
expect(mockedApi.setQueryParams).not.toHaveBeenCalled();
|
||||
@ -65,36 +65,30 @@ 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} active />
|
||||
);
|
||||
const BackgroundPanel = mount(<Panel channel={SpiedChannel} api={mockedApi} active />);
|
||||
SpiedChannel.emit(Events.SET, backgrounds);
|
||||
|
||||
expect(backgroundPanel.state('backgrounds')).toEqual(backgrounds);
|
||||
expect(BackgroundPanel.state('backgrounds')).toEqual(backgrounds);
|
||||
});
|
||||
|
||||
it('should allow setting a default swatch', () => {
|
||||
const SpiedChannel = new EventEmitter();
|
||||
const backgroundPanel = mount(
|
||||
<BackgroundPanel channel={SpiedChannel} api={mockedApi} active />
|
||||
);
|
||||
const BackgroundPanel = mount(<Panel channel={SpiedChannel} api={mockedApi} active />);
|
||||
const [head, ...tail] = backgrounds;
|
||||
const localBgs = [{ ...head, default: true }, ...tail];
|
||||
SpiedChannel.emit(Events.SET, localBgs);
|
||||
|
||||
expect(backgroundPanel.state('backgrounds')).toEqual(localBgs);
|
||||
backgroundPanel.setState({ backgrounds: localBgs }); // force re-render
|
||||
expect(BackgroundPanel.state('backgrounds')).toEqual(localBgs);
|
||||
BackgroundPanel.setState({ backgrounds: localBgs }); // force re-render
|
||||
|
||||
// check to make sure the default bg was added
|
||||
const headings = backgroundPanel.find('h4');
|
||||
const headings = BackgroundPanel.find('h4');
|
||||
expect(headings).toHaveLength(8);
|
||||
});
|
||||
|
||||
it('should allow the default swatch become the background color', () => {
|
||||
const SpiedChannel = new EventEmitter();
|
||||
const backgroundPanel = mount(
|
||||
<BackgroundPanel channel={SpiedChannel} api={mockedApi} active />
|
||||
);
|
||||
const BackgroundPanel = mount(<Panel channel={SpiedChannel} api={mockedApi} active />);
|
||||
const [head, second, ...tail] = backgrounds;
|
||||
const localBgs = [head, { ...second, default: true }, ...tail];
|
||||
SpiedChannel.on('background', bg => {
|
||||
@ -102,41 +96,36 @@ describe('Background Panel', () => {
|
||||
});
|
||||
SpiedChannel.emit(Events.SET, localBgs);
|
||||
|
||||
expect(backgroundPanel.state('backgrounds')).toEqual(localBgs);
|
||||
backgroundPanel.setState({ backgrounds: localBgs }); // force re-render
|
||||
expect(BackgroundPanel.state('backgrounds')).toEqual(localBgs);
|
||||
BackgroundPanel.setState({ backgrounds: localBgs }); // force re-render
|
||||
|
||||
// check to make sure the default bg was added
|
||||
const headings = backgroundPanel.find('h4');
|
||||
const headings = BackgroundPanel.find('h4');
|
||||
expect(headings).toHaveLength(8);
|
||||
});
|
||||
|
||||
it('should unset all swatches on receiving the background-unset message', () => {
|
||||
const SpiedChannel = new EventEmitter();
|
||||
const backgroundPanel = mount(
|
||||
<BackgroundPanel channel={SpiedChannel} api={mockedApi} active />
|
||||
);
|
||||
const BackgroundPanel = mount(<Panel channel={SpiedChannel} api={mockedApi} active />);
|
||||
SpiedChannel.emit(Events.SET, backgrounds);
|
||||
|
||||
expect(backgroundPanel.state('backgrounds')).toEqual(backgrounds);
|
||||
backgroundPanel.setState({ backgrounds }); // force re-render
|
||||
expect(BackgroundPanel.state('backgrounds')).toEqual(backgrounds);
|
||||
BackgroundPanel.setState({ backgrounds }); // force re-render
|
||||
|
||||
SpiedChannel.emit(Events.UNSET);
|
||||
expect(backgroundPanel.state('backgrounds')).toHaveLength(0);
|
||||
expect(BackgroundPanel.state('backgrounds')).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should set iframe background', () => {
|
||||
const SpiedChannel = new EventEmitter();
|
||||
const backgroundPanel = mount(
|
||||
<BackgroundPanel channel={SpiedChannel} api={mockedApi} active />
|
||||
);
|
||||
backgroundPanel.setState({ backgrounds }); // force re-render
|
||||
const BackgroundPanel = mount(<Panel channel={SpiedChannel} api={mockedApi} active />);
|
||||
BackgroundPanel.setState({ backgrounds }); // force re-render
|
||||
|
||||
backgroundPanel
|
||||
.find('h4')
|
||||
BackgroundPanel.find('h4')
|
||||
.first()
|
||||
.simulate('click');
|
||||
|
||||
expect(backgroundPanel.instance().iframe.style).toMatchObject({
|
||||
expect(BackgroundPanel.instance().iframe.style).toMatchObject({
|
||||
background: backgrounds[0].value,
|
||||
});
|
||||
});
|
||||
|
@ -1,14 +1,26 @@
|
||||
import React from 'react';
|
||||
import addons from '@storybook/addons';
|
||||
import addons, { types } from '@storybook/addons';
|
||||
|
||||
import { ADDON_ID, PANEL_ID } from './constants';
|
||||
import BackgroundPanel from './BackgroundPanel';
|
||||
import Panel from './Panel';
|
||||
import Tool from './Tool';
|
||||
|
||||
addons.register(ADDON_ID, api => {
|
||||
const channel = addons.getChannel();
|
||||
addons.addPanel(PANEL_ID, {
|
||||
title: 'Backgrounds',
|
||||
// eslint-disable-next-line react/prop-types
|
||||
render: ({ active }) => <BackgroundPanel channel={channel} api={api} active={active} />,
|
||||
function init() {
|
||||
addons.register(ADDON_ID, api => {
|
||||
const channel = addons.getChannel();
|
||||
|
||||
addons.add(PANEL_ID, {
|
||||
type: types.TOOL,
|
||||
render: () => <Tool channel={channel} api={api} />,
|
||||
});
|
||||
|
||||
addons.add(PANEL_ID, {
|
||||
type: types.PANEL,
|
||||
title: 'Backgrounds',
|
||||
// eslint-disable-next-line react/prop-types
|
||||
render: ({ active }) => <Panel channel={channel} api={api} active={active} />,
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export { init };
|
||||
|
78
addons/notes/src/Panel.js
Normal file
78
addons/notes/src/Panel.js
Normal file
@ -0,0 +1,78 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import styled from '@emotion/styled';
|
||||
import { SyntaxHighlighter, Placeholder } from '@storybook/components';
|
||||
import Markdown from 'markdown-to-jsx';
|
||||
|
||||
const Panel = styled.div({
|
||||
padding: 10,
|
||||
boxSizing: 'border-box',
|
||||
width: '100%',
|
||||
});
|
||||
|
||||
export default class NotesPanel extends React.Component {
|
||||
state = {
|
||||
markdown: '',
|
||||
};
|
||||
|
||||
// use our SyntaxHighlighter component in place of a <code> element when
|
||||
// converting markdown to react elements
|
||||
markdownOpts = { overrides: { code: SyntaxHighlighter } };
|
||||
|
||||
componentDidMount() {
|
||||
this.mounted = true;
|
||||
const { channel, api } = this.props;
|
||||
|
||||
// Clear the current notes on every story change.
|
||||
this.stopListeningOnStory = api.onStory(() => {
|
||||
const { text } = this.state;
|
||||
if (this.mounted && text !== '') {
|
||||
this.onAddNotes('');
|
||||
}
|
||||
});
|
||||
channel.on('storybook/notes/add_notes', this.onAddNotes);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.mounted = false;
|
||||
const { channel } = this.props;
|
||||
|
||||
this.stopListeningOnStory();
|
||||
channel.removeListener('storybook/notes/add_notes', this.onAddNotes);
|
||||
}
|
||||
|
||||
onAddNotes = markdown => {
|
||||
this.setState({ markdown });
|
||||
};
|
||||
|
||||
render() {
|
||||
const { active } = this.props;
|
||||
const { markdown } = this.state;
|
||||
|
||||
if (!active) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return markdown ? (
|
||||
<Panel className="addon-notes-container">
|
||||
<Markdown options={this.markdownOpts}>{markdown}</Markdown>
|
||||
</Panel>
|
||||
) : (
|
||||
<Placeholder>There is no info/note</Placeholder>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
NotesPanel.propTypes = {
|
||||
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,
|
||||
};
|
@ -1,103 +1,25 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import styled from '@emotion/styled';
|
||||
import addons from '@storybook/addons';
|
||||
import { SyntaxHighlighter, Placeholder } from '@storybook/components';
|
||||
import Markdown from 'markdown-to-jsx';
|
||||
import addons, { types } from '@storybook/addons';
|
||||
import Panel from './Panel';
|
||||
import { ADDON_ID, PANEL_ID } from './shared';
|
||||
|
||||
const Panel = styled.div({
|
||||
padding: 10,
|
||||
boxSizing: 'border-box',
|
||||
width: '100%',
|
||||
});
|
||||
|
||||
export class NotesPanel extends React.Component {
|
||||
state = {
|
||||
markdown: '',
|
||||
};
|
||||
|
||||
// use our SyntaxHighlighter component in place of a <code> element when
|
||||
// converting markdown to react elements
|
||||
markdownOpts = { overrides: { code: SyntaxHighlighter } };
|
||||
|
||||
componentDidMount() {
|
||||
this.mounted = true;
|
||||
const { channel, api } = this.props;
|
||||
|
||||
// Clear the current notes on every story change.
|
||||
this.stopListeningOnStory = api.onStory(() => {
|
||||
const { text } = this.state;
|
||||
if (this.mounted && text !== '') {
|
||||
this.onAddNotes('');
|
||||
}
|
||||
});
|
||||
channel.on('storybook/notes/add_notes', this.onAddNotes);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.mounted = false;
|
||||
const { channel } = this.props;
|
||||
|
||||
this.stopListeningOnStory();
|
||||
channel.removeListener('storybook/notes/add_notes', this.onAddNotes);
|
||||
}
|
||||
|
||||
onAddNotes = markdown => {
|
||||
this.setState({ markdown });
|
||||
};
|
||||
|
||||
render() {
|
||||
const { active } = this.props;
|
||||
const { markdown } = this.state;
|
||||
|
||||
if (!active) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return markdown ? (
|
||||
<Panel className="addon-notes-container">
|
||||
<Markdown options={this.markdownOpts}>{markdown}</Markdown>
|
||||
</Panel>
|
||||
) : (
|
||||
<Placeholder>There is no info/note</Placeholder>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
NotesPanel.propTypes = {
|
||||
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,
|
||||
};
|
||||
|
||||
addons.register(ADDON_ID, api => {
|
||||
const channel = addons.getChannel();
|
||||
|
||||
// eslint-disable-next-line react/prop-types
|
||||
const render = ({ active }) => <NotesPanel channel={channel} api={api} active={active} />;
|
||||
const render = ({ active }) => <Panel channel={channel} api={api} active={active} />;
|
||||
const title = 'Notes';
|
||||
const type = 'tab';
|
||||
|
||||
if (addons.addMain) {
|
||||
addons.addMain(PANEL_ID, {
|
||||
type,
|
||||
title,
|
||||
route: '/info/',
|
||||
render,
|
||||
});
|
||||
} else {
|
||||
addons.addPanel(PANEL_ID, {
|
||||
title,
|
||||
render,
|
||||
});
|
||||
}
|
||||
addons.add(PANEL_ID, {
|
||||
type: types.TAB,
|
||||
title,
|
||||
route: '/info/',
|
||||
render,
|
||||
});
|
||||
|
||||
addons.add(PANEL_ID, {
|
||||
type: types.PANEL,
|
||||
title,
|
||||
route: '/info/',
|
||||
render,
|
||||
});
|
||||
});
|
||||
|
191
addons/viewport/src/manager/components/Tool.js
Normal file
191
addons/viewport/src/manager/components/Tool.js
Normal file
@ -0,0 +1,191 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { document } from 'global';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { Popout, Item, Icons, IconButton, Title, List } from '@storybook/components';
|
||||
|
||||
import { resetViewport, viewportsTransformer } from './viewportInfo';
|
||||
import {
|
||||
SET_STORY_DEFAULT_VIEWPORT_EVENT_ID,
|
||||
CONFIGURE_VIEWPORT_EVENT_ID,
|
||||
UPDATE_VIEWPORT_EVENT_ID,
|
||||
VIEWPORT_CHANGED_EVENT_ID,
|
||||
INITIAL_VIEWPORTS,
|
||||
DEFAULT_VIEWPORT,
|
||||
} from '../../shared';
|
||||
|
||||
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];
|
||||
|
||||
const getViewports = viewports =>
|
||||
Object.keys(viewports).length > 0 ? viewports : INITIAL_VIEWPORTS;
|
||||
|
||||
export default class ViewportTool extends Component {
|
||||
static defaultOptions = {
|
||||
viewports: INITIAL_VIEWPORTS,
|
||||
defaultViewport: DEFAULT_VIEWPORT,
|
||||
};
|
||||
|
||||
iframe = undefined;
|
||||
|
||||
previousViewport = DEFAULT_VIEWPORT;
|
||||
|
||||
static propTypes = {
|
||||
api: PropTypes.shape({
|
||||
selectStory: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
channel: PropTypes.shape({
|
||||
on: PropTypes.func,
|
||||
emit: PropTypes.func,
|
||||
removeListener: PropTypes.func,
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
state = {
|
||||
viewport: DEFAULT_VIEWPORT,
|
||||
defaultViewport: DEFAULT_VIEWPORT,
|
||||
viewports: viewportsTransformer(INITIAL_VIEWPORTS),
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.mounted = true;
|
||||
const { channel, api } = this.props;
|
||||
const { defaultViewport } = this.state;
|
||||
|
||||
channel.on(UPDATE_VIEWPORT_EVENT_ID, this.changeViewport);
|
||||
channel.on(CONFIGURE_VIEWPORT_EVENT_ID, this.configure);
|
||||
channel.on(SET_STORY_DEFAULT_VIEWPORT_EVENT_ID, this.setStoryDefaultViewport);
|
||||
|
||||
this.unsubscribeFromOnStory = api.onStory(() => {
|
||||
const { storyDefaultViewport } = this.state;
|
||||
if (this.mounted && !storyDefaultViewport === defaultViewport) {
|
||||
this.setStoryDefaultViewport(defaultViewport);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.mounted = false;
|
||||
const { channel } = this.props;
|
||||
|
||||
this.unsubscribeFromOnStory();
|
||||
|
||||
channel.removeListener(UPDATE_VIEWPORT_EVENT_ID, this.changeViewport);
|
||||
channel.removeListener(CONFIGURE_VIEWPORT_EVENT_ID, this.configure);
|
||||
channel.removeListener(SET_STORY_DEFAULT_VIEWPORT_EVENT_ID, this.setStoryDefaultViewport);
|
||||
}
|
||||
|
||||
setStoryDefaultViewport = viewport => {
|
||||
const { viewports } = this.state;
|
||||
const defaultViewport = getDefaultViewport(viewports, viewport);
|
||||
|
||||
this.setState({
|
||||
storyDefaultViewport: defaultViewport,
|
||||
});
|
||||
this.changeViewport(defaultViewport);
|
||||
};
|
||||
|
||||
configure = (options = ViewportTool.defaultOptions) => {
|
||||
this.iframe = document.getElementById(storybookIframe);
|
||||
const viewports = getViewports(options.viewports);
|
||||
const defaultViewport = getDefaultViewport(viewports, options.defaultViewport);
|
||||
|
||||
this.setState(
|
||||
{
|
||||
defaultViewport,
|
||||
viewport: defaultViewport,
|
||||
viewports: viewportsTransformer(viewports),
|
||||
},
|
||||
this.updateIframe
|
||||
);
|
||||
};
|
||||
|
||||
changeViewport = viewport => {
|
||||
const { viewport: previousViewport } = this.state;
|
||||
|
||||
if (previousViewport !== viewport) {
|
||||
this.setState(
|
||||
{
|
||||
viewport,
|
||||
},
|
||||
() => {
|
||||
this.updateIframe();
|
||||
this.emitViewportChanged();
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
emitViewportChanged = () => {
|
||||
const { channel } = this.props;
|
||||
const { viewport, viewports } = this.state;
|
||||
|
||||
if (!this.shouldNotify()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.previousViewport = viewport;
|
||||
|
||||
channel.emit(VIEWPORT_CHANGED_EVENT_ID, {
|
||||
viewport: viewports[viewport],
|
||||
});
|
||||
};
|
||||
|
||||
shouldNotify = () => {
|
||||
const { viewport } = this.state;
|
||||
|
||||
return this.previousViewport !== viewport;
|
||||
};
|
||||
|
||||
updateIframe = () => {
|
||||
const { viewports, viewport: viewportKey } = this.state;
|
||||
const viewport = viewports[viewportKey] || resetViewport;
|
||||
|
||||
if (!this.iframe) {
|
||||
throw new Error('Cannot find Storybook iframe');
|
||||
}
|
||||
|
||||
Object.keys(viewport.styles).forEach(prop => {
|
||||
this.iframe.style[prop] = viewport.styles[prop];
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { viewports } = this.state;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Popout key="viewports">
|
||||
<IconButton key="viewport" title="Change Viewport">
|
||||
<Icons icon="browser" />
|
||||
</IconButton>
|
||||
{({ hide }) => (
|
||||
<List>
|
||||
{Object.entries(viewports).map(([key, { name }]) => (
|
||||
<Item
|
||||
key={key}
|
||||
onClick={() => {
|
||||
this.changeViewport(key);
|
||||
|
||||
hide();
|
||||
}}
|
||||
>
|
||||
<Title>{name}</Title>
|
||||
</Item>
|
||||
))}
|
||||
</List>
|
||||
)}
|
||||
</Popout>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
@ -1,13 +1,21 @@
|
||||
import React from 'react';
|
||||
import addons from '@storybook/addons';
|
||||
import addons, { types } from '@storybook/addons';
|
||||
|
||||
import Tool from './components/Tool';
|
||||
import Panel from './components/Panel';
|
||||
|
||||
import { ADDON_ID, PANEL_ID } from '../shared';
|
||||
|
||||
const addChannel = api => {
|
||||
const channel = addons.getChannel();
|
||||
addons.addPanel(PANEL_ID, {
|
||||
|
||||
addons.add(PANEL_ID, {
|
||||
type: types.TOOL,
|
||||
render: () => <Tool channel={channel} api={api} />,
|
||||
});
|
||||
|
||||
addons.add(PANEL_ID, {
|
||||
type: types.PANEL,
|
||||
title: 'Viewport',
|
||||
// eslint-disable-next-line react/prop-types
|
||||
render: ({ active }) => <Panel channel={channel} api={api} active={active} />,
|
||||
|
4
app/react-native/src/manager/provider.js
vendored
4
app/react-native/src/manager/provider.js
vendored
@ -38,10 +38,6 @@ export default class ReactProvider extends Provider {
|
||||
}
|
||||
}
|
||||
|
||||
getPanels() {
|
||||
return addons.getPanels();
|
||||
}
|
||||
|
||||
renderPreview(kind, story) {
|
||||
this.selection = { kind, story };
|
||||
this.channel.emit(Events.SET_CURRENT_STORY, { kind, story });
|
||||
|
@ -1,15 +1,18 @@
|
||||
// Resolves to window in browser and to global in node
|
||||
import global from 'global';
|
||||
import { types, isSupportedType } from './types';
|
||||
// import { TabWrapper } from '@storybook/components';
|
||||
|
||||
export mockChannel from './storybook-channel-mock';
|
||||
export { makeDecorator } from './make-decorator';
|
||||
|
||||
export { types, isSupportedType };
|
||||
|
||||
export class AddonStore {
|
||||
constructor() {
|
||||
this.loaders = {};
|
||||
this.panels = {};
|
||||
this.mains = {};
|
||||
this.elements = {};
|
||||
this.channel = null;
|
||||
this.preview = null;
|
||||
this.database = null;
|
||||
@ -49,30 +52,24 @@ export class AddonStore {
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
getPanels() {
|
||||
return this.panels;
|
||||
}
|
||||
|
||||
addPanel(name, panel) {
|
||||
// supporting legacy addons, which have not migrated to the active-prop
|
||||
// const original = panel.render;
|
||||
// if (original && original.toString() && !original.toString().match(/active/)) {
|
||||
// this.panels[name] = {
|
||||
// ...panel,
|
||||
// render: ({ active }) => TabWrapper({ active, render: original }),
|
||||
// };
|
||||
// } else {
|
||||
this.panels[name] = panel;
|
||||
// }
|
||||
this.add(name, {
|
||||
type: types.PANEL,
|
||||
...panel,
|
||||
});
|
||||
}
|
||||
|
||||
// might reconsider using something more generic and adding a 'type'
|
||||
getMains() {
|
||||
return this.mains;
|
||||
add(name, options) {
|
||||
const { type } = options;
|
||||
// assert isSupportedType
|
||||
if (!this.elements[type]) {
|
||||
this.elements[type] = [];
|
||||
}
|
||||
this.elements[type].push(options);
|
||||
}
|
||||
|
||||
addMain(name, panel) {
|
||||
this.mains[name] = panel;
|
||||
getElements(type) {
|
||||
return this.elements[type] || [];
|
||||
}
|
||||
|
||||
register(name, loader) {
|
||||
|
9
lib/addons/src/types.js
Normal file
9
lib/addons/src/types.js
Normal file
@ -0,0 +1,9 @@
|
||||
export const types = {
|
||||
TAB: 'tab',
|
||||
PANEL: 'panel',
|
||||
TOOL: 'tool',
|
||||
};
|
||||
|
||||
export function isSupportedType(type) {
|
||||
return Object.key(types).find(typeKey => types[typeKey] === type);
|
||||
}
|
@ -30,6 +30,7 @@
|
||||
"@emotion/styled": "^0.10.6",
|
||||
"@reach/router": "^1.1.1",
|
||||
"@storybook/core-events": "4.0.0-alpha.23",
|
||||
"@storybook/addons": "4.0.0-alpha.23",
|
||||
"global": "^4.3.2",
|
||||
"immer": "^1.5.0",
|
||||
"js-beautify": "^1.8.6",
|
||||
|
@ -20,6 +20,7 @@ export { default as Placeholder } from './placeholder/placeholder';
|
||||
|
||||
export { Explorer } from './explorer/explorer';
|
||||
export { Preview } from './preview/preview';
|
||||
export { IconButton, Separator, Toolbar } from './preview/toolbar';
|
||||
|
||||
export { default as Heading } from './heading/heading';
|
||||
|
||||
|
@ -3,8 +3,7 @@ import React, { Component, Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import styled from '@emotion/styled';
|
||||
import Events from '@storybook/core-events';
|
||||
|
||||
import { Popout, Item, Icon, Title, List } from '../menu/menu';
|
||||
import { types } from '@storybook/addons';
|
||||
|
||||
import { IconButton, Toolbar, Separator } from './toolbar';
|
||||
import Icons from '../icon/icon';
|
||||
@ -90,25 +89,17 @@ const Frame = styled.div({
|
||||
},
|
||||
});
|
||||
|
||||
const FrameWrap = styled.div(
|
||||
({ offset }) => ({
|
||||
position: 'absolute',
|
||||
overflow: 'auto',
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
top: offset,
|
||||
zIndex: 3,
|
||||
height: offset ? `calc(100% - ${offset}px)` : '100%',
|
||||
background: 'transparent',
|
||||
}),
|
||||
({ filter }) =>
|
||||
filter
|
||||
? {
|
||||
filter: filter === 'mono' ? 'grayscale(100%)' : `url('#${filter}')`,
|
||||
}
|
||||
: {}
|
||||
);
|
||||
const FrameWrap = styled.div(({ offset }) => ({
|
||||
position: 'absolute',
|
||||
overflow: 'auto',
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
top: offset,
|
||||
zIndex: 3,
|
||||
height: offset ? `calc(100% - ${offset}px)` : '100%',
|
||||
background: 'transparent',
|
||||
}));
|
||||
|
||||
const UnstyledLink = styled(Link)({
|
||||
color: 'inherit',
|
||||
@ -116,24 +107,14 @@ const UnstyledLink = styled(Link)({
|
||||
display: 'inline-block',
|
||||
});
|
||||
|
||||
const ColorIcon = styled.span(
|
||||
{
|
||||
background: 'linear-gradient(to right, #F44336, #FF9800, #FFEB3B, #8BC34A, #2196F3, #9C27B0)',
|
||||
},
|
||||
({ filter }) => ({
|
||||
filter: filter === 'mono' ? 'grayscale(100%)' : `url('#${filter}')`,
|
||||
})
|
||||
);
|
||||
|
||||
// eslint-disable-next-line react/no-multi-comp
|
||||
class Preview extends Component {
|
||||
state = {
|
||||
zoom: 1,
|
||||
grid: false,
|
||||
filter: false,
|
||||
};
|
||||
|
||||
shouldComponentUpdate({ location, toolbar, options }, { zoom, grid, filter }) {
|
||||
shouldComponentUpdate({ location, toolbar, options }, { zoom, grid }) {
|
||||
const { props, state } = this;
|
||||
|
||||
return (
|
||||
@ -141,7 +122,6 @@ class Preview extends Component {
|
||||
location !== props.location ||
|
||||
toolbar !== props.toolbar ||
|
||||
zoom !== state.zoom ||
|
||||
filter !== state.filter ||
|
||||
grid !== state.grid
|
||||
);
|
||||
}
|
||||
@ -154,77 +134,19 @@ class Preview extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { id, toolbar = true, location = '/', panels, actions, options } = this.props;
|
||||
const { zoom, grid, filter } = this.state;
|
||||
const { id, toolbar = true, location = '/', getElements, actions, options } = this.props;
|
||||
const { zoom, grid } = this.state;
|
||||
const panels = getElements(types.PANEL);
|
||||
|
||||
const toolbarHeight = toolbar ? 40 : 0;
|
||||
const panelList = Object.entries(panels).map(([key, value]) => ({ ...value, key }));
|
||||
const tabsList = panelList.length
|
||||
? [{ route: '/components/', title: 'Canvas', key: 'canvas' }].concat(panelList)
|
||||
: [];
|
||||
const tabsList = [{ route: '/components/', title: 'Canvas', key: 'canvas' }].concat(
|
||||
getElements(types.TAB)
|
||||
);
|
||||
const toolList = getElements(types.TOOL);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<svg key="svg">
|
||||
<defs>
|
||||
<filter id="protanopia">
|
||||
<feColorMatrix
|
||||
in="SourceGraphic"
|
||||
type="matrix"
|
||||
values="0.567, 0.433, 0, 0, 0 0.558, 0.442, 0, 0, 0 0, 0.242, 0.758, 0, 0 0, 0, 0, 1, 0"
|
||||
/>
|
||||
</filter>
|
||||
<filter id="protanomaly">
|
||||
<feColorMatrix
|
||||
in="SourceGraphic"
|
||||
type="matrix"
|
||||
values="0.817, 0.183, 0, 0, 0 0.333, 0.667, 0, 0, 0 0, 0.125, 0.875, 0, 0 0, 0, 0, 1, 0"
|
||||
/>
|
||||
</filter>
|
||||
<filter id="deuteranopia">
|
||||
<feColorMatrix
|
||||
in="SourceGraphic"
|
||||
type="matrix"
|
||||
values="0.625, 0.375, 0, 0, 0 0.7, 0.3, 0, 0, 0 0, 0.3, 0.7, 0, 0 0, 0, 0, 1, 0"
|
||||
/>
|
||||
</filter>
|
||||
<filter id="deuteranomaly">
|
||||
<feColorMatrix
|
||||
in="SourceGraphic"
|
||||
type="matrix"
|
||||
values="0.8, 0.2, 0, 0, 0 0.258, 0.742, 0, 0, 0 0, 0.142, 0.858, 0, 0 0, 0, 0, 1, 0"
|
||||
/>
|
||||
</filter>
|
||||
<filter id="tritanopia">
|
||||
<feColorMatrix
|
||||
in="SourceGraphic"
|
||||
type="matrix"
|
||||
values="0.95, 0.05, 0, 0, 0 0, 0.433, 0.567, 0, 0 0, 0.475, 0.525, 0, 0 0, 0, 0, 1, 0"
|
||||
/>
|
||||
</filter>
|
||||
<filter id="tritanomaly">
|
||||
<feColorMatrix
|
||||
in="SourceGraphic"
|
||||
type="matrix"
|
||||
values="0.967, 0.033, 0, 0, 0 0, 0.733, 0.267, 0, 0 0, 0.183, 0.817, 0, 0 0, 0, 0, 1, 0"
|
||||
/>
|
||||
</filter>
|
||||
<filter id="achromatopsia">
|
||||
<feColorMatrix
|
||||
in="SourceGraphic"
|
||||
type="matrix"
|
||||
values="0.299, 0.587, 0.114, 0, 0 0.299, 0.587, 0.114, 0, 0 0.299, 0.587, 0.114, 0, 0 0, 0, 0, 1, 0"
|
||||
/>
|
||||
</filter>
|
||||
<filter id="achromatomaly">
|
||||
<feColorMatrix
|
||||
in="SourceGraphic"
|
||||
type="matrix"
|
||||
values="0.618, 0.320, 0.062, 0, 0 0.163, 0.775, 0.062, 0, 0 0.163, 0.320, 0.516, 0, 0 0, 0, 0, 1, 0"
|
||||
/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
{toolbar ? (
|
||||
<Toolbar
|
||||
key="toolbar"
|
||||
@ -250,54 +172,11 @@ class Preview extends Component {
|
||||
<IconButton active={!!grid} key="grid" onClick={() => this.setState({ grid: !grid })}>
|
||||
<Icons icon="grid" />
|
||||
</IconButton>,
|
||||
<Popout key="filters">
|
||||
<IconButton key="filter" active={!!filter}>
|
||||
<Icons icon="mirror" />
|
||||
</IconButton>
|
||||
{({ hide }) => (
|
||||
<List>
|
||||
{[
|
||||
'protanopia',
|
||||
'protanomaly',
|
||||
'deuteranopia',
|
||||
'deuteranomaly',
|
||||
'tritanopia',
|
||||
'tritanomaly',
|
||||
'achromatopsia',
|
||||
'achromatomaly',
|
||||
].map(i => (
|
||||
<Item
|
||||
key={i}
|
||||
onClick={() => {
|
||||
this.setState({ filter: filter === i ? null : i });
|
||||
hide();
|
||||
}}
|
||||
>
|
||||
<Icon type={<ColorIcon filter={i} />} />
|
||||
<Title>{i}</Title>
|
||||
</Item>
|
||||
))}
|
||||
<Item
|
||||
onClick={() => {
|
||||
this.setState({ filter: filter === 'mono' ? null : 'mono' });
|
||||
hide();
|
||||
}}
|
||||
>
|
||||
<Icon type={<ColorIcon filter="mono" />} />
|
||||
<Title>mono</Title>
|
||||
</Item>
|
||||
<Item
|
||||
onClick={() => {
|
||||
this.setState({ filter: null });
|
||||
hide();
|
||||
}}
|
||||
>
|
||||
<Icon type={<ColorIcon />} />
|
||||
<Title>Off</Title>
|
||||
</Item>
|
||||
</List>
|
||||
)}
|
||||
</Popout>,
|
||||
<Fragment>
|
||||
{toolList.map(t => (
|
||||
<Fragment>{t.render()}</Fragment>
|
||||
))}
|
||||
</Fragment>,
|
||||
]}
|
||||
right={[
|
||||
<Separator key="1" />,
|
||||
@ -311,7 +190,7 @@ class Preview extends Component {
|
||||
]}
|
||||
/>
|
||||
) : null}
|
||||
<FrameWrap key="frame" offset={toolbarHeight} filter={filter || undefined}>
|
||||
<FrameWrap key="frame" offset={toolbarHeight}>
|
||||
<Route path="components" startsWith hideOnly>
|
||||
<Frame
|
||||
style={{
|
||||
@ -348,7 +227,7 @@ Preview.propTypes = {
|
||||
removeListener: PropTypes.func,
|
||||
}).isRequired,
|
||||
location: PropTypes.string.isRequired,
|
||||
panels: PropTypes.shape({}).isRequired,
|
||||
getElements: PropTypes.func.isRequired,
|
||||
options: PropTypes.shape({
|
||||
isFullscreen: PropTypes.bool,
|
||||
}).isRequired,
|
||||
|
@ -12,12 +12,8 @@ export default class ReactProvider extends Provider {
|
||||
this.channel.emit(Events.CHANNEL_CREATED);
|
||||
}
|
||||
|
||||
getPanels() {
|
||||
return addons.getPanels();
|
||||
}
|
||||
|
||||
getMains() {
|
||||
return addons.getMains();
|
||||
getElements(type) {
|
||||
return addons.getElements(type);
|
||||
}
|
||||
|
||||
handleAPI(api) {
|
||||
|
@ -36,7 +36,7 @@ import React from 'react';
|
||||
import { Provider } from '@storybook/ui';
|
||||
|
||||
export default class MyProvider extends Provider {
|
||||
getPanels() {
|
||||
getElements(type) {
|
||||
return {};
|
||||
}
|
||||
|
||||
|
@ -29,6 +29,7 @@
|
||||
"@emotion/provider": "^0.11.2",
|
||||
"@emotion/styled": "^0.10.6",
|
||||
"@reach/router": "^1.1.1",
|
||||
"@storybook/addons": "4.0.0-alpha.23",
|
||||
"@storybook/components": "4.0.0-alpha.23",
|
||||
"@storybook/core-events": "4.0.0-alpha.23",
|
||||
"@storybook/mantra-core": "^1.7.2",
|
||||
|
@ -13,7 +13,7 @@ describe('manager.ui.containers.panel', () => {
|
||||
test2: {},
|
||||
sdp: {},
|
||||
};
|
||||
const getPanels = () => panels;
|
||||
const getElements = () => panels;
|
||||
|
||||
const props = {};
|
||||
const env = {
|
||||
@ -24,7 +24,7 @@ describe('manager.ui.containers.panel', () => {
|
||||
}),
|
||||
context: () => ({
|
||||
provider: {
|
||||
getPanels,
|
||||
getElements,
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
@ -4,7 +4,7 @@ import { Preview } from '@storybook/components';
|
||||
export function mapper({ store, uiStore }) {
|
||||
return {
|
||||
channel: store.channel,
|
||||
panels: store.mains,
|
||||
getElements: store.getElements,
|
||||
actions: {
|
||||
toggleFullscreen: () => uiStore.toggleFullscreen(),
|
||||
},
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { observable, action, set } from 'mobx';
|
||||
import pick from 'lodash.pick';
|
||||
import { themes } from '@storybook/components';
|
||||
import { types } from '@storybook/addons';
|
||||
import qs from 'qs';
|
||||
|
||||
export function ensurePanel(panels, selectedPanel, currentPanel) {
|
||||
@ -47,21 +48,25 @@ const createStore = ({ provider, history }) => {
|
||||
],
|
||||
|
||||
selectedPanelValue: null,
|
||||
|
||||
get selectedPanel() {
|
||||
return ensurePanel(this.panels, this.selectedPanelValue, this.selectedPanelValue);
|
||||
},
|
||||
|
||||
set selectedPanel(value) {
|
||||
this.selectedPanelValue = value;
|
||||
},
|
||||
|
||||
get panels() {
|
||||
return provider.getPanels();
|
||||
},
|
||||
get channel() {
|
||||
return provider.channel;
|
||||
},
|
||||
get mains() {
|
||||
return provider.getMains();
|
||||
|
||||
get panels() {
|
||||
return this.getElements(types.PANEL);
|
||||
},
|
||||
|
||||
getElements(type) {
|
||||
return provider.getElements(type);
|
||||
},
|
||||
|
||||
setOptions(changes) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user