Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

change docs to render stories out of context #14911

Closed
wants to merge 17 commits into from
Closed
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
74 changes: 28 additions & 46 deletions addons/docs/src/blocks/Story.tsx
@@ -1,16 +1,15 @@
import React, { FunctionComponent, ReactNode, ElementType, ComponentProps } from 'react';
import React, { FunctionComponent, ReactNode, ElementType, useEffect } from 'react';
import { MDXProvider } from '@mdx-js/react';
import { resetComponents, Story as PureStory } from '@storybook/components';
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 } from '@storybook/addons';
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 PureStoryProps = ComponentProps<typeof PureStory>;

type CommonProps = BaseAnnotations<Args, any> & {
height?: string;
inline?: boolean;
Expand Down Expand Up @@ -41,53 +40,36 @@ export const lookupStoryId = (
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 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 storyProps = getStoryProps(props, context);
if (!storyProps) {
return null;
}
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 (
<div id={storyBlockIdFromId(storyProps.id)}>
<MDXProvider components={resetComponents}>
<PureStory {...storyProps} />
</MDXProvider>
</div>
<MDXProvider components={resetComponents}>
<Placeholder id={previewId} name={name} />
</MDXProvider>
);
}}
</DocsContext.Consumer>
Expand Down
9 changes: 5 additions & 4 deletions app/angular/src/client/preview/render.ts
Expand Up @@ -5,20 +5,21 @@ import { renderNgApp } from './angular/helpers';
import { StoryFnAngularReturnType } from './types';
import { Parameters } from './types-6-0';

const rootElement = global.document.getElementById('root');

