mirror of
https://github.com/storybookjs/storybook.git
synced 2025-04-06 06:31:27 +08:00
FIX tool addons, no longer needing a direct reference to the iframe
- using global styles instead, which is marginally better
This commit is contained in:
parent
8fc746a491
commit
a86e25f040
@ -1,26 +1,25 @@
|
||||
import { document } from 'global';
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import memoize from 'memoizerific';
|
||||
|
||||
import { logger } from '@storybook/client-logger';
|
||||
import { SET_STORIES } from '@storybook/core-events';
|
||||
import { Global } from '@storybook/theming';
|
||||
|
||||
import { Popout, Item, Icons, Icon, IconButton, Title, Detail, List } from '@storybook/components';
|
||||
import * as S from './components';
|
||||
|
||||
import { PARAM_KEY } from './constants';
|
||||
|
||||
const getIframe = () => document.getElementById('storybook-preview-background');
|
||||
const iframeId = 'storybook-preview-background';
|
||||
|
||||
const getState = (props, state) => {
|
||||
const getState = memoize(10)((props, state) => {
|
||||
const data = props.api.getCurrentStoryData();
|
||||
const list = data && data.parameters && data.parameters[PARAM_KEY];
|
||||
|
||||
return list && list.length
|
||||
? list.reduce(
|
||||
(acc, { name, value, default: isSelected }) => {
|
||||
acc.backgrounds.push({ name, value });
|
||||
acc.items.push({ name, value });
|
||||
|
||||
if (isSelected && state.selected !== 'transparent') {
|
||||
if (!list.find(i => i.value === state.selected)) {
|
||||
@ -30,23 +29,14 @@ const getState = (props, state) => {
|
||||
return acc;
|
||||
},
|
||||
{
|
||||
backgrounds: [],
|
||||
items: [],
|
||||
selected: state.selected,
|
||||
}
|
||||
)
|
||||
: {
|
||||
backgrounds: [],
|
||||
items: [],
|
||||
selected: 'transparent',
|
||||
};
|
||||
};
|
||||
|
||||
const apply = memoize(1)((value, iframe) => {
|
||||
if (iframe) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
iframe.style.background = value;
|
||||
} else {
|
||||
logger.error('Cannot find Storybook iframe');
|
||||
}
|
||||
});
|
||||
|
||||
export default class BackgroundTool extends Component {
|
||||
@ -54,31 +44,42 @@ export default class BackgroundTool extends Component {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
backgrounds: [],
|
||||
items: [],
|
||||
selected: 'transparent',
|
||||
};
|
||||
|
||||
this.listener = () => {
|
||||
this.setState({ selected: null });
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { api } = this.props;
|
||||
api.on(SET_STORIES, this.listener);
|
||||
}
|
||||
|
||||
api.on(SET_STORIES, () => {
|
||||
const { state, props } = this;
|
||||
this.setState(getState(props, state));
|
||||
});
|
||||
componentWillUnmount() {
|
||||
const { api } = this.props;
|
||||
api.off(SET_STORIES, this.listener);
|
||||
}
|
||||
|
||||
change = selected => {
|
||||
this.setState({ selected }, this.apply);
|
||||
this.setState({ selected });
|
||||
};
|
||||
|
||||
render() {
|
||||
const { backgrounds, selected } = getState(this.props, this.state);
|
||||
const iframe = getIframe();
|
||||
const { items, selected } = getState(this.props, this.state);
|
||||
|
||||
apply(selected, iframe);
|
||||
return items.length ? (
|
||||
<Fragment>
|
||||
<Global
|
||||
styles={{
|
||||
[`#${iframeId}`]: {
|
||||
background: selected,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
return backgrounds.length ? (
|
||||
<Popout key="backgrounds">
|
||||
<IconButton key="background" title="Backgrounds">
|
||||
<Icons icon="photo" />
|
||||
@ -101,7 +102,7 @@ export default class BackgroundTool extends Component {
|
||||
</Fragment>
|
||||
) : null}
|
||||
|
||||
{backgrounds.map(({ name, value }) => (
|
||||
{items.map(({ name, value }) => (
|
||||
<Item
|
||||
key={name}
|
||||
onClick={() => {
|
||||
@ -117,6 +118,7 @@ export default class BackgroundTool extends Component {
|
||||
</List>
|
||||
)}
|
||||
</Popout>
|
||||
</Fragment>
|
||||
) : null;
|
||||
}
|
||||
}
|
||||
|
@ -1,121 +1,120 @@
|
||||
import { document } from 'global';
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import memoize from 'memoizerific';
|
||||
|
||||
import { Global, css } from '@storybook/theming';
|
||||
import { Global } from '@storybook/theming';
|
||||
|
||||
import { Popout, Item, Icons, Icon, IconButton, Title, List } from '@storybook/components';
|
||||
import { STORY_CHANGED } from '@storybook/core-events';
|
||||
import { logger } from '@storybook/client-logger';
|
||||
import { SET_STORIES } from '@storybook/core-events';
|
||||
|
||||
import { PARAM_KEY } from './constants';
|
||||
|
||||
const toList = memoize(50)(viewports => Object.entries(viewports));
|
||||
const getIframe = memoize(1)(() => document.getElementById('storybook-preview-iframe'));
|
||||
const iframeClass = 'storybook-preview-iframe-viewport';
|
||||
const toList = memoize(50)(items =>
|
||||
items ? Object.entries(items).map(([id, value]) => ({ ...value, id })) : []
|
||||
);
|
||||
const iframeId = 'storybook-preview-background';
|
||||
|
||||
const getState = memoize(10)((props, state) => {
|
||||
const data = props.api.getCurrentStoryData();
|
||||
const list = toList(data && data.parameters && data.parameters[PARAM_KEY]);
|
||||
|
||||
return list && list.length
|
||||
? list.reduce(
|
||||
(acc, { name, styles: value, id }) => {
|
||||
acc.items.push({ name, value, id });
|
||||
|
||||
if (state.selected !== 'responsive') {
|
||||
if (!list.find(i => i.id === state.selected)) {
|
||||
acc.selected = id;
|
||||
}
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{
|
||||
isRotated: state.isRotated,
|
||||
items: [],
|
||||
selected: state.selected,
|
||||
}
|
||||
)
|
||||
: {
|
||||
isRotated: false,
|
||||
items: [],
|
||||
selected: 'responsive',
|
||||
};
|
||||
});
|
||||
|
||||
const flip = ({ width, height }) => ({ height: width, widht: height });
|
||||
|
||||
export default class ViewportTool extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
viewports: {},
|
||||
selected: undefined,
|
||||
isRotated: false,
|
||||
items: [],
|
||||
selected: 'responsive',
|
||||
};
|
||||
|
||||
this.listener = () => {
|
||||
this.setState({});
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { api } = this.props;
|
||||
api.on(STORY_CHANGED, this.onStoryChange);
|
||||
api.on(SET_STORIES, this.listener);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
const { api } = this.props;
|
||||
api.off(STORY_CHANGED, this.onStoryChange);
|
||||
api.off(SET_STORIES, this.listener);
|
||||
}
|
||||
|
||||
onStoryChange = id => {
|
||||
const { api } = this.props;
|
||||
const viewports = api.getParameters(id, PARAM_KEY);
|
||||
|
||||
if (viewports) {
|
||||
this.setState({ viewports });
|
||||
}
|
||||
};
|
||||
|
||||
change = key => {
|
||||
this.setState({ selected: key }, () => {
|
||||
this.apply();
|
||||
});
|
||||
change = selected => {
|
||||
this.setState({ selected });
|
||||
};
|
||||
|
||||
rotate = () => {
|
||||
const { isRotated } = this.state;
|
||||
this.setState({ isRotated: !isRotated }, () => {
|
||||
this.apply();
|
||||
});
|
||||
};
|
||||
|
||||
apply = () => {
|
||||
const iframe = getIframe();
|
||||
const { isRotated, selected, viewports } = this.state;
|
||||
|
||||
if (iframe) {
|
||||
if (selected) {
|
||||
const {
|
||||
styles: { width: a, height: b },
|
||||
} = viewports[selected];
|
||||
iframe.style.width = isRotated ? b : a;
|
||||
iframe.style.height = isRotated ? a : b;
|
||||
|
||||
if (!iframe.classList.item(iframeClass)) {
|
||||
iframe.classList.add(iframeClass);
|
||||
}
|
||||
} else {
|
||||
iframe.style.width = '100%';
|
||||
iframe.style.height = '100%';
|
||||
iframe.classList.remove(iframeClass);
|
||||
}
|
||||
} else {
|
||||
logger.error('Cannot find Storybook iframe');
|
||||
}
|
||||
this.setState({ isRotated: !isRotated });
|
||||
};
|
||||
|
||||
render() {
|
||||
const { viewports, selected } = this.state;
|
||||
const { items, selected, isRotated } = getState(this.props, this.state);
|
||||
const item = items.find(i => i.id === selected);
|
||||
|
||||
const list = toList(viewports);
|
||||
|
||||
if (!list.length) {
|
||||
if (!items.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{item && item.value ? (
|
||||
<Global
|
||||
styles={css({
|
||||
[`.${iframeClass}`]: {
|
||||
styles={{
|
||||
[`#${iframeId}`]: {
|
||||
border: '10px solid black',
|
||||
borderRadius: 4,
|
||||
margin: 10,
|
||||
|
||||
...(isRotated ? flip(item.value || {}) : item.value || {}),
|
||||
},
|
||||
})}
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
<Popout key="viewports">
|
||||
<IconButton key="viewport" title="Change Viewport">
|
||||
<Icons icon="grow" />
|
||||
</IconButton>
|
||||
{({ hide }) => (
|
||||
<List>
|
||||
{selected !== undefined ? (
|
||||
{selected !== 'responsive' ? (
|
||||
<Fragment>
|
||||
<Item
|
||||
key="reset"
|
||||
onClick={() => {
|
||||
hide();
|
||||
this.change(undefined);
|
||||
this.change('responsive');
|
||||
}}
|
||||
>
|
||||
<Icon type="undo" />
|
||||
@ -134,12 +133,12 @@ export default class ViewportTool extends Component {
|
||||
</Fragment>
|
||||
) : null}
|
||||
|
||||
{list.map(([key, { name, type }]) => (
|
||||
{items.map(({ id, name, type }) => (
|
||||
<Item
|
||||
key={key}
|
||||
key={id}
|
||||
onClick={() => {
|
||||
hide();
|
||||
this.change(key);
|
||||
this.change(id);
|
||||
}}
|
||||
>
|
||||
<Icon type={type} />
|
||||
@ -155,14 +154,7 @@ export default class ViewportTool extends Component {
|
||||
}
|
||||
|
||||
ViewportTool.propTypes = {
|
||||
channel: PropTypes.shape({
|
||||
on: PropTypes.func,
|
||||
emit: PropTypes.func,
|
||||
removeListener: PropTypes.func,
|
||||
}).isRequired,
|
||||
api: PropTypes.shape({
|
||||
on: PropTypes.func,
|
||||
getQueryParam: PropTypes.func,
|
||||
setQueryParams: PropTypes.func,
|
||||
}).isRequired,
|
||||
};
|
||||
|
@ -6,11 +6,10 @@ import { ADDON_ID } from './constants';
|
||||
import Tool from './Tool';
|
||||
|
||||
addons.register(ADDON_ID, api => {
|
||||
const channel = addons.getChannel();
|
||||
addons.add(ADDON_ID, {
|
||||
type: types.TOOL,
|
||||
title: 'viewport / media-queries',
|
||||
type: types.TOOL,
|
||||
match: ({ viewMode }) => viewMode === 'story',
|
||||
render: () => <Tool channel={channel} api={api} />,
|
||||
render: () => <Tool api={api} />,
|
||||
});
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user