mirror of
https://github.com/storybookjs/storybook.git
synced 2025-04-04 21:01:08 +08:00
Merge pull request #11332 from storybookjs/8342-dynamic-source
Addon-docs: Dynamic Source rendering for React
This commit is contained in:
commit
b8b6a612d0
@ -66,12 +66,13 @@
|
||||
"core-js": "^3.0.1",
|
||||
"doctrine": "^3.0.0",
|
||||
"escodegen": "^1.12.0",
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"global": "^4.3.2",
|
||||
"html-tags": "^3.1.0",
|
||||
"js-string-escape": "^1.0.1",
|
||||
"lodash": "^4.17.15",
|
||||
"prop-types": "^15.7.2",
|
||||
"react-element-to-jsx-string": "^14.1.0",
|
||||
"react-element-to-jsx-string": "^14.3.1",
|
||||
"regenerator-runtime": "^0.13.3",
|
||||
"remark-external-links": "^6.0.0",
|
||||
"remark-slug": "^6.0.0",
|
||||
|
@ -9,6 +9,7 @@ import { components as htmlComponents } from '@storybook/components/html';
|
||||
import { DocsContextProps, DocsContext } from './DocsContext';
|
||||
import { anchorBlockIdFromId } from './Anchor';
|
||||
import { storyBlockIdFromId } from './Story';
|
||||
import { SourceContainer } from './SourceContainer';
|
||||
import { CodeOrSourceMdx, AnchorMdx, HeadersMdx } from './mdx';
|
||||
import { scrollToElement } from './utils';
|
||||
|
||||
@ -75,13 +76,15 @@ export const DocsContainer: FunctionComponent<DocsContainerProps> = ({ context,
|
||||
|
||||
return (
|
||||
<DocsContext.Provider value={context}>
|
||||
<ThemeProvider theme={theme}>
|
||||
<MDXProvider components={allComponents}>
|
||||
<DocsWrapper className="sbdocs sbdocs-wrapper">
|
||||
<DocsContent className="sbdocs sbdocs-content">{children}</DocsContent>
|
||||
</DocsWrapper>
|
||||
</MDXProvider>
|
||||
</ThemeProvider>
|
||||
<SourceContainer>
|
||||
<ThemeProvider theme={theme}>
|
||||
<MDXProvider components={allComponents}>
|
||||
<DocsWrapper className="sbdocs sbdocs-wrapper">
|
||||
<DocsContent className="sbdocs sbdocs-content">{children}</DocsContent>
|
||||
</DocsWrapper>
|
||||
</MDXProvider>
|
||||
</ThemeProvider>
|
||||
</SourceContainer>
|
||||
</DocsContext.Provider>
|
||||
);
|
||||
};
|
||||
|
@ -1,10 +1,11 @@
|
||||
import React, { FunctionComponent, ReactElement, ReactNode, ReactNodeArray } from 'react';
|
||||
import React, { FC, ReactElement, ReactNode, ReactNodeArray, useContext } from 'react';
|
||||
import { MDXProvider } from '@mdx-js/react';
|
||||
import { toId, storyNameFromExport } from '@storybook/csf';
|
||||
import { resetComponents } from '@storybook/components/html';
|
||||
import { Preview as PurePreview, PreviewProps as PurePreviewProps } from '@storybook/components';
|
||||
import { getSourceProps } from './Source';
|
||||
import { DocsContext, DocsContextProps } from './DocsContext';
|
||||
import { SourceContext, SourceContextProps } from './SourceContainer';
|
||||
import { getSourceProps } from './Source';
|
||||
|
||||
export enum SourceState {
|
||||
OPEN = 'open',
|
||||
@ -24,7 +25,8 @@ const getPreviewProps = (
|
||||
children,
|
||||
...props
|
||||
}: PreviewProps & { children?: ReactNode },
|
||||
{ mdxStoryNameToKey, mdxComponentMeta, storyStore }: DocsContextProps
|
||||
docsContext: DocsContextProps,
|
||||
sourceContext: SourceContextProps
|
||||
): PurePreviewProps => {
|
||||
if (withSource === SourceState.NONE) {
|
||||
return props;
|
||||
@ -32,13 +34,14 @@ const getPreviewProps = (
|
||||
if (mdxSource) {
|
||||
return {
|
||||
...props,
|
||||
withSource: getSourceProps({ code: decodeURI(mdxSource) }, { storyStore }),
|
||||
withSource: getSourceProps({ code: decodeURI(mdxSource) }, docsContext, sourceContext),
|
||||
};
|
||||
}
|
||||
const childArray: ReactNodeArray = Array.isArray(children) ? children : [children];
|
||||
const stories = childArray.filter(
|
||||
(c: ReactElement) => c.props && (c.props.id || c.props.name)
|
||||
) as ReactElement[];
|
||||
const { mdxComponentMeta, mdxStoryNameToKey } = docsContext;
|
||||
const targetIds = stories.map(
|
||||
(s) =>
|
||||
s.props.id ||
|
||||
@ -47,7 +50,7 @@ const getPreviewProps = (
|
||||
storyNameFromExport(mdxStoryNameToKey[s.props.name])
|
||||
)
|
||||
);
|
||||
const sourceProps = getSourceProps({ ids: targetIds }, { storyStore });
|
||||
const sourceProps = getSourceProps({ ids: targetIds }, docsContext, sourceContext);
|
||||
return {
|
||||
...props, // pass through columns etc.
|
||||
withSource: sourceProps,
|
||||
@ -55,15 +58,14 @@ const getPreviewProps = (
|
||||
};
|
||||
};
|
||||
|
||||
export const Preview: FunctionComponent<PreviewProps> = (props) => (
|
||||
<DocsContext.Consumer>
|
||||
{(context) => {
|
||||
const previewProps = getPreviewProps(props, context);
|
||||
return (
|
||||
<MDXProvider components={resetComponents}>
|
||||
<PurePreview {...previewProps}>{props.children}</PurePreview>
|
||||
</MDXProvider>
|
||||
);
|
||||
}}
|
||||
</DocsContext.Consumer>
|
||||
);
|
||||
export const Preview: FC<PreviewProps> = (props) => {
|
||||
const docsContext = useContext(DocsContext);
|
||||
const sourceContext = useContext(SourceContext);
|
||||
const previewProps = getPreviewProps(props, docsContext, sourceContext);
|
||||
const { children } = props;
|
||||
return (
|
||||
<MDXProvider components={resetComponents}>
|
||||
<PurePreview {...previewProps}>{children}</PurePreview>
|
||||
</MDXProvider>
|
||||
);
|
||||
};
|
||||
|
@ -1,12 +1,19 @@
|
||||
import React, { FunctionComponent } from 'react';
|
||||
import { Source, SourceProps as PureSourceProps, SourceError } from '@storybook/components';
|
||||
import React, { FC, useContext } from 'react';
|
||||
import {
|
||||
Source as PureSource,
|
||||
SourceError,
|
||||
SourceProps as PureSourceProps,
|
||||
} from '@storybook/components';
|
||||
import { DocsContext, DocsContextProps } from './DocsContext';
|
||||
import { SourceContext, SourceContextProps } from './SourceContainer';
|
||||
import { CURRENT_SELECTION } from './types';
|
||||
|
||||
import { enhanceSource } from './enhanceSource';
|
||||
|
||||
interface CommonProps {
|
||||
language?: string;
|
||||
dark?: boolean;
|
||||
code?: string;
|
||||
}
|
||||
|
||||
type SingleSourceProps = {
|
||||
@ -27,8 +34,12 @@ type SourceProps = SingleSourceProps | MultiSourceProps | CodeProps | NoneProps;
|
||||
|
||||
export const getSourceProps = (
|
||||
props: SourceProps,
|
||||
{ id: currentId, storyStore }: DocsContextProps
|
||||
docsContext: DocsContextProps,
|
||||
sourceContext: SourceContextProps
|
||||
): PureSourceProps => {
|
||||
const { id: currentId, storyStore } = docsContext;
|
||||
const { sources } = sourceContext;
|
||||
|
||||
const codeProps = props as CodeProps;
|
||||
const singleProps = props as SingleSourceProps;
|
||||
const multiProps = props as MultiSourceProps;
|
||||
@ -40,9 +51,15 @@ export const getSourceProps = (
|
||||
const targetIds = multiProps.ids || [targetId];
|
||||
source = targetIds
|
||||
.map((sourceId) => {
|
||||
const data = storyStore.fromId(sourceId);
|
||||
const enhanced = data && (enhanceSource(data) || data.parameters);
|
||||
return enhanced?.docs?.source?.code || '';
|
||||
if (sources) {
|
||||
return sources[sourceId];
|
||||
}
|
||||
if (storyStore) {
|
||||
const data = storyStore.fromId(sourceId);
|
||||
const enhanced = data && (enhanceSource(data) || data.parameters);
|
||||
return enhanced?.docs?.source?.code || '';
|
||||
}
|
||||
return '';
|
||||
})
|
||||
.join('\n\n');
|
||||
}
|
||||
@ -56,13 +73,9 @@ export const getSourceProps = (
|
||||
* or the source for a story if `storyId` is provided, or
|
||||
* the source for the current story if nothing is provided.
|
||||
*/
|
||||
const SourceContainer: FunctionComponent<SourceProps> = (props) => (
|
||||
<DocsContext.Consumer>
|
||||
{(context) => {
|
||||
const sourceProps = getSourceProps(props, context);
|
||||
return <Source {...sourceProps} />;
|
||||
}}
|
||||
</DocsContext.Consumer>
|
||||
);
|
||||
|
||||
export { SourceContainer as Source };
|
||||
export const Source: FC<SourceProps> = (props) => {
|
||||
const sourceContext = useContext(SourceContext);
|
||||
const docsContext = useContext(DocsContext);
|
||||
const sourceProps = getSourceProps(props, docsContext, sourceContext);
|
||||
return <PureSource {...sourceProps} />;
|
||||
};
|
||||
|
43
addons/docs/src/blocks/SourceContainer.tsx
Normal file
43
addons/docs/src/blocks/SourceContainer.tsx
Normal file
@ -0,0 +1,43 @@
|
||||
import React, { FC, Context, createContext, useEffect, useState } from 'react';
|
||||
import deepEqual from 'fast-deep-equal';
|
||||
import { addons } from '@storybook/addons';
|
||||
import { StoryId } from '@storybook/api';
|
||||
import { SNIPPET_RENDERED } from '../shared';
|
||||
|
||||
export type SourceItem = string;
|
||||
export type StorySources = Record<StoryId, SourceItem>;
|
||||
|
||||
export interface SourceContextProps {
|
||||
sources?: StorySources;
|
||||
setSource?: (id: StoryId, item: SourceItem) => void;
|
||||
}
|
||||
|
||||
export const SourceContext: Context<SourceContextProps> = createContext({});
|
||||
|
||||
export const SourceContainer: FC<{}> = ({ children }) => {
|
||||
const [sources, setSources] = useState<StorySources>({});
|
||||
const channel = addons.getChannel();
|
||||
|
||||
const sourcesRef = React.useRef<StorySources>();
|
||||
const handleSnippetRendered = (id: StoryId, newItem: SourceItem) => {
|
||||
if (newItem !== sources[id]) {
|
||||
const newSources = { ...sourcesRef.current, [id]: newItem };
|
||||
sourcesRef.current = newSources;
|
||||
}
|
||||
};
|
||||
|
||||
// Bind this early (instead of inside `useEffect`), because the `SNIPPET_RENDERED` event
|
||||
// is triggered *during* the rendering process, not after. We have to use the ref
|
||||
// to ensure we don't end up calling setState outside the effect though.
|
||||
channel.on(SNIPPET_RENDERED, handleSnippetRendered);
|
||||
|
||||
useEffect(() => {
|
||||
if (!deepEqual(sources, sourcesRef.current)) {
|
||||
setSources(sourcesRef.current);
|
||||
}
|
||||
|
||||
return () => channel.off(SNIPPET_RENDERED, handleSnippetRendered);
|
||||
});
|
||||
|
||||
return <SourceContext.Provider value={{ sources }}>{children}</SourceContext.Provider>;
|
||||
};
|
@ -48,8 +48,8 @@ export function webpack(webpackConfig: any = {}, options: any = {}) {
|
||||
babelOptions,
|
||||
mdxBabelOptions,
|
||||
configureJSX = options.framework !== 'react', // if not user-specified
|
||||
sourceLoaderOptions = options.framework === 'react' ? null : {},
|
||||
transcludeMarkdown = false,
|
||||
sourceLoaderOptions = {},
|
||||
} = options;
|
||||
|
||||
const mdxLoaderOptions = {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { StoryFn } from '@storybook/addons';
|
||||
import { extractArgTypes } from './extractArgTypes';
|
||||
import { extractComponentDescription } from '../../lib/docgen';
|
||||
import { jsxDecorator } from './jsxDecorator';
|
||||
|
||||
export const parameters = {
|
||||
docs: {
|
||||
@ -11,3 +12,5 @@ export const parameters = {
|
||||
extractComponentDescription,
|
||||
},
|
||||
};
|
||||
|
||||
export const decorators = [jsxDecorator];
|
||||
|
90
addons/docs/src/frameworks/react/jsxDecorator.test.tsx
Normal file
90
addons/docs/src/frameworks/react/jsxDecorator.test.tsx
Normal file
@ -0,0 +1,90 @@
|
||||
/* eslint-disable jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */
|
||||
import React from 'react';
|
||||
import range from 'lodash/range';
|
||||
import { renderJsx } from './jsxDecorator';
|
||||
|
||||
expect.addSnapshotSerializer({
|
||||
print: (val: any) => val,
|
||||
test: (val) => typeof val === 'string',
|
||||
});
|
||||
|
||||
describe('renderJsx', () => {
|
||||
it('basic', () => {
|
||||
expect(renderJsx(<div>hello</div>, {})).toMatchInlineSnapshot(`
|
||||
<div>
|
||||
hello
|
||||
</div>
|
||||
`);
|
||||
});
|
||||
it('functions', () => {
|
||||
// eslint-disable-next-line no-console
|
||||
const onClick = () => console.log('onClick');
|
||||
expect(renderJsx(<div onClick={onClick}>hello</div>, {})).toMatchInlineSnapshot(`
|
||||
<div onClick={() => {}}>
|
||||
hello
|
||||
</div>
|
||||
`);
|
||||
});
|
||||
it('large objects', () => {
|
||||
const obj: Record<string, string> = {};
|
||||
range(20).forEach((i) => {
|
||||
obj[`key_${i}`] = `val_${i}`;
|
||||
});
|
||||
expect(renderJsx(<div data-val={obj} />, {})).toMatchInlineSnapshot(`
|
||||
<div
|
||||
data-val={{
|
||||
key_0: 'val_0',
|
||||
key_1: 'val_1',
|
||||
key_10: 'val_10',
|
||||
key_11: 'val_11',
|
||||
key_12: 'val_12',
|
||||
key_13: 'val_13',
|
||||
key_14: 'val_14',
|
||||
key_15: 'val_15',
|
||||
key_16: 'val_16',
|
||||
key_17: 'val_17',
|
||||
key_18: 'val_18',
|
||||
key_19: 'val_19',
|
||||
key_2: 'val_2',
|
||||
key_3: 'val_3',
|
||||
key_4: 'val_4',
|
||||
key_5: 'val_5',
|
||||
key_6: 'val_6',
|
||||
key_7: 'val_7',
|
||||
key_8: 'val_8',
|
||||
key_9: 'val_9'
|
||||
}}
|
||||
/>
|
||||
`);
|
||||
});
|
||||
|
||||
it('long arrays', () => {
|
||||
const arr = range(20).map((i) => `item ${i}`);
|
||||
expect(renderJsx(<div data-val={arr} />, {})).toMatchInlineSnapshot(`
|
||||
<div
|
||||
data-val={[
|
||||
'item 0',
|
||||
'item 1',
|
||||
'item 2',
|
||||
'item 3',
|
||||
'item 4',
|
||||
'item 5',
|
||||
'item 6',
|
||||
'item 7',
|
||||
'item 8',
|
||||
'item 9',
|
||||
'item 10',
|
||||
'item 11',
|
||||
'item 12',
|
||||
'item 13',
|
||||
'item 14',
|
||||
'item 15',
|
||||
'item 16',
|
||||
'item 17',
|
||||
'item 18',
|
||||
'item 19'
|
||||
]}
|
||||
/>
|
||||
`);
|
||||
});
|
||||
});
|
122
addons/docs/src/frameworks/react/jsxDecorator.tsx
Normal file
122
addons/docs/src/frameworks/react/jsxDecorator.tsx
Normal file
@ -0,0 +1,122 @@
|
||||
import React from 'react';
|
||||
import reactElementToJSXString, { Options } from 'react-element-to-jsx-string';
|
||||
|
||||
import { addons, StoryContext } from '@storybook/addons';
|
||||
import { logger } from '@storybook/client-logger';
|
||||
|
||||
import { SNIPPET_RENDERED } from '../../shared';
|
||||
|
||||
type VueComponent = {
|
||||
/** The template for the Vue component */
|
||||
template?: string;
|
||||
};
|
||||
|
||||
interface JSXOptions {
|
||||
/** How many wrappers to skip when rendering the jsx */
|
||||
skip?: number;
|
||||
/** Whether to show the function in the jsx tab */
|
||||
showFunctions?: boolean;
|
||||
/** Whether to format HTML or Vue markup */
|
||||
enableBeautify?: boolean;
|
||||
/** Override the display name used for a component */
|
||||
displayName?: string | Options['displayName'];
|
||||
/** A function ran before the story is rendered */
|
||||
onBeforeRender?(dom: string): string;
|
||||
}
|
||||
|
||||
/** Run the user supplied onBeforeRender function if it exists */
|
||||
const applyBeforeRender = (domString: string, options: JSXOptions) => {
|
||||
if (typeof options.onBeforeRender !== 'function') {
|
||||
return domString;
|
||||
}
|
||||
|
||||
return options.onBeforeRender(domString);
|
||||
};
|
||||
|
||||
/** Apply the users parameters and render the jsx for a story */
|
||||
export const renderJsx = (code: React.ReactElement, options: JSXOptions) => {
|
||||
if (typeof code === 'undefined') {
|
||||
logger.warn('Too many skip or undefined component');
|
||||
return null;
|
||||
}
|
||||
|
||||
let renderedJSX = code;
|
||||
const Type = renderedJSX.type;
|
||||
|
||||
for (let i = 0; i < options.skip; i += 1) {
|
||||
if (typeof renderedJSX === 'undefined') {
|
||||
logger.warn('Cannot skip undefined element');
|
||||
return null;
|
||||
}
|
||||
|
||||
if (React.Children.count(renderedJSX) > 1) {
|
||||
logger.warn('Trying to skip an array of elements');
|
||||
return null;
|
||||
}
|
||||
|
||||
if (typeof renderedJSX.props.children === 'undefined') {
|
||||
logger.warn('Not enough children to skip elements.');
|
||||
|
||||
if (typeof Type === 'function' && Type.name === '') {
|
||||
renderedJSX = <Type {...renderedJSX.props} />;
|
||||
}
|
||||
} else if (typeof renderedJSX.props.children === 'function') {
|
||||
renderedJSX = renderedJSX.props.children();
|
||||
} else {
|
||||
renderedJSX = renderedJSX.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
const opts =
|
||||
typeof options.displayName === 'string'
|
||||
? {
|
||||
...options,
|
||||
showFunctions: true,
|
||||
displayName: () => options.displayName,
|
||||
}
|
||||
: options;
|
||||
|
||||
const result = React.Children.map(code, (c) => {
|
||||
// @ts-ignore FIXME: workaround react-element-to-jsx-string
|
||||
const child = typeof c === 'number' ? c.toString() : c;
|
||||
let string = applyBeforeRender(reactElementToJSXString(child, opts as Options), options);
|
||||
const matches = string.match(/\S+=\\"([^"]*)\\"/g);
|
||||
|
||||
if (matches) {
|
||||
matches.forEach((match) => {
|
||||
string = string.replace(match, match.replace(/"/g, "'"));
|
||||
});
|
||||
}
|
||||
|
||||
return string;
|
||||
}).join('\n');
|
||||
|
||||
return result.replace(/function\s+noRefCheck\(\)\s+\{\}/, '() => {}');
|
||||
};
|
||||
|
||||
const defaultOpts = {
|
||||
skip: 0,
|
||||
showFunctions: false,
|
||||
enableBeautify: true,
|
||||
};
|
||||
|
||||
export const jsxDecorator = (storyFn: any, context: StoryContext) => {
|
||||
const story: ReturnType<typeof storyFn> & VueComponent = storyFn();
|
||||
|
||||
const channel = addons.getChannel();
|
||||
|
||||
const options = {
|
||||
...defaultOpts,
|
||||
...((context && context.parameters.jsx) || {}),
|
||||
} as Required<JSXOptions>;
|
||||
|
||||
let jsx = '';
|
||||
const rendered = renderJsx(story, options);
|
||||
if (rendered) {
|
||||
jsx = rendered;
|
||||
}
|
||||
|
||||
channel.emit(SNIPPET_RENDERED, (context || {}).id, jsx);
|
||||
|
||||
return story;
|
||||
};
|
@ -45,7 +45,7 @@ function generateReactObject(rawDefaultProp: any) {
|
||||
const { type } = rawDefaultProp;
|
||||
const { displayName } = type;
|
||||
|
||||
const jsx = reactElementToJSXString(rawDefaultProp);
|
||||
const jsx = reactElementToJSXString(rawDefaultProp, {});
|
||||
|
||||
if (displayName != null) {
|
||||
const prettyIdentifier = getPrettyElementIdentifier(displayName);
|
||||
|
@ -1,3 +1,5 @@
|
||||
export const ADDON_ID = 'storybook/docs';
|
||||
export const PANEL_ID = `${ADDON_ID}/panel`;
|
||||
export const PARAM_KEY = `docs`;
|
||||
|
||||
export const SNIPPET_RENDERED = `${ADDON_ID}/snippet-rendered`;
|
||||
|
10
addons/docs/src/typings.d.ts
vendored
10
addons/docs/src/typings.d.ts
vendored
@ -7,3 +7,13 @@ declare module 'babel-plugin-react-docgen';
|
||||
declare module 'require-from-string';
|
||||
declare module 'styled-components';
|
||||
declare module 'acorn-jsx';
|
||||
|
||||
declare module 'react-element-to-jsx-string' {
|
||||
export interface Options {
|
||||
showFunctions?: boolean;
|
||||
displayName?(): string;
|
||||
tabStop?: number;
|
||||
}
|
||||
|
||||
export default function render(element: React.ReactNode, options: Options): string;
|
||||
}
|
||||
|
@ -0,0 +1,20 @@
|
||||
import React from 'react';
|
||||
import Button from '../components/TsButton';
|
||||
|
||||
export default {
|
||||
title: 'Addons/Controls',
|
||||
component: Button,
|
||||
};
|
||||
|
||||
const Story = (args) => <Button {...args} />;
|
||||
|
||||
export const Basic = Story.bind({});
|
||||
Basic.args = {
|
||||
children: 'basic',
|
||||
};
|
||||
|
||||
export const Action = Story.bind({});
|
||||
Action.args = {
|
||||
children: 'hmmm',
|
||||
type: 'action',
|
||||
};
|
@ -12,7 +12,6 @@ const StyledSyntaxHighlighter = styled(SyntaxHighlighter)<{}>(({ theme }) => ({
|
||||
borderRadius: theme.appBorderRadius,
|
||||
boxShadow:
|
||||
theme.base === 'light' ? 'rgba(0, 0, 0, 0.10) 0 1px 3px 0' : 'rgba(0, 0, 0, 0.20) 0 2px 5px 0',
|
||||
|
||||
'pre.hljs': {
|
||||
padding: 20,
|
||||
background: 'inherit',
|
||||
@ -45,7 +44,7 @@ export type SourceProps = SourceErrorProps & SourceCodeProps;
|
||||
const Source: FunctionComponent<SourceProps> = (props) => {
|
||||
const { error } = props as SourceErrorProps;
|
||||
if (error) {
|
||||
return <EmptyBlock {...props}>{error}</EmptyBlock>;
|
||||
return <EmptyBlock>{error}</EmptyBlock>;
|
||||
}
|
||||
|
||||
const { language, code, dark, format, ...rest } = props as SourceCodeProps;
|
||||
|
@ -14,14 +14,14 @@ export default {
|
||||
parameters: { passArgsFirst: false },
|
||||
decorators: [
|
||||
withKnobs,
|
||||
((StoryFn, c) => {
|
||||
((storyFn, c) => {
|
||||
const mocked = boolean('mock', true);
|
||||
|
||||
const props = {
|
||||
...(mocked ? mockProps : realProps),
|
||||
};
|
||||
|
||||
return <StoryFn props={props} {...c} />;
|
||||
return storyFn({ props, ...c });
|
||||
}) as DecoratorFn,
|
||||
],
|
||||
};
|
||||
|
@ -26640,7 +26640,7 @@ react-draggable@^4.0.3:
|
||||
classnames "^2.2.5"
|
||||
prop-types "^15.6.0"
|
||||
|
||||
react-element-to-jsx-string@^14.1.0:
|
||||
react-element-to-jsx-string@^14.3.1:
|
||||
version "14.3.1"
|
||||
resolved "https://registry.yarnpkg.com/react-element-to-jsx-string/-/react-element-to-jsx-string-14.3.1.tgz#a08fa6e46eb76061aca7eabc2e70f433583cb203"
|
||||
integrity sha512-LRdQWRB+xcVPOL4PU4RYuTg6dUJ/FNmaQ8ls6w38YbzkbV6Yr5tFNESroub9GiSghtnMq8dQg2LcNN5aMIDzVg==
|
||||
|
Loading…
x
Reference in New Issue
Block a user