Skip to content

Commit

Permalink
WIP Got Story Rendering working
Browse files Browse the repository at this point in the history
  • Loading branch information
tmeasday committed May 2, 2022
1 parent 87bee5e commit ce7a34e
Show file tree
Hide file tree
Showing 9 changed files with 131 additions and 49 deletions.
4 changes: 2 additions & 2 deletions addons/docs/src/blocks/DocsContext.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Context, createContext } from 'react';
import { window as globalWindow } from 'global';

import type { DocsContextProps } from '@storybook/preview-web';
import type { DocsContextProps, ModernDocsContextProps } from '@storybook/preview-web';
import type { AnyFramework } from '@storybook/csf';

export type { DocsContextProps };
export type { DocsContextProps, ModernDocsContextProps };

// We add DocsContext to window. The reason is that in case DocsContext.ts is
// imported multiple times (maybe once directly, and another time from a minified bundle)
Expand Down
26 changes: 20 additions & 6 deletions addons/docs/src/blocks/Story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { StoryId, toId, storyNameFromExport, StoryAnnotations, AnyFramework } fr
import { Story as StoryType } from '@storybook/store';
import { addons } from '@storybook/addons';
import Events from '@storybook/core-events';
import { ModernDocsContext, ModernDocsContextProps } from '@storybook/preview-web';