// add proper types
export default function render({
export default function renderMain({
storyFn,
showMain,
forceRender,
parameters,
targetDOMNode = rootElement,
}: {
storyFn: StoryFn<StoryFnAngularReturnType>;
showMain: () => void;
forceRender: boolean;
parameters: Parameters;
targetDOMNode: HTMLElement;
}) {
showMain();

if (parameters.angularLegacyRendering) {
renderNgApp(storyFn, forceRender);
return;
Expand Down
15 changes: 10 additions & 5 deletions app/ember/src/client/preview/render.ts
Expand Up @@ -4,12 +4,12 @@ import { RenderContext, ElementArgs, OptionsArgs } from './types';

declare let Ember: any;

const rootEl = document.getElementById('root');
const rootElement = document.getElementById('root');

const config = globalWindow.require(`${globalWindow.STORYBOOK_NAME}/config/environment`);
const app = globalWindow.require(`${globalWindow.STORYBOOK_NAME}/app`).default.create({
autoboot: false,
rootElement: rootEl,
rootElement,
...config.APP,
});

Expand Down Expand Up @@ -57,7 +57,13 @@ function render(options: OptionsArgs, el: ElementArgs) {
});
}

export default function renderMain({ storyFn, kind, name, showMain, showError }: RenderContext) {
export default function renderMain({
storyFn,
kind,
name,
showError,
targetDOMNode = rootElement,
}: RenderContext) {
const element = storyFn();

if (!element) {
Expand All @@ -73,6 +79,5 @@ export default function renderMain({ storyFn, kind, name, showMain, showError }:
return;
}

showMain();
render(element, rootEl);
render(element, { el: targetDOMNode });
}
14 changes: 7 additions & 7 deletions app/html/src/client/preview/render.ts
@@ -1,3 +1,4 @@
/* eslint-disable no-param-reassign */
import { document, Node } from 'global';
import dedent from 'ts-dedent';
import { simulatePageLoad, simulateDOMContentLoaded } from '@storybook/client-api';
Expand All @@ -9,23 +10,22 @@ export default function renderMain({
storyFn,
kind,
name,
showMain,
showError,
forceRender,
targetDOMNode = rootElement,
}: RenderContext) {
const element = storyFn();
showMain();
if (typeof element === 'string') {
rootElement.innerHTML = element;
simulatePageLoad(rootElement);
targetDOMNode.innerHTML = element;
simulatePageLoad(targetDOMNode);
} else if (element instanceof Node) {
// Don't re-mount the element if it didn't change and neither did the story
if (rootElement.firstChild === element && forceRender === true) {
if (targetDOMNode.firstChild === element && forceRender === true) {
return;
}

rootElement.innerHTML = '';
rootElement.appendChild(element);
targetDOMNode.innerHTML = '';
targetDOMNode.appendChild(element);
simulateDOMContentLoaded();
} else {
showError({
Expand Down
16 changes: 12 additions & 4 deletions app/lit/src/client/preview/render.ts
Expand Up @@ -6,13 +6,21 @@ import { RenderContext } from './types';

const rootElement = document.getElementById('root');

export default function renderMain({ storyFn, kind, name, showMain, showError }: RenderContext) {
export default function renderMain({
storyFn,
kind,
name,
showError,
targetDOMNode = rootElement,
}: RenderContext) {
const element = storyFn();

showMain();
if (targetDOMNode === null) {
return;
}

if (isTemplateResult(element)) {
render(element, rootElement);
if (isTemplateResult(element) && targetDOMNode) {
render(element, targetDOMNode);
} else {
showError({
title: `Expecting an lit template result from the story: "${name}" of "${kind}".`,
Expand Down
14 changes: 6 additions & 8 deletions app/preact/src/client/preview/render.tsx
Expand Up @@ -7,12 +7,12 @@ const rootElement = document ? document.getElementById('root') : null;

let renderedStory: Element;

function preactRender(story: StoryFnPreactReturnType): void {
function preactRender(story: StoryFnPreactReturnType, targetDOMNode: HTMLElement): void {
if (preact.Fragment) {
// Preact 10 only:
preact.render(story, rootElement);
preact.render(story, targetDOMNode);
} else {
renderedStory = (preact.render(story, rootElement, renderedStory) as unknown) as Element;
renderedStory = (preact.render(story, targetDOMNode, renderedStory) as unknown) as Element;
}
}

Expand Down Expand Up @@ -40,16 +40,14 @@ export default function renderMain({
storyFn,
kind,
name,
showMain,
showError,
forceRender,
targetDOMNode = rootElement,
}: RenderContext) {
// But forceRender means that it's the same story, so we want to keep the state in that case.
if (!forceRender) {
preactRender(null);
preactRender(null, targetDOMNode);
}

showMain();

preactRender(preact.h(StoryHarness, { name, kind, showError, storyFn }));
preactRender(preact.h(StoryHarness, { name, kind, showError, storyFn }), targetDOMNode);
}
11 changes: 8 additions & 3 deletions app/react/src/client/preview/render.tsx
Expand Up @@ -4,7 +4,7 @@ import ReactDOM from 'react-dom';

import { StoryContext, RenderContext } from './types';

const rootEl = document ? document.getElementById('root') : null;
const rootElement = document ? document.getElementById('root') : null;

const render = (node: ReactElement, el: Element) =>
new Promise((resolve) => {
Expand Down Expand Up @@ -51,6 +51,7 @@ export default async function renderMain({
showMain,
showException,
forceRender,
targetDOMNode = rootElement,
}: RenderContext) {
const Story = unboundStoryFn as FunctionComponent<StoryContext>;

Expand All @@ -69,8 +70,12 @@ export default async function renderMain({
// https://github.com/storybookjs/react-storybook/issues/81
// But forceRender means that it's the same story, so we want too keep the state in that case.
if (!forceRender) {
ReactDOM.unmountComponentAtNode(rootEl);
try {
ReactDOM.unmountComponentAtNode(targetDOMNode);
} catch (e) {
//
}
}

await render(element, rootEl);
await render(element, targetDOMNode);
}
14 changes: 7 additions & 7 deletions app/server/src/client/preview/render.ts
@@ -1,3 +1,4 @@
/* eslint-disable no-param-reassign */
import { document, fetch, Node } from 'global';
import dedent from 'ts-dedent';
import { Args, ArgTypes } from '@storybook/api';
Expand Down Expand Up @@ -48,14 +49,14 @@ export async function renderMain({
id,
kind,
name,
showMain,
showError,
forceRender,
parameters,
storyContext,
storyFn,
args,
argTypes,
targetDOMNode = rootElement,
}: RenderContext) {
// Some addons wrap the storyFn so we need to call it even though Server doesn't need the answer
storyFn();
Expand All @@ -69,18 +70,17 @@ export async function renderMain({
const storyParams = { ...params, ...storyArgs };
const element = await fetchStoryHtml(url, fetchId, storyParams, storyContext);

showMain();
if (typeof element === 'string') {
rootElement.innerHTML = element;
simulatePageLoad(rootElement);
targetDOMNode.innerHTML = element;
simulatePageLoad(targetDOMNode);
} else if (element instanceof Node) {
// Don't re-mount the element if it didn't change and neither did the story
if (rootElement.firstChild === element && forceRender === true) {
if (targetDOMNode.firstChild === element && forceRender === true) {
return;
}

rootElement.innerHTML = '';
rootElement.appendChild(element);
targetDOMNode.innerHTML = '';
targetDOMNode.appendChild(element);
simulateDOMContentLoaded();
} else {
showError({
Expand Down