mirror of
https://github.com/storybookjs/storybook.git
synced 2025-04-07 03:41:06 +08:00
WIP
This commit is contained in:
parent
d2fb2eca92
commit
68c1664ec9
@ -19,7 +19,7 @@ const Violations = styled('span')(({ theme }) => ({
|
|||||||
color: theme.failColor,
|
color: theme.failColor,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
class Panel extends Component {
|
class A11YPanel extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
active: PropTypes.bool.isRequired,
|
active: PropTypes.bool.isRequired,
|
||||||
channel: PropTypes.shape({
|
channel: PropTypes.shape({
|
||||||
@ -99,4 +99,4 @@ class Panel extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Panel;
|
export default A11YPanel;
|
||||||
|
@ -10,37 +10,33 @@ import ActionLoggerComponent from '../../components/ActionLogger';
|
|||||||
import { EVENT_ID } from '../..';
|
import { EVENT_ID } from '../..';
|
||||||
|
|
||||||
export default class ActionLogger extends React.Component {
|
export default class ActionLogger extends React.Component {
|
||||||
constructor(props, ...args) {
|
state = { actions: [] };
|
||||||
super(props, ...args);
|
|
||||||
this.state = { actions: [] };
|
|
||||||
this._actionListener = action => this.addAction(action);
|
|
||||||
this._storyChangeListener = () => this.handleStoryChange();
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
this.mounted = true;
|
||||||
const { channel, api } = this.props;
|
const { channel, api } = this.props;
|
||||||
|
|
||||||
channel.on(EVENT_ID, this._actionListener);
|
channel.on(EVENT_ID, this._actionListener);
|
||||||
this.stopListeningOnStory = api.onStory(this._storyChangeListener);
|
this.stopListeningOnStory = api.onStory(this.handleStoryChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
|
this.mounted = false;
|
||||||
const { channel } = this.props;
|
const { channel } = this.props;
|
||||||
|
|
||||||
channel.removeListener(EVENT_ID, this._actionListener);
|
channel.removeListener(EVENT_ID, this.addAction);
|
||||||
if (this.stopListeningOnStory) {
|
|
||||||
this.stopListeningOnStory();
|
this.stopListeningOnStory();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleStoryChange() {
|
handleStoryChange = () => {
|
||||||
const { actions } = this.state;
|
const { actions } = this.state;
|
||||||
if (actions.length > 0 && actions[0].options.clearOnStoryChange) {
|
if (actions.length > 0 && actions[0].options.clearOnStoryChange) {
|
||||||
this.clearActions();
|
this.clearActions();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
addAction(action) {
|
addAction = action => {
|
||||||
let { actions = [] } = this.state;
|
let { actions = [] } = this.state;
|
||||||
actions = [...actions];
|
actions = [...actions];
|
||||||
|
|
||||||
@ -55,18 +51,18 @@ export default class ActionLogger extends React.Component {
|
|||||||
actions.unshift(action);
|
actions.unshift(action);
|
||||||
}
|
}
|
||||||
this.setState({ actions: actions.slice(0, action.options.limit) });
|
this.setState({ actions: actions.slice(0, action.options.limit) });
|
||||||
}
|
};
|
||||||
|
|
||||||
clearActions() {
|
clearActions = () => {
|
||||||
this.setState({ actions: [] });
|
this.setState({ actions: [] });
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { actions = [] } = this.state;
|
const { actions = [] } = this.state;
|
||||||
const { active } = this.props;
|
const { active } = this.props;
|
||||||
const props = {
|
const props = {
|
||||||
actions,
|
actions,
|
||||||
onClear: () => this.clearActions(),
|
onClear: this.clearActions,
|
||||||
};
|
};
|
||||||
return active ? <ActionLoggerComponent {...props} /> : null;
|
return active ? <ActionLoggerComponent {...props} /> : null;
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,7 @@ const Item = styled('div')({
|
|||||||
padding: 5,
|
padding: 5,
|
||||||
});
|
});
|
||||||
|
|
||||||
const storybookIframe = 'storybook-preview-iframe';
|
const storybookIframe = 'storybook-preview-background';
|
||||||
const style = {
|
const style = {
|
||||||
iframe: {
|
iframe: {
|
||||||
transition: 'background 0.25s ease-in-out',
|
transition: 'background 0.25s ease-in-out',
|
||||||
@ -83,7 +83,8 @@ export default class BackgroundPanel extends Component {
|
|||||||
this.iframe = document.getElementById(storybookIframe);
|
this.iframe = document.getElementById(storybookIframe);
|
||||||
|
|
||||||
if (!this.iframe) {
|
if (!this.iframe) {
|
||||||
throw new Error('Cannot find Storybook iframe');
|
return;
|
||||||
|
// throw new Error('Cannot find Storybook iframe');
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.keys(style.iframe).forEach(prop => {
|
Object.keys(style.iframe).forEach(prop => {
|
||||||
@ -97,8 +98,6 @@ export default class BackgroundPanel extends Component {
|
|||||||
const current = api.getQueryParam('background');
|
const current = api.getQueryParam('background');
|
||||||
const defaultOrFirst = backgrounds.find(x => x.default) || backgrounds[0];
|
const defaultOrFirst = backgrounds.find(x => x.default) || backgrounds[0];
|
||||||
|
|
||||||
// debugger;
|
|
||||||
|
|
||||||
if (current && backgrounds.find(bg => bg.value === current)) {
|
if (current && backgrounds.find(bg => bg.value === current)) {
|
||||||
this.updateIframe(current);
|
this.updateIframe(current);
|
||||||
} else if (defaultOrFirst) {
|
} else if (defaultOrFirst) {
|
||||||
|
@ -13,7 +13,7 @@ const Wrapper = styled('div')({
|
|||||||
minHeight: '100%',
|
minHeight: '100%',
|
||||||
});
|
});
|
||||||
|
|
||||||
export default class Events extends Component {
|
export default class EventsPanel extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
active: PropTypes.bool.isRequired,
|
active: PropTypes.bool.isRequired,
|
||||||
channel: PropTypes.shape({
|
channel: PropTypes.shape({
|
||||||
|
@ -96,10 +96,10 @@ const SuiteTitle = styled('div')({
|
|||||||
|
|
||||||
const Content = styled(({ tests, className }) => (
|
const Content = styled(({ tests, className }) => (
|
||||||
<div className={className}>
|
<div className={className}>
|
||||||
{tests.map(({ name, result }) => {
|
{tests.filter(({ result } = {}) => !!result).map(({ name, result }) => {
|
||||||
if (!result) {
|
// if (!result) {
|
||||||
return <NoTests key={name}>This story has tests configured, but no file was found</NoTests>;
|
// return <NoTests key={name}>This story has tests configured, but no file was found</NoTests>;
|
||||||
}
|
// }
|
||||||
|
|
||||||
const successNumber = result.assertionResults.filter(({ status }) => status === 'passed')
|
const successNumber = result.assertionResults.filter(({ status }) => status === 'passed')
|
||||||
.length;
|
.length;
|
||||||
@ -141,14 +141,14 @@ const Content = styled(({ tests, className }) => (
|
|||||||
flex: '1 1 0%',
|
flex: '1 1 0%',
|
||||||
});
|
});
|
||||||
|
|
||||||
const Panel = ({ tests }) =>
|
const JestPanel = ({ tests }) =>
|
||||||
tests ? <Content tests={tests} /> : <NoTests>This story has no tests configures</NoTests>;
|
tests ? <Content tests={tests} /> : <NoTests>This story has no tests configured</NoTests>;
|
||||||
|
|
||||||
Panel.defaultProps = {
|
JestPanel.defaultProps = {
|
||||||
tests: null,
|
tests: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
Panel.propTypes = {
|
JestPanel.propTypes = {
|
||||||
tests: PropTypes.arrayOf(
|
tests: PropTypes.arrayOf(
|
||||||
PropTypes.shape({
|
PropTypes.shape({
|
||||||
result: PropTypes.object,
|
result: PropTypes.object,
|
||||||
@ -156,4 +156,4 @@ Panel.propTypes = {
|
|||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
export default provideJestResult(Panel);
|
export default provideJestResult(JestPanel);
|
||||||
|
@ -15,27 +15,30 @@ const provideTests = Component =>
|
|||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
active: true,
|
active: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {};
|
state = {};
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
this.mounted = true;
|
||||||
const { channel, api } = this.props;
|
const { channel, api } = this.props;
|
||||||
|
|
||||||
this.stopListeningOnStory = api.onStory(() => {
|
this.stopListeningOnStory = api.onStory(() => {
|
||||||
this.onAddTests({});
|
const { kind, storyName, tests } = this.state;
|
||||||
|
if (this.mounted && (kind || storyName || tests)) {
|
||||||
|
this.onAddTests({});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
channel.on('storybook/tests/add_tests', this.onAddTests);
|
channel.on('storybook/tests/add_tests', this.onAddTests);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
|
this.mounted = false;
|
||||||
const { channel } = this.props;
|
const { channel } = this.props;
|
||||||
|
|
||||||
if (this.stopListeningOnStory) {
|
this.stopListeningOnStory();
|
||||||
this.stopListeningOnStory();
|
|
||||||
}
|
|
||||||
channel.removeListener('storybook/tests/add_tests', this.onAddTests);
|
channel.removeListener('storybook/tests/add_tests', this.onAddTests);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,7 +48,9 @@ const provideTests = Component =>
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { active } = this.props;
|
const { active } = this.props;
|
||||||
return active ? <Component {...this.state} /> : null;
|
const { tests } = this.state;
|
||||||
|
|
||||||
|
return active && tests ? <Component {...this.state} /> : null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ export const withTests = userOptions => {
|
|||||||
] = args;
|
] = args;
|
||||||
|
|
||||||
if (testFiles && !testFiles.disable) {
|
if (testFiles && !testFiles.disable) {
|
||||||
emitAddTests({ kind, story, testFiles, options });
|
emitAddTests({ kind, story, testFiles: [].concat(testFiles), options });
|
||||||
}
|
}
|
||||||
|
|
||||||
return story();
|
return story();
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import addons from '@storybook/addons';
|
import addons from '@storybook/addons';
|
||||||
|
|
||||||
import PanelTitle from './components/PanelTitle';
|
// import PanelTitle from './components/PanelTitle';
|
||||||
import Panel from './components/Panel';
|
import Panel from './components/Panel';
|
||||||
|
|
||||||
addons.register('storybook/tests', api => {
|
addons.register('storybook/tests', api => {
|
||||||
const channel = addons.getChannel();
|
const channel = addons.getChannel();
|
||||||
|
|
||||||
addons.addPanel('storybook/tests/panel', {
|
addons.addPanel('storybook/tests/panel', {
|
||||||
title: <PanelTitle channel={addons.getChannel()} api={api} />,
|
title: 'tests',
|
||||||
|
// title: () => <PanelTitle channel={channel} api={api} />,
|
||||||
// eslint-disable-next-line react/prop-types
|
// eslint-disable-next-line react/prop-types
|
||||||
render: ({ active }) => <Panel channel={channel} api={api} active={active} />,
|
render: ({ active }) => <Panel channel={channel} api={api} active={active} />,
|
||||||
});
|
});
|
||||||
|
@ -18,7 +18,7 @@ const PanelWrapper = styled('div')({
|
|||||||
width: '100%',
|
width: '100%',
|
||||||
});
|
});
|
||||||
|
|
||||||
export default class Panel extends PureComponent {
|
export default class KnobPanel extends PureComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = { knobs: {} };
|
this.state = { knobs: {} };
|
||||||
@ -29,17 +29,21 @@ export default class Panel extends PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
this.mounted = true;
|
||||||
const { channel, api } = this.props;
|
const { channel, api } = this.props;
|
||||||
channel.on('addon:knobs:setKnobs', this.setKnobs);
|
channel.on('addon:knobs:setKnobs', this.setKnobs);
|
||||||
channel.on('addon:knobs:setOptions', this.setOptions);
|
channel.on('addon:knobs:setOptions', this.setOptions);
|
||||||
|
|
||||||
this.stopListeningOnStory = api.onStory(() => {
|
this.stopListeningOnStory = api.onStory(() => {
|
||||||
this.setState({ knobs: {} });
|
if (this.mounted) {
|
||||||
|
this.setState({ knobs: {} });
|
||||||
|
}
|
||||||
channel.emit('addon:knobs:reset');
|
channel.emit('addon:knobs:reset');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
|
this.mounted = false;
|
||||||
const { channel } = this.props;
|
const { channel } = this.props;
|
||||||
|
|
||||||
channel.removeListener('addon:knobs:setKnobs', this.setKnobs);
|
channel.removeListener('addon:knobs:setKnobs', this.setKnobs);
|
||||||
@ -190,7 +194,7 @@ export default class Panel extends PureComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Panel.propTypes = {
|
KnobPanel.propTypes = {
|
||||||
active: PropTypes.bool.isRequired,
|
active: PropTypes.bool.isRequired,
|
||||||
onReset: PropTypes.object, // eslint-disable-line
|
onReset: PropTypes.object, // eslint-disable-line
|
||||||
channel: PropTypes.shape({
|
channel: PropTypes.shape({
|
||||||
|
@ -27,8 +27,14 @@ class ArrayType extends React.Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { knob } = this.props;
|
const { knob } = this.props;
|
||||||
|
return (
|
||||||
return <Textarea id={knob.name} value={knob.value} onChange={this.handleChange} size="flex" />;
|
<Textarea
|
||||||
|
id={knob.name}
|
||||||
|
value={knob.value.join(',')}
|
||||||
|
onChange={this.handleChange}
|
||||||
|
size="flex"
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ import marked from 'marked';
|
|||||||
|
|
||||||
function renderMarkdown(text, options) {
|
function renderMarkdown(text, options) {
|
||||||
marked.setOptions({ ...marked.defaults, options });
|
marked.setOptions({ ...marked.defaults, options });
|
||||||
|
|
||||||
return marked(text);
|
return marked(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -11,6 +12,7 @@ export const withNotes = makeDecorator({
|
|||||||
parameterName: 'notes',
|
parameterName: 'notes',
|
||||||
skipIfNoParametersOrOptions: true,
|
skipIfNoParametersOrOptions: true,
|
||||||
allowDeprecatedUsage: true,
|
allowDeprecatedUsage: true,
|
||||||
|
|
||||||
wrapper: (getStory, context, { options, parameters }) => {
|
wrapper: (getStory, context, { options, parameters }) => {
|
||||||
const channel = addons.getChannel();
|
const channel = addons.getChannel();
|
||||||
|
|
||||||
|
@ -10,38 +10,36 @@ const Panel = styled('div')({
|
|||||||
width: '100%',
|
width: '100%',
|
||||||
});
|
});
|
||||||
|
|
||||||
export class Notes extends React.Component {
|
export class NotesPanel extends React.Component {
|
||||||
constructor(...args) {
|
state = {
|
||||||
super(...args);
|
text: '',
|
||||||
this.state = { text: '' };
|
};
|
||||||
this.onAddNotes = this.onAddNotes.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
this.mounted = true;
|
||||||
const { channel, api } = this.props;
|
const { channel, api } = this.props;
|
||||||
// Listen to the notes and render it.
|
|
||||||
channel.on('storybook/notes/add_notes', this.onAddNotes);
|
|
||||||
|
|
||||||
// Clear the current notes on every story change.
|
// Clear the current notes on every story change.
|
||||||
this.stopListeningOnStory = api.onStory(() => {
|
this.stopListeningOnStory = api.onStory(() => {
|
||||||
this.onAddNotes('');
|
const { text } = this.state;
|
||||||
|
if (this.mounted && text !== '') {
|
||||||
|
this.onAddNotes('');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
channel.on('storybook/notes/add_notes', this.onAddNotes);
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is some cleanup tasks when the Notes panel is unmounting.
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
if (this.stopListeningOnStory) {
|
this.mounted = false;
|
||||||
this.stopListeningOnStory();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.unmounted = true;
|
|
||||||
const { channel } = this.props;
|
const { channel } = this.props;
|
||||||
|
|
||||||
|
this.stopListeningOnStory();
|
||||||
channel.removeListener('storybook/notes/add_notes', this.onAddNotes);
|
channel.removeListener('storybook/notes/add_notes', this.onAddNotes);
|
||||||
}
|
}
|
||||||
|
|
||||||
onAddNotes(text) {
|
onAddNotes = text => {
|
||||||
this.setState({ text });
|
this.setState({ text });
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { active } = this.props;
|
const { active } = this.props;
|
||||||
@ -62,7 +60,7 @@ export class Notes extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Notes.propTypes = {
|
NotesPanel.propTypes = {
|
||||||
active: PropTypes.bool.isRequired,
|
active: PropTypes.bool.isRequired,
|
||||||
channel: PropTypes.shape({
|
channel: PropTypes.shape({
|
||||||
on: PropTypes.func,
|
on: PropTypes.func,
|
||||||
@ -81,6 +79,6 @@ addons.register('storybook/notes', api => {
|
|||||||
addons.addPanel('storybook/notes/panel', {
|
addons.addPanel('storybook/notes/panel', {
|
||||||
title: 'Notes',
|
title: 'Notes',
|
||||||
// eslint-disable-next-line react/prop-types
|
// eslint-disable-next-line react/prop-types
|
||||||
render: ({ active }) => <Notes channel={channel} api={api} active={active} />,
|
render: ({ active }) => <NotesPanel channel={channel} api={api} active={active} />,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -95,7 +95,7 @@ setOptions({
|
|||||||
* id to select an addon panel
|
* id to select an addon panel
|
||||||
* @type {String}
|
* @type {String}
|
||||||
*/
|
*/
|
||||||
selectedAddonPanel: undefined, // The order of addons in the "Addon panel" is the same as you import them in 'addons.js'. The first panel will be opened by default as you run Storybook
|
selectedPanel: undefined, // The order of addons in the "Addon panel" is the same as you import them in 'addons.js'. The first panel will be opened by default as you run Storybook
|
||||||
/**
|
/**
|
||||||
* enable/disable shortcuts
|
* enable/disable shortcuts
|
||||||
* @type {Boolean}
|
* @type {Boolean}
|
||||||
|
@ -62,18 +62,10 @@ export default class StoryPanel extends Component {
|
|||||||
state = { source: '// Here will be dragons 🐉' };
|
state = { source: '// Here will be dragons 🐉' };
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
this.mounted = true;
|
||||||
const { channel } = this.props;
|
const { channel } = this.props;
|
||||||
|
|
||||||
channel.on(EVENT_ID, ({ source, currentLocation, locationsMap }) => {
|
channel.on(EVENT_ID, this.listener);
|
||||||
const locationsKeys = getLocationKeys(locationsMap);
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
source,
|
|
||||||
currentLocation,
|
|
||||||
locationsMap,
|
|
||||||
locationsKeys,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate() {
|
componentDidUpdate() {
|
||||||
@ -82,10 +74,27 @@ export default class StoryPanel extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
const { channel } = this.props;
|
||||||
|
|
||||||
|
channel.removeListener(EVENT_ID, this.listener);
|
||||||
|
}
|
||||||
|
|
||||||
setSelectedStoryRef = ref => {
|
setSelectedStoryRef = ref => {
|
||||||
this.selectedStoryRef = ref;
|
this.selectedStoryRef = ref;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
listener = ({ source, currentLocation, locationsMap }) => {
|
||||||
|
const locationsKeys = getLocationKeys(locationsMap);
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
source,
|
||||||
|
currentLocation,
|
||||||
|
locationsMap,
|
||||||
|
locationsKeys,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
clickOnStory = (kind, story) => {
|
clickOnStory = (kind, story) => {
|
||||||
const { api } = this.props;
|
const { api } = this.props;
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ const getDefaultViewport = (viewports, candidateViewport) =>
|
|||||||
const getViewports = viewports =>
|
const getViewports = viewports =>
|
||||||
Object.keys(viewports).length > 0 ? viewports : INITIAL_VIEWPORTS;
|
Object.keys(viewports).length > 0 ? viewports : INITIAL_VIEWPORTS;
|
||||||
|
|
||||||
export class Panel extends Component {
|
export default class ViewportPanel extends Component {
|
||||||
static defaultOptions = {
|
static defaultOptions = {
|
||||||
viewports: INITIAL_VIEWPORTS,
|
viewports: INITIAL_VIEWPORTS,
|
||||||
defaultViewport: DEFAULT_VIEWPORT,
|
defaultViewport: DEFAULT_VIEWPORT,
|
||||||
@ -59,6 +59,7 @@ export class Panel extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
this.mounted = true;
|
||||||
const { channel, api } = this.props;
|
const { channel, api } = this.props;
|
||||||
const { defaultViewport } = this.state;
|
const { defaultViewport } = this.state;
|
||||||
|
|
||||||
@ -69,16 +70,18 @@ export class Panel extends Component {
|
|||||||
channel.on(SET_STORY_DEFAULT_VIEWPORT_EVENT_ID, this.setStoryDefaultViewport);
|
channel.on(SET_STORY_DEFAULT_VIEWPORT_EVENT_ID, this.setStoryDefaultViewport);
|
||||||
|
|
||||||
this.unsubscribeFromOnStory = api.onStory(() => {
|
this.unsubscribeFromOnStory = api.onStory(() => {
|
||||||
this.setStoryDefaultViewport(defaultViewport);
|
const { storyDefaultViewport } = this.state;
|
||||||
|
if (this.mounted && !storyDefaultViewport === defaultViewport) {
|
||||||
|
this.setStoryDefaultViewport(defaultViewport);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
|
this.mounted = false;
|
||||||
const { channel } = this.props;
|
const { channel } = this.props;
|
||||||
|
|
||||||
if (this.unsubscribeFromOnStory) {
|
this.unsubscribeFromOnStory();
|
||||||
this.unsubscribeFromOnStory();
|
|
||||||
}
|
|
||||||
|
|
||||||
channel.removeListener(UPDATE_VIEWPORT_EVENT_ID, this.changeViewport);
|
channel.removeListener(UPDATE_VIEWPORT_EVENT_ID, this.changeViewport);
|
||||||
channel.removeListener(CONFIGURE_VIEWPORT_EVENT_ID, this.configure);
|
channel.removeListener(CONFIGURE_VIEWPORT_EVENT_ID, this.configure);
|
||||||
@ -95,7 +98,7 @@ export class Panel extends Component {
|
|||||||
this.changeViewport(defaultViewport);
|
this.changeViewport(defaultViewport);
|
||||||
};
|
};
|
||||||
|
|
||||||
configure = (options = Panel.defaultOptions) => {
|
configure = (options = ViewportPanel.defaultOptions) => {
|
||||||
const viewports = getViewports(options.viewports);
|
const viewports = getViewports(options.viewports);
|
||||||
const defaultViewport = getDefaultViewport(viewports, options.defaultViewport);
|
const defaultViewport = getDefaultViewport(viewports, options.defaultViewport);
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import addons from '@storybook/addons';
|
import addons from '@storybook/addons';
|
||||||
|
|
||||||
import { Panel } from './components/Panel';
|
import Panel from './components/Panel';
|
||||||
|
|
||||||
import { ADDON_ID, PANEL_ID } from '../shared';
|
import { ADDON_ID, PANEL_ID } from '../shared';
|
||||||
|
|
||||||
|
@ -83,26 +83,26 @@
|
|||||||
// .addDecorator(withKnobs)
|
// .addDecorator(withKnobs)
|
||||||
// .add('with text', () => (
|
// .add('with text', () => (
|
||||||
// <Button onClick={action('clicked')}>
|
// <Button onClick={action('clicked')}>
|
||||||
// {setOptions({ selectedAddonPanel: 'storybook/actions/actions-panel' })}
|
// {setOptions({ selectedPanel: 'storybook/actions/actions-panel' })}
|
||||||
// Hello Button
|
// Hello Button
|
||||||
// </Button>
|
// </Button>
|
||||||
// ))
|
// ))
|
||||||
// .add('with some emoji', () => (
|
// .add('with some emoji', () => (
|
||||||
// <Button onClick={action('clicked')}>
|
// <Button onClick={action('clicked')}>
|
||||||
// {setOptions({ selectedAddonPanel: 'storybook/actions/actions-panel' })}
|
// {setOptions({ selectedPanel: 'storybook/actions/actions-panel' })}
|
||||||
// <span role="img" aria-label="so cool">😀 😎 👍 💯</span>
|
// <span role="img" aria-label="so cool">😀 😎 👍 💯</span>
|
||||||
// </Button>
|
// </Button>
|
||||||
// ))
|
// ))
|
||||||
// .add('with notes', () => (
|
// .add('with notes', () => (
|
||||||
// <WithNotes notes={'A very simple button'}>
|
// <WithNotes notes={'A very simple button'}>
|
||||||
// <Button>
|
// <Button>
|
||||||
// {setOptions({ selectedAddonPanel: 'storybook/notes/panel' })}
|
// {setOptions({ selectedPanel: 'storybook/notes/panel' })}
|
||||||
// Check my notes in the notes panel
|
// Check my notes in the notes panel
|
||||||
// </Button>
|
// </Button>
|
||||||
// </WithNotes>
|
// </WithNotes>
|
||||||
// ))
|
// ))
|
||||||
// .add('with knobs', () => {
|
// .add('with knobs', () => {
|
||||||
// setOptions({ selectedAddonPanel: 'storybooks/storybook-addon-knobs' });
|
// setOptions({ selectedPanel: 'storybooks/storybook-addon-knobs' });
|
||||||
// const name = text('Name', 'Storyteller');
|
// const name = text('Name', 'Storyteller');
|
||||||
// const age = number('Age', 70, { range: true, min: 0, max: 90, step: 5 });
|
// const age = number('Age', 70, { range: true, min: 0, max: 90, step: 5 });
|
||||||
// const fruits = {
|
// const fruits = {
|
||||||
@ -173,7 +173,7 @@
|
|||||||
// 'Use the [info addon](https://github.com/storybooks/storybook/tree/master/addons/info) with its new painless API.'
|
// 'Use the [info addon](https://github.com/storybooks/storybook/tree/master/addons/info) with its new painless API.'
|
||||||
// )(context => (
|
// )(context => (
|
||||||
// <Container>
|
// <Container>
|
||||||
// {setOptions({ selectedAddonPanel: 'storybook/info/info-panel' })}
|
// {setOptions({ selectedPanel: 'storybook/info/info-panel' })}
|
||||||
// click the <InfoButton /> label in top right for info about "{context.story}"
|
// click the <InfoButton /> label in top right for info about "{context.story}"
|
||||||
// </Container>
|
// </Container>
|
||||||
// ))
|
// ))
|
||||||
@ -183,7 +183,7 @@
|
|||||||
// withInfo('see Notes panel for composition info')(
|
// withInfo('see Notes panel for composition info')(
|
||||||
// withNotes('Composition: Info(Notes())')(context => (
|
// withNotes('Composition: Info(Notes())')(context => (
|
||||||
// <div>
|
// <div>
|
||||||
// {setOptions({ selectedAddonPanel: 'storybook/notes/panel' })}
|
// {setOptions({ selectedPanel: 'storybook/notes/panel' })}
|
||||||
// click the <InfoButton /> label in top right for info about "{context.story}"
|
// click the <InfoButton /> label in top right for info about "{context.story}"
|
||||||
// </div>
|
// </div>
|
||||||
// ))
|
// ))
|
||||||
|
@ -36,13 +36,13 @@ storiesOf('Button', module)
|
|||||||
.addDecorator(withNotes)
|
.addDecorator(withNotes)
|
||||||
.add('with text', () => (
|
.add('with text', () => (
|
||||||
<Button onClick={action('clicked')}>
|
<Button onClick={action('clicked')}>
|
||||||
{setOptions({ selectedAddonPanel: 'storybook/actions/actions-panel' })}
|
{setOptions({ selectedPanel: 'storybook/actions/actions-panel' })}
|
||||||
Hello Button
|
Hello Button
|
||||||
</Button>
|
</Button>
|
||||||
))
|
))
|
||||||
.add('with some emoji', () => (
|
.add('with some emoji', () => (
|
||||||
<Button onClick={action('clicked')}>
|
<Button onClick={action('clicked')}>
|
||||||
{setOptions({ selectedAddonPanel: 'storybook/actions/actions-panel' })}
|
{setOptions({ selectedPanel: 'storybook/actions/actions-panel' })}
|
||||||
<span role="img" aria-label="so cool">
|
<span role="img" aria-label="so cool">
|
||||||
😀 😎 👍 💯
|
😀 😎 👍 💯
|
||||||
</span>
|
</span>
|
||||||
@ -52,7 +52,7 @@ storiesOf('Button', module)
|
|||||||
'with notes',
|
'with notes',
|
||||||
() => (
|
() => (
|
||||||
<Button>
|
<Button>
|
||||||
{setOptions({ selectedAddonPanel: 'storybook/notes/panel' })}
|
{setOptions({ selectedPanel: 'storybook/notes/panel' })}
|
||||||
Check my notes in the notes panel
|
Check my notes in the notes panel
|
||||||
</Button>
|
</Button>
|
||||||
),
|
),
|
||||||
@ -64,7 +64,7 @@ storiesOf('Button', module)
|
|||||||
'Use the [info addon](https://github.com/storybooks/storybook/tree/master/addons/info) with its new painless API.'
|
'Use the [info addon](https://github.com/storybooks/storybook/tree/master/addons/info) with its new painless API.'
|
||||||
)(context => (
|
)(context => (
|
||||||
<Container>
|
<Container>
|
||||||
{setOptions({ selectedAddonPanel: 'storybook/info/info-panel' })}
|
{setOptions({ selectedPanel: 'storybook/info/info-panel' })}
|
||||||
click the <InfoButton /> label in top right for info about "{context.story}"
|
click the <InfoButton /> label in top right for info about "{context.story}"
|
||||||
</Container>
|
</Container>
|
||||||
))
|
))
|
||||||
@ -74,7 +74,7 @@ storiesOf('Button', module)
|
|||||||
withInfo('see Notes panel for composition info')(
|
withInfo('see Notes panel for composition info')(
|
||||||
withNotes('Composition: Info(Notes())')(context => (
|
withNotes('Composition: Info(Notes())')(context => (
|
||||||
<div>
|
<div>
|
||||||
{setOptions({ selectedAddonPanel: 'storybook/notes/panel' })}
|
{setOptions({ selectedPanel: 'storybook/notes/panel' })}
|
||||||
click the <InfoButton /> label in top right for info about "{context.story}"
|
click the <InfoButton /> label in top right for info about "{context.story}"
|
||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
|
@ -9,7 +9,7 @@ const text = 'Testing the a11y addon';
|
|||||||
storiesOf('Addons|a11y', module)
|
storiesOf('Addons|a11y', module)
|
||||||
.addDecorator(checkA11y)
|
.addDecorator(checkA11y)
|
||||||
.addDecorator(fn => {
|
.addDecorator(fn => {
|
||||||
setOptions({ selectedAddonPanel: '@storybook/addon-a11y/panel' });
|
setOptions({ selectedPanel: '@storybook/addon-a11y/panel' });
|
||||||
return fn();
|
return fn();
|
||||||
})
|
})
|
||||||
.add('Default', () => `<button></button>`)
|
.add('Default', () => `<button></button>`)
|
||||||
|
@ -15,7 +15,7 @@ const text = 'Testing the a11y addon';
|
|||||||
storiesOf('Addons|a11y', module)
|
storiesOf('Addons|a11y', module)
|
||||||
.addDecorator(checkA11y)
|
.addDecorator(checkA11y)
|
||||||
.addDecorator(fn => {
|
.addDecorator(fn => {
|
||||||
setOptions({ selectedAddonPanel: '@storybook/addon-a11y/panel' });
|
setOptions({ selectedPanel: '@storybook/addon-a11y/panel' });
|
||||||
return fn();
|
return fn();
|
||||||
})
|
})
|
||||||
.add('Default', () => <BaseButton label="" />)
|
.add('Default', () => <BaseButton label="" />)
|
||||||
|
@ -76,7 +76,7 @@ storiesOf('Addons|Actions', module)
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{setOptions({ selectedAddonPanel: 'storybook/actions/actions-panel' })}
|
{setOptions({ selectedPanel: 'storybook/actions/actions-panel' })}
|
||||||
<Button onClick={() => action('Array')(['foo', 'bar', { foo: 'bar' }])}>Array</Button>
|
<Button onClick={() => action('Array')(['foo', 'bar', { foo: 'bar' }])}>Array</Button>
|
||||||
<Button onClick={() => action('Boolean')(false)}>Boolean</Button>
|
<Button onClick={() => action('Boolean')(false)}>Boolean</Button>
|
||||||
<Button onClick={() => action('Empty Object')({})}>Empty Object</Button>
|
<Button onClick={() => action('Empty Object')({})}>Empty Object</Button>
|
||||||
|
File diff suppressed because one or more lines are too long
@ -61,7 +61,7 @@ export class PostmsgTransport {
|
|||||||
_handleEvent(rawEvent) {
|
_handleEvent(rawEvent) {
|
||||||
try {
|
try {
|
||||||
const { data } = rawEvent;
|
const { data } = rawEvent;
|
||||||
const { key, event } = JSON.parse(data);
|
const { key, event } = typeof data === 'string' ? JSON.parse(data) : data;
|
||||||
if (key === KEY) {
|
if (key === KEY) {
|
||||||
this._handler(event);
|
this._handler(event);
|
||||||
}
|
}
|
||||||
|
@ -6,16 +6,7 @@ import styled from 'react-emotion';
|
|||||||
import Stories from './tree';
|
import Stories from './tree';
|
||||||
import TextFilter from './text_filter';
|
import TextFilter from './text_filter';
|
||||||
|
|
||||||
const Wrapper = styled('div')(
|
const Wrapper = styled('div')({});
|
||||||
({ isMobileDevice }) =>
|
|
||||||
isMobileDevice
|
|
||||||
? {
|
|
||||||
padding: '10px',
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
padding: '10px 0 10px 10px',
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const storyProps = [
|
const storyProps = [
|
||||||
'selectedKind',
|
'selectedKind',
|
||||||
@ -62,7 +53,6 @@ Explorer.defaultProps = {
|
|||||||
storiesHierarchies: [],
|
storiesHierarchies: [],
|
||||||
storyFilter: null,
|
storyFilter: null,
|
||||||
onStoryFilter: () => {},
|
onStoryFilter: () => {},
|
||||||
openShortcutsHelp: null,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Explorer.propTypes = {
|
Explorer.propTypes = {
|
||||||
@ -75,8 +65,6 @@ Explorer.propTypes = {
|
|||||||
),
|
),
|
||||||
storyFilter: PropTypes.string,
|
storyFilter: PropTypes.string,
|
||||||
onStoryFilter: PropTypes.func,
|
onStoryFilter: PropTypes.func,
|
||||||
|
|
||||||
openShortcutsHelp: PropTypes.func,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export { Explorer };
|
export { Explorer };
|
||||||
|
21
lib/components/src/explorer/routedLink.js
Normal file
21
lib/components/src/explorer/routedLink.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { MenuLink } from '@storybook/components';
|
||||||
|
import { inject } from 'mobx-react';
|
||||||
|
import qs from 'qs';
|
||||||
|
|
||||||
|
export function mapper(store, { overrideParams = {} }) {
|
||||||
|
const search = qs.stringify(
|
||||||
|
{
|
||||||
|
...store.urlState,
|
||||||
|
...overrideParams,
|
||||||
|
},
|
||||||
|
{ addQueryPrefix: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
href: search,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const ComposedMenuLink = inject(({ store }, props) => mapper(store, props))(MenuLink);
|
||||||
|
|
||||||
|
export { ComposedMenuLink as MenuLink };
|
@ -8,7 +8,7 @@ import styled from 'react-emotion';
|
|||||||
|
|
||||||
import TreeHeader from './tree_header';
|
import TreeHeader from './tree_header';
|
||||||
import treeNodeTypes from './tree_node_type';
|
import treeNodeTypes from './tree_node_type';
|
||||||
// import treeDecorators from './tree_decorators';
|
import treeDecorators from './tree_decorators';
|
||||||
import treeStyle from './tree_style';
|
import treeStyle from './tree_style';
|
||||||
|
|
||||||
const namespaceSeparator = '@';
|
const namespaceSeparator = '@';
|
||||||
@ -148,8 +148,8 @@ class Stories extends React.Component {
|
|||||||
data={data}
|
data={data}
|
||||||
onToggle={this.onToggle}
|
onToggle={this.onToggle}
|
||||||
animations={sidebarAnimations ? undefined : false}
|
animations={sidebarAnimations ? undefined : false}
|
||||||
|
decorators={treeDecorators}
|
||||||
/>
|
/>
|
||||||
{/* decorators={treeDecorators} */}
|
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -3,8 +3,8 @@ import PropTypes from 'prop-types';
|
|||||||
|
|
||||||
import { decorators } from '@ndelangen/react-treebeard';
|
import { decorators } from '@ndelangen/react-treebeard';
|
||||||
import Icons from '../icons/index';
|
import Icons from '../icons/index';
|
||||||
import { MenuLink } from '../../../containers/routed_link';
|
import { MenuLink } from './routedLink';
|
||||||
import MenuItem from '../menu_item';
|
import MenuItem from './menu_item';
|
||||||
import treeNodeTypes from './tree_node_type';
|
import treeNodeTypes from './tree_node_type';
|
||||||
import { highlightNode } from './tree_decorators_utils';
|
import { highlightNode } from './tree_decorators_utils';
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ export { default as Layout } from './layout/index';
|
|||||||
export { Root } from './root/root';
|
export { Root } from './root/root';
|
||||||
export { Nav } from './nav/nav';
|
export { Nav } from './nav/nav';
|
||||||
export { Explorer } from './explorer/explorer';
|
export { Explorer } from './explorer/explorer';
|
||||||
|
export { Preview } from './preview/preview';
|
||||||
|
|
||||||
export { default as Header } from './header/header';
|
export { default as Header } from './header/header';
|
||||||
export { default as Icons } from './icons/index';
|
export { default as Icons } from './icons/index';
|
||||||
|
15
lib/components/src/preview/pointerBlock.js
Normal file
15
lib/components/src/preview/pointerBlock.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import styled from 'react-emotion';
|
||||||
|
|
||||||
|
const PointerBlock = styled('span')(({ shown }) => ({
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
bottom: 0,
|
||||||
|
right: 0,
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
zIndex: shown ? 2 : 0,
|
||||||
|
background: 'rgba(255,255,255,0.05)',
|
||||||
|
}));
|
||||||
|
|
||||||
|
export { PointerBlock };
|
104
lib/components/src/preview/preview.js
Normal file
104
lib/components/src/preview/preview.js
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
import React, { Component, Fragment } from 'react';
|
||||||
|
import styled from 'react-emotion';
|
||||||
|
|
||||||
|
import { PointerBlock } from './pointerBlock';
|
||||||
|
import { Toolbar } from './toolbar';
|
||||||
|
|
||||||
|
const Typography = styled('div')({
|
||||||
|
fontSize: 11,
|
||||||
|
lineHeight: '40px',
|
||||||
|
marginRight: 5,
|
||||||
|
borderRight: '1px solid rgba(0,0,0,0.2)',
|
||||||
|
paddingRight: 10,
|
||||||
|
});
|
||||||
|
|
||||||
|
const defaults = {
|
||||||
|
grid: {
|
||||||
|
backgroundSize: '100px 100px, 100px 100px, 20px 20px, 20px 20px',
|
||||||
|
backgroundPosition: '-2px -2px, -2px -2px, -1px -1px, -1px -1px',
|
||||||
|
|
||||||
|
backgroundImage: `
|
||||||
|
linear-gradient(rgba(0,0,0,0.05) 2px, transparent 2px),
|
||||||
|
linear-gradient(90deg, rgba(0,0,0,0.05) 2px, transparent 2px),
|
||||||
|
linear-gradient(rgba(0,0,0,0.1) 1px, transparent 1px),
|
||||||
|
linear-gradient(90deg, rgba(0,0,0,0.1) 1px, transparent 1px)`,
|
||||||
|
},
|
||||||
|
background: {
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const Frame = styled('iframe')(
|
||||||
|
{
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
border: '0 none',
|
||||||
|
overflow: 'hidden',
|
||||||
|
transition: 'transform .2s ease-out, height .2s ease-out, width .2s ease-out',
|
||||||
|
transformOrigin: 'top left',
|
||||||
|
},
|
||||||
|
({ grid = defaults.grid }) => grid,
|
||||||
|
({ background = defaults.background }) => background
|
||||||
|
);
|
||||||
|
|
||||||
|
const FrameWrap = styled('div')(({ offset, full }) => ({
|
||||||
|
position: full ? 'fixed' : 'absolute',
|
||||||
|
overflow: 'auto',
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
top: full ? 0 : offset,
|
||||||
|
zIndex: full ? 1 : 3,
|
||||||
|
height: full ? '100vh' : `calc(100% - ${offset}px)`,
|
||||||
|
background: full ? 'white' : 'transparent',
|
||||||
|
}));
|
||||||
|
|
||||||
|
class Preview extends Component {
|
||||||
|
state = {
|
||||||
|
zoom: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
isDragging,
|
||||||
|
full = false,
|
||||||
|
toolbar = true,
|
||||||
|
url = 'https://example.com',
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const { zoom } = this.state;
|
||||||
|
|
||||||
|
const toolbarHeight = toolbar ? 40 : 0;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<PointerBlock shown={isDragging} />
|
||||||
|
<FrameWrap offset={toolbarHeight} full={full} id="storybook-preview-background">
|
||||||
|
<Frame
|
||||||
|
id="storybook-preview-iframe"
|
||||||
|
title={id || 'preview'}
|
||||||
|
src={url}
|
||||||
|
allowFullScreen
|
||||||
|
style={{
|
||||||
|
width: `${100 * zoom}%`,
|
||||||
|
height: `${100 * zoom}%`,
|
||||||
|
transform: `scale(${1 / zoom})`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FrameWrap>
|
||||||
|
{toolbar && !full ? (
|
||||||
|
<Toolbar onZoomChange={val => this.setState({ zoom: zoom * val })}>
|
||||||
|
<Typography>
|
||||||
|
({parseFloat(100 / zoom).toFixed(0)}
|
||||||
|
%)
|
||||||
|
</Typography>
|
||||||
|
</Toolbar>
|
||||||
|
) : null}
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Preview };
|
6
lib/components/src/preview/preview.stories.js
Normal file
6
lib/components/src/preview/preview.stories.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { storiesOf } from '@storybook/react';
|
||||||
|
|
||||||
|
import { Iframe } from './preview';
|
||||||
|
|
||||||
|
storiesOf('Components|Preview', module).add('default', () => <Iframe />);
|
39
lib/components/src/preview/toolbar.js
Normal file
39
lib/components/src/preview/toolbar.js
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import styled from 'react-emotion';
|
||||||
|
|
||||||
|
const ZoomInIcon = () => <span>+</span>;
|
||||||
|
const ZoomOutIcon = () => <span>-</span>;
|
||||||
|
const IconButton = styled('button')({
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
background: 'none',
|
||||||
|
border: '0 none',
|
||||||
|
});
|
||||||
|
|
||||||
|
const Wrapper = styled('div')({
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
right: 0,
|
||||||
|
left: 0,
|
||||||
|
height: 40,
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
borderBottom: '1px solid rgba(0, 0, 0, 0.12)',
|
||||||
|
background: '#fff',
|
||||||
|
zIndex: 2,
|
||||||
|
});
|
||||||
|
|
||||||
|
const Toolbar = ({ children, onZoomChange }) => (
|
||||||
|
<Wrapper>
|
||||||
|
{children}
|
||||||
|
<IconButton onClick={() => onZoomChange(0.8)}>
|
||||||
|
<ZoomInIcon />
|
||||||
|
</IconButton>
|
||||||
|
<IconButton onClick={() => onZoomChange(1.25)}>
|
||||||
|
<ZoomOutIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Wrapper>
|
||||||
|
);
|
||||||
|
|
||||||
|
export { Toolbar };
|
@ -48,7 +48,7 @@ const DesktopPadding = styled('div')(({ left }) => ({
|
|||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const RoundedBorder = styled('div')({
|
const Rounded = styled('div')(({ border }) => ({
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
left: 0,
|
left: 0,
|
||||||
top: 0,
|
top: 0,
|
||||||
@ -59,7 +59,8 @@ const RoundedBorder = styled('div')({
|
|||||||
borderRadius: 5,
|
borderRadius: 5,
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
boxSizing: 'border-box',
|
boxSizing: 'border-box',
|
||||||
});
|
border: border ? '1px solid gray' : '0 none',
|
||||||
|
}));
|
||||||
|
|
||||||
const Desktop = ({
|
const Desktop = ({
|
||||||
Nav,
|
Nav,
|
||||||
@ -79,12 +80,12 @@ const Desktop = ({
|
|||||||
primary="second"
|
primary="second"
|
||||||
show={panel ? [0, 1] : [0]}
|
show={panel ? [0, 1] : [0]}
|
||||||
>
|
>
|
||||||
<RoundedBorder>
|
<Rounded border>
|
||||||
<Preview />
|
<Preview full={full} isDragging />
|
||||||
</RoundedBorder>
|
</Rounded>
|
||||||
<RoundedBorder>
|
<Rounded>
|
||||||
<Panel />
|
<Panel />
|
||||||
</RoundedBorder>
|
</Rounded>
|
||||||
</MemSplit>
|
</MemSplit>
|
||||||
</DesktopPadding>
|
</DesktopPadding>
|
||||||
</MemSplit>
|
</MemSplit>
|
||||||
|
@ -16,13 +16,22 @@ const Root = ({
|
|||||||
},
|
},
|
||||||
}) => (
|
}) => (
|
||||||
<ResizeDetector handleWidth>
|
<ResizeDetector handleWidth>
|
||||||
{width =>
|
{width => {
|
||||||
width > 500 ? (
|
switch (true) {
|
||||||
<Desktop {...{ Nav, Preview, Panel, options }} />
|
case width === 0: {
|
||||||
) : (
|
return <div />;
|
||||||
<Mobile {...{ Nav, Preview, Panel, options }} />
|
}
|
||||||
)
|
case width < 500: {
|
||||||
}
|
return <Mobile {...{ Nav, Preview, Panel, options }} />;
|
||||||
|
}
|
||||||
|
case width > 500: {
|
||||||
|
return <Desktop {...{ Nav, Preview, Panel, options }} />;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
return <div />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
</ResizeDetector>
|
</ResizeDetector>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
"@storybook/channel-postmessage": "4.0.0-alpha.16",
|
"@storybook/channel-postmessage": "4.0.0-alpha.16",
|
||||||
"@storybook/client-logger": "4.0.0-alpha.16",
|
"@storybook/client-logger": "4.0.0-alpha.16",
|
||||||
"@storybook/core-events": "4.0.0-alpha.16",
|
"@storybook/core-events": "4.0.0-alpha.16",
|
||||||
|
"@storybook/components": "4.0.0-alpha.16",
|
||||||
"@storybook/node-logger": "4.0.0-alpha.16",
|
"@storybook/node-logger": "4.0.0-alpha.16",
|
||||||
"@storybook/ui": "4.0.0-alpha.16",
|
"@storybook/ui": "4.0.0-alpha.16",
|
||||||
"airbnb-js-shims": "^2.1.0",
|
"airbnb-js-shims": "^2.1.0",
|
||||||
|
@ -1,35 +1,3 @@
|
|||||||
import PropTypes from 'prop-types';
|
import { Preview } from '@storybook/components';
|
||||||
import React, { Component } from 'react';
|
|
||||||
|
|
||||||
import styled from 'react-emotion';
|
export { Preview as default };
|
||||||
|
|
||||||
const Iframe = styled('iframe')({
|
|
||||||
width: '100%',
|
|
||||||
height: '100%',
|
|
||||||
border: 0,
|
|
||||||
margin: 'auto',
|
|
||||||
padding: 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
class Preview extends Component {
|
|
||||||
shouldComponentUpdate() {
|
|
||||||
// When the manager is re-rendered, due to changes in the layout (going full screen / changing
|
|
||||||
// addon panel to right) Preview section will update. If its re-rendered the whole html page
|
|
||||||
// inside the html is re-rendered making the story to re-mount.
|
|
||||||
// We dont have to re-render this component for any reason since changes are communicated to
|
|
||||||
// story using the channel and necessary changes are done by it.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { url } = this.props;
|
|
||||||
|
|
||||||
return <Iframe id="storybook-preview-iframe" title="preview" src={url} allowFullScreen />;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Preview.propTypes = {
|
|
||||||
url: PropTypes.string.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Preview;
|
|
||||||
|
@ -6,16 +6,7 @@ import styled from 'react-emotion';
|
|||||||
import Stories from './stories_tree';
|
import Stories from './stories_tree';
|
||||||
import TextFilter from './text_filter';
|
import TextFilter from './text_filter';
|
||||||
|
|
||||||
const Wrapper = styled('div')(
|
const Wrapper = styled('div')({});
|
||||||
../../../../components/src/explorer/text_filterDevice }) =>
|
|
||||||
isMobileDevice
|
|
||||||
? {
|
|
||||||
padding: '10px',
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
padding: '10px 0 10px 10px',
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const storyProps = [
|
const storyProps = [
|
||||||
'selectedKind',
|
'selectedKind',
|
||||||
|
@ -4,7 +4,7 @@ import { AddonPanel } from '@storybook/components';
|
|||||||
export function mapper(store) {
|
export function mapper(store) {
|
||||||
return {
|
return {
|
||||||
panels: store.panels,
|
panels: store.panels,
|
||||||
selectedPanel: store.selectedAddonPanel,
|
selectedPanel: store.selectedPanel,
|
||||||
onPanelSelect: panel => store.selectAddonPanel(panel),
|
onPanelSelect: panel => store.selectAddonPanel(panel),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ describe('manager.ui.containers.addon_panel', () => {
|
|||||||
describe('mapper', () => {
|
describe('mapper', () => {
|
||||||
test('should give correct data', () => {
|
test('should give correct data', () => {
|
||||||
const state = {
|
const state = {
|
||||||
selectedAddonPanel: 'sdp',
|
selectedPanel: 'sdp',
|
||||||
};
|
};
|
||||||
|
|
||||||
const selectAddonPanel = () => 'selectAddonPanel';
|
const selectAddonPanel = () => 'selectAddonPanel';
|
||||||
|
@ -12,17 +12,21 @@ import {
|
|||||||
} from '../libs/hierarchy';
|
} from '../libs/hierarchy';
|
||||||
|
|
||||||
export const mapper = store => {
|
export const mapper = store => {
|
||||||
const { stories, selectedKind, selectedStory, uiOptions, storyFilter } = store;
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
name,
|
stories,
|
||||||
url,
|
selectedKind,
|
||||||
|
selectedStory,
|
||||||
|
uiOptions: {
|
||||||
|
name,
|
||||||
|
url,
|
||||||
|
|
||||||
sortStoriesByKind,
|
sortStoriesByKind,
|
||||||
hierarchySeparator,
|
hierarchySeparator,
|
||||||
hierarchyRootSeparator,
|
hierarchyRootSeparator,
|
||||||
sidebarAnimations,
|
sidebarAnimations,
|
||||||
} = uiOptions;
|
},
|
||||||
|
storyFilter,
|
||||||
|
} = store;
|
||||||
|
|
||||||
const preparedStories = prepareStoriesForHierarchy(
|
const preparedStories = prepareStoriesForHierarchy(
|
||||||
stories,
|
stories,
|
||||||
@ -44,13 +48,23 @@ export const mapper = store => {
|
|||||||
const selectedHierarchy = resolveStoryHierarchy(storyName, hierarchySeparator);
|
const selectedHierarchy = resolveStoryHierarchy(storyName, hierarchySeparator);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name,
|
title: name,
|
||||||
url,
|
url,
|
||||||
sections: [
|
sections: [
|
||||||
{
|
{
|
||||||
id: 'components',
|
id: 'components',
|
||||||
name: 'components',
|
name: 'components',
|
||||||
render: () => <div>LIST GOES HERE </div>,
|
render: () => (
|
||||||
|
<Explorer
|
||||||
|
storiesHierarchies={storiesHierarchies}
|
||||||
|
storyFilter={storyFilter}
|
||||||
|
onStoryFilter={filter => store.setStoryFilter(filter)}
|
||||||
|
sidebarAnimations={sidebarAnimations}
|
||||||
|
selectedKind={selectedKind}
|
||||||
|
selectedHierarchy={selectedHierarchy}
|
||||||
|
selectedStory={selectedStory}
|
||||||
|
/>
|
||||||
|
),
|
||||||
active: true,
|
active: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -67,10 +81,8 @@ export const mapper = store => {
|
|||||||
selectedStory,
|
selectedStory,
|
||||||
selectedHierarchy,
|
selectedHierarchy,
|
||||||
onSelectStory: (kind, story) => store.selectStory(kind, story),
|
onSelectStory: (kind, story) => store.selectStory(kind, story),
|
||||||
// shortcutOptions,
|
|
||||||
storyFilter,
|
storyFilter,
|
||||||
onStoryFilter: filter => store.setStoryFilter(filter),
|
onStoryFilter: filter => store.setStoryFilter(filter),
|
||||||
openShortcutsHelp: () => store.toggleShortcutsHelp(),
|
|
||||||
sidebarAnimations,
|
sidebarAnimations,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -9,7 +9,9 @@ export default inject(stores => {
|
|||||||
const currentOptions = pick(shortcutOptions, 'panel', 'full', 'nav');
|
const currentOptions = pick(shortcutOptions, 'panel', 'full', 'nav');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...currentOptions,
|
options: {
|
||||||
...uiOptions,
|
...currentOptions,
|
||||||
|
...uiOptions,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
})(Root);
|
})(Root);
|
||||||
|
14
lib/ui/src/containers/themeProvider.js
Normal file
14
lib/ui/src/containers/themeProvider.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { ThemeProvider } from 'emotion-theming';
|
||||||
|
|
||||||
|
import { inject } from 'mobx-react';
|
||||||
|
|
||||||
|
export default inject(stores => {
|
||||||
|
const state = stores.store;
|
||||||
|
const {
|
||||||
|
uiOptions: { theme },
|
||||||
|
} = state;
|
||||||
|
|
||||||
|
return {
|
||||||
|
theme,
|
||||||
|
};
|
||||||
|
})(ThemeProvider);
|
@ -6,7 +6,9 @@ import { BrowserRouter as Router } from 'react-router-dom';
|
|||||||
import { createBrowserHistory } from 'history';
|
import { createBrowserHistory } from 'history';
|
||||||
|
|
||||||
import App from './app';
|
import App from './app';
|
||||||
|
import ThemeProvider from './containers/themeProvider';
|
||||||
import Provider from './provider';
|
import Provider from './provider';
|
||||||
|
|
||||||
import initProviderApi from './init-provider-api';
|
import initProviderApi from './init-provider-api';
|
||||||
import initHistoryHandler from './init-history-handler';
|
import initHistoryHandler from './init-history-handler';
|
||||||
import initKeyHandler from './init-key-handler';
|
import initKeyHandler from './init-key-handler';
|
||||||
@ -40,7 +42,9 @@ function renderStorybookUI(domNode, provider) {
|
|||||||
<Container>
|
<Container>
|
||||||
<MobxProvider store={store}>
|
<MobxProvider store={store}>
|
||||||
<Router>
|
<Router>
|
||||||
<App />
|
<ThemeProvider>
|
||||||
|
<App />
|
||||||
|
</ThemeProvider>
|
||||||
</Router>
|
</Router>
|
||||||
</MobxProvider>
|
</MobxProvider>
|
||||||
</Container>
|
</Container>
|
||||||
|
@ -11,7 +11,9 @@ const QueryRoute = ({ path, render }) => (
|
|||||||
|
|
||||||
// keep backwards compatibility by asserting /components as default
|
// keep backwards compatibility by asserting /components as default
|
||||||
const match = matchPath(path, { path: query.path || '/components' });
|
const match = matchPath(path, { path: query.path || '/components' });
|
||||||
if (match) return render({ ...props, location, match, query });
|
if (match) {
|
||||||
|
return render({ ...props, location, match, query });
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}}
|
}}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { console as logger } from 'global';
|
|
||||||
import { observable, action, set } from 'mobx';
|
import { observable, action, set } from 'mobx';
|
||||||
import pick from 'lodash.pick';
|
import pick from 'lodash.pick';
|
||||||
|
import { themes } from '@storybook/components';
|
||||||
|
|
||||||
import { features } from './libs/key_events';
|
import { features } from './libs/key_events';
|
||||||
|
|
||||||
@ -28,12 +28,15 @@ function ensureStory(stories, selectedKind, selectedStory) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function ensurePanel(panels, selectedPanel, currentPanel) {
|
export function ensurePanel(panels, selectedPanel, currentPanel) {
|
||||||
if (Object.keys(panels).indexOf(selectedPanel) >= 0) return selectedPanel;
|
const keys = Object.keys(panels);
|
||||||
// if the selected panel is non-existant, select the current panel
|
|
||||||
// and output to console all available panels
|
if (keys.indexOf(selectedPanel) >= 0) {
|
||||||
logger.group('Available Panels ID:');
|
return selectedPanel;
|
||||||
Object.keys(panels).forEach(panelID => logger.log(`${panelID} (${panels[panelID].title})`));
|
}
|
||||||
logger.groupEnd('Available Panels ID:');
|
|
||||||
|
if (keys.length) {
|
||||||
|
return keys[0];
|
||||||
|
}
|
||||||
return currentPanel;
|
return currentPanel;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,9 +44,7 @@ const createStore = ({ provider }) => {
|
|||||||
const store = observable(
|
const store = observable(
|
||||||
{
|
{
|
||||||
stories: [],
|
stories: [],
|
||||||
showShortcutsHelp: false,
|
|
||||||
storyFilter: null,
|
storyFilter: null,
|
||||||
selectedAddonPanel: null,
|
|
||||||
shortcutOptions: {
|
shortcutOptions: {
|
||||||
full: false,
|
full: false,
|
||||||
nav: true,
|
nav: true,
|
||||||
@ -59,7 +60,7 @@ const createStore = ({ provider }) => {
|
|||||||
hierarchySeparator: '/',
|
hierarchySeparator: '/',
|
||||||
hierarchyRootSeparator: null,
|
hierarchyRootSeparator: null,
|
||||||
sidebarAnimations: true,
|
sidebarAnimations: true,
|
||||||
theme: null,
|
theme: themes.normal,
|
||||||
},
|
},
|
||||||
customQueryParams: {},
|
customQueryParams: {},
|
||||||
|
|
||||||
@ -67,20 +68,24 @@ const createStore = ({ provider }) => {
|
|||||||
return () => provider.renderPreview(this.selectedKind, this.selectedStory);
|
return () => provider.renderPreview(this.selectedKind, this.selectedStory);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
selectedPanelValue: null,
|
||||||
|
get selectedPanel() {
|
||||||
|
return ensurePanel(this.panels, this.selectedPanelValue, this.selectedPanelValue);
|
||||||
|
},
|
||||||
|
set selectedPanel(value) {
|
||||||
|
this.selectedPanelValue = value;
|
||||||
|
},
|
||||||
|
|
||||||
get panels() {
|
get panels() {
|
||||||
return provider.getPanels();
|
return provider.getPanels();
|
||||||
},
|
},
|
||||||
|
|
||||||
setOptions(options) {
|
setOptions(options) {
|
||||||
const { selectedAddonPanel, ...uiOptions } = options;
|
const { selectedPanel, ...uiOptions } = options;
|
||||||
const newOptions = pick(uiOptions, Object.keys(this.uiOptions));
|
const newOptions = pick(uiOptions, Object.keys(this.uiOptions));
|
||||||
|
|
||||||
if (selectedAddonPanel) {
|
if (selectedPanel) {
|
||||||
this.selectedAddonPanel = ensurePanel(
|
set(this.selectedPanel, ensurePanel(this.panels, selectedPanel, this.selectedPanel));
|
||||||
this.panels,
|
|
||||||
selectedAddonPanel,
|
|
||||||
this.selectedAddonPanel
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
set(this.uiOptions, newOptions);
|
set(this.uiOptions, newOptions);
|
||||||
@ -155,9 +160,13 @@ const createStore = ({ provider }) => {
|
|||||||
return {
|
return {
|
||||||
selectedKind: this.selectedKind,
|
selectedKind: this.selectedKind,
|
||||||
selectedStory: this.selectedStory,
|
selectedStory: this.selectedStory,
|
||||||
full: this.shortcutOptions.full,
|
selectedPanel: this.selectedPanel,
|
||||||
panel: this.shortcutOptions.panel,
|
full: Number(Boolean(this.shortcutOptions.full)),
|
||||||
nav: this.shortcutOptions.nav,
|
panel:
|
||||||
|
this.shortcutOptions.panel === 'right' || this.shortcutOptions.panel === 'bottom'
|
||||||
|
? this.shortcutOptions.panel
|
||||||
|
: false,
|
||||||
|
nav: Number(Boolean(this.shortcutOptions.nav)),
|
||||||
...this.customQueryParams,
|
...this.customQueryParams,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@ -172,7 +181,7 @@ const createStore = ({ provider }) => {
|
|||||||
},
|
},
|
||||||
|
|
||||||
selectAddonPanel(panelName) {
|
selectAddonPanel(panelName) {
|
||||||
this.selectedAddonPanel = panelName;
|
this.selectedPanel = panelName;
|
||||||
},
|
},
|
||||||
|
|
||||||
setStories(stories) {
|
setStories(stories) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user