From 1853097f1d65722db1debac979d597e02e1bce16 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Mon, 15 Sep 2025 17:55:21 +0200 Subject: [PATCH 01/44] Integrate launch-editor-middleware for enhanced editor support. Added open-in-editor functionality to Subnav and Preview components, allowing users to open files directly in their code editor. Updated package dependencies and added related types for improved type safety. --- code/builders/builder-webpack5/package.json | 1 + code/builders/builder-webpack5/src/index.ts | 5 ++ code/core/src/manager-api/index.mock.ts | 2 + code/core/src/manager-api/index.ts | 2 + .../src/manager-api/lib/open-in-editor.ts | 31 ++++++++++++ .../preview/tools/open-in-editor.tsx | 50 +++++++++++++++++++ code/core/src/manager/container/Preview.tsx | 3 +- code/core/src/manager/globals/exports.ts | 2 + code/yarn.lock | 27 ++++++++++ 9 files changed, 122 insertions(+), 1 deletion(-) create mode 100644 code/core/src/manager-api/lib/open-in-editor.ts create mode 100644 code/core/src/manager/components/preview/tools/open-in-editor.tsx diff --git a/code/builders/builder-webpack5/package.json b/code/builders/builder-webpack5/package.json index bdca7e9bf6d7..202a62a305fc 100644 --- a/code/builders/builder-webpack5/package.json +++ b/code/builders/builder-webpack5/package.json @@ -60,6 +60,7 @@ "es-module-lexer": "^1.5.0", "fork-ts-checker-webpack-plugin": "^9.1.0", "html-webpack-plugin": "^5.5.0", + "launch-editor-middleware": "^2.11.1", "magic-string": "^0.30.5", "style-loader": "^4.0.0", "terser-webpack-plugin": "^5.3.14", diff --git a/code/builders/builder-webpack5/src/index.ts b/code/builders/builder-webpack5/src/index.ts index 1897e0d8c972..e3cdc0782146 100644 --- a/code/builders/builder-webpack5/src/index.ts +++ b/code/builders/builder-webpack5/src/index.ts @@ -195,6 +195,11 @@ const starter: StarterFunction = async function* starterGeneratorFn({ immutable: true, }) ); + + // @ts-expect-error no types available, see https://github.com/yyx990803/launch-editor/issues/84 + const { default: launchMiddleware } = await import('launch-editor-middleware'); + router.use('/__open-in-editor', launchMiddleware()); + router.use(compilation); router.use(webpackHotMiddleware(compiler, { log: false })); diff --git a/code/core/src/manager-api/index.mock.ts b/code/core/src/manager-api/index.mock.ts index d79920fec81a..f6950b1a28e5 100644 --- a/code/core/src/manager-api/index.mock.ts +++ b/code/core/src/manager-api/index.mock.ts @@ -1,5 +1,7 @@ export * from './root'; +export { openInEditor } from './lib/open-in-editor'; + export { UniversalStore as experimental_UniversalStore } from '../shared/universal-store'; export { useUniversalStore as experimental_useUniversalStore } from '../shared/universal-store/use-universal-store-manager'; export { MockUniversalStore as experimental_MockUniversalStore } from '../shared/universal-store/mock'; diff --git a/code/core/src/manager-api/index.ts b/code/core/src/manager-api/index.ts index dfb70cd637a4..e0bbc154d2df 100644 --- a/code/core/src/manager-api/index.ts +++ b/code/core/src/manager-api/index.ts @@ -1,5 +1,7 @@ export * from './root'; +export { openInEditor } from './lib/open-in-editor'; + export { UniversalStore as experimental_UniversalStore } from '../shared/universal-store'; export { useUniversalStore as experimental_useUniversalStore } from '../shared/universal-store/use-universal-store-manager'; export { MockUniversalStore as experimental_MockUniversalStore } from '../shared/universal-store/mock'; diff --git a/code/core/src/manager-api/lib/open-in-editor.ts b/code/core/src/manager-api/lib/open-in-editor.ts new file mode 100644 index 000000000000..55e98bb15fe5 --- /dev/null +++ b/code/core/src/manager-api/lib/open-in-editor.ts @@ -0,0 +1,31 @@ +/** + * Open the file in the editor + * + * Available for builders which support https://github.com/yyx990803/launch-editor + * + * Known builders: Webpack5, Vite + * + * @param filePath - The path to the file to open in the editor + * @returns Void + */ +export async function openInEditor( + filePath: string, + line?: number, + column?: number +): Promise { + let fileLocation = filePath; + if (typeof line === 'number') { + fileLocation += `:${line}`; + if (typeof column === 'number') { + fileLocation += `:${column}`; + } + } + + try { + await fetch(`/__open-in-editor?file=${encodeURIComponent(fileLocation)}`, { + method: 'POST', + }); + } catch { + // no-op + } +} diff --git a/code/core/src/manager/components/preview/tools/open-in-editor.tsx b/code/core/src/manager/components/preview/tools/open-in-editor.tsx new file mode 100644 index 000000000000..b8e9f51d14f4 --- /dev/null +++ b/code/core/src/manager/components/preview/tools/open-in-editor.tsx @@ -0,0 +1,50 @@ +import React from 'react'; + +import { IconButton } from 'storybook/internal/components'; +import type { Addon_BaseType } from 'storybook/internal/types'; + +import { global } from '@storybook/global'; +import { MarkupIcon } from '@storybook/icons'; + +import { Consumer, openInEditor, types } from 'storybook/manager-api'; +import type { Combo } from 'storybook/manager-api'; + +const mapper = ({ state, api }: Combo) => { + const { storyId, refId } = state; + const entry = api.getData(storyId, refId); + + const isCompositionStory = !!refId; // Only allow opening local stories in editor + + return { + storyId, + isCompositionStory, + importPath: entry?.importPath as string | undefined, + }; +}; + +export const openInEditorTool: Addon_BaseType = { + title: 'open-in-editor', + id: 'open-in-editor', + type: types.TOOL, + match: ({ viewMode, tabId }) => + global.CONFIG_TYPE === 'DEVELOPMENT' && (viewMode === 'story' || viewMode === 'docs') && !tabId, + render: () => ( + + {({ importPath, isCompositionStory }) => { + if (isCompositionStory || !importPath) { + return null; + } + return ( + openInEditor(importPath)} + title="Open in editor" + aria-label="Open in editor" + > + + + ); + }} + + ), +}; diff --git a/code/core/src/manager/container/Preview.tsx b/code/core/src/manager/container/Preview.tsx index 95973adfc3e7..4c8191dadbbc 100644 --- a/code/core/src/manager/container/Preview.tsx +++ b/code/core/src/manager/container/Preview.tsx @@ -17,13 +17,14 @@ import { addonsTool } from '../components/preview/tools/addons'; import { copyTool } from '../components/preview/tools/copy'; import { ejectTool } from '../components/preview/tools/eject'; import { menuTool } from '../components/preview/tools/menu'; +import { openInEditorTool } from '../components/preview/tools/open-in-editor'; import { remountTool } from '../components/preview/tools/remount'; import { zoomTool } from '../components/preview/tools/zoom'; import type { PreviewProps } from '../components/preview/utils/types'; const defaultTabs = [createCanvasTab()]; const defaultTools = [menuTool, remountTool, zoomTool]; -const defaultToolsExtra = [addonsTool, fullScreenTool, ejectTool, copyTool]; +const defaultToolsExtra = [addonsTool, fullScreenTool, ejectTool, copyTool, openInEditorTool]; const emptyTabsList: Addon_BaseType[] = []; diff --git a/code/core/src/manager/globals/exports.ts b/code/core/src/manager/globals/exports.ts index 2f5d81f6cfc9..7387be7ae8bc 100644 --- a/code/core/src/manager/globals/exports.ts +++ b/code/core/src/manager/globals/exports.ts @@ -307,6 +307,7 @@ export default { 'storybook/manager-api': [ 'ActiveTabs', 'Consumer', + 'openInEditor', 'ManagerContext', 'Provider', 'RequestResponseError', @@ -683,6 +684,7 @@ export default { 'storybook/internal/manager-api': [ 'ActiveTabs', 'Consumer', + 'openInEditor', 'ManagerContext', 'Provider', 'RequestResponseError', diff --git a/code/yarn.lock b/code/yarn.lock index 75a8351f03b5..e19a41aea84b 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -6234,6 +6234,7 @@ __metadata: es-module-lexer: "npm:^1.5.0" fork-ts-checker-webpack-plugin: "npm:^9.1.0" html-webpack-plugin: "npm:^5.5.0" + launch-editor-middleware: "npm:^2.11.1" magic-string: "npm:^0.30.5" pretty-hrtime: "npm:^1.0.3" sirv: "npm:^2.0.4" @@ -17912,6 +17913,25 @@ __metadata: languageName: node linkType: hard +"launch-editor-middleware@npm:^2.11.1": + version: 2.11.1 + resolution: "launch-editor-middleware@npm:2.11.1" + dependencies: + launch-editor: "npm:^2.11.1" + checksum: 10c0/d78a3cf0e166ebf9023f81a2f4b3f570422a7bd9edd505e85018247f3a75b7705f4973f3f3c3db0ec7aa118159fa85affc6d885f72ef0a267d1155ff3f5c2d19 + languageName: node + linkType: hard + +"launch-editor@npm:^2.11.1": + version: 2.11.1 + resolution: "launch-editor@npm:2.11.1" + dependencies: + picocolors: "npm:^1.1.1" + shell-quote: "npm:^1.8.3" + checksum: 10c0/b1aad04eef3a675aa35e82498bedaaeb790b9a02834a9cff79987dd7c6f5d92fd8f79ff7a8a4cd61681e0d462069de30d0bc65b41a936a7e3d700a4fdac1090e + languageName: node + linkType: hard + "launch-editor@npm:^2.6.1": version: 2.10.0 resolution: "launch-editor@npm:2.10.0" @@ -23966,6 +23986,13 @@ __metadata: languageName: node linkType: hard +"shell-quote@npm:^1.8.3": + version: 1.8.3 + resolution: "shell-quote@npm:1.8.3" + checksum: 10c0/bee87c34e1e986cfb4c30846b8e6327d18874f10b535699866f368ade11ea4ee45433d97bf5eada22c4320c27df79c3a6a7eb1bf3ecfc47f2c997d9e5e2672fd + languageName: node + linkType: hard + "side-channel-list@npm:^1.0.0": version: 1.0.0 resolution: "side-channel-list@npm:1.0.0" From b665876c045a422407e1d3b31e0bc66a03396715 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Mon, 15 Sep 2025 19:32:21 +0200 Subject: [PATCH 02/44] Introduce open in editor and copy story name actions in sidebar context menu, as well as shortcuts --- .../core-server/utils/StoryIndexGenerator.ts | 1 + .../core/src/manager-api/modules/shortcuts.ts | 20 +++++++ .../components/sidebar/ContextMenu.tsx | 59 +++++++++++++++++-- code/core/src/manager/settings/shortcuts.tsx | 2 + code/core/src/types/modules/api-stories.ts | 1 + 5 files changed, 77 insertions(+), 6 deletions(-) diff --git a/code/core/src/core-server/utils/StoryIndexGenerator.ts b/code/core/src/core-server/utils/StoryIndexGenerator.ts index 64841b4727a4..bc6fb15082cf 100644 --- a/code/core/src/core-server/utils/StoryIndexGenerator.ts +++ b/code/core/src/core-server/utils/StoryIndexGenerator.ts @@ -451,6 +451,7 @@ export class StoryIndexGenerator { importPath, componentPath, tags, + ...(input.exportName ? { exportName: input.exportName } : {}), }; }); diff --git a/code/core/src/manager-api/modules/shortcuts.ts b/code/core/src/manager-api/modules/shortcuts.ts index 3970d3cf2324..256048f40999 100644 --- a/code/core/src/manager-api/modules/shortcuts.ts +++ b/code/core/src/manager-api/modules/shortcuts.ts @@ -7,6 +7,9 @@ import { import { global } from '@storybook/global'; +import copy from 'copy-to-clipboard'; + +import { openInEditor } from '../lib/open-in-editor'; import type { KeyboardEventLike } from '../lib/shortcut'; import { eventToShortcut, shortcutMatchesShortcut } from '../lib/shortcut'; import type { ModuleFn } from '../lib/types'; @@ -110,6 +113,8 @@ export interface API_Shortcuts { collapseAll: API_KeyCollection; expandAll: API_KeyCollection; remount: API_KeyCollection; + openInEditor: API_KeyCollection; + copyStoryName: API_KeyCollection; } export type API_Action = keyof API_Shortcuts; @@ -145,6 +150,8 @@ export const defaultShortcuts: API_Shortcuts = Object.freeze({ collapseAll: [controlOrMetaKey(), 'shift', 'ArrowUp'], expandAll: [controlOrMetaKey(), 'shift', 'ArrowDown'], remount: ['alt', 'R'], + openInEditor: ['alt', 'shift', 'E'], + copyStoryName: ['alt', 'shift', 'C'], }); const addonsShortcuts: API_AddonShortcuts = {}; @@ -379,6 +386,19 @@ export const init: ModuleFn = ({ store, fullAPI, provider }) => { fullAPI.emit(FORCE_REMOUNT, { storyId }); break; } + case 'openInEditor': { + if (global.CONFIG_TYPE === 'DEVELOPMENT') { + openInEditor(fullAPI.getCurrentStoryData().importPath); + } + break; + } + case 'copyStoryName': { + const storyData = fullAPI.getCurrentStoryData(); + if (storyData.type === 'story') { + copy(storyData.exportName); + } + break; + } default: addonsShortcuts[feature].action(); break; diff --git a/code/core/src/manager/components/sidebar/ContextMenu.tsx b/code/core/src/manager/components/sidebar/ContextMenu.tsx index f4fb0ac86a86..516dbfc717a8 100644 --- a/code/core/src/manager/components/sidebar/ContextMenu.tsx +++ b/code/core/src/manager/components/sidebar/ContextMenu.tsx @@ -9,13 +9,16 @@ import { Addon_TypesEnum, } from 'storybook/internal/types'; -import { EllipsisIcon } from '@storybook/icons'; +import { global } from '@storybook/global'; +import { CopyIcon, EllipsisIcon, MarkupIcon } from '@storybook/icons'; -import { useStorybookApi } from 'storybook/manager-api'; +import copy from 'copy-to-clipboard'; +import { openInEditor, useStorybookApi } from 'storybook/manager-api'; import type { API } from 'storybook/manager-api'; import { styled } from 'storybook/theming'; import type { Link } from '../../../components/components/tooltip/TooltipLinkList'; +import { Shortcut } from '../../container/Menu'; import { StatusButton } from './StatusButton'; import type { ExcludesNull } from './Tree'; @@ -111,12 +114,56 @@ const LiveContextMenu: FC<{ context: API_HashEntry } & ComponentProps { - const registeredTestProviders = useStorybookApi().getElements( - Addon_TypesEnum.experimental_TEST_PROVIDER - ); + const api = useStorybookApi(); + const entry = api.getData(context.id, context.refId); + const importPath = entry?.importPath; + const storyName = (entry && 'exportName' in entry && entry.exportName) || context?.name; + const [copyText, setCopyText] = React.useState('Copy story name'); + + const shortcutKeys = api.getShortcutKeys(); + const enableShortcuts = !!shortcutKeys; + + const registeredTestProviders = api.getElements(Addon_TypesEnum.experimental_TEST_PROVIDER); const providerLinks: Link[] = generateTestProviderLinks(registeredTestProviders, context); + + const topLinks: Link[] = []; + + if (importPath) { + if (global.CONFIG_TYPE === 'DEVELOPMENT') { + topLinks.push({ + id: 'open-in-editor', + title: 'Open in editor', + icon: , + right: enableShortcuts ? : null, + onClick: (e) => { + e.preventDefault(); + if (importPath && !context.refId) { + openInEditor(importPath); + } + }, + }); + } + + topLinks.push({ + id: 'copy-story-name', + title: copyText, + icon: , + right: enableShortcuts ? : null, + onClick: () => { + if (storyName) { + copy(String(storyName)); + setCopyText('Copied!'); + setTimeout(() => { + setCopyText('Copy story name'); + }, 2000); + } + }, + }); + } + const groups = Array.isArray(links[0]) ? (links as Link[][]) : [links as Link[]]; - const all = groups.concat([providerLinks]); + const all = + topLinks.length > 0 ? [topLinks, ...groups, providerLinks] : [...groups, providerLinks]; return ; }; diff --git a/code/core/src/manager/settings/shortcuts.tsx b/code/core/src/manager/settings/shortcuts.tsx index 94bc6bfc6505..43c5dbc55050 100644 --- a/code/core/src/manager/settings/shortcuts.tsx +++ b/code/core/src/manager/settings/shortcuts.tsx @@ -132,6 +132,8 @@ const shortcutLabels = { collapseAll: 'Collapse all items on sidebar', expandAll: 'Expand all items on sidebar', remount: 'Remount component', + openInEditor: 'Open story in editor', + copyStoryName: 'Copy story name to clipboard', }; export type Feature = keyof typeof shortcutLabels; diff --git a/code/core/src/types/modules/api-stories.ts b/code/core/src/types/modules/api-stories.ts index 5dbe24143285..c62892c93750 100644 --- a/code/core/src/types/modules/api-stories.ts +++ b/code/core/src/types/modules/api-stories.ts @@ -46,6 +46,7 @@ export interface API_StoryEntry extends API_BaseEntry { parent: StoryId; title: ComponentTitle; importPath: Path; + exportName: string; prepared: boolean; parameters?: { [parameterName: string]: any; From 534e35ba8a8b63e3d789a04d3217d120abd44242 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Mon, 15 Sep 2025 19:49:49 +0200 Subject: [PATCH 03/44] add open in editor action in interactions panel --- .../components/InteractionsPanel.stories.tsx | 5 +++ .../components/Subnav.stories.tsx | 17 ++++++++ .../component-testing/components/Subnav.tsx | 39 ++++++++++++++++--- 3 files changed, 55 insertions(+), 6 deletions(-) diff --git a/code/core/src/component-testing/components/InteractionsPanel.stories.tsx b/code/core/src/component-testing/components/InteractionsPanel.stories.tsx index 83a5adf660f4..46b7f983b07d 100644 --- a/code/core/src/component-testing/components/InteractionsPanel.stories.tsx +++ b/code/core/src/component-testing/components/InteractionsPanel.stories.tsx @@ -30,6 +30,11 @@ const managerContext: any = { api: { getDocsUrl: fn().mockName('api::getDocsUrl'), emit: fn().mockName('api::emit'), + getData: fn() + .mockName('api::getData') + .mockImplementation(() => ({ + importPath: 'core/src/component-testing/components/InteractionsPanel.stories.tsx', + })), }, }; diff --git a/code/core/src/component-testing/components/Subnav.stories.tsx b/code/core/src/component-testing/components/Subnav.stories.tsx index b6af565de7c8..36b757c4d355 100644 --- a/code/core/src/component-testing/components/Subnav.stories.tsx +++ b/code/core/src/component-testing/components/Subnav.stories.tsx @@ -1,13 +1,30 @@ +import React from 'react'; + import { action } from 'storybook/actions'; +import { ManagerContext } from 'storybook/manager-api'; import { Subnav } from './Subnav'; +const managerContext: any = { + state: {}, + api: { + getData: () => ({ importPath: 'core/src/component-testing/components/Subnav.stories.tsx' }), + }, +}; + export default { title: 'Subnav', component: Subnav, parameters: { layout: 'fullscreen', }, + decorators: [ + (Story: any) => ( + + + + ), + ], args: { controls: { start: action('start'), diff --git a/code/core/src/component-testing/components/Subnav.tsx b/code/core/src/component-testing/components/Subnav.tsx index 6fbbd9d69114..e4c9ed2d62df 100644 --- a/code/core/src/component-testing/components/Subnav.tsx +++ b/code/core/src/component-testing/components/Subnav.tsx @@ -11,6 +11,7 @@ import { WithTooltip, } from 'storybook/internal/components'; +import { global } from '@storybook/global'; import { FastForwardIcon, PlayBackIcon, @@ -19,6 +20,8 @@ import { SyncIcon, } from '@storybook/icons'; +import { Consumer, openInEditor } from 'storybook/manager-api'; +import type { Combo } from 'storybook/manager-api'; import { styled, useTheme } from 'storybook/theming'; import { type ControlStates } from '../../instrumenter/types'; @@ -74,7 +77,9 @@ const StyledSeparator = styled(Separator)({ }); const StyledLocation = styled(P)(({ theme }) => ({ - color: theme.textMutedColor, + color: theme.color.secondary, + cursor: 'pointer', + fontWeight: theme.typography.weight.bold, justifyContent: 'flex-end', textAlign: 'right', whiteSpace: 'nowrap', @@ -182,11 +187,33 @@ export const Subnav: React.FC = ({ - {storyFileName && ( - - {storyFileName} - - )} + + ({ + importPath: api.getData(state.storyId, state.refId)?.importPath as + | string + | undefined, + isLocal: !state.refId, + })} + > + {({ importPath, isLocal }) => + global.CONFIG_TYPE === 'DEVELOPMENT' && isLocal && (importPath || storyFileName) ? ( + } + > + openInEditor((importPath || storyFileName) as string)} + > + {storyFileName} + + + ) : null + } + + From e5bcac03a14ad7b2d3ec6603e58bd3179a0c8b95 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Mon, 15 Sep 2025 21:06:07 +0200 Subject: [PATCH 04/44] Add share tool functionality with QR code support in the preview component. Updated package dependencies to include 'react-qr-code' and added related stories for testing. Enhanced sidebar context menu with new shortcut keys. --- code/core/package.json | 1 + .../preview/tools/share.stories.tsx | 44 +++++ .../components/preview/tools/share.tsx | 174 ++++++++++++++++++ .../components/sidebar/Tree.stories.tsx | 2 + code/core/src/manager/container/Preview.tsx | 5 +- code/yarn.lock | 20 ++ 6 files changed, 243 insertions(+), 3 deletions(-) create mode 100644 code/core/src/manager/components/preview/tools/share.stories.tsx create mode 100644 code/core/src/manager/components/preview/tools/share.tsx diff --git a/code/core/package.json b/code/core/package.json index c8ca010ca76b..6896d42547d7 100644 --- a/code/core/package.json +++ b/code/core/package.json @@ -236,6 +236,7 @@ "@vitest/spy": "3.2.4", "better-opn": "^3.0.2", "esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0", + "react-qr-code": "^2.0.12", "recast": "^0.23.5", "semver": "^7.6.2", "ws": "^8.18.0" diff --git a/code/core/src/manager/components/preview/tools/share.stories.tsx b/code/core/src/manager/components/preview/tools/share.stories.tsx new file mode 100644 index 000000000000..a86bd48adbbb --- /dev/null +++ b/code/core/src/manager/components/preview/tools/share.stories.tsx @@ -0,0 +1,44 @@ +import React from 'react'; + +import type { StoryObj } from '@storybook/react-vite'; + +import { ManagerContext } from 'storybook/manager-api'; +import { expect, screen } from 'storybook/test'; + +import { shareTool } from './share'; + +const managerContext: any = { + state: { + storyId: 'manager-preview-tools-share--default', + refId: undefined, + refs: {}, + customQueryParams: {}, + }, + api: { + getShortcutKeys: () => ({ copyStoryLink: ['meta', 'shift', 'c'] }), + }, +}; + +const ManagerDecorator = (Story: any) => ( + +
{Story()}
+
+); + +const meta = { + title: 'Manager/Preview/Tools/Share', + render: shareTool.render, + decorators: [ManagerDecorator], + parameters: { layout: 'centered' }, +}; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + play: async ({ userEvent, canvas }) => { + await userEvent.click(canvas.getByRole('button')); + await expect(await screen.findByText('Scan me')).toBeVisible(); + }, +}; diff --git a/code/core/src/manager/components/preview/tools/share.tsx b/code/core/src/manager/components/preview/tools/share.tsx new file mode 100644 index 000000000000..97f6d0a9108e --- /dev/null +++ b/code/core/src/manager/components/preview/tools/share.tsx @@ -0,0 +1,174 @@ +import React, { useMemo, useState } from 'react'; + +import { + IconButton, + TooltipLinkList, + WithTooltip, + getStoryHref, +} from 'storybook/internal/components'; +import type { Addon_BaseType } from 'storybook/internal/types'; + +import { global } from '@storybook/global'; +import { LinkIcon, ShareAltIcon } from '@storybook/icons'; + +import copy from 'copy-to-clipboard'; +// @ts-expect-error see https://github.com/rosskhanas/react-qr-code/issues/251 +import { QRCode } from 'react-qr-code'; +import { Consumer, types } from 'storybook/manager-api'; +import type { Combo } from 'storybook/manager-api'; +import { styled, useTheme } from 'storybook/theming'; + +const { PREVIEW_URL, document } = global; + +const mapper = ({ state }: Combo) => { + const { storyId, refId, refs } = state; + const { location } = document; + // @ts-expect-error (non strict) + const ref = refs[refId]; + let baseUrl = `${location.origin}${location.pathname}`; + + if (!baseUrl.endsWith('/')) { + baseUrl += '/'; + } + + return { + refId, + baseUrl: ref ? `${ref.url}/iframe.html` : (PREVIEW_URL as string) || `${baseUrl}iframe.html`, + storyId, + queryParams: state.customQueryParams, + }; +}; + +const QRContainer = styled.div(() => ({ + display: 'flex', + alignItems: 'center', + padding: 8, + maxWidth: 200, +})); + +const QRImageContainer = styled.div(() => ({ + width: 64, + height: 64, + marginRight: 12, + backgroundColor: 'white', + padding: 2, +})); + +const QRImage = ({ value }: { value: string }) => { + const theme = useTheme(); + return ( + value && ( + + + + ) + ); +}; + +const QRContent = styled.div(() => ({})); + +const QRTitle = styled.div(({ theme }) => ({ + fontWeight: theme.typography.weight.bold, + fontSize: theme.typography.size.s1, + marginBottom: 4, +})); + +const QRDescription = styled.div(({ theme }) => ({ + fontSize: theme.typography.size.s1, + color: theme.textMutedColor, +})); + +function ShareMenu({ + baseUrl, + storyId, + queryParams, + qrUrl = 'http://192.168.68.112:6006', +}: { + baseUrl: string; + storyId: string; + queryParams: Record; + qrUrl?: string; +}) { + // const api = useStorybookApi(); + // const shortcutKeys = api.getShortcutKeys(); + // const enableShortcuts = !!shortcutKeys; + const [copied, setCopied] = useState(false); + + const links = useMemo(() => { + const copyTitle = copied ? 'Copied!' : 'Copy story link'; + const baseLinks = [ + [ + { + id: 'copy-link', + title: copyTitle, + icon: , + // right: enableShortcuts ? : null, + onClick: () => { + copy(getStoryHref(baseUrl, storyId, queryParams)); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }, + }, + { + id: 'open-new-tab', + title: 'Open in isolation mode', + icon: , + onClick: () => { + const href = getStoryHref(baseUrl, storyId, queryParams); + window.open(href, '_blank', 'noopener,noreferrer'); + }, + }, + ], + ]; + + if (qrUrl) { + baseLinks.push([ + { + id: 'qr-section', + // @ts-expect-error (non strict) + content: ( + + + + Scan me + Must be on the same network as this device. + + + ), + }, + ]); + } + + return baseLinks; + }, [baseUrl, storyId, queryParams, copied, qrUrl]); + + return ; +} + +export const shareTool: Addon_BaseType = { + title: 'share', + id: 'share', + type: types.TOOL, + match: ({ viewMode, tabId }) => viewMode === 'story' && !tabId, + render: () => { + // todo: figure things out, this will have to be retrieved from server later + const externalUrl = 'http://192.168.68.112:6006'; + return ( + + {({ baseUrl, storyId, queryParams }) => + storyId ? ( + } + > + + + + + ) : null + } + + ); + }, +}; diff --git a/code/core/src/manager/components/sidebar/Tree.stories.tsx b/code/core/src/manager/components/sidebar/Tree.stories.tsx index 4e8a8cfdbad0..3557ee29fe30 100644 --- a/code/core/src/manager/components/sidebar/Tree.stories.tsx +++ b/code/core/src/manager/components/sidebar/Tree.stories.tsx @@ -28,6 +28,8 @@ const managerContext: any = { on: fn().mockName('api::on'), off: fn().mockName('api::off'), emit: fn().mockName('api::emit'), + getShortcutKeys: fn().mockName('api::getShortcutKeys'), + getCurrentStoryData: fn().mockName('api::getCurrentStoryData'), getElements: fn( () => ({ diff --git a/code/core/src/manager/container/Preview.tsx b/code/core/src/manager/container/Preview.tsx index 4c8191dadbbc..e410ca0aecac 100644 --- a/code/core/src/manager/container/Preview.tsx +++ b/code/core/src/manager/container/Preview.tsx @@ -14,17 +14,16 @@ import { Preview, createCanvasTab, filterTabs } from '../components/preview/Prev import { filterToolsSide, fullScreenTool } from '../components/preview/Toolbar'; import { defaultWrappers } from '../components/preview/Wrappers'; import { addonsTool } from '../components/preview/tools/addons'; -import { copyTool } from '../components/preview/tools/copy'; -import { ejectTool } from '../components/preview/tools/eject'; import { menuTool } from '../components/preview/tools/menu'; import { openInEditorTool } from '../components/preview/tools/open-in-editor'; import { remountTool } from '../components/preview/tools/remount'; +import { shareTool } from '../components/preview/tools/share'; import { zoomTool } from '../components/preview/tools/zoom'; import type { PreviewProps } from '../components/preview/utils/types'; const defaultTabs = [createCanvasTab()]; const defaultTools = [menuTool, remountTool, zoomTool]; -const defaultToolsExtra = [addonsTool, fullScreenTool, ejectTool, copyTool, openInEditorTool]; +const defaultToolsExtra = [addonsTool, fullScreenTool, shareTool, openInEditorTool]; const emptyTabsList: Addon_BaseType[] = []; diff --git a/code/yarn.lock b/code/yarn.lock index e19a41aea84b..5b53e9bc8a01 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -22063,6 +22063,13 @@ __metadata: languageName: node linkType: hard +"qr.js@npm:0.0.0": + version: 0.0.0 + resolution: "qr.js@npm:0.0.0" + checksum: 10c0/1c6a4c7a58d04e52ec2fee99e39b680fdc5b2a510a981df42c36b716a8eac6634d130fc4d65af8f030f2a07dbf5fa046b97cdfa7456c250ebb50a73916efdcb5 + languageName: node + linkType: hard + "qs@npm:6.13.0": version: 6.13.0 resolution: "qs@npm:6.13.0" @@ -22395,6 +22402,18 @@ __metadata: languageName: node linkType: hard +"react-qr-code@npm:^2.0.12": + version: 2.0.18 + resolution: "react-qr-code@npm:2.0.18" + dependencies: + prop-types: "npm:^15.8.1" + qr.js: "npm:0.0.0" + peerDependencies: + react: "*" + checksum: 10c0/4e13b795cbb10f1dcf0e39d682bb59851e4c84010ba2be7225b2ad9d5c1ffea52d2d38f884ee26235b7002b8ca99e83b805f55e877663c39d67496764d975cf1 + languageName: node + linkType: hard + "react-refresh@npm:^0.14.0": version: 0.14.2 resolution: "react-refresh@npm:0.14.2" @@ -24556,6 +24575,7 @@ __metadata: react-helmet-async: "npm:^1.3.0" react-inspector: "npm:^6.0.0" react-popper-tooltip: "npm:^4.4.2" + react-qr-code: "npm:^2.0.12" react-router-dom: "npm:6.15.0" react-syntax-highlighter: "npm:^15.4.5" react-textarea-autosize: "npm:^8.3.0" From 90b7020abdf3c73da96dca0fa8cfa708084038b8 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Mon, 15 Sep 2025 23:23:36 +0200 Subject: [PATCH 05/44] surface network address to Storybook's UI --- code/addons/a11y/package.json | 2 +- code/addons/docs/package.json | 2 +- code/addons/jest/package.json | 2 +- code/addons/onboarding/package.json | 2 +- code/addons/pseudo-states/package.json | 2 +- code/addons/themes/package.json | 2 +- code/addons/vitest/package.json | 2 +- code/core/package.json | 2 +- code/core/src/builder-manager/index.ts | 3 +++ .../core/src/builder-manager/utils/template.ts | 9 +++++++++ code/core/src/core-server/dev-server.ts | 5 +++++ .../components/preview/tools/share.stories.tsx | 9 +++++++-- .../manager/components/preview/tools/share.tsx | 13 ++++++------- code/core/src/manager/typings.d.ts | 2 ++ code/core/src/types/modules/core-common.ts | 3 +++ code/yarn.lock | 18 +++++++++--------- 16 files changed, 52 insertions(+), 26 deletions(-) diff --git a/code/addons/a11y/package.json b/code/addons/a11y/package.json index 0b8923cdca7f..174d9d9bfbce 100644 --- a/code/addons/a11y/package.json +++ b/code/addons/a11y/package.json @@ -64,7 +64,7 @@ }, "devDependencies": { "@radix-ui/react-tabs": "1.0.4", - "@storybook/icons": "^1.4.0", + "@storybook/icons": "1.4.0", "@testing-library/react": "^14.0.0", "execa": "^9.5.2", "react": "^18.2.0", diff --git a/code/addons/docs/package.json b/code/addons/docs/package.json index 5b5365c3ef3c..2c49bcbae3f0 100644 --- a/code/addons/docs/package.json +++ b/code/addons/docs/package.json @@ -85,7 +85,7 @@ "dependencies": { "@mdx-js/react": "^3.0.0", "@storybook/csf-plugin": "workspace:*", - "@storybook/icons": "^1.4.0", + "@storybook/icons": "1.4.0", "@storybook/react-dom-shim": "workspace:*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", diff --git a/code/addons/jest/package.json b/code/addons/jest/package.json index bd630e4f1004..a3db158c67e0 100644 --- a/code/addons/jest/package.json +++ b/code/addons/jest/package.json @@ -54,7 +54,7 @@ "upath": "^2.0.1" }, "devDependencies": { - "@storybook/icons": "^1.4.0", + "@storybook/icons": "1.4.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-resize-detector": "^7.1.2", diff --git a/code/addons/onboarding/package.json b/code/addons/onboarding/package.json index b015f580a55e..37a0890f1daa 100644 --- a/code/addons/onboarding/package.json +++ b/code/addons/onboarding/package.json @@ -52,7 +52,7 @@ }, "devDependencies": { "@neoconfetti/react": "^1.0.0", - "@storybook/icons": "^1.4.0", + "@storybook/icons": "1.4.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-joyride": "^2.8.2", diff --git a/code/addons/pseudo-states/package.json b/code/addons/pseudo-states/package.json index e754eb16bb67..7328912b9b81 100644 --- a/code/addons/pseudo-states/package.json +++ b/code/addons/pseudo-states/package.json @@ -55,7 +55,7 @@ "prep": "jiti ../../../scripts/build/build-package.ts" }, "devDependencies": { - "@storybook/icons": "^1.4.0", + "@storybook/icons": "1.4.0", "react": "^18.2.0", "react-dom": "^18.2.0", "typescript": "^5.8.3" diff --git a/code/addons/themes/package.json b/code/addons/themes/package.json index 977fc73d92ce..8b887858975d 100644 --- a/code/addons/themes/package.json +++ b/code/addons/themes/package.json @@ -61,7 +61,7 @@ "ts-dedent": "^2.0.0" }, "devDependencies": { - "@storybook/icons": "^1.4.0", + "@storybook/icons": "1.4.0", "react": "^18.2.0", "react-dom": "^18.2.0", "typescript": "^5.8.3" diff --git a/code/addons/vitest/package.json b/code/addons/vitest/package.json index f94405d306a6..6d458affae59 100644 --- a/code/addons/vitest/package.json +++ b/code/addons/vitest/package.json @@ -73,7 +73,7 @@ }, "dependencies": { "@storybook/global": "^5.0.0", - "@storybook/icons": "^1.4.0", + "@storybook/icons": "1.4.0", "prompts": "^2.4.0", "ts-dedent": "^2.2.0" }, diff --git a/code/core/package.json b/code/core/package.json index 6896d42547d7..faf419921973 100644 --- a/code/core/package.json +++ b/code/core/package.json @@ -228,7 +228,7 @@ }, "dependencies": { "@storybook/global": "^5.0.0", - "@storybook/icons": "^1.4.0", + "@storybook/icons": "1.4.0", "@testing-library/jest-dom": "^6.6.3", "@testing-library/user-event": "^14.6.1", "@vitest/expect": "3.2.4", diff --git a/code/core/src/builder-manager/index.ts b/code/core/src/builder-manager/index.ts index 113ebfc56d9f..b0c4d96aac6a 100644 --- a/code/core/src/builder-manager/index.ts +++ b/code/core/src/builder-manager/index.ts @@ -200,6 +200,9 @@ const starter: StarterFunction = async function* starterGeneratorFn({ // Build additional global values const globals: Record = await buildFrameworkGlobalsFromOptions(options); + // Surface server addresses to the manager for UI usage (e.g., share tool) + globals.STORYBOOK_ADDRESS = options.address; + globals.STORYBOOK_NETWORK_ADDRESS = options.networkAddress; yield; diff --git a/code/core/src/builder-manager/utils/template.ts b/code/core/src/builder-manager/utils/template.ts index c31088c39d41..0d44d2c70e80 100644 --- a/code/core/src/builder-manager/utils/template.ts +++ b/code/core/src/builder-manager/utils/template.ts @@ -56,6 +56,15 @@ 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 + // Server addresses available in development for sharing over network + STORYBOOK_ADDRESS: JSON.stringify( + (globals as any).STORYBOOK_ADDRESS ?? (globalThis as any).STORYBOOK_ADDRESS ?? undefined + ), + STORYBOOK_NETWORK_ADDRESS: JSON.stringify( + (globals as any).STORYBOOK_NETWORK_ADDRESS ?? + (globalThis as any).STORYBOOK_NETWORK_ADDRESS ?? + undefined + ), TAGS_OPTIONS: JSON.stringify(await tagsOptions, null, 2), ...stringifiedGlobals, }, diff --git a/code/core/src/core-server/dev-server.ts b/code/core/src/core-server/dev-server.ts index f79251d221a3..066edfcb04f0 100644 --- a/code/core/src/core-server/dev-server.ts +++ b/code/core/src/core-server/dev-server.ts @@ -55,6 +55,11 @@ export async function storybookDevServer(options: Options) { const proto = options.https ? 'https' : 'http'; const { address, networkAddress } = getServerAddresses(port, host, proto, initialPath); + // Expose addresses on options for the manager builder to surface in globals + // This is safe because options is only used within the current dev server lifecycle + options.address = address; + options.networkAddress = networkAddress; + if (!core?.builder) { throw new MissingBuilderError(); } diff --git a/code/core/src/manager/components/preview/tools/share.stories.tsx b/code/core/src/manager/components/preview/tools/share.stories.tsx index a86bd48adbbb..6b9c24c6e7c2 100644 --- a/code/core/src/manager/components/preview/tools/share.stories.tsx +++ b/code/core/src/manager/components/preview/tools/share.stories.tsx @@ -1,5 +1,7 @@ import React from 'react'; +import { global } from '@storybook/global'; + import type { StoryObj } from '@storybook/react-vite'; import { ManagerContext } from 'storybook/manager-api'; @@ -7,7 +9,7 @@ import { expect, screen } from 'storybook/test'; import { shareTool } from './share'; -const managerContext: any = { +const managerContext = { state: { storyId: 'manager-preview-tools-share--default', refId: undefined, @@ -17,7 +19,7 @@ const managerContext: any = { api: { getShortcutKeys: () => ({ copyStoryLink: ['meta', 'shift', 'c'] }), }, -}; +} as any; const ManagerDecorator = (Story: any) => ( @@ -37,6 +39,9 @@ export default meta; type Story = StoryObj; export const Default: Story = { + beforeEach: () => { + global.STORYBOOK_NETWORK_ADDRESS = 'http://127.0.0.1:6006'; + }, play: async ({ userEvent, canvas }) => { await userEvent.click(canvas.getByRole('button')); await expect(await screen.findByText('Scan me')).toBeVisible(); diff --git a/code/core/src/manager/components/preview/tools/share.tsx b/code/core/src/manager/components/preview/tools/share.tsx index 97f6d0a9108e..42a52a2c0d6c 100644 --- a/code/core/src/manager/components/preview/tools/share.tsx +++ b/code/core/src/manager/components/preview/tools/share.tsx @@ -9,7 +9,7 @@ import { import type { Addon_BaseType } from 'storybook/internal/types'; import { global } from '@storybook/global'; -import { LinkIcon, ShareAltIcon } from '@storybook/icons'; +import { ShareAltIcon as BugIcon, LinkIcon } from '@storybook/icons'; import copy from 'copy-to-clipboard'; // @ts-expect-error see https://github.com/rosskhanas/react-qr-code/issues/251 @@ -18,7 +18,7 @@ import { Consumer, types } from 'storybook/manager-api'; import type { Combo } from 'storybook/manager-api'; import { styled, useTheme } from 'storybook/theming'; -const { PREVIEW_URL, document } = global; +const { PREVIEW_URL, document, STORYBOOK_NETWORK_ADDRESS } = global as any; const mapper = ({ state }: Combo) => { const { storyId, refId, refs } = state; @@ -82,7 +82,7 @@ function ShareMenu({ baseUrl, storyId, queryParams, - qrUrl = 'http://192.168.68.112:6006', + qrUrl, }: { baseUrl: string; storyId: string; @@ -112,7 +112,7 @@ function ShareMenu({ { id: 'open-new-tab', title: 'Open in isolation mode', - icon: , + icon: , onClick: () => { const href = getStoryHref(baseUrl, storyId, queryParams); window.open(href, '_blank', 'noopener,noreferrer'); @@ -151,8 +151,7 @@ export const shareTool: Addon_BaseType = { type: types.TOOL, match: ({ viewMode, tabId }) => viewMode === 'story' && !tabId, render: () => { - // todo: figure things out, this will have to be retrieved from server later - const externalUrl = 'http://192.168.68.112:6006'; + const externalUrl = (STORYBOOK_NETWORK_ADDRESS as string | undefined) ?? undefined; return ( {({ baseUrl, storyId, queryParams }) => @@ -163,7 +162,7 @@ export const shareTool: Addon_BaseType = { tooltip={} > - + ) : null diff --git a/code/core/src/manager/typings.d.ts b/code/core/src/manager/typings.d.ts index 64bba1c00cdf..6d89d60870ab 100644 --- a/code/core/src/manager/typings.d.ts +++ b/code/core/src/manager/typings.d.ts @@ -1,6 +1,8 @@ declare var DOCS_OPTIONS: any; declare var CONFIG_TYPE: 'DEVELOPMENT' | 'PRODUCTION'; declare var PREVIEW_URL: any; +declare var STORYBOOK_ADDRESS: string | undefined; +declare var STORYBOOK_NETWORK_ADDRESS: string | undefined; declare var __STORYBOOK_ADDONS_MANAGER: any; declare var RELEASE_NOTES_DATA: any; diff --git a/code/core/src/types/modules/core-common.ts b/code/core/src/types/modules/core-common.ts index caff102504df..aeb5c6cf0da1 100644 --- a/code/core/src/types/modules/core-common.ts +++ b/code/core/src/types/modules/core-common.ts @@ -204,6 +204,9 @@ export interface BuilderOptions { versionCheck?: VersionCheck; disableWebpackDefaults?: boolean; serverChannelUrl?: string; + /** Server addresses computed at runtime. Available in development to surface in the manager UI. */ + address?: string; + networkAddress?: string; } export interface StorybookConfigOptions { diff --git a/code/yarn.lock b/code/yarn.lock index 5b53e9bc8a01..e0a9a876f5e7 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -5944,7 +5944,7 @@ __metadata: dependencies: "@radix-ui/react-tabs": "npm:1.0.4" "@storybook/global": "npm:^5.0.0" - "@storybook/icons": "npm:^1.4.0" + "@storybook/icons": "npm:1.4.0" "@testing-library/react": "npm:^14.0.0" axe-core: "npm:^4.2.0" execa: "npm:^9.5.2" @@ -5986,7 +5986,7 @@ __metadata: "@mdx-js/react": "npm:^3.0.0" "@rollup/pluginutils": "npm:^5.0.2" "@storybook/csf-plugin": "workspace:*" - "@storybook/icons": "npm:^1.4.0" + "@storybook/icons": "npm:1.4.0" "@storybook/react-dom-shim": "workspace:*" "@types/color-convert": "npm:^2.0.0" "@types/react": "npm:^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" @@ -6015,7 +6015,7 @@ __metadata: version: 0.0.0-use.local resolution: "@storybook/addon-jest@workspace:addons/jest" dependencies: - "@storybook/icons": "npm:^1.4.0" + "@storybook/icons": "npm:1.4.0" react: "npm:^18.2.0" react-dom: "npm:^18.2.0" react-resize-detector: "npm:^7.1.2" @@ -6047,7 +6047,7 @@ __metadata: resolution: "@storybook/addon-onboarding@workspace:addons/onboarding" dependencies: "@neoconfetti/react": "npm:^1.0.0" - "@storybook/icons": "npm:^1.4.0" + "@storybook/icons": "npm:1.4.0" react: "npm:^18.2.0" react-dom: "npm:^18.2.0" react-joyride: "npm:^2.8.2" @@ -6061,7 +6061,7 @@ __metadata: version: 0.0.0-use.local resolution: "@storybook/addon-themes@workspace:addons/themes" dependencies: - "@storybook/icons": "npm:^1.4.0" + "@storybook/icons": "npm:1.4.0" react: "npm:^18.2.0" react-dom: "npm:^18.2.0" ts-dedent: "npm:^2.0.0" @@ -6076,7 +6076,7 @@ __metadata: resolution: "@storybook/addon-vitest@workspace:addons/vitest" dependencies: "@storybook/global": "npm:^5.0.0" - "@storybook/icons": "npm:^1.4.0" + "@storybook/icons": "npm:1.4.0" "@types/istanbul-lib-report": "npm:^3.0.3" "@types/micromatch": "npm:^4.0.0" "@types/node": "npm:^22.0.0" @@ -6407,7 +6407,7 @@ __metadata: languageName: unknown linkType: soft -"@storybook/icons@npm:^1.4.0": +"@storybook/icons@npm:1.4.0": version: 1.4.0 resolution: "@storybook/icons@npm:1.4.0" peerDependencies: @@ -24452,7 +24452,7 @@ __metadata: version: 0.0.0-use.local resolution: "storybook-addon-pseudo-states@workspace:addons/pseudo-states" dependencies: - "@storybook/icons": "npm:^1.4.0" + "@storybook/icons": "npm:1.4.0" react: "npm:^18.2.0" react-dom: "npm:^18.2.0" typescript: "npm:^5.8.3" @@ -24489,7 +24489,7 @@ __metadata: "@rolldown/pluginutils": "npm:1.0.0-beta.18" "@storybook/docs-mdx": "npm:4.0.0-next.1" "@storybook/global": "npm:^5.0.0" - "@storybook/icons": "npm:^1.4.0" + "@storybook/icons": "npm:1.4.0" "@tanstack/react-virtual": "npm:^3.3.0" "@testing-library/dom": "npm:10.4.0" "@testing-library/jest-dom": "npm:^6.6.3" From c110aed8665c49ba975039ae79bebad412d6995a Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Mon, 15 Sep 2025 23:43:58 +0200 Subject: [PATCH 06/44] fix types and most tests --- .../components/TestProviderRender.stories.tsx | 1 + .../utils/StoryIndexGenerator.test.ts | 52 +++++++++++++++++++ .../utils/__tests__/index-extraction.test.ts | 10 ++++ .../core-server/utils/stories-json.test.ts | 16 ++++++ .../components/preview/tools/share.tsx | 2 +- .../src/manager/settings/defaultShortcuts.tsx | 2 + 6 files changed, 82 insertions(+), 1 deletion(-) diff --git a/code/addons/vitest/src/components/TestProviderRender.stories.tsx b/code/addons/vitest/src/components/TestProviderRender.stories.tsx index fa5456ce69c1..b5c4919ad5a2 100644 --- a/code/addons/vitest/src/components/TestProviderRender.stories.tsx +++ b/code/addons/vitest/src/components/TestProviderRender.stories.tsx @@ -316,6 +316,7 @@ export const InSidebarContextMenu: Story = { importPath: './path/to/story', prepared: true, parent: 'parent-id', + exportName: 'ExampleStory', depth: 1, }, }, diff --git a/code/core/src/core-server/utils/StoryIndexGenerator.test.ts b/code/core/src/core-server/utils/StoryIndexGenerator.test.ts index f366f1e8ac74..27fa366eba4e 100644 --- a/code/core/src/core-server/utils/StoryIndexGenerator.test.ts +++ b/code/core/src/core-server/utils/StoryIndexGenerator.test.ts @@ -84,6 +84,7 @@ describe('StoryIndexGenerator', () => { "entries": { "a--story-one": { "componentPath": undefined, + "exportName": "StoryOne", "id": "a--story-one", "importPath": "./src/A.stories.js", "name": "Story One", @@ -146,6 +147,7 @@ describe('StoryIndexGenerator', () => { }, "f--story-one": { "componentPath": undefined, + "exportName": "StoryOne", "id": "f--story-one", "importPath": "./src/F.story.ts", "name": "Story One", @@ -192,6 +194,7 @@ describe('StoryIndexGenerator', () => { }, "stories--story-one": { "componentPath": undefined, + "exportName": "StoryOne", "id": "stories--story-one", "importPath": "./src/stories.ts", "name": "Story One", @@ -225,6 +228,7 @@ describe('StoryIndexGenerator', () => { "entries": { "componentpath-extension--story-one": { "componentPath": "./src/componentPath/component.js", + "exportName": "StoryOne", "id": "componentpath-extension--story-one", "importPath": "./src/componentPath/extension.stories.js", "name": "Story One", @@ -237,6 +241,7 @@ describe('StoryIndexGenerator', () => { }, "componentpath-noextension--story-one": { "componentPath": "./src/componentPath/component.js", + "exportName": "StoryOne", "id": "componentpath-noextension--story-one", "importPath": "./src/componentPath/noExtension.stories.js", "name": "Story One", @@ -249,6 +254,7 @@ describe('StoryIndexGenerator', () => { }, "componentpath-package--story-one": { "componentPath": "component-package", + "exportName": "StoryOne", "id": "componentpath-package--story-one", "importPath": "./src/componentPath/package.stories.js", "name": "Story One", @@ -261,6 +267,7 @@ describe('StoryIndexGenerator', () => { }, "nested-button--story-one": { "componentPath": undefined, + "exportName": "StoryOne", "id": "nested-button--story-one", "importPath": "./src/nested/Button.stories.ts", "name": "Story One", @@ -274,6 +281,7 @@ describe('StoryIndexGenerator', () => { }, "second-nested-g--story-one": { "componentPath": undefined, + "exportName": "StoryOne", "id": "second-nested-g--story-one", "importPath": "./src/second-nested/G.stories.ts", "name": "Story One", @@ -306,6 +314,7 @@ describe('StoryIndexGenerator', () => { "entries": { "a--story-one": { "componentPath": undefined, + "exportName": "StoryOne", "id": "a--story-one", "importPath": "./src/A.stories.js", "name": "Story One", @@ -333,6 +342,7 @@ describe('StoryIndexGenerator', () => { }, "b--story-one": { "componentPath": undefined, + "exportName": "StoryOne", "id": "b--story-one", "importPath": "./src/B.stories.ts", "name": "Story One", @@ -346,6 +356,7 @@ describe('StoryIndexGenerator', () => { }, "componentpath-extension--story-one": { "componentPath": "./src/componentPath/component.js", + "exportName": "StoryOne", "id": "componentpath-extension--story-one", "importPath": "./src/componentPath/extension.stories.js", "name": "Story One", @@ -358,6 +369,7 @@ describe('StoryIndexGenerator', () => { }, "componentpath-noextension--story-one": { "componentPath": "./src/componentPath/component.js", + "exportName": "StoryOne", "id": "componentpath-noextension--story-one", "importPath": "./src/componentPath/noExtension.stories.js", "name": "Story One", @@ -370,6 +382,7 @@ describe('StoryIndexGenerator', () => { }, "componentpath-package--story-one": { "componentPath": "component-package", + "exportName": "StoryOne", "id": "componentpath-package--story-one", "importPath": "./src/componentPath/package.stories.js", "name": "Story One", @@ -395,6 +408,7 @@ describe('StoryIndexGenerator', () => { }, "d--story-one": { "componentPath": undefined, + "exportName": "StoryOne", "id": "d--story-one", "importPath": "./src/D.stories.jsx", "name": "Story One", @@ -408,6 +422,7 @@ describe('StoryIndexGenerator', () => { }, "example-button--story-one": { "componentPath": undefined, + "exportName": "StoryOne", "id": "example-button--story-one", "importPath": "./src/Button.stories.ts", "name": "Story One", @@ -421,6 +436,7 @@ describe('StoryIndexGenerator', () => { }, "first-nested-deeply-f--story-one": { "componentPath": undefined, + "exportName": "StoryOne", "id": "first-nested-deeply-f--story-one", "importPath": "./src/first-nested/deeply/F.stories.js", "name": "Story One", @@ -433,6 +449,7 @@ describe('StoryIndexGenerator', () => { }, "first-nested-deeply-features--with-csf-1": { "componentPath": undefined, + "exportName": "WithCSF1", "id": "first-nested-deeply-features--with-csf-1", "importPath": "./src/first-nested/deeply/Features.stories.jsx", "name": "With CSF 1", @@ -445,6 +462,7 @@ describe('StoryIndexGenerator', () => { }, "first-nested-deeply-features--with-play": { "componentPath": undefined, + "exportName": "WithPlay", "id": "first-nested-deeply-features--with-play", "importPath": "./src/first-nested/deeply/Features.stories.jsx", "name": "With Play", @@ -458,6 +476,7 @@ describe('StoryIndexGenerator', () => { }, "first-nested-deeply-features--with-render": { "componentPath": undefined, + "exportName": "WithRender", "id": "first-nested-deeply-features--with-render", "importPath": "./src/first-nested/deeply/Features.stories.jsx", "name": "With Render", @@ -470,6 +489,7 @@ describe('StoryIndexGenerator', () => { }, "first-nested-deeply-features--with-story-fn": { "componentPath": undefined, + "exportName": "WithStoryFn", "id": "first-nested-deeply-features--with-story-fn", "importPath": "./src/first-nested/deeply/Features.stories.jsx", "name": "With Story Fn", @@ -482,6 +502,7 @@ describe('StoryIndexGenerator', () => { }, "first-nested-deeply-features--with-test": { "componentPath": undefined, + "exportName": "WithTest", "id": "first-nested-deeply-features--with-test", "importPath": "./src/first-nested/deeply/Features.stories.jsx", "name": "With Test", @@ -508,6 +529,7 @@ describe('StoryIndexGenerator', () => { }, "h--story-one": { "componentPath": undefined, + "exportName": "StoryOne", "id": "h--story-one", "importPath": "./src/H.stories.mjs", "name": "Story One", @@ -521,6 +543,7 @@ describe('StoryIndexGenerator', () => { }, "nested-button--story-one": { "componentPath": undefined, + "exportName": "StoryOne", "id": "nested-button--story-one", "importPath": "./src/nested/Button.stories.ts", "name": "Story One", @@ -534,6 +557,7 @@ describe('StoryIndexGenerator', () => { }, "second-nested-g--story-one": { "componentPath": undefined, + "exportName": "StoryOne", "id": "second-nested-g--story-one", "importPath": "./src/second-nested/G.stories.ts", "name": "Story One", @@ -586,6 +610,7 @@ describe('StoryIndexGenerator', () => { "entries": { "a--story-one": { "componentPath": undefined, + "exportName": "StoryOne", "id": "a--story-one", "importPath": "./src/A.stories.js", "name": "Story One", @@ -613,6 +638,7 @@ describe('StoryIndexGenerator', () => { }, "b--story-one": { "componentPath": undefined, + "exportName": "StoryOne", "id": "b--story-one", "importPath": "./src/B.stories.ts", "name": "Story One", @@ -626,6 +652,7 @@ describe('StoryIndexGenerator', () => { }, "componentpath-extension--story-one": { "componentPath": "./src/componentPath/component.js", + "exportName": "StoryOne", "id": "componentpath-extension--story-one", "importPath": "./src/componentPath/extension.stories.js", "name": "Story One", @@ -638,6 +665,7 @@ describe('StoryIndexGenerator', () => { }, "componentpath-noextension--story-one": { "componentPath": "./src/componentPath/component.js", + "exportName": "StoryOne", "id": "componentpath-noextension--story-one", "importPath": "./src/componentPath/noExtension.stories.js", "name": "Story One", @@ -650,6 +678,7 @@ describe('StoryIndexGenerator', () => { }, "componentpath-package--story-one": { "componentPath": "component-package", + "exportName": "StoryOne", "id": "componentpath-package--story-one", "importPath": "./src/componentPath/package.stories.js", "name": "Story One", @@ -675,6 +704,7 @@ describe('StoryIndexGenerator', () => { }, "d--story-one": { "componentPath": undefined, + "exportName": "StoryOne", "id": "d--story-one", "importPath": "./src/D.stories.jsx", "name": "Story One", @@ -688,6 +718,7 @@ describe('StoryIndexGenerator', () => { }, "example-button--story-one": { "componentPath": undefined, + "exportName": "StoryOne", "id": "example-button--story-one", "importPath": "./src/Button.stories.ts", "name": "Story One", @@ -701,6 +732,7 @@ describe('StoryIndexGenerator', () => { }, "first-nested-deeply-f--story-one": { "componentPath": undefined, + "exportName": "StoryOne", "id": "first-nested-deeply-f--story-one", "importPath": "./src/first-nested/deeply/F.stories.js", "name": "Story One", @@ -713,6 +745,7 @@ describe('StoryIndexGenerator', () => { }, "first-nested-deeply-features--with-csf-1": { "componentPath": undefined, + "exportName": "WithCSF1", "id": "first-nested-deeply-features--with-csf-1", "importPath": "./src/first-nested/deeply/Features.stories.jsx", "name": "With CSF 1", @@ -725,6 +758,7 @@ describe('StoryIndexGenerator', () => { }, "first-nested-deeply-features--with-play": { "componentPath": undefined, + "exportName": "WithPlay", "id": "first-nested-deeply-features--with-play", "importPath": "./src/first-nested/deeply/Features.stories.jsx", "name": "With Play", @@ -738,6 +772,7 @@ describe('StoryIndexGenerator', () => { }, "first-nested-deeply-features--with-render": { "componentPath": undefined, + "exportName": "WithRender", "id": "first-nested-deeply-features--with-render", "importPath": "./src/first-nested/deeply/Features.stories.jsx", "name": "With Render", @@ -750,6 +785,7 @@ describe('StoryIndexGenerator', () => { }, "first-nested-deeply-features--with-story-fn": { "componentPath": undefined, + "exportName": "WithStoryFn", "id": "first-nested-deeply-features--with-story-fn", "importPath": "./src/first-nested/deeply/Features.stories.jsx", "name": "With Story Fn", @@ -762,6 +798,7 @@ describe('StoryIndexGenerator', () => { }, "first-nested-deeply-features--with-test": { "componentPath": undefined, + "exportName": "WithTest", "id": "first-nested-deeply-features--with-test", "importPath": "./src/first-nested/deeply/Features.stories.jsx", "name": "With Test", @@ -788,6 +825,7 @@ describe('StoryIndexGenerator', () => { }, "h--story-one": { "componentPath": undefined, + "exportName": "StoryOne", "id": "h--story-one", "importPath": "./src/H.stories.mjs", "name": "Story One", @@ -801,6 +839,7 @@ describe('StoryIndexGenerator', () => { }, "nested-button--story-one": { "componentPath": undefined, + "exportName": "StoryOne", "id": "nested-button--story-one", "importPath": "./src/nested/Button.stories.ts", "name": "Story One", @@ -814,6 +853,7 @@ describe('StoryIndexGenerator', () => { }, "second-nested-g--story-one": { "componentPath": undefined, + "exportName": "StoryOne", "id": "second-nested-g--story-one", "importPath": "./src/second-nested/G.stories.ts", "name": "Story One", @@ -1033,6 +1073,7 @@ describe('StoryIndexGenerator', () => { }, "b--story-one": { "componentPath": undefined, + "exportName": "StoryOne", "id": "b--story-one", "importPath": "./src/B.stories.ts", "name": "Story One", @@ -1098,6 +1139,7 @@ describe('StoryIndexGenerator', () => { }, "b--story-one": { "componentPath": undefined, + "exportName": "StoryOne", "id": "b--story-one", "importPath": "./src/B.stories.ts", "name": "Story One", @@ -1155,6 +1197,7 @@ describe('StoryIndexGenerator', () => { }, "a--story-one": { "componentPath": undefined, + "exportName": "StoryOne", "id": "a--story-one", "importPath": "./src/A.stories.js", "name": "Story One", @@ -1212,6 +1255,7 @@ describe('StoryIndexGenerator', () => { }, "a--story-one": { "componentPath": undefined, + "exportName": "StoryOne", "id": "a--story-one", "importPath": "./src/A.stories.js", "name": "Story One", @@ -1261,6 +1305,7 @@ describe('StoryIndexGenerator', () => { }, "duplicate-a--story-one": { "componentPath": undefined, + "exportName": "StoryOne", "id": "duplicate-a--story-one", "importPath": "./duplicate/A.stories.js", "name": "Story One", @@ -1274,6 +1319,7 @@ describe('StoryIndexGenerator', () => { }, "duplicate-a--story-two": { "componentPath": undefined, + "exportName": "StoryTwo", "id": "duplicate-a--story-two", "importPath": "./duplicate/SecondA.stories.js", "name": "Story Two", @@ -1338,6 +1384,7 @@ describe('StoryIndexGenerator', () => { }, "my-component-a--story-one": { "componentPath": undefined, + "exportName": "StoryOne", "id": "my-component-a--story-one", "importPath": "./docs-id-generation/A.stories.jsx", "name": "Story One", @@ -1401,6 +1448,7 @@ describe('StoryIndexGenerator', () => { }, "a--story-one": { "componentPath": undefined, + "exportName": "StoryOne", "id": "a--story-one", "importPath": "./src/A.stories.js", "name": "Story One", @@ -1552,6 +1600,7 @@ describe('StoryIndexGenerator', () => { }, "a--story-one": { "componentPath": undefined, + "exportName": "StoryOne", "id": "a--story-one", "importPath": "./src/A.stories.js", "name": "Story One", @@ -1640,6 +1689,7 @@ describe('StoryIndexGenerator', () => { "entries": { "a--story-one": { "componentPath": undefined, + "exportName": "StoryOne", "id": "a--story-one", "importPath": "./src/A.stories.js", "name": "Story One", @@ -1667,6 +1717,7 @@ describe('StoryIndexGenerator', () => { }, "b--story-one": { "componentPath": undefined, + "exportName": "StoryOne", "id": "b--story-one", "importPath": "./src/B.stories.ts", "name": "Story One", @@ -1736,6 +1787,7 @@ describe('StoryIndexGenerator', () => { }, "my-component-b--story-one": { "componentPath": undefined, + "exportName": "StoryOne", "id": "my-component-b--story-one", "importPath": "./docs-id-generation/B.stories.jsx", "name": "Story One", diff --git a/code/core/src/core-server/utils/__tests__/index-extraction.test.ts b/code/core/src/core-server/utils/__tests__/index-extraction.test.ts index 41cfdf116fcd..4c6bea472f1f 100644 --- a/code/core/src/core-server/utils/__tests__/index-extraction.test.ts +++ b/code/core/src/core-server/utils/__tests__/index-extraction.test.ts @@ -63,6 +63,7 @@ describe('story extraction', () => { "entries": [ { "componentPath": undefined, + "exportName": "StoryOne", "extra": { "metaId": "a", "stats": {}, @@ -78,6 +79,7 @@ describe('story extraction', () => { }, { "componentPath": undefined, + "exportName": "StoryOne", "extra": { "metaId": "custom-id", "stats": {}, @@ -125,6 +127,7 @@ describe('story extraction', () => { "entries": [ { "componentPath": undefined, + "exportName": "StoryOne", "extra": { "metaId": undefined, "stats": {}, @@ -174,6 +177,7 @@ describe('story extraction', () => { "entries": [ { "componentPath": undefined, + "exportName": "StoryOne", "extra": { "metaId": "a", "stats": {}, @@ -225,6 +229,7 @@ describe('story extraction', () => { "entries": [ { "componentPath": undefined, + "exportName": "StoryOne", "extra": { "metaId": "a", "stats": {}, @@ -294,6 +299,7 @@ describe('story extraction', () => { "entries": [ { "componentPath": undefined, + "exportName": "StoryOne", "extra": { "metaId": undefined, "stats": {}, @@ -309,6 +315,7 @@ describe('story extraction', () => { }, { "componentPath": undefined, + "exportName": "StoryTwo", "extra": { "metaId": undefined, "stats": {}, @@ -324,6 +331,7 @@ describe('story extraction', () => { }, { "componentPath": undefined, + "exportName": "StoryThree", "extra": { "metaId": "custom-meta-id", "stats": {}, @@ -372,6 +380,7 @@ describe('story extraction', () => { "entries": [ { "componentPath": undefined, + "exportName": "StoryOne", "extra": { "metaId": undefined, "stats": {}, @@ -437,6 +446,7 @@ describe('docs entries from story extraction', () => { }, { "componentPath": undefined, + "exportName": "StoryOne", "extra": { "metaId": undefined, "stats": {}, diff --git a/code/core/src/core-server/utils/stories-json.test.ts b/code/core/src/core-server/utils/stories-json.test.ts index 6f5907f2c38c..0c426a7b960b 100644 --- a/code/core/src/core-server/utils/stories-json.test.ts +++ b/code/core/src/core-server/utils/stories-json.test.ts @@ -150,6 +150,7 @@ describe('useStoriesJson', () => { "type": "docs", }, "a--story-one": { + "exportName": "StoryOne", "id": "a--story-one", "importPath": "./src/A.stories.js", "name": "Story One", @@ -176,6 +177,7 @@ describe('useStoriesJson', () => { "type": "docs", }, "b--story-one": { + "exportName": "StoryOne", "id": "b--story-one", "importPath": "./src/B.stories.ts", "name": "Story One", @@ -189,6 +191,7 @@ describe('useStoriesJson', () => { }, "componentpath-extension--story-one": { "componentPath": "./src/componentPath/component.js", + "exportName": "StoryOne", "id": "componentpath-extension--story-one", "importPath": "./src/componentPath/extension.stories.js", "name": "Story One", @@ -201,6 +204,7 @@ describe('useStoriesJson', () => { }, "componentpath-noextension--story-one": { "componentPath": "./src/componentPath/component.js", + "exportName": "StoryOne", "id": "componentpath-noextension--story-one", "importPath": "./src/componentPath/noExtension.stories.js", "name": "Story One", @@ -213,6 +217,7 @@ describe('useStoriesJson', () => { }, "componentpath-package--story-one": { "componentPath": "component-package", + "exportName": "StoryOne", "id": "componentpath-package--story-one", "importPath": "./src/componentPath/package.stories.js", "name": "Story One", @@ -237,6 +242,7 @@ describe('useStoriesJson', () => { "type": "docs", }, "d--story-one": { + "exportName": "StoryOne", "id": "d--story-one", "importPath": "./src/D.stories.jsx", "name": "Story One", @@ -303,6 +309,7 @@ describe('useStoriesJson', () => { "type": "docs", }, "example-button--story-one": { + "exportName": "StoryOne", "id": "example-button--story-one", "importPath": "./src/Button.stories.ts", "name": "Story One", @@ -315,6 +322,7 @@ describe('useStoriesJson', () => { "type": "story", }, "first-nested-deeply-f--story-one": { + "exportName": "StoryOne", "id": "first-nested-deeply-f--story-one", "importPath": "./src/first-nested/deeply/F.stories.js", "name": "Story One", @@ -326,6 +334,7 @@ describe('useStoriesJson', () => { "type": "story", }, "first-nested-deeply-features--with-csf-1": { + "exportName": "WithCSF1", "id": "first-nested-deeply-features--with-csf-1", "importPath": "./src/first-nested/deeply/Features.stories.jsx", "name": "With CSF 1", @@ -337,6 +346,7 @@ describe('useStoriesJson', () => { "type": "story", }, "first-nested-deeply-features--with-play": { + "exportName": "WithPlay", "id": "first-nested-deeply-features--with-play", "importPath": "./src/first-nested/deeply/Features.stories.jsx", "name": "With Play", @@ -349,6 +359,7 @@ describe('useStoriesJson', () => { "type": "story", }, "first-nested-deeply-features--with-render": { + "exportName": "WithRender", "id": "first-nested-deeply-features--with-render", "importPath": "./src/first-nested/deeply/Features.stories.jsx", "name": "With Render", @@ -360,6 +371,7 @@ describe('useStoriesJson', () => { "type": "story", }, "first-nested-deeply-features--with-story-fn": { + "exportName": "WithStoryFn", "id": "first-nested-deeply-features--with-story-fn", "importPath": "./src/first-nested/deeply/Features.stories.jsx", "name": "With Story Fn", @@ -371,6 +383,7 @@ describe('useStoriesJson', () => { "type": "story", }, "first-nested-deeply-features--with-test": { + "exportName": "WithTest", "id": "first-nested-deeply-features--with-test", "importPath": "./src/first-nested/deeply/Features.stories.jsx", "name": "With Test", @@ -396,6 +409,7 @@ describe('useStoriesJson', () => { "type": "docs", }, "h--story-one": { + "exportName": "StoryOne", "id": "h--story-one", "importPath": "./src/H.stories.mjs", "name": "Story One", @@ -408,6 +422,7 @@ describe('useStoriesJson', () => { "type": "story", }, "nested-button--story-one": { + "exportName": "StoryOne", "id": "nested-button--story-one", "importPath": "./src/nested/Button.stories.ts", "name": "Story One", @@ -420,6 +435,7 @@ describe('useStoriesJson', () => { "type": "story", }, "second-nested-g--story-one": { + "exportName": "StoryOne", "id": "second-nested-g--story-one", "importPath": "./src/second-nested/G.stories.ts", "name": "Story One", diff --git a/code/core/src/manager/components/preview/tools/share.tsx b/code/core/src/manager/components/preview/tools/share.tsx index 42a52a2c0d6c..0c97b09f399d 100644 --- a/code/core/src/manager/components/preview/tools/share.tsx +++ b/code/core/src/manager/components/preview/tools/share.tsx @@ -151,7 +151,7 @@ export const shareTool: Addon_BaseType = { type: types.TOOL, match: ({ viewMode, tabId }) => viewMode === 'story' && !tabId, render: () => { - const externalUrl = (STORYBOOK_NETWORK_ADDRESS as string | undefined) ?? undefined; + const externalUrl = (global.STORYBOOK_NETWORK_ADDRESS as string | undefined) ?? undefined; return ( {({ baseUrl, storyId, queryParams }) => diff --git a/code/core/src/manager/settings/defaultShortcuts.tsx b/code/core/src/manager/settings/defaultShortcuts.tsx index cefc82db1ddd..61edee89d2e6 100644 --- a/code/core/src/manager/settings/defaultShortcuts.tsx +++ b/code/core/src/manager/settings/defaultShortcuts.tsx @@ -20,4 +20,6 @@ export const defaultShortcuts: State['shortcuts'] = { collapseAll: ['ctrl', 'shift', 'ArrowUp'], expandAll: ['ctrl', 'shift', 'ArrowDown'], remount: ['alt', 'R'], + openInEditor: ['alt', 'shift', 'E'], + copyStoryName: ['alt', 'shift', 'C'], }; From 4c3209ae819dbeaaf9f7060aeeb5d5be4653c621 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Tue, 16 Sep 2025 09:12:07 +0200 Subject: [PATCH 07/44] fix story --- code/core/src/manager/components/sidebar/Tree.stories.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/code/core/src/manager/components/sidebar/Tree.stories.tsx b/code/core/src/manager/components/sidebar/Tree.stories.tsx index 3557ee29fe30..643c86e50670 100644 --- a/code/core/src/manager/components/sidebar/Tree.stories.tsx +++ b/code/core/src/manager/components/sidebar/Tree.stories.tsx @@ -47,6 +47,7 @@ const managerContext: any = { }, }) satisfies Addon_Collection ), + getData: fn().mockName('api::getData'), }, }; From c83b79c7e4d81b815705070bdcff6c375bd4f130 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Tue, 16 Sep 2025 09:39:24 +0200 Subject: [PATCH 08/44] only hide tooltip on escape key --- .../src/components/components/tooltip/WithTooltip.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/code/core/src/components/components/tooltip/WithTooltip.tsx b/code/core/src/components/components/tooltip/WithTooltip.tsx index 8276bcd5e43a..20252273e6b8 100644 --- a/code/core/src/components/components/tooltip/WithTooltip.tsx +++ b/code/core/src/components/components/tooltip/WithTooltip.tsx @@ -177,7 +177,12 @@ const WithToolTipState = ({ useEffect(() => { const hide = () => onVisibilityChange(false); - document.addEventListener('keydown', hide, false); + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === 'Escape') { + hide(); + } + }; + document.addEventListener('keydown', handleKeyDown, false); // Find all iframes on the screen and bind to clicks inside them (waiting until the iframe is ready) const iframes: HTMLIFrameElement[] = Array.from(document.getElementsByTagName('iframe')); @@ -211,7 +216,7 @@ const WithToolTipState = ({ }); return () => { - document.removeEventListener('keydown', hide); + document.removeEventListener('keydown', handleKeyDown); unbinders.forEach((unbind) => { unbind(); }); From f16eb5dcdfba85c6b46aba43a411da31caf584bd Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Tue, 16 Sep 2025 10:42:19 +0200 Subject: [PATCH 09/44] update storybook icons and use new bug icon --- code/addons/a11y/package.json | 2 +- code/addons/docs/package.json | 2 +- code/addons/jest/package.json | 2 +- code/addons/onboarding/package.json | 2 +- code/addons/pseudo-states/package.json | 2 +- code/addons/themes/package.json | 2 +- code/addons/vitest/package.json | 2 +- code/core/package.json | 2 +- .../components/preview/tools/share.tsx | 2 +- code/core/src/manager/globals/exports.ts | 1 + code/yarn.lock | 24 +++++++++---------- 11 files changed, 22 insertions(+), 21 deletions(-) diff --git a/code/addons/a11y/package.json b/code/addons/a11y/package.json index 174d9d9bfbce..9d0d871da9b8 100644 --- a/code/addons/a11y/package.json +++ b/code/addons/a11y/package.json @@ -64,7 +64,7 @@ }, "devDependencies": { "@radix-ui/react-tabs": "1.0.4", - "@storybook/icons": "1.4.0", + "@storybook/icons": "^1.5.0", "@testing-library/react": "^14.0.0", "execa": "^9.5.2", "react": "^18.2.0", diff --git a/code/addons/docs/package.json b/code/addons/docs/package.json index 2c49bcbae3f0..334d47f2800f 100644 --- a/code/addons/docs/package.json +++ b/code/addons/docs/package.json @@ -85,7 +85,7 @@ "dependencies": { "@mdx-js/react": "^3.0.0", "@storybook/csf-plugin": "workspace:*", - "@storybook/icons": "1.4.0", + "@storybook/icons": "^1.5.0", "@storybook/react-dom-shim": "workspace:*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", diff --git a/code/addons/jest/package.json b/code/addons/jest/package.json index a3db158c67e0..7a73487fa285 100644 --- a/code/addons/jest/package.json +++ b/code/addons/jest/package.json @@ -54,7 +54,7 @@ "upath": "^2.0.1" }, "devDependencies": { - "@storybook/icons": "1.4.0", + "@storybook/icons": "^1.5.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-resize-detector": "^7.1.2", diff --git a/code/addons/onboarding/package.json b/code/addons/onboarding/package.json index 37a0890f1daa..ee9caab4a014 100644 --- a/code/addons/onboarding/package.json +++ b/code/addons/onboarding/package.json @@ -52,7 +52,7 @@ }, "devDependencies": { "@neoconfetti/react": "^1.0.0", - "@storybook/icons": "1.4.0", + "@storybook/icons": "^1.5.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-joyride": "^2.8.2", diff --git a/code/addons/pseudo-states/package.json b/code/addons/pseudo-states/package.json index 7328912b9b81..858a1e63b0f2 100644 --- a/code/addons/pseudo-states/package.json +++ b/code/addons/pseudo-states/package.json @@ -55,7 +55,7 @@ "prep": "jiti ../../../scripts/build/build-package.ts" }, "devDependencies": { - "@storybook/icons": "1.4.0", + "@storybook/icons": "^1.5.0", "react": "^18.2.0", "react-dom": "^18.2.0", "typescript": "^5.8.3" diff --git a/code/addons/themes/package.json b/code/addons/themes/package.json index 8b887858975d..f1966315b19a 100644 --- a/code/addons/themes/package.json +++ b/code/addons/themes/package.json @@ -61,7 +61,7 @@ "ts-dedent": "^2.0.0" }, "devDependencies": { - "@storybook/icons": "1.4.0", + "@storybook/icons": "^1.5.0", "react": "^18.2.0", "react-dom": "^18.2.0", "typescript": "^5.8.3" diff --git a/code/addons/vitest/package.json b/code/addons/vitest/package.json index 6d458affae59..b1c91a0b0d26 100644 --- a/code/addons/vitest/package.json +++ b/code/addons/vitest/package.json @@ -73,7 +73,7 @@ }, "dependencies": { "@storybook/global": "^5.0.0", - "@storybook/icons": "1.4.0", + "@storybook/icons": "^1.5.0", "prompts": "^2.4.0", "ts-dedent": "^2.2.0" }, diff --git a/code/core/package.json b/code/core/package.json index faf419921973..1f2809044c5f 100644 --- a/code/core/package.json +++ b/code/core/package.json @@ -228,7 +228,7 @@ }, "dependencies": { "@storybook/global": "^5.0.0", - "@storybook/icons": "1.4.0", + "@storybook/icons": "^1.5.0", "@testing-library/jest-dom": "^6.6.3", "@testing-library/user-event": "^14.6.1", "@vitest/expect": "3.2.4", diff --git a/code/core/src/manager/components/preview/tools/share.tsx b/code/core/src/manager/components/preview/tools/share.tsx index 0c97b09f399d..6394edfb9cad 100644 --- a/code/core/src/manager/components/preview/tools/share.tsx +++ b/code/core/src/manager/components/preview/tools/share.tsx @@ -9,7 +9,7 @@ import { import type { Addon_BaseType } from 'storybook/internal/types'; import { global } from '@storybook/global'; -import { ShareAltIcon as BugIcon, LinkIcon } from '@storybook/icons'; +import { BugIcon, LinkIcon } from '@storybook/icons'; import copy from 'copy-to-clipboard'; // @ts-expect-error see https://github.com/rosskhanas/react-qr-code/issues/251 diff --git a/code/core/src/manager/globals/exports.ts b/code/core/src/manager/globals/exports.ts index 7387be7ae8bc..3420989288a7 100644 --- a/code/core/src/manager/globals/exports.ts +++ b/code/core/src/manager/globals/exports.ts @@ -97,6 +97,7 @@ export default { 'BranchIcon', 'BrowserIcon', 'ButtonIcon', + 'BugIcon', 'CPUIcon', 'CalendarIcon', 'CameraIcon', diff --git a/code/yarn.lock b/code/yarn.lock index e0a9a876f5e7..245eba202ab2 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -5944,7 +5944,7 @@ __metadata: dependencies: "@radix-ui/react-tabs": "npm:1.0.4" "@storybook/global": "npm:^5.0.0" - "@storybook/icons": "npm:1.4.0" + "@storybook/icons": "npm:^1.5.0" "@testing-library/react": "npm:^14.0.0" axe-core: "npm:^4.2.0" execa: "npm:^9.5.2" @@ -5986,7 +5986,7 @@ __metadata: "@mdx-js/react": "npm:^3.0.0" "@rollup/pluginutils": "npm:^5.0.2" "@storybook/csf-plugin": "workspace:*" - "@storybook/icons": "npm:1.4.0" + "@storybook/icons": "npm:^1.5.0" "@storybook/react-dom-shim": "workspace:*" "@types/color-convert": "npm:^2.0.0" "@types/react": "npm:^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" @@ -6015,7 +6015,7 @@ __metadata: version: 0.0.0-use.local resolution: "@storybook/addon-jest@workspace:addons/jest" dependencies: - "@storybook/icons": "npm:1.4.0" + "@storybook/icons": "npm:^1.5.0" react: "npm:^18.2.0" react-dom: "npm:^18.2.0" react-resize-detector: "npm:^7.1.2" @@ -6047,7 +6047,7 @@ __metadata: resolution: "@storybook/addon-onboarding@workspace:addons/onboarding" dependencies: "@neoconfetti/react": "npm:^1.0.0" - "@storybook/icons": "npm:1.4.0" + "@storybook/icons": "npm:^1.5.0" react: "npm:^18.2.0" react-dom: "npm:^18.2.0" react-joyride: "npm:^2.8.2" @@ -6061,7 +6061,7 @@ __metadata: version: 0.0.0-use.local resolution: "@storybook/addon-themes@workspace:addons/themes" dependencies: - "@storybook/icons": "npm:1.4.0" + "@storybook/icons": "npm:^1.5.0" react: "npm:^18.2.0" react-dom: "npm:^18.2.0" ts-dedent: "npm:^2.0.0" @@ -6076,7 +6076,7 @@ __metadata: resolution: "@storybook/addon-vitest@workspace:addons/vitest" dependencies: "@storybook/global": "npm:^5.0.0" - "@storybook/icons": "npm:1.4.0" + "@storybook/icons": "npm:^1.5.0" "@types/istanbul-lib-report": "npm:^3.0.3" "@types/micromatch": "npm:^4.0.0" "@types/node": "npm:^22.0.0" @@ -6407,13 +6407,13 @@ __metadata: languageName: unknown linkType: soft -"@storybook/icons@npm:1.4.0": - version: 1.4.0 - resolution: "@storybook/icons@npm:1.4.0" +"@storybook/icons@npm:^1.5.0": + version: 1.5.0 + resolution: "@storybook/icons@npm:1.5.0" peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - checksum: 10c0/fd0514fb3fa431a8b5939fe1d9fc336b253ef2c25b34792d2d4ee59e13321108d34f8bf223a0981482f54f83c5ef47ffd1a98c376ca9071011c1b8afe2b01d43 + checksum: 10c0/cc8189a7d431929ccc079438b4ad55cd606421cfcd167459d1fd60656798315b620183b7fb95f2dd36859cad08f5ec8160dc8d4fe1a91b96ba8faeee10db8cc7 languageName: node linkType: hard @@ -24452,7 +24452,7 @@ __metadata: version: 0.0.0-use.local resolution: "storybook-addon-pseudo-states@workspace:addons/pseudo-states" dependencies: - "@storybook/icons": "npm:1.4.0" + "@storybook/icons": "npm:^1.5.0" react: "npm:^18.2.0" react-dom: "npm:^18.2.0" typescript: "npm:^5.8.3" @@ -24489,7 +24489,7 @@ __metadata: "@rolldown/pluginutils": "npm:1.0.0-beta.18" "@storybook/docs-mdx": "npm:4.0.0-next.1" "@storybook/global": "npm:^5.0.0" - "@storybook/icons": "npm:1.4.0" + "@storybook/icons": "npm:^1.5.0" "@tanstack/react-virtual": "npm:^3.3.0" "@testing-library/dom": "npm:10.4.0" "@testing-library/jest-dom": "npm:^6.6.3" From f72aa770ebee69fae75b85a6d76a16b01a54d9ac Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Tue, 16 Sep 2025 10:48:46 +0200 Subject: [PATCH 10/44] fix exports --- code/core/src/manager/globals/exports.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/code/core/src/manager/globals/exports.ts b/code/core/src/manager/globals/exports.ts index 3420989288a7..7635e69dc9ce 100644 --- a/code/core/src/manager/globals/exports.ts +++ b/code/core/src/manager/globals/exports.ts @@ -96,8 +96,8 @@ export default { 'BoxIcon', 'BranchIcon', 'BrowserIcon', - 'ButtonIcon', 'BugIcon', + 'ButtonIcon', 'CPUIcon', 'CalendarIcon', 'CameraIcon', @@ -170,6 +170,7 @@ export default { 'FolderIcon', 'FormIcon', 'GDriveIcon', + 'GiftIcon', 'GithubIcon', 'GitlabIcon', 'GlobeIcon', @@ -210,6 +211,7 @@ export default { 'NutIcon', 'OutboxIcon', 'OutlineIcon', + 'PaintBrushAltIcon', 'PaintBrushIcon', 'PaperClipIcon', 'ParagraphIcon', @@ -253,6 +255,8 @@ export default { 'SidebarAltToggleIcon', 'SidebarIcon', 'SidebarToggleIcon', + 'SortDownIcon', + 'SortUpIcon', 'SpeakerIcon', 'StackedIcon', 'StarHollowIcon', @@ -308,7 +312,6 @@ export default { 'storybook/manager-api': [ 'ActiveTabs', 'Consumer', - 'openInEditor', 'ManagerContext', 'Provider', 'RequestResponseError', @@ -335,6 +338,7 @@ export default { 'keyToSymbol', 'merge', 'mockChannel', + 'openInEditor', 'optionOrAltSymbol', 'shortcutMatchesShortcut', 'shortcutToHumanString', @@ -685,7 +689,6 @@ export default { 'storybook/internal/manager-api': [ 'ActiveTabs', 'Consumer', - 'openInEditor', 'ManagerContext', 'Provider', 'RequestResponseError', @@ -712,6 +715,7 @@ export default { 'keyToSymbol', 'merge', 'mockChannel', + 'openInEditor', 'optionOrAltSymbol', 'shortcutMatchesShortcut', 'shortcutToHumanString', From 33f12f15380b2dd1535736d6bef2329334e7af0e Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Tue, 16 Sep 2025 12:20:52 +0200 Subject: [PATCH 11/44] use share icon --- code/core/src/manager/components/preview/tools/share.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/code/core/src/manager/components/preview/tools/share.tsx b/code/core/src/manager/components/preview/tools/share.tsx index 6394edfb9cad..240c729ffe67 100644 --- a/code/core/src/manager/components/preview/tools/share.tsx +++ b/code/core/src/manager/components/preview/tools/share.tsx @@ -9,7 +9,7 @@ import { import type { Addon_BaseType } from 'storybook/internal/types'; import { global } from '@storybook/global'; -import { BugIcon, LinkIcon } from '@storybook/icons'; +import { BugIcon, LinkIcon, ShareIcon } from '@storybook/icons'; import copy from 'copy-to-clipboard'; // @ts-expect-error see https://github.com/rosskhanas/react-qr-code/issues/251 @@ -18,7 +18,7 @@ import { Consumer, types } from 'storybook/manager-api'; import type { Combo } from 'storybook/manager-api'; import { styled, useTheme } from 'storybook/theming'; -const { PREVIEW_URL, document, STORYBOOK_NETWORK_ADDRESS } = global as any; +const { PREVIEW_URL, document } = global as any; const mapper = ({ state }: Combo) => { const { storyId, refId, refs } = state; @@ -151,7 +151,7 @@ export const shareTool: Addon_BaseType = { type: types.TOOL, match: ({ viewMode, tabId }) => viewMode === 'story' && !tabId, render: () => { - const externalUrl = (global.STORYBOOK_NETWORK_ADDRESS as string | undefined) ?? undefined; + const externalUrl = global.STORYBOOK_NETWORK_ADDRESS ?? undefined; return ( {({ baseUrl, storyId, queryParams }) => @@ -162,7 +162,7 @@ export const shareTool: Addon_BaseType = { tooltip={} > - + ) : null From 983e4798b5cd3a43b56e8aa5c8a9ee4708ccbd63 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Tue, 16 Sep 2025 14:56:45 +0200 Subject: [PATCH 12/44] in the interactions panel, show just story name as text on deployed/composed Storybooks --- .../component-testing/components/Subnav.tsx | 63 +++++++++---------- 1 file changed, 31 insertions(+), 32 deletions(-) diff --git a/code/core/src/component-testing/components/Subnav.tsx b/code/core/src/component-testing/components/Subnav.tsx index e4c9ed2d62df..51b7222fca00 100644 --- a/code/core/src/component-testing/components/Subnav.tsx +++ b/code/core/src/component-testing/components/Subnav.tsx @@ -20,8 +20,7 @@ import { SyncIcon, } from '@storybook/icons'; -import { Consumer, openInEditor } from 'storybook/manager-api'; -import type { Combo } from 'storybook/manager-api'; +import { openInEditor, useStorybookApi, useStorybookState } from 'storybook/manager-api'; import { styled, useTheme } from 'storybook/theming'; import { type ControlStates } from '../../instrumenter/types'; @@ -76,10 +75,10 @@ const StyledSeparator = styled(Separator)({ marginTop: 0, }); -const StyledLocation = styled(P)(({ theme }) => ({ - color: theme.color.secondary, - cursor: 'pointer', - fontWeight: theme.typography.weight.bold, +const StyledLocation = styled(P)<{ isText?: boolean }>(({ theme, isText }) => ({ + color: isText ? theme.textMutedColor : theme.color.secondary, + cursor: isText ? 'default' : 'pointer', + fontWeight: isText ? theme.typography.weight.regular : theme.typography.weight.bold, justifyContent: 'flex-end', textAlign: 'right', whiteSpace: 'nowrap', @@ -127,6 +126,11 @@ export const Subnav: React.FC = ({ }) => { const buttonText = status === 'errored' ? 'Scroll to error' : 'Scroll to end'; const theme = useTheme(); + const state = useStorybookState(); + const api = useStorybookApi(); + const data = api.getData(state.storyId, state.refId); + const importPath = data?.importPath as string | undefined; + const isLocal = !state.refId; return ( @@ -187,33 +191,28 @@ export const Subnav: React.FC = ({ - - ({ - importPath: api.getData(state.storyId, state.refId)?.importPath as - | string - | undefined, - isLocal: !state.refId, - })} - > - {({ importPath, isLocal }) => - global.CONFIG_TYPE === 'DEVELOPMENT' && isLocal && (importPath || storyFileName) ? ( - } + {(importPath || storyFileName) && ( + + {global.CONFIG_TYPE === 'DEVELOPMENT' && isLocal ? ( + } + > + { + openInEditor(importPath as string); + }} > - openInEditor((importPath || storyFileName) as string)} - > - {storyFileName} - - - ) : null - } - - + {storyFileName} + + + ) : ( + {storyFileName} + )} + + )} From 7a741619ff03c8fd8ae2be92f841dfefcd0f3cf8 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Tue, 16 Sep 2025 15:51:14 +0200 Subject: [PATCH 13/44] fix shortcuts fonts --- code/core/src/manager/components/sidebar/Search.tsx | 1 + code/core/src/manager/container/Menu.tsx | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/code/core/src/manager/components/sidebar/Search.tsx b/code/core/src/manager/components/sidebar/Search.tsx index 598b8fd7e132..a6113815b4e6 100644 --- a/code/core/src/manager/components/sidebar/Search.tsx +++ b/code/core/src/manager/components/sidebar/Search.tsx @@ -129,6 +129,7 @@ const FocusKey = styled.code(({ theme }) => ({ margin: 5, marginTop: 6, height: 16, + fontFamily: theme.typography.fonts.base, lineHeight: '16px', textAlign: 'center', fontSize: '11px', diff --git a/code/core/src/manager/container/Menu.tsx b/code/core/src/manager/container/Menu.tsx index b61dcc06c472..e51f35bb1c9b 100644 --- a/code/core/src/manager/container/Menu.tsx +++ b/code/core/src/manager/container/Menu.tsx @@ -32,13 +32,14 @@ const Key = styled.span(({ theme }) => ({ padding: '0 6px', })); -const KeyChild = styled.code({ +const KeyChild = styled.code(({ theme }) => ({ padding: 0, + fontFamily: theme.typography.fonts.base, verticalAlign: 'middle', '& + &': { marginLeft: 6, }, -}); +})); export const Shortcut: FC<{ keys: string[] }> = ({ keys }) => ( From 22e796c1ac10c3836669ac05138b529dee8ee2c7 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Tue, 16 Sep 2025 16:09:06 +0200 Subject: [PATCH 14/44] fix registering shortcuts with alt on macos --- code/core/src/manager-api/lib/shortcut.ts | 32 ++++++++++++++++++-- code/core/src/manager/settings/shortcuts.tsx | 9 ++++-- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/code/core/src/manager-api/lib/shortcut.ts b/code/core/src/manager-api/lib/shortcut.ts index 10ec6130f0ed..afc4472ff718 100644 --- a/code/core/src/manager-api/lib/shortcut.ts +++ b/code/core/src/manager-api/lib/shortcut.ts @@ -41,18 +41,44 @@ export const eventToShortcut = (e: KeyboardEventLike): (string | string[])[] | n keys.push('shift'); } + // Derive a key from the physical code (letter/digit/punctuation) when needed + const codeUpper = e.code?.toUpperCase(); + const codeToCharMap: Record = { + MINUS: '-', + EQUAL: '=', + BRACKETLEFT: '[', + BRACKETRIGHT: ']', + BACKSLASH: '\\', + SEMICOLON: ';', + QUOTE: "'", + BACKQUOTE: '`', + COMMA: ',', + PERIOD: '.', + SLASH: '/', + }; + const codeChar = codeUpper + ? codeUpper.startsWith('KEY') && codeUpper.length === 4 + ? codeUpper.replace('KEY', '') + : codeUpper.startsWith('DIGIT') + ? codeUpper.replace('DIGIT', '') + : codeToCharMap[codeUpper] + : undefined; + if (e.key && e.key.length === 1 && e.key !== ' ') { const key = e.key.toUpperCase(); - // Using `event.code` to support `alt (option) + ` on macos which returns special characters + // Using `event.code` to support `alt (option) + ` on macOS which returns special characters // See full list of event.code here: // https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_code_values - const code = e.code?.toUpperCase().replace('KEY', '').replace('DIGIT', ''); + const code = codeChar; if (code && code.length === 1 && code !== key) { keys.push([key, code]); } else { keys.push(key); } + } else if (e.key === 'Dead' && codeChar) { + // Handle dead keys (e.g., Option+E on macOS) by using the physical key from code + keys.push(codeChar); } if (e.key === ' ') { keys.push('space'); @@ -139,7 +165,7 @@ export const keyToSymbol = (key: string): string => { if (key === 'ArrowRight') { return '→'; } - return key.toUpperCase(); + return key?.toUpperCase(); }; // Display the shortcut as a human readable string diff --git a/code/core/src/manager/settings/shortcuts.tsx b/code/core/src/manager/settings/shortcuts.tsx index 43c5dbc55050..645459243293 100644 --- a/code/core/src/manager/settings/shortcuts.tsx +++ b/code/core/src/manager/settings/shortcuts.tsx @@ -196,16 +196,21 @@ class ShortcutsScreen extends Component 'O') + const normalizedShortcut = shortcut.map((key) => + Array.isArray(key) ? key[key.length - 1] : key + ); + // Check we don't match any other shortcuts const error = !!Object.entries(shortcutKeys).find( ([feature, { shortcut: existingShortcut }]) => feature !== activeFeature && existingShortcut && - shortcutMatchesShortcut(shortcut, existingShortcut) + shortcutMatchesShortcut(normalizedShortcut, existingShortcut) ); return this.setState({ - shortcutKeys: { ...shortcutKeys, [activeFeature]: { shortcut, error } }, + shortcutKeys: { ...shortcutKeys, [activeFeature]: { shortcut: normalizedShortcut, error } }, }); }; From cceb6276579d0cd1fdf052e39dcad84d4b919e98 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Tue, 16 Sep 2025 16:12:15 +0200 Subject: [PATCH 15/44] fix tests --- .../manager/components/preview/tools/share.stories.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/code/core/src/manager/components/preview/tools/share.stories.tsx b/code/core/src/manager/components/preview/tools/share.stories.tsx index 6b9c24c6e7c2..cc363dfb190a 100644 --- a/code/core/src/manager/components/preview/tools/share.stories.tsx +++ b/code/core/src/manager/components/preview/tools/share.stories.tsx @@ -5,7 +5,7 @@ import { global } from '@storybook/global'; import type { StoryObj } from '@storybook/react-vite'; import { ManagerContext } from 'storybook/manager-api'; -import { expect, screen } from 'storybook/test'; +import { expect, screen, waitFor } from 'storybook/test'; import { shareTool } from './share'; @@ -43,7 +43,9 @@ export const Default: Story = { global.STORYBOOK_NETWORK_ADDRESS = 'http://127.0.0.1:6006'; }, play: async ({ userEvent, canvas }) => { - await userEvent.click(canvas.getByRole('button')); - await expect(await screen.findByText('Scan me')).toBeVisible(); + await waitFor(async () => { + await userEvent.click(canvas.getByRole('button')); + await expect(await screen.findByText('Scan me')).toBeVisible(); + }); }, }; From 4ce4bfd1e6ce68d30bdddf82d0d759d29c24eb20 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Tue, 16 Sep 2025 17:54:23 +0200 Subject: [PATCH 16/44] cleanup and add copyStoryLink shortcut --- .../core/src/manager-api/modules/shortcuts.ts | 6 ++ .../manager/components/preview/Toolbar.tsx | 13 ----- .../manager/components/preview/tools/copy.tsx | 55 ------------------- .../components/preview/tools/eject.tsx | 51 ----------------- .../preview/tools/share.stories.tsx | 2 +- .../components/preview/tools/share.tsx | 16 +++--- code/core/src/manager/settings/shortcuts.tsx | 1 + 7 files changed, 17 insertions(+), 127 deletions(-) delete mode 100644 code/core/src/manager/components/preview/tools/copy.tsx delete mode 100644 code/core/src/manager/components/preview/tools/eject.tsx diff --git a/code/core/src/manager-api/modules/shortcuts.ts b/code/core/src/manager-api/modules/shortcuts.ts index 256048f40999..42018eec3803 100644 --- a/code/core/src/manager-api/modules/shortcuts.ts +++ b/code/core/src/manager-api/modules/shortcuts.ts @@ -115,6 +115,7 @@ export interface API_Shortcuts { remount: API_KeyCollection; openInEditor: API_KeyCollection; copyStoryName: API_KeyCollection; + copyStoryLink: API_KeyCollection; } export type API_Action = keyof API_Shortcuts; @@ -152,6 +153,7 @@ export const defaultShortcuts: API_Shortcuts = Object.freeze({ remount: ['alt', 'R'], openInEditor: ['alt', 'shift', 'E'], copyStoryName: ['alt', 'shift', 'C'], + copyStoryLink: ['alt', 'shift', 'L'], }); const addonsShortcuts: API_AddonShortcuts = {}; @@ -399,6 +401,10 @@ export const init: ModuleFn = ({ store, fullAPI, provider }) => { } break; } + case 'copyStoryLink': { + copy(window.location.href); + break; + } default: addonsShortcuts[feature].action(); break; diff --git a/code/core/src/manager/components/preview/Toolbar.tsx b/code/core/src/manager/components/preview/Toolbar.tsx index 2875b370edbc..028691c8a936 100644 --- a/code/core/src/manager/components/preview/Toolbar.tsx +++ b/code/core/src/manager/components/preview/Toolbar.tsx @@ -19,11 +19,6 @@ import { import { styled } from 'storybook/theming'; import { useLayout } from '../layout/LayoutProvider'; -import { addonsTool } from './tools/addons'; -import { copyTool } from './tools/copy'; -import { ejectTool } from './tools/eject'; -import { remountTool } from './tools/remount'; -import { zoomTool } from './tools/zoom'; import type { PreviewProps } from './utils/types'; export const getTools = (getFn: API['getElements']) => Object.values(getFn(types.TOOL)); @@ -112,14 +107,6 @@ export const createTabsTool = (tabs: Addon_BaseType[]): Addon_BaseType => ({ ), }); -export const defaultTools: Addon_BaseType[] = [remountTool, zoomTool]; -export const defaultToolsExtra: Addon_BaseType[] = [ - addonsTool, - fullScreenTool, - ejectTool, - copyTool, -]; - export interface ToolData { isShown: boolean; tabs: Addon_BaseType[]; diff --git a/code/core/src/manager/components/preview/tools/copy.tsx b/code/core/src/manager/components/preview/tools/copy.tsx deleted file mode 100644 index 9be9b1d35ccd..000000000000 --- a/code/core/src/manager/components/preview/tools/copy.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import React from 'react'; - -import { IconButton, getStoryHref } from 'storybook/internal/components'; -import type { Addon_BaseType } from 'storybook/internal/types'; - -import { global } from '@storybook/global'; -import { LinkIcon } from '@storybook/icons'; - -import copy from 'copy-to-clipboard'; -import { Consumer, types } from 'storybook/manager-api'; -import type { Combo } from 'storybook/manager-api'; - -const { PREVIEW_URL, document } = global; - -const copyMapper = ({ state }: Combo) => { - const { storyId, refId, refs } = state; - const { location } = document; - // @ts-expect-error (non strict) - const ref = refs[refId]; - let baseUrl = `${location.origin}${location.pathname}`; - - if (!baseUrl.endsWith('/')) { - baseUrl += '/'; - } - - return { - refId, - baseUrl: ref ? `${ref.url}/iframe.html` : (PREVIEW_URL as string) || `${baseUrl}iframe.html`, - storyId, - queryParams: state.customQueryParams, - }; -}; - -export const copyTool: Addon_BaseType = { - title: 'copy', - id: 'copy', - type: types.TOOL, - match: ({ viewMode, tabId }) => viewMode === 'story' && !tabId, - render: () => ( - - {({ baseUrl, storyId, queryParams }) => - storyId ? ( - copy(getStoryHref(baseUrl, storyId, queryParams))} - title="Copy canvas link" - > - - - ) : null - } - - ), -}; diff --git a/code/core/src/manager/components/preview/tools/eject.tsx b/code/core/src/manager/components/preview/tools/eject.tsx deleted file mode 100644 index a6857c778c42..000000000000 --- a/code/core/src/manager/components/preview/tools/eject.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import React from 'react'; - -import { IconButton, getStoryHref } from 'storybook/internal/components'; -import type { Addon_BaseType } from 'storybook/internal/types'; - -import { global } from '@storybook/global'; -import { ShareAltIcon } from '@storybook/icons'; - -import { Consumer, types } from 'storybook/manager-api'; -import type { Combo } from 'storybook/manager-api'; - -const { PREVIEW_URL } = global; - -const ejectMapper = ({ state }: Combo) => { - const { storyId, refId, refs } = state; - // @ts-expect-error (non strict) - const ref = refs[refId]; - - return { - refId, - baseUrl: ref ? `${ref.url}/iframe.html` : (PREVIEW_URL as string) || 'iframe.html', - storyId, - queryParams: state.customQueryParams, - }; -}; - -export const ejectTool: Addon_BaseType = { - title: 'eject', - id: 'eject', - type: types.TOOL, - match: ({ viewMode, tabId }) => viewMode === 'story' && !tabId, - render: () => ( - - {({ baseUrl, storyId, queryParams }) => - storyId ? ( - - - - - - ) : null - } - - ), -}; diff --git a/code/core/src/manager/components/preview/tools/share.stories.tsx b/code/core/src/manager/components/preview/tools/share.stories.tsx index cc363dfb190a..fa7394a6da93 100644 --- a/code/core/src/manager/components/preview/tools/share.stories.tsx +++ b/code/core/src/manager/components/preview/tools/share.stories.tsx @@ -17,7 +17,7 @@ const managerContext = { customQueryParams: {}, }, api: { - getShortcutKeys: () => ({ copyStoryLink: ['meta', 'shift', 'c'] }), + getShortcutKeys: () => ({ copyStoryLink: ['alt', 'shift', 'k'] }), }, } as any; diff --git a/code/core/src/manager/components/preview/tools/share.tsx b/code/core/src/manager/components/preview/tools/share.tsx index 240c729ffe67..ff2121ae4ae6 100644 --- a/code/core/src/manager/components/preview/tools/share.tsx +++ b/code/core/src/manager/components/preview/tools/share.tsx @@ -14,10 +14,12 @@ import { BugIcon, LinkIcon, ShareIcon } from '@storybook/icons'; import copy from 'copy-to-clipboard'; // @ts-expect-error see https://github.com/rosskhanas/react-qr-code/issues/251 import { QRCode } from 'react-qr-code'; -import { Consumer, types } from 'storybook/manager-api'; +import { Consumer, types, useStorybookApi } from 'storybook/manager-api'; import type { Combo } from 'storybook/manager-api'; import { styled, useTheme } from 'storybook/theming'; +import { Shortcut } from '../../../container/Menu'; + const { PREVIEW_URL, document } = global as any; const mapper = ({ state }: Combo) => { @@ -89,9 +91,9 @@ function ShareMenu({ queryParams: Record; qrUrl?: string; }) { - // const api = useStorybookApi(); - // const shortcutKeys = api.getShortcutKeys(); - // const enableShortcuts = !!shortcutKeys; + const api = useStorybookApi(); + const shortcutKeys = api.getShortcutKeys(); + const enableShortcuts = !!shortcutKeys; const [copied, setCopied] = useState(false); const links = useMemo(() => { @@ -102,9 +104,9 @@ function ShareMenu({ id: 'copy-link', title: copyTitle, icon: , - // right: enableShortcuts ? : null, + right: enableShortcuts ? : null, onClick: () => { - copy(getStoryHref(baseUrl, storyId, queryParams)); + copy(window.location.href); setCopied(true); setTimeout(() => setCopied(false), 2000); }, @@ -140,7 +142,7 @@ function ShareMenu({ } return baseLinks; - }, [baseUrl, storyId, queryParams, copied, qrUrl]); + }, [baseUrl, storyId, queryParams, copied, qrUrl, enableShortcuts, shortcutKeys.copyStoryLink]); return ; } diff --git a/code/core/src/manager/settings/shortcuts.tsx b/code/core/src/manager/settings/shortcuts.tsx index 645459243293..2a8b2d77556c 100644 --- a/code/core/src/manager/settings/shortcuts.tsx +++ b/code/core/src/manager/settings/shortcuts.tsx @@ -134,6 +134,7 @@ const shortcutLabels = { remount: 'Remount component', openInEditor: 'Open story in editor', copyStoryName: 'Copy story name to clipboard', + copyStoryLink: 'Copy story link to clipboard', }; export type Feature = keyof typeof shortcutLabels; From 03ddcf977967499ed9de4d5a5e271010e1f10e1a Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Tue, 16 Sep 2025 18:03:36 +0200 Subject: [PATCH 17/44] temporarily disable copy story name shortcut --- code/core/src/manager-api/modules/shortcuts.ts | 3 ++- code/core/src/manager/components/sidebar/ContextMenu.tsx | 6 +++++- code/core/src/manager/settings/defaultShortcuts.tsx | 3 ++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/code/core/src/manager-api/modules/shortcuts.ts b/code/core/src/manager-api/modules/shortcuts.ts index 42018eec3803..cc133bad7e9e 100644 --- a/code/core/src/manager-api/modules/shortcuts.ts +++ b/code/core/src/manager-api/modules/shortcuts.ts @@ -152,8 +152,9 @@ export const defaultShortcuts: API_Shortcuts = Object.freeze({ expandAll: [controlOrMetaKey(), 'shift', 'ArrowDown'], remount: ['alt', 'R'], openInEditor: ['alt', 'shift', 'E'], - copyStoryName: ['alt', 'shift', 'C'], copyStoryLink: ['alt', 'shift', 'L'], + // TODO: bring this back once we want to add shortcuts for this + // copyStoryName: ['alt', 'shift', 'C'], }); const addonsShortcuts: API_AddonShortcuts = {}; diff --git a/code/core/src/manager/components/sidebar/ContextMenu.tsx b/code/core/src/manager/components/sidebar/ContextMenu.tsx index 516dbfc717a8..8f5dd07841fb 100644 --- a/code/core/src/manager/components/sidebar/ContextMenu.tsx +++ b/code/core/src/manager/components/sidebar/ContextMenu.tsx @@ -148,7 +148,11 @@ const LiveContextMenu: FC<{ context: API_HashEntry } & ComponentProps, - right: enableShortcuts ? : null, + // TODO: bring this back once we want to add shortcuts for this + // right: + // enableShortcuts && shortcutKeys.copyStoryName ? ( + // + // ) : null, onClick: () => { if (storyName) { copy(String(storyName)); diff --git a/code/core/src/manager/settings/defaultShortcuts.tsx b/code/core/src/manager/settings/defaultShortcuts.tsx index 61edee89d2e6..4a6e3d651078 100644 --- a/code/core/src/manager/settings/defaultShortcuts.tsx +++ b/code/core/src/manager/settings/defaultShortcuts.tsx @@ -21,5 +21,6 @@ export const defaultShortcuts: State['shortcuts'] = { expandAll: ['ctrl', 'shift', 'ArrowDown'], remount: ['alt', 'R'], openInEditor: ['alt', 'shift', 'E'], - copyStoryName: ['alt', 'shift', 'C'], + // TODO: bring this back once we want to add shortcuts for this + // copyStoryName: ['alt', 'shift', 'C'], }; From 1af1dbb077ec53653c4efb07cb0adce72f4b2e70 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Tue, 16 Sep 2025 18:04:57 +0200 Subject: [PATCH 18/44] small rename on measure addon shortcut --- code/core/src/measure/Tool.tsx | 2 +- docs/_snippets/storybook-addon-toolkit-types.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/code/core/src/measure/Tool.tsx b/code/core/src/measure/Tool.tsx index 8cebffa01a3e..e259cf5334e0 100644 --- a/code/core/src/measure/Tool.tsx +++ b/code/core/src/measure/Tool.tsx @@ -23,7 +23,7 @@ export const Tool = () => { useEffect(() => { api.setAddonShortcut(ADDON_ID, { - label: 'Toggle Measure [M]', + label: 'Toggle Measure', defaultShortcut: ['M'], actionName: 'measure', showInMenu: false, diff --git a/docs/_snippets/storybook-addon-toolkit-types.md b/docs/_snippets/storybook-addon-toolkit-types.md index 067ca2e03a4d..2f8547ce146c 100644 --- a/docs/_snippets/storybook-addon-toolkit-types.md +++ b/docs/_snippets/storybook-addon-toolkit-types.md @@ -21,8 +21,8 @@ export const Tool = memo(function MyAddonSelector() { useEffect(() => { api.setAddonShortcut(ADDON_ID, { - label: 'Toggle Measure [O]', - defaultShortcut: ['O'], + label: 'Toggle Outline', + defaultShortcut: ['alt', 'O'], actionName: 'outline', showInMenu: false, action: toggleMyTool, From 003663567f7679118d3e40f71f3c52adf19f5ad2 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Tue, 16 Sep 2025 18:31:55 +0200 Subject: [PATCH 19/44] fix qr url --- code/core/src/manager/components/preview/tools/share.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/code/core/src/manager/components/preview/tools/share.tsx b/code/core/src/manager/components/preview/tools/share.tsx index ff2121ae4ae6..176f539c1e88 100644 --- a/code/core/src/manager/components/preview/tools/share.tsx +++ b/code/core/src/manager/components/preview/tools/share.tsx @@ -153,7 +153,8 @@ export const shareTool: Addon_BaseType = { type: types.TOOL, match: ({ viewMode, tabId }) => viewMode === 'story' && !tabId, render: () => { - const externalUrl = global.STORYBOOK_NETWORK_ADDRESS ?? undefined; + const externalBaseUrl = global.STORYBOOK_NETWORK_ADDRESS ?? ''; + const storyUrl = `${externalBaseUrl}${window.location.search}`; return ( {({ baseUrl, storyId, queryParams }) => @@ -161,7 +162,7 @@ export const shareTool: Addon_BaseType = { } + tooltip={} > From f252cdb0ab9d295cd17fc0ee8fa1ddfd3b3f90f1 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Tue, 16 Sep 2025 18:36:06 +0200 Subject: [PATCH 20/44] small refactor --- code/core/src/builder-manager/index.ts | 2 -- code/core/src/core-server/dev-server.ts | 4 +--- code/core/src/types/modules/core-common.ts | 2 -- 3 files changed, 1 insertion(+), 7 deletions(-) diff --git a/code/core/src/builder-manager/index.ts b/code/core/src/builder-manager/index.ts index b0c4d96aac6a..3b24655f254e 100644 --- a/code/core/src/builder-manager/index.ts +++ b/code/core/src/builder-manager/index.ts @@ -200,8 +200,6 @@ const starter: StarterFunction = async function* starterGeneratorFn({ // Build additional global values const globals: Record = await buildFrameworkGlobalsFromOptions(options); - // Surface server addresses to the manager for UI usage (e.g., share tool) - globals.STORYBOOK_ADDRESS = options.address; globals.STORYBOOK_NETWORK_ADDRESS = options.networkAddress; yield; diff --git a/code/core/src/core-server/dev-server.ts b/code/core/src/core-server/dev-server.ts index 066edfcb04f0..ad726f0d173b 100644 --- a/code/core/src/core-server/dev-server.ts +++ b/code/core/src/core-server/dev-server.ts @@ -55,9 +55,7 @@ export async function storybookDevServer(options: Options) { const proto = options.https ? 'https' : 'http'; const { address, networkAddress } = getServerAddresses(port, host, proto, initialPath); - // Expose addresses on options for the manager builder to surface in globals - // This is safe because options is only used within the current dev server lifecycle - options.address = address; + // Expose addresses on options for the manager builder to surface in globals, important for QR code link sharing options.networkAddress = networkAddress; if (!core?.builder) { diff --git a/code/core/src/types/modules/core-common.ts b/code/core/src/types/modules/core-common.ts index aeb5c6cf0da1..b3032b07c91d 100644 --- a/code/core/src/types/modules/core-common.ts +++ b/code/core/src/types/modules/core-common.ts @@ -204,8 +204,6 @@ export interface BuilderOptions { versionCheck?: VersionCheck; disableWebpackDefaults?: boolean; serverChannelUrl?: string; - /** Server addresses computed at runtime. Available in development to surface in the manager UI. */ - address?: string; networkAddress?: string; } From 383fe6f843f61859818b47a28ec222ea2c090d56 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Tue, 16 Sep 2025 18:37:02 +0200 Subject: [PATCH 21/44] fix types --- code/core/src/manager-api/modules/shortcuts.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/code/core/src/manager-api/modules/shortcuts.ts b/code/core/src/manager-api/modules/shortcuts.ts index cc133bad7e9e..479237fd7664 100644 --- a/code/core/src/manager-api/modules/shortcuts.ts +++ b/code/core/src/manager-api/modules/shortcuts.ts @@ -114,8 +114,9 @@ export interface API_Shortcuts { expandAll: API_KeyCollection; remount: API_KeyCollection; openInEditor: API_KeyCollection; - copyStoryName: API_KeyCollection; copyStoryLink: API_KeyCollection; + // TODO: bring this back once we want to add shortcuts for this + // copyStoryName: API_KeyCollection; } export type API_Action = keyof API_Shortcuts; From f4bb5fa83d21372126aa31bf67d9534ecd18f11e Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Tue, 16 Sep 2025 18:48:18 +0200 Subject: [PATCH 22/44] fix types --- code/core/src/manager-api/modules/shortcuts.ts | 15 ++++++++------- code/core/src/manager/settings/shortcuts.tsx | 3 ++- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/code/core/src/manager-api/modules/shortcuts.ts b/code/core/src/manager-api/modules/shortcuts.ts index 479237fd7664..4242a9c93f40 100644 --- a/code/core/src/manager-api/modules/shortcuts.ts +++ b/code/core/src/manager-api/modules/shortcuts.ts @@ -396,13 +396,14 @@ export const init: ModuleFn = ({ store, fullAPI, provider }) => { } break; } - case 'copyStoryName': { - const storyData = fullAPI.getCurrentStoryData(); - if (storyData.type === 'story') { - copy(storyData.exportName); - } - break; - } + // TODO: bring this back once we want to add shortcuts for this + // case 'copyStoryName': { + // const storyData = fullAPI.getCurrentStoryData(); + // if (storyData.type === 'story') { + // copy(storyData.exportName); + // } + // break; + // } case 'copyStoryLink': { copy(window.location.href); break; diff --git a/code/core/src/manager/settings/shortcuts.tsx b/code/core/src/manager/settings/shortcuts.tsx index 2a8b2d77556c..d057d814ff83 100644 --- a/code/core/src/manager/settings/shortcuts.tsx +++ b/code/core/src/manager/settings/shortcuts.tsx @@ -133,8 +133,9 @@ const shortcutLabels = { expandAll: 'Expand all items on sidebar', remount: 'Remount component', openInEditor: 'Open story in editor', - copyStoryName: 'Copy story name to clipboard', copyStoryLink: 'Copy story link to clipboard', + // TODO: bring this back once we want to add shortcuts for this + // copyStoryName: 'Copy story name to clipboard', }; export type Feature = keyof typeof shortcutLabels; From 6636c1dc076e196a3555dbb2d4d8984575738fad Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Tue, 16 Sep 2025 18:57:52 +0200 Subject: [PATCH 23/44] make qrcode fallback to current url --- code/core/src/manager/components/preview/tools/share.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/core/src/manager/components/preview/tools/share.tsx b/code/core/src/manager/components/preview/tools/share.tsx index 176f539c1e88..65f160a0dd40 100644 --- a/code/core/src/manager/components/preview/tools/share.tsx +++ b/code/core/src/manager/components/preview/tools/share.tsx @@ -153,7 +153,7 @@ export const shareTool: Addon_BaseType = { type: types.TOOL, match: ({ viewMode, tabId }) => viewMode === 'story' && !tabId, render: () => { - const externalBaseUrl = global.STORYBOOK_NETWORK_ADDRESS ?? ''; + const externalBaseUrl = global.STORYBOOK_NETWORK_ADDRESS ?? window.location.href; const storyUrl = `${externalBaseUrl}${window.location.search}`; return ( From 809d2201b4cdafa035c0d2a1bf08273ffa2edf89 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Tue, 16 Sep 2025 19:17:01 +0200 Subject: [PATCH 24/44] add copy story link default shortcut --- code/core/src/manager/settings/defaultShortcuts.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/code/core/src/manager/settings/defaultShortcuts.tsx b/code/core/src/manager/settings/defaultShortcuts.tsx index 4a6e3d651078..2c7fa19e6d2e 100644 --- a/code/core/src/manager/settings/defaultShortcuts.tsx +++ b/code/core/src/manager/settings/defaultShortcuts.tsx @@ -21,6 +21,7 @@ export const defaultShortcuts: State['shortcuts'] = { expandAll: ['ctrl', 'shift', 'ArrowDown'], remount: ['alt', 'R'], openInEditor: ['alt', 'shift', 'E'], + copyStoryLink: ['alt', 'shift', 'L'], // TODO: bring this back once we want to add shortcuts for this // copyStoryName: ['alt', 'shift', 'C'], }; From 45daed4378dfc3937dc53daeb6dc1c32ef4ff29e Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Tue, 16 Sep 2025 19:25:27 +0200 Subject: [PATCH 25/44] skip vitest test --- code/core/src/manager/components/preview/tools/share.stories.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/code/core/src/manager/components/preview/tools/share.stories.tsx b/code/core/src/manager/components/preview/tools/share.stories.tsx index fa7394a6da93..7884890e1a29 100644 --- a/code/core/src/manager/components/preview/tools/share.stories.tsx +++ b/code/core/src/manager/components/preview/tools/share.stories.tsx @@ -32,6 +32,7 @@ const meta = { render: shareTool.render, decorators: [ManagerDecorator], parameters: { layout: 'centered' }, + tags: ['!vitest'], }; export default meta; From 632561d6656e7c494df44a838091abe60c060127 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Tue, 16 Sep 2025 20:08:45 +0200 Subject: [PATCH 26/44] fix qr code url calculation --- .../src/manager/components/preview/tools/share.tsx | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/code/core/src/manager/components/preview/tools/share.tsx b/code/core/src/manager/components/preview/tools/share.tsx index 65f160a0dd40..b0738d888e16 100644 --- a/code/core/src/manager/components/preview/tools/share.tsx +++ b/code/core/src/manager/components/preview/tools/share.tsx @@ -153,12 +153,14 @@ export const shareTool: Addon_BaseType = { type: types.TOOL, match: ({ viewMode, tabId }) => viewMode === 'story' && !tabId, render: () => { - const externalBaseUrl = global.STORYBOOK_NETWORK_ADDRESS ?? window.location.href; - const storyUrl = `${externalBaseUrl}${window.location.search}`; return ( - {({ baseUrl, storyId, queryParams }) => - storyId ? ( + {({ baseUrl, storyId, queryParams }) => { + const storyUrl = global.STORYBOOK_NETWORK_ADDRESS + ? `${global.STORYBOOK_NETWORK_ADDRESS}${window.location.search}` + : window.location.href; + + return storyId ? ( - ) : null - } + ) : null; + }} ); }, From 5be73b14131326835e0ecbe49bd0e37140ef41c9 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Wed, 17 Sep 2025 09:53:44 +0200 Subject: [PATCH 27/44] update icons and use the new editor icon --- code/addons/a11y/package.json | 2 +- code/addons/docs/package.json | 2 +- code/addons/jest/package.json | 2 +- code/addons/onboarding/package.json | 2 +- code/addons/pseudo-states/package.json | 2 +- code/addons/themes/package.json | 2 +- code/addons/vitest/package.json | 2 +- code/core/package.json | 2 +- .../preview/tools/open-in-editor.tsx | 4 ++-- .../components/sidebar/ContextMenu.tsx | 4 ++-- code/core/src/manager/globals/exports.ts | 1 + code/yarn.lock | 24 +++++++++---------- 12 files changed, 25 insertions(+), 24 deletions(-) diff --git a/code/addons/a11y/package.json b/code/addons/a11y/package.json index 50dbe7f1c3d9..d56b29290e59 100644 --- a/code/addons/a11y/package.json +++ b/code/addons/a11y/package.json @@ -64,7 +64,7 @@ }, "devDependencies": { "@radix-ui/react-tabs": "1.0.4", - "@storybook/icons": "^1.5.0", + "@storybook/icons": "^1.6.0", "@testing-library/react": "^14.0.0", "execa": "^9.5.2", "react": "^18.2.0", diff --git a/code/addons/docs/package.json b/code/addons/docs/package.json index 7b1f79327876..2324b92306ac 100644 --- a/code/addons/docs/package.json +++ b/code/addons/docs/package.json @@ -85,7 +85,7 @@ "dependencies": { "@mdx-js/react": "^3.0.0", "@storybook/csf-plugin": "workspace:*", - "@storybook/icons": "^1.5.0", + "@storybook/icons": "^1.6.0", "@storybook/react-dom-shim": "workspace:*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", diff --git a/code/addons/jest/package.json b/code/addons/jest/package.json index 520de0cd12a9..7858fdb5e845 100644 --- a/code/addons/jest/package.json +++ b/code/addons/jest/package.json @@ -54,7 +54,7 @@ "upath": "^2.0.1" }, "devDependencies": { - "@storybook/icons": "^1.5.0", + "@storybook/icons": "^1.6.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-resize-detector": "^7.1.2", diff --git a/code/addons/onboarding/package.json b/code/addons/onboarding/package.json index 0c2b64c8ea85..6f256fcf9f12 100644 --- a/code/addons/onboarding/package.json +++ b/code/addons/onboarding/package.json @@ -52,7 +52,7 @@ }, "devDependencies": { "@neoconfetti/react": "^1.0.0", - "@storybook/icons": "^1.5.0", + "@storybook/icons": "^1.6.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-joyride": "^2.8.2", diff --git a/code/addons/pseudo-states/package.json b/code/addons/pseudo-states/package.json index b4ed58881a57..91b036471279 100644 --- a/code/addons/pseudo-states/package.json +++ b/code/addons/pseudo-states/package.json @@ -55,7 +55,7 @@ "prep": "jiti ../../../scripts/build/build-package.ts" }, "devDependencies": { - "@storybook/icons": "^1.5.0", + "@storybook/icons": "^1.6.0", "react": "^18.2.0", "react-dom": "^18.2.0", "typescript": "^5.8.3" diff --git a/code/addons/themes/package.json b/code/addons/themes/package.json index ace0545ee0aa..b66763152772 100644 --- a/code/addons/themes/package.json +++ b/code/addons/themes/package.json @@ -61,7 +61,7 @@ "ts-dedent": "^2.0.0" }, "devDependencies": { - "@storybook/icons": "^1.5.0", + "@storybook/icons": "^1.6.0", "react": "^18.2.0", "react-dom": "^18.2.0", "typescript": "^5.8.3" diff --git a/code/addons/vitest/package.json b/code/addons/vitest/package.json index 8c49fedd69e8..bd1e95381645 100644 --- a/code/addons/vitest/package.json +++ b/code/addons/vitest/package.json @@ -73,7 +73,7 @@ }, "dependencies": { "@storybook/global": "^5.0.0", - "@storybook/icons": "^1.5.0", + "@storybook/icons": "^1.6.0", "prompts": "^2.4.0", "ts-dedent": "^2.2.0" }, diff --git a/code/core/package.json b/code/core/package.json index 16d4944fe109..eeb969dc0cfa 100644 --- a/code/core/package.json +++ b/code/core/package.json @@ -228,7 +228,7 @@ }, "dependencies": { "@storybook/global": "^5.0.0", - "@storybook/icons": "^1.5.0", + "@storybook/icons": "^1.6.0", "@testing-library/jest-dom": "^6.6.3", "@testing-library/user-event": "^14.6.1", "@vitest/expect": "3.2.4", diff --git a/code/core/src/manager/components/preview/tools/open-in-editor.tsx b/code/core/src/manager/components/preview/tools/open-in-editor.tsx index b8e9f51d14f4..46e8458a7003 100644 --- a/code/core/src/manager/components/preview/tools/open-in-editor.tsx +++ b/code/core/src/manager/components/preview/tools/open-in-editor.tsx @@ -4,7 +4,7 @@ import { IconButton } from 'storybook/internal/components'; import type { Addon_BaseType } from 'storybook/internal/types'; import { global } from '@storybook/global'; -import { MarkupIcon } from '@storybook/icons'; +import { EditorIcon } from '@storybook/icons'; import { Consumer, openInEditor, types } from 'storybook/manager-api'; import type { Combo } from 'storybook/manager-api'; @@ -41,7 +41,7 @@ export const openInEditorTool: Addon_BaseType = { title="Open in editor" aria-label="Open in editor" > - + ); }} diff --git a/code/core/src/manager/components/sidebar/ContextMenu.tsx b/code/core/src/manager/components/sidebar/ContextMenu.tsx index 8f5dd07841fb..3d74346e1e29 100644 --- a/code/core/src/manager/components/sidebar/ContextMenu.tsx +++ b/code/core/src/manager/components/sidebar/ContextMenu.tsx @@ -10,7 +10,7 @@ import { } from 'storybook/internal/types'; import { global } from '@storybook/global'; -import { CopyIcon, EllipsisIcon, MarkupIcon } from '@storybook/icons'; +import { CopyIcon, EditorIcon, EllipsisIcon } from '@storybook/icons'; import copy from 'copy-to-clipboard'; import { openInEditor, useStorybookApi } from 'storybook/manager-api'; @@ -133,7 +133,7 @@ const LiveContextMenu: FC<{ context: API_HashEntry } & ComponentProps, + icon: , right: enableShortcuts ? : null, onClick: (e) => { e.preventDefault(); diff --git a/code/core/src/manager/globals/exports.ts b/code/core/src/manager/globals/exports.ts index 7635e69dc9ce..ee431d430161 100644 --- a/code/core/src/manager/globals/exports.ts +++ b/code/core/src/manager/globals/exports.ts @@ -152,6 +152,7 @@ export default { 'DownloadIcon', 'DragIcon', 'EditIcon', + 'EditorIcon', 'EllipsisIcon', 'EmailIcon', 'ExpandAltIcon', diff --git a/code/yarn.lock b/code/yarn.lock index 2af9c5a96207..7bdd10c14337 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -6304,7 +6304,7 @@ __metadata: dependencies: "@radix-ui/react-tabs": "npm:1.0.4" "@storybook/global": "npm:^5.0.0" - "@storybook/icons": "npm:^1.5.0" + "@storybook/icons": "npm:^1.6.0" "@testing-library/react": "npm:^14.0.0" axe-core: "npm:^4.2.0" execa: "npm:^9.5.2" @@ -6346,7 +6346,7 @@ __metadata: "@mdx-js/react": "npm:^3.0.0" "@rollup/pluginutils": "npm:^5.0.2" "@storybook/csf-plugin": "workspace:*" - "@storybook/icons": "npm:^1.5.0" + "@storybook/icons": "npm:^1.6.0" "@storybook/react-dom-shim": "workspace:*" "@types/color-convert": "npm:^2.0.0" "@types/react": "npm:^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" @@ -6375,7 +6375,7 @@ __metadata: version: 0.0.0-use.local resolution: "@storybook/addon-jest@workspace:addons/jest" dependencies: - "@storybook/icons": "npm:^1.5.0" + "@storybook/icons": "npm:^1.6.0" react: "npm:^18.2.0" react-dom: "npm:^18.2.0" react-resize-detector: "npm:^7.1.2" @@ -6407,7 +6407,7 @@ __metadata: resolution: "@storybook/addon-onboarding@workspace:addons/onboarding" dependencies: "@neoconfetti/react": "npm:^1.0.0" - "@storybook/icons": "npm:^1.5.0" + "@storybook/icons": "npm:^1.6.0" react: "npm:^18.2.0" react-dom: "npm:^18.2.0" react-joyride: "npm:^2.8.2" @@ -6421,7 +6421,7 @@ __metadata: version: 0.0.0-use.local resolution: "@storybook/addon-themes@workspace:addons/themes" dependencies: - "@storybook/icons": "npm:^1.5.0" + "@storybook/icons": "npm:^1.6.0" react: "npm:^18.2.0" react-dom: "npm:^18.2.0" ts-dedent: "npm:^2.0.0" @@ -6436,7 +6436,7 @@ __metadata: resolution: "@storybook/addon-vitest@workspace:addons/vitest" dependencies: "@storybook/global": "npm:^5.0.0" - "@storybook/icons": "npm:^1.5.0" + "@storybook/icons": "npm:^1.6.0" "@types/istanbul-lib-report": "npm:^3.0.3" "@types/micromatch": "npm:^4.0.0" "@types/node": "npm:^22.0.0" @@ -6765,13 +6765,13 @@ __metadata: languageName: unknown linkType: soft -"@storybook/icons@npm:^1.5.0": - version: 1.5.0 - resolution: "@storybook/icons@npm:1.5.0" +"@storybook/icons@npm:^1.6.0": + version: 1.6.0 + resolution: "@storybook/icons@npm:1.6.0" peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - checksum: 10c0/cc8189a7d431929ccc079438b4ad55cd606421cfcd167459d1fd60656798315b620183b7fb95f2dd36859cad08f5ec8160dc8d4fe1a91b96ba8faeee10db8cc7 + checksum: 10c0/bbec9201a78a730195f9cf377b15856dc414a54d04e30d16c379d062425cc617bfd0d6586ba1716012cfbdab461f0c9693a6a52920f9bd09c7b4291fb116f59c languageName: node linkType: hard @@ -24870,7 +24870,7 @@ __metadata: version: 0.0.0-use.local resolution: "storybook-addon-pseudo-states@workspace:addons/pseudo-states" dependencies: - "@storybook/icons": "npm:^1.5.0" + "@storybook/icons": "npm:^1.6.0" react: "npm:^18.2.0" react-dom: "npm:^18.2.0" typescript: "npm:^5.8.3" @@ -24907,7 +24907,7 @@ __metadata: "@rolldown/pluginutils": "npm:1.0.0-beta.18" "@storybook/docs-mdx": "npm:4.0.0-next.1" "@storybook/global": "npm:^5.0.0" - "@storybook/icons": "npm:^1.5.0" + "@storybook/icons": "npm:^1.6.0" "@tanstack/react-virtual": "npm:^3.3.0" "@testing-library/dom": "npm:10.4.0" "@testing-library/jest-dom": "npm:^6.6.3" From 6984ccd8726bb274cca069dee48d83916161616e Mon Sep 17 00:00:00 2001 From: Norbert de Langen Date: Wed, 17 Sep 2025 10:56:42 +0200 Subject: [PATCH 28/44] Enhance exports: Add 'EditorIcon' to the list of exported icons in globals --- code/core/src/manager/globals/exports.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/code/core/src/manager/globals/exports.ts b/code/core/src/manager/globals/exports.ts index 7635e69dc9ce..ee431d430161 100644 --- a/code/core/src/manager/globals/exports.ts +++ b/code/core/src/manager/globals/exports.ts @@ -152,6 +152,7 @@ export default { 'DownloadIcon', 'DragIcon', 'EditIcon', + 'EditorIcon', 'EllipsisIcon', 'EmailIcon', 'ExpandAltIcon', From 90817f18130dfbdd46b1ab8cbdef8747d4538655 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Wed, 17 Sep 2025 14:02:45 +0200 Subject: [PATCH 29/44] rework the open in editor feature and add error handling --- code/builders/builder-webpack5/package.json | 1 - code/builders/builder-webpack5/src/index.ts | 4 - code/core/package.json | 1 + .../src/core-events/data/open-in-editor.ts | 8 ++ code/core/src/core-events/index.ts | 6 ++ .../src/core-server/presets/common-preset.ts | 2 + .../server-channel/open-in-editor-channel.ts | 73 +++++++++++++++++++ .../src/manager-api/lib/open-in-editor.ts | 44 +++++------ .../{notifications.ts => notifications.tsx} | 34 ++++++++- code/core/src/manager/globals/exports.ts | 4 + code/core/src/telemetry/types.ts | 1 + code/yarn.lock | 11 +-- 12 files changed, 147 insertions(+), 42 deletions(-) create mode 100644 code/core/src/core-events/data/open-in-editor.ts create mode 100644 code/core/src/core-server/server-channel/open-in-editor-channel.ts rename code/core/src/manager-api/modules/{notifications.ts => notifications.tsx} (64%) diff --git a/code/builders/builder-webpack5/package.json b/code/builders/builder-webpack5/package.json index ed89197038c7..001997cea3a5 100644 --- a/code/builders/builder-webpack5/package.json +++ b/code/builders/builder-webpack5/package.json @@ -60,7 +60,6 @@ "es-module-lexer": "^1.5.0", "fork-ts-checker-webpack-plugin": "^9.1.0", "html-webpack-plugin": "^5.5.0", - "launch-editor-middleware": "^2.11.1", "magic-string": "^0.30.5", "style-loader": "^4.0.0", "terser-webpack-plugin": "^5.3.14", diff --git a/code/builders/builder-webpack5/src/index.ts b/code/builders/builder-webpack5/src/index.ts index e3cdc0782146..202d69563179 100644 --- a/code/builders/builder-webpack5/src/index.ts +++ b/code/builders/builder-webpack5/src/index.ts @@ -196,10 +196,6 @@ const starter: StarterFunction = async function* starterGeneratorFn({ }) ); - // @ts-expect-error no types available, see https://github.com/yyx990803/launch-editor/issues/84 - const { default: launchMiddleware } = await import('launch-editor-middleware'); - router.use('/__open-in-editor', launchMiddleware()); - router.use(compilation); router.use(webpackHotMiddleware(compiler, { log: false })); diff --git a/code/core/package.json b/code/core/package.json index f2c8b2b06b4a..fe0d126a3717 100644 --- a/code/core/package.json +++ b/code/core/package.json @@ -235,6 +235,7 @@ "@vitest/mocker": "3.2.4", "@vitest/spy": "3.2.4", "esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0", + "launch-editor": "^2.11.1", "react-qr-code": "^2.0.12", "recast": "^0.23.5", "semver": "^7.6.2", diff --git a/code/core/src/core-events/data/open-in-editor.ts b/code/core/src/core-events/data/open-in-editor.ts new file mode 100644 index 000000000000..3e4df8202c7c --- /dev/null +++ b/code/core/src/core-events/data/open-in-editor.ts @@ -0,0 +1,8 @@ +export type OpenInEditorRequestPayload = { file: string; line?: number; column?: number }; + +export type OpenInEditorResponsePayload = { + file: string; + line?: number; + column?: number; + error: string | null; +}; diff --git a/code/core/src/core-events/index.ts b/code/core/src/core-events/index.ts index d0ff9cca7d54..3ab4b36fb3a1 100644 --- a/code/core/src/core-events/index.ts +++ b/code/core/src/core-events/index.ts @@ -86,6 +86,9 @@ enum events { ARGTYPES_INFO_RESPONSE = 'argtypesInfoResponse', CREATE_NEW_STORYFILE_REQUEST = 'createNewStoryfileRequest', CREATE_NEW_STORYFILE_RESPONSE = 'createNewStoryfileResponse', + // Open a file in the code editor + OPEN_IN_EDITOR_REQUEST = 'openInEditorRequest', + OPEN_IN_EDITOR_RESPONSE = 'openInEditorResponse', } // Enables: `import Events from ...` @@ -151,6 +154,8 @@ export const { SAVE_STORY_RESPONSE, ARGTYPES_INFO_REQUEST, ARGTYPES_INFO_RESPONSE, + OPEN_IN_EDITOR_REQUEST, + OPEN_IN_EDITOR_RESPONSE, } = events; export * from './data/create-new-story'; @@ -160,3 +165,4 @@ export * from './data/request-response'; export * from './data/save-story'; export * from './data/whats-new'; export * from './data/phases'; +export * from './data/open-in-editor'; diff --git a/code/core/src/core-server/presets/common-preset.ts b/code/core/src/core-server/presets/common-preset.ts index d95626ee7e4a..fe09e8647688 100644 --- a/code/core/src/core-server/presets/common-preset.ts +++ b/code/core/src/core-server/presets/common-preset.ts @@ -32,6 +32,7 @@ import { dedent } from 'ts-dedent'; import { resolvePackageDir } from '../../shared/utils/module'; import { initCreateNewStoryChannel } from '../server-channel/create-new-story-channel'; import { initFileSearchChannel } from '../server-channel/file-search-channel'; +import { initOpenInEditorChannel } from '../server-channel/open-in-editor-channel'; import { defaultFavicon, defaultStaticDirs } from '../utils/constants'; import { initializeSaveStory } from '../utils/save-story/save-story'; import { parseStaticDir } from '../utils/server-statics'; @@ -256,6 +257,7 @@ export const experimental_serverChannel = async ( initFileSearchChannel(channel, options, coreOptions); initCreateNewStoryChannel(channel, options, coreOptions); + initOpenInEditorChannel(channel, options, coreOptions); return channel; }; diff --git a/code/core/src/core-server/server-channel/open-in-editor-channel.ts b/code/core/src/core-server/server-channel/open-in-editor-channel.ts new file mode 100644 index 000000000000..d33c83b8e005 --- /dev/null +++ b/code/core/src/core-server/server-channel/open-in-editor-channel.ts @@ -0,0 +1,73 @@ +import { join } from 'node:path'; + +import type { Channel } from 'storybook/internal/channels'; +import { getProjectRoot } from 'storybook/internal/common'; +import type { + OpenInEditorRequestPayload, + OpenInEditorResponsePayload, + RequestData, + ResponseData, +} from 'storybook/internal/core-events'; +import { OPEN_IN_EDITOR_REQUEST, OPEN_IN_EDITOR_RESPONSE } from 'storybook/internal/core-events'; +import { telemetry } from 'storybook/internal/telemetry'; +import type { CoreConfig, Options, StoryIndex } from 'storybook/internal/types'; + +import launch from 'launch-editor'; + +export async function initOpenInEditorChannel( + channel: Channel, + _options: Options, + coreOptions: CoreConfig +) { + channel.on(OPEN_IN_EDITOR_REQUEST, async (payload: OpenInEditorRequestPayload) => { + const sendTelemetry = (data: { success: boolean; error?: string }) => { + if (!coreOptions.disableTelemetry) { + telemetry('open-in-editor', data); + } + }; + try { + const targetFile = payload.file; + const line = payload.line; + const column = payload.column; + + if (!targetFile) { + throw new Error('No file was provided to open'); + } + + const location = + typeof line === 'number' + ? `${targetFile}:${line}${typeof column === 'number' ? `:${column}` : ''}` + : targetFile; + + await new Promise((resolve, reject) => { + launch(location, undefined, (_fileName: string, errorMessage: string | null) => { + if (errorMessage) { + reject(new Error(errorMessage)); + } else { + resolve(); + } + }); + }); + + channel.emit(OPEN_IN_EDITOR_RESPONSE, { + file: targetFile!, + line, + column, + error: null, + } satisfies OpenInEditorResponsePayload); + + sendTelemetry({ success: true }); + } catch (e: any) { + console.log(e); + const error = e?.message || 'Failed to open in editor'; + channel.emit(OPEN_IN_EDITOR_RESPONSE, { + error, + ...payload, + } satisfies OpenInEditorResponsePayload); + + sendTelemetry({ success: false, error }); + } + }); + + return channel; +} diff --git a/code/core/src/manager-api/lib/open-in-editor.ts b/code/core/src/manager-api/lib/open-in-editor.ts index 55e98bb15fe5..7d3b68b5389e 100644 --- a/code/core/src/manager-api/lib/open-in-editor.ts +++ b/code/core/src/manager-api/lib/open-in-editor.ts @@ -1,31 +1,25 @@ -/** - * Open the file in the editor - * - * Available for builders which support https://github.com/yyx990803/launch-editor - * - * Known builders: Webpack5, Vite - * - * @param filePath - The path to the file to open in the editor - * @returns Void - */ +import { + OPEN_IN_EDITOR_REQUEST, + OPEN_IN_EDITOR_RESPONSE, + type OpenInEditorResponsePayload, +} from 'storybook/internal/core-events'; + +import { addons } from './addons'; + export async function openInEditor( - filePath: string, + file: string, line?: number, column?: number -): Promise { - let fileLocation = filePath; - if (typeof line === 'number') { - fileLocation += `:${line}`; - if (typeof column === 'number') { - fileLocation += `:${column}`; - } - } +): Promise { + return new Promise((resolve) => { + const channel = addons.getChannel(); + const payload = { file, line, column }; - try { - await fetch(`/__open-in-editor?file=${encodeURIComponent(fileLocation)}`, { - method: 'POST', + channel.on(OPEN_IN_EDITOR_RESPONSE, (payload: OpenInEditorResponsePayload) => { + resolve(payload); }); - } catch { - // no-op - } + + console.log('sending request'); + channel.emit(OPEN_IN_EDITOR_REQUEST, payload); + }); } diff --git a/code/core/src/manager-api/modules/notifications.ts b/code/core/src/manager-api/modules/notifications.tsx similarity index 64% rename from code/core/src/manager-api/modules/notifications.ts rename to code/core/src/manager-api/modules/notifications.tsx index 14d876274bdd..279c745eba4e 100644 --- a/code/core/src/manager-api/modules/notifications.ts +++ b/code/core/src/manager-api/modules/notifications.tsx @@ -1,6 +1,15 @@ +import React from 'react'; + +import { + OPEN_IN_EDITOR_RESPONSE, + type OpenInEditorResponsePayload, +} from 'storybook/internal/core-events'; import type { API_Notification } from 'storybook/internal/types'; +import { FailedIcon } from '@storybook/icons'; + import { partition } from 'es-toolkit/array'; +import { color } from 'storybook/theming'; import type { ModuleFn } from '../lib/types'; @@ -26,7 +35,7 @@ export interface SubAPI { clearNotification: (id: string) => void; } -export const init: ModuleFn = ({ store }) => { +export const init: ModuleFn = ({ store, provider }) => { const api: SubAPI = { addNotification: (newNotification) => { store.setState(({ notifications }) => { @@ -55,5 +64,26 @@ export const init: ModuleFn = ({ store }) => { const state: SubState = { notifications: [] }; - return { api, state }; + return { + api, + state, + init: async () => { + provider.channel?.on(OPEN_IN_EDITOR_RESPONSE, (payload: OpenInEditorResponsePayload) => { + if (payload.error !== null) { + api.addNotification({ + id: 'open-in-editor-error', + content: { + headline: 'Failed to open in editor', + subHeadline: + payload.error || + 'Check the Storybook process on the command line for more details.', + }, + icon: , + duration: 8_000, + }); + throw new Error(payload.error); + } + }); + }, + }; }; diff --git a/code/core/src/manager/globals/exports.ts b/code/core/src/manager/globals/exports.ts index ee431d430161..e15be43fe1f4 100644 --- a/code/core/src/manager/globals/exports.ts +++ b/code/core/src/manager/globals/exports.ts @@ -563,6 +563,8 @@ export default { 'FORCE_RE_RENDER', 'GLOBALS_UPDATED', 'NAVIGATE_URL', + 'OPEN_IN_EDITOR_REQUEST', + 'OPEN_IN_EDITOR_RESPONSE', 'PLAY_FUNCTION_THREW_EXCEPTION', 'PRELOAD_ENTRIES', 'PREVIEW_BUILDER_PROGRESS', @@ -622,6 +624,8 @@ export default { 'FORCE_RE_RENDER', 'GLOBALS_UPDATED', 'NAVIGATE_URL', + 'OPEN_IN_EDITOR_REQUEST', + 'OPEN_IN_EDITOR_RESPONSE', 'PLAY_FUNCTION_THREW_EXCEPTION', 'PRELOAD_ENTRIES', 'PREVIEW_BUILDER_PROGRESS', diff --git a/code/core/src/telemetry/types.ts b/code/core/src/telemetry/types.ts index d1827d07219e..de36f4af5fb1 100644 --- a/code/core/src/telemetry/types.ts +++ b/code/core/src/telemetry/types.ts @@ -24,6 +24,7 @@ export type EventType = | 'save-story' | 'create-new-story-file' | 'create-new-story-file-search' + | 'open-in-editor' | 'testing-module-watch-mode' | 'testing-module-completed-report' | 'testing-module-crash-report' diff --git a/code/yarn.lock b/code/yarn.lock index 7828c743b785..99c68802e879 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -6373,7 +6373,6 @@ __metadata: es-module-lexer: "npm:^1.5.0" fork-ts-checker-webpack-plugin: "npm:^9.1.0" html-webpack-plugin: "npm:^5.5.0" - launch-editor-middleware: "npm:^2.11.1" magic-string: "npm:^0.30.5" pretty-hrtime: "npm:^1.0.3" sirv: "npm:^2.0.4" @@ -18027,15 +18026,6 @@ __metadata: languageName: node linkType: hard -"launch-editor-middleware@npm:^2.11.1": - version: 2.11.1 - resolution: "launch-editor-middleware@npm:2.11.1" - dependencies: - launch-editor: "npm:^2.11.1" - checksum: 10c0/d78a3cf0e166ebf9023f81a2f4b3f570422a7bd9edd505e85018247f3a75b7705f4973f3f3c3db0ec7aa118159fa85affc6d885f72ef0a267d1155ff3f5c2d19 - languageName: node - linkType: hard - "launch-editor@npm:^2.11.1, launch-editor@npm:^2.6.1": version: 2.11.1 resolution: "launch-editor@npm:2.11.1" @@ -24567,6 +24557,7 @@ __metadata: jiti: "npm:^2.4.2" js-yaml: "npm:^4.1.0" jsdoc-type-pratt-parser: "npm:^4.0.0" + launch-editor: "npm:^2.11.1" lazy-universal-dotenv: "npm:^4.0.0" leven: "npm:^4.0.0" memfs: "npm:^4.11.1" From cf13e705717d4b0cccbd1818c18237933ffd45b1 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Wed, 17 Sep 2025 14:09:38 +0200 Subject: [PATCH 30/44] address PR feedback --- code/core/src/builder-manager/index.ts | 1 - code/core/src/builder-manager/utils/framework.ts | 4 ++++ code/core/src/manager-api/index.mock.ts | 4 +++- code/core/src/manager/components/preview/tools/share.tsx | 5 +++-- code/core/src/manager/settings/shortcuts.tsx | 4 ++-- code/core/src/manager/typings.d.ts | 5 ++++- 6 files changed, 16 insertions(+), 7 deletions(-) diff --git a/code/core/src/builder-manager/index.ts b/code/core/src/builder-manager/index.ts index 41f739e09981..462d5dc6263c 100644 --- a/code/core/src/builder-manager/index.ts +++ b/code/core/src/builder-manager/index.ts @@ -200,7 +200,6 @@ const starter: StarterFunction = async function* starterGeneratorFn({ // Build additional global values const globals: Record = await buildFrameworkGlobalsFromOptions(options); - globals.STORYBOOK_NETWORK_ADDRESS = options.networkAddress; yield; diff --git a/code/core/src/builder-manager/utils/framework.ts b/code/core/src/builder-manager/utils/framework.ts index 8362e423813f..d980b1c5d65f 100644 --- a/code/core/src/builder-manager/utils/framework.ts +++ b/code/core/src/builder-manager/utils/framework.ts @@ -55,5 +55,9 @@ export const buildFrameworkGlobalsFromOptions = async (options: Options) => { globals.STORYBOOK_FRAMEWORK = framework; } + if (options.networkAddress) { + globals.STORYBOOK_NETWORK_ADDRESS = options.networkAddress; + } + return globals; }; diff --git a/code/core/src/manager-api/index.mock.ts b/code/core/src/manager-api/index.mock.ts index f6950b1a28e5..231a30b526ea 100644 --- a/code/core/src/manager-api/index.mock.ts +++ b/code/core/src/manager-api/index.mock.ts @@ -1,6 +1,8 @@ +import { fn } from 'storybook/test'; + export * from './root'; -export { openInEditor } from './lib/open-in-editor'; +export const openInEditor = fn(); export { UniversalStore as experimental_UniversalStore } from '../shared/universal-store'; export { useUniversalStore as experimental_useUniversalStore } from '../shared/universal-store/use-universal-store-manager'; diff --git a/code/core/src/manager/components/preview/tools/share.tsx b/code/core/src/manager/components/preview/tools/share.tsx index b0738d888e16..378a293d4556 100644 --- a/code/core/src/manager/components/preview/tools/share.tsx +++ b/code/core/src/manager/components/preview/tools/share.tsx @@ -95,6 +95,7 @@ function ShareMenu({ const shortcutKeys = api.getShortcutKeys(); const enableShortcuts = !!shortcutKeys; const [copied, setCopied] = useState(false); + const copyStoryLink = shortcutKeys?.copyStoryLink; const links = useMemo(() => { const copyTitle = copied ? 'Copied!' : 'Copy story link'; @@ -104,7 +105,7 @@ function ShareMenu({ id: 'copy-link', title: copyTitle, icon: , - right: enableShortcuts ? : null, + right: enableShortcuts ? : null, onClick: () => { copy(window.location.href); setCopied(true); @@ -142,7 +143,7 @@ function ShareMenu({ } return baseLinks; - }, [baseUrl, storyId, queryParams, copied, qrUrl, enableShortcuts, shortcutKeys.copyStoryLink]); + }, [baseUrl, storyId, queryParams, copied, qrUrl, enableShortcuts, copyStoryLink]); return ; } diff --git a/code/core/src/manager/settings/shortcuts.tsx b/code/core/src/manager/settings/shortcuts.tsx index d057d814ff83..03c644e18be5 100644 --- a/code/core/src/manager/settings/shortcuts.tsx +++ b/code/core/src/manager/settings/shortcuts.tsx @@ -200,8 +200,8 @@ class ShortcutsScreen extends Component 'O') const normalizedShortcut = shortcut.map((key) => - Array.isArray(key) ? key[key.length - 1] : key - ); + Array.isArray(key) ? key.at(-1) : key + ) as string[]; // Check we don't match any other shortcuts const error = !!Object.entries(shortcutKeys).find( diff --git a/code/core/src/manager/typings.d.ts b/code/core/src/manager/typings.d.ts index 6d89d60870ab..fb3fa90c7557 100644 --- a/code/core/src/manager/typings.d.ts +++ b/code/core/src/manager/typings.d.ts @@ -1,7 +1,10 @@ declare var DOCS_OPTIONS: any; declare var CONFIG_TYPE: 'DEVELOPMENT' | 'PRODUCTION'; declare var PREVIEW_URL: any; -declare var STORYBOOK_ADDRESS: string | undefined; +/** + * The network address of the Storybook instance. Used by Storybook to generate a QR code so users + * can access the story on mobile devices. + */ declare var STORYBOOK_NETWORK_ADDRESS: string | undefined; declare var __STORYBOOK_ADDONS_MANAGER: any; From 244fac11bdd4f77668755b03b9bcd5682c42e77d Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Wed, 17 Sep 2025 15:42:14 +0200 Subject: [PATCH 31/44] revamp context menu and support opening mdx stories --- .../components/InteractionsPanel.tsx | 6 + .../component-testing/components/Panel.tsx | 10 ++ .../components/Subnav.stories.tsx | 29 ++--- .../component-testing/components/Subnav.tsx | 11 +- .../components/sidebar/ContextMenu.tsx | 112 +++++++++--------- 5 files changed, 91 insertions(+), 77 deletions(-) diff --git a/code/core/src/component-testing/components/InteractionsPanel.tsx b/code/core/src/component-testing/components/InteractionsPanel.tsx index 7e2d4f4bffb5..9c6bb4fa5e0f 100644 --- a/code/core/src/component-testing/components/InteractionsPanel.tsx +++ b/code/core/src/component-testing/components/InteractionsPanel.tsx @@ -44,6 +44,8 @@ interface InteractionsPanelProps { onScrollToEnd?: () => void; hasResultMismatch?: boolean; browserTestStatus?: CallStates; + importPath?: string; + canOpenInEditor?: boolean; } const Container = styled.div(({ theme }) => ({ @@ -104,6 +106,8 @@ export const InteractionsPanel: React.FC = React.memo( endRef, hasResultMismatch, browserTestStatus, + importPath, + canOpenInEditor, }) { const filter = useAnsiToHtmlFilter(); const hasRealInteractions = interactions.some((i) => i.id !== INTERNAL_RENDER_CALL_ID); @@ -120,6 +124,8 @@ export const InteractionsPanel: React.FC = React.memo( status={status} storyFileName={fileName} onScrollToEnd={onScrollToEnd} + importPath={importPath} + canOpenInEditor={canOpenInEditor} />
{interactions.map((call) => ( diff --git a/code/core/src/component-testing/components/Panel.tsx b/code/core/src/component-testing/components/Panel.tsx index 16df7a552a67..9f1b36d58bf9 100644 --- a/code/core/src/component-testing/components/Panel.tsx +++ b/code/core/src/component-testing/components/Panel.tsx @@ -17,6 +17,8 @@ import { useAddonState, useChannel, useParameter, + useStorybookApi, + useStorybookState, } from 'storybook/manager-api'; import { @@ -191,6 +193,12 @@ export const Panel = memo<{ refId?: string; storyId: string; storyUrl: string }> }); // shared state + const state = useStorybookState(); + const api = useStorybookApi(); + const data = api.getData(state.storyId, state.refId); + const importPath = data?.importPath as string | undefined; + const canOpenInEditor = global.CONFIG_TYPE === 'DEVELOPMENT' && !state.refId; + const [panelState, set] = useAddonState(ADDON_ID, { status: 'rendering' as PlayStatus, controlStates: INITIAL_CONTROL_STATES, @@ -406,6 +414,8 @@ export const Panel = memo<{ refId?: string; storyId: string; storyUrl: string }> // @ts-expect-error TODO endRef={endRef} onScrollToEnd={scrollTarget && scrollToTarget} + importPath={importPath} + canOpenInEditor={canOpenInEditor} /> ); diff --git a/code/core/src/component-testing/components/Subnav.stories.tsx b/code/core/src/component-testing/components/Subnav.stories.tsx index 36b757c4d355..edefd75c551e 100644 --- a/code/core/src/component-testing/components/Subnav.stories.tsx +++ b/code/core/src/component-testing/components/Subnav.stories.tsx @@ -5,26 +5,12 @@ import { ManagerContext } from 'storybook/manager-api'; import { Subnav } from './Subnav'; -const managerContext: any = { - state: {}, - api: { - getData: () => ({ importPath: 'core/src/component-testing/components/Subnav.stories.tsx' }), - }, -}; - export default { title: 'Subnav', component: Subnav, parameters: { layout: 'fullscreen', }, - decorators: [ - (Story: any) => ( - - - - ), - ], args: { controls: { start: action('start'), @@ -149,3 +135,18 @@ export const Detached = { }, }, }; + +export const WithOpenInEditorLink = { + args: { + status: 'completed', + controlStates: { + detached: true, + start: false, + back: false, + goto: false, + next: false, + end: false, + }, + canOpenInEditor: true, + }, +}; diff --git a/code/core/src/component-testing/components/Subnav.tsx b/code/core/src/component-testing/components/Subnav.tsx index 51b7222fca00..9fa653e375af 100644 --- a/code/core/src/component-testing/components/Subnav.tsx +++ b/code/core/src/component-testing/components/Subnav.tsx @@ -49,6 +49,8 @@ interface SubnavProps { status: PlayStatus; storyFileName?: string; onScrollToEnd?: () => void; + importPath?: string; + canOpenInEditor?: boolean; } const StyledButton = styled(Button)(({ theme }) => ({ @@ -123,14 +125,11 @@ export const Subnav: React.FC = ({ status, storyFileName, onScrollToEnd, + importPath, + canOpenInEditor, }) => { const buttonText = status === 'errored' ? 'Scroll to error' : 'Scroll to end'; const theme = useTheme(); - const state = useStorybookState(); - const api = useStorybookApi(); - const data = api.getData(state.storyId, state.refId); - const importPath = data?.importPath as string | undefined; - const isLocal = !state.refId; return ( @@ -193,7 +192,7 @@ export const Subnav: React.FC = ({ {(importPath || storyFileName) && ( - {global.CONFIG_TYPE === 'DEVELOPMENT' && isLocal ? ( + {canOpenInEditor ? ( { const [hoverCount, setHoverCount] = useState(0); const [isOpen, setIsOpen] = useState(false); + const [copyText, setCopyText] = React.useState('Copy story name'); + + const shortcutKeys = api.getShortcutKeys(); + const enableShortcuts = !!shortcutKeys; + + const topLinks = useMemo(() => { + const defaultLinks = []; + if (context.type === 'docs' || context.type === 'story') { + defaultLinks.push({ + id: 'open-in-editor', + title: 'Open in editor', + icon: , + right: enableShortcuts ? : null, + onClick: (e: SyntheticEvent) => { + e.preventDefault(); + openInEditor(context.importPath); + }, + }); + } + + if (context.type === 'story') { + defaultLinks.push({ + id: 'copy-story-name', + title: copyText, + icon: , + // TODO: bring this back once we want to add shortcuts for this + // right: + // enableShortcuts && shortcutKeys.copyStoryName ? ( + // + // ) : null, + onClick: (e: SyntheticEvent) => { + e.preventDefault(); + copy(context.exportName); + setCopyText('Copied!'); + setTimeout(() => { + setCopyText('Copy story name'); + }, 2000); + }, + }); + } + + return defaultLinks; + }, [context, copyText, enableShortcuts, shortcutKeys]); const handlers = useMemo(() => { return { @@ -56,7 +98,6 @@ export const useContextMenu = (context: API_HashEntry, links: Link[], api: API) }, }; }, []); - /** * Calculate the providerLinks whenever the user mouses over the container. We use an incrementor, * instead of a simple boolean to ensure that the links are recalculated @@ -70,7 +111,9 @@ export const useContextMenu = (context: API_HashEntry, links: Link[], api: API) return []; }, [api, context, hoverCount]); - const isRendered = providerLinks.length > 0 || links.length > 0; + // We just don't want to render the context menu for composed storybook stories + const shouldRender = + !context.refId && (providerLinks.length > 0 || links.length > 0 || topLinks.length > 0); return useMemo(() => { // Never show the SidebarContextMenu in production @@ -80,7 +123,7 @@ export const useContextMenu = (context: API_HashEntry, links: Link[], api: API) return { onMouseEnter: handlers.onMouseEnter, - node: isRendered ? ( + node: shouldRender ? ( } + tooltip={} > @@ -101,7 +144,7 @@ export const useContextMenu = (context: API_HashEntry, links: Link[], api: API) ) : null, }; - }, [context, handlers, isOpen, isRendered, links]); + }, [context, handlers, isOpen, shouldRender, links, topLinks]); }; /** @@ -114,60 +157,15 @@ const LiveContextMenu: FC<{ context: API_HashEntry } & ComponentProps { - const api = useStorybookApi(); - const entry = api.getData(context.id, context.refId); - const importPath = entry?.importPath; - const storyName = (entry && 'exportName' in entry && entry.exportName) || context?.name; - const [copyText, setCopyText] = React.useState('Copy story name'); - - const shortcutKeys = api.getShortcutKeys(); - const enableShortcuts = !!shortcutKeys; - - const registeredTestProviders = api.getElements(Addon_TypesEnum.experimental_TEST_PROVIDER); + const registeredTestProviders = useStorybookApi().getElements( + Addon_TypesEnum.experimental_TEST_PROVIDER + ); const providerLinks: Link[] = generateTestProviderLinks(registeredTestProviders, context); - const topLinks: Link[] = []; - - if (importPath) { - if (global.CONFIG_TYPE === 'DEVELOPMENT') { - topLinks.push({ - id: 'open-in-editor', - title: 'Open in editor', - icon: , - right: enableShortcuts ? : null, - onClick: (e) => { - e.preventDefault(); - if (importPath && !context.refId) { - openInEditor(importPath); - } - }, - }); - } - - topLinks.push({ - id: 'copy-story-name', - title: copyText, - icon: , - // TODO: bring this back once we want to add shortcuts for this - // right: - // enableShortcuts && shortcutKeys.copyStoryName ? ( - // - // ) : null, - onClick: () => { - if (storyName) { - copy(String(storyName)); - setCopyText('Copied!'); - setTimeout(() => { - setCopyText('Copy story name'); - }, 2000); - } - }, - }); - } + const groups: Link[][] = + Array.isArray(links[0]) || links.length === 0 ? (links as Link[][]) : [links as Link[]]; - const groups = Array.isArray(links[0]) ? (links as Link[][]) : [links as Link[]]; - const all = - topLinks.length > 0 ? [topLinks, ...groups, providerLinks] : [...groups, providerLinks]; + const all = groups.concat([providerLinks]); return ; }; From cd1b6e5da404a15299c32c4a9deda95891e461e0 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Wed, 17 Sep 2025 15:54:38 +0200 Subject: [PATCH 32/44] replace qrcode library --- code/core/package.json | 2 +- .../components/preview/tools/share.tsx | 5 ++-- code/yarn.lock | 24 ++++++------------- 3 files changed, 10 insertions(+), 21 deletions(-) diff --git a/code/core/package.json b/code/core/package.json index fe0d126a3717..3b72fe45419d 100644 --- a/code/core/package.json +++ b/code/core/package.json @@ -236,7 +236,6 @@ "@vitest/spy": "3.2.4", "esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0", "launch-editor": "^2.11.1", - "react-qr-code": "^2.0.12", "recast": "^0.23.5", "semver": "^7.6.2", "ws": "^8.18.0" @@ -336,6 +335,7 @@ "prettier": "^3.5.3", "pretty-hrtime": "^1.0.3", "prompts": "^2.4.0", + "qrcode.react": "^4.2.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-helmet-async": "^1.3.0", diff --git a/code/core/src/manager/components/preview/tools/share.tsx b/code/core/src/manager/components/preview/tools/share.tsx index 378a293d4556..de79ffc5784d 100644 --- a/code/core/src/manager/components/preview/tools/share.tsx +++ b/code/core/src/manager/components/preview/tools/share.tsx @@ -12,8 +12,7 @@ import { global } from '@storybook/global'; import { BugIcon, LinkIcon, ShareIcon } from '@storybook/icons'; import copy from 'copy-to-clipboard'; -// @ts-expect-error see https://github.com/rosskhanas/react-qr-code/issues/251 -import { QRCode } from 'react-qr-code'; +import { QRCodeSVG as QRCode } from 'qrcode.react'; import { Consumer, types, useStorybookApi } from 'storybook/manager-api'; import type { Combo } from 'storybook/manager-api'; import { styled, useTheme } from 'storybook/theming'; @@ -61,7 +60,7 @@ const QRImage = ({ value }: { value: string }) => { return ( value && ( - + ) ); diff --git a/code/yarn.lock b/code/yarn.lock index 99c68802e879..958e2d7b011c 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -22072,10 +22072,12 @@ __metadata: languageName: node linkType: hard -"qr.js@npm:0.0.0": - version: 0.0.0 - resolution: "qr.js@npm:0.0.0" - checksum: 10c0/1c6a4c7a58d04e52ec2fee99e39b680fdc5b2a510a981df42c36b716a8eac6634d130fc4d65af8f030f2a07dbf5fa046b97cdfa7456c250ebb50a73916efdcb5 +"qrcode.react@npm:^4.2.0": + version: 4.2.0 + resolution: "qrcode.react@npm:4.2.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + checksum: 10c0/68c691d130e5fda2f57cee505ed7aea840e7d02033100687b764601f9595e1116e34c13876628a93e1a5c2b85e4efc27d30b2fda72e2050c02f3e1c4e998d248 languageName: node linkType: hard @@ -22411,18 +22413,6 @@ __metadata: languageName: node linkType: hard -"react-qr-code@npm:^2.0.12": - version: 2.0.18 - resolution: "react-qr-code@npm:2.0.18" - dependencies: - prop-types: "npm:^15.8.1" - qr.js: "npm:0.0.0" - peerDependencies: - react: "*" - checksum: 10c0/4e13b795cbb10f1dcf0e39d682bb59851e4c84010ba2be7225b2ad9d5c1ffea52d2d38f884ee26235b7002b8ca99e83b805f55e877663c39d67496764d975cf1 - languageName: node - linkType: hard - "react-refresh@npm:^0.14.0": version: 0.14.2 resolution: "react-refresh@npm:0.14.2" @@ -24575,12 +24565,12 @@ __metadata: prettier: "npm:^3.5.3" pretty-hrtime: "npm:^1.0.3" prompts: "npm:^2.4.0" + qrcode.react: "npm:^4.2.0" react: "npm:^18.2.0" react-dom: "npm:^18.2.0" react-helmet-async: "npm:^1.3.0" react-inspector: "npm:^6.0.0" react-popper-tooltip: "npm:^4.4.2" - react-qr-code: "npm:^2.0.12" react-router-dom: "npm:6.15.0" react-syntax-highlighter: "npm:^15.4.5" react-textarea-autosize: "npm:^8.3.0" From a363ba861253181dfbdd507688ce58d6a5c2821b Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Wed, 17 Sep 2025 16:02:26 +0200 Subject: [PATCH 33/44] more fixes --- code/core/src/builder-manager/utils/template.ts | 9 --------- code/core/src/manager-api/lib/open-in-editor.ts | 13 +++++++------ code/core/src/manager-api/modules/notifications.tsx | 1 - 3 files changed, 7 insertions(+), 16 deletions(-) diff --git a/code/core/src/builder-manager/utils/template.ts b/code/core/src/builder-manager/utils/template.ts index 0d44d2c70e80..c31088c39d41 100644 --- a/code/core/src/builder-manager/utils/template.ts +++ b/code/core/src/builder-manager/utils/template.ts @@ -56,15 +56,6 @@ 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 - // Server addresses available in development for sharing over network - STORYBOOK_ADDRESS: JSON.stringify( - (globals as any).STORYBOOK_ADDRESS ?? (globalThis as any).STORYBOOK_ADDRESS ?? undefined - ), - STORYBOOK_NETWORK_ADDRESS: JSON.stringify( - (globals as any).STORYBOOK_NETWORK_ADDRESS ?? - (globalThis as any).STORYBOOK_NETWORK_ADDRESS ?? - undefined - ), TAGS_OPTIONS: JSON.stringify(await tagsOptions, null, 2), ...stringifiedGlobals, }, diff --git a/code/core/src/manager-api/lib/open-in-editor.ts b/code/core/src/manager-api/lib/open-in-editor.ts index 7d3b68b5389e..25e65ad745db 100644 --- a/code/core/src/manager-api/lib/open-in-editor.ts +++ b/code/core/src/manager-api/lib/open-in-editor.ts @@ -14,12 +14,13 @@ export async function openInEditor( return new Promise((resolve) => { const channel = addons.getChannel(); const payload = { file, line, column }; - - channel.on(OPEN_IN_EDITOR_RESPONSE, (payload: OpenInEditorResponsePayload) => { - resolve(payload); - }); - - console.log('sending request'); + const handler = (res: OpenInEditorResponsePayload) => { + if (res.file === file && res.line === line && res.column === column) { + channel.off(OPEN_IN_EDITOR_RESPONSE, handler); + resolve(res); + } + }; + channel.on(OPEN_IN_EDITOR_RESPONSE, handler); channel.emit(OPEN_IN_EDITOR_REQUEST, payload); }); } diff --git a/code/core/src/manager-api/modules/notifications.tsx b/code/core/src/manager-api/modules/notifications.tsx index 279c745eba4e..c26e4ac45757 100644 --- a/code/core/src/manager-api/modules/notifications.tsx +++ b/code/core/src/manager-api/modules/notifications.tsx @@ -81,7 +81,6 @@ export const init: ModuleFn = ({ store, provider }) => { icon: , duration: 8_000, }); - throw new Error(payload.error); } }); }, From d0653adac60c4ac2b420869796440eb47cf69057 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Wed, 17 Sep 2025 16:37:55 +0200 Subject: [PATCH 34/44] fix stories --- code/core/src/manager/components/sidebar/Refs.stories.tsx | 4 +++- code/core/src/manager/components/sidebar/Tree.stories.tsx | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/code/core/src/manager/components/sidebar/Refs.stories.tsx b/code/core/src/manager/components/sidebar/Refs.stories.tsx index 5042fc663119..d183e41896b8 100644 --- a/code/core/src/manager/components/sidebar/Refs.stories.tsx +++ b/code/core/src/manager/components/sidebar/Refs.stories.tsx @@ -14,7 +14,9 @@ const managerContext = { api: { on: fn().mockName('api::on'), off: fn().mockName('api::off'), - getElements: fn(() => ({})), + emit: fn().mockName('api::emit'), + getElements: fn(() => ({})).mockName('api::getElements'), + getShortcutKeys: fn(() => ({})).mockName('api::getShortcutKeys'), }, } as any; diff --git a/code/core/src/manager/components/sidebar/Tree.stories.tsx b/code/core/src/manager/components/sidebar/Tree.stories.tsx index 643c86e50670..beaffabf02bb 100644 --- a/code/core/src/manager/components/sidebar/Tree.stories.tsx +++ b/code/core/src/manager/components/sidebar/Tree.stories.tsx @@ -304,8 +304,8 @@ export const WithContextContent: Story = { const link = await screen.findByText('TooltipBuildList'); await userEvent.hover(link); - const contextButton = await screen.findByTestId('context-menu'); - await userEvent.click(contextButton); + const contextButton = await screen.findAllByTestId('context-menu'); + await userEvent.click(contextButton[0]); const body = await within(document.body); From 1190cb5c509f8f989d2d230b6d3001eb1e0f59e9 Mon Sep 17 00:00:00 2001 From: Norbert de Langen Date: Wed, 17 Sep 2025 19:54:26 +0200 Subject: [PATCH 35/44] fix: add missing newline in no-stories-of.md documentation --- code/lib/eslint-plugin/docs/rules/no-stories-of.md | 1 + 1 file changed, 1 insertion(+) diff --git a/code/lib/eslint-plugin/docs/rules/no-stories-of.md b/code/lib/eslint-plugin/docs/rules/no-stories-of.md index 9a8cb23162f5..a58f962f43e9 100644 --- a/code/lib/eslint-plugin/docs/rules/no-stories-of.md +++ b/code/lib/eslint-plugin/docs/rules/no-stories-of.md @@ -14,6 +14,7 @@ Examples of **incorrect** code for this rule: ```js import { storiesOf } from '@storybook/react'; + import Button from '../components/Button'; storiesOf('Button', module).add('primary', () =>