mirror of
https://github.com/storybookjs/storybook.git
synced 2025-04-04 23:01:16 +08:00
REORGANISE tools & preview
This commit is contained in:
parent
3503ccb113
commit
9d39929a6b
@ -278,7 +278,8 @@ class ManagerProvider extends Component<Props, State> {
|
||||
...state,
|
||||
location: props.location,
|
||||
path: props.path,
|
||||
viewMode: props.viewMode,
|
||||
// if its a docsOnly page, even the 'story' view mode is considered 'docs'
|
||||
viewMode: (props.docsMode && props.viewMode) === 'story' ? 'docs' : props.viewMode,
|
||||
storyId: props.storyId,
|
||||
};
|
||||
}
|
||||
|
@ -62,6 +62,7 @@ export class IFrame extends Component<IFrameProps & IframeHTMLAttributes<HTMLIFr
|
||||
const { id, title, src, allowFullScreen, scale, ...rest } = this.props;
|
||||
return (
|
||||
<StyledIframe
|
||||
data-is-storybook="true"
|
||||
scrolling="yes"
|
||||
id={id}
|
||||
title={title}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { types, Addon } from '@storybook/addons';
|
||||
import { API, State } from '@storybook/api';
|
||||
import { PreviewProps } from './PreviewProps';
|
||||
import { PreviewProps } from './utils/types';
|
||||
|
||||
export const previewProps: PreviewProps = {
|
||||
id: 'string',
|
||||
@ -22,7 +22,23 @@ export const previewProps: PreviewProps = {
|
||||
]
|
||||
: []) as API['getElements'],
|
||||
} as any) as API,
|
||||
storyId: 'string',
|
||||
story: {
|
||||
id: 'storyId',
|
||||
depth: 1,
|
||||
isComponent: false,
|
||||
isLeaf: true,
|
||||
isRoot: false,
|
||||
kind: 'kind',
|
||||
name: 'story name',
|
||||
parent: 'root',
|
||||
children: [],
|
||||
knownAs: 'storyId',
|
||||
parameters: {
|
||||
filename: '',
|
||||
options: {},
|
||||
docsOnly: false,
|
||||
},
|
||||
},
|
||||
path: 'string',
|
||||
viewMode: 'story',
|
||||
location: ({} as any) as State['location'],
|
||||
@ -35,5 +51,5 @@ export const previewProps: PreviewProps = {
|
||||
withLoader: false,
|
||||
docsOnly: false,
|
||||
description: '',
|
||||
parameters: {},
|
||||
refs: {},
|
||||
};
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React, { Fragment, FunctionComponent, useMemo, useEffect } from 'react';
|
||||
import { API, Consumer, Combo, State } from '@storybook/api';
|
||||
import { API, Consumer, Combo } from '@storybook/api';
|
||||
import { SET_CURRENT_STORY } from '@storybook/core-events';
|
||||
import addons, { types, Addon } from '@storybook/addons';
|
||||
import merge from '@storybook/api/dist/lib/merge';
|
||||
@ -7,17 +7,18 @@ import { Loader } from '@storybook/components';
|
||||
|
||||
import { Helmet } from 'react-helmet-async';
|
||||
|
||||
import { Toolbar, defaultTools, defaultToolsExtra, createTabsTool } from './toolbar';
|
||||
import { Location } from '@storybook/router';
|
||||
|
||||
import * as S from './components';
|
||||
import * as S from './utils/components';
|
||||
|
||||
import { ZoomProvider, ZoomConsumer } from './zoom';
|
||||
import { ZoomProvider, ZoomConsumer } from './tools/zoom';
|
||||
|
||||
import { IFrame } from './iframe';
|
||||
import { PreviewProps, ApplyWrappersProps, IframeRenderer } from './PreviewProps';
|
||||
import { PreviewProps, ApplyWrappersProps, IframeRenderer } from './utils/types';
|
||||
|
||||
import { defaultWrappers, ApplyWrappers } from './wrappers';
|
||||
import { stringifyQueryParams } from './stringifyQueryParams';
|
||||
import { stringifyQueryParams } from './utils/stringifyQueryParams';
|
||||
import { ToolbarComp } from './toolbar';
|
||||
|
||||
export const renderIframe: IframeRenderer = (
|
||||
storyId,
|
||||
@ -39,12 +40,11 @@ export const renderIframe: IframeRenderer = (
|
||||
|
||||
const getWrapper = (getFn: API['getElements']) => Object.values(getFn<Addon>(types.PREVIEW));
|
||||
const getTabs = (getFn: API['getElements']) => Object.values(getFn<Addon>(types.TAB));
|
||||
const getTools = (getFn: API['getElements']) => Object.values(getFn<Addon>(types.TOOL));
|
||||
const getToolsExtra = (getFn: API['getElements']) => Object.values(getFn<Addon>(types.TOOLEXTRA));
|
||||
|
||||
const getDocumentTitle = (description: string) => {
|
||||
return description ? `${description} ⋅ Storybook` : 'Storybook';
|
||||
};
|
||||
export const getTools = (getFn: API['getElements']) => Object.values(getFn<Addon>(types.TOOL));
|
||||
|
||||
export const getToolsExtra = (getFn: API['getElements']) =>
|
||||
Object.values(getFn<Addon>(types.TOOLEXTRA));
|
||||
|
||||
const mapper = ({ state, api }: Combo) => ({
|
||||
storyId: state.storyId,
|
||||
@ -111,7 +111,7 @@ const useTabs = (
|
||||
baseUrl: PreviewProps['baseUrl'],
|
||||
withLoader: PreviewProps['withLoader'],
|
||||
getElements: API['getElements'],
|
||||
parameters: PreviewProps['parameters']
|
||||
story: PreviewProps['story']
|
||||
) => {
|
||||
const canvas = useMemo(() => {
|
||||
return createCanvas(id, baseUrl, withLoader);
|
||||
@ -122,98 +122,58 @@ const useTabs = (
|
||||
}, [getElements]);
|
||||
|
||||
return useMemo(() => {
|
||||
return filterTabs([canvas, ...tabsFromConfig], parameters);
|
||||
}, [canvas, ...tabsFromConfig, parameters]);
|
||||
};
|
||||
if (story && story.parameters) {
|
||||
return filterTabs([canvas, ...tabsFromConfig], story.parameters);
|
||||
}
|
||||
|
||||
const useViewMode = (docsOnly: boolean, viewMode: PreviewProps['viewMode']) => {
|
||||
return docsOnly && viewMode === 'story' ? 'docs' : viewMode;
|
||||
};
|
||||
|
||||
const useTools = (
|
||||
getElements: API['getElements'],
|
||||
tabs: Addon[],
|
||||
viewMode: PreviewProps['viewMode'],
|
||||
storyId: PreviewProps['storyId'],
|
||||
location: PreviewProps['location'],
|
||||
path: PreviewProps['path']
|
||||
) => {
|
||||
const toolsFromConfig = useMemo(() => {
|
||||
return getTools(getElements);
|
||||
}, [getElements]);
|
||||
|
||||
const toolsExtraFromConfig = useMemo(() => {
|
||||
return getToolsExtra(getElements);
|
||||
}, [getElements]);
|
||||
|
||||
const tools = useMemo(() => {
|
||||
return [...defaultTools, ...toolsFromConfig];
|
||||
}, [defaultTools, toolsFromConfig]);
|
||||
|
||||
const toolsExtra = useMemo(() => {
|
||||
return [...defaultToolsExtra, ...toolsExtraFromConfig];
|
||||
}, [defaultToolsExtra, toolsExtraFromConfig]);
|
||||
|
||||
return useMemo(() => {
|
||||
return filterTools(tools, toolsExtra, tabs, {
|
||||
viewMode,
|
||||
storyId,
|
||||
location,
|
||||
path,
|
||||
});
|
||||
}, [viewMode, storyId, location, path, tools, toolsExtra, tabs]);
|
||||
return [canvas, ...tabsFromConfig];
|
||||
}, [story, canvas, ...tabsFromConfig]);
|
||||
};
|
||||
|
||||
const Preview: FunctionComponent<PreviewProps> = props => {
|
||||
const {
|
||||
api,
|
||||
id,
|
||||
location,
|
||||
options,
|
||||
docsOnly = false,
|
||||
storyId = undefined,
|
||||
path = undefined,
|
||||
description = undefined,
|
||||
viewMode,
|
||||
story = undefined,
|
||||
description,
|
||||
baseUrl = 'iframe.html',
|
||||
parameters = undefined,
|
||||
withLoader = true,
|
||||
} = props;
|
||||
const { isToolshown } = options;
|
||||
const { getElements } = api;
|
||||
|
||||
// eslint-disable-next-line react/destructuring-assignment
|
||||
const viewMode = useViewMode(docsOnly, props.viewMode);
|
||||
const tabs = useTabs(id, baseUrl, withLoader, getElements, parameters);
|
||||
const { left, right } = useTools(getElements, tabs, viewMode, storyId, location, path);
|
||||
const tabs = useTabs(id, baseUrl, withLoader, getElements, story);
|
||||
|
||||
useEffect(() => {
|
||||
api.emit(SET_CURRENT_STORY, { storyId, viewMode });
|
||||
}, [storyId, viewMode]);
|
||||
if (story) {
|
||||
api.emit(SET_CURRENT_STORY, { storyId: story.id, viewMode });
|
||||
}
|
||||
}, [story, viewMode]);
|
||||
|
||||
return (
|
||||
<ZoomProvider>
|
||||
<Fragment>
|
||||
{id === 'main' && (
|
||||
<Helmet key="description">
|
||||
<title>{getDocumentTitle(description)}</title>
|
||||
</Helmet>
|
||||
)}
|
||||
{(left || right) && (
|
||||
<Toolbar key="toolbar" shown={isToolshown} border>
|
||||
<Fragment key="left">{left}</Fragment>
|
||||
<Fragment key="right">{right}</Fragment>
|
||||
</Toolbar>
|
||||
)}
|
||||
<Fragment>
|
||||
{id === 'main' && (
|
||||
<Helmet key="description">
|
||||
<title>{description}</title>
|
||||
</Helmet>
|
||||
)}
|
||||
<ZoomProvider>
|
||||
<ToolbarComp story={story} api={api} isShown={isToolshown} tabs={tabs} />
|
||||
<S.FrameWrap key="frame" offset={isToolshown ? 40 : 0}>
|
||||
{tabs.map((p, i) => (
|
||||
{tabs.map(({ render: Render, match, ...t }, i) => {
|
||||
// @ts-ignore
|
||||
<Fragment key={p.id || p.key || i}>
|
||||
{p.render({ active: p.match({ storyId, viewMode, location, path }) })}
|
||||
</Fragment>
|
||||
))}
|
||||
const key = t.id || t.key || i;
|
||||
return (
|
||||
<Fragment key={key}>
|
||||
<Location>{lp => <Render active={match(lp)} />}</Location>
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</S.FrameWrap>
|
||||
</Fragment>
|
||||
</ZoomProvider>
|
||||
</ZoomProvider>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
@ -261,56 +221,3 @@ function filterTabs(panels: Addon[], parameters: Record<string, any>) {
|
||||
}
|
||||
return panels;
|
||||
}
|
||||
|
||||
function filterTools(
|
||||
tools: Addon[],
|
||||
toolsExtra: Addon[],
|
||||
tabs: Addon[],
|
||||
{
|
||||
viewMode,
|
||||
storyId,
|
||||
location,
|
||||
path,
|
||||
}: {
|
||||
viewMode: State['viewMode'];
|
||||
storyId: State['storyId'];
|
||||
location: State['location'];
|
||||
path: State['path'];
|
||||
}
|
||||
) {
|
||||
const tabsTool = createTabsTool(tabs);
|
||||
const toolsLeft = [tabs.filter(p => !p.hidden).length > 1 ? tabsTool : null, ...tools];
|
||||
|
||||
const toolsRight = [...toolsExtra];
|
||||
|
||||
// if its a docsOnly page, even the 'story' view mode is considered 'docs'
|
||||
const filter = (item: Partial<Addon>) =>
|
||||
item &&
|
||||
(!item.match ||
|
||||
item.match({
|
||||
storyId,
|
||||
viewMode,
|
||||
location,
|
||||
path,
|
||||
}));
|
||||
|
||||
const displayItems = (list: Partial<Addon>[]) =>
|
||||
list.reduce(
|
||||
(acc, item, index) =>
|
||||
item ? (
|
||||
// @ts-ignore
|
||||
<Fragment key={item.id || item.key || `f-${index}`}>
|
||||
{acc}
|
||||
{item.render({}) || item}
|
||||
</Fragment>
|
||||
) : (
|
||||
acc
|
||||
),
|
||||
null
|
||||
);
|
||||
|
||||
const left = displayItems(toolsLeft.filter(filter));
|
||||
const right = displayItems(toolsRight.filter(filter));
|
||||
|
||||
return { left, right };
|
||||
}
|
||||
|
@ -1,4 +0,0 @@
|
||||
export const stringifyQueryParams = (queryParams: Record<string, string>) =>
|
||||
Object.entries(queryParams).reduce((acc, [k, v]) => {
|
||||
return `${acc}&${k}=${v}`;
|
||||
}, '');
|
@ -1,16 +1,20 @@
|
||||
import React, { FunctionComponent, Fragment } from 'react';
|
||||
import copy from 'copy-to-clipboard';
|
||||
import React, { Fragment, useMemo, FunctionComponent, ReactElement } from 'react';
|
||||
|
||||
import { styled } from '@storybook/theming';
|
||||
|
||||
import { FlexBar, IconButton, Icons, Separator, TabButton, TabBar } from '@storybook/components';
|
||||
import { Consumer, Combo } from '@storybook/api';
|
||||
import { Consumer, Combo, API, Story, Group, State } from '@storybook/api';
|
||||
import { Addon } from '@storybook/addons';
|
||||
|
||||
import { stringifyQueryParams } from './stringifyQueryParams';
|
||||
import { ZoomConsumer, Zoom } from './zoom';
|
||||
import { Location, RenderData } from '@storybook/router';
|
||||
import { ZoomConsumer, Zoom, zoomTool } from './tools/zoom';
|
||||
|
||||
import * as S from './components';
|
||||
import * as S from './utils/components';
|
||||
|
||||
import { PreviewProps } from './utils/types';
|
||||
import { getTools, getToolsExtra } from './preview';
|
||||
import { copyTool } from './tools/copy';
|
||||
import { ejectTool } from './tools/eject';
|
||||
|
||||
const Bar: FunctionComponent<{ shown: boolean } & Record<string, any>> = ({ shown, ...props }) => (
|
||||
<FlexBar {...props} />
|
||||
@ -54,74 +58,6 @@ export const fullScreenTool: Addon = {
|
||||
),
|
||||
};
|
||||
|
||||
const copyMapper = ({ state }: Combo) => ({
|
||||
origin: state.location.origin,
|
||||
pathname: state.location.pathname,
|
||||
storyId: state.storyId,
|
||||
baseUrl: 'iframe.html',
|
||||
queryParams: state.customQueryParams,
|
||||
});
|
||||
|
||||
export const copyTool: Addon = {
|
||||
title: 'copy',
|
||||
match: p => p.viewMode === 'story',
|
||||
render: () => (
|
||||
<Consumer filter={copyMapper}>
|
||||
{({ baseUrl, storyId, origin, pathname, queryParams }: ReturnType<typeof copyMapper>) => (
|
||||
<IconButton
|
||||
key="copy"
|
||||
onClick={() =>
|
||||
copy(`${origin}${pathname}${baseUrl}?id=${storyId}${stringifyQueryParams(queryParams)}`)
|
||||
}
|
||||
title="Copy canvas link"
|
||||
>
|
||||
<Icons icon="copy" />
|
||||
</IconButton>
|
||||
)}
|
||||
</Consumer>
|
||||
),
|
||||
};
|
||||
|
||||
const ejectMapper = ({ state }: Combo) => ({
|
||||
baseUrl: 'iframe.html',
|
||||
storyId: state.storyId,
|
||||
queryParams: state.customQueryParams,
|
||||
});
|
||||
|
||||
export const ejectTool: Addon = {
|
||||
title: 'eject',
|
||||
match: p => p.viewMode === 'story',
|
||||
render: () => (
|
||||
<Consumer filter={ejectMapper}>
|
||||
{({ baseUrl, storyId, queryParams }: ReturnType<typeof ejectMapper>) => (
|
||||
<IconButton
|
||||
key="opener"
|
||||
href={`${baseUrl}?id=${storyId}${stringifyQueryParams(queryParams)}`}
|
||||
target="_blank"
|
||||
title="Open canvas in new tab"
|
||||
>
|
||||
<Icons icon="share" />
|
||||
</IconButton>
|
||||
)}
|
||||
</Consumer>
|
||||
),
|
||||
};
|
||||
|
||||
const zoomTool: Addon = {
|
||||
title: 'zoom',
|
||||
match: p => p.viewMode === 'story',
|
||||
render: () => (
|
||||
<Fragment>
|
||||
<ZoomConsumer>
|
||||
{({ set, value }) => (
|
||||
<Zoom key="zoom" set={(v: number) => set(value * v)} reset={() => set(1)} />
|
||||
)}
|
||||
</ZoomConsumer>
|
||||
<Separator />
|
||||
</Fragment>
|
||||
),
|
||||
};
|
||||
|
||||
const tabsMapper = ({ state }: Combo) => ({
|
||||
viewMode: state.docsOnly,
|
||||
storyId: state.storyId,
|
||||
@ -159,3 +95,119 @@ export const createTabsTool = (tabs: Addon[]): Addon => ({
|
||||
|
||||
export const defaultTools: Addon[] = [zoomTool];
|
||||
export const defaultToolsExtra: Addon[] = [fullScreenTool, ejectTool, copyTool];
|
||||
|
||||
const useTools = (
|
||||
getElements: API['getElements'],
|
||||
tabs: Addon[],
|
||||
viewMode: PreviewProps['viewMode'],
|
||||
story: PreviewProps['story'],
|
||||
location: PreviewProps['location'],
|
||||
path: PreviewProps['path']
|
||||
) => {
|
||||
const toolsFromConfig = useMemo(() => {
|
||||
return getTools(getElements);
|
||||
}, [getElements]);
|
||||
|
||||
const toolsExtraFromConfig = useMemo(() => {
|
||||
return getToolsExtra(getElements);
|
||||
}, [getElements]);
|
||||
|
||||
const tools = useMemo(() => {
|
||||
return [...defaultTools, ...toolsFromConfig];
|
||||
}, [defaultTools, toolsFromConfig]);
|
||||
|
||||
const toolsExtra = useMemo(() => {
|
||||
return [...defaultToolsExtra, ...toolsExtraFromConfig];
|
||||
}, [defaultToolsExtra, toolsExtraFromConfig]);
|
||||
|
||||
return useMemo(() => {
|
||||
if (story && story.parameters) {
|
||||
return filterTools(tools, toolsExtra, tabs, {
|
||||
viewMode,
|
||||
story,
|
||||
location,
|
||||
path,
|
||||
});
|
||||
}
|
||||
return { left: tools, right: toolsExtra };
|
||||
}, [viewMode, story, location, path, tools, toolsExtra, tabs]);
|
||||
};
|
||||
|
||||
export interface ToolData {
|
||||
isShown: boolean;
|
||||
tabs: Addon[];
|
||||
api: API;
|
||||
story: Story | Group;
|
||||
}
|
||||
export const ToolRes: FunctionComponent<ToolData & RenderData> = ({
|
||||
api,
|
||||
story,
|
||||
tabs,
|
||||
isShown,
|
||||
location,
|
||||
path,
|
||||
viewMode,
|
||||
}) => {
|
||||
const { left, right } = useTools(api.getElements, tabs, viewMode, story, location, path);
|
||||
|
||||
return left || right ? (
|
||||
<Toolbar key="toolbar" shown={isShown} border>
|
||||
<Tools key="left" list={left} />
|
||||
<Tools key="right" list={right} />
|
||||
</Toolbar>
|
||||
) : null;
|
||||
};
|
||||
|
||||
export const ToolbarComp: FunctionComponent<ToolData> = p => (
|
||||
<Location>{l => <ToolRes {...l} {...p} />}</Location>
|
||||
);
|
||||
|
||||
export const Tools: FunctionComponent<{
|
||||
list: Addon[];
|
||||
}> = ({ list }) =>
|
||||
list.filter(Boolean).reduce((acc, { render: Render, id, ...t }, index) => {
|
||||
// @ts-ignore
|
||||
const key = id || t.key || `f-${index}`;
|
||||
return (
|
||||
<Fragment key={key}>
|
||||
{acc}
|
||||
<Render />
|
||||
</Fragment>
|
||||
);
|
||||
}, null as ReactElement);
|
||||
|
||||
export function filterTools(
|
||||
tools: Addon[],
|
||||
toolsExtra: Addon[],
|
||||
tabs: Addon[],
|
||||
{
|
||||
viewMode,
|
||||
story,
|
||||
location,
|
||||
path,
|
||||
}: {
|
||||
viewMode: State['viewMode'];
|
||||
story: PreviewProps['story'];
|
||||
location: State['location'];
|
||||
path: State['path'];
|
||||
}
|
||||
) {
|
||||
const tabsTool = createTabsTool(tabs);
|
||||
const toolsLeft = [tabs.filter(p => !p.hidden).length > 1 ? tabsTool : null, ...tools];
|
||||
const toolsRight = [...toolsExtra];
|
||||
|
||||
const filter = (item: Partial<Addon>) =>
|
||||
item &&
|
||||
(!item.match ||
|
||||
item.match({
|
||||
storyId: story.id,
|
||||
viewMode,
|
||||
location,
|
||||
path,
|
||||
}));
|
||||
|
||||
const left = toolsLeft.filter(filter);
|
||||
const right = toolsRight.filter(filter);
|
||||
|
||||
return { left, right };
|
||||
}
|
||||
|
34
lib/ui/src/components/preview/tools/copy.tsx
Normal file
34
lib/ui/src/components/preview/tools/copy.tsx
Normal file
@ -0,0 +1,34 @@
|
||||
import React from 'react';
|
||||
import copy from 'copy-to-clipboard';
|
||||
import { IconButton, Icons } from '@storybook/components';
|
||||
import { Consumer, Combo } from '@storybook/api';
|
||||
import { Addon } from '@storybook/addons';
|
||||
import { stringifyQueryParams } from '../utils/stringifyQueryParams';
|
||||
|
||||
const copyMapper = ({ state }: Combo) => ({
|
||||
origin: state.location.origin,
|
||||
pathname: state.location.pathname,
|
||||
storyId: state.storyId,
|
||||
baseUrl: 'iframe.html',
|
||||
queryParams: state.customQueryParams,
|
||||
});
|
||||
|
||||
export const copyTool: Addon = {
|
||||
title: 'copy',
|
||||
match: p => p.viewMode === 'story',
|
||||
render: () => (
|
||||
<Consumer filter={copyMapper}>
|
||||
{({ baseUrl, storyId, origin, pathname, queryParams }: ReturnType<typeof copyMapper>) => (
|
||||
<IconButton
|
||||
key="copy"
|
||||
onClick={() =>
|
||||
copy(`${origin}${pathname}${baseUrl}?id=${storyId}${stringifyQueryParams(queryParams)}`)
|
||||
}
|
||||
title="Copy canvas link"
|
||||
>
|
||||
<Icons icon="copy" />
|
||||
</IconButton>
|
||||
)}
|
||||
</Consumer>
|
||||
),
|
||||
};
|
30
lib/ui/src/components/preview/tools/eject.tsx
Normal file
30
lib/ui/src/components/preview/tools/eject.tsx
Normal file
@ -0,0 +1,30 @@
|
||||
import React from 'react';
|
||||
import { IconButton, Icons } from '@storybook/components';
|
||||
import { Consumer, Combo } from '@storybook/api';
|
||||
import { Addon } from '@storybook/addons';
|
||||
import { stringifyQueryParams } from '../utils/stringifyQueryParams';
|
||||
|
||||
const ejectMapper = ({ state }: Combo) => ({
|
||||
baseUrl: 'iframe.html',
|
||||
storyId: state.storyId,
|
||||
queryParams: state.customQueryParams,
|
||||
});
|
||||
|
||||
export const ejectTool: Addon = {
|
||||
title: 'eject',
|
||||
match: p => p.viewMode === 'story',
|
||||
render: () => (
|
||||
<Consumer filter={ejectMapper}>
|
||||
{({ baseUrl, storyId, queryParams }: ReturnType<typeof ejectMapper>) => (
|
||||
<IconButton
|
||||
key="opener"
|
||||
href={`${baseUrl}?id=${storyId}${stringifyQueryParams(queryParams)}`}
|
||||
target="_blank"
|
||||
title="Open canvas in new tab"
|
||||
>
|
||||
<Icons icon="share" />
|
||||
</IconButton>
|
||||
)}
|
||||
</Consumer>
|
||||
),
|
||||
};
|
@ -1,10 +1,11 @@
|
||||
import React, { Fragment, Component, FunctionComponent, SyntheticEvent } from 'react';
|
||||
|
||||
import { Icons, IconButton } from '@storybook/components';
|
||||
import { Icons, IconButton, Separator } from '@storybook/components';
|
||||
import { Addon } from '@storybook/addons';
|
||||
|
||||
const Context = React.createContext({ value: 1, set: (v: number) => {} });
|
||||
|
||||
class Provider extends Component<{}, { value: number }> {
|
||||
class ZoomProvider extends Component<{}, { value: number }> {
|
||||
state = {
|
||||
value: 1,
|
||||
};
|
||||
@ -20,7 +21,7 @@ class Provider extends Component<{}, { value: number }> {
|
||||
}
|
||||
}
|
||||
|
||||
const { Consumer } = Context;
|
||||
const { Consumer: ZoomConsumer } = Context;
|
||||
|
||||
const cancel = (e: SyntheticEvent) => {
|
||||
e.preventDefault();
|
||||
@ -49,4 +50,19 @@ const Zoom: FunctionComponent<{ set: Function; reset: Function }> = ({ set, rese
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
export { Zoom, Consumer as ZoomConsumer, Provider as ZoomProvider };
|
||||
export { Zoom, ZoomConsumer, ZoomProvider };
|
||||
|
||||
export const zoomTool: Addon = {
|
||||
title: 'zoom',
|
||||
match: p => p.viewMode === 'story',
|
||||
render: () => (
|
||||
<Fragment>
|
||||
<ZoomConsumer>
|
||||
{({ set, value }) => (
|
||||
<Zoom key="zoom" set={(v: number) => set(value * v)} reset={() => set(1)} />
|
||||
)}
|
||||
</ZoomConsumer>
|
||||
<Separator />
|
||||
</Fragment>
|
||||
),
|
||||
};
|
@ -26,3 +26,14 @@ export const DesktopOnly = styled.span({
|
||||
display: 'none',
|
||||
},
|
||||
});
|
||||
|
||||
export const IframeWrapper = styled.div(({ theme }) => ({
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
background: theme.background.content,
|
||||
}));
|
@ -0,0 +1,2 @@
|
||||
export const stringifyQueryParams = (queryParams: Record<string, string>) =>
|
||||
Object.entries(queryParams).reduce((acc, [k, v]) => `${acc}&${k}=${v}`, '');
|
@ -1,12 +1,13 @@
|
||||
import { State, API } from '@storybook/api';
|
||||
import { State, API, Story, Group } from '@storybook/api';
|
||||
import { FunctionComponent, ReactNode } from 'react';
|
||||
|
||||
type ViewMode = State['viewMode'];
|
||||
|
||||
export interface PreviewProps {
|
||||
api: API;
|
||||
storyId: string;
|
||||
viewMode: ViewMode;
|
||||
refs: State['refs'];
|
||||
story: Group | Story;
|
||||
docsOnly: boolean;
|
||||
options: {
|
||||
isFullscreen: boolean;
|
||||
@ -19,7 +20,6 @@ export interface PreviewProps {
|
||||
customCanvas?: IframeRenderer;
|
||||
description: string;
|
||||
baseUrl: string;
|
||||
parameters: Record<string, any>;
|
||||
withLoader: boolean;
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React, { Fragment, FunctionComponent } from 'react';
|
||||
import { styled } from '@storybook/theming';
|
||||
import { ApplyWrappersProps, Wrapper } from './PreviewProps';
|
||||
import { ApplyWrappersProps, Wrapper } from './utils/types';
|
||||
import { IframeWrapper } from './utils/components';
|
||||
|
||||
export const ApplyWrappers: FunctionComponent<ApplyWrappersProps> = ({
|
||||
wrappers,
|
||||
@ -19,16 +19,6 @@ export const ApplyWrappers: FunctionComponent<ApplyWrappersProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
const IframeWrapper = styled.div(({ theme }) => ({
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
background: theme.background.content,
|
||||
}));
|
||||
export const defaultWrappers = [
|
||||
{
|
||||
render: p => (
|
||||
|
@ -1,10 +1,12 @@
|
||||
import { PREVIEW_URL } from 'global';
|
||||
import React from 'react';
|
||||
|
||||
import { State, Consumer, Combo, StoriesHash } from '@storybook/api';
|
||||
import { Consumer, Combo, StoriesHash, isRoot, isGroup, isStory } from '@storybook/api';
|
||||
|
||||
import { Preview } from '../components/preview/preview';
|
||||
import { PreviewProps } from '../components/preview/PreviewProps';
|
||||
import { PreviewProps } from '../components/preview/utils/types';
|
||||
|
||||
type Item = StoriesHash[keyof StoriesHash];
|
||||
|
||||
const nonAlphanumSpace = /[^a-z0-9 ]/gi;
|
||||
const doubleSpace = /\s\s/gi;
|
||||
@ -13,51 +15,59 @@ const replacer = (match: string) => ` ${match} `;
|
||||
const addExtraWhiteSpace = (input: string) =>
|
||||
input.replace(nonAlphanumSpace, replacer).replace(doubleSpace, ' ');
|
||||
|
||||
const getDescription = (storiesHash: StoriesHash, storyId: string) => {
|
||||
const storyInfo = storiesHash[storyId];
|
||||
|
||||
if (storyInfo) {
|
||||
// @ts-ignore
|
||||
const { kind, name } = storyInfo;
|
||||
return kind && name ? addExtraWhiteSpace(`${kind} - ${name}`) : '';
|
||||
const getDescription = (item: Item) => {
|
||||
if (isRoot(item)) {
|
||||
return item.name ? `${item.name} ⋅ Storybook` : 'Storybook';
|
||||
}
|
||||
if (isGroup(item)) {
|
||||
return item.name ? `${item.name} ⋅ Storybook` : 'Storybook';
|
||||
}
|
||||
if (isStory(item)) {
|
||||
const { kind, name } = item;
|
||||
return kind && name ? addExtraWhiteSpace(`${kind} - ${name} ⋅ Storybook`) : 'Storybook';
|
||||
}
|
||||
|
||||
return '';
|
||||
return 'Storybook';
|
||||
};
|
||||
|
||||
const mapper = ({ api, state }: Combo) => {
|
||||
const { layout, location, customQueryParams, storiesHash, storyId } = state;
|
||||
const { parameters } = storiesHash[storyId] || {};
|
||||
const { layout, location, customQueryParams, storyId, refs, viewMode, path } = state;
|
||||
const story = api.getData(storyId);
|
||||
const docsOnly = story && story.parameters ? !!story.parameters.docsOnly : false;
|
||||
|
||||
return {
|
||||
api,
|
||||
story,
|
||||
options: layout,
|
||||
description: getDescription(storiesHash, storyId),
|
||||
...api.getUrlState(),
|
||||
description: getDescription(story),
|
||||
viewMode,
|
||||
path,
|
||||
refs,
|
||||
queryParams: customQueryParams,
|
||||
docsOnly: (parameters && parameters.docsOnly) as boolean,
|
||||
docsOnly,
|
||||
location,
|
||||
parameters,
|
||||
};
|
||||
};
|
||||
|
||||
function getBaseUrl(): string {
|
||||
const getBaseUrl = (): string => {
|
||||
try {
|
||||
return PREVIEW_URL || 'iframe.html';
|
||||
} catch (e) {
|
||||
return 'iframe.html';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const PreviewConnected = React.memo<{ id: string; withLoader: boolean }>(props => (
|
||||
<Consumer filter={mapper}>
|
||||
{(fromState: ReturnType<typeof mapper>) => {
|
||||
const p = {
|
||||
const p: PreviewProps = {
|
||||
...props,
|
||||
baseUrl: getBaseUrl(),
|
||||
...fromState,
|
||||
} as PreviewProps;
|
||||
};
|
||||
|
||||
return <Preview {...p} />;
|
||||
return <pre>{JSON.stringify(p, null, 2)}</pre>;
|
||||
}}
|
||||
</Consumer>
|
||||
));
|
||||
|
Loading…
x
Reference in New Issue
Block a user