diff --git a/code/addons/docs/template/stories/docspage/basic.stories.ts b/code/addons/docs/template/stories/docspage/basic.stories.ts index 36f2ca2ca7a4..46d78074ecf8 100644 --- a/code/addons/docs/template/stories/docspage/basic.stories.ts +++ b/code/addons/docs/template/stories/docspage/basic.stories.ts @@ -2,6 +2,7 @@ import globalThis from 'global'; export default { component: globalThis.Components.Button, + tags: ['docsPage'], args: { label: 'Click Me!' }, parameters: { chromatic: { disable: true } }, }; diff --git a/code/addons/docs/template/stories/docspage/description.stories.ts b/code/addons/docs/template/stories/docspage/description.stories.ts index 5e9dae4ab56a..7eb04413a94a 100644 --- a/code/addons/docs/template/stories/docspage/description.stories.ts +++ b/code/addons/docs/template/stories/docspage/description.stories.ts @@ -6,6 +6,7 @@ export default { subcomponents: { Pre: globalThis.Components.Pre, }, + tags: ['docsPage'], args: { label: 'Click Me!' }, parameters: { docs: { diff --git a/code/addons/docs/template/stories/docspage/extract-description.stories.ts b/code/addons/docs/template/stories/docspage/extract-description.stories.ts index 89bc6355f089..51ab3513c58d 100644 --- a/code/addons/docs/template/stories/docspage/extract-description.stories.ts +++ b/code/addons/docs/template/stories/docspage/extract-description.stories.ts @@ -2,6 +2,7 @@ import globalThis from 'global'; export default { component: globalThis.Components.Button, + tags: ['docsPage'], args: { label: 'Click Me!' }, parameters: { docs: { diff --git a/code/addons/docs/template/stories/docspage/iframe.stories.ts b/code/addons/docs/template/stories/docspage/iframe.stories.ts index 8cd7caa65a67..df9cf977c9fc 100644 --- a/code/addons/docs/template/stories/docspage/iframe.stories.ts +++ b/code/addons/docs/template/stories/docspage/iframe.stories.ts @@ -2,6 +2,7 @@ import globalThis from 'global'; export default { component: globalThis.Components.Button, + tags: ['docsPage'], args: { label: 'Rendered in iframe' }, parameters: { chromatic: { disable: true }, diff --git a/code/addons/docs/template/stories/docspage/overflow.stories.ts b/code/addons/docs/template/stories/docspage/overflow.stories.ts index 6be8604b0e19..a281100c8854 100644 --- a/code/addons/docs/template/stories/docspage/overflow.stories.ts +++ b/code/addons/docs/template/stories/docspage/overflow.stories.ts @@ -2,6 +2,7 @@ import globalThis from 'global'; export default { component: globalThis.Components.Pre, + tags: ['docsPage'], args: { text: 'Demonstrates overflow', style: { width: 2000, height: 500, background: 'hotpink' }, diff --git a/code/addons/docs/template/stories/docspage/override.stories.ts b/code/addons/docs/template/stories/docspage/override.stories.ts index 9b33668b8840..b96dde24c07a 100644 --- a/code/addons/docs/template/stories/docspage/override.stories.ts +++ b/code/addons/docs/template/stories/docspage/override.stories.ts @@ -15,6 +15,7 @@ const Override = () => 'overridden'; export default { component: globalThis.Components.Button, + tags: ['docsPage'], args: { label: 'Click Me!' }, parameters: { chromatic: { disable: true }, diff --git a/code/addons/docs/template/stories/docspage/source.stories.ts b/code/addons/docs/template/stories/docspage/source.stories.ts index 93260bc1ceb8..62eb646c6a00 100644 --- a/code/addons/docs/template/stories/docspage/source.stories.ts +++ b/code/addons/docs/template/stories/docspage/source.stories.ts @@ -2,6 +2,7 @@ import globalThis from 'global'; export default { component: globalThis.Components.Button, + tags: ['docsPage'], args: { label: 'Click Me!' }, parameters: { chromatic: { disable: true } }, }; diff --git a/code/e2e-tests/addon-actions.spec.ts b/code/e2e-tests/addon-actions.spec.ts index 70b1de7357f6..06ebe27b09e6 100644 --- a/code/e2e-tests/addon-actions.spec.ts +++ b/code/e2e-tests/addon-actions.spec.ts @@ -13,7 +13,7 @@ test.describe('addon-actions', () => { test('should trigger an action', async ({ page }) => { const sbPage = new SbPage(page); - await sbPage.navigateToStory('example-button', 'primary'); + await sbPage.navigateToStory('example/button', 'primary'); const root = sbPage.previewRoot(); const button = root.locator('button', { hasText: 'Button' }); await button.click(); diff --git a/code/e2e-tests/addon-backgrounds.spec.ts b/code/e2e-tests/addon-backgrounds.spec.ts index f43c4b7c4021..a3ea50f9d454 100644 --- a/code/e2e-tests/addon-backgrounds.spec.ts +++ b/code/e2e-tests/addon-backgrounds.spec.ts @@ -13,7 +13,7 @@ test.describe('addon-backgrounds', () => { test('should have a dark background', async ({ page }) => { const sbPage = new SbPage(page); - await sbPage.navigateToStory('example-button', 'primary'); + await sbPage.navigateToStory('example/button', 'primary'); await sbPage.selectToolbar('[title="Change the background of the preview"]', '#dark'); await expect(sbPage.getCanvasBodyElement()).toHaveCSS('background-color', 'rgb(51, 51, 51)'); @@ -22,7 +22,7 @@ test.describe('addon-backgrounds', () => { test('should apply a grid', async ({ page }) => { const sbPage = new SbPage(page); - await sbPage.navigateToStory('example-button', 'primary'); + await sbPage.navigateToStory('example/button', 'primary'); await sbPage.selectToolbar('[title="Apply a grid to the preview"]'); await expect(sbPage.getCanvasBodyElement()).toHaveCSS('background-image', /linear-gradient/); diff --git a/code/e2e-tests/addon-controls.spec.ts b/code/e2e-tests/addon-controls.spec.ts index df032a81ecaa..bcbf96019947 100644 --- a/code/e2e-tests/addon-controls.spec.ts +++ b/code/e2e-tests/addon-controls.spec.ts @@ -10,7 +10,7 @@ test.describe('addon-controls', () => { const sbPage = new SbPage(page); await sbPage.waitUntilLoaded(); - await sbPage.navigateToStory('example-button', 'primary'); + await sbPage.navigateToStory('example/button', 'primary'); await sbPage.viewAddonPanel('Controls'); // Text input: Label diff --git a/code/e2e-tests/addon-docs.spec.ts b/code/e2e-tests/addon-docs.spec.ts index 4fd01df65580..0ae090f8dc21 100644 --- a/code/e2e-tests/addon-docs.spec.ts +++ b/code/e2e-tests/addon-docs.spec.ts @@ -22,7 +22,7 @@ test.describe('addon-docs', () => { ); const sbPage = new SbPage(page); - await sbPage.navigateToStory('example-button', 'docs'); + await sbPage.navigateToStory('addons/docs/docspage/basic', 'docs'); const root = sbPage.previewRoot(); const toggles = root.locator('.docblock-code-toggle'); diff --git a/code/e2e-tests/addon-interactions.spec.ts b/code/e2e-tests/addon-interactions.spec.ts index 42dc91730c70..22b189b7217b 100644 --- a/code/e2e-tests/addon-interactions.spec.ts +++ b/code/e2e-tests/addon-interactions.spec.ts @@ -23,7 +23,7 @@ test.describe('addon-interactions', () => { const sbPage = new SbPage(page); - await sbPage.navigateToStory('example-page', 'logged-in'); + await sbPage.navigateToStory('example/page', 'logged-in'); await sbPage.viewAddonPanel('Interactions'); const welcome = await sbPage.previewRoot().locator('.welcome'); diff --git a/code/e2e-tests/addon-viewport.spec.ts b/code/e2e-tests/addon-viewport.spec.ts index 1a3624b1ec94..f15b9fa55704 100644 --- a/code/e2e-tests/addon-viewport.spec.ts +++ b/code/e2e-tests/addon-viewport.spec.ts @@ -14,7 +14,7 @@ test.describe('addon-viewport', () => { const sbPage = new SbPage(page); // Click on viewport button and select small mobile - await sbPage.navigateToStory('example-button', 'primary'); + await sbPage.navigateToStory('example/button', 'primary'); await sbPage.selectToolbar('[title="Change the size of the preview"]', '#mobile1'); // Check that Button story is still displayed diff --git a/code/e2e-tests/util.ts b/code/e2e-tests/util.ts index cb8ce73949f1..32beef6b3fe9 100644 --- a/code/e2e-tests/util.ts +++ b/code/e2e-tests/util.ts @@ -1,5 +1,6 @@ -/* eslint-disable jest/no-standalone-expect */ +/* eslint-disable jest/no-standalone-expect, no-await-in-loop */ import { expect, Page } from '@playwright/test'; +import { toId } from '@storybook/csf'; export class SbPage { readonly page: Page; @@ -8,15 +9,25 @@ export class SbPage { this.page = page; } - async navigateToStory(title: string, name: string) { - const titleId = title.replace(/ /g, '-').toLowerCase(); - const storyId = name.replace(/ /g, '-').toLowerCase(); + async openComponent(title: string, hasRoot = true) { + const parts = title.split('/'); + for (let i = hasRoot ? 1 : 0; i < parts.length; i += 1) { + const parentId = toId(parts.slice(0, i + 1).join('/')); + + const parentLink = this.page.locator(`#${parentId}`); - const titleLink = this.page.locator(`#${titleId}`); - if ((await titleLink.getAttribute('aria-expanded')) === 'false') { - await titleLink.click(); + await expect(parentLink).toBeVisible(); + if ((await parentLink.getAttribute('aria-expanded')) === 'false') { + await parentLink.click(); + } } + } + + async navigateToStory(title: string, name: string) { + await this.openComponent(title); + const titleId = toId(title); + const storyId = toId(name); const storyLinkId = `#${titleId}--${storyId}`; await this.page.waitForSelector(storyLinkId); const storyLink = this.page.locator(storyLinkId); diff --git a/code/lib/builder-webpack5/src/preview/iframe-webpack.config.ts b/code/lib/builder-webpack5/src/preview/iframe-webpack.config.ts index c0545d85bee8..ac29c5ad5d95 100644 --- a/code/lib/builder-webpack5/src/preview/iframe-webpack.config.ts +++ b/code/lib/builder-webpack5/src/preview/iframe-webpack.config.ts @@ -135,7 +135,7 @@ export default async ( // of the `previewAnnotationFilename` in the template works. const entryFilename = previewAnnotationFilename.startsWith('.') ? `${previewAnnotationFilename.replace(/(\w)(\/|\\)/g, '$1-')}-generated-config-entry.js` - : previewAnnotationFilename; + : `${previewAnnotationFilename}-generated-config-entry.js`; // NOTE: although this file is also from the `dist/cjs` directory, it is actually a ESM // file, see https://github.com/storybookjs/storybook/pull/16727#issuecomment-986485173 virtualModuleMapping[entryFilename] = interpolate(entryTemplate, { diff --git a/code/lib/client-api/src/ClientApi.ts b/code/lib/client-api/src/ClientApi.ts index 76a4e5f82ece..71713d384f47 100644 --- a/code/lib/client-api/src/ClientApi.ts +++ b/code/lib/client-api/src/ClientApi.ts @@ -360,7 +360,7 @@ Read more here: https://github.com/storybookjs/storybook/blob/master/MIGRATION.m return api; }; - api.addParameters = ({ component, args, argTypes, ...parameters }: Parameters) => { + api.addParameters = ({ component, args, argTypes, tags, ...parameters }: Parameters) => { if (hasAdded) throw new Error(`You cannot add parameters after the first story for a kind. Read more here: https://github.com/storybookjs/storybook/blob/master/MIGRATION.md#can-no-longer-add-decoratorsparameters-after-stories`); @@ -369,6 +369,7 @@ Read more here: https://github.com/storybookjs/storybook/blob/master/MIGRATION.m if (component) meta.component = component; if (args) meta.args = { ...meta.args, ...args }; if (argTypes) meta.argTypes = { ...meta.argTypes, ...argTypes }; + if (tags) meta.tags = tags; return api; }; diff --git a/code/lib/client-api/src/StoryStoreFacade.ts b/code/lib/client-api/src/StoryStoreFacade.ts index 6d7136eedbd5..552cb45119eb 100644 --- a/code/lib/client-api/src/StoryStoreFacade.ts +++ b/code/lib/client-api/src/StoryStoreFacade.ts @@ -146,7 +146,7 @@ export class StoryStoreFacade { // eslint-disable-next-line @typescript-eslint/naming-convention const { default: defaultExport, __namedExportsOrder, ...namedExports } = fileExports; // eslint-disable-next-line prefer-const - let { id: componentId, title } = defaultExport || {}; + let { id: componentId, title, tags: componentTags = [] } = defaultExport || {}; const specifiers = (global.STORIES || []).map( (specifier: Store_NormalizedStoriesSpecifier & { importPathMatcher: string }) => ({ @@ -188,6 +188,30 @@ export class StoryStoreFacade { isExportStory(key, defaultExport) ); + // NOTE: this logic is equivalent to the `extractStories` function of `StoryIndexGenerator` + const docsOptions = (global.DOCS_OPTIONS || {}) as DocsOptions; + if (docsOptions.enabled && storyExports.length) { + // We will use tags soon and this crappy filename test will go away + if ( + fileName.match(/\.mdx$/) || + (docsOptions.docsPage && componentTags.includes('docsPage')) + ) { + const name = docsOptions.defaultName; + const docsId = toId(componentId || title, name); + this.entries[docsId] = { + type: 'docs', + standalone: false, + id: docsId, + title, + name, + importPath: fileName, + ...(componentId && { componentId }), + tags: [...componentTags, 'docs'], + storiesImports: [], + }; + } + } + storyExports.forEach(([key, storyExport]: [string, any]) => { const exportName = storyNameFromExport(key); const id = storyExport.parameters?.__id || toId(componentId || title, exportName); @@ -204,31 +228,10 @@ export class StoryStoreFacade { name, title, importPath: fileName, - componentId, - tags: [...(storyExport.tags || defaultExport.tags || []), 'story'], + ...(componentId && { componentId }), + tags: [...(storyExport.tags || componentTags), 'story'], }; } }); - - // NOTE: this logic is equivalent to the `extractStories` function of `StoryIndexGenerator` - const docsOptions = (global.DOCS_OPTIONS || {}) as DocsOptions; - if (docsOptions.enabled && storyExports.length) { - // We will use tags soon and this crappy filename test will go away - if (fileName.match(/\.mdx$/) || docsOptions.docsPage) { - const name = docsOptions.defaultName; - const docsId = toId(componentId || title, name); - this.entries[docsId] = { - type: 'docs', - standalone: false, - id: docsId, - title, - name, - importPath: fileName, - componentId, - tags: [...(defaultExport.tags || []), 'docs'], - storiesImports: [], - }; - } - } } } diff --git a/code/lib/core-client/src/start.test.ts b/code/lib/core-client/src/start.test.ts index 255660db0ff4..83c7bb4e673d 100644 --- a/code/lib/core-client/src/start.test.ts +++ b/code/lib/core-client/src/start.test.ts @@ -550,7 +550,7 @@ describe('start', () => { const componentCExports = { default: { title: 'Component C', - tags: ['component-tag'], + tags: ['component-tag', 'docsPage'], }, StoryOne: { render: jest.fn(), @@ -574,7 +574,6 @@ describe('start', () => { "component-c--story-one": Object { "argTypes": Object {}, "args": Object {}, - "componentId": undefined, "id": "component-c--story-one", "importPath": "exports-map-0", "initialArgs": Object {}, @@ -594,7 +593,6 @@ describe('start', () => { "component-c--story-two": Object { "argTypes": Object {}, "args": Object {}, - "componentId": undefined, "id": "component-c--story-two", "importPath": "exports-map-0", "initialArgs": Object {}, @@ -606,6 +604,7 @@ describe('start', () => { }, "tags": Array [ "component-tag", + "docsPage", "story", ], "title": "Component C", @@ -696,7 +695,6 @@ describe('start', () => { "component-c--story-one": Object { "argTypes": Object {}, "args": Object {}, - "componentId": undefined, "id": "component-c--story-one", "importPath": "exports-map-0", "initialArgs": Object {}, @@ -716,7 +714,6 @@ describe('start', () => { "component-c--story-three": Object { "argTypes": Object {}, "args": Object {}, - "componentId": undefined, "id": "component-c--story-three", "importPath": "exports-map-0", "initialArgs": Object {}, @@ -728,6 +725,7 @@ describe('start', () => { }, "tags": Array [ "component-tag", + "docsPage", "story", ], "title": "Component C", @@ -736,7 +734,6 @@ describe('start', () => { "component-c--story-two": Object { "argTypes": Object {}, "args": Object {}, - "componentId": undefined, "id": "component-c--story-two", "importPath": "exports-map-0", "initialArgs": Object {}, @@ -748,6 +745,7 @@ describe('start', () => { }, "tags": Array [ "component-tag", + "docsPage", "story", ], "title": "Component C", @@ -788,7 +786,6 @@ describe('start', () => { "component-c--story-one": Object { "argTypes": Object {}, "args": Object {}, - "componentId": undefined, "id": "component-c--story-one", "importPath": "exports-map-0", "initialArgs": Object {}, @@ -808,7 +805,6 @@ describe('start', () => { "component-c--story-two": Object { "argTypes": Object {}, "args": Object {}, - "componentId": undefined, "id": "component-c--story-two", "importPath": "exports-map-0", "initialArgs": Object {}, @@ -820,6 +816,7 @@ describe('start', () => { }, "tags": Array [ "component-tag", + "docsPage", "story", ], "title": "Component C", @@ -828,7 +825,6 @@ describe('start', () => { "component-d--story-four": Object { "argTypes": Object {}, "args": Object {}, - "componentId": undefined, "id": "component-d--story-four", "importPath": "exports-map-1", "initialArgs": Object {}, @@ -862,7 +858,6 @@ describe('start', () => { "component-c--story-one": Object { "argTypes": Object {}, "args": Object {}, - "componentId": undefined, "id": "component-c--story-one", "importPath": "exports-map-0", "initialArgs": Object {}, @@ -882,7 +877,6 @@ describe('start', () => { "component-c--story-two": Object { "argTypes": Object {}, "args": Object {}, - "componentId": undefined, "id": "component-c--story-two", "importPath": "exports-map-0", "initialArgs": Object {}, @@ -894,6 +888,7 @@ describe('start', () => { }, "tags": Array [ "component-tag", + "docsPage", "story", ], "title": "Component C", @@ -961,7 +956,6 @@ describe('start', () => { Object { "entries": Object { "introduction": Object { - "componentId": undefined, "id": "introduction", "importPath": "./Introduction.stories.mdx", "name": undefined, @@ -1070,7 +1064,6 @@ describe('start', () => { "component-c--story-one": Object { "argTypes": Object {}, "args": Object {}, - "componentId": undefined, "id": "component-c--story-one", "importPath": "exports-map-0", "initialArgs": Object {}, @@ -1090,7 +1083,6 @@ describe('start', () => { "component-c--story-two": Object { "argTypes": Object {}, "args": Object {}, - "componentId": undefined, "id": "component-c--story-two", "importPath": "exports-map-0", "initialArgs": Object {}, @@ -1102,6 +1094,7 @@ describe('start', () => { }, "tags": Array [ "component-tag", + "docsPage", "story", ], "title": "Component C", @@ -1125,10 +1118,165 @@ describe('start', () => { describe('docsPage', () => { beforeEach(() => { - global.DOCS_OPTIONS = { enabled: true, docsPage: true, defaultTitle: 'Docs' }; + global.DOCS_OPTIONS = { enabled: true, docsPage: true, defaultName: 'Docs' }; }); - it('adds stories for each component', async () => {}); + it('adds stories for each component with docsPage tag', async () => { + const renderToDOM = jest.fn(); + + const { configure, clientApi } = start(renderToDOM); + configure('test', () => { + clientApi + .storiesOf('Component A', { id: 'file1' } as NodeModule) + .add('Story One', jest.fn()) + .add('Story Two', jest.fn()); + + clientApi + .storiesOf('Component B', { id: 'file2' } as NodeModule) + .addParameters({ tags: ['docsPage'] }) + .add('Story Three', jest.fn()); + + return [componentCExports]; + }); + + await waitForRender(); + expect(mockChannel.emit.mock.calls.find((call: [string, any]) => call[0] === SET_INDEX)[1]) + .toMatchInlineSnapshot(` + Object { + "entries": Object { + "component-a--story-one": Object { + "argTypes": Object {}, + "args": Object {}, + "componentId": "component-a", + "id": "component-a--story-one", + "importPath": "file1", + "initialArgs": Object {}, + "name": "Story One", + "parameters": Object { + "__id": "component-a--story-one", + "__isArgsStory": false, + "fileName": "file1", + "framework": "test", + }, + "tags": Array [ + "story", + ], + "title": "Component A", + "type": "story", + }, + "component-a--story-two": Object { + "argTypes": Object {}, + "args": Object {}, + "componentId": "component-a", + "id": "component-a--story-two", + "importPath": "file1", + "initialArgs": Object {}, + "name": "Story Two", + "parameters": Object { + "__id": "component-a--story-two", + "__isArgsStory": false, + "fileName": "file1", + "framework": "test", + }, + "tags": Array [ + "story", + ], + "title": "Component A", + "type": "story", + }, + "component-b--docs": Object { + "componentId": "component-b", + "id": "component-b--docs", + "importPath": "file2", + "name": "Docs", + "standalone": false, + "storiesImports": Array [], + "tags": Array [ + "docsPage", + "docs", + ], + "title": "Component B", + "type": "docs", + }, + "component-b--story-three": Object { + "argTypes": Object {}, + "args": Object {}, + "componentId": "component-b", + "id": "component-b--story-three", + "importPath": "file2", + "initialArgs": Object {}, + "name": "Story Three", + "parameters": Object { + "__id": "component-b--story-three", + "__isArgsStory": false, + "fileName": "file2", + "framework": "test", + }, + "tags": Array [ + "docsPage", + "story", + ], + "title": "Component B", + "type": "story", + }, + "component-c--docs": Object { + "id": "component-c--docs", + "importPath": "exports-map-0", + "name": "Docs", + "standalone": false, + "storiesImports": Array [], + "tags": Array [ + "component-tag", + "docsPage", + "docs", + ], + "title": "Component C", + "type": "docs", + }, + "component-c--story-one": Object { + "argTypes": Object {}, + "args": Object {}, + "id": "component-c--story-one", + "importPath": "exports-map-0", + "initialArgs": Object {}, + "name": "Story One", + "parameters": Object { + "__isArgsStory": false, + "fileName": "exports-map-0", + "framework": "test", + }, + "tags": Array [ + "story-tag", + "story", + ], + "title": "Component C", + "type": "story", + }, + "component-c--story-two": Object { + "argTypes": Object {}, + "args": Object {}, + "id": "component-c--story-two", + "importPath": "exports-map-0", + "initialArgs": Object {}, + "name": "Story Two", + "parameters": Object { + "__isArgsStory": false, + "fileName": "exports-map-0", + "framework": "test", + }, + "tags": Array [ + "component-tag", + "docsPage", + "story", + ], + "title": "Component C", + "type": "story", + }, + }, + "v": 4, + } + `); + }); }); }); @@ -1153,7 +1301,6 @@ describe('start', () => { "auto-title--story-one": Object { "argTypes": Object {}, "args": Object {}, - "componentId": undefined, "id": "auto-title--story-one", "importPath": "exports-map-0", "initialArgs": Object {}, diff --git a/code/lib/core-server/src/utils/StoryIndexGenerator.test.ts b/code/lib/core-server/src/utils/StoryIndexGenerator.test.ts index 6b5eb1b34b10..49482300b580 100644 --- a/code/lib/core-server/src/utils/StoryIndexGenerator.test.ts +++ b/code/lib/core-server/src/utils/StoryIndexGenerator.test.ts @@ -4,7 +4,10 @@ import path from 'path'; import fs from 'fs-extra'; import { normalizeStoriesEntry } from '@storybook/core-common'; -import type { CoreCommon_NormalizedStoriesSpecifier } from '@storybook/types'; +import type { + CoreCommon_NormalizedStoriesSpecifier, + CoreCommon_StoryIndexer, +} from '@storybook/types'; import { loadCsf, getStorySortParameter } from '@storybook/csf-tools'; import { toId } from '@storybook/csf'; import { logger } from '@storybook/node-logger'; @@ -50,7 +53,9 @@ const csfIndexer = async (fileName: string, opts: any) => { const options = { configDir: path.join(__dirname, '__mockdata__'), workingDir: path.join(__dirname, '__mockdata__'), - storyIndexers: [{ test: /\.stories\..*$/, indexer: csfIndexer }], + storyIndexers: [ + { test: /\.stories\..*$/, indexer: csfIndexer as any as CoreCommon_StoryIndexer['indexer'] }, + ], storiesV2Compatibility: false, storyStoreV7: true, docs: { enabled: true, defaultName: 'docs', docsPage: false }, @@ -172,6 +177,7 @@ describe('StoryIndexGenerator', () => { "importPath": "./src/B.stories.ts", "name": "Story One", "tags": Array [ + "docsPage", "story", ], "title": "B", @@ -182,6 +188,7 @@ describe('StoryIndexGenerator', () => { "importPath": "./src/D.stories.jsx", "name": "Story One", "tags": Array [ + "docsPage", "story", ], "title": "D", @@ -251,6 +258,7 @@ describe('StoryIndexGenerator', () => { "storiesImports": Array [], "tags": Array [ "component-tag", + "docsPage", "docs", ], "title": "A", @@ -311,7 +319,7 @@ describe('StoryIndexGenerator', () => { ...options, docs: { ...options.docs, docsPage: true }, }; - it('generates an entry per CSF file', async () => { + it('generates an entry per CSF file with the docsPage tag', async () => { const specifier: CoreCommon_NormalizedStoriesSpecifier = normalizeStoriesEntry( './src/**/*.stories.(ts|js|jsx)', options @@ -331,6 +339,7 @@ describe('StoryIndexGenerator', () => { "storiesImports": Array [], "tags": Array [ "component-tag", + "docsPage", "docs", ], "title": "A", @@ -354,6 +363,7 @@ describe('StoryIndexGenerator', () => { "standalone": false, "storiesImports": Array [], "tags": Array [ + "docsPage", "docs", ], "title": "B", @@ -364,6 +374,7 @@ describe('StoryIndexGenerator', () => { "importPath": "./src/B.stories.ts", "name": "Story One", "tags": Array [ + "docsPage", "story", ], "title": "B", @@ -376,6 +387,7 @@ describe('StoryIndexGenerator', () => { "standalone": false, "storiesImports": Array [], "tags": Array [ + "docsPage", "docs", ], "title": "D", @@ -386,23 +398,12 @@ describe('StoryIndexGenerator', () => { "importPath": "./src/D.stories.jsx", "name": "Story One", "tags": Array [ + "docsPage", "story", ], "title": "D", "type": "story", }, - "first-nested-deeply-f--docs": Object { - "id": "first-nested-deeply-f--docs", - "importPath": "./src/first-nested/deeply/F.stories.js", - "name": "docs", - "standalone": false, - "storiesImports": Array [], - "tags": Array [ - "docs", - ], - "title": "first-nested/deeply/F", - "type": "docs", - }, "first-nested-deeply-f--story-one": Object { "id": "first-nested-deeply-f--story-one", "importPath": "./src/first-nested/deeply/F.stories.js", @@ -413,19 +414,6 @@ describe('StoryIndexGenerator', () => { "title": "first-nested/deeply/F", "type": "story", }, - "nested-button--docs": Object { - "id": "nested-button--docs", - "importPath": "./src/nested/Button.stories.ts", - "name": "docs", - "standalone": false, - "storiesImports": Array [], - "tags": Array [ - "component-tag", - "docs", - ], - "title": "nested/Button", - "type": "docs", - }, "nested-button--story-one": Object { "id": "nested-button--story-one", "importPath": "./src/nested/Button.stories.ts", @@ -437,18 +425,6 @@ describe('StoryIndexGenerator', () => { "title": "nested/Button", "type": "story", }, - "second-nested-g--docs": Object { - "id": "second-nested-g--docs", - "importPath": "./src/second-nested/G.stories.ts", - "name": "docs", - "standalone": false, - "storiesImports": Array [], - "tags": Array [ - "docs", - ], - "title": "second-nested/G", - "type": "docs", - }, "second-nested-g--story-one": Object { "id": "second-nested-g--story-one", "importPath": "./src/second-nested/G.stories.ts", @@ -534,6 +510,7 @@ describe('StoryIndexGenerator', () => { "./duplicate/SecondA.stories.js", ], "tags": Array [ + "docsPage", "docs", ], "title": "duplicate/A", @@ -544,6 +521,7 @@ describe('StoryIndexGenerator', () => { "importPath": "./duplicate/A.stories.js", "name": "Story One", "tags": Array [ + "docsPage", "story", ], "title": "duplicate/A", @@ -554,6 +532,7 @@ describe('StoryIndexGenerator', () => { "importPath": "./duplicate/SecondA.stories.js", "name": "Story Two", "tags": Array [ + "docsPage", "story", ], "title": "duplicate/A", diff --git a/code/lib/core-server/src/utils/StoryIndexGenerator.ts b/code/lib/core-server/src/utils/StoryIndexGenerator.ts index c5205b356711..2fd5658b68c5 100644 --- a/code/lib/core-server/src/utils/StoryIndexGenerator.ts +++ b/code/lib/core-server/src/utils/StoryIndexGenerator.ts @@ -215,10 +215,10 @@ export class StoryIndexGenerator { } const csf = await storyIndexer.indexer(absolutePath, { makeTitle }); - const componentTags = csf.meta.tags; + const componentTags = csf.meta.tags || []; csf.stories.forEach(({ id, name, tags: storyTags, parameters }) => { if (!parameters?.docsOnly) { - const tags = [...(storyTags || componentTags || []), 'story']; + const tags = [...(storyTags || componentTags), 'story']; entries.push({ id, title: csf.meta.title, name, importPath, tags, type: 'story' }); } }); @@ -226,7 +226,10 @@ export class StoryIndexGenerator { if (this.options.docs.enabled && csf.stories.length) { // We always add a template for *.stories.mdx, but only if docs page is enabled for // regular CSF files - if (storyIndexer.addDocsTemplate || this.options.docs.docsPage) { + if ( + storyIndexer.addDocsTemplate || + (this.options.docs.docsPage && componentTags.includes('docsPage')) + ) { const name = this.options.docs.defaultName; const id = toId(csf.meta.title, name); entries.unshift({ @@ -235,7 +238,7 @@ export class StoryIndexGenerator { name, importPath, type: 'docs', - tags: [...(componentTags || []), 'docs'], + tags: [...componentTags, 'docs'], storiesImports: [], standalone: false, }); diff --git a/code/lib/core-server/src/utils/__mockdata__/duplicate/A.stories.js b/code/lib/core-server/src/utils/__mockdata__/duplicate/A.stories.js index 75205409de90..4dcd5cd6ef0c 100644 --- a/code/lib/core-server/src/utils/__mockdata__/duplicate/A.stories.js +++ b/code/lib/core-server/src/utils/__mockdata__/duplicate/A.stories.js @@ -2,6 +2,7 @@ const component = {}; export default { component, title: 'duplicate/A', + tags: ['docsPage'], }; export const StoryOne = {}; diff --git a/code/lib/core-server/src/utils/__mockdata__/duplicate/SecondA.stories.js b/code/lib/core-server/src/utils/__mockdata__/duplicate/SecondA.stories.js index 5f813ef5073e..f7a2fe743116 100644 --- a/code/lib/core-server/src/utils/__mockdata__/duplicate/SecondA.stories.js +++ b/code/lib/core-server/src/utils/__mockdata__/duplicate/SecondA.stories.js @@ -2,6 +2,7 @@ const component = {}; export default { component, title: 'duplicate/A', + tags: ['docsPage'], }; export const StoryTwo = {}; diff --git a/code/lib/core-server/src/utils/__mockdata__/src/A.stories.js b/code/lib/core-server/src/utils/__mockdata__/src/A.stories.js index a4f0e4ef27c4..849343a613e6 100644 --- a/code/lib/core-server/src/utils/__mockdata__/src/A.stories.js +++ b/code/lib/core-server/src/utils/__mockdata__/src/A.stories.js @@ -1,7 +1,7 @@ const component = {}; export default { component, - tags: ['component-tag'], + tags: ['component-tag', 'docsPage'], }; export const StoryOne = { tags: ['story-tag'] }; diff --git a/code/lib/core-server/src/utils/__mockdata__/src/B.stories.ts b/code/lib/core-server/src/utils/__mockdata__/src/B.stories.ts index 220074c7390c..a41f1009ff48 100644 --- a/code/lib/core-server/src/utils/__mockdata__/src/B.stories.ts +++ b/code/lib/core-server/src/utils/__mockdata__/src/B.stories.ts @@ -1,6 +1,7 @@ const component = {}; export default { component, + tags: ['docsPage'], }; export const StoryOne = {}; diff --git a/code/lib/core-server/src/utils/__mockdata__/src/D.stories.jsx b/code/lib/core-server/src/utils/__mockdata__/src/D.stories.jsx index 220074c7390c..a41f1009ff48 100644 --- a/code/lib/core-server/src/utils/__mockdata__/src/D.stories.jsx +++ b/code/lib/core-server/src/utils/__mockdata__/src/D.stories.jsx @@ -1,6 +1,7 @@ const component = {}; export default { component, + tags: ['docsPage'], }; export const StoryOne = {}; diff --git a/code/lib/core-server/src/utils/stories-json.test.ts b/code/lib/core-server/src/utils/stories-json.test.ts index 3066983c958e..57ee000e09f4 100644 --- a/code/lib/core-server/src/utils/stories-json.test.ts +++ b/code/lib/core-server/src/utils/stories-json.test.ts @@ -146,6 +146,7 @@ describe('useStoriesJson', () => { "importPath": "./src/B.stories.ts", "name": "Story One", "tags": Array [ + "docsPage", "story", ], "title": "B", @@ -156,6 +157,7 @@ describe('useStoriesJson', () => { "importPath": "./src/D.stories.jsx", "name": "Story One", "tags": Array [ + "docsPage", "story", ], "title": "D", @@ -302,6 +304,7 @@ describe('useStoriesJson', () => { }, "story": "Story One", "tags": Array [ + "docsPage", "story", ], "title": "B", @@ -318,6 +321,7 @@ describe('useStoriesJson', () => { }, "story": "Story One", "tags": Array [ + "docsPage", "story", ], "title": "D", @@ -481,6 +485,7 @@ describe('useStoriesJson', () => { }, "story": "Story One", "tags": Array [ + "docsPage", "story", ], "title": "B", @@ -497,6 +502,7 @@ describe('useStoriesJson', () => { }, "story": "Story One", "tags": Array [ + "docsPage", "story", ], "title": "D", @@ -630,6 +636,7 @@ describe('useStoriesJson', () => { }, "story": "Story One", "tags": Array [ + "docsPage", "story", ], "title": "B", @@ -646,6 +653,7 @@ describe('useStoriesJson', () => { }, "story": "Story One", "tags": Array [ + "docsPage", "story", ], "title": "D", diff --git a/code/lib/csf-tools/src/CsfFile.ts b/code/lib/csf-tools/src/CsfFile.ts index db10bc4b9305..3f342854dfab 100644 --- a/code/lib/csf-tools/src/CsfFile.ts +++ b/code/lib/csf-tools/src/CsfFile.ts @@ -5,7 +5,7 @@ import * as t from '@babel/types'; import generate from '@babel/generator'; import traverse from '@babel/traverse'; import { toId, isExportStory, storyNameFromExport } from '@storybook/csf'; -import type { CSF_Meta, CSF_Story, CSF_Tag } from '@storybook/types'; +import type { CSF_Meta, CSF_Story, Tag } from '@storybook/types'; import { babelParse } from './babelParse'; const logger = console; @@ -33,7 +33,7 @@ function parseTags(prop: t.Node) { return prop.elements.map((e) => { if (t.isStringLiteral(e)) return e.value; throw new Error(`CSF: Expected tag to be string literal`); - }) as CSF_Tag[]; + }) as Tag[]; } const findVarInitialization = (identifier: string, program: t.Program) => { diff --git a/code/lib/types/src/modules/csf.ts b/code/lib/types/src/modules/csf.ts index e178e7b54e1d..e17fc1131ec6 100644 --- a/code/lib/types/src/modules/csf.ts +++ b/code/lib/types/src/modules/csf.ts @@ -117,22 +117,20 @@ export type { Tag, }; -export type CSF_Tag = string; - export interface CSF_Meta { id?: string; title?: string; component?: string; includeStories?: string[] | RegExp; excludeStories?: string[] | RegExp; - tags?: CSF_Tag[]; + tags?: Tag[]; } export interface CSF_Story { id: string; name: string; parameters: Parameters; - tags?: CSF_Tag[]; + tags?: Tag[]; } export type ViewMode = ViewModeBase | 'story' | 'info' | 'settings' | string | undefined; diff --git a/code/package.json b/code/package.json index 275056141f1c..5b8bb5994c8b 100644 --- a/code/package.json +++ b/code/package.json @@ -183,6 +183,7 @@ "@storybook/core-events": "workspace:*", "@storybook/core-server": "workspace:*", "@storybook/core-webpack": "workspace:*", + "@storybook/csf": "next", "@storybook/csf-tools": "workspace:*", "@storybook/docs-tools": "workspace:*", "@storybook/ember": "workspace:*", diff --git a/code/yarn.lock b/code/yarn.lock index 7cb256e7fc96..6a091a343c17 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -7425,6 +7425,7 @@ __metadata: "@storybook/core-events": "workspace:*" "@storybook/core-server": "workspace:*" "@storybook/core-webpack": "workspace:*" + "@storybook/csf": next "@storybook/csf-tools": "workspace:*" "@storybook/docs-tools": "workspace:*" "@storybook/ember": "workspace:*"