Skip to content

Commit

Permalink
Add a feature flag for modern inline rendering
Browse files Browse the repository at this point in the history
  • Loading branch information
shilman committed Jun 25, 2021
1 parent e0ac328 commit cdf70bd
Show file tree
Hide file tree
Showing 9 changed files with 206 additions and 81 deletions.
102 changes: 102 additions & 0 deletions addons/docs/src/blocks/LegacyStory.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import React, { FunctionComponent, ReactNode, ElementType, ComponentProps } from 'react';
import { MDXProvider } from '@mdx-js/react';
import { resetComponents, Story as PureStory } from '@storybook/components';
import { toId, storyNameFromExport } from '@storybook/csf';
import { Args, BaseAnnotations } from '@storybook/addons';
import { CURRENT_SELECTION } from './types';

import { DocsContext, DocsContextProps } from './DocsContext';

export const storyBlockIdFromId = (storyId: string) => `story--${storyId}`;

type PureStoryProps = ComponentProps<typeof PureStory>;

type CommonProps = BaseAnnotations<Args, any> & {
height?: string;
inline?: boolean;
};

type StoryDefProps = {
name: string;
children: ReactNode;
};

type StoryRefProps = {
id?: string;
};

type StoryImportProps = {
name: string;
story: ElementType;
};

export type StoryProps = (StoryDefProps | StoryRefProps | StoryImportProps) & CommonProps;

export const lookupStoryId = (
storyName: string,
{ mdxStoryNameToKey, mdxComponentMeta }: DocsContextProps
) =>
toId(
mdxComponentMeta.id || mdxComponentMeta.title,
storyNameFromExport(mdxStoryNameToKey[storyName])
);

export const getStoryProps = (props: StoryProps, context: DocsContextProps): PureStoryProps => {
const { id } = props as StoryRefProps;
const { name } = props as StoryDefProps;
const inputId = id === CURRENT_SELECTION ? context.id : id;
const previewId = inputId || lookupStoryId(name, context);
const data = context.storyStore.fromId(previewId) || {};

const { height, inline } = props;
const { storyFn = undefined, name: storyName = undefined, parameters = {} } = data;
const { docs = {} } = parameters;

if (docs.disable) {
return null;
}

// prefer block props, then story parameters defined by the framework-specific settings and optionally overridden by users
const { inlineStories = false, iframeHeight = 100, prepareForInline } = docs;
const storyIsInline = typeof inline === 'boolean' ? inline : inlineStories;
if (storyIsInline && !prepareForInline) {
throw new Error(
`Story '${storyName}' is set to render inline, but no 'prepareForInline' function is implemented in your docs configuration!`
);
}

return {
parameters,
inline: storyIsInline,
id: previewId,
storyFn: prepareForInline && storyFn ? () => prepareForInline(storyFn, data) : storyFn,
height: height || (storyIsInline ? undefined : iframeHeight),
title: storyName,
};
};

const Story: FunctionComponent<StoryProps> = (props) => (
<DocsContext.Consumer>
{(context) => {
const storyProps = getStoryProps(props, context);

if (!storyProps) {
return null;
}
return (
<div id={storyBlockIdFromId(storyProps.id)}>
<MDXProvider components={resetComponents}>
<PureStory {...storyProps} />
</MDXProvider>
</div>
);
}}
</DocsContext.Consumer>
);

Story.defaultProps = {
children: null,
name: null,
};

export { Story };
83 changes: 83 additions & 0 deletions addons/docs/src/blocks/ModernStory.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import React, { FunctionComponent, ReactNode, ElementType, useEffect } from 'react';
import { MDXProvider } from '@mdx-js/react';
import { resetComponents } from '@storybook/components';
import { DOCS_TARGETTED_RENDER, DOCS_TARGETTED_DESTROY } from '@storybook/core-events';
import { toId, storyNameFromExport } from '@storybook/csf';
import { Args, BaseAnnotations, addons } from '@storybook/addons';
import { CURRENT_SELECTION } from './types';

