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

Addon-docs: Switch Meta block to receive all module exports #18514

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 14 additions & 10 deletions addons/docs/src/blocks/DocsContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,17 +36,21 @@ const warnOptionsTheme = deprecate(
);

export const DocsContainer: FunctionComponent<DocsContainerProps> = ({ context, children }) => {
const { id: storyId, storyById } = context;
const {
parameters: { options = {}, docs = {} },
} = storyById(storyId);
let themeVars = docs.theme;
if (!themeVars && options.theme) {
warnOptionsTheme();
themeVars = options.theme;
const { id: storyId, type, storyById } = context;
const allComponents = { ...defaultComponents };
let theme = ensureTheme(null);
if (type === 'legacy') {
const {
parameters: { options = {}, docs = {} },
} = storyById(storyId);
let themeVars = docs.theme;
if (!themeVars && options.theme) {
warnOptionsTheme();
themeVars = options.theme;
}
theme = ensureTheme(themeVars);
Object.assign(allComponents, docs.components);
}
const theme = ensureTheme(themeVars);
const allComponents = { ...defaultComponents, ...docs.components };

useEffect(() => {
let url;
Expand Down
11 changes: 2 additions & 9 deletions addons/docs/src/blocks/DocsRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { DocsRenderFunction } from '@storybook/preview-web';

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

export class DocsRenderer<TFramework extends AnyFramework> {
public render: DocsRenderFunction<TFramework>;
Expand Down Expand Up @@ -33,15 +33,8 @@ async function renderDocsAsync<TFramework extends AnyFramework>(
docsParameters: Parameters,
element: HTMLElement
) {
// FIXME -- use DocsContainer, make it work for modern
const SimpleContainer = ({ children }: any) => (
<DocsContext.Provider value={docsContext}>{children} </DocsContext.Provider>
);

const Container: ComponentType<{ context: DocsContextProps<TFramework> }> =
docsParameters.container ||
(await docsParameters.getContainer?.()) ||
(docsContext.type === 'legacy' ? DocsContainer : SimpleContainer);
docsParameters.container || (await docsParameters.getContainer?.()) || DocsContainer;

const Page: ComponentType = docsParameters.page || (await docsParameters.getPage?.()) || DocsPage;

Expand Down
7 changes: 4 additions & 3 deletions addons/docs/src/blocks/ExternalDocsContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react';

import { ThemeProvider, themes, ensure } from '@storybook/theming';
import { DocsContextProps } from '@storybook/preview-web';
import { ModuleExport, Story } from '@storybook/store';
import { ModuleExport, ModuleExports, Story } from '@storybook/store';
import { AnyFramework, StoryId } from '@storybook/csf';

import { DocsContext } from './DocsContext';
Expand All @@ -28,8 +28,8 @@ export const ExternalDocsContainer: React.FC<{ projectAnnotations: any }> = ({
title: 'External',
name: 'Docs',

storyIdByModuleExport: (storyExport: ModuleExport) => {
return preview.storyIdByModuleExport(storyExport, pageMeta);
storyIdByModuleExport: (storyExport: ModuleExport, metaExport: ModuleExports) => {
return preview.storyIdByModuleExport(storyExport, metaExport || pageMeta);
},

storyById: (id: StoryId) => {
Expand All @@ -41,6 +41,7 @@ export const ExternalDocsContainer: React.FC<{ projectAnnotations: any }> = ({
},

componentStories: () => {
// TODO: could implement in a very similar way to in DocsRender. (TODO: How to share code?)
throw new Error('not implemented');
},

Expand Down
21 changes: 14 additions & 7 deletions addons/docs/src/blocks/ExternalPreview.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { StoryId } from '@storybook/csf';
import { ExternalPreview } from './ExternalPreview';

const projectAnnotations = { render: jest.fn(), renderToDOM: jest.fn() };
Expand All @@ -18,7 +19,10 @@ describe('ExternalPreview', () => {
it('handles csf files with titles', async () => {
const preview = new ExternalPreview(projectAnnotations);

const storyId = preview.storyIdByModuleExport(csfFileWithTitle.one, csfFileWithTitle.default);
const storyId = preview.storyIdByModuleExport(
csfFileWithTitle.one,
csfFileWithTitle
) as StoryId;
const story = preview.storyById(storyId);

expect(story).toMatchObject({
Expand All @@ -30,10 +34,13 @@ describe('ExternalPreview', () => {
it('returns consistent story ids and objects', () => {
const preview = new ExternalPreview(projectAnnotations);

const storyId = preview.storyIdByModuleExport(csfFileWithTitle.one, csfFileWithTitle.default);
const storyId = preview.storyIdByModuleExport(
csfFileWithTitle.one,
csfFileWithTitle
) as StoryId;
const story = preview.storyById(storyId);

expect(preview.storyIdByModuleExport(csfFileWithTitle.one, csfFileWithTitle.default)).toEqual(
expect(preview.storyIdByModuleExport(csfFileWithTitle.one, csfFileWithTitle)).toEqual(
storyId
);
expect(preview.storyById(storyId)).toBe(story);
Expand All @@ -43,11 +50,11 @@ describe('ExternalPreview', () => {
const preview = new ExternalPreview(projectAnnotations);

preview.storyById(
preview.storyIdByModuleExport(csfFileWithTitle.one, csfFileWithTitle.default)
preview.storyIdByModuleExport(csfFileWithTitle.one, csfFileWithTitle) as StoryId
);

const story = preview.storyById(
preview.storyIdByModuleExport(csfFileWithTitle.two, csfFileWithTitle.default)
preview.storyIdByModuleExport(csfFileWithTitle.two, csfFileWithTitle) as StoryId
);
expect(story).toMatchObject({
title: 'Component',
Expand All @@ -60,8 +67,8 @@ describe('ExternalPreview', () => {

const storyId = preview.storyIdByModuleExport(
csfFileWithoutTitle.one,
csfFileWithoutTitle.default
);
csfFileWithoutTitle
) as StoryId;
const story = preview.storyById(storyId);

expect(story).toMatchObject({
Expand Down
23 changes: 10 additions & 13 deletions addons/docs/src/blocks/ExternalPreview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Path, ModuleExports, StoryIndex, ModuleExport } from '@storybook/store'
import { toId, AnyFramework, ComponentTitle, StoryId, ProjectAnnotations } from '@storybook/csf';

type StoryExport = ModuleExport;
type MetaExport = ModuleExport;
type MetaExport = ModuleExports;
type ExportName = string;

class ConstantMap<TKey, TValue extends string> {
Expand All @@ -27,8 +27,6 @@ export class ExternalPreview<TFramework extends AnyFramework> extends Preview<TF

private titles = new ConstantMap<MetaExport, ComponentTitle>('title-');

private exportNames = new ConstantMap<StoryExport, ExportName>('story-');

public storyIds = new Map<StoryExport, StoryId>();

private storyIndex: StoryIndex = { v: 4, entries: {} };
Expand All @@ -46,18 +44,17 @@ export class ExternalPreview<TFramework extends AnyFramework> extends Preview<TF

addStoryFromExports(storyExport: StoryExport, meta: MetaExport) {
const importPath = this.importPaths.get(meta);
const title = meta.title || this.titles.get(meta);
this.moduleExportsByImportPath[importPath] = meta;

const exportName = this.exportNames.get(storyExport);
const storyId = toId(title, exportName);
this.storyIds.set(storyExport, storyId);
const title = meta.default.title || this.titles.get(meta);

// We need to be sure to create a new object each time here to bust caches
this.moduleExportsByImportPath[importPath] = {
...this.moduleExportsByImportPath[importPath],
default: meta,
[exportName]: storyExport,
};
const exportEntry = Object.entries(meta).find(
([_, moduleExport]) => moduleExport === storyExport
);
if (!exportEntry)
throw new Error(`Didn't find \`of\` used in Story block in the provided CSF exports`);
const storyId = toId(title, exportEntry[0]);
this.storyIds.set(storyExport, storyId);

this.storyIndex.entries[storyId] = {
id: storyId,
Expand Down
4 changes: 3 additions & 1 deletion addons/docs/src/blocks/Meta.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import React, { FC, useContext } from 'react';
import global from 'global';
import { BaseAnnotations } from '@storybook/csf';
import type { ModuleExports } from '@storybook/store';

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

const { document } = global;

type MetaProps = BaseAnnotations & { of?: any };
type MetaProps = BaseAnnotations & { of?: ModuleExports };

function getFirstStoryId(docsContext: DocsContextProps): string {
const stories = docsContext.componentStories();
Expand Down
9 changes: 5 additions & 4 deletions addons/docs/src/blocks/Story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import React, {
import { MDXProvider } from '@mdx-js/react';
import { resetComponents, Story as PureStory, StorySkeleton } from '@storybook/components';
import { StoryId, toId, storyNameFromExport, StoryAnnotations, AnyFramework } from '@storybook/csf';
import type { Story as StoryType } from '@storybook/store';
import type { ModuleExport, ModuleExports, Story as StoryType } from '@storybook/store';

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

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

type StoryImportProps = {
Expand All @@ -53,10 +54,10 @@ export const lookupStoryId = (
);

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

if (of) {
return context.storyIdByModuleExport(of);
return context.storyIdByModuleExport(of, meta);
}

const { name } = props as StoryDefProps;
Expand Down
6 changes: 3 additions & 3 deletions examples/external-docs/components/AccountForm.mdx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Meta, Story } from '@storybook/addon-docs';
import meta, { Standard } from './AccountForm.stories';
import * as AccountFormStories from './AccountForm.stories';

## Docs for Account form

<Meta of={meta} />
<Meta of={AccountFormStories} />

<Story of={Standard} />
<Story of={AccountFormStories.Standard} />
2 changes: 1 addition & 1 deletion examples/external-docs/pages/AccountForm.mdx
10 changes: 5 additions & 5 deletions examples/external-docs/pages/index.mdx
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import Callout from 'nextra-theme-docs/callout';
import { Title, Meta, Story, Canvas } from '@storybook/addon-docs';
import meta, { Standard } from '../components/AccountForm.stories';
import buttonMeta, { Basic } from '../components/button.stories';
import * as AccountFormStories from '../components/AccountForm.stories';
import * as ButtonStories from '../components/button.stories';

<Title>Embedded docs demo</Title>

<Meta of={meta} />
<Meta of={AccountFormStories} />

This is an example of an MDX file that embeds Doc Blocks and CSF stories.

<Canvas withSource={{ language: 'html', code: '<h1>hahaha</h1>' }}>
<Story of={Standard} />
<Story of={AccountFormStories.Standard} />
</Canvas>

<Story of={Basic} meta={buttonMeta} />
<Story of={ButtonStories.Basic} meta={ButtonStories} />

<Callout emoji="✅">
**MDX** (the library), at its core, transforms MDX (the syntax) to JSX. It receives an MDX string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ DarkModeDocs.decorators = [
(storyFn) => (
<DocsContainer
context={{
type: 'legacy',
componentStories: () => [],
storyById: () => ({ parameters: { docs: { theme: themes.dark } } }),
}}
Expand Down
10 changes: 6 additions & 4 deletions examples/react-ts/src/docs2/MetaOf.mdx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { Meta, Story } from '@storybook/addon-docs';
import meta, { Basic } from '../button.stories';
import { Meta, Story, Stories } from '@storybook/addon-docs';
import * as ButtonStories from '../button.stories';

<Meta of={meta} />
<Meta of={ButtonStories} />

# Docs with of

hello docs

<Story of={Basic} />
<Story of={ButtonStories.Basic} />

<Stories />
4 changes: 2 additions & 2 deletions lib/core-server/src/utils/StoryIndexGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ export class StoryIndexGenerator {
async ensureExtracted(): Promise<IndexEntry[]> {
// First process all the story files. Then, in a second pass,
// process the docs files. The reason for this is that the docs
// files may use the `<Meta of={meta} />` syntax, which requires
// files may use the `<Meta of={XStories} />` syntax, which requires
// that the story file that contains the meta be processed first.
await this.updateExtracted(async (specifier, absolutePath) =>
this.isDocsMdx(absolutePath) ? false : this.extractStories(specifier, absolutePath)
Expand Down Expand Up @@ -193,7 +193,7 @@ export class StoryIndexGenerator {
// are invalidated.
const dependencies = this.findDependencies(absoluteImports);

// Also, if `result.of` is set, it means that we're using the `<Meta of={meta} />` syntax,
// Also, if `result.of` is set, it means that we're using the `<Meta of={XStories} />` syntax,
// so find the `title` defined the file that `meta` points to.
let ofTitle: string;
if (result.of) {
Expand Down
Loading