Skip to content

Commit

Permalink
Merge pull request #16149 from storybookjs/16147-fix-angular-inline
Browse files Browse the repository at this point in the history
Addon-docs/Angular: Fix inline story rendering
  • Loading branch information
shilman authored Oct 4, 2021
2 parents 5d5b6ee + 868768f commit efee268
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 20 deletions.
12 changes: 10 additions & 2 deletions addons/docs/src/blocks/useStory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,19 @@ export function useStories<TFramework extends AnyFramework = AnyFramework>(
useEffect(() => {
Promise.all(
storyIds.map(async (storyId) => {
// loadStory will be called every single time useStory is called
// because useEffect does not use storyIds as an input. This is because
// HMR can change the story even when the storyId hasn't changed. However, it
// will be a no-op once the story has loaded. Furthermore, the `story` will
// have an exact equality when the story hasn't changed, so it won't trigger
// any unnecessary re-renders
const story = await context.loadStory(storyId);
setStories((current) => ({ ...current, [storyId]: story }));
setStories((current) =>
current[storyId] === story ? current : { ...current, [storyId]: story }
);
})
);
}, storyIds);
});

return storyIds.map((storyId) => storiesById[storyId]);
}
38 changes: 23 additions & 15 deletions addons/docs/src/frameworks/angular/prepareForInline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,31 @@ const limit = pLimit(1);
*/
export const prepareForInline = (
storyFn: PartialStoryFn<AngularFramework>,
{ id, parameters }: StoryContext
{ id, parameters, component }: StoryContext
) => {
return React.createElement('div', {
ref: async (node?: HTMLDivElement): Promise<void> => {
if (!node) {
return null;
}
const el = React.useRef();

return limit(async () => {
const renderer = await rendererFactory.getRendererInstance(`${id}-${nanoid(10)}`, node);
await renderer.render({
forced: false,
parameters,
storyFnAngular: storyFn(),
targetDOMNode: node,
});
React.useEffect(() => {
(async () => {
limit(async () => {
const renderer = await rendererFactory.getRendererInstance(
`${id}-${nanoid(10)}`.toLowerCase(),
el.current
);
if (renderer) {
await renderer.render({
forced: false,
component,
parameters,
storyFnAngular: storyFn(),
targetDOMNode: el.current,
});
}
});
},
})();
});

return React.createElement('div', {
ref: el,
});
};
12 changes: 11 additions & 1 deletion app/angular/src/client/preview/angular-beta/RendererFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,17 @@ export class RendererFactory {

private rendererMap = new Map<string, AbstractRenderer>();

public async getRendererInstance(storyId: string, targetDOMNode: HTMLElement) {
public async getRendererInstance(
storyId: string,
targetDOMNode: HTMLElement
): Promise<AbstractRenderer | null> {
// do nothing if the target node is null
// fix a problem when the docs asks 2 times the same component at the same time
// the 1st targetDOMNode of the 1st requested rendering becomes null 🤷‍♂️
if (targetDOMNode === null) {
return null;
}

const renderType = getRenderType(targetDOMNode);
// keep only instances of the same type
if (this.lastRenderType && this.lastRenderType !== renderType) {
Expand Down
5 changes: 5 additions & 0 deletions cypress/generated/addon-docs.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ describe('addon-action', () => {
it('should have docs tab', () => {
cy.navigateToStory('example-button', 'primary');
cy.viewAddonTab('Docs');

// MDX rendering
cy.getDocsElement().find('h1').should('contain.text', 'Button');

// inline story rendering
cy.getDocsElement().find('button').should('contain.text', 'Button');
});
});
7 changes: 5 additions & 2 deletions lib/preview-web/src/PreviewWeb.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,7 @@ export class PreviewWeb<TFramework extends AnyFramework> {
}

async renderDocs({ story }: { story: Story<TFramework> }) {
const { id, title, name } = story;
const { id, title, name, componentId } = story;
const element = this.view.prepareForDocs();
const csfFile: CSFFile<TFramework> = await this.storyStore.loadCSFFileByStoryId(id, {
sync: false,
Expand Down Expand Up @@ -368,11 +368,14 @@ export class PreviewWeb<TFramework extends AnyFramework> {
docs.container || (({ children }: { children: Element }) => <>{children}</>);
const Page: ComponentType = docs.page || NoDocs;

// Use `componentId` as a key so that we force a re-render every time
// we switch components
const docsElement = (
<DocsContainer context={docsContext}>
<DocsContainer key={componentId} context={docsContext}>
<Page />
</DocsContainer>
);

ReactDOM.render(docsElement, element, async () => {
await Promise.all(renderingStoryPromises);
this.channel.emit(Events.DOCS_RENDERED, id);
Expand Down

0 comments on commit efee268

Please sign in to comment.