import { CURRENT_SELECTION } from './types';
import { DocsContext, DocsContextProps } from './DocsContext';
Expand All @@ -36,6 +37,7 @@ type StoryDefProps = {

type StoryRefProps = {
id?: string;
of?: any;
};

type StoryImportProps = {
Expand All @@ -54,8 +56,17 @@ export const lookupStoryId = (
storyNameFromExport(mdxStoryNameToKey[storyName])
);

export const getStoryId = (props: StoryProps, context: DocsContextProps): StoryId => {
const { id } = props as StoryRefProps;
export const getStoryId = (
props: StoryProps,
context: DocsContextProps,
modernContext: ModernDocsContextProps
): StoryId => {
const { id, of } = props as StoryRefProps;

if (of) {
return modernContext.storyIdByRef(of);
}

const { name } = props as StoryDefProps;
const inputId = id === CURRENT_SELECTION ? context.id : id;
return inputId || lookupStoryId(name, context);
Expand All @@ -64,7 +75,7 @@ export const getStoryId = (props: StoryProps, context: DocsContextProps): StoryI
export const getStoryProps = <TFramework extends AnyFramework>(
{ height, inline }: StoryProps,
story: StoryType<TFramework>,
context: DocsContextProps<TFramework>,
context: DocsContextProps<TFramework> | ModernDocsContextProps<TFramework>,
onStoryFnCalled: () => void
): PureStoryProps => {
const { name: storyName, parameters } = story;
Expand Down Expand Up @@ -121,12 +132,15 @@ function makeGate(): [Promise<void>, () => void] {

const Story: FunctionComponent<StoryProps> = (props) => {
const context = useContext(DocsContext);
const modernContext = useContext<ModernDocsContextProps>(ModernDocsContext);
const channel = addons.getChannel();
const storyRef = useRef();
const storyId = getStoryId(props, context);
const story = useStory(storyId, context);
const storyId = getStoryId(props, context, modernContext);
const story = useStory(storyId, context || modernContext);
const [showLoader, setShowLoader] = useState(true);

console.log(storyId, story);

useEffect(() => {
let cleanup: () => void;
if (story && storyRef.current) {
Expand All @@ -145,7 +159,7 @@ const Story: FunctionComponent<StoryProps> = (props) => {
return <StorySkeleton />;
}

const storyProps = getStoryProps(props, story, context, onStoryFnRan);
const storyProps = getStoryProps(props, story, context || modernContext, onStoryFnRan);
if (!storyProps) {
return null;
}
Expand Down
6 changes: 3 additions & 3 deletions addons/docs/src/blocks/useStory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,19 @@ import { useState, useEffect } from 'react';
import type { StoryId, AnyFramework } from '@storybook/csf';
import type { Story } from '@storybook/store';

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

export function useStory<TFramework extends AnyFramework = AnyFramework>(
storyId: StoryId,
context: DocsContextProps<TFramework>
context: DocsContextProps<TFramework> | ModernDocsContextProps<TFramework>
): Story<TFramework> | void {
const stories = useStories([storyId], context);
return stories && stories[0];
}

export function useStories<TFramework extends AnyFramework = AnyFramework>(
storyIds: StoryId[],
context: DocsContextProps<TFramework>
context: DocsContextProps<TFramework> | ModernDocsContextProps<TFramework>
): (Story<TFramework> | void)[] {
const initialStoriesById = context.componentStories().reduce((acc, story) => {
acc[story.id] = story;
Expand Down
6 changes: 4 additions & 2 deletions examples/react-ts/src/docs2/MetaOf.docs.mdx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { Meta } from '@storybook/addon-docs';
import meta from '../button.stories';
import { Meta, Story } from '@storybook/addon-docs';
import meta, { Basic } from '../button.stories';

<Meta of={meta} />

# Docs with of

hello docs

<Story of={Basic} />
66 changes: 52 additions & 14 deletions lib/preview-web/src/DocsRender.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Channel } from '@storybook/addons';
import { DOCS_RENDERED } from '@storybook/core-events';

import { Render, RenderType } from './StoryRender';
import type { DocsContextProps } from './types';
import type { DocsContextProps, ModernDocsContextProps } from './types';

export class DocsRender<TFramework extends AnyFramework> implements Render<TFramework> {
public type: RenderType = 'docs';
Expand All @@ -22,7 +22,7 @@ export class DocsRender<TFramework extends AnyFramework> implements Render<TFram

private canvasElement?: HTMLElement;

private context?: DocsContextProps;
private context?: ModernDocsContextProps | DocsContextProps;

public disableKeyListeners = false;

Expand Down Expand Up @@ -56,16 +56,13 @@ export class DocsRender<TFramework extends AnyFramework> implements Render<TFram
return this.preparing;
}

async renderToElement(
canvasElement: HTMLElement,
async legacyDocsContext(
renderStoryToElement: DocsContextProps['renderStoryToElement']
) {
this.canvasElement = canvasElement;

): Promise<DocsContextProps> {
const { id, title, name } = this.entry;
const csfFile: CSFFile<TFramework> = await this.store.loadCSFFileByStoryId(this.id);

this.context = {
return {
id,
title,
name,
Expand All @@ -79,9 +76,44 @@ export class DocsRender<TFramework extends AnyFramework> implements Render<TFram
...this.store.getStoryContext(renderedStory),
viewMode: 'docs' as ViewMode,
} as StoryContextForLoaders<TFramework>),
// Put all the storyContext fields onto the docs context for back-compat
...(!global.FEATURES?.breakingChangesV7 && this.store.getStoryContext(this.story)),
};
}

docsContext(
renderStoryToElement: DocsContextProps['renderStoryToElement']
): ModernDocsContextProps {
const { id, title, name } = this.entry;

return {
id,
title,
name,

storyIdByRef: (ref: any) => this.store.storyIdByRef({ ref }),

componentStories: () => [],
loadStory: (storyId: StoryId) => this.store.loadStory({ storyId }),

renderStoryToElement,
getStoryContext: (renderedStory: Story<TFramework>) =>
({
...this.store.getStoryContext(renderedStory),
viewMode: 'docs' as ViewMode,
} as StoryContextForLoaders<TFramework>),
};
}

async renderToElement(
canvasElement: HTMLElement,
renderStoryToElement: DocsContextProps['renderStoryToElement']
) {
this.canvasElement = canvasElement;

if (this.legacy) {
this.context = await this.legacyDocsContext(renderStoryToElement);
} else {
this.context = this.docsContext(renderStoryToElement);
}

return this.render();
}
Expand All @@ -93,12 +125,18 @@ export class DocsRender<TFramework extends AnyFramework> implements Render<TFram
const renderer = await import('./renderDocs');

if (this.legacy) {
renderer.renderLegacyDocs(this.story, this.context, this.canvasElement, () =>
this.channel.emit(DOCS_RENDERED, this.id)
renderer.renderLegacyDocs(
this.story,
this.context as DocsContextProps,
this.canvasElement,
() => this.channel.emit(DOCS_RENDERED, this.id)
);
} else {
renderer.renderDocs(this.exports, this.context, this.canvasElement, () =>
this.channel.emit(DOCS_RENDERED, this.id)
renderer.renderDocs(
this.exports,
this.context as ModernDocsContextProps,
this.canvasElement,
() => this.channel.emit(DOCS_RENDERED, this.id)
);
}
}
Expand Down
12 changes: 7 additions & 5 deletions lib/preview-web/src/renderDocs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import ReactDOM from 'react-dom';
import { AnyFramework } from '@storybook/csf';
import { ModuleExports, Story } from '@storybook/store';

import { DocsContextProps } from './types';
import { ModernDocsContextProps, DocsContextProps, ModernDocsContext } from './types';
import { NoDocs } from './NoDocs';

export function renderLegacyDocs<TFramework extends AnyFramework>(
Expand All @@ -17,7 +17,7 @@ export function renderLegacyDocs<TFramework extends AnyFramework>(

export function renderDocs<TFramework extends AnyFramework>(
exports: ModuleExports,
docsContext: DocsContextProps<TFramework>,
docsContext: ModernDocsContextProps<TFramework>,
element: HTMLElement,
callback: () => void
) {
Expand Down Expand Up @@ -56,11 +56,13 @@ async function renderLegacyDocsAsync<TFramework extends AnyFramework>(

async function renderDocsAsync<TFramework extends AnyFramework>(
exports: ModuleExports,
docsContext: DocsContextProps<TFramework>,
docsContext: ModernDocsContextProps<TFramework>,
element: HTMLElement
) {
// FIXME -- is this at all correct?
const DocsContainer = ({ children }: { children: ReactElement }) => <>{children}</>;
console.log('rendering DocsContainer', docsContext);
const DocsContainer = ({ children }: { children: ReactElement }) => (
<ModernDocsContext.Provider value={docsContext}>{children}</ModernDocsContext.Provider>
);

const Page = exports.default;

Expand Down
32 changes: 19 additions & 13 deletions lib/preview-web/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { createContext } from 'react';

import type {
StoryId,
StoryName,
AnyFramework,
StoryContextForLoaders,
ComponentTitle,
Args,
Globals,
} from '@storybook/csf';
import type { Story } from '@storybook/store';
import { PreviewWeb } from './PreviewWeb';
Expand All @@ -27,16 +27,22 @@ export interface DocsContextProps<TFramework extends AnyFramework = AnyFramework
*/
mdxStoryNameToKey?: Record<string, string>;
mdxComponentAnnotations?: any;
}

// FIXME -- should we change the above to legacy?
export interface ModernDocsContextProps<TFramework extends AnyFramework = AnyFramework> {
id: StoryId;
title: ComponentTitle;
name: StoryName;
storyIdByRef: (ref: any) => StoryId;

// These keys are deprecated and will be removed in v7
/** @deprecated */
kind?: ComponentTitle;
/** @deprecated */
story?: StoryName;
/** @deprecated */
args?: Args;
/** @deprecated */
globals?: Globals;
/** @deprecated */
parameters?: Globals;
// FIXME: do we still want these?
componentStories: () => Story<TFramework>[];
loadStory: (id: StoryId) => Promise<Story<TFramework>>;

renderStoryToElement: PreviewWeb<TFramework>['renderStoryToElement'];
getStoryContext: (story: Story<TFramework>) => StoryContextForLoaders<TFramework>;
}

// FIXME -- we can't have a dependency on react here
export const ModernDocsContext = createContext<ModernDocsContextProps>(null);
13 changes: 12 additions & 1 deletion lib/store/src/StoryStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ export class StoryStore<TFramework extends AnyFramework> {

resolveInitializationPromise: () => void;

/**
* A map of exported ref to story id, for later consumption
*/
refMap: Map<any, StoryId> = new Map();

constructor() {
this.globals = new GlobalsStore();
this.args = new ArgsStore();
Expand Down Expand Up @@ -144,7 +149,7 @@ export class StoryStore<TFramework extends AnyFramework> {
const { importPath, title } = this.storyIndex.storyIdToEntry(storyId);
return this.importFn(importPath).then((moduleExports) =>
// We pass the title in here as it may have been generated by autoTitle on the server.
this.processCSFFileWithCache(moduleExports, importPath, title)
this.processCSFFileWithCache(moduleExports, importPath, title, this.refMap)
);
}

Expand Down Expand Up @@ -184,6 +189,12 @@ export class StoryStore<TFramework extends AnyFramework> {
return this.storyFromCSFFile({ storyId, csfFile });
}

storyIdByRef({ ref }: { ref: any }) {
if (this.refMap.has(ref)) return this.refMap.get(ref);

throw new Error(`Couldn't find story for that reference: ${ref}.`);
}

// This function is synchronous for convenience -- often times if you have a CSF file already
// it is easier not to have to await `loadStory`.
storyFromCSFFile({
Expand Down
15 changes: 12 additions & 3 deletions lib/store/src/csf/processCSFFile.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import type { Parameters, AnyFramework, ComponentTitle } from '@storybook/csf';
import type { Parameters, AnyFramework, ComponentTitle, StoryId } from '@storybook/csf';
import { isExportStory } from '@storybook/csf';
import { logger } from '@storybook/client-logger';

import { normalizeStory } from './normalizeStory';
import { normalizeComponentAnnotations } from './normalizeComponentAnnotations';
import type { ModuleExports, CSFFile, NormalizedComponentAnnotations, Path } from '../types';
import type {
ModuleExports,
CSFFile,
NormalizedComponentAnnotations,
Path,
NormalizedStoryAnnotations,
} from '../types';

const checkGlobals = (parameters: Parameters) => {
const { globals, globalTypes } = parameters;
Expand Down Expand Up @@ -36,7 +42,8 @@ const checkDisallowedParameters = (parameters: Parameters) => {
export function processCSFFile<TFramework extends AnyFramework>(
moduleExports: ModuleExports,
importPath: Path,
title: ComponentTitle
title: ComponentTitle,
refMap?: Map<any, StoryId>
): CSFFile<TFramework> {
const { default: defaultExport, __namedExportsOrder, ...namedExports } = moduleExports;

Expand All @@ -51,6 +58,8 @@ export function processCSFFile<TFramework extends AnyFramework>(
const storyMeta = normalizeStory(key, namedExports[key], meta);
checkDisallowedParameters(storyMeta.parameters);

// Track which exports get turned into which ids
if (refMap) refMap.set(namedExports[key], storyMeta.id);
csfFile.stories[storyMeta.id] = storyMeta;
}
});
Expand Down

0 comments on commit ce7a34e

Please sign in to comment.