From a7cbe9907f868647f0c8b42a6c98f27349e1a636 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Wed, 13 Dec 2023 13:41:01 +0100 Subject: [PATCH 01/53] Prevent Stories lookup in node_modules --- .../src/preview/iframe-webpack.config.ts | 1 + code/lib/core-webpack/src/to-importFn.ts | 18 +++++++++++++++++- code/lib/csf-plugin/src/index.ts | 3 ++- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/code/builders/builder-webpack5/src/preview/iframe-webpack.config.ts b/code/builders/builder-webpack5/src/preview/iframe-webpack.config.ts index ac573827715..c31238b5c5e 100644 --- a/code/builders/builder-webpack5/src/preview/iframe-webpack.config.ts +++ b/code/builders/builder-webpack5/src/preview/iframe-webpack.config.ts @@ -218,6 +218,7 @@ export default async ( rules: [ { test: /\.stories\.([tj])sx?$|(stories|story)\.mdx$/, + exclude: /node_modules/, enforce: 'post', use: [ { diff --git a/code/lib/core-webpack/src/to-importFn.ts b/code/lib/core-webpack/src/to-importFn.ts index 20c3be9dc1c..83455c66cb6 100644 --- a/code/lib/core-webpack/src/to-importFn.ts +++ b/code/lib/core-webpack/src/to-importFn.ts @@ -4,6 +4,20 @@ import { globToRegexp } from '@storybook/core-common'; import { importPipeline } from './importPipeline'; +function adjustRegexToExcludeNodeModules(originalRegex: RegExp) { + const originalRegexString = originalRegex.source; + const startsWithCaret = originalRegexString.startsWith('^'); + const excludeNodeModulesPattern = startsWithCaret ? '(?!.*node_modules)' : '^(?!.*node_modules)'; + + // Combine the new exclusion pattern with the original regex + const adjustedRegexString = startsWithCaret + ? `^${excludeNodeModulesPattern}${originalRegexString.substring(1)}` + : excludeNodeModulesPattern + originalRegexString; + + // Create and return the new regex + return new RegExp(adjustedRegexString); +} + export function webpackIncludeRegexp(specifier: NormalizedStoriesSpecifier) { const { directory, files } = specifier; @@ -17,7 +31,9 @@ export function webpackIncludeRegexp(specifier: NormalizedStoriesSpecifier) { const webpackIncludeGlob = ['.', '..'].includes(directory) ? files : `${directoryWithoutLeadingDots}/${files}`; - const webpackIncludeRegexpWithCaret = globToRegexp(webpackIncludeGlob); + const webpackIncludeRegexpWithCaret = webpackIncludeGlob.includes('node_modules') + ? globToRegexp(webpackIncludeGlob) + : adjustRegexToExcludeNodeModules(globToRegexp(webpackIncludeGlob)); // picomatch is creating an exact match, but we are only matching the end of the filename return new RegExp(webpackIncludeRegexpWithCaret.source.replace(/^\^/, '')); } diff --git a/code/lib/csf-plugin/src/index.ts b/code/lib/csf-plugin/src/index.ts index ec0aaa1d52a..6cc9cfcd594 100644 --- a/code/lib/csf-plugin/src/index.ts +++ b/code/lib/csf-plugin/src/index.ts @@ -5,7 +5,8 @@ import type { EnrichCsfOptions } from '@storybook/csf-tools'; export type CsfPluginOptions = EnrichCsfOptions; -const STORIES_REGEX = /\.(story|stories)\.[tj]sx?$/; +// Ignore node_modules +const STORIES_REGEX = /(? Date: Thu, 21 Dec 2023 15:18:58 +0000 Subject: [PATCH 02/53] Fix addon-action module resolution broken for react-native --- code/addons/actions/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/code/addons/actions/package.json b/code/addons/actions/package.json index da85fef889d..045d22b145a 100644 --- a/code/addons/actions/package.json +++ b/code/addons/actions/package.json @@ -40,6 +40,7 @@ }, "main": "dist/index.js", "module": "dist/index.mjs", + "react-native": "dist/index.mjs", "types": "dist/index.d.ts", "typesVersions": { "*": { From 685b825ebf19dd9d55ea579579fc8d4fd40accd5 Mon Sep 17 00:00:00 2001 From: Michael Shilman Date: Thu, 28 Dec 2023 01:11:13 +0800 Subject: [PATCH 03/53] Docs: Add autodocs filter function parameter --- code/addons/docs/src/preview.ts | 3 +++ code/ui/blocks/src/blocks/Stories.tsx | 8 ++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/code/addons/docs/src/preview.ts b/code/addons/docs/src/preview.ts index 0d1183bd0cf..f0e41435bb2 100644 --- a/code/addons/docs/src/preview.ts +++ b/code/addons/docs/src/preview.ts @@ -1,8 +1,11 @@ +import type { PreparedStory } from '@storybook/types'; + export const parameters: any = { docs: { renderer: async () => { const { DocsRenderer } = (await import('./DocsRenderer')) as any; return new DocsRenderer(); }, + autodocsFilter: (story: PreparedStory) => !story.parameters?.docs?.disable, }, }; diff --git a/code/ui/blocks/src/blocks/Stories.tsx b/code/ui/blocks/src/blocks/Stories.tsx index de42c50d2e4..93e8bfd49cf 100644 --- a/code/ui/blocks/src/blocks/Stories.tsx +++ b/code/ui/blocks/src/blocks/Stories.tsx @@ -27,9 +27,13 @@ const StyledHeading: typeof Heading = styled(Heading)(({ theme }) => ({ })); export const Stories: FC = ({ title = 'Stories', includePrimary = true }) => { - const { componentStories } = useContext(DocsContext); + const { componentStories, projectAnnotations, getStoryContext } = useContext(DocsContext); - let stories = componentStories().filter((story) => !story.parameters?.docs?.disable); + let stories = componentStories(); + const { autodocsFilter } = projectAnnotations.parameters?.docs || {}; + if (autodocsFilter) { + stories = stories.filter((story) => autodocsFilter(story, getStoryContext(story))); + } if (!includePrimary) stories = stories.slice(1); From 5fe992e9201fece88571cf8e8a918a0113ff5faf Mon Sep 17 00:00:00 2001 From: Michael Shilman Date: Tue, 2 Jan 2024 18:29:20 +0800 Subject: [PATCH 04/53] Fix sidebar bottom/top --- code/ui/manager/src/container/Sidebar.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/code/ui/manager/src/container/Sidebar.tsx b/code/ui/manager/src/container/Sidebar.tsx index 9041badfd5d..a9433f94840 100755 --- a/code/ui/manager/src/container/Sidebar.tsx +++ b/code/ui/manager/src/container/Sidebar.tsx @@ -41,9 +41,8 @@ const Sidebar = React.memo(function Sideber({ onMenuClick }: SidebarProps) { const whatsNewNotificationsEnabled = state.whatsNewData?.status === 'SUCCESS' && !state.disableWhatsNewNotifications; - const items = api.getElements(types.experimental_SIDEBAR_BOTTOM); - const bottom = useMemo(() => Object.values(items), [items]); - const top = useMemo(() => Object.values(api.getElements(types.experimental_SIDEBAR_TOP)), []); + const bottom = Object.values(api.getElements(types.experimental_SIDEBAR_BOTTOM)); + const top = Object.values(api.getElements(types.experimental_SIDEBAR_TOP)); return { title: name, From 70b462ef7ddf6bf55d7500af1eab8addfcafe5c1 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Fri, 5 Jan 2024 19:15:10 -0300 Subject: [PATCH 05/53] Doc blocks: Remove deprecated props from Source block --- MIGRATION.md | 5 +++++ code/ui/blocks/src/blocks/Source.tsx | 33 ++-------------------------- docs/api/doc-block-source.md | 16 -------------- 3 files changed, 7 insertions(+), 47 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index 9d894c1b89c..2047d52480c 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -40,6 +40,7 @@ - [Deprecated docs parameters](#deprecated-docs-parameters) - [Description Doc block properties](#description-doc-block-properties) - [Manager API expandAll and collapseAll methods](#manager-api-expandall-and-collapseall-methods) + - [Source Doc block properties](#source-doc-block-properties) - [From version 7.5.0 to 7.6.0](#from-version-750-to-760) - [CommonJS with Vite is deprecated](#commonjs-with-vite-is-deprecated) - [Using implicit actions during rendering is deprecated](#using-implicit-actions-during-rendering-is-deprecated) @@ -690,6 +691,10 @@ api.collapseAll() // becomes api.emit(STORIES_COLLAPSE_ALL) api.expandAll() // becomes api.emit(STORIES_EXPAND_ALL) ``` +#### Source Doc block properties + +`id` and `ids` are now removed in favor of the `of` property. [More info](#doc-blocks). + ## From version 7.5.0 to 7.6.0 #### CommonJS with Vite is deprecated diff --git a/code/ui/blocks/src/blocks/Source.tsx b/code/ui/blocks/src/blocks/Source.tsx index 033dfd71857..38626876fa8 100644 --- a/code/ui/blocks/src/blocks/Source.tsx +++ b/code/ui/blocks/src/blocks/Source.tsx @@ -9,8 +9,6 @@ import type { } from '@storybook/types'; import { SourceType } from '@storybook/docs-tools'; -import { deprecate } from '@storybook/client-logger'; -import dedent from 'ts-dedent'; import type { SourceCodeProps } from '../components/Source'; import { Source as PureSource, SourceError } from '../components/Source'; import type { DocsContextProps } from './DocsContext'; @@ -18,8 +16,6 @@ import { DocsContext } from './DocsContext'; import type { SourceContextProps, SourceItem } from './SourceContainer'; import { UNKNOWN_ARGS_HASH, argsHash, SourceContext } from './SourceContainer'; -import { useStories } from './useStory'; - export enum SourceState { OPEN = 'open', CLOSED = 'closed', @@ -54,12 +50,6 @@ export type SourceProps = SourceParameters & { */ of?: ModuleExport; - /** @deprecated use of={storyExport} instead */ - id?: string; - - /** @deprecated use of={storyExport} instead */ - ids?: string[]; - /** * Internal prop to control if a story re-renders on args updates */ @@ -134,11 +124,7 @@ export const useSourceProps = ( docsContext: DocsContextProps, sourceContext: SourceContextProps ): PureSourceProps & SourceStateProps => { - const storyIds = props.ids || (props.id ? [props.id] : []); - const storiesFromIds = useStories(storyIds, docsContext); - - // The check didn't actually change the type. - let stories: PreparedStory[] = storiesFromIds as PreparedStory[]; + let stories: PreparedStory[] = []; const { of } = props; if ('of' in props && of === undefined) { throw new Error('Unexpected `of={undefined}`, did you mistype a CSF file reference?'); @@ -147,7 +133,7 @@ export const useSourceProps = ( if (of) { const resolved = docsContext.resolveOf(of, ['story']); stories = [resolved.story]; - } else if (stories.length === 0) { + } else { try { // Always fall back to the primary story for source parameters, even if code is set. stories = [docsContext.storyById()]; @@ -155,9 +141,6 @@ export const useSourceProps = ( // You are allowed to use and unattached. } } - if (!storiesFromIds.every(Boolean)) { - return { error: SourceError.SOURCE_UNAVAILABLE, state: SourceState.NONE }; - } const sourceParameters = (stories[0]?.parameters?.docs?.source || {}) as SourceParameters; let { code } = props; // We will fall back to `sourceParameters.code`, but per story below @@ -213,18 +196,6 @@ export const useSourceProps = ( * the source for the current story if nothing is provided. */ export const Source: FC = (props) => { - if (props.id) { - deprecate(dedent`The \`id\` prop on Source is deprecated, please use the \`of\` prop instead to reference a story. - - Please refer to the migration guide: https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#source-block - `); - } - if (props.ids) { - deprecate(dedent`The \`ids\` prop on Source is deprecated, please use the \`of\` prop instead to reference a story. - - Please refer to the migration guide: https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#source-block - `); - } const sourceContext = useContext(SourceContext); const docsContext = useContext(DocsContext); const { state, ...sourceProps } = useSourceProps(props, docsContext, sourceContext); diff --git a/docs/api/doc-block-source.md b/docs/api/doc-block-source.md index 1c4c68c5051..e1fa1682257 100644 --- a/docs/api/doc-block-source.md +++ b/docs/api/doc-block-source.md @@ -164,19 +164,3 @@ Specifies how the source code is rendered. Note that dynamic snippets will only work if the story uses [`args`](../writing-stories/args.md) and the [`Story` block](./doc-block-story.md) for that story is rendered along with the `Source` block. - -### `id` - -(⛔️ **Deprecated**) - -Type: `string` - -Specifies the story id for which to render the source code. Referencing a story this way is no longer supported; use the [`of` prop](#of), instead. - -### `ids` - -(⛔️ **Deprecated**) - -Type: `string[]` - -Specifies the story ids for which to render source code. Multiple stories are no longer supported; to render a single story's source, use the [`of` prop](#of). From 0de1e994b508852f33624ac0a477ede958d96a74 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Fri, 5 Jan 2024 19:41:17 -0300 Subject: [PATCH 06/53] remove unnecessary file --- .../internal/InternalSource.stories.tsx | 30 ------------------- 1 file changed, 30 deletions(-) delete mode 100644 code/ui/blocks/src/blocks/internal/InternalSource.stories.tsx diff --git a/code/ui/blocks/src/blocks/internal/InternalSource.stories.tsx b/code/ui/blocks/src/blocks/internal/InternalSource.stories.tsx deleted file mode 100644 index 035f1c5e108..00000000000 --- a/code/ui/blocks/src/blocks/internal/InternalSource.stories.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import { Source } from '../Source'; - -const meta: Meta = { - component: Source, - parameters: { - relativeCsfPaths: ['../examples/Button.stories', '../examples/SourceParameters.stories'], - attached: false, - docsStyles: true, - }, -}; - -export default meta; -type Story = StoryObj; - -export const Id: Story = { - args: { - id: 'storybook-blocks-examples-button--primary', - }, -}; - -export const Ids: Story = { - args: { - ids: [ - 'storybook-blocks-examples-button--primary', - 'storybook-blocks-examples-button--secondary', - ], - }, -}; From 61dc8a0bae682e58f80ad6919dd7b47adb2a1b6f Mon Sep 17 00:00:00 2001 From: Michael Shilman Date: Sat, 6 Jan 2024 22:23:11 +0800 Subject: [PATCH 07/53] Tags: Add sidebar/autodocs configuration --- code/addons/docs/src/preview.ts | 14 ++++++++++- code/builders/builder-manager/src/index.ts | 2 ++ .../builder-manager/src/utils/data.ts | 2 ++ .../builder-manager/src/utils/template.ts | 4 +++- code/builders/builder-vite/input/iframe.html | 1 + .../builder-vite/src/transform-iframe-html.ts | 4 +++- .../src/preview/iframe-webpack.config.ts | 3 +++ code/lib/core-server/package.json | 2 ++ .../core-server/src/presets/common-manager.ts | 24 +++++++++++++++++++ .../core-server/src/presets/common-preset.ts | 15 ++++++++++++ code/lib/manager-api/src/typings.d.ts | 1 + code/lib/preview-api/src/typings.d.ts | 1 + code/lib/types/src/modules/core-common.ts | 16 +++++++++++++ code/yarn.lock | 1 + 14 files changed, 87 insertions(+), 3 deletions(-) create mode 100644 code/lib/core-server/src/presets/common-manager.ts diff --git a/code/addons/docs/src/preview.ts b/code/addons/docs/src/preview.ts index f0e41435bb2..97605103af6 100644 --- a/code/addons/docs/src/preview.ts +++ b/code/addons/docs/src/preview.ts @@ -1,4 +1,13 @@ import type { PreparedStory } from '@storybook/types'; +import { global } from '@storybook/global'; + +const excludeTags = Object.entries(global.TAGS_OPTIONS).reduce((acc, entry) => { + const [tag, option] = entry; + if ((option as any).excludeFromAutodocs) { + acc[tag] = true; + } + return acc; +}, {} as Record); export const parameters: any = { docs: { @@ -6,6 +15,9 @@ export const parameters: any = { const { DocsRenderer } = (await import('./DocsRenderer')) as any; return new DocsRenderer(); }, - autodocsFilter: (story: PreparedStory) => !story.parameters?.docs?.disable, + autodocsFilter: (story: PreparedStory) => { + const tags = story.tags || []; + return tags.filter((tag) => excludeTags[tag]).length === 0 && !story.parameters.docs?.disable; + }, }, }; diff --git a/code/builders/builder-manager/src/index.ts b/code/builders/builder-manager/src/index.ts index b7923a64a2b..006c998014d 100644 --- a/code/builders/builder-manager/src/index.ts +++ b/code/builders/builder-manager/src/index.ts @@ -138,6 +138,7 @@ const starter: StarterFunction = async function* starterGeneratorFn({ title, logLevel, docsOptions, + tagsOptions, } = await getData(options); yield; @@ -175,6 +176,7 @@ const starter: StarterFunction = async function* starterGeneratorFn({ refs, logLevel, docsOptions, + tagsOptions, options ); diff --git a/code/builders/builder-manager/src/utils/data.ts b/code/builders/builder-manager/src/utils/data.ts index eb6754e7635..2b7fc15e137 100644 --- a/code/builders/builder-manager/src/utils/data.ts +++ b/code/builders/builder-manager/src/utils/data.ts @@ -14,6 +14,7 @@ export const getData = async (options: Options) => { const logLevel = options.presets.apply('logLevel'); const title = options.presets.apply('title'); const docsOptions = options.presets.apply('docs', {}); + const tagsOptions = options.presets.apply('tags', {}); const template = readTemplate('template.ejs'); const customHead = options.presets.apply('managerHead'); @@ -35,5 +36,6 @@ export const getData = async (options: Options) => { config, logLevel, favicon, + tagsOptions, }; }; diff --git a/code/builders/builder-manager/src/utils/template.ts b/code/builders/builder-manager/src/utils/template.ts index 0d7b67a1dff..4ccb2d50864 100644 --- a/code/builders/builder-manager/src/utils/template.ts +++ b/code/builders/builder-manager/src/utils/template.ts @@ -3,7 +3,7 @@ import fs from 'fs-extra'; import { render } from 'ejs'; -import type { DocsOptions, Options, Ref } from '@storybook/types'; +import type { DocsOptions, TagsOptions, Options, Ref } from '@storybook/types'; export const getTemplatePath = async (template: string) => { return join( @@ -34,6 +34,7 @@ export const renderHTML = async ( refs: Promise>, logLevel: Promise, docsOptions: Promise, + tagsOptions: Promise, { versionCheck, previewUrl, configType, ignorePreview }: Options ) => { const titleRef = await title; @@ -52,6 +53,7 @@ export const renderHTML = async ( // These two need to be double stringified because the UI expects a string VERSIONCHECK: JSON.stringify(JSON.stringify(versionCheck), null, 2), PREVIEW_URL: JSON.stringify(previewUrl, null, 2), // global preview URL + TAGS_OPTIONS: JSON.stringify(await tagsOptions, null, 2), }, head: (await customHead) || '', ignorePreview, diff --git a/code/builders/builder-vite/input/iframe.html b/code/builders/builder-vite/input/iframe.html index 867a16a4a22..dd976d6c4ab 100644 --- a/code/builders/builder-vite/input/iframe.html +++ b/code/builders/builder-vite/input/iframe.html @@ -21,6 +21,7 @@ window.FEATURES = '[FEATURES HERE]'; window.STORIES = '[STORIES HERE]'; window.DOCS_OPTIONS = '[DOCS_OPTIONS HERE]'; + window.TAGS_OPTIONS = '[TAGS_OPTIONS HERE]'; ('OTHER_GLOBLALS HERE'); diff --git a/code/builders/builder-vite/src/transform-iframe-html.ts b/code/builders/builder-vite/src/transform-iframe-html.ts index 8c054612516..a4a482b2f11 100644 --- a/code/builders/builder-vite/src/transform-iframe-html.ts +++ b/code/builders/builder-vite/src/transform-iframe-html.ts @@ -1,5 +1,5 @@ import { normalizeStories } from '@storybook/core-common'; -import type { DocsOptions, Options } from '@storybook/types'; +import type { DocsOptions, TagsOptions, Options } from '@storybook/types'; export type PreviewHtml = string | undefined; @@ -11,6 +11,7 @@ export async function transformIframeHtml(html: string, options: Options) { const bodyHtmlSnippet = await presets.apply('previewBody'); const logLevel = await presets.apply('logLevel', undefined); const docsOptions = await presets.apply('docs'); + const tagsOptions = await presets.apply('tags'); const coreOptions = await presets.apply('core'); const stories = normalizeStories(await options.presets.apply('stories', [], options), { @@ -42,6 +43,7 @@ export async function transformIframeHtml(html: string, options: Options) { .replace(`'[FEATURES HERE]'`, JSON.stringify(features || {})) .replace(`'[STORIES HERE]'`, JSON.stringify(stories || {})) .replace(`'[DOCS_OPTIONS HERE]'`, JSON.stringify(docsOptions || {})) + .replace(`'[TAGS_OPTIONS HERE]'`, JSON.stringify(tagsOptions || {})) .replace('', headHtmlSnippet || '') .replace('', bodyHtmlSnippet || ''); } diff --git a/code/builders/builder-webpack5/src/preview/iframe-webpack.config.ts b/code/builders/builder-webpack5/src/preview/iframe-webpack.config.ts index ac573827715..5c18633dd92 100644 --- a/code/builders/builder-webpack5/src/preview/iframe-webpack.config.ts +++ b/code/builders/builder-webpack5/src/preview/iframe-webpack.config.ts @@ -84,6 +84,7 @@ export default async ( nonNormalizedStories, modulesCount = 1000, build, + tagsOptions, ] = await Promise.all([ presets.apply('core'), presets.apply('frameworkOptions'), @@ -97,6 +98,7 @@ export default async ( presets.apply('stories', []), options.cache?.get('modulesCount').catch(() => {}), options.presets.apply('build'), + presets.apply('tags', {}), ]); const stories = normalizeStories(nonNormalizedStories, { @@ -188,6 +190,7 @@ export default async ( importPathMatcher: specifier.importPathMatcher.source, })), DOCS_OPTIONS: docsOptions, + TAGS_OPTIONS: tagsOptions, ...(build?.test?.disableBlocks ? { __STORYBOOK_BLOCKS_EMPTY_MODULE__: {} } : {}), }, headHtmlSnippet, diff --git a/code/lib/core-server/package.json b/code/lib/core-server/package.json index cdea9a4b00d..5734af7ed7f 100644 --- a/code/lib/core-server/package.json +++ b/code/lib/core-server/package.json @@ -71,6 +71,7 @@ "@storybook/docs-mdx": "3.0.0", "@storybook/global": "^5.0.0", "@storybook/manager": "workspace:*", + "@storybook/manager-api": "workspace:*", "@storybook/node-logger": "workspace:*", "@storybook/preview-api": "workspace:*", "@storybook/telemetry": "workspace:*", @@ -121,6 +122,7 @@ "./src/index.ts", "./src/presets/babel-cache-preset.ts", "./src/presets/common-preset.ts", + "./src/presets/common-manager.ts", "./src/presets/common-override-preset.ts" ], "platform": "node" diff --git a/code/lib/core-server/src/presets/common-manager.ts b/code/lib/core-server/src/presets/common-manager.ts new file mode 100644 index 00000000000..2f9accbe586 --- /dev/null +++ b/code/lib/core-server/src/presets/common-manager.ts @@ -0,0 +1,24 @@ +import { addons } from '@storybook/manager-api'; +import { global } from '@storybook/global'; + +const STATIC_FILTER = 'static-filter'; + +const excludeTags = Object.entries(global.TAGS_OPTIONS).reduce((acc, entry) => { + const [tag, option] = entry; + if ((option as any).excludeFromSidebar) { + acc[tag] = true; + } + return acc; +}, {} as Record); + +addons.register(STATIC_FILTER, (api) => { + api.experimental_setFilter(STATIC_FILTER, (item) => { + const tags = item.tags || []; + // very strange behavior here. Auto-generated docs entries get + // the tags of the primary story by default, so if that story + // happens to be `docs-only`, then filtering it out of the sidebar + // ALSO filter out the sidebar entry, which is not what we want. + // Here we special case it, but there should be a better solution. + return tags.includes('docs') || tags.filter((tag) => excludeTags[tag]).length === 0; + }); +}); diff --git a/code/lib/core-server/src/presets/common-preset.ts b/code/lib/core-server/src/presets/common-preset.ts index cb68267391f..1914353abfb 100644 --- a/code/lib/core-server/src/presets/common-preset.ts +++ b/code/lib/core-server/src/presets/common-preset.ts @@ -361,3 +361,18 @@ export const resolvedReact = async (existing: any) => { return existing; } }; + +/** + * Set up `docs-only` and `test-only` tags out of the box + */ +export const tags = async (existing: any) => { + return { + ...existing, + 'test-only': { excludeFromSidebar: true, excludeFromAutodocs: true }, + 'docs-only': { excludeFromSidebar: true }, + }; +}; + +export const managerEntries = async (existing: any, options: Options) => { + return [require.resolve('./common-manager'), ...(existing || [])]; +}; diff --git a/code/lib/manager-api/src/typings.d.ts b/code/lib/manager-api/src/typings.d.ts index d14b4196753..63edef9413d 100644 --- a/code/lib/manager-api/src/typings.d.ts +++ b/code/lib/manager-api/src/typings.d.ts @@ -3,6 +3,7 @@ declare var __STORYBOOK_ADDONS_MANAGER: any; declare var CONFIG_TYPE: string; declare var FEATURES: import('@storybook/types').StorybookConfigRaw['features']; +declare var TAGS_OPTIONS: import('@storybook/types').StorybookConfigRaw['tags']; declare var REFS: any; declare var VERSIONCHECK: any; declare var LOGLEVEL: 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'silent' | undefined; diff --git a/code/lib/preview-api/src/typings.d.ts b/code/lib/preview-api/src/typings.d.ts index e6f7de7ada4..0b3cd54d4fe 100644 --- a/code/lib/preview-api/src/typings.d.ts +++ b/code/lib/preview-api/src/typings.d.ts @@ -11,6 +11,7 @@ declare var FEATURES: import('@storybook/types').StorybookConfigRaw['features']; declare var STORIES: any; declare var DOCS_OPTIONS: any; +declare var TAGS_OPTIONS: import('@storybook/types').StorybookConfigRaw['tags']; // To enable user code to detect if it is running in Storybook declare var IS_STORYBOOK: boolean; diff --git a/code/lib/types/src/modules/core-common.ts b/code/lib/types/src/modules/core-common.ts index a189c8fe5f5..9f7a7dd1716 100644 --- a/code/lib/types/src/modules/core-common.ts +++ b/code/lib/types/src/modules/core-common.ts @@ -335,6 +335,15 @@ export interface TestBuildConfig { test?: TestBuildFlags; } +type Tag = string; + +export interface TagOptions { + excludeFromSidebar: boolean; + excludeFromAutodocs: boolean; +} + +export type TagsOptions = Record>; + /** * The interface for Storybook configuration used internally in presets * The difference is that these values are the raw values, AKA, not wrapped with `PresetValue<>` @@ -427,6 +436,8 @@ export interface StorybookConfigRaw { previewMainTemplate?: string; managerHead?: string; + + tags?: TagsOptions; } /** @@ -541,6 +552,11 @@ export interface StorybookConfig { * which is the existing head content, and return a modified string. */ managerHead?: PresetValue; + + /** + * Cofnigure non-standard tag behaviors + */ + tags?: PresetValue; } export type PresetValue = T | ((config: T, options: Options) => T | Promise); diff --git a/code/yarn.lock b/code/yarn.lock index d97a6fb453c..bfd17238825 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -5428,6 +5428,7 @@ __metadata: "@storybook/docs-mdx": "npm:3.0.0" "@storybook/global": "npm:^5.0.0" "@storybook/manager": "workspace:*" + "@storybook/manager-api": "workspace:*" "@storybook/node-logger": "workspace:*" "@storybook/preview-api": "workspace:*" "@storybook/telemetry": "workspace:*" From 687ec800f84c3951a90dfe4d5b1675064418cd29 Mon Sep 17 00:00:00 2001 From: Michael Shilman Date: Sat, 6 Jan 2024 23:13:25 +0800 Subject: [PATCH 08/53] Fix build, type errors --- code/addons/docs/src/typings.d.ts | 2 ++ code/builders/builder-manager/src/index.ts | 2 ++ code/lib/core-server/src/typings.d.ts | 1 + 3 files changed, 5 insertions(+) diff --git a/code/addons/docs/src/typings.d.ts b/code/addons/docs/src/typings.d.ts index cfa3c4639f8..e57b02ba0bc 100644 --- a/code/addons/docs/src/typings.d.ts +++ b/code/addons/docs/src/typings.d.ts @@ -11,3 +11,5 @@ declare module 'sveltedoc-parser' { declare var FEATURES: import('@storybook/types').StorybookConfigRaw['features']; declare var LOGLEVEL: 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'silent' | undefined; + +declare var TAGS_OPTIONS: any; diff --git a/code/builders/builder-manager/src/index.ts b/code/builders/builder-manager/src/index.ts index 006c998014d..d55a8bb2d89 100644 --- a/code/builders/builder-manager/src/index.ts +++ b/code/builders/builder-manager/src/index.ts @@ -224,6 +224,7 @@ const builder: BuilderFunction = async function* builderGeneratorFn({ startTime, title, logLevel, docsOptions, + tagsOptions, } = await getData(options); yield; @@ -264,6 +265,7 @@ const builder: BuilderFunction = async function* builderGeneratorFn({ startTime, refs, logLevel, docsOptions, + tagsOptions, options ); diff --git a/code/lib/core-server/src/typings.d.ts b/code/lib/core-server/src/typings.d.ts index 597bae6cdc1..5481011d0e0 100644 --- a/code/lib/core-server/src/typings.d.ts +++ b/code/lib/core-server/src/typings.d.ts @@ -7,3 +7,4 @@ declare module '@discoveryjs/json-ext'; declare module 'watchpack'; declare var FEATURES: import('@storybook/types').StorybookConfigRaw['features']; +declare var TAGS_OPTIONS: any; From 34a184b07e1df40f44ccb849db0154920a371710 Mon Sep 17 00:00:00 2001 From: Michael Shilman Date: Sun, 7 Jan 2024 00:54:55 +0800 Subject: [PATCH 09/53] Add `dev-only` tag --- code/lib/core-server/src/presets/common-preset.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/code/lib/core-server/src/presets/common-preset.ts b/code/lib/core-server/src/presets/common-preset.ts index 1914353abfb..b571cf00a6b 100644 --- a/code/lib/core-server/src/presets/common-preset.ts +++ b/code/lib/core-server/src/presets/common-preset.ts @@ -363,13 +363,14 @@ export const resolvedReact = async (existing: any) => { }; /** - * Set up `docs-only` and `test-only` tags out of the box + * Set up `dev-only`, `docs-only`, `test-only` tags out of the box */ export const tags = async (existing: any) => { return { ...existing, - 'test-only': { excludeFromSidebar: true, excludeFromAutodocs: true }, + 'dev-only': { excludeFromAutodocs: true }, 'docs-only': { excludeFromSidebar: true }, + 'test-only': { excludeFromSidebar: true, excludeFromAutodocs: true }, }; }; From 168b086348c252ead9c273fc7cc9eddfabfa7132 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Tue, 9 Jan 2024 14:27:21 +0100 Subject: [PATCH 10/53] Remove deprecated code from Canvas and Source block components --- MIGRATION.md | 13 ++ code/ui/blocks/src/blocks/Canvas.tsx | 170 +------------- code/ui/blocks/src/blocks/Source.tsx | 94 +++----- .../internal/InternalCanvas.stories.tsx | 211 ------------------ 4 files changed, 59 insertions(+), 429 deletions(-) delete mode 100644 code/ui/blocks/src/blocks/internal/InternalCanvas.stories.tsx diff --git a/MIGRATION.md b/MIGRATION.md index 2047d52480c..885eaaf60c7 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -41,6 +41,7 @@ - [Description Doc block properties](#description-doc-block-properties) - [Manager API expandAll and collapseAll methods](#manager-api-expandall-and-collapseall-methods) - [Source Doc block properties](#source-doc-block-properties) + - [Canvas Doc block properties](#canvas-doc-block-properties) - [From version 7.5.0 to 7.6.0](#from-version-750-to-760) - [CommonJS with Vite is deprecated](#commonjs-with-vite-is-deprecated) - [Using implicit actions during rendering is deprecated](#using-implicit-actions-during-rendering-is-deprecated) @@ -695,6 +696,18 @@ api.expandAll() // becomes api.emit(STORIES_EXPAND_ALL) `id` and `ids` are now removed in favor of the `of` property. [More info](#doc-blocks). +#### Canvas Doc block properties + +The following properties were removed from the Canvas Doc block: + +- children +- isColumn +- columns +- withSource +- mdxSource + +[More info](#doc-blocks). + ## From version 7.5.0 to 7.6.0 #### CommonJS with Vite is deprecated diff --git a/code/ui/blocks/src/blocks/Canvas.tsx b/code/ui/blocks/src/blocks/Canvas.tsx index 74b997de126..556817296b4 100644 --- a/code/ui/blocks/src/blocks/Canvas.tsx +++ b/code/ui/blocks/src/blocks/Canvas.tsx @@ -1,47 +1,17 @@ /* eslint-disable react/destructuring-assignment */ -import React, { Children, useContext } from 'react'; -import type { FC, ReactElement, ReactNode } from 'react'; -import type { ModuleExport, ModuleExports, PreparedStory, Renderer } from '@storybook/types'; -import { deprecate } from '@storybook/client-logger'; -import dedent from 'ts-dedent'; +import React, { useContext } from 'react'; +import type { FC } from 'react'; +import type { ModuleExport, ModuleExports } from '@storybook/types'; import type { Layout, PreviewProps as PurePreviewProps } from '../components'; -import { Preview as PurePreview, PreviewSkeleton } from '../components'; -import type { DocsContextProps } from './DocsContext'; +import { Preview as PurePreview } from '../components'; import { DocsContext } from './DocsContext'; -import type { SourceContextProps } from './SourceContainer'; import { SourceContext } from './SourceContainer'; import type { SourceProps } from './Source'; -import { useSourceProps, SourceState as DeprecatedSourceState, SourceState } from './Source'; -import { useStories } from './useStory'; +import { useSourceProps } from './Source'; import type { StoryProps } from './Story'; -import { getStoryId, Story } from './Story'; +import { Story } from './Story'; import { useOf } from './useOf'; -export { DeprecatedSourceState as SourceState }; - -type DeprecatedCanvasProps = { - /** - * @deprecated multiple stories are not supported - */ - isColumn?: boolean; - /** - * @deprecated multiple stories are not supported - */ - columns?: number; - /** - * @deprecated use `sourceState` instead - */ - withSource?: DeprecatedSourceState; - /** - * @deprecated use `source.code` instead - */ - mdxSource?: string; - /** - * @deprecated reference stories with the `of` prop instead - */ - children?: ReactNode; -}; - type CanvasProps = Pick & { /** * Pass the export defining a story to render that story @@ -92,136 +62,16 @@ type CanvasProps = Pick; }; -const useDeprecatedPreviewProps = ( - { - withSource, - mdxSource, - children, - layout: layoutProp, - ...props - }: DeprecatedCanvasProps & { of?: ModuleExport; layout?: Layout }, - docsContext: DocsContextProps, - sourceContext: SourceContextProps -) => { - /* - get all story IDs by traversing through the children, - filter out any non-story children (e.g. text nodes) - and then get the id from each story depending on available props - */ - const storyIds = (Children.toArray(children) as ReactElement[]) - .filter((c) => c.props && (c.props.id || c.props.name || c.props.of)) - .map((c) => getStoryId(c.props, docsContext)); - - const stories = useStories(storyIds, docsContext); - const isLoading = stories.some((s) => !s); - const sourceProps = useSourceProps( - { - ...(mdxSource ? { code: decodeURI(mdxSource) } : { ids: storyIds }), - ...(props.of && { of: props.of }), - }, - docsContext, - sourceContext - ); - if (withSource === SourceState.NONE) { - return { isLoading, previewProps: props }; - } - - // if the user has specified a layout prop, use that... - let layout = layoutProp; - // ...otherwise, try to infer it from the children 'parameters' prop - Children.forEach(children, (child) => { - if (layout) { - return; - } - layout = (child as ReactElement)?.props?.parameters?.layout; - }); - // ...otherwise, try to infer it from the story parameters - stories.forEach((story) => { - if (layout || !story) { - return; - } - layout = story?.parameters.layout ?? story.parameters.docs?.canvas?.layout; - }); - - return { - isLoading, - previewProps: { - ...props, // pass through columns etc. - layout: layout ?? 'padded', - withSource: sourceProps, - isExpanded: (withSource || sourceProps.state) === SourceState.OPEN, - }, - }; -}; - -export const Canvas: FC = (props) => { +export const Canvas: FC = (props) => { const docsContext = useContext(DocsContext); const sourceContext = useContext(SourceContext); - const { children, of, source } = props; + const { of, source } = props; if ('of' in props && of === undefined) { throw new Error('Unexpected `of={undefined}`, did you mistype a CSF file reference?'); } - const { isLoading, previewProps } = useDeprecatedPreviewProps(props, docsContext, sourceContext); - - let story: PreparedStory; - let sourceProps; - /** - * useOf and useSourceProps will throw if they can't find the story, in the scenario where - * the doc is unattached (no primary story) and 'of' is undefined. - * That scenario is valid in the deprecated API, where children is used as story refs rather than 'of'. - * So if children is passed we allow the error to be swallowed and we'll use them instead. - * We use two separate try blocks and throw the error afterwards to not break the rules of hooks. - */ - let hookError; - try { - ({ story } = useOf(of || 'story', ['story'])); - } catch (error) { - if (!children) { - hookError = error; - } - } - try { - sourceProps = useSourceProps({ ...source, ...(of && { of }) }, docsContext, sourceContext); - } catch (error) { - if (!children) { - hookError = error; - } - } - if (hookError) { - // eslint-disable-next-line @typescript-eslint/no-throw-literal - throw hookError; - } - - if (props.withSource) { - deprecate(dedent`Setting source state with \`withSource\` is deprecated, please use \`sourceState\` with 'hidden', 'shown' or 'none' instead. - - Please refer to the migration guide: https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#canvas-block - `); - } - if (props.mdxSource) { - deprecate(dedent`Setting source code with \`mdxSource\` is deprecated, please use source={{code: '...'}} instead. - - Please refer to the migration guide: https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#canvas-block - `); - } - if (props.isColumn !== undefined || props.columns !== undefined) { - deprecate(dedent`\`isColumn\` and \`columns\` props are deprecated as the Canvas block now only supports showing a single story. - - Please refer to the migration guide: https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#canvas-block - `); - } - if (children) { - deprecate(dedent`Passing children to Canvas is deprecated, please use the \`of\` prop instead to reference a story. - - Please refer to the migration guide: https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#canvas-block - `); - return isLoading ? ( - - ) : ( - {children} - ); - } + const { story } = useOf(of || 'story', ['story']); + const sourceProps = useSourceProps({ ...source, ...(of && { of }) }, docsContext, sourceContext); const layout = props.layout ?? story.parameters.layout ?? story.parameters.docs?.canvas?.layout ?? 'padded'; diff --git a/code/ui/blocks/src/blocks/Source.tsx b/code/ui/blocks/src/blocks/Source.tsx index 38626876fa8..59906aa77c4 100644 --- a/code/ui/blocks/src/blocks/Source.tsx +++ b/code/ui/blocks/src/blocks/Source.tsx @@ -16,12 +16,6 @@ import { DocsContext } from './DocsContext'; import type { SourceContextProps, SourceItem } from './SourceContainer'; import { UNKNOWN_ARGS_HASH, argsHash, SourceContext } from './SourceContainer'; -export enum SourceState { - OPEN = 'open', - CLOSED = 'closed', - NONE = 'none', -} - type SourceParameters = SourceCodeProps & { /** * Where to read the source code from, see `SourceType` @@ -56,13 +50,6 @@ export type SourceProps = SourceParameters & { __forceInitialArgs?: boolean; }; -const getSourceState = (stories: PreparedStory[]) => { - const states = stories.map((story) => story.parameters.docs?.source?.state).filter(Boolean); - if (states.length === 0) return SourceState.CLOSED; - // FIXME: handling multiple stories is a pain - return states[0]; -}; - const getStorySource = ( storyId: StoryId, args: Args, @@ -116,15 +103,14 @@ const getSnippet = ({ }; // state is used by the Canvas block, which also calls useSourceProps -type SourceStateProps = { state: SourceState }; type PureSourceProps = ComponentProps; export const useSourceProps = ( props: SourceProps, docsContext: DocsContextProps, sourceContext: SourceContextProps -): PureSourceProps & SourceStateProps => { - let stories: PreparedStory[] = []; +): PureSourceProps => { + let story: PreparedStory; const { of } = props; if ('of' in props && of === undefined) { throw new Error('Unexpected `of={undefined}`, did you mistype a CSF file reference?'); @@ -132,62 +118,54 @@ export const useSourceProps = ( if (of) { const resolved = docsContext.resolveOf(of, ['story']); - stories = [resolved.story]; + story = resolved.story; } else { try { // Always fall back to the primary story for source parameters, even if code is set. - stories = [docsContext.storyById()]; + story = docsContext.storyById(); } catch (err) { // You are allowed to use and unattached. } } - const sourceParameters = (stories[0]?.parameters?.docs?.source || {}) as SourceParameters; - let { code } = props; // We will fall back to `sourceParameters.code`, but per story below + const sourceParameters = (story?.parameters?.docs?.source || {}) as SourceParameters; + const { code } = props; // We will fall back to `sourceParameters.code`, but per story below let format = props.format ?? sourceParameters.format; const language = props.language ?? sourceParameters.language ?? 'jsx'; const dark = props.dark ?? sourceParameters.dark ?? false; - if (!code) { - code = stories - .map((story, index) => { - // In theory you can use a storyId from a different CSF file that hasn't loaded yet. - if (!story) return ''; - - const storyContext = docsContext.getStoryContext(story); - - // eslint-disable-next-line no-underscore-dangle - const argsForSource = props.__forceInitialArgs - ? storyContext.initialArgs - : storyContext.unmappedArgs; - - const source = getStorySource(story.id, argsForSource, sourceContext); - if (index === 0) { - // Take the format from the first story - format = source.format ?? story.parameters.docs?.source?.format ?? false; - } - return getSnippet({ - snippet: source.code, - storyContext: { ...storyContext, args: argsForSource }, - typeFromProps: props.type, - transformFromProps: props.transform, - }); - }) - .join('\n\n'); + if (!code && !story) { + return { error: SourceError.SOURCE_UNAVAILABLE }; } + if (code) { + return { + code, + format, + language, + dark, + }; + } + const storyContext = docsContext.getStoryContext(story); - const state = getSourceState(stories as PreparedStory[]); + // eslint-disable-next-line no-underscore-dangle + const argsForSource = props.__forceInitialArgs + ? storyContext.initialArgs + : storyContext.unmappedArgs; - return code - ? { - code, - format, - language, - dark, - // state is used by the Canvas block when it calls this function - state, - } - : { error: SourceError.SOURCE_UNAVAILABLE, state }; + const source = getStorySource(story.id, argsForSource, sourceContext); + format = source.format ?? story.parameters.docs?.source?.format ?? false; + + return { + code: getSnippet({ + snippet: source.code, + storyContext: { ...storyContext, args: argsForSource }, + typeFromProps: props.type, + transformFromProps: props.transform, + }), + format, + language, + dark, + }; }; /** @@ -198,6 +176,6 @@ export const useSourceProps = ( export const Source: FC = (props) => { const sourceContext = useContext(SourceContext); const docsContext = useContext(DocsContext); - const { state, ...sourceProps } = useSourceProps(props, docsContext, sourceContext); + const sourceProps = useSourceProps(props, docsContext, sourceContext); return ; }; diff --git a/code/ui/blocks/src/blocks/internal/InternalCanvas.stories.tsx b/code/ui/blocks/src/blocks/internal/InternalCanvas.stories.tsx deleted file mode 100644 index 77602505077..00000000000 --- a/code/ui/blocks/src/blocks/internal/InternalCanvas.stories.tsx +++ /dev/null @@ -1,211 +0,0 @@ -/// ; -/// ; -import React from 'react'; -import type { Meta, StoryObj } from '@storybook/react'; -import { userEvent, within } from '@storybook/testing-library'; -import { expect } from '@storybook/jest'; -import { Canvas, SourceState } from '../Canvas'; -import { Story as StoryComponent } from '../Story'; -import * as ButtonStories from '../../examples/Button.stories'; -import * as CanvasParameterStories from '../../examples/CanvasParameters.stories'; - -const meta: Meta = { - component: Canvas, - parameters: { - theme: 'light', - relativeCsfPaths: ['../examples/Button.stories', '../examples/CanvasParameters.stories'], - docsStyles: true, - }, - render: (args) => { - return ( - - - - ); - }, -}; -export default meta; - -type Story = StoryObj; - -const expectAmountOfStoriesInSource = - (amount: number): Story['play'] => - async ({ canvasElement }) => { - const canvas = within(canvasElement); - - // Arrange - find the "Show code" button - const showCodeButton = await canvas.findByText('Show code'); - await expect(showCodeButton).toBeInTheDocument(); - - // Act - click button to show code - await userEvent.click(showCodeButton); - - // Assert - check that the correct amount of stories' source is shown - const buttonNodes = await canvas.findAllByText(`label`); - await expect(buttonNodes).toHaveLength(amount); - }; - -export const BasicStoryChild: Story = {}; - -export const BasicStoryChildUnattached: Story = { - parameters: { attached: false }, -}; - -export const NoStoryChildrenUnattached: Story = { - parameters: { attached: false }, - render: (args) => { - return ( - -

This is a plain paragraph, no stories

-
- ); - }, -}; -export const NoStoryChildrenUnattachedWithMDXSource: Story = { - ...NoStoryChildrenUnattached, - args: { - mdxSource: `const customStaticSource = true;`, - }, -}; - -export const WithSourceOpen: Story = { - args: { - withSource: SourceState.OPEN, - }, -}; -export const WithSourceClosed: Story = { - args: { - withSource: SourceState.CLOSED, - }, -}; - -export const WithMdxSource: Story = { - name: 'With MDX Source', - args: { - withSource: SourceState.OPEN, - mdxSource: ` - ), + export const Primary = { + render: () => ( + + ), - name: 'Primary', - }; - - `); + name: 'Primary', + }; + `); }); -it('story child is CSF3', () => { +it('story child is CSF3', async () => { const input = dedent` import { Story } from '@storybook/addon-docs'; import { Button } from './button'; @@ -547,7 +529,7 @@ it('story child is CSF3', () => { } args={{label: 'Hello' }} /> `; - jscodeshift({ source: input, path: 'Foobar.stories.mdx' }); + await jscodeshift({ source: input, path: 'Foobar.stories.mdx' }); const [, csf] = fs.writeFileSync.mock.calls[0]; @@ -563,11 +545,10 @@ it('story child is CSF3', () => { label: 'Hello', }, }; - `); }); -it('story child is arrow function', () => { +it('story child is arrow function', async () => { const input = dedent` import { Canvas, Meta, Story } from '@storybook/addon-docs'; import { Button } from './button'; @@ -577,23 +558,22 @@ it('story child is arrow function', () => { `; - jscodeshift({ source: input, path: 'Foobar.stories.mdx' }); + await jscodeshift({ source: input, path: 'Foobar.stories.mdx' }); const [, csf] = fs.writeFileSync.mock.calls[0]; expect(csf).toMatchInlineSnapshot(` - import { Button } from './button'; - export default {}; + import { Button } from './button'; + export default {}; - export const Primary = { - render: (args) =>