mirror of
https://github.com/storybookjs/storybook.git
synced 2025-04-04 13:11:20 +08:00
IMPROVE the addon-background & background-viewport to use new APIs
This commit is contained in:
parent
42b25ee076
commit
85bed265d7
@ -1,5 +1,5 @@
|
||||
import { document } from 'global';
|
||||
import React, { Component } from 'react';
|
||||
import React, { Component, ReactNode } from 'react';
|
||||
import memoize from 'memoizerific';
|
||||
import { styled } from '@storybook/theming';
|
||||
|
||||
@ -38,86 +38,90 @@ const ColorIcon = styled.span(
|
||||
interface ColorBlindnessProps {}
|
||||
|
||||
interface ColorBlindnessState {
|
||||
expanded: boolean;
|
||||
filter: string | null;
|
||||
active: string | null;
|
||||
}
|
||||
|
||||
const baseList = [
|
||||
'protanopia',
|
||||
'protanomaly',
|
||||
'deuteranopia',
|
||||
'deuteranomaly',
|
||||
'tritanopia',
|
||||
'tritanomaly',
|
||||
'achromatopsia',
|
||||
'achromatomaly',
|
||||
'mono',
|
||||
];
|
||||
|
||||
export interface Link {
|
||||
id: string;
|
||||
title: ReactNode;
|
||||
right?: ReactNode;
|
||||
active: boolean;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
const getColorList = (active: string | null, set: (i: string | null) => void): Link[] => [
|
||||
...(active !== null
|
||||
? [
|
||||
{
|
||||
id: 'reset',
|
||||
title: 'Reset color filter',
|
||||
onClick: () => {
|
||||
set(null);
|
||||
},
|
||||
right: undefined,
|
||||
active: false,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...baseList.map(i => ({
|
||||
id: i,
|
||||
title: i.charAt(0).toUpperCase() + i.slice(1),
|
||||
onClick: () => {
|
||||
set(i);
|
||||
},
|
||||
right: <ColorIcon filter={i} />,
|
||||
active: active === i,
|
||||
})),
|
||||
];
|
||||
|
||||
export class ColorBlindness extends Component<ColorBlindnessProps, ColorBlindnessState> {
|
||||
state: ColorBlindnessState = {
|
||||
expanded: false,
|
||||
filter: null,
|
||||
active: null,
|
||||
};
|
||||
|
||||
setFilter = (filter: string | null) => {
|
||||
setActive = (active: string | null) => {
|
||||
const iframe = getIframe();
|
||||
|
||||
if (iframe) {
|
||||
iframe.style.filter = getFilter(filter);
|
||||
iframe.style.filter = getFilter(active);
|
||||
this.setState({
|
||||
expanded: false,
|
||||
filter,
|
||||
active,
|
||||
});
|
||||
} else {
|
||||
logger.error('Cannot find Storybook iframe');
|
||||
}
|
||||
};
|
||||
|
||||
onVisibilityChange = (s: boolean) => {
|
||||
const { expanded } = this.state;
|
||||
if (expanded !== s) {
|
||||
this.setState({ expanded: s });
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { filter, expanded } = this.state;
|
||||
|
||||
let colorList = [
|
||||
'protanopia',
|
||||
'protanomaly',
|
||||
'deuteranopia',
|
||||
'deuteranomaly',
|
||||
'tritanopia',
|
||||
'tritanomaly',
|
||||
'achromatopsia',
|
||||
'achromatomaly',
|
||||
'mono',
|
||||
].map(i => ({
|
||||
id: i,
|
||||
title: i.charAt(0).toUpperCase() + i.slice(1),
|
||||
onClick: () => {
|
||||
this.setFilter(i);
|
||||
},
|
||||
right: <ColorIcon filter={i} />,
|
||||
active: filter === i,
|
||||
}));
|
||||
|
||||
if (filter !== null) {
|
||||
colorList = [
|
||||
{
|
||||
id: 'reset',
|
||||
title: 'Reset color filter',
|
||||
onClick: () => {
|
||||
this.setFilter(null);
|
||||
},
|
||||
right: undefined,
|
||||
active: false,
|
||||
},
|
||||
...colorList,
|
||||
];
|
||||
}
|
||||
const { active } = this.state;
|
||||
|
||||
return (
|
||||
<WithTooltip
|
||||
placement="top"
|
||||
trigger="click"
|
||||
tooltipShown={expanded}
|
||||
onVisibilityChange={this.onVisibilityChange}
|
||||
tooltip={<TooltipLinkList links={colorList} />}
|
||||
tooltip={({ onHide }) => {
|
||||
const colorList = getColorList(active, i => {
|
||||
this.setActive(i);
|
||||
onHide();
|
||||
});
|
||||
return <TooltipLinkList links={colorList} />;
|
||||
}}
|
||||
closeOnClick
|
||||
onDoubleClick={() => this.setFilter(null)}
|
||||
onDoubleClick={() => this.setActive(null)}
|
||||
>
|
||||
<IconButton key="filter" active={!!filter} title="Color Blindness Emulation">
|
||||
<IconButton key="filter" active={!!active} title="Color Blindness Emulation">
|
||||
<Icons icon="mirror" />
|
||||
</IconButton>
|
||||
</WithTooltip>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import React, { Component, Fragment, ReactElement } from 'react';
|
||||
import memoize from 'memoizerific';
|
||||
|
||||
import { Combo, Consumer, API } from '@storybook/api';
|
||||
@ -14,7 +14,7 @@ interface Item {
|
||||
title: string;
|
||||
onClick: () => void;
|
||||
value: string;
|
||||
right?: any;
|
||||
right?: ReactElement;
|
||||
}
|
||||
|
||||
interface Input {
|
||||
@ -23,7 +23,7 @@ interface Input {
|
||||
default?: boolean;
|
||||
}
|
||||
|
||||
const iframeId = 'storybook-preview-background';
|
||||
const iframeId = 'storybook-preview-iframe';
|
||||
|
||||
const createBackgroundSelectorItem = memoize(1000)(
|
||||
(
|
||||
@ -71,72 +71,56 @@ const mapper = ({ api, state }: Combo): { items: Input[]; selected: string | nul
|
||||
return { items: list || [], selected };
|
||||
};
|
||||
|
||||
const getDisplayedItems = memoize(10)((list: Input[], selected: string | null, change) => {
|
||||
let availableBackgroundSelectorItems: Item[] = [];
|
||||
const getDisplayedItems = memoize(10)(
|
||||
(
|
||||
list: Input[],
|
||||
selected: string | null,
|
||||
change: (arg: { selected: string; name: string }) => void
|
||||
) => {
|
||||
let availableBackgroundSelectorItems: Item[] = [];
|
||||
|
||||
if (selected !== 'transparent') {
|
||||
availableBackgroundSelectorItems.push(
|
||||
createBackgroundSelectorItem('reset', 'Clear background', 'transparent', null, change)
|
||||
);
|
||||
if (selected !== 'transparent') {
|
||||
availableBackgroundSelectorItems.push(
|
||||
createBackgroundSelectorItem('reset', 'Clear background', 'transparent', null, change)
|
||||
);
|
||||
}
|
||||
|
||||
if (list.length) {
|
||||
availableBackgroundSelectorItems = [
|
||||
...availableBackgroundSelectorItems,
|
||||
...list.map(({ name, value }) =>
|
||||
createBackgroundSelectorItem(null, name, value, true, change)
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
return availableBackgroundSelectorItems;
|
||||
}
|
||||
|
||||
if (list.length) {
|
||||
availableBackgroundSelectorItems = [
|
||||
...availableBackgroundSelectorItems,
|
||||
...list.map(({ name, value }) =>
|
||||
createBackgroundSelectorItem(null, name, value, true, change)
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
return availableBackgroundSelectorItems;
|
||||
});
|
||||
);
|
||||
|
||||
interface GlobalState {
|
||||
name: string | undefined;
|
||||
selected: string | undefined;
|
||||
}
|
||||
|
||||
interface State {
|
||||
expanded: boolean;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
api: API;
|
||||
}
|
||||
|
||||
export class BackgroundSelector extends Component<Props, State> {
|
||||
state: State = {
|
||||
expanded: false,
|
||||
};
|
||||
|
||||
export class BackgroundSelector extends Component<Props> {
|
||||
change = ({ selected, name }: GlobalState) => {
|
||||
const { api } = this.props;
|
||||
const { expanded } = this.state;
|
||||
if (expanded) {
|
||||
this.setState({ expanded: false });
|
||||
}
|
||||
if (typeof selected === 'string') {
|
||||
api.setAddonState<string>(PARAM_KEY, selected);
|
||||
}
|
||||
api.emit(EVENTS.UPDATE, { selected, name });
|
||||
};
|
||||
|
||||
onVisibilityChange = (s: boolean) => {
|
||||
const { expanded } = this.state;
|
||||
if (expanded !== s) {
|
||||
this.setState({ expanded: s });
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { expanded } = this.state;
|
||||
|
||||
return (
|
||||
<Consumer filter={mapper}>
|
||||
{({ items, selected }: ReturnType<typeof mapper>) => {
|
||||
const selectedBackgroundColor = getSelectedBackgroundColor(items, selected);
|
||||
const links = getDisplayedItems(items, selectedBackgroundColor, this.change);
|
||||
|
||||
return items.length ? (
|
||||
<Fragment>
|
||||
@ -155,9 +139,14 @@ export class BackgroundSelector extends Component<Props, State> {
|
||||
<WithTooltip
|
||||
placement="top"
|
||||
trigger="click"
|
||||
tooltipShown={expanded}
|
||||
onVisibilityChange={this.onVisibilityChange}
|
||||
tooltip={<TooltipLinkList links={links} />}
|
||||
tooltip={({ onHide }) => (
|
||||
<TooltipLinkList
|
||||
links={getDisplayedItems(items, selectedBackgroundColor, i => {
|
||||
this.change(i);
|
||||
onHide();
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
closeOnClick
|
||||
>
|
||||
<IconButton
|
||||
|
@ -1,119 +1,77 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import React, { Fragment, ReactNode, useEffect, useRef, FunctionComponent } from 'react';
|
||||
import memoize from 'memoizerific';
|
||||
import deprecate from 'util-deprecate';
|
||||
|
||||
import { styled, Global } from '@storybook/theming';
|
||||
import { styled, Global, Theme, withTheme } from '@storybook/theming';
|
||||
|
||||
import { Icons, IconButton, WithTooltip, TooltipLinkList } from '@storybook/components';
|
||||
import { SET_STORIES } from '@storybook/core-events';
|
||||
|
||||
import { PARAM_KEY } from './constants';
|
||||
import { INITIAL_VIEWPORTS, DEFAULT_VIEWPORT } from './defaults';
|
||||
import { ViewportAddonParameter, ViewportMap, ViewportStyles } from './models';
|
||||
import { useParameter, useAddonState } from '@storybook/api';
|
||||
import { PARAM_KEY, ADDON_ID } from './constants';
|
||||
import { ViewportAddonParameter, ViewportMap, ViewportStyles, Viewport, Styles } from './models';
|
||||
|
||||
const toList = memoize(50)((items: ViewportMap) =>
|
||||
items ? Object.entries(items).map(([id, value]) => ({ ...value, id })) : []
|
||||
);
|
||||
const iframeId = 'storybook-preview-iframe';
|
||||
|
||||
interface ViewportVM {
|
||||
interface ViewportItem {
|
||||
id: string;
|
||||
title: string;
|
||||
onClick: () => void;
|
||||
right: string;
|
||||
value: ViewportStyles;
|
||||
styles: Styles;
|
||||
type: 'desktop' | 'mobile' | 'tablet' | 'other';
|
||||
default?: boolean;
|
||||
}
|
||||
|
||||
const createItem = memoize(1000)(
|
||||
(id: string, name: string, value: ViewportStyles, change: (...args: unknown[]) => void) => {
|
||||
const result: ViewportVM = {
|
||||
id: id || name,
|
||||
title: name,
|
||||
onClick: () => {
|
||||
change({ selected: id, expanded: false });
|
||||
},
|
||||
right: `${value.width.replace('px', '')}x${value.height.replace('px', '')}`,
|
||||
value,
|
||||
};
|
||||
return result;
|
||||
}
|
||||
);
|
||||
const toList = memoize(50)((items: ViewportMap): ViewportItem[] => [
|
||||
...baseViewports,
|
||||
...Object.entries(items).map(([id, { name, ...rest }]) => ({ ...rest, id, title: name })),
|
||||
]);
|
||||
|
||||
const responsiveViewport: ViewportItem = {
|
||||
id: 'reset',
|
||||
title: 'Reset viewport',
|
||||
styles: null,
|
||||
type: 'other',
|
||||
};
|
||||
|
||||
const baseViewports: ViewportItem[] = [responsiveViewport];
|
||||
|
||||
const toLinks = memoize(50)((list: ViewportItem[], active: LinkBase, set, state, close): Link[] => {
|
||||
return list
|
||||
.map(i => {
|
||||
switch (i.id) {
|
||||
case responsiveViewport.id: {
|
||||
if (active.id === i.id) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line no-fallthrough
|
||||
default: {
|
||||
return {
|
||||
...i,
|
||||
onClick: () => {
|
||||
set({ ...state, selected: i.id });
|
||||
close();
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
})
|
||||
.filter(Boolean);
|
||||
});
|
||||
|
||||
const iframeId = 'storybook-preview-iframe';
|
||||
const wrapperId = 'storybook-preview-wrapper';
|
||||
|
||||
interface LinkBase {
|
||||
id: string;
|
||||
title: string;
|
||||
right?: ReactNode;
|
||||
type: 'desktop' | 'mobile' | 'tablet' | 'other';
|
||||
styles: ViewportStyles | ((s: ViewportStyles) => ViewportStyles) | null;
|
||||
}
|
||||
|
||||
interface Link extends LinkBase {
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
const flip = ({ width, height }: ViewportStyles) => ({ height: width, width: height });
|
||||
|
||||
const deprecatedViewportString = deprecate(
|
||||
() => 0,
|
||||
'The viewport parameter must be an object with keys `viewports` and `defaultViewport`'
|
||||
);
|
||||
const deprecateOnViewportChange = deprecate(
|
||||
() => 0,
|
||||
'The viewport parameter `onViewportChange` is no longer supported'
|
||||
);
|
||||
|
||||
const getState = memoize(10)(
|
||||
(
|
||||
props: ViewportToolProps,
|
||||
state: ViewportToolState,
|
||||
change: (statePatch: Partial<ViewportToolState>) => void
|
||||
) => {
|
||||
const data = props.api.getCurrentStoryData();
|
||||
const parameters: ViewportAddonParameter =
|
||||
data && (data as any).parameters && (data as any).parameters[PARAM_KEY];
|
||||
|
||||
if (parameters && typeof parameters !== 'object') {
|
||||
deprecatedViewportString();
|
||||
}
|
||||
|
||||
const { disable, viewports, defaultViewport, onViewportChange } = parameters || ({} as any);
|
||||
|
||||
if (onViewportChange) {
|
||||
deprecateOnViewportChange();
|
||||
}
|
||||
|
||||
const list = disable ? [] : toList(viewports || INITIAL_VIEWPORTS);
|
||||
const viewportVMList = list.map(({ id, name, styles: value }) =>
|
||||
createItem(id, name, value, change)
|
||||
);
|
||||
|
||||
const selected =
|
||||
state.selected === 'responsive' || list.find(i => i.id === state.selected)
|
||||
? state.selected
|
||||
: list.find(i => i.default) || defaultViewport || DEFAULT_VIEWPORT;
|
||||
|
||||
const resets: ViewportVM[] =
|
||||
selected !== 'responsive'
|
||||
? [
|
||||
{
|
||||
id: 'reset',
|
||||
title: 'Reset viewport',
|
||||
onClick: () => {
|
||||
change({ selected: undefined, expanded: false });
|
||||
},
|
||||
right: undefined,
|
||||
value: undefined,
|
||||
},
|
||||
{
|
||||
id: 'rotate',
|
||||
title: 'Rotate viewport',
|
||||
onClick: () => {
|
||||
change({ isRotated: !state.isRotated, expanded: false });
|
||||
},
|
||||
right: undefined,
|
||||
value: undefined,
|
||||
},
|
||||
]
|
||||
: [];
|
||||
|
||||
const items = viewportVMList.length !== 0 ? resets.concat(viewportVMList) : [];
|
||||
|
||||
return {
|
||||
isRotated: state.isRotated,
|
||||
items,
|
||||
selected,
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
const ActiveViewportSize = styled.div(() => ({
|
||||
display: 'inline-flex',
|
||||
}));
|
||||
@ -144,125 +102,132 @@ const IconButtonLabel = styled.div<{}>(({ theme }) => ({
|
||||
|
||||
interface ViewportToolState {
|
||||
isRotated: boolean;
|
||||
items: any[];
|
||||
selected: string;
|
||||
expanded: boolean;
|
||||
}
|
||||
interface ViewportToolProps {
|
||||
api: any;
|
||||
selected: string | null;
|
||||
}
|
||||
|
||||
export class ViewportTool extends Component<ViewportToolProps, ViewportToolState> {
|
||||
listener: () => void;
|
||||
const getStyles = (
|
||||
prevStyles: ViewportStyles,
|
||||
styles: Styles,
|
||||
isRotated: boolean
|
||||
): ViewportStyles => {
|
||||
if (styles === null) {
|
||||
return null;
|
||||
}
|
||||
const result = typeof styles === 'function' ? styles(prevStyles) : styles;
|
||||
return isRotated ? flip(result) : result;
|
||||
};
|
||||
|
||||
constructor(props: ViewportToolProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
export const ViewportTool: FunctionComponent<{}> = React.memo(
|
||||
withTheme(({ theme }: { theme: Theme }) => {
|
||||
const { viewports, defaultViewport, disable } = useParameter<ViewportAddonParameter>(
|
||||
PARAM_KEY,
|
||||
{
|
||||
viewports: {},
|
||||
defaultViewport: responsiveViewport.id,
|
||||
}
|
||||
);
|
||||
const [state, setState] = useAddonState<ViewportToolState>(ADDON_ID, {
|
||||
selected: defaultViewport || responsiveViewport.id,
|
||||
isRotated: false,
|
||||
items: [],
|
||||
selected: 'responsive',
|
||||
expanded: false,
|
||||
};
|
||||
});
|
||||
const list = toList(viewports);
|
||||
|
||||
this.listener = () => {
|
||||
this.setState({
|
||||
selected: null,
|
||||
});
|
||||
};
|
||||
}
|
||||
const { selected, isRotated } = state;
|
||||
const item =
|
||||
list.find(i => i.id === selected) ||
|
||||
list.find(i => i.id === defaultViewport) ||
|
||||
list.find(i => i.default) ||
|
||||
responsiveViewport;
|
||||
|
||||
componentDidMount() {
|
||||
const { api } = this.props;
|
||||
api.on(SET_STORIES, this.listener);
|
||||
}
|
||||
const ref = useRef<ViewportStyles>();
|
||||
|
||||
componentWillUnmount() {
|
||||
const { api } = this.props;
|
||||
api.off(SET_STORIES, this.listener);
|
||||
}
|
||||
const styles = item
|
||||
? getStyles(ref.current, item.styles, isRotated)
|
||||
: (responsiveViewport.styles as ViewportStyles);
|
||||
|
||||
// @ts-ignore
|
||||
change = (...args: any[]) => this.setState(...args);
|
||||
useEffect(() => {
|
||||
ref.current = styles;
|
||||
}, [item]);
|
||||
|
||||
flipViewport = () =>
|
||||
this.setState(({ isRotated }: { isRotated: boolean }) => ({
|
||||
isRotated: !isRotated,
|
||||
expanded: false,
|
||||
}));
|
||||
|
||||
resetViewport = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||
e.stopPropagation();
|
||||
e.nativeEvent.stopImmediatePropagation();
|
||||
|
||||
this.setState({ selected: undefined, expanded: false });
|
||||
};
|
||||
|
||||
render() {
|
||||
const { expanded } = this.state;
|
||||
const { items, selected, isRotated } = getState(this.props, this.state, this.change);
|
||||
const item = items.find(i => i.id === selected);
|
||||
|
||||
let viewportX = '0';
|
||||
let viewportY = '0';
|
||||
let viewportTitle = '';
|
||||
if (item) {
|
||||
const height = item.value.height.replace('px', '');
|
||||
const width = item.value.width.replace('px', '');
|
||||
|
||||
viewportX = isRotated ? height : width;
|
||||
viewportY = isRotated ? width : height;
|
||||
|
||||
viewportTitle = isRotated ? `${item.title} (L)` : `${item.title} (P)`;
|
||||
if (styles === null) {
|
||||
// debugger;
|
||||
}
|
||||
|
||||
return items.length ? (
|
||||
<Fragment>
|
||||
{item ? (
|
||||
<Global
|
||||
styles={{
|
||||
[`#${iframeId}`]: {
|
||||
position: 'relative',
|
||||
display: 'block',
|
||||
margin: '10px auto',
|
||||
border: '1px solid #888',
|
||||
borderRadius: 4,
|
||||
boxShadow: '0 4px 8px 0 rgba(0,0,0,0.12), 0 2px 4px 0 rgba(0,0,0,0.08);',
|
||||
boxSizing: 'content-box',
|
||||
if (disable || Object.entries(viewports).length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
...(isRotated ? flip(item.value) : item.value),
|
||||
},
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
return (
|
||||
<Fragment>
|
||||
<WithTooltip
|
||||
placement="top"
|
||||
trigger="click"
|
||||
tooltipShown={expanded}
|
||||
onVisibilityChange={s => this.setState({ expanded: s })}
|
||||
tooltip={<TooltipLinkList links={items} />}
|
||||
tooltip={({ onHide }) => (
|
||||
<TooltipLinkList links={toLinks(list, item, setState, state, onHide)} />
|
||||
)}
|
||||
closeOnClick
|
||||
>
|
||||
<IconButtonWithLabel
|
||||
key="viewport"
|
||||
title="Change the size of the preview"
|
||||
active={!!item}
|
||||
onDoubleClick={e => this.resetViewport(e)}
|
||||
active={!!styles}
|
||||
onDoubleClick={() => {
|
||||
setState({ ...state, selected: responsiveViewport.id });
|
||||
}}
|
||||
>
|
||||
<Icons icon="grow" />
|
||||
<IconButtonLabel>{viewportTitle}</IconButtonLabel>
|
||||
{styles ? (
|
||||
<IconButtonLabel>
|
||||
{isRotated ? `${item.title} (L)` : `${item.title} (P)`}
|
||||
</IconButtonLabel>
|
||||
) : null}
|
||||
</IconButtonWithLabel>
|
||||
</WithTooltip>
|
||||
{item ? (
|
||||
|
||||
{styles ? (
|
||||
<ActiveViewportSize>
|
||||
<ActiveViewportLabel title="Viewport width">{viewportX}</ActiveViewportLabel>
|
||||
<IconButton key="viewport-rotate" title="Rotate viewport" onClick={this.flipViewport}>
|
||||
<Global
|
||||
styles={{
|
||||
[`#${iframeId}`]: {
|
||||
margin: `auto`,
|
||||
transition: 'width .3s, height .3s',
|
||||
position: 'relative',
|
||||
border: `${theme.layoutMargin}px solid black`,
|
||||
borderRadius: theme.appBorderRadius,
|
||||
boxShadow:
|
||||
'0 0 100px 1000px rgba(0,0,0,0.5), 0 4px 8px 0 rgba(0,0,0,0.12), 0 2px 4px 0 rgba(0,0,0,0.08)',
|
||||
|
||||
...styles,
|
||||
},
|
||||
[`#${wrapperId}`]: {
|
||||
padding: theme.layoutMargin,
|
||||
display: 'flex',
|
||||
alignContent: 'center',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
justifyItems: 'center',
|
||||
overflow: 'auto',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<ActiveViewportLabel title="Viewport width">
|
||||
{styles.width.replace('px', '')}
|
||||
</ActiveViewportLabel>
|
||||
<IconButton
|
||||
key="viewport-rotate"
|
||||
title="Rotate viewport"
|
||||
onClick={() => {
|
||||
setState({ ...state, isRotated: !isRotated });
|
||||
}}
|
||||
>
|
||||
<Icons icon="transfer" />
|
||||
</IconButton>
|
||||
<ActiveViewportLabel title="Viewport height">{viewportY}</ActiveViewportLabel>
|
||||
<ActiveViewportLabel title="Viewport height">
|
||||
{styles.height.replace('px', '')}
|
||||
</ActiveViewportLabel>
|
||||
</ActiveViewportSize>
|
||||
) : null}
|
||||
</Fragment>
|
||||
) : null;
|
||||
}
|
||||
}
|
||||
);
|
||||
})
|
||||
);
|
||||
|
@ -1,7 +1,9 @@
|
||||
export type Styles = ViewportStyles | ((s: ViewportStyles) => ViewportStyles) | null;
|
||||
|
||||
export interface Viewport {
|
||||
name: string;
|
||||
styles: ViewportStyles;
|
||||
type: 'desktop' | 'mobile' | 'tablet';
|
||||
styles: Styles;
|
||||
type: 'desktop' | 'mobile' | 'tablet' | 'other';
|
||||
/*
|
||||
* @deprecated
|
||||
* Deprecated option?
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { ViewportMap } from './Viewport';
|
||||
|
||||
export interface ViewportAddonParameter {
|
||||
disable: boolean;
|
||||
defaultViewport: string;
|
||||
disable?: boolean;
|
||||
defaultViewport?: string;
|
||||
viewports: ViewportMap;
|
||||
/*
|
||||
* @deprecated
|
||||
|
@ -5,11 +5,11 @@ import { ADDON_ID } from './constants';
|
||||
|
||||
import { ViewportTool } from './Tool';
|
||||
|
||||
addons.register(ADDON_ID, api => {
|
||||
addons.register(ADDON_ID, () => {
|
||||
addons.add(ADDON_ID, {
|
||||
title: 'viewport / media-queries',
|
||||
type: types.TOOL,
|
||||
match: ({ viewMode }) => viewMode === 'story',
|
||||
render: () => <ViewportTool api={api} />,
|
||||
render: () => <ViewportTool />,
|
||||
});
|
||||
});
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { ReactElement, Component, useContext, useEffect } from 'react';
|
||||
import React, { ReactElement, Component, useContext, useEffect, useRef } from 'react';
|
||||
import memoize from 'memoizerific';
|
||||
// @ts-ignore shallow-equal is not in DefinitelyTyped
|
||||
import shallowEqualObjects from 'shallow-equal/objects';
|
||||
@ -314,6 +314,7 @@ type StateMerger<S> = (input: S) => S;
|
||||
|
||||
export function useAddonState<S>(addonId: string, defaultState?: S) {
|
||||
const api = useStorybookApi();
|
||||
const ref = useRef<{ [k: string]: boolean }>({});
|
||||
|
||||
const existingState = api.getAddonState<S>(addonId);
|
||||
const state = orDefault<S>(existingState, defaultState);
|
||||
@ -322,8 +323,12 @@ export function useAddonState<S>(addonId: string, defaultState?: S) {
|
||||
return api.setAddonState<S>(addonId, newStateOrMerger, options);
|
||||
};
|
||||
|
||||
if (typeof existingState === 'undefined') {
|
||||
api.setAddonState<S>(addonId, state);
|
||||
if (typeof existingState === 'undefined' && typeof state !== 'undefined') {
|
||||
if (!ref.current[addonId]) {
|
||||
// debugger;
|
||||
api.setAddonState<S>(addonId, state);
|
||||
ref.current[addonId] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return [state, setState] as [
|
||||
|
@ -115,6 +115,7 @@ export default ({ provider, store }: Module) => {
|
||||
options?: Options
|
||||
): Promise<S> {
|
||||
let nextState;
|
||||
const { addons: existing } = store.getState();
|
||||
if (typeof newStateOrMerger === 'function') {
|
||||
const merger = newStateOrMerger as StateMerger<S>;
|
||||
nextState = merger(api.getAddonState<S>(addonId));
|
||||
@ -122,10 +123,11 @@ export default ({ provider, store }: Module) => {
|
||||
nextState = newStateOrMerger;
|
||||
}
|
||||
return store
|
||||
.setState({ addons: { [addonId]: nextState } }, options)
|
||||
.setState({ addons: { ...existing, [addonId]: nextState } }, options)
|
||||
.then(() => api.getAddonState(addonId));
|
||||
},
|
||||
getAddonState: addonId => {
|
||||
// debugger;
|
||||
return store.getState().addons[addonId];
|
||||
},
|
||||
};
|
||||
|
@ -41,7 +41,7 @@ export const links = [
|
||||
storiesOf('basics/Tooltip/TooltipLinkList', module)
|
||||
.addDecorator(storyFn => (
|
||||
<div style={{ height: '300px' }}>
|
||||
<WithTooltip placement="top" trigger="click" tooltipShown tooltip={storyFn()}>
|
||||
<WithTooltip placement="top" trigger="click" startOpen tooltip={storyFn()}>
|
||||
<div>Tooltip</div>
|
||||
</WithTooltip>
|
||||
</div>
|
||||
|
@ -18,6 +18,10 @@ const TargetSvgContainer = styled.g<{ mode: string }>`
|
||||
cursor: ${props => (props.mode === 'hover' ? 'default' : 'pointer')};
|
||||
`;
|
||||
|
||||
interface WithHideFn {
|
||||
onHide: () => void;
|
||||
}
|
||||
|
||||
export interface WithTooltipPureProps {
|
||||
svg?: boolean;
|
||||
trigger?: 'none' | 'hover' | 'click' | 'right-click';
|
||||
@ -25,7 +29,7 @@ export interface WithTooltipPureProps {
|
||||
placement?: Placement;
|
||||
modifiers?: Modifiers;
|
||||
hasChrome?: boolean;
|
||||
tooltip: ReactNode;
|
||||
tooltip: ReactNode | ((p: WithHideFn) => ReactNode);
|
||||
children: ReactNode;
|
||||
tooltipShown?: boolean;
|
||||
onVisibilityChange?: (visibility: boolean) => void;
|
||||
|
@ -2,6 +2,18 @@ import window from 'global';
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { styled } from '@storybook/theming';
|
||||
|
||||
const StyledIframe = styled.iframe({
|
||||
position: 'absolute',
|
||||
display: 'block',
|
||||
boxSizing: 'content-box',
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
border: '0 none',
|
||||
transition: 'background .3s',
|
||||
});
|
||||
|
||||
export class IFrame extends Component {
|
||||
iframe = null;
|
||||
|
||||
@ -34,7 +46,7 @@ export class IFrame extends Component {
|
||||
render() {
|
||||
const { id, title, src, allowFullScreen, scale, ...rest } = this.props;
|
||||
return (
|
||||
<iframe
|
||||
<StyledIframe
|
||||
scrolling="yes"
|
||||
id={id}
|
||||
title={title}
|
||||
|
@ -64,18 +64,23 @@ const ActualPreview = ({
|
||||
);
|
||||
};
|
||||
|
||||
const IframeWrapper = styled.div(({ theme }) => ({
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
background: theme.background.content,
|
||||
}));
|
||||
|
||||
const defaultWrappers = [
|
||||
{ render: p => <div hidden={!p.active}>{p.children}</div> },
|
||||
{
|
||||
render: p => (
|
||||
<BackgroundConsumer>
|
||||
{({ value, grid }) => (
|
||||
<Background id="storybook-preview-background" value={value}>
|
||||
{grid ? <Grid /> : null}
|
||||
{p.children}
|
||||
</Background>
|
||||
)}
|
||||
</BackgroundConsumer>
|
||||
<IframeWrapper id="storybook-preview-wrapper" hidden={!p.active}>
|
||||
{p.children}
|
||||
</IframeWrapper>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
Loading…
x
Reference in New Issue
Block a user