import { DocsContext, DocsContextProps } from './DocsContext';

export const storyBlockIdFromId = (storyId: string) => `story--${storyId}`;

type CommonProps = BaseAnnotations<Args, any> & {
height?: string;
inline?: boolean;
};

type StoryDefProps = {
name: string;
children: ReactNode;
};

type StoryRefProps = {
id?: string;
};

type StoryImportProps = {
name: string;
story: ElementType;
};

export type StoryProps = (StoryDefProps | StoryRefProps | StoryImportProps) & CommonProps;

export const lookupStoryId = (
storyName: string,
{ mdxStoryNameToKey, mdxComponentMeta }: DocsContextProps
) =>
toId(
mdxComponentMeta.id || mdxComponentMeta.title,
storyNameFromExport(mdxStoryNameToKey[storyName])
);

const Placeholder: FunctionComponent<any> = ({ id, name }) => {
const channel = addons.getChannel();
const identifier = storyBlockIdFromId(id);
useEffect(() => {
channel.emit(DOCS_TARGETTED_RENDER, { identifier, id, name });
return () => {
channel.emit(DOCS_TARGETTED_DESTROY, { identifier, id, name });
// TODO this does nothing, should it do something though?
};
});

return (
<div id={identifier} data-name={name}>
<span data-is-loadering-indicator="true">loading story...</span>
</div>
);
};

const Story: FunctionComponent<StoryProps> = (props) => (
<DocsContext.Consumer>
{(context) => {
const { id } = props as StoryRefProps;
const { name } = props as StoryDefProps;
const inputId = id === CURRENT_SELECTION ? context.id : id;
const previewId = inputId || lookupStoryId(name, context);

return (
<MDXProvider components={resetComponents}>
<Placeholder id={previewId} name={name} />
</MDXProvider>
);
}}
</DocsContext.Consumer>
);

Story.defaultProps = {
children: null,
name: null,
};

export { Story };
87 changes: 6 additions & 81 deletions addons/docs/src/blocks/Story.tsx
Original file line number Diff line number Diff line change
@@ -1,83 +1,8 @@
import React, { FunctionComponent, ReactNode, ElementType, useEffect } from 'react';
import { MDXProvider } from '@mdx-js/react';
import { resetComponents } from '@storybook/components';
import { DOCS_TARGETTED_RENDER, DOCS_TARGETTED_DESTROY } from '@storybook/core-events';
import { toId, storyNameFromExport } from '@storybook/csf';
import { Args, BaseAnnotations, addons } from '@storybook/addons';
import { CURRENT_SELECTION } from './types';
import global from 'global';
import { Story as LegacyStory } from './LegacyStory';
import { Story as ModernStory } from './ModernStory';

import { DocsContext, DocsContextProps } from './DocsContext';
export const Story = global?.MODERN_INLINE_RENDER ? ModernStory : LegacyStory;

export const storyBlockIdFromId = (storyId: string) => `story--${storyId}`;

type CommonProps = BaseAnnotations<Args, any> & {
height?: string;
inline?: boolean;
};

type StoryDefProps = {
name: string;
children: ReactNode;
};

type StoryRefProps = {
id?: string;
};

type StoryImportProps = {
name: string;
story: ElementType;
};

export type StoryProps = (StoryDefProps | StoryRefProps | StoryImportProps) & CommonProps;

export const lookupStoryId = (
storyName: string,
{ mdxStoryNameToKey, mdxComponentMeta }: DocsContextProps
) =>
toId(
mdxComponentMeta.id || mdxComponentMeta.title,
storyNameFromExport(mdxStoryNameToKey[storyName])
);

const Placeholder: FunctionComponent<any> = ({ id, name }) => {
const channel = addons.getChannel();
const identifier = storyBlockIdFromId(id);
useEffect(() => {
channel.emit(DOCS_TARGETTED_RENDER, { identifier, id, name });
return () => {
channel.emit(DOCS_TARGETTED_DESTROY, { identifier, id, name });
// TODO this does nothing, should it do something though?
};
});

return (
<div id={identifier} data-name={name}>
<span data-is-loadering-indicator="true">loading story...</span>
</div>
);
};

