Skip to content

Commit

Permalink
Merge pull request #18730 from storybookjs/add-channel-to-docs-context
Browse files Browse the repository at this point in the history
Addon-docs: Localize channel to docs context
  • Loading branch information
tmeasday authored Jul 19, 2022
2 parents e925226 + bbecd18 commit 5d56f4b
Show file tree
Hide file tree
Showing 19 changed files with 87 additions and 95 deletions.
12 changes: 5 additions & 7 deletions examples/official-storybook/stories/addon-docs/mdx.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from 'react';
import { DocsContainer } from '@storybook/addon-docs';
import { themes } from '@storybook/theming';
import { MDXProvider } from '@mdx-js/react';
import { Channel } from '@storybook/channels';

import markdown from './markdown.stories.mdx';
import { defaultComponents } from '../../../../addons/docs/src/DocsRenderer';
Expand All @@ -11,6 +12,8 @@ export default {
parameters: { layout: 'fullscreen' },
};

const context = { channel: new Channel(), componentStories: () => [], storyById: () => ({}) };

// The purpose of these stories are to document that MDX renders properly in docs itself
// As tools like Chromatic cannot capture docs entries, we need to create a story that
// actually renders it's own docs, much like the DocsRenderer might.
Expand All @@ -22,9 +25,7 @@ export const Typography = () => {
Typography.decorators = [
(storyFn) => (
<MDXProvider components={defaultComponents}>
<DocsContainer context={{ componentStories: () => [], storyById: () => ({}) }}>
{storyFn()}
</DocsContainer>
<DocsContainer context={context}>{storyFn()}</DocsContainer>
</MDXProvider>
),
];
Expand All @@ -37,10 +38,7 @@ export const DarkModeDocs = () => {
DarkModeDocs.decorators = [
(storyFn) => (
<MDXProvider components={defaultComponents}>
<DocsContainer
context={{ componentStories: () => [], storyById: () => ({}) }}
theme={themes.dark}
>
<DocsContainer context={context} theme={themes.dark}>
{storyFn()}
</DocsContainer>
</MDXProvider>
Expand Down
5 changes: 3 additions & 2 deletions lib/blocks/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@
"prepare": "esrun ../../scripts/prepare/bundle.ts"
},
"dependencies": {
"@storybook/addons": "7.0.0-alpha.13",
"@storybook/api": "7.0.0-alpha.13",
"@storybook/channels": "7.0.0-alpha.13",
"@storybook/client-logger": "7.0.0-alpha.13",
"@storybook/components": "7.0.0-alpha.13",
"@storybook/core-events": "7.0.0-alpha.13",
Expand All @@ -64,7 +64,8 @@
"util-deprecate": "^1.0.2"
},
"devDependencies": {
"@digitak/esrun": "^3.2.2"
"@digitak/esrun": "^3.2.2",
"@storybook/addons": "7.0.0-alpha.13"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
Expand Down
22 changes: 12 additions & 10 deletions lib/blocks/src/blocks/ArgsTable.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import React, { FC, useContext, useEffect, useState, useCallback } from 'react';
import mapValues from 'lodash/mapValues';
import { ArgTypesExtractor } from '@storybook/docs-tools';
import { addons } from '@storybook/addons';
import { filterArgTypes, PropDescriptor } from '@storybook/store';
import Events from '@storybook/core-events';
import {
STORY_ARGS_UPDATED,
UPDATE_STORY_ARGS,
RESET_STORY_ARGS,
GLOBALS_UPDATED,
} from '@storybook/core-events';
import { StrictArgTypes, Args, Globals } from '@storybook/csf';
import {
ArgsTable as PureArgsTable,
Expand Down Expand Up @@ -45,7 +49,6 @@ const useArgs = (
storyId: string,
context: DocsContextProps
): [Args, (args: Args) => void, (argNames?: string[]) => void] => {
const channel = addons.getChannel();
const storyContext = context.getStoryContext(context.storyById());

const [args, setArgs] = useState(storyContext.args);
Expand All @@ -55,31 +58,30 @@ const useArgs = (
setArgs(changed.args);
}
};
channel.on(Events.STORY_ARGS_UPDATED, cb);
return () => channel.off(Events.STORY_ARGS_UPDATED, cb);
context.channel.on(STORY_ARGS_UPDATED, cb);
return () => context.channel.off(STORY_ARGS_UPDATED, cb);
}, [storyId]);
const updateArgs = useCallback(
(updatedArgs) => channel.emit(Events.UPDATE_STORY_ARGS, { storyId, updatedArgs }),
(updatedArgs) => context.channel.emit(UPDATE_STORY_ARGS, { storyId, updatedArgs }),
[storyId]
);
const resetArgs = useCallback(
(argNames?: string[]) => channel.emit(Events.RESET_STORY_ARGS, { storyId, argNames }),
(argNames?: string[]) => context.channel.emit(RESET_STORY_ARGS, { storyId, argNames }),
[storyId]
);
return [args, updateArgs, resetArgs];
};

const useGlobals = (context: DocsContextProps): [Globals] => {
const channel = addons.getChannel();
const storyContext = context.getStoryContext(context.storyById());
const [globals, setGlobals] = useState(storyContext.globals);

useEffect(() => {
const cb = (changed: { globals: Globals }) => {
setGlobals(changed.globals);
};
channel.on(Events.GLOBALS_UPDATED, cb);
return () => channel.off(Events.GLOBALS_UPDATED, cb);
context.channel.on(GLOBALS_UPDATED, cb);
return () => context.channel.off(GLOBALS_UPDATED, cb);
}, []);

return [globals];
Expand Down
2 changes: 1 addition & 1 deletion lib/blocks/src/blocks/DocsContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export const DocsContainer: FunctionComponent<DocsContainerProps> = ({

return (
<DocsContext.Provider value={context}>
<SourceContainer>
<SourceContainer channel={context.channel}>
<ThemeProvider theme={ensureTheme(theme)}>
<DocsWrapper className="sbdocs sbdocs-wrapper">
<DocsContent className="sbdocs sbdocs-content">{children}</DocsContent>
Expand Down
5 changes: 2 additions & 3 deletions lib/blocks/src/blocks/SourceContainer.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { FC, Context, createContext, useEffect, useState } from 'react';

import { dequal as deepEqual } from 'dequal';
import { addons } from '@storybook/addons';
import type { Channel } from '@storybook/addons';

import { SNIPPET_RENDERED } from '@storybook/docs-tools';
import type { SyntaxHighlighterFormatTypes } from '@storybook/components';
Expand All @@ -20,9 +20,8 @@ export interface SourceContextProps {

export const SourceContext: Context<SourceContextProps> = createContext({ sources: {} });

export const SourceContainer: FC<{}> = ({ children }) => {
export const SourceContainer: FC<{ channel: Channel }> = ({ children, channel }) => {
const [sources, setSources] = useState<StorySources>({});
const channel = addons.getChannel();

useEffect(() => {
const handleSnippetRendered = (
Expand Down
2 changes: 1 addition & 1 deletion lib/blocks/src/blocks/enhanceSource.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { StoryContext } from '@storybook/addons';
import type { StoryContext } from '@storybook/csf';
import { enhanceSource } from './enhanceSource';

const emptyContext: StoryContext = {
Expand Down
2 changes: 1 addition & 1 deletion lib/blocks/src/blocks/enhanceSource.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Parameters } from '@storybook/addons';
import type { Parameters } from '@storybook/csf';
import type { Story } from '@storybook/store';
import { combineParameters } from '@storybook/store';

Expand Down
15 changes: 8 additions & 7 deletions lib/blocks/src/blocks/external/ExternalDocsContext.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import { StoryId, AnyFramework, ComponentTitle, StoryName } from '@storybook/csf';
import { DocsContext, DocsContextProps } from '@storybook/preview-web';
import { CSFFile, ModuleExport, ModuleExports, StoryStore } from '@storybook/store';
import { AnyFramework } from '@storybook/csf';
import { DocsContext } from '@storybook/preview-web';
import { StoryStore } from '@storybook/store';
import type { DocsContextProps } from '@storybook/preview-web';
import type { CSFFile, ModuleExport, ModuleExports } from '@storybook/store';
import type { Channel } from '@storybook/channels';

export class ExternalDocsContext<TFramework extends AnyFramework> extends DocsContext<TFramework> {
constructor(
public readonly id: StoryId,
public readonly title: ComponentTitle,
public readonly name: StoryName,
public channel: Channel,
protected store: StoryStore<TFramework>,
public renderStoryToElement: DocsContextProps['renderStoryToElement'],
private processMetaExports: (metaExports: ModuleExports) => CSFFile<TFramework>
) {
super(id, title, name, store, renderStoryToElement, [], true);
super(channel, store, renderStoryToElement, [], true);
}

setMeta = (metaExports: ModuleExports) => {
Expand Down
8 changes: 4 additions & 4 deletions lib/blocks/src/blocks/external/ExternalPreview.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { Preview } from '@storybook/preview-web';
import { Path, ModuleExports, StoryIndex, composeConfigs } from '@storybook/store';
import { AnyFramework, ComponentTitle, ProjectAnnotations } from '@storybook/csf';
import { Channel } from '@storybook/channels';

import { ExternalDocsContext } from './ExternalDocsContext';

type MetaExports = ModuleExports;
Expand Down Expand Up @@ -31,7 +33,7 @@ export class ExternalPreview<
private moduleExportsByImportPath: Record<Path, ModuleExports> = {};

constructor(public projectAnnotations: ProjectAnnotations) {
super();
super(new Channel());

this.initialize({
getStoryIndex: () => this.storyIndex,
Expand Down Expand Up @@ -75,9 +77,7 @@ export class ExternalPreview<

docsContext = () => {
return new ExternalDocsContext(
'storybook--docs',
'Storybook',
'Docs',
this.channel,
this.storyStore,
this.renderStoryToElement.bind(this),
this.processMetaExports.bind(this)
Expand Down
48 changes: 27 additions & 21 deletions lib/blocks/src/blocks/mdx.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React, { FC, SyntheticEvent } from 'react';
import { addons } from '@storybook/addons';
import React, { useContext, FC, SyntheticEvent } from 'react';
import { NAVIGATE_URL } from '@storybook/core-events';
import { Code, components } from '@storybook/components';
import global from 'global';
Expand Down Expand Up @@ -50,8 +49,8 @@ export const CodeOrSourceMdx: FC<CodeOrSourceMdxProps> = ({ className, children,
);
};

function navigate(url: string) {
addons.getChannel().emit(NAVIGATE_URL, url);
function navigate(context: DocsContextProps, url: string) {
context.channel.emit(NAVIGATE_URL, url);
}

// @ts-ignore
Expand All @@ -61,21 +60,25 @@ interface AnchorInPageProps {
hash: string;
}

const AnchorInPage: FC<AnchorInPageProps> = ({ hash, children }) => (
<A
href={hash}
target="_self"
onClick={(event: SyntheticEvent) => {
const id = hash.substring(1);
const element = document.getElementById(id);
if (element) {
navigate(hash);
}
}}
>
{children}
</A>
);
const AnchorInPage: FC<AnchorInPageProps> = ({ hash, children }) => {
const context = useContext(DocsContext);

return (
<A
href={hash}
target="_self"
onClick={(event: SyntheticEvent) => {
const id = hash.substring(1);
const element = document.getElementById(id);
if (element) {
navigate(context, hash);
}
}}
>
{children}
</A>
);
};

interface AnchorMdxProps {
href: string;
Expand All @@ -84,6 +87,7 @@ interface AnchorMdxProps {

export const AnchorMdx: FC<AnchorMdxProps> = (props) => {
const { href, target, children, ...rest } = props;
const context = useContext(DocsContext);

if (href) {
// Enable scrolling for in-page anchors.
Expand All @@ -100,7 +104,7 @@ export const AnchorMdx: FC<AnchorMdxProps> = (props) => {
event.preventDefault();
// use the A element's href, which has been modified for
// local paths without a `?path=` query param prefix
navigate(event.currentTarget.getAttribute('href'));
navigate(context, event.currentTarget.getAttribute('href'));
}}
target={target}
{...rest}
Expand Down Expand Up @@ -153,6 +157,8 @@ const HeaderWithOcticonAnchor: FC<HeaderWithOcticonAnchorProps> = ({
children,
...rest
}) => {
const context = useContext(DocsContext);

// @ts-ignore
const OcticonHeader = OcticonHeaders[as];
const hash = `#${id}`;
Expand All @@ -167,7 +173,7 @@ const HeaderWithOcticonAnchor: FC<HeaderWithOcticonAnchorProps> = ({
onClick={(event: SyntheticEvent) => {
const element = document.getElementById(id);
if (element) {
navigate(hash);
navigate(context, hash);
}
}}
>
Expand Down
1 change: 1 addition & 0 deletions lib/preview-web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"dependencies": {
"@storybook/addons": "7.0.0-alpha.13",
"@storybook/channel-postmessage": "7.0.0-alpha.13",
"@storybook/channels": "7.0.0-alpha.13",
"@storybook/client-logger": "7.0.0-alpha.13",
"@storybook/core-events": "7.0.0-alpha.13",
"@storybook/csf": "0.0.2--canary.4566f4d.1",
Expand Down
5 changes: 1 addition & 4 deletions lib/preview-web/src/Preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,6 @@ type MaybePromise<T> = Promise<T> | T;
const STORY_INDEX_PATH = './index.json';

export class Preview<TFramework extends AnyFramework> {
channel: Channel;

serverChannel?: Channel;

storyStore: StoryStore<TFramework>;
Expand All @@ -53,8 +51,7 @@ export class Preview<TFramework extends AnyFramework> {

previewEntryError?: Error;

constructor() {
this.channel = addons.getChannel();
constructor(protected channel: Channel = addons.getChannel()) {
if (global.FEATURES?.storyStoreV7 && addons.hasServerChannel()) {
this.serverChannel = addons.getServerChannel();
}
Expand Down
16 changes: 5 additions & 11 deletions lib/preview-web/src/PreviewWeb.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2120,7 +2120,7 @@ describe('PreviewWeb', () => {
expect(preview.view.prepareForDocs).toHaveBeenCalled();
});

it('render the docs container with the correct context', async () => {
it('render the docs container with the correct context, template render', async () => {
document.location.search = '?id=component-one--a';
await createAndRenderPreview();

Expand All @@ -2132,16 +2132,10 @@ describe('PreviewWeb', () => {
await waitForSetCurrentStory();
await waitForRender();

expect(docsRenderer.render).toHaveBeenCalledWith(
expect.objectContaining({
id: 'component-one--a',
title: 'Component One',
name: 'Docs',
}),
expect.any(Object),
'docs-element',
expect.any(Function)
);
expect(docsRenderer.render).toHaveBeenCalled();
expect(docsRenderer.render.mock.calls[0][0].storyById()).toMatchObject({
id: 'component-one--a',
});
});

it('emits DOCS_RENDERED', async () => {
Expand Down
15 changes: 4 additions & 11 deletions lib/preview-web/src/docs-context/DocsContext.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
import {
AnyFramework,
ComponentTitle,
StoryContextForLoaders,
StoryId,
StoryName,
} from '@storybook/csf';
import { CSFFile, ModuleExport, ModuleExports, Story, StoryStore } from '@storybook/store';
import type { AnyFramework, StoryContextForLoaders, StoryId, StoryName } from '@storybook/csf';
import type { CSFFile, ModuleExport, ModuleExports, Story, StoryStore } from '@storybook/store';
import type { Channel } from '@storybook/channels';

import { DocsContextProps } from './DocsContextProps';

Expand All @@ -21,9 +16,7 @@ export class DocsContext<TFramework extends AnyFramework> implements DocsContext
private primaryStory?: Story<TFramework>;

constructor(
public readonly id: StoryId,
public readonly title: ComponentTitle,
public readonly name: StoryName,
public channel: Channel,
protected store: StoryStore<TFramework>,
public renderStoryToElement: DocsContextProps['renderStoryToElement'],
/** The CSF files known (via the index) to be refererenced by this docs file */
Expand Down
Loading

0 comments on commit 5d56f4b

Please sign in to comment.