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;
};
/**