const Story: FunctionComponent<StoryProps> = (props) => (
<DocsContext.Consumer>
{(context) => {
const { id } = props as StoryRefProps;
const { name } = props as StoryDefProps;
const inputId = id === CURRENT_SELECTION ? context.id : id;
const previewId = inputId || lookupStoryId(name, context);

return (
<MDXProvider components={resetComponents}>
<Placeholder id={previewId} name={name} />
</MDXProvider>
);
}}
</DocsContext.Consumer>
);

Story.defaultProps = {
children: null,
name: null,
};

export { Story };
// FIXME: refactor
export { storyBlockIdFromId, lookupStoryId } from './ModernStory';
1 change: 1 addition & 0 deletions examples/react-ts/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const config: StorybookConfig = {
features: {
postcss: false,
previewCsfV3: true,
modernInlineRender: true,
},
};

Expand Down
2 changes: 2 additions & 0 deletions lib/builder-webpack4/src/preview/iframe-webpack.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export default async ({
typescriptOptions,
modern,
previewCsfV3,
modernInlineRender,
}: Options & Record<string, any>): Promise<Configuration> => {
const logLevel = await presets.apply('logLevel', undefined);
const frameworkOptions = await presets.apply(`${framework}Options`, {});
Expand Down Expand Up @@ -155,6 +156,7 @@ export default async ({
LOGLEVEL: logLevel,
FRAMEWORK_OPTIONS: frameworkOptions,
PREVIEW_CSF_V3: previewCsfV3,
MODERN_INLINE_RENDER: modernInlineRender,
},
headHtmlSnippet,
bodyHtmlSnippet,
Expand Down
4 changes: 4 additions & 0 deletions lib/builder-webpack5/src/preview/iframe-webpack.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ export default async ({
presets,
typescriptOptions,
modern,
previewCsfV3,
modernInlineRender,
}: Options & Record<string, any>): Promise<Configuration> => {
const envs = await presets.apply<Record<string, string>>('env');
const logLevel = await presets.apply('logLevel', undefined);
Expand Down Expand Up @@ -153,6 +155,8 @@ export default async ({
globals: {
LOGLEVEL: logLevel,
FRAMEWORK_OPTIONS: frameworkOptions,
PREVIEW_CSF_V3: previewCsfV3,
MODERN_INLINE_RENDER: modernInlineRender,
},
headHtmlSnippet,
bodyHtmlSnippet,
Expand Down
6 changes: 6 additions & 0 deletions lib/core-common/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ export interface BuilderOptions {
configDir: string;
docsMode: boolean;
previewCsfV3?: boolean;
modernInlineRender?: boolean;
versionCheck?: VersionCheck;
releaseNotesData?: ReleaseNotesData;
disableWebpackDefaults?: boolean;
Expand Down Expand Up @@ -251,6 +252,11 @@ export interface StorybookConfig {
* Activate preview of CSF v3.0
*/
previewCsfV3?: boolean;

/**
* Activate modern inline rendering
*/
modernInlineRender?: boolean;
};
/**
* Tells Storybook where to find stories.
Expand Down
1 change: 1 addition & 0 deletions lib/core-server/src/build-dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ export async function buildDevStandalone(options: CLIOptions & LoadOptions & Bui
...options,
presets,
previewCsfV3: features?.previewCsfV3,
modernInlineRender: features?.modernInlineRender,
};

const { address, networkAddress, managerResult, previewResult } = await storybookDevServer(
Expand Down
1 change: 1 addition & 0 deletions lib/core-server/src/build-static.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ export async function buildStaticStandalone(options: CLIOptions & LoadOptions &
...options,
presets,
previewCsfV3: features?.previewCsfV3,
modernInlineRender: features?.modernInlineRender,
};

const core = await presets.apply<{ builder?: string }>('core');
Expand Down

0 comments on commit cdf70bd

Please sign in to comment.