diff --git a/MIGRATION.md b/MIGRATION.md index 58b42f10c0aa..28b4bc5d6ddc 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -1,6 +1,7 @@

Migration

- [From version 8.4.x to 8.5.x](#from-version-84x-to-85x) + - [Added source code panel to docs](#added-source-code-panel-to-docs) - [Addon-a11y: Component test integration](#addon-a11y-component-test-integration) - [Addon-a11y: Deprecated `parameters.a11y.manual`](#addon-a11y-deprecated-parametersa11ymanual) - [Indexing behavior of @storybook/experimental-addon-test is changed](#indexing-behavior-of-storybookexperimental-addon-test-is-changed) @@ -425,6 +426,22 @@ ## From version 8.4.x to 8.5.x +### Added source code panel to docs + +Starting in 8.5, Storybook Docs (`@storybook/addon-docs`) automatically adds a new addon panel to stories that displays a source snippet beneath each story. This works similarly to the existing [source snippet doc block](https://storybook.js.org/docs/writing-docs/doc-blocks#source), but in the story view. It is intended to replace the [Storysource addon](https://storybook.js.org/addons/@storybook/addon-storysource). + +If you wish to disable this panel globally, add the following line to your `.storybook/preview.js` project configuration. You can also selectively disable/enable at the story level. + +```js +export default { + parameters: { + docs: { + codePanel: false, + }, + }, +}; +``` + ### Addon-a11y: Component test integration In Storybook 8.4, we introduced the [Test addon](https://storybook.js.org/docs/writing-tests/test-addon) (`@storybook/experimental-addon-test`). Powered by Vitest under the hood, this addon lets you watch, run, and debug your component tests directly in Storybook. diff --git a/code/addons/docs/package.json b/code/addons/docs/package.json index e515eb363746..85ad01ecc334 100644 --- a/code/addons/docs/package.json +++ b/code/addons/docs/package.json @@ -71,7 +71,12 @@ "./angular": "./angular/index.js", "./angular/index.js": "./angular/index.js", "./web-components/index.js": "./web-components/index.js", - "./package.json": "./package.json" + "./package.json": "./package.json", + "./manager": { + "types": "./dist/manager.d.ts", + "import": "./dist/manager.mjs", + "require": "./dist/manager.js" + } }, "main": "dist/index.js", "module": "dist/index.mjs", @@ -129,7 +134,11 @@ "./src/preview.ts", "./src/blocks.ts", "./src/shims/mdx-react-shim.ts", - "./src/mdx-loader.ts" + "./src/mdx-loader.ts", + "./src/manager.tsx" + ], + "managerEntries": [ + "./src/manager.tsx" ] }, "gitHead": "e6a7fd8a655c69780bc20b9749c2699e44beae16", diff --git a/code/addons/docs/src/manager.tsx b/code/addons/docs/src/manager.tsx new file mode 100644 index 000000000000..0b84cd6e36f8 --- /dev/null +++ b/code/addons/docs/src/manager.tsx @@ -0,0 +1,57 @@ +import React from 'react'; + +import { AddonPanel, type SyntaxHighlighterFormatTypes } from 'storybook/internal/components'; +import { ADDON_ID, PANEL_ID, PARAM_KEY, SNIPPET_RENDERED } from 'storybook/internal/docs-tools'; +import { addons, types, useAddonState, useChannel } from 'storybook/internal/manager-api'; + +import { Source } from '@storybook/blocks'; + +addons.register(ADDON_ID, (api) => { + addons.add(PANEL_ID, { + title: 'Code', + type: types.PANEL, + paramKey: PARAM_KEY, + /** + * This code panel can be disabled by the user by adding this parameter: + * + * @example + * + * ```ts + * parameters: { + * docs: { + * codePanel: false, + * }, + * }, + * ``` + */ + disabled: (parameters) => { + return ( + !!parameters && + typeof parameters[PARAM_KEY] === 'object' && + parameters[PARAM_KEY].codePanel === false + ); + }, + match: ({ viewMode }) => viewMode === 'story', + render: ({ active }) => { + const [codeSnippet, setSourceCode] = useAddonState<{ + source: string; + format: SyntaxHighlighterFormatTypes; + }>(ADDON_ID, { + source: '', + format: 'html', + }); + + useChannel({ + [SNIPPET_RENDERED]: ({ source, format }) => { + setSourceCode({ source, format }); + }, + }); + + return ( + + + + ); + }, + }); +}); diff --git a/code/addons/docs/template/stories/sourcePanel/index.stories.tsx b/code/addons/docs/template/stories/sourcePanel/index.stories.tsx new file mode 100644 index 000000000000..9958096cb815 --- /dev/null +++ b/code/addons/docs/template/stories/sourcePanel/index.stories.tsx @@ -0,0 +1,23 @@ +export default { + component: globalThis.Components.Button, + tags: ['autodocs'], + parameters: { + chromatic: { disable: true }, + docs: { + codePanel: false, + }, + }, +}; + +export const One = { args: { label: 'One' } }; + +export const Two = { args: { label: 'Two' } }; + +export const WithSource = { + args: { label: 'Three' }, + parameters: { + docs: { + codePanel: true, + }, + }, +}; diff --git a/code/addons/essentials/package.json b/code/addons/essentials/package.json index a8b609df935b..8946fc941275 100644 --- a/code/addons/essentials/package.json +++ b/code/addons/essentials/package.json @@ -40,6 +40,7 @@ }, "./backgrounds/manager": "./dist/backgrounds/manager.js", "./controls/manager": "./dist/controls/manager.js", + "./docs/manager": "./dist/docs/manager.js", "./docs/preview": { "types": "./dist/docs/preview.d.ts", "import": "./dist/docs/preview.mjs", @@ -114,10 +115,14 @@ "./src/docs/preset.ts", "./src/docs/mdx-react-shim.ts" ], + "entries": [ + "./src/docs/manager.ts" + ], "managerEntries": [ "./src/actions/manager.ts", "./src/backgrounds/manager.ts", "./src/controls/manager.ts", + "./src/docs/manager.ts", "./src/measure/manager.ts", "./src/outline/manager.ts", "./src/toolbars/manager.ts", diff --git a/code/addons/essentials/src/docs/manager.ts b/code/addons/essentials/src/docs/manager.ts new file mode 100644 index 000000000000..6101f7d79261 --- /dev/null +++ b/code/addons/essentials/src/docs/manager.ts @@ -0,0 +1,2 @@ +// @ts-expect-error (no types needed for this) +export * from '@storybook/addon-docs/manager'; diff --git a/code/addons/essentials/src/index.ts b/code/addons/essentials/src/index.ts index 5809420bc1b8..a72554227ba2 100644 --- a/code/addons/essentials/src/index.ts +++ b/code/addons/essentials/src/index.ts @@ -88,9 +88,9 @@ export function addons(options: PresetOptions) { // NOTE: The order of these addons is important. return [ - 'docs', 'controls', 'actions', + 'docs', 'backgrounds', 'viewport', 'toolbars', diff --git a/code/core/src/manager/components/preview/Toolbar.tsx b/code/core/src/manager/components/preview/Toolbar.tsx index 5dd460e80e69..05385487a804 100644 --- a/code/core/src/manager/components/preview/Toolbar.tsx +++ b/code/core/src/manager/components/preview/Toolbar.tsx @@ -92,7 +92,7 @@ export const createTabsTool = (tabs: Addon_BaseType[]): Addon_BaseType => ({ const isActive = rp.path.includes(`tab=${tab.id}`); return ( { rp.applyQueryParams({ tab: tabIdToApply }); @@ -146,7 +146,7 @@ export const ToolbarComp = React.memo(function ToolbarComp({ {tabs.map((tab, index) => { return ( { api.applyQueryParams({ tab: tab.id === 'canvas' ? undefined : tab.id }); diff --git a/code/core/src/manager/container/Panel.tsx b/code/core/src/manager/container/Panel.tsx index c81e489d8f68..f8cc2877cef0 100644 --- a/code/core/src/manager/container/Panel.tsx +++ b/code/core/src/manager/container/Panel.tsx @@ -32,6 +32,12 @@ const getPanels = (api: API) => { if (paramKey && parameters && parameters[paramKey] && parameters[paramKey].disable) { return; } + if ( + panel.disabled === true || + (typeof panel.disabled === 'function' && panel.disabled(parameters)) + ) { + return; + } filteredPanels[id] = panel; }); diff --git a/code/core/src/types/modules/addons.ts b/code/core/src/types/modules/addons.ts index 47f5aec6412b..b2c7b4ac2f3c 100644 --- a/code/core/src/types/modules/addons.ts +++ b/code/core/src/types/modules/addons.ts @@ -5,7 +5,7 @@ import type { TestProviderConfig, TestingModuleProgressReportProgress } from '.. import type { RenderData as RouterData } from '../../router/types'; import type { ThemeVars } from '../../theming/types'; import type { API_SidebarOptions } from './api'; -import type { API_HashEntry, API_StatusState, API_StatusUpdate } from './api-stories'; +import type { API_HashEntry, API_StoryEntry } from './api-stories'; import type { Args, ArgsStoryFn as ArgsStoryFnForFramework, @@ -392,7 +392,7 @@ export interface Addon_BaseType { /** @unstable */ paramKey?: string; /** @unstable */ - disabled?: boolean; + disabled?: boolean | ((parameters: API_StoryEntry['parameters']) => boolean); /** @unstable */ hidden?: boolean; } diff --git a/code/renderers/vue3/src/docs/sourceDecorator.ts b/code/renderers/vue3/src/docs/sourceDecorator.ts index 7eb8734305af..cc2f922f63f1 100644 --- a/code/renderers/vue3/src/docs/sourceDecorator.ts +++ b/code/renderers/vue3/src/docs/sourceDecorator.ts @@ -107,7 +107,6 @@ ${template}`; * Checks if the source code generation should be skipped for the given Story context. Will be true * if one of the following is true: * - * - View mode is not "docs" * - Story is no arg story * - Story has set custom source code via parameters.docs.source.code * - Story has set source type to "code" via parameters.docs.source.type @@ -120,13 +119,10 @@ export const shouldSkipSourceCodeGeneration = (context: StoryContext): boolean = } const isArgsStory = context?.parameters.__isArgsStory; - const isDocsViewMode = context?.viewMode === 'docs'; // never render if the user is forcing the block to render code, or // if the user provides code, or if it's not an args story. - return ( - !isDocsViewMode || !isArgsStory || sourceParams?.code || sourceParams?.type === SourceType.CODE - ); + return !isArgsStory || sourceParams?.code || sourceParams?.type === SourceType.CODE; }; /**