From a7982fd84b2e42c01ece236cb283db6a23d2f3be Mon Sep 17 00:00:00 2001 From: Gert Hengeveld Date: Tue, 23 Dec 2025 11:38:12 +0100 Subject: [PATCH 01/33] Move getStoryHref into addon-docs, simplify logic and deprecate original getStoryHref helper --- .../docs/src/blocks/components/Preview.tsx | 1 - .../docs/src/blocks/components/Story.tsx | 8 +++---- .../docs/src/blocks/components/Toolbar.tsx | 17 ++++--------- code/addons/docs/src/blocks/getStoryHref.ts | 24 +++++++++++++++++++ .../components/utils/getStoryHref.ts | 6 +++++ 5 files changed, 38 insertions(+), 18 deletions(-) create mode 100644 code/addons/docs/src/blocks/getStoryHref.ts diff --git a/code/addons/docs/src/blocks/components/Preview.tsx b/code/addons/docs/src/blocks/components/Preview.tsx index b43dd30519a3..edaeb88a9876 100644 --- a/code/addons/docs/src/blocks/components/Preview.tsx +++ b/code/addons/docs/src/blocks/components/Preview.tsx @@ -260,7 +260,6 @@ export const Preview: FC = ({ zoom={(z: number) => setScale(scale * z)} resetZoom={() => setScale(1)} storyId={!isLoading && childProps ? getStoryId(childProps, context) : undefined} - baseUrl="./iframe.html" /> )} diff --git a/code/addons/docs/src/blocks/components/Story.tsx b/code/addons/docs/src/blocks/components/Story.tsx index 5af081fce655..144d14445fba 100644 --- a/code/addons/docs/src/blocks/components/Story.tsx +++ b/code/addons/docs/src/blocks/components/Story.tsx @@ -2,17 +2,15 @@ import type { FunctionComponent } from 'react'; import React, { useEffect, useRef, useState } from 'react'; -import { ErrorFormatter, Loader, getStoryHref } from 'storybook/internal/components'; +import { ErrorFormatter, Loader } from 'storybook/internal/components'; import type { DocsContextProps, PreparedStory } from 'storybook/internal/types'; import { styled } from 'storybook/theming'; +import { getStoryHref } from '../getStoryHref'; import { IFrame } from './IFrame'; import { ZoomContext } from './ZoomContext'; -const { PREVIEW_URL } = globalThis; -const BASE_URL = PREVIEW_URL || 'iframe.html'; - interface CommonProps { story: PreparedStory; inline: boolean; @@ -98,7 +96,7 @@ const IFrameStory: FunctionComponent = ({ story, height = '500 key="iframe" id={`iframe--${story.id}`} title={story.name} - src={getStoryHref(BASE_URL, story.id, { viewMode: 'story' })} + src={getStoryHref(story.id, { viewMode: 'story' })} allowFullScreen scale={scale} style={{ diff --git a/code/addons/docs/src/blocks/components/Toolbar.tsx b/code/addons/docs/src/blocks/components/Toolbar.tsx index 18713fc474fc..afebccb47b68 100644 --- a/code/addons/docs/src/blocks/components/Toolbar.tsx +++ b/code/addons/docs/src/blocks/components/Toolbar.tsx @@ -1,12 +1,14 @@ import type { FC, SyntheticEvent } from 'react'; import React from 'react'; -import { Button, Toolbar as SharedToolbar, getStoryHref } from 'storybook/internal/components'; +import { Button, Toolbar as SharedToolbar } from 'storybook/internal/components'; import { ShareAltIcon, ZoomIcon, ZoomOutIcon, ZoomResetIcon } from '@storybook/icons'; import { styled } from 'storybook/theming'; +import { getStoryHref } from '../getStoryHref'; + interface ZoomProps { zoom: (val: number) => void; resetZoom: () => void; @@ -14,7 +16,6 @@ interface ZoomProps { interface EjectProps { storyId?: string; - baseUrl?: string; } interface BarProps { @@ -52,14 +53,7 @@ const IconPlaceholder = styled.div(({ theme }) => ({ animation: `${theme.animation.glow} 1.5s ease-in-out infinite`, })); -export const Toolbar: FC = ({ - isLoading, - storyId, - baseUrl, - zoom, - resetZoom, - ...rest -}) => ( +export const Toolbar: FC = ({ isLoading, storyId, zoom, resetZoom, ...rest }) => ( {isLoading ? ( @@ -111,7 +105,6 @@ export const Toolbar: FC = ({ ) : ( - baseUrl && storyId && ( diff --git a/code/addons/docs/src/blocks/getStoryHref.ts b/code/addons/docs/src/blocks/getStoryHref.ts new file mode 100644 index 000000000000..6651679864cd --- /dev/null +++ b/code/addons/docs/src/blocks/getStoryHref.ts @@ -0,0 +1,24 @@ +const baseUrl = globalThis.PREVIEW_URL || 'iframe.html'; +const [url, paramsStr] = baseUrl.split('?'); + +export const getStoryHref = (storyId: string, additionalParams: Record = {}) => { + const params = { + ...(paramsStr ? parseQuery(paramsStr) : {}), + ...additionalParams, + id: storyId, + }; + return `${url}?${Object.entries(params) + .map((item) => `${item[0]}=${item[1]}`) + .join('&')}`; +}; + +function parseQuery(queryString: string) { + const query: Record = {}; + const pairs = queryString.split('&'); + + for (let i = 0; i < pairs.length; i++) { + const pair = pairs[i].split('='); + query[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1] || ''); + } + return query; +} diff --git a/code/core/src/components/components/utils/getStoryHref.ts b/code/core/src/components/components/utils/getStoryHref.ts index 335bead96aa3..8784c8670667 100644 --- a/code/core/src/components/components/utils/getStoryHref.ts +++ b/code/core/src/components/components/utils/getStoryHref.ts @@ -1,3 +1,5 @@ +import { deprecate } from 'storybook/internal/client-logger'; + function parseQuery(queryString: string) { const query: Record = {}; const pairs = queryString.split('&'); @@ -9,11 +11,15 @@ function parseQuery(queryString: string) { return query; } +/** @deprecated Use the api.getStoryHrefs method instead */ export const getStoryHref = ( baseUrl: string, storyId: string, additionalParams: Record = {} ) => { + deprecate( + 'getStoryHref is deprecated and will be removed in Storybook 11, use the api.getStoryHrefs method instead' + ); const [url, paramsStr] = baseUrl.split('?'); const params = paramsStr ? { From 2a864fdb0fbfcce84ecf95a37271f4195b535104 Mon Sep 17 00:00:00 2001 From: Gert Hengeveld Date: Tue, 23 Dec 2025 12:04:05 +0100 Subject: [PATCH 02/33] Add getStoryHrefs API and use it for preview iframe URL --- code/core/src/manager-api/modules/url.ts | 99 +++++++++- code/core/src/manager-api/tests/url.test.js | 174 +++++++++++++++++- .../components/preview/FramesRenderer.tsx | 12 +- .../manager/components/preview/Preview.tsx | 4 +- .../components/preview/utils/types.tsx | 1 + 5 files changed, 275 insertions(+), 15 deletions(-) diff --git a/code/core/src/manager-api/modules/url.ts b/code/core/src/manager-api/modules/url.ts index 2eda0d3c4752..252889a85885 100644 --- a/code/core/src/manager-api/modules/url.ts +++ b/code/core/src/manager-api/modules/url.ts @@ -7,7 +7,7 @@ import { } from 'storybook/internal/core-events'; import { buildArgsParam, queryFromLocation } from 'storybook/internal/router'; import type { NavigateOptions } from 'storybook/internal/router'; -import type { API_Layout, API_UI, Args } from 'storybook/internal/types'; +import type { API_Layout, API_UI, API_ViewMode, Args } from 'storybook/internal/types'; import { global } from '@storybook/global'; @@ -16,8 +16,6 @@ import { dequal as deepEqual } from 'dequal'; import type { ModuleArgs, ModuleFn } from '../lib/types'; import { defaultLayoutState } from './layout'; -const { window: globalWindow } = global; - export interface SubState { customQueryParams: QueryParams; } @@ -33,6 +31,22 @@ const parseBoolean = (value: string) => { return undefined; }; +const parseSerializedParam = (param: string) => + Object.fromEntries( + param + .split(';') + .map((pair) => pair.split(':')) + .filter(([key, value]) => key && value) + ); + +const mergeSerializedParams = (params: string, extraParams: string) => { + const pairs = parseSerializedParam(params); + const extra = parseSerializedParam(extraParams); + return Object.entries({ ...pairs, ...extra }) + .map(([key, value]) => `${key}:${value}`) + .join(';'); +}; + // Initialize the state based on the URL. // NOTE: // Although we don't change the URL when you change the state, we do support setting initial state @@ -121,6 +135,33 @@ export interface SubAPI { * @returns {void} */ navigateUrl: (url: string, options: NavigateOptions) => void; + /** + * Get the manager and preview hrefs for a story. + * + * @param {string} storyId - The ID of the story to get the URL for. + * @param {Object} options - Options for the URL. + * @param {string} [options.base] - Return an absolute href based on the current origin or network + * address. + * @param {boolean} [options.inheritArgs] - Inherit args from the current URL. If storyId matches + * current story, inheritArgs defaults to true. + * @param {boolean} [options.inheritGlobals] - Inherit globals from the current URL. Defaults to + * true. + * @param {QueryParams} [options.queryParams] - Query params to add to the URL. + * @param {string} [options.refId] - ID of the ref to get the URL for (for composed Storybooks) + * @param {string} [options.viewMode] - The view mode to use, defaults to 'story'. + * @returns {Object} Manager and preview hrefs for the story. + */ + getStoryHrefs( + storyId: string, + options?: { + base?: 'origin' | 'network'; + inheritArgs?: boolean; + inheritGlobals?: boolean; + queryParams?: QueryParams; + refId?: string; + viewMode?: API_ViewMode; + } + ): { managerHref: string; previewHref: string }; /** * Get the value of a query parameter from the current URL. * @@ -183,6 +224,52 @@ export const init: ModuleFn = (moduleArgs) => { }; const api: SubAPI = { + getStoryHrefs(storyId, options = {}) { + const { id: currentStoryId, refId: currentRefId } = fullAPI.getCurrentStoryData() ?? {}; + const isCurrentStory = storyId === currentStoryId && options.refId === currentRefId; + + const { customQueryParams, location, refs } = store.getState(); + const { + base, + inheritArgs = isCurrentStory, + inheritGlobals = true, + queryParams = {}, + refId, + viewMode = 'story', + } = options; + + const originAddress = global.window.location.origin + location.pathname; + const networkAddress = global.STORYBOOK_NETWORK_ADDRESS ?? originAddress; + const managerBase = + base === 'origin' ? originAddress : base === 'network' ? networkAddress : location.pathname; + const previewBase = refId + ? refs[refId]?.url + '/iframe.html' + : global.PREVIEW_URL || `${managerBase}iframe.html`; + + let { args = '', globals = '' } = queryParams; + if (inheritArgs) { + const currentArgs = customQueryParams?.args; + args = mergeSerializedParams(currentArgs ?? '', args); + } + if (inheritGlobals) { + const currentGlobals = customQueryParams?.globals; + globals = mergeSerializedParams(currentGlobals ?? '', globals); + } + + // Args and globals are serialized and therefore should not be URL encoded. + // Custom query params on the other hand _should_ be URL encoded. + const argsParam = args ? `&args=${args}` : ''; + const globalsParam = globals ? `&globals=${globals}` : ''; + const customParams = Object.entries(queryParams) + .filter(([key, value]) => key !== 'args' && key !== 'globals' && value !== undefined) + .map(([key, value]) => `&${key}=${encodeURIComponent(value!)}`) + .join(''); + + return { + managerHref: `${managerBase}?path=/${viewMode}/${refId ? `${refId}_` : ''}${storyId}${argsParam}${globalsParam}${customParams}`, + previewHref: `${previewBase}?id=${storyId}&viewMode=${viewMode}${argsParam}${refId ? '' : globalsParam}${customParams}`, + }; + }, getQueryParam(key) { const { customQueryParams } = store.getState(); return customQueryParams ? customQueryParams[key] : undefined; @@ -253,11 +340,11 @@ export const init: ModuleFn = (moduleArgs) => { let handleOrId: any; provider.channel?.on(STORY_ARGS_UPDATED, () => { - if ('requestIdleCallback' in globalWindow) { + if ('requestIdleCallback' in global.window) { if (handleOrId) { - globalWindow.cancelIdleCallback(handleOrId); + global.window.cancelIdleCallback(handleOrId); } - handleOrId = globalWindow.requestIdleCallback(updateArgsParam, { timeout: 1000 }); + handleOrId = global.window.requestIdleCallback(updateArgsParam, { timeout: 1000 }); } else { if (handleOrId) { clearTimeout(handleOrId); diff --git a/code/core/src/manager-api/tests/url.test.js b/code/core/src/manager-api/tests/url.test.js index 349783223291..2f0256ce0448 100644 --- a/code/core/src/manager-api/tests/url.test.js +++ b/code/core/src/manager-api/tests/url.test.js @@ -6,11 +6,25 @@ import { UPDATE_QUERY_PARAMS, } from 'storybook/internal/core-events'; +import { global } from '@storybook/global'; + import EventEmitter from 'events'; import { init as initURL } from '../modules/url'; vi.mock('storybook/internal/client-logger'); +vi.mock('@storybook/global', () => ({ + global: { + window: { + location: { + hash: '', + href: 'http://localhost:6006', + origin: 'http://localhost:6006', + }, + }, + STORYBOOK_NETWORK_ADDRESS: 'http://192.168.1.1:6006/', + }, +})); const storyState = (storyId) => ({ path: `/story/${storyId}`, @@ -19,8 +33,6 @@ const storyState = (storyId) => ({ }); describe('initial state', () => { - const viewMode = 'story'; - describe('config query parameters', () => { it('handles full parameter', () => { const navigate = vi.fn(); @@ -236,3 +248,161 @@ describe('initModule', () => { ); }); }); + +describe('getStoryHrefs', () => { + let state = {}; + const store = { + setState: (change) => { + state = { ...state, ...change }; + }, + getState: () => state, + }; + + it('returns manager and preview URLs for a story', () => { + const { api, state } = initURL({ + store, + provider: { channel: new EventEmitter() }, + state: { location: { pathname: '/', search: '' } }, + navigate: vi.fn(), + fullAPI: { getCurrentStoryData: () => ({ id: 'test--story' }) }, + }); + store.setState(state); + + const { managerHref, previewHref } = api.getStoryHrefs('test--story'); + expect(managerHref).toEqual('/?path=/story/test--story'); + expect(previewHref).toEqual('/iframe.html?id=test--story&viewMode=story'); + }); + + it('retains args and globals from the URL', () => { + const { api, state } = initURL({ + store, + provider: { channel: new EventEmitter() }, + state: { location: { pathname: '/', search: '?args=a:1&globals=b:2' } }, + navigate: vi.fn(), + fullAPI: { getCurrentStoryData: () => ({ id: 'test--story' }) }, + }); + store.setState(state); + + const { managerHref, previewHref } = api.getStoryHrefs('test--story'); + expect(managerHref).toContain('&args=a:1&globals=b:2'); + expect(previewHref).toContain('&args=a:1&globals=b:2'); + }); + + it('drops args but retains globals when changing stories', () => { + const { api, state } = initURL({ + store, + provider: { channel: new EventEmitter() }, + state: { location: { pathname: '/', search: '?args=a:1&globals=b:2' } }, + navigate: vi.fn(), + fullAPI: { getCurrentStoryData: () => ({ id: 'test--story' }) }, + }); + store.setState(state); + + const { managerHref, previewHref } = api.getStoryHrefs('test--another-story'); + expect(managerHref).toEqual('/?path=/story/test--another-story&globals=b:2'); + expect(previewHref).toEqual('/iframe.html?id=test--another-story&viewMode=story&globals=b:2'); + }); + + it('supports disabling inheritance of args and globals', () => { + const { api, state } = initURL({ + store, + provider: { channel: new EventEmitter() }, + state: { location: { pathname: '/', search: '?args=a:1&globals=b:2' } }, + navigate: vi.fn(), + fullAPI: { getCurrentStoryData: () => ({ id: 'test--story' }) }, + }); + store.setState(state); + + const { managerHref, previewHref } = api.getStoryHrefs('test--story', { + inheritArgs: false, + inheritGlobals: false, + }); + expect(managerHref).toEqual('/?path=/story/test--story'); + expect(previewHref).toEqual('/iframe.html?id=test--story&viewMode=story'); + }); + + it('supports extra args and globals with merging', () => { + const { api, state } = initURL({ + store, + provider: { channel: new EventEmitter() }, + state: { location: { pathname: '/', search: '?args=a:1;b:2&globals=c:3;d:4' } }, + navigate: vi.fn(), + fullAPI: { getCurrentStoryData: () => ({ id: 'test--story' }) }, + }); + store.setState(state); + + const { managerHref, previewHref } = api.getStoryHrefs('test--story', { + queryParams: { args: 'a:2;c:3', globals: 'd:5' }, + }); + expect(managerHref).toContain('&args=a:2;b:2;c:3&globals=c:3;d:5'); + expect(previewHref).toContain('&args=a:2;b:2;c:3&globals=c:3;d:5'); + }); + + it('supports additional query params', () => { + const { api, state } = initURL({ + store, + provider: { channel: new EventEmitter() }, + state: { location: { pathname: '/', search: '?args=a:1&globals=b:2' } }, + navigate: vi.fn(), + fullAPI: { getCurrentStoryData: () => ({ id: 'test--story' }) }, + }); + store.setState(state); + + const { managerHref, previewHref } = api.getStoryHrefs('test--story', { + queryParams: { foo: 'bar' }, + }); + expect(managerHref).toContain('&args=a:1&globals=b:2&foo=bar'); + expect(previewHref).toContain('&args=a:1&globals=b:2&foo=bar'); + }); + + it('supports returning absolute URLs using the base option', () => { + const { api, state } = initURL({ + store, + provider: { channel: new EventEmitter() }, + state: { location: { pathname: '/', search: '' } }, + navigate: vi.fn(), + fullAPI: { getCurrentStoryData: () => ({ id: 'test--story' }) }, + }); + store.setState(state); + + const origin = api.getStoryHrefs('test--story', { base: 'origin' }); + expect(origin.managerHref).toContain('http://localhost:6006/?path='); + expect(origin.previewHref).toContain('http://localhost:6006/iframe.html'); + + const network = api.getStoryHrefs('test--story', { base: 'network' }); + expect(network.managerHref).toContain('http://192.168.1.1:6006/?path='); + expect(network.previewHref).toContain('http://192.168.1.1:6006/iframe.html'); + }); + + it('supports linking to a ref, dropping globals in preview', () => { + const { api, state } = initURL({ + store, + provider: { channel: new EventEmitter() }, + state: { location: { pathname: '/', search: '?args=a:1&globals=b:2' } }, + navigate: vi.fn(), + fullAPI: { getCurrentStoryData: () => ({ id: 'test--story' }) }, + }); + store.setState(state); + store.setState({ refs: { external: { url: 'https://sb.example.com' } } }); + + const { managerHref, previewHref } = api.getStoryHrefs('test--story', { refId: 'external' }); + expect(managerHref).toEqual('/?path=/story/external_test--story&globals=b:2'); + expect(previewHref).toEqual('https://sb.example.com/iframe.html?id=test--story&viewMode=story'); + }); + + it('supports PREVIEW_URL override', () => { + global.PREVIEW_URL = 'https://custom.preview.url/'; + const { api, state } = initURL({ + store, + provider: { channel: new EventEmitter() }, + state: { location: { pathname: '/', search: '' } }, + navigate: vi.fn(), + fullAPI: { getCurrentStoryData: () => ({ id: 'test--story' }) }, + }); + store.setState(state); + + const { managerHref, previewHref } = api.getStoryHrefs('test--story'); + expect(managerHref).toEqual('/?path=/story/test--story'); + expect(previewHref).toEqual('https://custom.preview.url/?id=test--story&viewMode=story'); + }); +}); diff --git a/code/core/src/manager/components/preview/FramesRenderer.tsx b/code/core/src/manager/components/preview/FramesRenderer.tsx index fd15d6e19a95..b66e2861c983 100644 --- a/code/core/src/manager/components/preview/FramesRenderer.tsx +++ b/code/core/src/manager/components/preview/FramesRenderer.tsx @@ -1,7 +1,7 @@ import type { FC } from 'react'; import React, { Fragment, useRef } from 'react'; -import { Button, getStoryHref } from 'storybook/internal/components'; +import { Button } from 'storybook/internal/components'; import type { Combo } from 'storybook/manager-api'; import { Consumer } from 'storybook/manager-api'; @@ -53,12 +53,12 @@ const styles: CSSObject = { }; export const FramesRenderer: FC = ({ + api, refs, scale, viewMode = 'story', refId, queryParams = {}, - baseUrl, storyId = '*', }) => { const version = refs[refId]?.version; @@ -74,11 +74,11 @@ export const FramesRenderer: FC = ({ }, {}); if (!frames['storybook-preview-iframe']) { - frames['storybook-preview-iframe'] = getStoryHref(baseUrl, storyId, { - ...queryParams, - ...(version && { version }), + frames['storybook-preview-iframe'] = api.getStoryHrefs(storyId, { + queryParams: { ...queryParams, ...(version && { version }) }, + refId, viewMode, - }); + }).preview; } refsToLoad.forEach((ref) => { diff --git a/code/core/src/manager/components/preview/Preview.tsx b/code/core/src/manager/components/preview/Preview.tsx index dc21a9699dda..2ffcaba4f939 100644 --- a/code/core/src/manager/components/preview/Preview.tsx +++ b/code/core/src/manager/components/preview/Preview.tsx @@ -20,6 +20,7 @@ import * as S from './utils/components'; import type { PreviewProps } from './utils/types'; const canvasMapper = ({ state, api }: Combo) => ({ + api, storyId: state.storyId, refId: state.refId, viewMode: state.viewMode, @@ -153,6 +154,7 @@ const Canvas: FC<{ return ( {({ + api, entry, refs, customCanvas, @@ -202,7 +204,7 @@ const Canvas: FC<{ customCanvas(storyId, viewMode, id, baseUrl, scale, queryParams) ) : ( ReactElement | null; export interface FramesRendererProps { + api: API; entry: LeafEntry; storyId: StoryId; refId: string; From 628f6700dcf69477a2e2a8eeb9ef3778020b3fea Mon Sep 17 00:00:00 2001 From: Gert Hengeveld Date: Tue, 23 Dec 2025 12:06:57 +0100 Subject: [PATCH 03/33] Add openInIsolation shortcut and update ShareMenu to utilize new API for story links --- .../core/src/manager-api/modules/shortcuts.ts | 11 +- .../preview/tools/share.stories.tsx | 11 +- .../components/preview/tools/share.tsx | 159 +++++++----------- .../src/manager/settings/defaultShortcuts.tsx | 1 + code/core/src/manager/settings/shortcuts.tsx | 1 + 5 files changed, 82 insertions(+), 101 deletions(-) diff --git a/code/core/src/manager-api/modules/shortcuts.ts b/code/core/src/manager-api/modules/shortcuts.ts index 57233fca2eaa..25c0ac2059bf 100644 --- a/code/core/src/manager-api/modules/shortcuts.ts +++ b/code/core/src/manager-api/modules/shortcuts.ts @@ -113,6 +113,7 @@ export interface API_Shortcuts { expandAll: API_KeyCollection; remount: API_KeyCollection; openInEditor: API_KeyCollection; + openInIsolation: API_KeyCollection; copyStoryLink: API_KeyCollection; // TODO: bring this back once we want to add shortcuts for this // copyStoryName: API_KeyCollection; @@ -152,6 +153,7 @@ export const defaultShortcuts: API_Shortcuts = Object.freeze({ expandAll: [controlOrMetaKey(), 'shift', 'ArrowDown'], remount: ['alt', 'R'], openInEditor: ['alt', 'shift', 'E'], + openInIsolation: ['alt', 'shift', 'I'], copyStoryLink: ['alt', 'shift', 'L'], // TODO: bring this back once we want to add shortcuts for this // copyStoryName: ['alt', 'shift', 'C'], @@ -247,6 +249,7 @@ export const init: ModuleFn = ({ store, fullAPI, provider }) => { const { ui: { enableShortcuts }, storyId, + refId, } = store.getState(); if (!enableShortcuts) { return; @@ -397,6 +400,11 @@ export const init: ModuleFn = ({ store, fullAPI, provider }) => { } break; } + case 'openInIsolation': { + const { previewHref } = fullAPI.getStoryHrefs(storyId, { refId }); + window.open(previewHref, '_blank', 'noopener,noreferrer'); + break; + } // TODO: bring this back once we want to add shortcuts for this // case 'copyStoryName': { // const storyData = fullAPI.getCurrentStoryData(); @@ -406,7 +414,8 @@ export const init: ModuleFn = ({ store, fullAPI, provider }) => { // break; // } case 'copyStoryLink': { - copy(window.location.href); + const { managerHref } = fullAPI.getStoryHrefs(storyId, { refId }); + copy(managerHref); break; } default: 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 fe0b80327030..97b934e684b6 100644 --- a/code/core/src/manager/components/preview/tools/share.stories.tsx +++ b/code/core/src/manager/components/preview/tools/share.stories.tsx @@ -13,11 +13,16 @@ const managerContext = { state: { storyId: 'manager-preview-tools-share--default', refId: undefined, - refs: {}, - customQueryParams: {}, }, api: { - getShortcutKeys: () => ({ copyStoryLink: ['alt', 'shift', 'k'] }), + getShortcutKeys: () => ({ + copyStoryLink: ['alt', 'shift', 'k'], + openInIsolation: ['alt', 'shift', 'i'], + }), + getStoryHrefs: () => ({ + manager: '/?path=/story/manager-preview-tools-share--default', + preview: '/iframe.html?id=manager-preview-tools-share--default&viewMode=story', + }), }, } 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 5517dec0b54c..b7dd9720cccc 100644 --- a/code/core/src/manager/components/preview/tools/share.tsx +++ b/code/core/src/manager/components/preview/tools/share.tsx @@ -1,43 +1,22 @@ import React, { useMemo, useState } from 'react'; -import { - Button, - PopoverProvider, - TooltipLinkList, - getStoryHref, -} from 'storybook/internal/components'; +import { Button, PopoverProvider, TooltipLinkList } from 'storybook/internal/components'; import type { Addon_BaseType } from 'storybook/internal/types'; import { global } from '@storybook/global'; -import { BugIcon, LinkIcon, ShareIcon } from '@storybook/icons'; +import { LinkIcon, ShareAltIcon, ShareIcon } from '@storybook/icons'; import copy from 'copy-to-clipboard'; import { QRCodeSVG as QRCode } from 'qrcode.react'; -import { Consumer, types, useStorybookApi } from 'storybook/manager-api'; -import type { Combo } from 'storybook/manager-api'; +import { Consumer, types } from 'storybook/manager-api'; +import type { API, 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) => { - 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 mapper = ({ api, state }: Combo) => { + const { storyId, refId } = state; + return { api, refId, storyId }; }; const QRContainer = styled.div(() => ({ @@ -77,28 +56,27 @@ const QRDescription = styled.div(({ theme }) => ({ color: theme.textMutedColor, })); -function ShareMenu({ - baseUrl, +const ShareMenu = React.memo(function ShareMenu({ + api, storyId, - queryParams, - qrUrl, - isDevelopment, + refId, }: { - baseUrl: string; + api: API; storyId: string; - queryParams: Record; - qrUrl: string; - isDevelopment: boolean; + refId: string | undefined; }) { - const api = useStorybookApi(); const shortcutKeys = api.getShortcutKeys(); const enableShortcuts = !!shortcutKeys; const [copied, setCopied] = useState(false); const copyStoryLink = shortcutKeys?.copyStoryLink; + const openInIsolation = shortcutKeys?.openInIsolation; const links = useMemo(() => { const copyTitle = copied ? 'Copied!' : 'Copy story link'; - const baseLinks = [ + const originHrefs = api.getStoryHrefs(storyId, { base: 'origin', refId }); + const networkHrefs = api.getStoryHrefs(storyId, { base: 'network', refId }); + + return [ [ { id: 'copy-link', @@ -106,7 +84,7 @@ function ShareMenu({ icon: , right: enableShortcuts ? : null, onClick: () => { - copy(window.location.href); + copy(originHrefs.managerHref); setCopied(true); setTimeout(() => setCopied(false), 2000); }, @@ -114,71 +92,58 @@ function ShareMenu({ { id: 'open-new-tab', title: 'Open in isolation mode', - icon: , - onClick: () => { - const href = getStoryHref(baseUrl, storyId, queryParams); - window.open(href, '_blank', 'noopener,noreferrer'); - }, + icon: , + right: enableShortcuts ? : null, + href: originHrefs.previewHref, + target: '_blank', + rel: 'noopener noreferrer', + }, + ], + [ + { + id: 'qr-section', + content: ( + + + + Scan to open + + {global.CONFIG_TYPE === 'DEVELOPMENT' + ? 'Device must be on the same network.' + : 'View story on another device.'} + + + + ), }, ], ]; + }, [api, storyId, refId, copied, enableShortcuts, copyStoryLink, openInIsolation]); - baseLinks.push([ - { - id: 'qr-section', - // @ts-expect-error (non strict) - content: ( - - - - Scan to open - - {isDevelopment - ? 'Device must be on the same network.' - : 'View story on another device.'} - - - - ), - }, - ]); - - return baseLinks; - }, [baseUrl, storyId, queryParams, copied, qrUrl, enableShortcuts, copyStoryLink, isDevelopment]); - - return ; -} + return ; +}); export const shareTool: Addon_BaseType = { title: 'share', id: 'share', type: types.TOOL, match: ({ viewMode, tabId }) => viewMode === 'story' && !tabId, - render: () => { - return ( - - {({ baseUrl, storyId, queryParams }) => { - const isDevelopment = global.CONFIG_TYPE === 'DEVELOPMENT'; - const storyUrl = global.STORYBOOK_NETWORK_ADDRESS - ? new URL(window.location.search, global.STORYBOOK_NETWORK_ADDRESS).href - : window.location.href; - - return storyId ? ( - - } - > - - - ) : null; - }} - - ); - }, + render: () => ( + + {({ api, storyId, refId }) => + storyId ? ( + } + > + + + ) : null + } + + ), }; diff --git a/code/core/src/manager/settings/defaultShortcuts.tsx b/code/core/src/manager/settings/defaultShortcuts.tsx index 2c7fa19e6d2e..b892f742ddd5 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'], + openInIsolation: ['alt', 'shift', 'I'], copyStoryLink: ['alt', 'shift', 'L'], // TODO: bring this back once we want to add shortcuts for this // copyStoryName: ['alt', 'shift', 'C'], diff --git a/code/core/src/manager/settings/shortcuts.tsx b/code/core/src/manager/settings/shortcuts.tsx index 9713715f3155..ebb0c4951a4c 100644 --- a/code/core/src/manager/settings/shortcuts.tsx +++ b/code/core/src/manager/settings/shortcuts.tsx @@ -133,6 +133,7 @@ const shortcutLabels = { expandAll: 'Expand all items on sidebar', remount: 'Reload story', openInEditor: 'Open story in editor', + openInIsolation: 'Open story in isolation', copyStoryLink: 'Copy story link to clipboard', // TODO: bring this back once we want to add shortcuts for this // copyStoryName: 'Copy story name to clipboard', From b9b78dde714da3793ac84589758c20b9bd711010 Mon Sep 17 00:00:00 2001 From: Gert Hengeveld Date: Tue, 23 Dec 2025 13:46:08 +0100 Subject: [PATCH 04/33] Use getStoryHrefs for ref iframes as well, and fix types --- code/core/src/manager-api/modules/url.ts | 32 ++++++++----------- code/core/src/manager-api/tests/url.test.js | 12 ++++--- .../components/preview/FramesRenderer.tsx | 17 ++++------ .../preview/utils/stringifyQueryParams.tsx | 10 ------ code/core/src/typings.d.ts | 3 ++ 5 files changed, 31 insertions(+), 43 deletions(-) delete mode 100644 code/core/src/manager/components/preview/utils/stringifyQueryParams.tsx diff --git a/code/core/src/manager-api/modules/url.ts b/code/core/src/manager-api/modules/url.ts index 252889a85885..884590325259 100644 --- a/code/core/src/manager-api/modules/url.ts +++ b/code/core/src/manager-api/modules/url.ts @@ -12,6 +12,7 @@ import type { API_Layout, API_UI, API_ViewMode, Args } from 'storybook/internal/ import { global } from '@storybook/global'; import { dequal as deepEqual } from 'dequal'; +import { stringify } from 'picoquery'; import type { ModuleArgs, ModuleFn } from '../lib/types'; import { defaultLayoutState } from './layout'; @@ -246,28 +247,23 @@ export const init: ModuleFn = (moduleArgs) => { ? refs[refId]?.url + '/iframe.html' : global.PREVIEW_URL || `${managerBase}iframe.html`; - let { args = '', globals = '' } = queryParams; - if (inheritArgs) { - const currentArgs = customQueryParams?.args; - args = mergeSerializedParams(currentArgs ?? '', args); - } - if (inheritGlobals) { - const currentGlobals = customQueryParams?.globals; - globals = mergeSerializedParams(currentGlobals ?? '', globals); - } + const refParam = refId ? `&refId=${encodeURIComponent(refId)}` : ''; + const { args = '', globals = '', ...otherParams } = queryParams; + let argsParam = inheritArgs + ? mergeSerializedParams(customQueryParams?.args ?? '', args) + : args; + let globalsParam = inheritGlobals + ? mergeSerializedParams(customQueryParams?.globals ?? '', globals) + : globals; + let customParams = stringify(otherParams); - // Args and globals are serialized and therefore should not be URL encoded. - // Custom query params on the other hand _should_ be URL encoded. - const argsParam = args ? `&args=${args}` : ''; - const globalsParam = globals ? `&globals=${globals}` : ''; - const customParams = Object.entries(queryParams) - .filter(([key, value]) => key !== 'args' && key !== 'globals' && value !== undefined) - .map(([key, value]) => `&${key}=${encodeURIComponent(value!)}`) - .join(''); + argsParam = argsParam && `&args=${argsParam}`; + globalsParam = globalsParam && `&globals=${globalsParam}`; + customParams = customParams && `&${customParams}`; return { managerHref: `${managerBase}?path=/${viewMode}/${refId ? `${refId}_` : ''}${storyId}${argsParam}${globalsParam}${customParams}`, - previewHref: `${previewBase}?id=${storyId}&viewMode=${viewMode}${argsParam}${refId ? '' : globalsParam}${customParams}`, + previewHref: `${previewBase}?id=${storyId}&viewMode=${viewMode}${refParam}${argsParam}${refId ? '' : globalsParam}${customParams}`, }; }, getQueryParam(key) { diff --git a/code/core/src/manager-api/tests/url.test.js b/code/core/src/manager-api/tests/url.test.js index 2f0256ce0448..d67bf48d93ee 100644 --- a/code/core/src/manager-api/tests/url.test.js +++ b/code/core/src/manager-api/tests/url.test.js @@ -338,7 +338,7 @@ describe('getStoryHrefs', () => { expect(previewHref).toContain('&args=a:2;b:2;c:3&globals=c:3;d:5'); }); - it('supports additional query params', () => { + it('supports additional query params, including nested objects', () => { const { api, state } = initURL({ store, provider: { channel: new EventEmitter() }, @@ -349,10 +349,10 @@ describe('getStoryHrefs', () => { store.setState(state); const { managerHref, previewHref } = api.getStoryHrefs('test--story', { - queryParams: { foo: 'bar' }, + queryParams: { one: 1, foo: { bar: 'baz' } }, }); - expect(managerHref).toContain('&args=a:1&globals=b:2&foo=bar'); - expect(previewHref).toContain('&args=a:1&globals=b:2&foo=bar'); + expect(managerHref).toContain('&args=a:1&globals=b:2&one=1&foo.bar=baz'); + expect(previewHref).toContain('&args=a:1&globals=b:2&one=1&foo.bar=baz'); }); it('supports returning absolute URLs using the base option', () => { @@ -387,7 +387,9 @@ describe('getStoryHrefs', () => { const { managerHref, previewHref } = api.getStoryHrefs('test--story', { refId: 'external' }); expect(managerHref).toEqual('/?path=/story/external_test--story&globals=b:2'); - expect(previewHref).toEqual('https://sb.example.com/iframe.html?id=test--story&viewMode=story'); + expect(previewHref).toEqual( + 'https://sb.example.com/iframe.html?id=test--story&viewMode=story&refId=external' + ); }); it('supports PREVIEW_URL override', () => { diff --git a/code/core/src/manager/components/preview/FramesRenderer.tsx b/code/core/src/manager/components/preview/FramesRenderer.tsx index b66e2861c983..03a47e247676 100644 --- a/code/core/src/manager/components/preview/FramesRenderer.tsx +++ b/code/core/src/manager/components/preview/FramesRenderer.tsx @@ -9,7 +9,6 @@ import { Global, styled } from 'storybook/theming'; import type { CSSObject } from 'storybook/theming'; import { IFrame } from './Iframe'; -import { stringifyQueryParams } from './utils/stringifyQueryParams'; import type { FramesRendererProps } from './utils/types'; const getActive = (refId: FramesRendererProps['refId'], refs: FramesRendererProps['refs']) => { @@ -62,10 +61,6 @@ export const FramesRenderer: FC = ({ storyId = '*', }) => { const version = refs[refId]?.version; - const stringifiedQueryParams = stringifyQueryParams({ - ...queryParams, - ...(version && { version }), - }); const active = getActive(refId, refs); const { current: frames } = useRef>({}); @@ -78,15 +73,17 @@ export const FramesRenderer: FC = ({ queryParams: { ...queryParams, ...(version && { version }) }, refId, viewMode, - }).preview; + }).previewHref; } refsToLoad.forEach((ref) => { const id = `storybook-ref-${ref.id}`; - const existingUrl = frames[id]?.split('/iframe.html')[0]; - if (!existingUrl || ref.url !== existingUrl) { - const newUrl = `${ref.url}/iframe.html?id=${storyId}&viewMode=${viewMode}&refId=${ref.id}${stringifiedQueryParams}`; - frames[id] = newUrl; + if (!frames[id]?.startsWith(ref.url)) { + frames[id] = api.getStoryHrefs(storyId, { + queryParams: { ...queryParams, ...(version && { version }) }, + refId: ref.id, + viewMode, + }).previewHref; } }); diff --git a/code/core/src/manager/components/preview/utils/stringifyQueryParams.tsx b/code/core/src/manager/components/preview/utils/stringifyQueryParams.tsx deleted file mode 100644 index dea058659eac..000000000000 --- a/code/core/src/manager/components/preview/utils/stringifyQueryParams.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { stringify } from 'picoquery'; - -export const stringifyQueryParams = (queryParams: Record) => { - const result = stringify(queryParams); - if (result === '') { - return ''; - } - - return `&${result}`; -}; diff --git a/code/core/src/typings.d.ts b/code/core/src/typings.d.ts index 7426e797d488..b82e449d0c25 100644 --- a/code/core/src/typings.d.ts +++ b/code/core/src/typings.d.ts @@ -15,6 +15,9 @@ declare var STORYBOOK_RENDERER: import('./types/modules/renderers').SupportedRen declare var STORYBOOK_HOOKS_CONTEXT: any; declare var STORYBOOK_CURRENT_TASK_LOG: undefined | null | Array; +declare var STORYBOOK_NETWORK_ADDRESS: string | undefined; +declare var PREVIEW_URL: string | undefined; + declare var __STORYBOOK_ADDON_INTERACTIONS_INSTRUMENTER__: any; declare var __STORYBOOK_ADDON_INTERACTIONS_INSTRUMENTER_STATE__: any; declare var __STORYBOOK_ADDONS_CHANNEL__: any; From 2800dc69f5ec78b0811be6eef62629b185c85481 Mon Sep 17 00:00:00 2001 From: Gert Hengeveld Date: Tue, 23 Dec 2025 14:22:45 +0100 Subject: [PATCH 05/33] Handle review comments --- code/addons/docs/src/blocks/getStoryHref.ts | 7 +++---- code/core/src/manager-api/modules/shortcuts.ts | 6 ++++-- code/core/src/manager-api/modules/url.ts | 6 +++++- .../src/manager/components/preview/tools/share.stories.tsx | 4 ++-- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/code/addons/docs/src/blocks/getStoryHref.ts b/code/addons/docs/src/blocks/getStoryHref.ts index 6651679864cd..283b790d95ed 100644 --- a/code/addons/docs/src/blocks/getStoryHref.ts +++ b/code/addons/docs/src/blocks/getStoryHref.ts @@ -1,14 +1,13 @@ -const baseUrl = globalThis.PREVIEW_URL || 'iframe.html'; -const [url, paramsStr] = baseUrl.split('?'); - export const getStoryHref = (storyId: string, additionalParams: Record = {}) => { + const baseUrl = globalThis.PREVIEW_URL || 'iframe.html'; + const [url, paramsStr] = baseUrl.split('?'); const params = { ...(paramsStr ? parseQuery(paramsStr) : {}), ...additionalParams, id: storyId, }; return `${url}?${Object.entries(params) - .map((item) => `${item[0]}=${item[1]}`) + .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`) .join('&')}`; }; diff --git a/code/core/src/manager-api/modules/shortcuts.ts b/code/core/src/manager-api/modules/shortcuts.ts index 25c0ac2059bf..68e2e7cc6eb0 100644 --- a/code/core/src/manager-api/modules/shortcuts.ts +++ b/code/core/src/manager-api/modules/shortcuts.ts @@ -401,8 +401,10 @@ export const init: ModuleFn = ({ store, fullAPI, provider }) => { break; } case 'openInIsolation': { - const { previewHref } = fullAPI.getStoryHrefs(storyId, { refId }); - window.open(previewHref, '_blank', 'noopener,noreferrer'); + if (storyId) { + const { previewHref } = fullAPI.getStoryHrefs(storyId, { refId }); + window.open(previewHref, '_blank', 'noopener,noreferrer'); + } break; } // TODO: bring this back once we want to add shortcuts for this diff --git a/code/core/src/manager-api/modules/url.ts b/code/core/src/manager-api/modules/url.ts index 884590325259..6e96b4cdf51a 100644 --- a/code/core/src/manager-api/modules/url.ts +++ b/code/core/src/manager-api/modules/url.ts @@ -239,12 +239,16 @@ export const init: ModuleFn = (moduleArgs) => { viewMode = 'story', } = options; + if (refId && !refs[refId]) { + throw new Error(`Invalid refId: ${refId}`); + } + const originAddress = global.window.location.origin + location.pathname; const networkAddress = global.STORYBOOK_NETWORK_ADDRESS ?? originAddress; const managerBase = base === 'origin' ? originAddress : base === 'network' ? networkAddress : location.pathname; const previewBase = refId - ? refs[refId]?.url + '/iframe.html' + ? refs[refId].url + '/iframe.html' : global.PREVIEW_URL || `${managerBase}iframe.html`; const refParam = refId ? `&refId=${encodeURIComponent(refId)}` : ''; 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 97b934e684b6..1f5d721fcb23 100644 --- a/code/core/src/manager/components/preview/tools/share.stories.tsx +++ b/code/core/src/manager/components/preview/tools/share.stories.tsx @@ -20,8 +20,8 @@ const managerContext = { openInIsolation: ['alt', 'shift', 'i'], }), getStoryHrefs: () => ({ - manager: '/?path=/story/manager-preview-tools-share--default', - preview: '/iframe.html?id=manager-preview-tools-share--default&viewMode=story', + managerHref: '/?path=/story/manager-preview-tools-share--default', + previewHref: '/iframe.html?id=manager-preview-tools-share--default&viewMode=story', }), }, } as any; From 72174ff9b4e199e311779c2637eb945c61ac9a69 Mon Sep 17 00:00:00 2001 From: Steve Dodier-Lazaro Date: Wed, 24 Dec 2025 14:24:33 +0100 Subject: [PATCH 06/33] nit: use same shortcut in story as elsewhere --- .../core/src/manager/components/preview/tools/share.stories.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 1f5d721fcb23..2a7622bde711 100644 --- a/code/core/src/manager/components/preview/tools/share.stories.tsx +++ b/code/core/src/manager/components/preview/tools/share.stories.tsx @@ -16,7 +16,7 @@ const managerContext = { }, api: { getShortcutKeys: () => ({ - copyStoryLink: ['alt', 'shift', 'k'], + copyStoryLink: ['alt', 'shift', 'l'], openInIsolation: ['alt', 'shift', 'i'], }), getStoryHrefs: () => ({ From 8ea95a994b3b9839406d778213ff62c469b46384 Mon Sep 17 00:00:00 2001 From: Steve Dodier-Lazaro Date: Wed, 24 Dec 2025 14:25:04 +0100 Subject: [PATCH 07/33] rewrite getStoryHref docs util to use Web APIs --- code/addons/docs/src/blocks/getStoryHref.ts | 28 ++++++++------------- 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/code/addons/docs/src/blocks/getStoryHref.ts b/code/addons/docs/src/blocks/getStoryHref.ts index 283b790d95ed..bde3cddbb505 100644 --- a/code/addons/docs/src/blocks/getStoryHref.ts +++ b/code/addons/docs/src/blocks/getStoryHref.ts @@ -1,23 +1,15 @@ +// Only for internal use in addon-docs code, because the parent util in `core` cannot be imported. +// Unlike the parent util, this one only returns the preview URL. export const getStoryHref = (storyId: string, additionalParams: Record = {}) => { const baseUrl = globalThis.PREVIEW_URL || 'iframe.html'; const [url, paramsStr] = baseUrl.split('?'); - const params = { - ...(paramsStr ? parseQuery(paramsStr) : {}), - ...additionalParams, - id: storyId, - }; - return `${url}?${Object.entries(params) - .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`) - .join('&')}`; -}; + const params = new URLSearchParams(paramsStr || ''); + + Object.entries(additionalParams).forEach(([key, value]) => { + params.set(key, value); + }); -function parseQuery(queryString: string) { - const query: Record = {}; - const pairs = queryString.split('&'); + params.set('id', storyId); - for (let i = 0; i < pairs.length; i++) { - const pair = pairs[i].split('='); - query[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1] || ''); - } - return query; -} + return `${url}?${params.toString()}`; +}; From 7bf5c42091b85decf14f96fadcba4e081a59c464 Mon Sep 17 00:00:00 2001 From: Steve Dodier-Lazaro Date: Wed, 24 Dec 2025 14:25:37 +0100 Subject: [PATCH 08/33] nit: clarify nesting options in getStoryHrefs --- code/core/src/manager-api/modules/url.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/code/core/src/manager-api/modules/url.ts b/code/core/src/manager-api/modules/url.ts index 6e96b4cdf51a..04d2bb324f7c 100644 --- a/code/core/src/manager-api/modules/url.ts +++ b/code/core/src/manager-api/modules/url.ts @@ -259,7 +259,10 @@ export const init: ModuleFn = (moduleArgs) => { let globalsParam = inheritGlobals ? mergeSerializedParams(customQueryParams?.globals ?? '', globals) : globals; - let customParams = stringify(otherParams); + let customParams = stringify(otherParams, { + nesting: true, + nestingSyntax: 'js', + }); argsParam = argsParam && `&args=${argsParam}`; globalsParam = globalsParam && `&globals=${globalsParam}`; From e5d6628e24445dec1ad76b05817572327e172f09 Mon Sep 17 00:00:00 2001 From: Steve Dodier-Lazaro Date: Wed, 24 Dec 2025 14:26:06 +0100 Subject: [PATCH 09/33] support encoded parameters in getStoryHrefs --- code/core/src/manager-api/modules/url.ts | 2 + code/core/src/manager-api/tests/url.test.js | 47 +++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/code/core/src/manager-api/modules/url.ts b/code/core/src/manager-api/modules/url.ts index 04d2bb324f7c..c37e95537bf3 100644 --- a/code/core/src/manager-api/modules/url.ts +++ b/code/core/src/manager-api/modules/url.ts @@ -37,6 +37,8 @@ const parseSerializedParam = (param: string) => param .split(';') .map((pair) => pair.split(':')) + // Encoding values ensures we don't break already encoded args/globals but also don't encode our own special characters like ; and :. + .map(([key, value]) => [key, encodeURIComponent(value)]) .filter(([key, value]) => key && value) ); diff --git a/code/core/src/manager-api/tests/url.test.js b/code/core/src/manager-api/tests/url.test.js index d67bf48d93ee..1b095ff0de11 100644 --- a/code/core/src/manager-api/tests/url.test.js +++ b/code/core/src/manager-api/tests/url.test.js @@ -288,6 +288,21 @@ describe('getStoryHrefs', () => { expect(previewHref).toContain('&args=a:1&globals=b:2'); }); + it('retains args with special values', () => { + const { api, state } = initURL({ + store, + provider: { channel: new EventEmitter() }, + state: { location: { pathname: '/', search: '?args=a:!null;b:!hex(f00);c:!undefined' } }, + navigate: vi.fn(), + fullAPI: { getCurrentStoryData: () => ({ id: 'test--story' }) }, + }); + store.setState(state); + + const { managerHref, previewHref } = api.getStoryHrefs('test--story'); + expect(managerHref).toContain('&args=a:!null;b:!hex(f00);c:!undefined'); + expect(previewHref).toContain('&args=a:!null;b:!hex(f00);c:!undefined'); + }); + it('drops args but retains globals when changing stories', () => { const { api, state } = initURL({ store, @@ -355,6 +370,38 @@ describe('getStoryHrefs', () => { expect(previewHref).toContain('&args=a:1&globals=b:2&one=1&foo.bar=baz'); }); + it('correctly preserves args and globals encoding', () => { + const { api, state } = initURL({ + store, + provider: { channel: new EventEmitter() }, + state: { location: { pathname: '/', search: '?args=equal:g%3Dh&globals=ampersand:c%26d' } }, + navigate: vi.fn(), + fullAPI: { getCurrentStoryData: () => ({ id: 'test--story' }) }, + }); + store.setState(state); + + const { managerHref, previewHref } = api.getStoryHrefs('test--story'); + expect(managerHref).toContain('&args=equal:g%3Dh&globals=ampersand:c%26d'); + expect(previewHref).toContain('&args=equal:g%3Dh&globals=ampersand:c%26d'); + }); + + it('correctly encodes query params', () => { + const { api, state } = initURL({ + store, + provider: { channel: new EventEmitter() }, + state: { location: { pathname: '/' } }, + navigate: vi.fn(), + fullAPI: { getCurrentStoryData: () => ({ id: 'test--story' }) }, + }); + store.setState(state); + + const { managerHref, previewHref } = api.getStoryHrefs('test--story', { + queryParams: { equal: 'a=b', ampersand: 'c&d' }, + }); + expect(managerHref).toContain('&equal=a%3Db&ersand=c%26d'); + expect(previewHref).toContain('&equal=a%3Db&ersand=c%26d'); + }); + it('supports returning absolute URLs using the base option', () => { const { api, state } = initURL({ store, From d474f5f93a3508e6cd4eff55f1321b62eca832e4 Mon Sep 17 00:00:00 2001 From: Gert Hengeveld Date: Tue, 23 Dec 2025 14:11:26 +0100 Subject: [PATCH 10/33] Consistently use 'disableSnapshot' rather than 'disable' for Chromatic config --- MIGRATION.md | 2 +- code/addons/a11y/template/stories/parameters.stories.ts | 2 +- code/addons/a11y/template/stories/tests.stories.ts | 2 +- code/addons/docs/src/blocks/examples/Button.stories.tsx | 2 +- .../docs/src/blocks/examples/ButtonNoAutodocs.stories.tsx | 2 +- .../src/blocks/examples/ButtonSomeAutodocs.stories.tsx | 2 +- .../docs/template/stories/codePanel/index.stories.tsx | 2 +- code/addons/docs/template/stories/docs2/button.stories.ts | 2 +- .../stories/docs2/multiple-csf-files-a.stories.ts | 2 +- .../stories/docs2/multiple-csf-files-b.stories.ts | 2 +- .../docs/template/stories/docs2/resolved-react.stories.ts | 2 +- .../docs/template/stories/docspage/autoplay.stories.ts | 2 +- .../docs/template/stories/docspage/basic.stories.ts | 2 +- .../docs/template/stories/docspage/description.stories.ts | 2 +- .../docs/template/stories/docspage/error.stories.ts | 2 +- .../stories/docspage/extract-description.stories.ts | 2 +- .../docs/template/stories/docspage/iframe.stories.ts | 2 +- .../docs/template/stories/docspage/overflow.stories.ts | 2 +- .../docs/template/stories/docspage/override.stories.ts | 2 +- .../docs/template/stories/docspage/source.stories.ts | 2 +- code/addons/docs/template/stories/toc/basic.stories.ts | 2 +- .../docs/template/stories/toc/custom-selector.stories.ts | 2 +- .../docs/template/stories/toc/custom-title.stories.ts | 2 +- .../docs/template/stories/toc/ignore-selector.stories.ts | 2 +- code/addons/links/template/stories/decorator.stories.ts | 4 ++-- code/addons/links/template/stories/hrefto.stories.ts | 2 +- code/addons/links/template/stories/linkto.stories.ts | 4 ++-- code/addons/themes/template/stories/decorators.stories.ts | 2 +- code/addons/themes/template/stories/globals.stories.ts | 2 +- code/addons/themes/template/stories/parameters.stories.ts | 2 +- .../vitest/template/stories/unhandled-errors.stories.ts | 2 +- .../mobile/navigation/MobileNavigation.stories.tsx | 2 +- .../src/manager/components/preview/Iframe.stories.tsx | 8 ++++---- .../manager/components/sidebar/SidebarBottom.stories.tsx | 2 +- code/core/template/stories/argtype.stories.ts | 2 +- code/core/template/stories/backgrounds/globals.stories.ts | 2 +- code/core/template/stories/basics.stories.ts | 2 +- code/core/template/stories/before-each.stories.ts | 6 +++--- .../stories/component-test.unhandled-errors.stories.ts | 2 +- code/core/template/stories/configs.stories.ts | 2 +- code/core/template/stories/controls/sorting.stories.ts | 2 +- .../stories/destructuring-not-transpiled.stories.ts | 2 +- code/core/template/stories/loader-enhancements.stories.ts | 2 +- code/core/template/stories/moduleMocking.stories.ts | 2 +- code/core/template/stories/mount-in-play.stories.ts | 2 +- code/core/template/stories/parameters-actions.stories.ts | 2 +- code/core/template/stories/rendering.stories.ts | 2 +- code/core/template/stories/spies.stories.ts | 2 +- code/core/template/stories/tags-add.stories.ts | 4 ++-- code/core/template/stories/tags-config.stories.ts | 4 ++-- code/core/template/stories/tags-remove.stories.ts | 4 ++-- code/core/template/stories/viewport/globals.stories.ts | 2 +- .../component-with-on-destroy.stories.ts | 2 +- .../nextjs-vite/template/stories/RSC.stories.tsx | 4 ++-- code/frameworks/nextjs/template/stories/RSC.stories.tsx | 4 ++-- code/renderers/react/template/stories/csf1.stories.tsx | 2 +- code/renderers/react/template/stories/csf2.stories.tsx | 2 +- code/renderers/react/template/stories/errors.stories.tsx | 2 +- .../renderers/react/template/stories/teardown.stories.tsx | 2 +- 59 files changed, 71 insertions(+), 71 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index adb41e1c2f99..0805c3682b8b 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -3668,7 +3668,7 @@ export default { label: 'Click Me!', }, parameters: { - chromatic: { disable: true }, + chromatic: { disableSnapshot: true }, }, }; export const Basic = { diff --git a/code/addons/a11y/template/stories/parameters.stories.ts b/code/addons/a11y/template/stories/parameters.stories.ts index ac11fd1469dd..59a772a90ddd 100644 --- a/code/addons/a11y/template/stories/parameters.stories.ts +++ b/code/addons/a11y/template/stories/parameters.stories.ts @@ -6,7 +6,7 @@ export default { content: '', }, parameters: { - chromatic: { disable: true }, + chromatic: { disableSnapshot: true }, a11y: { test: 'error', }, diff --git a/code/addons/a11y/template/stories/tests.stories.ts b/code/addons/a11y/template/stories/tests.stories.ts index 1c92290264a4..fb1fabda9fe3 100644 --- a/code/addons/a11y/template/stories/tests.stories.ts +++ b/code/addons/a11y/template/stories/tests.stories.ts @@ -6,7 +6,7 @@ export default { content: '', }, parameters: { - chromatic: { disable: true }, + chromatic: { disableSnapshot: true }, }, }; diff --git a/code/addons/docs/src/blocks/examples/Button.stories.tsx b/code/addons/docs/src/blocks/examples/Button.stories.tsx index 73f59e5188da..48ab130420e3 100644 --- a/code/addons/docs/src/blocks/examples/Button.stories.tsx +++ b/code/addons/docs/src/blocks/examples/Button.stories.tsx @@ -136,7 +136,7 @@ export const ErrorStory: Story = { }, args: { label: 'Button' }, parameters: { - chromatic: { disable: true }, + chromatic: { disableSnapshot: true }, }, tags: ['!test', '!vitest'], }; diff --git a/code/addons/docs/src/blocks/examples/ButtonNoAutodocs.stories.tsx b/code/addons/docs/src/blocks/examples/ButtonNoAutodocs.stories.tsx index ac771a2c43d3..f3115eb38889 100644 --- a/code/addons/docs/src/blocks/examples/ButtonNoAutodocs.stories.tsx +++ b/code/addons/docs/src/blocks/examples/ButtonNoAutodocs.stories.tsx @@ -9,7 +9,7 @@ const meta = { backgroundColor: { control: 'color' }, }, parameters: { - chromatic: { disable: true }, + chromatic: { disableSnapshot: true }, }, } satisfies Meta; diff --git a/code/addons/docs/src/blocks/examples/ButtonSomeAutodocs.stories.tsx b/code/addons/docs/src/blocks/examples/ButtonSomeAutodocs.stories.tsx index 0065bf04761b..d691e2b8f91b 100644 --- a/code/addons/docs/src/blocks/examples/ButtonSomeAutodocs.stories.tsx +++ b/code/addons/docs/src/blocks/examples/ButtonSomeAutodocs.stories.tsx @@ -9,7 +9,7 @@ const meta = { backgroundColor: { control: 'color' }, }, parameters: { - chromatic: { disable: true }, + chromatic: { disableSnapshot: true }, }, } satisfies Meta; diff --git a/code/addons/docs/template/stories/codePanel/index.stories.tsx b/code/addons/docs/template/stories/codePanel/index.stories.tsx index bdb368a45b96..87daa4c6ed46 100644 --- a/code/addons/docs/template/stories/codePanel/index.stories.tsx +++ b/code/addons/docs/template/stories/codePanel/index.stories.tsx @@ -2,7 +2,7 @@ export default { component: globalThis.__TEMPLATE_COMPONENTS__.Button, tags: ['autodocs'], parameters: { - chromatic: { disable: true }, + chromatic: { disableSnapshot: true }, docs: { codePanel: true, }, diff --git a/code/addons/docs/template/stories/docs2/button.stories.ts b/code/addons/docs/template/stories/docs2/button.stories.ts index bb2bc66fbe33..ce289543933e 100644 --- a/code/addons/docs/template/stories/docs2/button.stories.ts +++ b/code/addons/docs/template/stories/docs2/button.stories.ts @@ -3,7 +3,7 @@ export default { tags: ['autodocs'], args: { onClick: () => console.log('clicked!') }, parameters: { - chromatic: { disable: true }, + chromatic: { disableSnapshot: true }, }, }; diff --git a/code/addons/docs/template/stories/docs2/multiple-csf-files-a.stories.ts b/code/addons/docs/template/stories/docs2/multiple-csf-files-a.stories.ts index 92b132b1f17d..c1b2a4e3f9a1 100644 --- a/code/addons/docs/template/stories/docs2/multiple-csf-files-a.stories.ts +++ b/code/addons/docs/template/stories/docs2/multiple-csf-files-a.stories.ts @@ -6,7 +6,7 @@ export default { content: '

paragraph

', }, parameters: { - chromatic: { disable: true }, + chromatic: { disableSnapshot: true }, }, }; diff --git a/code/addons/docs/template/stories/docs2/multiple-csf-files-b.stories.ts b/code/addons/docs/template/stories/docs2/multiple-csf-files-b.stories.ts index 37af4e2658e6..f12d59f064e9 100644 --- a/code/addons/docs/template/stories/docs2/multiple-csf-files-b.stories.ts +++ b/code/addons/docs/template/stories/docs2/multiple-csf-files-b.stories.ts @@ -6,7 +6,7 @@ export default { content: '

paragraph

', }, parameters: { - chromatic: { disable: true }, + chromatic: { disableSnapshot: true }, }, }; diff --git a/code/addons/docs/template/stories/docs2/resolved-react.stories.ts b/code/addons/docs/template/stories/docs2/resolved-react.stories.ts index 3fddc6184373..6c017e3182e1 100644 --- a/code/addons/docs/template/stories/docs2/resolved-react.stories.ts +++ b/code/addons/docs/template/stories/docs2/resolved-react.stories.ts @@ -49,7 +49,7 @@ export default { name: 'ResolvedReact', }, // the version string changes with every release of React/Next.js/Preact, not worth snapshotting - chromatic: { disable: true }, + chromatic: { disableSnapshot: true }, }, }; diff --git a/code/addons/docs/template/stories/docspage/autoplay.stories.ts b/code/addons/docs/template/stories/docspage/autoplay.stories.ts index a8271c9d1745..6c69b322e90d 100644 --- a/code/addons/docs/template/stories/docspage/autoplay.stories.ts +++ b/code/addons/docs/template/stories/docspage/autoplay.stories.ts @@ -4,7 +4,7 @@ export default { component: globalThis.__TEMPLATE_COMPONENTS__.Pre, tags: ['autodocs'], args: { text: 'Play has not run' }, - parameters: { chromatic: { disable: true } }, + parameters: { chromatic: { disableSnapshot: true } }, }; // Should not autoplay diff --git a/code/addons/docs/template/stories/docspage/basic.stories.ts b/code/addons/docs/template/stories/docspage/basic.stories.ts index 940ad0055876..f6563bb6e287 100644 --- a/code/addons/docs/template/stories/docspage/basic.stories.ts +++ b/code/addons/docs/template/stories/docspage/basic.stories.ts @@ -4,7 +4,7 @@ export default { component: globalThis.__TEMPLATE_COMPONENTS__.Button, tags: ['autodocs'], args: { label: 'Click Me!', onClick: fn() }, - parameters: { chromatic: { disable: true } }, + parameters: { chromatic: { disableSnapshot: true } }, }; /** A basic button */ diff --git a/code/addons/docs/template/stories/docspage/description.stories.ts b/code/addons/docs/template/stories/docspage/description.stories.ts index 8dd8bb0988a3..c896b5b42f28 100644 --- a/code/addons/docs/template/stories/docspage/description.stories.ts +++ b/code/addons/docs/template/stories/docspage/description.stories.ts @@ -11,7 +11,7 @@ export default { component: '**Component** description', }, }, - chromatic: { disable: true }, + chromatic: { disableSnapshot: true }, }, }; diff --git a/code/addons/docs/template/stories/docspage/error.stories.ts b/code/addons/docs/template/stories/docspage/error.stories.ts index 7114a8a65969..100b4dc5879a 100644 --- a/code/addons/docs/template/stories/docspage/error.stories.ts +++ b/code/addons/docs/template/stories/docspage/error.stories.ts @@ -3,7 +3,7 @@ export default { component: globalThis.__TEMPLATE_COMPONENTS__.Button, tags: ['autodocs', '!test', '!vitest'], args: { label: 'Click Me!' }, - parameters: { chromatic: { disable: true } }, + parameters: { chromatic: { disableSnapshot: true } }, }; /** A story that throws */ diff --git a/code/addons/docs/template/stories/docspage/extract-description.stories.ts b/code/addons/docs/template/stories/docspage/extract-description.stories.ts index 8db2dcdabeda..aa6f730cd979 100644 --- a/code/addons/docs/template/stories/docspage/extract-description.stories.ts +++ b/code/addons/docs/template/stories/docspage/extract-description.stories.ts @@ -9,7 +9,7 @@ export default { // for documentation purposes only. extractComponentDescription: () => 'component description', }, - chromatic: { disable: true }, + chromatic: { disableSnapshot: true }, }, }; diff --git a/code/addons/docs/template/stories/docspage/iframe.stories.ts b/code/addons/docs/template/stories/docspage/iframe.stories.ts index 685668a4d660..fbeb26981644 100644 --- a/code/addons/docs/template/stories/docspage/iframe.stories.ts +++ b/code/addons/docs/template/stories/docspage/iframe.stories.ts @@ -3,7 +3,7 @@ export default { tags: ['autodocs'], args: { label: 'Rendered in iframe' }, parameters: { - chromatic: { disable: true }, + chromatic: { disableSnapshot: true }, docs: { story: { iframeHeight: '120px', diff --git a/code/addons/docs/template/stories/docspage/overflow.stories.ts b/code/addons/docs/template/stories/docspage/overflow.stories.ts index 6a02c43b5702..12a25dd127b7 100644 --- a/code/addons/docs/template/stories/docspage/overflow.stories.ts +++ b/code/addons/docs/template/stories/docspage/overflow.stories.ts @@ -5,7 +5,7 @@ export default { text: 'Demonstrates overflow', style: { width: 2000, height: 500, background: 'hotpink' }, }, - parameters: { chromatic: { disable: true } }, + parameters: { chromatic: { disableSnapshot: true } }, }; export const Basic = {}; diff --git a/code/addons/docs/template/stories/docspage/override.stories.ts b/code/addons/docs/template/stories/docspage/override.stories.ts index dc8e926b0b47..439201374003 100644 --- a/code/addons/docs/template/stories/docspage/override.stories.ts +++ b/code/addons/docs/template/stories/docspage/override.stories.ts @@ -16,7 +16,7 @@ export default { tags: ['autodocs'], args: { label: 'Click Me!' }, parameters: { - chromatic: { disable: true }, + chromatic: { disableSnapshot: true }, docs: { page: Override }, }, }; diff --git a/code/addons/docs/template/stories/docspage/source.stories.ts b/code/addons/docs/template/stories/docspage/source.stories.ts index 940b44454f23..172072421a84 100644 --- a/code/addons/docs/template/stories/docspage/source.stories.ts +++ b/code/addons/docs/template/stories/docspage/source.stories.ts @@ -6,7 +6,7 @@ export default { component: globalThis.__TEMPLATE_COMPONENTS__.Button, tags: ['autodocs'], args: { label: 'Click Me!' }, - parameters: { chromatic: { disable: true } }, + parameters: { chromatic: { disableSnapshot: true } }, }; export const Auto = {}; diff --git a/code/addons/docs/template/stories/toc/basic.stories.ts b/code/addons/docs/template/stories/toc/basic.stories.ts index 4f9ef34272f6..80c8aaed3dff 100644 --- a/code/addons/docs/template/stories/toc/basic.stories.ts +++ b/code/addons/docs/template/stories/toc/basic.stories.ts @@ -2,7 +2,7 @@ export default { component: globalThis.__TEMPLATE_COMPONENTS__.Button, tags: ['autodocs'], parameters: { - chromatic: { disable: true }, + chromatic: { disableSnapshot: true }, docs: { toc: {} }, }, }; diff --git a/code/addons/docs/template/stories/toc/custom-selector.stories.ts b/code/addons/docs/template/stories/toc/custom-selector.stories.ts index 530844a22398..ce87e4ea719b 100644 --- a/code/addons/docs/template/stories/toc/custom-selector.stories.ts +++ b/code/addons/docs/template/stories/toc/custom-selector.stories.ts @@ -2,7 +2,7 @@ export default { component: globalThis.__TEMPLATE_COMPONENTS__.Button, tags: ['autodocs'], parameters: { - chromatic: { disable: true }, + chromatic: { disableSnapshot: true }, // Select all the headings in the document docs: { toc: { headingSelector: 'h1, h2, h3' } }, }, diff --git a/code/addons/docs/template/stories/toc/custom-title.stories.ts b/code/addons/docs/template/stories/toc/custom-title.stories.ts index 49b7784c8f0f..3255255bd079 100644 --- a/code/addons/docs/template/stories/toc/custom-title.stories.ts +++ b/code/addons/docs/template/stories/toc/custom-title.stories.ts @@ -2,7 +2,7 @@ export default { component: globalThis.__TEMPLATE_COMPONENTS__.Button, tags: ['autodocs'], parameters: { - chromatic: { disable: true }, + chromatic: { disableSnapshot: true }, // Custom title label docs: { toc: { title: 'Contents' } }, }, diff --git a/code/addons/docs/template/stories/toc/ignore-selector.stories.ts b/code/addons/docs/template/stories/toc/ignore-selector.stories.ts index 300daf2c0f97..ad922de36529 100644 --- a/code/addons/docs/template/stories/toc/ignore-selector.stories.ts +++ b/code/addons/docs/template/stories/toc/ignore-selector.stories.ts @@ -2,7 +2,7 @@ export default { component: globalThis.__TEMPLATE_COMPONENTS__.Button, tags: ['autodocs'], parameters: { - chromatic: { disable: true }, + chromatic: { disableSnapshot: true }, // Skip the first story in the TOC docs: { toc: { ignoreSelector: '#one' } }, }, diff --git a/code/addons/links/template/stories/decorator.stories.ts b/code/addons/links/template/stories/decorator.stories.ts index 74f40bda6936..7a5ced861412 100644 --- a/code/addons/links/template/stories/decorator.stories.ts +++ b/code/addons/links/template/stories/decorator.stories.ts @@ -5,7 +5,7 @@ import { withLinks } from '@storybook/addon-links'; export default { component: globalThis.__TEMPLATE_COMPONENTS__.Html, parameters: { - chromatic: { disable: true }, + chromatic: { disableSnapshot: true }, }, decorators: [withLinks], }; @@ -19,7 +19,7 @@ export const Target = { `, }, parameters: { - chromatic: { disable: true }, + chromatic: { disableSnapshot: true }, }, }; diff --git a/code/addons/links/template/stories/hrefto.stories.ts b/code/addons/links/template/stories/hrefto.stories.ts index 2371daafd040..5bed666702c4 100644 --- a/code/addons/links/template/stories/hrefto.stories.ts +++ b/code/addons/links/template/stories/hrefto.stories.ts @@ -4,7 +4,7 @@ export default { component: globalThis.__TEMPLATE_COMPONENTS__.Html, title: 'hrefTo', parameters: { - chromatic: { disable: true }, + chromatic: { disableSnapshot: true }, }, args: { content: '
Waiting for hrefTo to resolve...
', diff --git a/code/addons/links/template/stories/linkto.stories.ts b/code/addons/links/template/stories/linkto.stories.ts index 6bf541639af4..0208c7d06aca 100644 --- a/code/addons/links/template/stories/linkto.stories.ts +++ b/code/addons/links/template/stories/linkto.stories.ts @@ -9,7 +9,7 @@ export default { label: 'Click Me!', }, parameters: { - chromatic: { disable: true }, + chromatic: { disableSnapshot: true }, }, }; @@ -18,7 +18,7 @@ export const Target = { label: 'This is just a story to target with the links', }, parameters: { - chromatic: { disable: true }, + chromatic: { disableSnapshot: true }, }, }; diff --git a/code/addons/themes/template/stories/decorators.stories.ts b/code/addons/themes/template/stories/decorators.stories.ts index 5399acf1ecd7..dd1ea82127ba 100644 --- a/code/addons/themes/template/stories/decorators.stories.ts +++ b/code/addons/themes/template/stories/decorators.stories.ts @@ -49,7 +49,7 @@ export default { sb_theme: 'light', }, parameters: { - chromatic: { disable: true }, + chromatic: { disableSnapshot: true }, themes: { disable: false }, }, decorators: [addStyleSheetDecorator], diff --git a/code/addons/themes/template/stories/globals.stories.ts b/code/addons/themes/template/stories/globals.stories.ts index 6f4577b68b1a..93aaa39becae 100644 --- a/code/addons/themes/template/stories/globals.stories.ts +++ b/code/addons/themes/template/stories/globals.stories.ts @@ -42,7 +42,7 @@ export default { text: 'Testing the themes', }, parameters: { - chromatic: { disable: true }, + chromatic: { disableSnapshot: true }, themes: { disable: false }, }, globals: { diff --git a/code/addons/themes/template/stories/parameters.stories.ts b/code/addons/themes/template/stories/parameters.stories.ts index eee779be2ed4..0e9a5f49ee12 100644 --- a/code/addons/themes/template/stories/parameters.stories.ts +++ b/code/addons/themes/template/stories/parameters.stories.ts @@ -45,7 +45,7 @@ export default { sb_theme: 'light', }, parameters: { - chromatic: { disable: true }, + chromatic: { disableSnapshot: true }, themes: { disable: false }, }, decorators: [addStyleSheetDecorator], diff --git a/code/addons/vitest/template/stories/unhandled-errors.stories.ts b/code/addons/vitest/template/stories/unhandled-errors.stories.ts index 01103f88847c..48a554feaaff 100644 --- a/code/addons/vitest/template/stories/unhandled-errors.stories.ts +++ b/code/addons/vitest/template/stories/unhandled-errors.stories.ts @@ -13,7 +13,7 @@ export default { }, parameters: { actions: { argTypesRegex: '^on[A-Z].*' }, - chromatic: { disable: true }, + chromatic: { disableSnapshot: true }, }, tags: ['!test', '!vitest'], }; diff --git a/code/core/src/manager/components/mobile/navigation/MobileNavigation.stories.tsx b/code/core/src/manager/components/mobile/navigation/MobileNavigation.stories.tsx index 804af1d9bbc4..e8943f1dcd9b 100644 --- a/code/core/src/manager/components/mobile/navigation/MobileNavigation.stories.tsx +++ b/code/core/src/manager/components/mobile/navigation/MobileNavigation.stories.tsx @@ -114,7 +114,7 @@ export const Default: Story = { }; export const Dark: Story = { globals: { sb_theme: 'dark' }, - parameters: { chromatic: { disable: true } }, + parameters: { chromatic: { disableSnapshot: true } }, }; export const LongStoryName: Story = { diff --git a/code/core/src/manager/components/preview/Iframe.stories.tsx b/code/core/src/manager/components/preview/Iframe.stories.tsx index 84367478c4ec..2390ca501774 100644 --- a/code/core/src/manager/components/preview/Iframe.stories.tsx +++ b/code/core/src/manager/components/preview/Iframe.stories.tsx @@ -42,7 +42,7 @@ export const WorkingStory = () => ( /> ); WorkingStory.parameters = { - chromatic: { disable: true }, + chromatic: { disableSnapshot: true }, }; export const WorkingDocs = () => ( @@ -57,7 +57,7 @@ export const WorkingDocs = () => ( /> ); WorkingStory.parameters = { - chromatic: { disable: true }, + chromatic: { disableSnapshot: true }, }; export const MissingStory = { @@ -90,7 +90,7 @@ export const PreparingStory = () => ( /> ); PreparingStory.parameters = { - chromatic: { disable: true }, + chromatic: { disableSnapshot: true }, }; export const PreparingDocs = () => ( @@ -105,5 +105,5 @@ export const PreparingDocs = () => ( /> ); PreparingDocs.parameters = { - chromatic: { disable: true }, + chromatic: { disableSnapshot: true }, }; diff --git a/code/core/src/manager/components/sidebar/SidebarBottom.stories.tsx b/code/core/src/manager/components/sidebar/SidebarBottom.stories.tsx index ab447ff38d45..ddaa85831d71 100644 --- a/code/core/src/manager/components/sidebar/SidebarBottom.stories.tsx +++ b/code/core/src/manager/components/sidebar/SidebarBottom.stories.tsx @@ -137,7 +137,7 @@ export const DynamicHeight: Story = { // do not test in chromatic parameters: { chromatic: { - disable: true, + disableSnapshot: true, }, }, args: { diff --git a/code/core/template/stories/argtype.stories.ts b/code/core/template/stories/argtype.stories.ts index e642636e7e85..f977d59b5273 100644 --- a/code/core/template/stories/argtype.stories.ts +++ b/code/core/template/stories/argtype.stories.ts @@ -9,7 +9,7 @@ export default { onClick: {}, }, parameters: { - chromatic: { disable: true }, + chromatic: { disableSnapshot: true }, actions: { argTypesRegex: '^on.*' }, }, }; diff --git a/code/core/template/stories/backgrounds/globals.stories.ts b/code/core/template/stories/backgrounds/globals.stories.ts index 68410d6079fe..0a1ee96dad13 100644 --- a/code/core/template/stories/backgrounds/globals.stories.ts +++ b/code/core/template/stories/backgrounds/globals.stories.ts @@ -6,7 +6,7 @@ export default { text: 'Testing the background', }, parameters: { - chromatic: { disable: true }, + chromatic: { disableSnapshot: true }, backgrounds: { options: { red: { name: 'light', value: 'red' }, diff --git a/code/core/template/stories/basics.stories.ts b/code/core/template/stories/basics.stories.ts index 024fb449ee45..354bacc466c9 100644 --- a/code/core/template/stories/basics.stories.ts +++ b/code/core/template/stories/basics.stories.ts @@ -8,7 +8,7 @@ export default { label: 'Click Me!', }, parameters: { - chromatic: { disable: true }, + chromatic: { disableSnapshot: true }, }, }; diff --git a/code/core/template/stories/before-each.stories.ts b/code/core/template/stories/before-each.stories.ts index 485be7ec8b96..4dc557af3c87 100644 --- a/code/core/template/stories/before-each.stories.ts +++ b/code/core/template/stories/before-each.stories.ts @@ -9,7 +9,7 @@ const meta = { export default meta; export const before_each_and_loaders_can_extend_context = { - parameters: { chromatic: { disable: true } }, + parameters: { chromatic: { disableSnapshot: true } }, loaders(context) { context.foo = ['bar']; }, @@ -22,14 +22,14 @@ export const before_each_and_loaders_can_extend_context = { }; export const context_prop_is_available = { - parameters: { chromatic: { disable: true } }, + parameters: { chromatic: { disableSnapshot: true } }, async play({ context, canvasElement }) { await expect(context.canvasElement).toEqual(canvasElement); }, }; export const step_and_canvas_element_can_be_used_in_loaders_and_before_each = { - parameters: { chromatic: { disable: true } }, + parameters: { chromatic: { disableSnapshot: true } }, loaders({ step, canvasElement }) { step('loaders', async () => { await expect(canvasElement).toBeInTheDocument(); diff --git a/code/core/template/stories/component-test.unhandled-errors.stories.ts b/code/core/template/stories/component-test.unhandled-errors.stories.ts index 01103f88847c..48a554feaaff 100644 --- a/code/core/template/stories/component-test.unhandled-errors.stories.ts +++ b/code/core/template/stories/component-test.unhandled-errors.stories.ts @@ -13,7 +13,7 @@ export default { }, parameters: { actions: { argTypesRegex: '^on[A-Z].*' }, - chromatic: { disable: true }, + chromatic: { disableSnapshot: true }, }, tags: ['!test', '!vitest'], }; diff --git a/code/core/template/stories/configs.stories.ts b/code/core/template/stories/configs.stories.ts index fb3e33b3903b..33742a79a0d8 100644 --- a/code/core/template/stories/configs.stories.ts +++ b/code/core/template/stories/configs.stories.ts @@ -10,7 +10,7 @@ export default { label: 'Click Me!', }, parameters: { - chromatic: { disable: true }, + chromatic: { disableSnapshot: true }, }, }; diff --git a/code/core/template/stories/controls/sorting.stories.ts b/code/core/template/stories/controls/sorting.stories.ts index 7faf74c12e7b..5da2290d99fa 100644 --- a/code/core/template/stories/controls/sorting.stories.ts +++ b/code/core/template/stories/controls/sorting.stories.ts @@ -24,7 +24,7 @@ export default { b: 'b', c: 'c', }, - parameters: { chromatic: { disable: true } }, + parameters: { chromatic: { disableSnapshot: true } }, }; export const None = { parameters: { controls: { sort: 'none' } } }; diff --git a/code/core/template/stories/destructuring-not-transpiled.stories.ts b/code/core/template/stories/destructuring-not-transpiled.stories.ts index 440a2a26398b..bcfa2b0bc2d9 100644 --- a/code/core/template/stories/destructuring-not-transpiled.stories.ts +++ b/code/core/template/stories/destructuring-not-transpiled.stories.ts @@ -10,7 +10,7 @@ export default { // We must not transpile destructuring, to make sure that we can analyze the context properties that are used in play. // See: https://github.com/storybookjs/storybook/discussions/27389 export const DestructureNotTranspiled = { - parameters: { chromatic: { disable: true } }, + parameters: { chromatic: { disableSnapshot: true } }, async play() { async function fn({ destructured }: { destructured: unknown }) { console.log(destructured); diff --git a/code/core/template/stories/loader-enhancements.stories.ts b/code/core/template/stories/loader-enhancements.stories.ts index 638166713de9..619ecf4e56de 100644 --- a/code/core/template/stories/loader-enhancements.stories.ts +++ b/code/core/template/stories/loader-enhancements.stories.ts @@ -3,7 +3,7 @@ import { expect, within } from 'storybook/test'; const meta = { component: globalThis.__TEMPLATE_COMPONENTS__.Button, - parameters: { chromatic: { disable: true } }, + parameters: { chromatic: { disableSnapshot: true } }, args: { label: 'Button' }, }; diff --git a/code/core/template/stories/moduleMocking.stories.ts b/code/core/template/stories/moduleMocking.stories.ts index bbbe09d6b5ad..4fdb186a3165 100644 --- a/code/core/template/stories/moduleMocking.stories.ts +++ b/code/core/template/stories/moduleMocking.stories.ts @@ -13,7 +13,7 @@ export default { }, parameters: { chromatic: { - disable: true, + disableSnapshot: true, }, }, beforeEach: () => { diff --git a/code/core/template/stories/mount-in-play.stories.ts b/code/core/template/stories/mount-in-play.stories.ts index 5906b34ea6a9..5a3564175ee8 100644 --- a/code/core/template/stories/mount-in-play.stories.ts +++ b/code/core/template/stories/mount-in-play.stories.ts @@ -5,7 +5,7 @@ const meta = { component: globalThis.__TEMPLATE_COMPONENTS__.Button }; export default meta; export const MountShouldBeDestructured = { - parameters: { chromatic: { disable: true } }, + parameters: { chromatic: { disableSnapshot: true } }, args: { label: 'Button', onClick: fn(), diff --git a/code/core/template/stories/parameters-actions.stories.ts b/code/core/template/stories/parameters-actions.stories.ts index 94052223bb20..80be42c13e6f 100644 --- a/code/core/template/stories/parameters-actions.stories.ts +++ b/code/core/template/stories/parameters-actions.stories.ts @@ -8,7 +8,7 @@ export default { label: 'Click Me!', }, parameters: { - chromatic: { disable: true }, + chromatic: { disableSnapshot: true }, }, }; diff --git a/code/core/template/stories/rendering.stories.ts b/code/core/template/stories/rendering.stories.ts index b3e584bf62b2..8d98901d6396 100644 --- a/code/core/template/stories/rendering.stories.ts +++ b/code/core/template/stories/rendering.stories.ts @@ -54,7 +54,7 @@ let loadedLabel = 'Initial'; */ export const SlowLoader = { parameters: { - chromatic: { disable: true }, + chromatic: { disableSnapshot: true }, }, loaders: [ async () => { diff --git a/code/core/template/stories/spies.stories.ts b/code/core/template/stories/spies.stories.ts index e74f89c95c7c..002898408ca2 100644 --- a/code/core/template/stories/spies.stories.ts +++ b/code/core/template/stories/spies.stories.ts @@ -14,7 +14,7 @@ export default meta; export const ShowSpyOnInActions = { parameters: { - chromatic: { disable: true }, + chromatic: { disableSnapshot: true }, }, beforeEach() { console.log('second'); diff --git a/code/core/template/stories/tags-add.stories.ts b/code/core/template/stories/tags-add.stories.ts index f7cd5118a436..8f330a5eca0e 100644 --- a/code/core/template/stories/tags-add.stories.ts +++ b/code/core/template/stories/tags-add.stories.ts @@ -14,7 +14,7 @@ export default { }); }, ], - parameters: { chromatic: { disable: true } }, + parameters: { chromatic: { disableSnapshot: true } }, }; export const Inheritance = { @@ -25,7 +25,7 @@ export const Inheritance = { tags: ['story-one'], }); }, - parameters: { chromatic: { disable: false } }, + parameters: { chromatic: { disableSnapshot: false } }, }; export const Dev = { diff --git a/code/core/template/stories/tags-config.stories.ts b/code/core/template/stories/tags-config.stories.ts index 546edb2bdfff..73241353cb7f 100644 --- a/code/core/template/stories/tags-config.stories.ts +++ b/code/core/template/stories/tags-config.stories.ts @@ -14,7 +14,7 @@ export default { }); }, ], - parameters: { chromatic: { disable: true } }, + parameters: { chromatic: { disableSnapshot: true } }, }; export const Inheritance = { @@ -25,7 +25,7 @@ export const Inheritance = { tags: ['dev', 'test', 'component-one', 'component-two', 'autodocs', 'story-one'], }); }, - parameters: { chromatic: { disable: false } }, + parameters: { chromatic: { disableSnapshot: false } }, }; export const DocsOnly = { diff --git a/code/core/template/stories/tags-remove.stories.ts b/code/core/template/stories/tags-remove.stories.ts index 166236a39a3c..10b95a2932d7 100644 --- a/code/core/template/stories/tags-remove.stories.ts +++ b/code/core/template/stories/tags-remove.stories.ts @@ -14,7 +14,7 @@ export default { }); }, ], - parameters: { chromatic: { disable: true } }, + parameters: { chromatic: { disableSnapshot: true } }, }; export const Inheritance = { @@ -25,7 +25,7 @@ export const Inheritance = { tags: ['dev', 'test', 'component-one', 'autodocs', 'story-one'], }); }, - parameters: { chromatic: { disable: false } }, + parameters: { chromatic: { disableSnapshot: false } }, }; export const NoDev = { diff --git a/code/core/template/stories/viewport/globals.stories.ts b/code/core/template/stories/viewport/globals.stories.ts index de65d41ac89f..e0c23072fa15 100644 --- a/code/core/template/stories/viewport/globals.stories.ts +++ b/code/core/template/stories/viewport/globals.stories.ts @@ -10,7 +10,7 @@ export default { text: 'Testing the viewport', }, parameters: { - chromatic: { disable: true }, + chromatic: { disableSnapshot: true }, }, }; diff --git a/code/frameworks/angular/template/stories/basics/component-with-ng-on-destroy/component-with-on-destroy.stories.ts b/code/frameworks/angular/template/stories/basics/component-with-ng-on-destroy/component-with-on-destroy.stories.ts index b6a764d0c553..a678962eaa0c 100644 --- a/code/frameworks/angular/template/stories/basics/component-with-ng-on-destroy/component-with-on-destroy.stories.ts +++ b/code/frameworks/angular/template/stories/basics/component-with-ng-on-destroy/component-with-on-destroy.stories.ts @@ -34,7 +34,7 @@ const meta: Meta = { component: OnDestroyComponent, parameters: { // disabled due to new Date() - chromatic: { disable: true }, + chromatic: { disableSnapshot: true }, }, } as Meta; diff --git a/code/frameworks/nextjs-vite/template/stories/RSC.stories.tsx b/code/frameworks/nextjs-vite/template/stories/RSC.stories.tsx index 74b5580e9636..1f708f351e28 100644 --- a/code/frameworks/nextjs-vite/template/stories/RSC.stories.tsx +++ b/code/frameworks/nextjs-vite/template/stories/RSC.stories.tsx @@ -22,7 +22,7 @@ export const Default: Story = {}; export const DisableRSC: Story = { tags: ['!test'], parameters: { - chromatic: { disable: true }, + chromatic: { disableSnapshot: true }, nextjs: { rsc: false }, }, }; @@ -30,7 +30,7 @@ export const DisableRSC: Story = { export const Errored: Story = { tags: ['!test', '!vitest'], parameters: { - chromatic: { disable: true }, + chromatic: { disableSnapshot: true }, }, render: () => { throw new Error('RSC Error'); diff --git a/code/frameworks/nextjs/template/stories/RSC.stories.tsx b/code/frameworks/nextjs/template/stories/RSC.stories.tsx index f8ba1dc120a1..777eccbcfb7a 100644 --- a/code/frameworks/nextjs/template/stories/RSC.stories.tsx +++ b/code/frameworks/nextjs/template/stories/RSC.stories.tsx @@ -22,7 +22,7 @@ export const Default: Story = {}; export const DisableRSC: Story = { tags: ['!test'], parameters: { - chromatic: { disable: true }, + chromatic: { disableSnapshot: true }, nextjs: { rsc: false }, }, }; @@ -30,7 +30,7 @@ export const DisableRSC: Story = { export const Errored: Story = { tags: ['!test', '!vitest'], parameters: { - chromatic: { disable: true }, + chromatic: { disableSnapshot: true }, }, render: () => { throw new Error('RSC Error'); diff --git a/code/renderers/react/template/stories/csf1.stories.tsx b/code/renderers/react/template/stories/csf1.stories.tsx index 63a53672b396..eff0dfceb66b 100644 --- a/code/renderers/react/template/stories/csf1.stories.tsx +++ b/code/renderers/react/template/stories/csf1.stories.tsx @@ -3,7 +3,7 @@ import React from 'react'; export default { component: {}, parameters: { - chromatic: { disable: true }, + chromatic: { disableSnapshot: true }, }, }; diff --git a/code/renderers/react/template/stories/csf2.stories.tsx b/code/renderers/react/template/stories/csf2.stories.tsx index 6b78e54e36e2..f2b7ff0d63bb 100644 --- a/code/renderers/react/template/stories/csf2.stories.tsx +++ b/code/renderers/react/template/stories/csf2.stories.tsx @@ -3,7 +3,7 @@ import React from 'react'; export default { component: {}, parameters: { - chromatic: { disable: true }, + chromatic: { disableSnapshot: true }, }, }; diff --git a/code/renderers/react/template/stories/errors.stories.tsx b/code/renderers/react/template/stories/errors.stories.tsx index 2c83be0cfeb9..a6e13f04d6e6 100644 --- a/code/renderers/react/template/stories/errors.stories.tsx +++ b/code/renderers/react/template/stories/errors.stories.tsx @@ -6,7 +6,7 @@ const BadComponent = () => badOutput; export default { component: BadComponent, parameters: { - chromatic: { disable: true }, + chromatic: { disableSnapshot: true }, }, tags: ['!test', '!vitest'], }; diff --git a/code/renderers/react/template/stories/teardown.stories.tsx b/code/renderers/react/template/stories/teardown.stories.tsx index 0e5745d701c1..d6fcb91aee8e 100644 --- a/code/renderers/react/template/stories/teardown.stories.tsx +++ b/code/renderers/react/template/stories/teardown.stories.tsx @@ -15,7 +15,7 @@ export default { component: LoggingComponent, tags: ['autodocs'], parameters: { - chromatic: { disable: true }, + chromatic: { disableSnapshot: true }, }, }; From 2f4abff994f054102e3eef63cc4371d3bd90bbcc Mon Sep 17 00:00:00 2001 From: Jeevika R Date: Mon, 15 Dec 2025 23:21:18 +0530 Subject: [PATCH 11/33] Added disable feature for intercations. --- code/core/src/component-testing/constants.ts | 1 + .../src/component-testing/manager.test.tsx | 43 +++++++++++++++++++ code/core/src/component-testing/manager.tsx | 7 ++- code/core/src/component-testing/types.ts | 21 +++++++++ .../stories/component-play.stories.ts | 8 ++++ 5 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 code/core/src/component-testing/manager.test.tsx create mode 100644 code/core/src/component-testing/types.ts diff --git a/code/core/src/component-testing/constants.ts b/code/core/src/component-testing/constants.ts index d096abc1f3f1..8a7fb0a80792 100644 --- a/code/core/src/component-testing/constants.ts +++ b/code/core/src/component-testing/constants.ts @@ -1,5 +1,6 @@ export const ADDON_ID = 'storybook/interactions'; export const PANEL_ID = `${ADDON_ID}/panel`; +export const PARAM_KEY = 'interactions'; export const DOCUMENTATION_LINK = 'writing-tests/integrations/vitest-addon'; export const DOCUMENTATION_DISCREPANCY_LINK = `${DOCUMENTATION_LINK}#what-happens-when-there-are-different-test-results-in-multiple-environments`; diff --git a/code/core/src/component-testing/manager.test.tsx b/code/core/src/component-testing/manager.test.tsx new file mode 100644 index 000000000000..203a5c1fc904 --- /dev/null +++ b/code/core/src/component-testing/manager.test.tsx @@ -0,0 +1,43 @@ +import { describe, expect, it } from 'vitest'; + +import { PARAM_KEY } from './constants'; + +describe('Interactions Panel Disable Parameter', () => { + it('should return true when interactions.disable is true', () => { + const parameters = { + [PARAM_KEY]: { + disable: true, + }, + }; + + const disabled = !!parameters?.[PARAM_KEY]?.disable; + expect(disabled).toBe(true); + }); + + it('should return false when interactions.disable is false', () => { + const parameters = { + [PARAM_KEY]: { + disable: false, + }, + }; + + const disabled = !!parameters?.[PARAM_KEY]?.disable; + expect(disabled).toBe(false); + }); + + it('should return false when interactions parameter is not provided', () => { + const parameters = {}; + + const disabled = !!parameters?.[PARAM_KEY]?.disable; + expect(disabled).toBe(false); + }); + + it('should return false when disable is undefined', () => { + const parameters = { + [PARAM_KEY]: {}, + }; + + const disabled = !!parameters?.[PARAM_KEY]?.disable; + expect(disabled).toBe(false); + }); +}); diff --git a/code/core/src/component-testing/manager.tsx b/code/core/src/component-testing/manager.tsx index 7db4f722c368..cfcf653a3a78 100644 --- a/code/core/src/component-testing/manager.tsx +++ b/code/core/src/component-testing/manager.tsx @@ -2,11 +2,11 @@ import React from 'react'; import { AddonPanel } from 'storybook/internal/components'; -import { type Combo, Consumer, addons, types } from 'storybook/manager-api'; +import { type API_StoryEntry, type Combo, Consumer, addons, types } from 'storybook/manager-api'; import { Panel } from './components/Panel'; import { PanelTitle } from './components/PanelTitle'; -import { ADDON_ID, PANEL_ID } from './constants'; +import { ADDON_ID, PANEL_ID, PARAM_KEY } from './constants'; export default addons.register(ADDON_ID, () => { if (globalThis?.FEATURES?.interactions) { @@ -25,6 +25,9 @@ export default addons.register(ADDON_ID, () => { type: types.PANEL, title: () => , match: ({ viewMode }) => viewMode === 'story', + disabled: (parameters: API_StoryEntry['parameters']) => { + return !!parameters?.[PARAM_KEY]?.disable; + }, render: ({ active }) => { return ( diff --git a/code/core/src/component-testing/types.ts b/code/core/src/component-testing/types.ts new file mode 100644 index 000000000000..513a59c862b0 --- /dev/null +++ b/code/core/src/component-testing/types.ts @@ -0,0 +1,21 @@ +export interface InteractionsParameters { + /** + * Interactions configuration + * + * @see https://storybook.js.org/docs/writing-tests/interaction-testing + */ + interactions?: { + /** + * Removes the addon panel and disables the feature's behavior. If you wish to turn off this + * feature for the entire Storybook, you can set the option in your `main.js|ts` configuration + * file. + * + * @see https://storybook.js.org/docs/writing-tests/interaction-testing#disable + */ + disable?: boolean; + }; +} + +export interface InteractionsTypes { + parameters: InteractionsParameters; +} diff --git a/code/core/template/stories/component-play.stories.ts b/code/core/template/stories/component-play.stories.ts index 592e787299d4..729cbf882ed1 100644 --- a/code/core/template/stories/component-play.stories.ts +++ b/code/core/template/stories/component-play.stories.ts @@ -24,3 +24,11 @@ export default { export const StoryOne = {}; export const StoryTwo = {}; + +export const DisabledInteractions = { + parameters: { + interactions: { + disable: true, + }, + }, +}; From 73f0331d4a2e6ed640a5b7021403e8a3736ef31d Mon Sep 17 00:00:00 2001 From: Jeevika R Date: Mon, 15 Dec 2025 23:38:38 +0530 Subject: [PATCH 12/33] fix: resolve TypeScript errors in interactions disable feature --- code/core/src/component-testing/manager.test.tsx | 9 +++++---- code/core/src/component-testing/manager.tsx | 3 ++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/code/core/src/component-testing/manager.test.tsx b/code/core/src/component-testing/manager.test.tsx index 203a5c1fc904..c7f23ffe5ac9 100644 --- a/code/core/src/component-testing/manager.test.tsx +++ b/code/core/src/component-testing/manager.test.tsx @@ -1,10 +1,11 @@ import { describe, expect, it } from 'vitest'; +import type { InteractionsParameters } from './types'; import { PARAM_KEY } from './constants'; describe('Interactions Panel Disable Parameter', () => { it('should return true when interactions.disable is true', () => { - const parameters = { + const parameters: InteractionsParameters = { [PARAM_KEY]: { disable: true, }, @@ -15,7 +16,7 @@ describe('Interactions Panel Disable Parameter', () => { }); it('should return false when interactions.disable is false', () => { - const parameters = { + const parameters: InteractionsParameters = { [PARAM_KEY]: { disable: false, }, @@ -26,14 +27,14 @@ describe('Interactions Panel Disable Parameter', () => { }); it('should return false when interactions parameter is not provided', () => { - const parameters = {}; + const parameters: InteractionsParameters = {}; const disabled = !!parameters?.[PARAM_KEY]?.disable; expect(disabled).toBe(false); }); it('should return false when disable is undefined', () => { - const parameters = { + const parameters: InteractionsParameters = { [PARAM_KEY]: {}, }; diff --git a/code/core/src/component-testing/manager.tsx b/code/core/src/component-testing/manager.tsx index cfcf653a3a78..3916ff2c27a3 100644 --- a/code/core/src/component-testing/manager.tsx +++ b/code/core/src/component-testing/manager.tsx @@ -2,7 +2,8 @@ import React from 'react'; import { AddonPanel } from 'storybook/internal/components'; -import { type API_StoryEntry, type Combo, Consumer, addons, types } from 'storybook/manager-api'; +import { type Combo, Consumer, addons, types } from 'storybook/manager-api'; +import type { API_StoryEntry } from 'storybook/internal/types'; import { Panel } from './components/Panel'; import { PanelTitle } from './components/PanelTitle'; From 200d13784b8a0e39299a283600096533a8ed4783 Mon Sep 17 00:00:00 2001 From: Jeevika R Date: Tue, 16 Dec 2025 18:25:43 +0530 Subject: [PATCH 13/33] refactor: extract disable logic for proper unit testing --- code/core/src/component-testing/manager.test.tsx | 13 +++++-------- code/core/src/component-testing/manager.tsx | 8 +++----- code/core/src/component-testing/utils.ts | 10 ++++++++++ 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/code/core/src/component-testing/manager.test.tsx b/code/core/src/component-testing/manager.test.tsx index c7f23ffe5ac9..097006d9acc3 100644 --- a/code/core/src/component-testing/manager.test.tsx +++ b/code/core/src/component-testing/manager.test.tsx @@ -1,5 +1,6 @@ import { describe, expect, it } from 'vitest'; +import { isInteractionsDisabled } from './utils'; import type { InteractionsParameters } from './types'; import { PARAM_KEY } from './constants'; @@ -11,8 +12,7 @@ describe('Interactions Panel Disable Parameter', () => { }, }; - const disabled = !!parameters?.[PARAM_KEY]?.disable; - expect(disabled).toBe(true); + expect(isInteractionsDisabled(parameters)).toBe(true); }); it('should return false when interactions.disable is false', () => { @@ -22,15 +22,13 @@ describe('Interactions Panel Disable Parameter', () => { }, }; - const disabled = !!parameters?.[PARAM_KEY]?.disable; - expect(disabled).toBe(false); + expect(isInteractionsDisabled(parameters)).toBe(false); }); it('should return false when interactions parameter is not provided', () => { const parameters: InteractionsParameters = {}; - const disabled = !!parameters?.[PARAM_KEY]?.disable; - expect(disabled).toBe(false); + expect(isInteractionsDisabled(parameters)).toBe(false); }); it('should return false when disable is undefined', () => { @@ -38,7 +36,6 @@ describe('Interactions Panel Disable Parameter', () => { [PARAM_KEY]: {}, }; - const disabled = !!parameters?.[PARAM_KEY]?.disable; - expect(disabled).toBe(false); + expect(isInteractionsDisabled(parameters)).toBe(false); }); }); diff --git a/code/core/src/component-testing/manager.tsx b/code/core/src/component-testing/manager.tsx index 3916ff2c27a3..9b610ed38757 100644 --- a/code/core/src/component-testing/manager.tsx +++ b/code/core/src/component-testing/manager.tsx @@ -3,11 +3,11 @@ import React from 'react'; import { AddonPanel } from 'storybook/internal/components'; import { type Combo, Consumer, addons, types } from 'storybook/manager-api'; -import type { API_StoryEntry } from 'storybook/internal/types'; import { Panel } from './components/Panel'; import { PanelTitle } from './components/PanelTitle'; -import { ADDON_ID, PANEL_ID, PARAM_KEY } from './constants'; +import { ADDON_ID, PANEL_ID } from './constants'; +import { isInteractionsDisabled } from './utils'; export default addons.register(ADDON_ID, () => { if (globalThis?.FEATURES?.interactions) { @@ -26,9 +26,7 @@ export default addons.register(ADDON_ID, () => { type: types.PANEL, title: () => , match: ({ viewMode }) => viewMode === 'story', - disabled: (parameters: API_StoryEntry['parameters']) => { - return !!parameters?.[PARAM_KEY]?.disable; - }, + disabled: isInteractionsDisabled, render: ({ active }) => { return ( diff --git a/code/core/src/component-testing/utils.ts b/code/core/src/component-testing/utils.ts index dac3f236d38f..df9d3d649db1 100644 --- a/code/core/src/component-testing/utils.ts +++ b/code/core/src/component-testing/utils.ts @@ -3,6 +3,16 @@ import { type StorybookTheme, useTheme } from 'storybook/theming'; // eslint-disable-next-line depend/ban-dependencies import stripAnsi from 'strip-ansi'; +import type { API_StoryEntry } from 'storybook/internal/types'; + +import { PARAM_KEY } from './constants'; + +export function isInteractionsDisabled( + parameters: API_StoryEntry['parameters'] +): boolean { + return !!parameters?.[PARAM_KEY]?.disable; +} + export function isTestAssertionError(error: unknown) { return isChaiError(error) || isJestError(error); } From 5d19b63a9619c8dc8a493e88aa1f6a73a63aa867 Mon Sep 17 00:00:00 2001 From: Jeevika R Date: Wed, 17 Dec 2025 06:37:32 +0530 Subject: [PATCH 14/33] fix: prettier formatting --- .../src/component-testing/manager.test.tsx | 82 +++++++++---------- code/core/src/component-testing/utils.ts | 8 +- 2 files changed, 44 insertions(+), 46 deletions(-) diff --git a/code/core/src/component-testing/manager.test.tsx b/code/core/src/component-testing/manager.test.tsx index 097006d9acc3..60f5fdeb8e1d 100644 --- a/code/core/src/component-testing/manager.test.tsx +++ b/code/core/src/component-testing/manager.test.tsx @@ -1,41 +1,41 @@ -import { describe, expect, it } from 'vitest'; - -import { isInteractionsDisabled } from './utils'; -import type { InteractionsParameters } from './types'; -import { PARAM_KEY } from './constants'; - -describe('Interactions Panel Disable Parameter', () => { - it('should return true when interactions.disable is true', () => { - const parameters: InteractionsParameters = { - [PARAM_KEY]: { - disable: true, - }, - }; - - expect(isInteractionsDisabled(parameters)).toBe(true); - }); - - it('should return false when interactions.disable is false', () => { - const parameters: InteractionsParameters = { - [PARAM_KEY]: { - disable: false, - }, - }; - - expect(isInteractionsDisabled(parameters)).toBe(false); - }); - - it('should return false when interactions parameter is not provided', () => { - const parameters: InteractionsParameters = {}; - - expect(isInteractionsDisabled(parameters)).toBe(false); - }); - - it('should return false when disable is undefined', () => { - const parameters: InteractionsParameters = { - [PARAM_KEY]: {}, - }; - - expect(isInteractionsDisabled(parameters)).toBe(false); - }); -}); +import { describe, expect, it } from 'vitest'; + +import { PARAM_KEY } from './constants'; +import type { InteractionsParameters } from './types'; +import { isInteractionsDisabled } from './utils'; + +describe('Interactions Panel Disable Parameter', () => { + it('should return true when interactions.disable is true', () => { + const parameters: InteractionsParameters = { + [PARAM_KEY]: { + disable: true, + }, + }; + + expect(isInteractionsDisabled(parameters)).toBe(true); + }); + + it('should return false when interactions.disable is false', () => { + const parameters: InteractionsParameters = { + [PARAM_KEY]: { + disable: false, + }, + }; + + expect(isInteractionsDisabled(parameters)).toBe(false); + }); + + it('should return false when interactions parameter is not provided', () => { + const parameters: InteractionsParameters = {}; + + expect(isInteractionsDisabled(parameters)).toBe(false); + }); + + it('should return false when disable is undefined', () => { + const parameters: InteractionsParameters = { + [PARAM_KEY]: {}, + }; + + expect(isInteractionsDisabled(parameters)).toBe(false); + }); +}); diff --git a/code/core/src/component-testing/utils.ts b/code/core/src/component-testing/utils.ts index df9d3d649db1..cc779e3647ec 100644 --- a/code/core/src/component-testing/utils.ts +++ b/code/core/src/component-testing/utils.ts @@ -1,15 +1,13 @@ +import type { API_StoryEntry } from 'storybook/internal/types'; + import Filter from 'ansi-to-html'; import { type StorybookTheme, useTheme } from 'storybook/theming'; // eslint-disable-next-line depend/ban-dependencies import stripAnsi from 'strip-ansi'; -import type { API_StoryEntry } from 'storybook/internal/types'; - import { PARAM_KEY } from './constants'; -export function isInteractionsDisabled( - parameters: API_StoryEntry['parameters'] -): boolean { +export function isInteractionsDisabled(parameters: API_StoryEntry['parameters']): boolean { return !!parameters?.[PARAM_KEY]?.disable; } From c1e08b663f16d615f527f9a74c6d2c990051cb9f Mon Sep 17 00:00:00 2001 From: yue Date: Tue, 23 Dec 2025 00:16:32 +0900 Subject: [PATCH 15/33] fix: import with js extension for ESM compat --- .../nextjs-vite/src/export-mocks/headers/cookies.ts | 2 +- .../nextjs-vite/src/export-mocks/headers/headers.ts | 2 +- .../nextjs-vite/src/export-mocks/headers/index.ts | 6 +++--- .../nextjs-vite/src/export-mocks/navigation/index.ts | 8 ++++---- .../nextjs-vite/src/export-mocks/router/index.ts | 4 ++-- .../src/head-manager/head-manager-provider.tsx | 4 ++-- code/frameworks/nextjs-vite/src/preview.tsx | 2 +- .../nextjs-vite/src/routing/app-router-provider.tsx | 6 +++--- code/frameworks/nextjs-vite/src/routing/decorator.tsx | 2 +- .../nextjs-vite/src/routing/page-router-provider.tsx | 2 +- .../nextjs/src/compatibility/draft-mode.compat.ts | 2 +- code/frameworks/nextjs/src/export-mocks/cache/index.ts | 4 ++-- .../frameworks/nextjs/src/export-mocks/headers/cookies.ts | 2 +- .../frameworks/nextjs/src/export-mocks/headers/headers.ts | 2 +- code/frameworks/nextjs/src/export-mocks/headers/index.ts | 4 ++-- .../nextjs/src/export-mocks/navigation/index.ts | 8 ++++---- code/frameworks/nextjs/src/export-mocks/router/index.ts | 4 ++-- .../nextjs/src/head-manager/head-manager-provider.tsx | 4 ++-- code/frameworks/nextjs/src/preview.tsx | 2 +- .../frameworks/nextjs/src/routing/app-router-provider.tsx | 6 +++--- code/frameworks/nextjs/src/routing/decorator.tsx | 2 +- .../nextjs/src/routing/page-router-provider.tsx | 2 +- 22 files changed, 40 insertions(+), 40 deletions(-) diff --git a/code/frameworks/nextjs-vite/src/export-mocks/headers/cookies.ts b/code/frameworks/nextjs-vite/src/export-mocks/headers/cookies.ts index 9264a7e191c2..547a6cd3dbaa 100644 --- a/code/frameworks/nextjs-vite/src/export-mocks/headers/cookies.ts +++ b/code/frameworks/nextjs-vite/src/export-mocks/headers/cookies.ts @@ -5,7 +5,7 @@ // @ts-ignore we must ignore types here as during compilation they are not generated yet import { headers } from '@storybook/nextjs-vite/headers.mock'; -import { RequestCookies } from 'next/dist/compiled/@edge-runtime/cookies'; +import { RequestCookies } from 'next/dist/compiled/@edge-runtime/cookies/index.js'; import { fn } from 'storybook/test'; class RequestCookiesMock extends RequestCookies { diff --git a/code/frameworks/nextjs-vite/src/export-mocks/headers/headers.ts b/code/frameworks/nextjs-vite/src/export-mocks/headers/headers.ts index 1d65c9285d93..ede58f535cdc 100644 --- a/code/frameworks/nextjs-vite/src/export-mocks/headers/headers.ts +++ b/code/frameworks/nextjs-vite/src/export-mocks/headers/headers.ts @@ -1,4 +1,4 @@ -import { HeadersAdapter } from 'next/dist/server/web/spec-extension/adapters/headers'; +import { HeadersAdapter } from 'next/dist/server/web/spec-extension/adapters/headers.js'; import { fn } from 'storybook/test'; class HeadersAdapterMock extends HeadersAdapter { diff --git a/code/frameworks/nextjs-vite/src/export-mocks/headers/index.ts b/code/frameworks/nextjs-vite/src/export-mocks/headers/index.ts index f1e27b49c646..0926efabcefb 100644 --- a/code/frameworks/nextjs-vite/src/export-mocks/headers/index.ts +++ b/code/frameworks/nextjs-vite/src/export-mocks/headers/index.ts @@ -1,9 +1,9 @@ -import { draftMode as originalDraftMode } from 'next/dist/server/request/draft-mode'; -import * as headers from 'next/dist/server/request/headers'; +import { draftMode as originalDraftMode } from 'next/dist/server/request/draft-mode.js'; +import * as headers from 'next/dist/server/request/headers.js'; import { fn } from 'storybook/test'; // re-exports of the actual module -export * from 'next/dist/server/request/headers'; +export * from 'next/dist/server/request/headers.js'; // mock utilities/overrides (as of Next v14.2.0) export { headers } from './headers'; diff --git a/code/frameworks/nextjs-vite/src/export-mocks/navigation/index.ts b/code/frameworks/nextjs-vite/src/export-mocks/navigation/index.ts index 6bb5f1780b62..567f9bcae282 100644 --- a/code/frameworks/nextjs-vite/src/export-mocks/navigation/index.ts +++ b/code/frameworks/nextjs-vite/src/export-mocks/navigation/index.ts @@ -1,8 +1,8 @@ import { NextjsRouterMocksNotAvailable } from 'storybook/internal/preview-errors'; -import * as actual from 'next/dist/client/components/navigation'; -import { getRedirectError } from 'next/dist/client/components/redirect'; -import { RedirectStatusCode } from 'next/dist/client/components/redirect-status-code'; +import * as actual from 'next/dist/client/components/navigation.js'; +import { RedirectStatusCode } from 'next/dist/client/components/redirect-status-code.js'; +import { getRedirectError } from 'next/dist/client/components/redirect.js'; import type { Mock } from 'storybook/test'; import { fn } from 'storybook/test'; @@ -57,7 +57,7 @@ export const getRouter = () => { }; // re-exports of the actual module -export * from 'next/dist/client/components/navigation'; +export * from 'next/dist/client/components/navigation.js'; // mock utilities/overrides (as of Next v14.2.0) export const redirect = fn( diff --git a/code/frameworks/nextjs-vite/src/export-mocks/router/index.ts b/code/frameworks/nextjs-vite/src/export-mocks/router/index.ts index 26ee30342c70..3bb4cec038e3 100644 --- a/code/frameworks/nextjs-vite/src/export-mocks/router/index.ts +++ b/code/frameworks/nextjs-vite/src/export-mocks/router/index.ts @@ -1,6 +1,6 @@ import { NextjsRouterMocksNotAvailable } from 'storybook/internal/preview-errors'; -import singletonRouter, * as originalRouter from 'next/dist/client/router'; +import singletonRouter, * as originalRouter from 'next/dist/client/router.js'; import type { NextRouter, SingletonRouter } from 'next/router'; import type { Mock } from 'storybook/test'; import { fn } from 'storybook/test'; @@ -108,7 +108,7 @@ export const getRouter = () => { }; // re-exports of the actual module -export * from 'next/dist/client/router'; +export * from 'next/dist/client/router.js'; export default singletonRouter; // mock utilities/overrides (as of Next v14.2.0) diff --git a/code/frameworks/nextjs-vite/src/head-manager/head-manager-provider.tsx b/code/frameworks/nextjs-vite/src/head-manager/head-manager-provider.tsx index 69b58866c510..1fcfc3cbfa0c 100644 --- a/code/frameworks/nextjs-vite/src/head-manager/head-manager-provider.tsx +++ b/code/frameworks/nextjs-vite/src/head-manager/head-manager-provider.tsx @@ -1,8 +1,8 @@ import type { PropsWithChildren } from 'react'; import React, { useMemo } from 'react'; -import initHeadManager from 'next/dist/client/head-manager'; -import { HeadManagerContext } from 'next/dist/shared/lib/head-manager-context.shared-runtime'; +import initHeadManager from 'next/dist/client/head-manager.js'; +import { HeadManagerContext } from 'next/dist/shared/lib/head-manager-context.shared-runtime.js'; type HeadManagerValue = { updateHead?: ((state: JSX.Element[]) => void) | undefined; diff --git a/code/frameworks/nextjs-vite/src/preview.tsx b/code/frameworks/nextjs-vite/src/preview.tsx index 6be65e13db4a..5dc537d85732 100644 --- a/code/frameworks/nextjs-vite/src/preview.tsx +++ b/code/frameworks/nextjs-vite/src/preview.tsx @@ -11,7 +11,7 @@ import { createNavigation } from '@storybook/nextjs-vite/navigation.mock'; // @ts-ignore we must ignore types here as during compilation they are not generated yet import { createRouter } from '@storybook/nextjs-vite/router.mock'; -import { isNextRouterError } from 'next/dist/client/components/is-next-router-error'; +import { isNextRouterError } from 'next/dist/client/components/is-next-router-error.js'; import { HeadManagerDecorator } from './head-manager/decorator'; import { ImageDecorator } from './images/decorator'; diff --git a/code/frameworks/nextjs-vite/src/routing/app-router-provider.tsx b/code/frameworks/nextjs-vite/src/routing/app-router-provider.tsx index c01226bbd440..20c6cf214da4 100644 --- a/code/frameworks/nextjs-vite/src/routing/app-router-provider.tsx +++ b/code/frameworks/nextjs-vite/src/routing/app-router-provider.tsx @@ -12,13 +12,13 @@ import { AppRouterContext, GlobalLayoutRouterContext, LayoutRouterContext, -} from 'next/dist/shared/lib/app-router-context.shared-runtime'; +} from 'next/dist/shared/lib/app-router-context.shared-runtime.js'; import { PathParamsContext, PathnameContext, SearchParamsContext, -} from 'next/dist/shared/lib/hooks-client-context.shared-runtime'; -import { PAGE_SEGMENT_KEY } from 'next/dist/shared/lib/segment'; +} from 'next/dist/shared/lib/hooks-client-context.shared-runtime.js'; +import { PAGE_SEGMENT_KEY } from 'next/dist/shared/lib/segment.js'; import type { RouteParams } from './types'; diff --git a/code/frameworks/nextjs-vite/src/routing/decorator.tsx b/code/frameworks/nextjs-vite/src/routing/decorator.tsx index f21819f373a4..575870b62831 100644 --- a/code/frameworks/nextjs-vite/src/routing/decorator.tsx +++ b/code/frameworks/nextjs-vite/src/routing/decorator.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import type { Addon_StoryContext } from 'storybook/internal/types'; -import { RedirectBoundary } from 'next/dist/client/components/redirect-boundary'; +import { RedirectBoundary } from 'next/dist/client/components/redirect-boundary.js'; import { AppRouterProvider } from './app-router-provider'; import { PageRouterProvider } from './page-router-provider'; diff --git a/code/frameworks/nextjs-vite/src/routing/page-router-provider.tsx b/code/frameworks/nextjs-vite/src/routing/page-router-provider.tsx index 972ea1c2c140..5633321f7fb9 100644 --- a/code/frameworks/nextjs-vite/src/routing/page-router-provider.tsx +++ b/code/frameworks/nextjs-vite/src/routing/page-router-provider.tsx @@ -8,7 +8,7 @@ import React from 'react'; // @ts-ignore we must ignore types here as during compilation they are not generated yet import { getRouter } from '@storybook/nextjs-vite/router.mock'; -import { RouterContext } from 'next/dist/shared/lib/router-context.shared-runtime'; +import { RouterContext } from 'next/dist/shared/lib/router-context.shared-runtime.js'; export const PageRouterProvider: React.FC = ({ children }) => ( {children} diff --git a/code/frameworks/nextjs/src/compatibility/draft-mode.compat.ts b/code/frameworks/nextjs/src/compatibility/draft-mode.compat.ts index cdcaf63bbf1a..ed438d0c472f 100644 --- a/code/frameworks/nextjs/src/compatibility/draft-mode.compat.ts +++ b/code/frameworks/nextjs/src/compatibility/draft-mode.compat.ts @@ -1,2 +1,2 @@ // @ts-expect-error Compatibility for Next 14 -export { draftMode } from 'next/dist/client/components/headers'; +export { draftMode } from 'next/dist/client/components/headers.js'; diff --git a/code/frameworks/nextjs/src/export-mocks/cache/index.ts b/code/frameworks/nextjs/src/export-mocks/cache/index.ts index 16ecd0b42e4a..0962c0e19154 100644 --- a/code/frameworks/nextjs/src/export-mocks/cache/index.ts +++ b/code/frameworks/nextjs/src/export-mocks/cache/index.ts @@ -1,5 +1,5 @@ -import { unstable_cache } from 'next/dist/server/web/spec-extension/unstable-cache'; -import { unstable_noStore } from 'next/dist/server/web/spec-extension/unstable-no-store'; +import { unstable_cache } from 'next/dist/server/web/spec-extension/unstable-cache.js'; +import { unstable_noStore } from 'next/dist/server/web/spec-extension/unstable-no-store.js'; import { fn } from 'storybook/test'; // mock utilities/overrides (as of Next v14.2.0) diff --git a/code/frameworks/nextjs/src/export-mocks/headers/cookies.ts b/code/frameworks/nextjs/src/export-mocks/headers/cookies.ts index b8125b5fef1c..0f74f7f05b6b 100644 --- a/code/frameworks/nextjs/src/export-mocks/headers/cookies.ts +++ b/code/frameworks/nextjs/src/export-mocks/headers/cookies.ts @@ -5,7 +5,7 @@ // @ts-ignore we must ignore types here as during compilation they are not generated yet import { headers } from '@storybook/nextjs/headers.mock'; -import { RequestCookies } from 'next/dist/compiled/@edge-runtime/cookies'; +import { RequestCookies } from 'next/dist/compiled/@edge-runtime/cookies/index.js'; import { fn } from 'storybook/test'; class RequestCookiesMock extends RequestCookies { diff --git a/code/frameworks/nextjs/src/export-mocks/headers/headers.ts b/code/frameworks/nextjs/src/export-mocks/headers/headers.ts index 1d65c9285d93..ede58f535cdc 100644 --- a/code/frameworks/nextjs/src/export-mocks/headers/headers.ts +++ b/code/frameworks/nextjs/src/export-mocks/headers/headers.ts @@ -1,4 +1,4 @@ -import { HeadersAdapter } from 'next/dist/server/web/spec-extension/adapters/headers'; +import { HeadersAdapter } from 'next/dist/server/web/spec-extension/adapters/headers.js'; import { fn } from 'storybook/test'; class HeadersAdapterMock extends HeadersAdapter { diff --git a/code/frameworks/nextjs/src/export-mocks/headers/index.ts b/code/frameworks/nextjs/src/export-mocks/headers/index.ts index 0ae9091dd927..d0f1e41b0afb 100644 --- a/code/frameworks/nextjs/src/export-mocks/headers/index.ts +++ b/code/frameworks/nextjs/src/export-mocks/headers/index.ts @@ -1,8 +1,8 @@ -import * as headers from 'next/dist/server/request/headers'; +import * as headers from 'next/dist/server/request/headers.js'; import { fn } from 'storybook/test'; // re-exports of the actual module -export * from 'next/dist/server/request/headers'; +export * from 'next/dist/server/request/headers.js'; // mock utilities/overrides (as of Next v14.2.0) export { headers } from './headers'; diff --git a/code/frameworks/nextjs/src/export-mocks/navigation/index.ts b/code/frameworks/nextjs/src/export-mocks/navigation/index.ts index e7acaee34415..d294afdd473a 100644 --- a/code/frameworks/nextjs/src/export-mocks/navigation/index.ts +++ b/code/frameworks/nextjs/src/export-mocks/navigation/index.ts @@ -1,8 +1,8 @@ import { NextjsRouterMocksNotAvailable } from 'storybook/internal/preview-errors'; -import * as actual from 'next/dist/client/components/navigation'; -import { getRedirectError } from 'next/dist/client/components/redirect'; -import { RedirectStatusCode } from 'next/dist/client/components/redirect-status-code'; +import * as actual from 'next/dist/client/components/navigation.js'; +import { RedirectStatusCode } from 'next/dist/client/components/redirect-status-code.js'; +import { getRedirectError } from 'next/dist/client/components/redirect.js'; import type { Mock } from 'storybook/test'; import { fn } from 'storybook/test'; @@ -57,7 +57,7 @@ export const getRouter = () => { }; // re-exports of the actual module -export * from 'next/dist/client/components/navigation'; +export * from 'next/dist/client/components/navigation.js'; // mock utilities/overrides (as of Next v14.2.0) export const redirect = fn( diff --git a/code/frameworks/nextjs/src/export-mocks/router/index.ts b/code/frameworks/nextjs/src/export-mocks/router/index.ts index 26ee30342c70..3bb4cec038e3 100644 --- a/code/frameworks/nextjs/src/export-mocks/router/index.ts +++ b/code/frameworks/nextjs/src/export-mocks/router/index.ts @@ -1,6 +1,6 @@ import { NextjsRouterMocksNotAvailable } from 'storybook/internal/preview-errors'; -import singletonRouter, * as originalRouter from 'next/dist/client/router'; +import singletonRouter, * as originalRouter from 'next/dist/client/router.js'; import type { NextRouter, SingletonRouter } from 'next/router'; import type { Mock } from 'storybook/test'; import { fn } from 'storybook/test'; @@ -108,7 +108,7 @@ export const getRouter = () => { }; // re-exports of the actual module -export * from 'next/dist/client/router'; +export * from 'next/dist/client/router.js'; export default singletonRouter; // mock utilities/overrides (as of Next v14.2.0) diff --git a/code/frameworks/nextjs/src/head-manager/head-manager-provider.tsx b/code/frameworks/nextjs/src/head-manager/head-manager-provider.tsx index 69b58866c510..1fcfc3cbfa0c 100644 --- a/code/frameworks/nextjs/src/head-manager/head-manager-provider.tsx +++ b/code/frameworks/nextjs/src/head-manager/head-manager-provider.tsx @@ -1,8 +1,8 @@ import type { PropsWithChildren } from 'react'; import React, { useMemo } from 'react'; -import initHeadManager from 'next/dist/client/head-manager'; -import { HeadManagerContext } from 'next/dist/shared/lib/head-manager-context.shared-runtime'; +import initHeadManager from 'next/dist/client/head-manager.js'; +import { HeadManagerContext } from 'next/dist/shared/lib/head-manager-context.shared-runtime.js'; type HeadManagerValue = { updateHead?: ((state: JSX.Element[]) => void) | undefined; diff --git a/code/frameworks/nextjs/src/preview.tsx b/code/frameworks/nextjs/src/preview.tsx index b13dfd70ba53..13dcaf795389 100644 --- a/code/frameworks/nextjs/src/preview.tsx +++ b/code/frameworks/nextjs/src/preview.tsx @@ -12,7 +12,7 @@ import { createNavigation } from '@storybook/nextjs/navigation.mock'; // @ts-ignore we must ignore types here as during compilation they are not generated yet import { createRouter } from '@storybook/nextjs/router.mock'; -import { isNextRouterError } from 'next/dist/client/components/is-next-router-error'; +import { isNextRouterError } from 'next/dist/client/components/is-next-router-error.js'; import { HeadManagerDecorator } from './head-manager/decorator'; import { ImageDecorator } from './images/decorator'; diff --git a/code/frameworks/nextjs/src/routing/app-router-provider.tsx b/code/frameworks/nextjs/src/routing/app-router-provider.tsx index 082548ec7df6..fa4c5b756f62 100644 --- a/code/frameworks/nextjs/src/routing/app-router-provider.tsx +++ b/code/frameworks/nextjs/src/routing/app-router-provider.tsx @@ -12,13 +12,13 @@ import { AppRouterContext, GlobalLayoutRouterContext, LayoutRouterContext, -} from 'next/dist/shared/lib/app-router-context.shared-runtime'; +} from 'next/dist/shared/lib/app-router-context.shared-runtime.js'; import { PathParamsContext, PathnameContext, SearchParamsContext, -} from 'next/dist/shared/lib/hooks-client-context.shared-runtime'; -import { PAGE_SEGMENT_KEY } from 'next/dist/shared/lib/segment'; +} from 'next/dist/shared/lib/hooks-client-context.shared-runtime.js'; +import { PAGE_SEGMENT_KEY } from 'next/dist/shared/lib/segment.js'; import type { RouteParams } from './types'; diff --git a/code/frameworks/nextjs/src/routing/decorator.tsx b/code/frameworks/nextjs/src/routing/decorator.tsx index f21819f373a4..575870b62831 100644 --- a/code/frameworks/nextjs/src/routing/decorator.tsx +++ b/code/frameworks/nextjs/src/routing/decorator.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import type { Addon_StoryContext } from 'storybook/internal/types'; -import { RedirectBoundary } from 'next/dist/client/components/redirect-boundary'; +import { RedirectBoundary } from 'next/dist/client/components/redirect-boundary.js'; import { AppRouterProvider } from './app-router-provider'; import { PageRouterProvider } from './page-router-provider'; diff --git a/code/frameworks/nextjs/src/routing/page-router-provider.tsx b/code/frameworks/nextjs/src/routing/page-router-provider.tsx index 31cfd1ae175b..91c29ca14210 100644 --- a/code/frameworks/nextjs/src/routing/page-router-provider.tsx +++ b/code/frameworks/nextjs/src/routing/page-router-provider.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { getRouter } from '@storybook/nextjs/router.mock'; -import { RouterContext } from 'next/dist/shared/lib/router-context.shared-runtime'; +import { RouterContext } from 'next/dist/shared/lib/router-context.shared-runtime.js'; export const PageRouterProvider: React.FC = ({ children }) => ( {children} From f105b08c7784aff6b6c5e4510114c5b9f5377b9c Mon Sep 17 00:00:00 2001 From: storybook-bot <32066757+storybook-bot@users.noreply.github.com> Date: Mon, 29 Dec 2025 15:32:30 +0000 Subject: [PATCH 16/33] Update CHANGELOG.md for v10.1.11 [skip ci] --- CHANGELOG.md | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ca691693f51..bc9fd2b3e316 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 10.1.11 + +- React: Fix several CSF factory bugs - [#33354](https://github.com/storybookjs/storybook/pull/33354), thanks @kasperpeulen! +- UI: Fix React error 300 on some addons - [#33381](https://github.com/storybookjs/storybook/pull/33381), thanks @Sidnioulz! + ## 10.1.10 - Core: Fix `.env`-file parsing - [#33383](https://github.com/storybookjs/storybook/pull/33383), thanks @JReinhold! @@ -297,10 +302,6 @@ It also includes features to level up your UI development, documentation, and te -## 9.1.17 - -- Core: Fix .env-file parsing, thanks @jreinhold! - ## 9.1.16 - CLI: Fix Nextjs project creation in empty directories - [#32828](https://github.com/storybookjs/storybook/pull/32828), thanks @yannbf! @@ -846,10 +847,6 @@ Unique contributors: 29 -## 8.6.15 - -- Core: Fix .env-file parsing, thanks @jreinhold! - ## 8.6.14 - CLI: Add skip onboarding, recommended/minimal config - [#30930](https://github.com/storybookjs/storybook/pull/30930), thanks @shilman! @@ -1827,10 +1824,6 @@ It brings major improvements to Storybook's feature set for testing and document Please checkout our [Migration Guide](https://storybook.js.org/docs/8.0/migration-guide) to upgrade from earlier versions of Storybook. To see a comprehensive list of changes that went into 8.0, you can refer to the [8.0 prerelease changelogs](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.prerelease.md). -## 7.6.21 - -- Core: Fix .env-file parsing, thanks @jreinhold! - ## 7.6.17 - Addon-docs: Fix Table of Contents heading leak - [#23677](https://github.com/storybookjs/storybook/pull/23677), thanks [@vmizg](https://github.com/vmizg)! From daa4d6a789de26a1a40cf1275c739052a04c0d60 Mon Sep 17 00:00:00 2001 From: storybook-bot <32066757+storybook-bot@users.noreply.github.com> Date: Mon, 29 Dec 2025 12:07:40 +0000 Subject: [PATCH 17/33] Write changelog for 10.2.0-alpha.10 [skip ci] --- CHANGELOG.prerelease.md | 15 +++++++++++++++ code/package.json | 3 ++- docs/versions/next.json | 2 +- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.prerelease.md b/CHANGELOG.prerelease.md index 26116256a50d..669af7ba4be0 100644 --- a/CHANGELOG.prerelease.md +++ b/CHANGELOG.prerelease.md @@ -1,3 +1,18 @@ +## 10.2.0-alpha.10 + +- Dependencies: Bump various packages - [#33412](https://github.com/storybookjs/storybook/pull/33412), thanks @ndelangen! +- Interactions: Add disable parameter for interactions panel - [#33368](https://github.com/storybookjs/storybook/pull/33368), thanks @jeevikar14! +- Interactions: Fix state reset bug when switching stories with date mocks - [#33388](https://github.com/storybookjs/storybook/pull/33388), thanks @Sidnioulz! +- Manifests: Refactor from `componentManifestGenerator` to extensible `manifests` preset property - [#33392](https://github.com/storybookjs/storybook/pull/33392), thanks @JReinhold! +- Manifests: Support `!manifest` tag in preview files - [#33406](https://github.com/storybookjs/storybook/pull/33406), thanks @JReinhold! +- NextJS: Import `next/dist` with `.js`-extension for ESM compat - [#33380](https://github.com/storybookjs/storybook/pull/33380), thanks @yue4u! +- Preview: Treat canceled animations as finished - [#32401](https://github.com/storybookjs/storybook/pull/32401), thanks @bawjensen! +- UI: Ensure consistent right padding in TreeNode - [#33322](https://github.com/storybookjs/storybook/pull/33322), thanks @Sidnioulz! +- UI: Fix React error 300 on some addons - [#33381](https://github.com/storybookjs/storybook/pull/33381), thanks @Sidnioulz! +- UI: Prevent primary story from duplicating anchor ID - [#33384](https://github.com/storybookjs/storybook/pull/33384), thanks @Sidnioulz! +- Upgrade: Preserve package.json indentation when upgrading - [#32280](https://github.com/storybookjs/storybook/pull/32280), thanks @y-hsgw! +- Vitest: Fallback detecting vitest version in postinstall - [#33415](https://github.com/storybookjs/storybook/pull/33415), thanks @ndelangen! + ## 10.2.0-alpha.9 - Core and Vite: Use story index as source of truth for Vite paths - [#30612](https://github.com/storybookjs/storybook/pull/30612), thanks @JReinhold! diff --git a/code/package.json b/code/package.json index d83f1f723e6e..c4d77791c7c0 100644 --- a/code/package.json +++ b/code/package.json @@ -220,5 +220,6 @@ "Dependency Upgrades" ] ] - } + }, + "deferredNextVersion": "10.2.0-alpha.10" } diff --git a/docs/versions/next.json b/docs/versions/next.json index 120c4a4cd44d..93a5b271a37b 100644 --- a/docs/versions/next.json +++ b/docs/versions/next.json @@ -1 +1 @@ -{"version":"10.2.0-alpha.9","info":{"plain":"- Core and Vite: Use story index as source of truth for Vite paths - [#30612](https://github.com/storybookjs/storybook/pull/30612), thanks @JReinhold!"}} \ No newline at end of file +{"version":"10.2.0-alpha.10","info":{"plain":"- Dependencies: Bump various packages - [#33412](https://github.com/storybookjs/storybook/pull/33412), thanks @ndelangen!\n- Interactions: Add disable parameter for interactions panel - [#33368](https://github.com/storybookjs/storybook/pull/33368), thanks @jeevikar14!\n- Interactions: Fix state reset bug when switching stories with date mocks - [#33388](https://github.com/storybookjs/storybook/pull/33388), thanks @Sidnioulz!\n- Manifests: Refactor from `componentManifestGenerator` to extensible `manifests` preset property - [#33392](https://github.com/storybookjs/storybook/pull/33392), thanks @JReinhold!\n- Manifests: Support `!manifest` tag in preview files - [#33406](https://github.com/storybookjs/storybook/pull/33406), thanks @JReinhold!\n- NextJS: Import `next/dist` with `.js`-extension for ESM compat - [#33380](https://github.com/storybookjs/storybook/pull/33380), thanks @yue4u!\n- Preview: Treat canceled animations as finished - [#32401](https://github.com/storybookjs/storybook/pull/32401), thanks @bawjensen!\n- UI: Ensure consistent right padding in TreeNode - [#33322](https://github.com/storybookjs/storybook/pull/33322), thanks @Sidnioulz!\n- UI: Fix React error 300 on some addons - [#33381](https://github.com/storybookjs/storybook/pull/33381), thanks @Sidnioulz!\n- UI: Prevent primary story from duplicating anchor ID - [#33384](https://github.com/storybookjs/storybook/pull/33384), thanks @Sidnioulz!\n- Upgrade: Preserve package.json indentation when upgrading - [#32280](https://github.com/storybookjs/storybook/pull/32280), thanks @y-hsgw!\n- Vitest: Fallback detecting vitest version in postinstall - [#33415](https://github.com/storybookjs/storybook/pull/33415), thanks @ndelangen!"}} \ No newline at end of file From 9f8f8954de5142859194c436ab2f0cc7ed72d979 Mon Sep 17 00:00:00 2001 From: storybook-bot <32066757+storybook-bot@users.noreply.github.com> Date: Mon, 29 Dec 2025 15:39:48 +0000 Subject: [PATCH 18/33] Bump version from "10.2.0-alpha.9" to "10.2.0-alpha.10" [skip ci] --- code/addons/a11y/package.json | 2 +- code/addons/docs/package.json | 2 +- code/addons/links/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/builders/builder-vite/package.json | 2 +- code/builders/builder-webpack5/package.json | 2 +- code/core/package.json | 2 +- code/core/src/common/versions.ts | 84 +++++++++---------- code/core/src/manager-api/version.ts | 2 +- code/frameworks/angular/package.json | 2 +- code/frameworks/ember/package.json | 2 +- code/frameworks/html-vite/package.json | 2 +- code/frameworks/nextjs-vite/package.json | 2 +- code/frameworks/nextjs/package.json | 2 +- code/frameworks/preact-vite/package.json | 2 +- .../react-native-web-vite/package.json | 2 +- code/frameworks/react-vite/package.json | 2 +- code/frameworks/react-webpack5/package.json | 2 +- code/frameworks/server-webpack5/package.json | 2 +- code/frameworks/svelte-vite/package.json | 2 +- code/frameworks/sveltekit/package.json | 2 +- code/frameworks/vue3-vite/package.json | 2 +- .../web-components-vite/package.json | 2 +- code/lib/cli-sb/package.json | 2 +- code/lib/cli-storybook/package.json | 2 +- code/lib/codemod/package.json | 2 +- code/lib/core-webpack/package.json | 2 +- code/lib/create-storybook/package.json | 2 +- code/lib/csf-plugin/package.json | 2 +- code/lib/eslint-plugin/package.json | 2 +- code/lib/react-dom-shim/package.json | 2 +- code/package.json | 5 +- code/presets/create-react-app/package.json | 2 +- code/presets/react-webpack/package.json | 2 +- code/presets/server-webpack/package.json | 2 +- code/renderers/html/package.json | 2 +- code/renderers/preact/package.json | 2 +- code/renderers/react/package.json | 2 +- code/renderers/server/package.json | 2 +- code/renderers/svelte/package.json | 2 +- code/renderers/vue3/package.json | 2 +- code/renderers/web-components/package.json | 2 +- 45 files changed, 87 insertions(+), 88 deletions(-) diff --git a/code/addons/a11y/package.json b/code/addons/a11y/package.json index e6933f52b9c7..48582fd287ee 100644 --- a/code/addons/a11y/package.json +++ b/code/addons/a11y/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-a11y", - "version": "10.2.0-alpha.9", + "version": "10.2.0-alpha.10", "description": "Storybook Addon A11y: Test UI component compliance with WCAG web accessibility standards", "keywords": [ "a11y", diff --git a/code/addons/docs/package.json b/code/addons/docs/package.json index b99e8ec6ac7a..980cc2e97f78 100644 --- a/code/addons/docs/package.json +++ b/code/addons/docs/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-docs", - "version": "10.2.0-alpha.9", + "version": "10.2.0-alpha.10", "description": "Storybook Docs: Document UI components automatically with stories and MDX", "keywords": [ "docs", diff --git a/code/addons/links/package.json b/code/addons/links/package.json index 4eee785a90c3..92421d43066f 100644 --- a/code/addons/links/package.json +++ b/code/addons/links/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-links", - "version": "10.2.0-alpha.9", + "version": "10.2.0-alpha.10", "description": "Storybook Links: Link stories together to build demos and prototypes with your UI components", "keywords": [ "storybook", diff --git a/code/addons/onboarding/package.json b/code/addons/onboarding/package.json index 07118911b164..7c682745e649 100644 --- a/code/addons/onboarding/package.json +++ b/code/addons/onboarding/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-onboarding", - "version": "10.2.0-alpha.9", + "version": "10.2.0-alpha.10", "description": "Storybook Onboarding: Help new users learn how to write stories", "keywords": [ "storybook", diff --git a/code/addons/pseudo-states/package.json b/code/addons/pseudo-states/package.json index da3941dce5d6..5a2c12fd95db 100644 --- a/code/addons/pseudo-states/package.json +++ b/code/addons/pseudo-states/package.json @@ -1,6 +1,6 @@ { "name": "storybook-addon-pseudo-states", - "version": "10.2.0-alpha.9", + "version": "10.2.0-alpha.10", "description": "Storybook Pseudo-states addon: Manipulate CSS pseudo states", "keywords": [ "storybook", diff --git a/code/addons/themes/package.json b/code/addons/themes/package.json index 27b172ad39e3..b4c0ada5fd5e 100644 --- a/code/addons/themes/package.json +++ b/code/addons/themes/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-themes", - "version": "10.2.0-alpha.9", + "version": "10.2.0-alpha.10", "description": "Storybook Themes addon: Switch between themes from the toolbar", "keywords": [ "css", diff --git a/code/addons/vitest/package.json b/code/addons/vitest/package.json index 52d69c86dee6..a954f4aaa8ce 100644 --- a/code/addons/vitest/package.json +++ b/code/addons/vitest/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-vitest", - "version": "10.2.0-alpha.9", + "version": "10.2.0-alpha.10", "description": "Storybook Vitest addon: Blazing fast component testing using stories", "keywords": [ "storybook", diff --git a/code/builders/builder-vite/package.json b/code/builders/builder-vite/package.json index 3fc8592a1405..a0f4ce8daa5e 100644 --- a/code/builders/builder-vite/package.json +++ b/code/builders/builder-vite/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/builder-vite", - "version": "10.2.0-alpha.9", + "version": "10.2.0-alpha.10", "description": "A Storybook builder to dev and build with Vite", "keywords": [ "storybook", diff --git a/code/builders/builder-webpack5/package.json b/code/builders/builder-webpack5/package.json index 4f8a766d2cb8..48693ae3efcb 100644 --- a/code/builders/builder-webpack5/package.json +++ b/code/builders/builder-webpack5/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/builder-webpack5", - "version": "10.2.0-alpha.9", + "version": "10.2.0-alpha.10", "description": "A Storybook builder to dev and build with Webpack", "keywords": [ "storybook", diff --git a/code/core/package.json b/code/core/package.json index 7069c4356a1b..e379d9985056 100644 --- a/code/core/package.json +++ b/code/core/package.json @@ -1,6 +1,6 @@ { "name": "storybook", - "version": "10.2.0-alpha.9", + "version": "10.2.0-alpha.10", "description": "Storybook: Develop, document, and test UI components in isolation", "keywords": [ "storybook", diff --git a/code/core/src/common/versions.ts b/code/core/src/common/versions.ts index 47f3c4f4ac8a..9abfacabe68e 100644 --- a/code/core/src/common/versions.ts +++ b/code/core/src/common/versions.ts @@ -1,45 +1,45 @@ // auto generated file, do not edit export default { - '@storybook/addon-a11y': '10.2.0-alpha.9', - '@storybook/addon-docs': '10.2.0-alpha.9', - '@storybook/addon-links': '10.2.0-alpha.9', - '@storybook/addon-onboarding': '10.2.0-alpha.9', - 'storybook-addon-pseudo-states': '10.2.0-alpha.9', - '@storybook/addon-themes': '10.2.0-alpha.9', - '@storybook/addon-vitest': '10.2.0-alpha.9', - '@storybook/builder-vite': '10.2.0-alpha.9', - '@storybook/builder-webpack5': '10.2.0-alpha.9', - storybook: '10.2.0-alpha.9', - '@storybook/angular': '10.2.0-alpha.9', - '@storybook/ember': '10.2.0-alpha.9', - '@storybook/html-vite': '10.2.0-alpha.9', - '@storybook/nextjs': '10.2.0-alpha.9', - '@storybook/nextjs-vite': '10.2.0-alpha.9', - '@storybook/preact-vite': '10.2.0-alpha.9', - '@storybook/react-native-web-vite': '10.2.0-alpha.9', - '@storybook/react-vite': '10.2.0-alpha.9', - '@storybook/react-webpack5': '10.2.0-alpha.9', - '@storybook/server-webpack5': '10.2.0-alpha.9', - '@storybook/svelte-vite': '10.2.0-alpha.9', - '@storybook/sveltekit': '10.2.0-alpha.9', - '@storybook/vue3-vite': '10.2.0-alpha.9', - '@storybook/web-components-vite': '10.2.0-alpha.9', - sb: '10.2.0-alpha.9', - '@storybook/cli': '10.2.0-alpha.9', - '@storybook/codemod': '10.2.0-alpha.9', - '@storybook/core-webpack': '10.2.0-alpha.9', - 'create-storybook': '10.2.0-alpha.9', - '@storybook/csf-plugin': '10.2.0-alpha.9', - 'eslint-plugin-storybook': '10.2.0-alpha.9', - '@storybook/react-dom-shim': '10.2.0-alpha.9', - '@storybook/preset-create-react-app': '10.2.0-alpha.9', - '@storybook/preset-react-webpack': '10.2.0-alpha.9', - '@storybook/preset-server-webpack': '10.2.0-alpha.9', - '@storybook/html': '10.2.0-alpha.9', - '@storybook/preact': '10.2.0-alpha.9', - '@storybook/react': '10.2.0-alpha.9', - '@storybook/server': '10.2.0-alpha.9', - '@storybook/svelte': '10.2.0-alpha.9', - '@storybook/vue3': '10.2.0-alpha.9', - '@storybook/web-components': '10.2.0-alpha.9', + '@storybook/addon-a11y': '10.2.0-alpha.10', + '@storybook/addon-docs': '10.2.0-alpha.10', + '@storybook/addon-links': '10.2.0-alpha.10', + '@storybook/addon-onboarding': '10.2.0-alpha.10', + 'storybook-addon-pseudo-states': '10.2.0-alpha.10', + '@storybook/addon-themes': '10.2.0-alpha.10', + '@storybook/addon-vitest': '10.2.0-alpha.10', + '@storybook/builder-vite': '10.2.0-alpha.10', + '@storybook/builder-webpack5': '10.2.0-alpha.10', + storybook: '10.2.0-alpha.10', + '@storybook/angular': '10.2.0-alpha.10', + '@storybook/ember': '10.2.0-alpha.10', + '@storybook/html-vite': '10.2.0-alpha.10', + '@storybook/nextjs': '10.2.0-alpha.10', + '@storybook/nextjs-vite': '10.2.0-alpha.10', + '@storybook/preact-vite': '10.2.0-alpha.10', + '@storybook/react-native-web-vite': '10.2.0-alpha.10', + '@storybook/react-vite': '10.2.0-alpha.10', + '@storybook/react-webpack5': '10.2.0-alpha.10', + '@storybook/server-webpack5': '10.2.0-alpha.10', + '@storybook/svelte-vite': '10.2.0-alpha.10', + '@storybook/sveltekit': '10.2.0-alpha.10', + '@storybook/vue3-vite': '10.2.0-alpha.10', + '@storybook/web-components-vite': '10.2.0-alpha.10', + sb: '10.2.0-alpha.10', + '@storybook/cli': '10.2.0-alpha.10', + '@storybook/codemod': '10.2.0-alpha.10', + '@storybook/core-webpack': '10.2.0-alpha.10', + 'create-storybook': '10.2.0-alpha.10', + '@storybook/csf-plugin': '10.2.0-alpha.10', + 'eslint-plugin-storybook': '10.2.0-alpha.10', + '@storybook/react-dom-shim': '10.2.0-alpha.10', + '@storybook/preset-create-react-app': '10.2.0-alpha.10', + '@storybook/preset-react-webpack': '10.2.0-alpha.10', + '@storybook/preset-server-webpack': '10.2.0-alpha.10', + '@storybook/html': '10.2.0-alpha.10', + '@storybook/preact': '10.2.0-alpha.10', + '@storybook/react': '10.2.0-alpha.10', + '@storybook/server': '10.2.0-alpha.10', + '@storybook/svelte': '10.2.0-alpha.10', + '@storybook/vue3': '10.2.0-alpha.10', + '@storybook/web-components': '10.2.0-alpha.10', }; diff --git a/code/core/src/manager-api/version.ts b/code/core/src/manager-api/version.ts index a835c252fe55..6a3182f84ad0 100644 --- a/code/core/src/manager-api/version.ts +++ b/code/core/src/manager-api/version.ts @@ -1 +1 @@ -export const version = '10.2.0-alpha.9'; +export const version = '10.2.0-alpha.10'; diff --git a/code/frameworks/angular/package.json b/code/frameworks/angular/package.json index f12cf16da9dd..6423ede7873a 100644 --- a/code/frameworks/angular/package.json +++ b/code/frameworks/angular/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/angular", - "version": "10.2.0-alpha.9", + "version": "10.2.0-alpha.10", "description": "Storybook for Angular: Develop, document, and test UI components in isolation", "keywords": [ "storybook", diff --git a/code/frameworks/ember/package.json b/code/frameworks/ember/package.json index ed3a42efd771..02b744f3d382 100644 --- a/code/frameworks/ember/package.json +++ b/code/frameworks/ember/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/ember", - "version": "10.2.0-alpha.9", + "version": "10.2.0-alpha.10", "description": "Storybook for Ember: Develop, document, and test UI components in isolation", "keywords": [ "storybook", diff --git a/code/frameworks/html-vite/package.json b/code/frameworks/html-vite/package.json index 6933a3899cea..8b5bb0ac68ca 100644 --- a/code/frameworks/html-vite/package.json +++ b/code/frameworks/html-vite/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/html-vite", - "version": "10.2.0-alpha.9", + "version": "10.2.0-alpha.10", "description": "Storybook for HTML and Vite: Develop, document, and test UI components in isolation", "keywords": [ "storybook", diff --git a/code/frameworks/nextjs-vite/package.json b/code/frameworks/nextjs-vite/package.json index 033a4aa6ec45..c72233c916c6 100644 --- a/code/frameworks/nextjs-vite/package.json +++ b/code/frameworks/nextjs-vite/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/nextjs-vite", - "version": "10.2.0-alpha.9", + "version": "10.2.0-alpha.10", "description": "Storybook for Next.js and Vite: Develop, document, and test UI components in isolation", "keywords": [ "storybook", diff --git a/code/frameworks/nextjs/package.json b/code/frameworks/nextjs/package.json index 18adf3e4b302..35910ff630f6 100644 --- a/code/frameworks/nextjs/package.json +++ b/code/frameworks/nextjs/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/nextjs", - "version": "10.2.0-alpha.9", + "version": "10.2.0-alpha.10", "description": "Storybook for Next.js: Develop, document, and test UI components in isolation", "keywords": [ "storybook", diff --git a/code/frameworks/preact-vite/package.json b/code/frameworks/preact-vite/package.json index 18b51a5330f0..ddaf8f968866 100644 --- a/code/frameworks/preact-vite/package.json +++ b/code/frameworks/preact-vite/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/preact-vite", - "version": "10.2.0-alpha.9", + "version": "10.2.0-alpha.10", "description": "Storybook for Preact and Vite: Develop, document, and test UI components in isolation", "keywords": [ "storybook", diff --git a/code/frameworks/react-native-web-vite/package.json b/code/frameworks/react-native-web-vite/package.json index 0b0705e343df..6cde31263962 100644 --- a/code/frameworks/react-native-web-vite/package.json +++ b/code/frameworks/react-native-web-vite/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/react-native-web-vite", - "version": "10.2.0-alpha.9", + "version": "10.2.0-alpha.10", "description": "Storybook for React Native Web and Vite: Develop, document, and test UI components in isolation", "keywords": [ "storybook", diff --git a/code/frameworks/react-vite/package.json b/code/frameworks/react-vite/package.json index 910846b789a3..3f2fdf494791 100644 --- a/code/frameworks/react-vite/package.json +++ b/code/frameworks/react-vite/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/react-vite", - "version": "10.2.0-alpha.9", + "version": "10.2.0-alpha.10", "description": "Storybook for React and Vite: Develop, document, and test UI components in isolation", "keywords": [ "storybook", diff --git a/code/frameworks/react-webpack5/package.json b/code/frameworks/react-webpack5/package.json index 64c7ae988c45..3772c07de73a 100644 --- a/code/frameworks/react-webpack5/package.json +++ b/code/frameworks/react-webpack5/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/react-webpack5", - "version": "10.2.0-alpha.9", + "version": "10.2.0-alpha.10", "description": "Storybook for React and Webpack: Develop, document, and test UI components in isolation", "keywords": [ "storybook", diff --git a/code/frameworks/server-webpack5/package.json b/code/frameworks/server-webpack5/package.json index 800a6f60f0a8..11d7d052b505 100644 --- a/code/frameworks/server-webpack5/package.json +++ b/code/frameworks/server-webpack5/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/server-webpack5", - "version": "10.2.0-alpha.9", + "version": "10.2.0-alpha.10", "description": "Storybook for Server: View HTML snippets from a server in isolation with Hot Reloading.", "keywords": [ "storybook", diff --git a/code/frameworks/svelte-vite/package.json b/code/frameworks/svelte-vite/package.json index 60735c613a7b..28c69c8fe48c 100644 --- a/code/frameworks/svelte-vite/package.json +++ b/code/frameworks/svelte-vite/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/svelte-vite", - "version": "10.2.0-alpha.9", + "version": "10.2.0-alpha.10", "description": "Storybook for Svelte and Vite: Develop, document, and test UI components in isolation", "keywords": [ "storybook", diff --git a/code/frameworks/sveltekit/package.json b/code/frameworks/sveltekit/package.json index a784dc7c6092..37f06731d50d 100644 --- a/code/frameworks/sveltekit/package.json +++ b/code/frameworks/sveltekit/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/sveltekit", - "version": "10.2.0-alpha.9", + "version": "10.2.0-alpha.10", "description": "Storybook for SvelteKit: Develop, document, and test UI components in isolation", "keywords": [ "storybook", diff --git a/code/frameworks/vue3-vite/package.json b/code/frameworks/vue3-vite/package.json index 8bc8d15f3541..9795fd1f9d33 100644 --- a/code/frameworks/vue3-vite/package.json +++ b/code/frameworks/vue3-vite/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/vue3-vite", - "version": "10.2.0-alpha.9", + "version": "10.2.0-alpha.10", "description": "Storybook for Vue3 and Vite: Develop, document, and test UI components in isolation", "keywords": [ "storybook", diff --git a/code/frameworks/web-components-vite/package.json b/code/frameworks/web-components-vite/package.json index f2142c817c0e..7b51760f463b 100644 --- a/code/frameworks/web-components-vite/package.json +++ b/code/frameworks/web-components-vite/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/web-components-vite", - "version": "10.2.0-alpha.9", + "version": "10.2.0-alpha.10", "description": "Storybook for Web Components and Vite: Develop, document, and test UI components in isolation", "keywords": [ "storybook", diff --git a/code/lib/cli-sb/package.json b/code/lib/cli-sb/package.json index 0935dd3f35a1..8426fc59d484 100644 --- a/code/lib/cli-sb/package.json +++ b/code/lib/cli-sb/package.json @@ -1,6 +1,6 @@ { "name": "sb", - "version": "10.2.0-alpha.9", + "version": "10.2.0-alpha.10", "description": "Storybook CLI: Develop, document, and test UI components in isolation", "keywords": [ "storybook", diff --git a/code/lib/cli-storybook/package.json b/code/lib/cli-storybook/package.json index 6e5379168391..2c44155254b3 100644 --- a/code/lib/cli-storybook/package.json +++ b/code/lib/cli-storybook/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/cli", - "version": "10.2.0-alpha.9", + "version": "10.2.0-alpha.10", "description": "Storybook CLI: Develop, document, and test UI components in isolation", "keywords": [ "storybook", diff --git a/code/lib/codemod/package.json b/code/lib/codemod/package.json index deabbf813373..0c44f5146989 100644 --- a/code/lib/codemod/package.json +++ b/code/lib/codemod/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/codemod", - "version": "10.2.0-alpha.9", + "version": "10.2.0-alpha.10", "description": "A collection of codemod scripts written with JSCodeshift", "keywords": [ "storybook" diff --git a/code/lib/core-webpack/package.json b/code/lib/core-webpack/package.json index 0ac86f328535..8229d57b3170 100644 --- a/code/lib/core-webpack/package.json +++ b/code/lib/core-webpack/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/core-webpack", - "version": "10.2.0-alpha.9", + "version": "10.2.0-alpha.10", "description": "Storybook framework-agnostic API", "keywords": [ "storybook" diff --git a/code/lib/create-storybook/package.json b/code/lib/create-storybook/package.json index 14dd5e1aa334..b14bc2aa85fe 100644 --- a/code/lib/create-storybook/package.json +++ b/code/lib/create-storybook/package.json @@ -1,6 +1,6 @@ { "name": "create-storybook", - "version": "10.2.0-alpha.9", + "version": "10.2.0-alpha.10", "description": "Storybook installer: Develop, document, and test UI components in isolation", "keywords": [ "storybook", diff --git a/code/lib/csf-plugin/package.json b/code/lib/csf-plugin/package.json index da391658c2c0..35a89ad00d43 100644 --- a/code/lib/csf-plugin/package.json +++ b/code/lib/csf-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/csf-plugin", - "version": "10.2.0-alpha.9", + "version": "10.2.0-alpha.10", "description": "Enrich CSF files via static analysis", "keywords": [ "storybook" diff --git a/code/lib/eslint-plugin/package.json b/code/lib/eslint-plugin/package.json index d7d946646d35..6045a8094f77 100644 --- a/code/lib/eslint-plugin/package.json +++ b/code/lib/eslint-plugin/package.json @@ -1,6 +1,6 @@ { "name": "eslint-plugin-storybook", - "version": "10.2.0-alpha.9", + "version": "10.2.0-alpha.10", "description": "Storybook ESLint Plugin: Best practice rules for writing stories", "keywords": [ "eslint", diff --git a/code/lib/react-dom-shim/package.json b/code/lib/react-dom-shim/package.json index 58b5d7953008..1d0772288de0 100644 --- a/code/lib/react-dom-shim/package.json +++ b/code/lib/react-dom-shim/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/react-dom-shim", - "version": "10.2.0-alpha.9", + "version": "10.2.0-alpha.10", "description": "", "keywords": [ "storybook" diff --git a/code/package.json b/code/package.json index c4d77791c7c0..8def4d577473 100644 --- a/code/package.json +++ b/code/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/code", - "version": "10.2.0-alpha.9", + "version": "10.2.0-alpha.10", "private": true, "description": "Storybook root", "homepage": "https://storybook.js.org/", @@ -220,6 +220,5 @@ "Dependency Upgrades" ] ] - }, - "deferredNextVersion": "10.2.0-alpha.10" + } } diff --git a/code/presets/create-react-app/package.json b/code/presets/create-react-app/package.json index f09d90426055..95054124a870 100644 --- a/code/presets/create-react-app/package.json +++ b/code/presets/create-react-app/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/preset-create-react-app", - "version": "10.2.0-alpha.9", + "version": "10.2.0-alpha.10", "description": "Storybook for Create React App preset", "keywords": [ "storybook" diff --git a/code/presets/react-webpack/package.json b/code/presets/react-webpack/package.json index 902ce2c7690f..9561fabb4332 100644 --- a/code/presets/react-webpack/package.json +++ b/code/presets/react-webpack/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/preset-react-webpack", - "version": "10.2.0-alpha.9", + "version": "10.2.0-alpha.10", "description": "Storybook for React: Develop React Component in isolation with Hot Reloading", "keywords": [ "storybook" diff --git a/code/presets/server-webpack/package.json b/code/presets/server-webpack/package.json index 1ef021ab499f..d9a6d14cf49b 100644 --- a/code/presets/server-webpack/package.json +++ b/code/presets/server-webpack/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/preset-server-webpack", - "version": "10.2.0-alpha.9", + "version": "10.2.0-alpha.10", "description": "Storybook for Server: View HTML snippets from a server in isolation with Hot Reloading.", "keywords": [ "storybook" diff --git a/code/renderers/html/package.json b/code/renderers/html/package.json index 4b8010c4662d..9edb41c7d450 100644 --- a/code/renderers/html/package.json +++ b/code/renderers/html/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/html", - "version": "10.2.0-alpha.9", + "version": "10.2.0-alpha.10", "description": "Storybook HTML renderer: Develop, document, and test UI components in isolation", "keywords": [ "storybook", diff --git a/code/renderers/preact/package.json b/code/renderers/preact/package.json index f17440253943..40bb65c5feaf 100644 --- a/code/renderers/preact/package.json +++ b/code/renderers/preact/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/preact", - "version": "10.2.0-alpha.9", + "version": "10.2.0-alpha.10", "description": "Storybook Preact renderer: Develop, document, and test UI components in isolation", "keywords": [ "storybook", diff --git a/code/renderers/react/package.json b/code/renderers/react/package.json index faac44064c70..0cfc34c55b60 100644 --- a/code/renderers/react/package.json +++ b/code/renderers/react/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/react", - "version": "10.2.0-alpha.9", + "version": "10.2.0-alpha.10", "description": "Storybook React renderer", "keywords": [ "storybook" diff --git a/code/renderers/server/package.json b/code/renderers/server/package.json index 493b3650d01a..d093cf35dfa9 100644 --- a/code/renderers/server/package.json +++ b/code/renderers/server/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/server", - "version": "10.2.0-alpha.9", + "version": "10.2.0-alpha.10", "description": "Storybook Server renderer: Develop, document, and test UI components in isolation", "keywords": [ "storybook", diff --git a/code/renderers/svelte/package.json b/code/renderers/svelte/package.json index 74260f516053..927da5a7c89b 100644 --- a/code/renderers/svelte/package.json +++ b/code/renderers/svelte/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/svelte", - "version": "10.2.0-alpha.9", + "version": "10.2.0-alpha.10", "description": "Storybook Svelte renderer: Develop, document, and test UI components in isolation.", "keywords": [ "storybook", diff --git a/code/renderers/vue3/package.json b/code/renderers/vue3/package.json index 9195fb2c891b..fcd03b201037 100644 --- a/code/renderers/vue3/package.json +++ b/code/renderers/vue3/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/vue3", - "version": "10.2.0-alpha.9", + "version": "10.2.0-alpha.10", "description": "Storybook Vue 3 renderer: Develop, document, and test UI components in isolation", "keywords": [ "storybook", diff --git a/code/renderers/web-components/package.json b/code/renderers/web-components/package.json index d971722d25ed..623e325be37a 100644 --- a/code/renderers/web-components/package.json +++ b/code/renderers/web-components/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/web-components", - "version": "10.2.0-alpha.9", + "version": "10.2.0-alpha.10", "description": "Storybook Web Components renderer: Develop, document, and test UI components in isolation", "keywords": [ "storybook", From 424e74657238f13c8e66aa12a637468034a9a26c Mon Sep 17 00:00:00 2001 From: Steve Dodier-Lazaro Date: Mon, 29 Dec 2025 15:46:46 +0100 Subject: [PATCH 19/33] Docs: Mention Vite publicDir interference --- docs/api/main-config/main-config-static-dirs.mdx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/api/main-config/main-config-static-dirs.mdx b/docs/api/main-config/main-config-static-dirs.mdx index 5b026d11a370..0207e0b5e67d 100644 --- a/docs/api/main-config/main-config-static-dirs.mdx +++ b/docs/api/main-config/main-config-static-dirs.mdx @@ -17,6 +17,10 @@ Sets a list of directories of [static files](../../configure/integration/images- {/* prettier-ignore-end */} + +When using Vite-based frameworks, additional directories may be copied to your build directory because of Vite's own [static asset handling](https://vite.dev/guide/assets#the-public-directory). You can set Vite's `publicDir` option to `false` to disable this behavior. + + ## With configuration objects You can also use a configuration object to define the directories: @@ -25,4 +29,4 @@ You can also use a configuration object to define the directories: -{/* prettier-ignore-end */} +{/* prettier-ignore-end */} \ No newline at end of file From 9a6b80ae86e11afc38bbd0402b93e1c01d075ae3 Mon Sep 17 00:00:00 2001 From: Steve Dodier-Lazaro Date: Tue, 30 Dec 2025 13:32:40 +0100 Subject: [PATCH 20/33] Docs: Mention Vite publicDir interference --- docs/configure/integration/images-and-assets.mdx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/configure/integration/images-and-assets.mdx b/docs/configure/integration/images-and-assets.mdx index 8670b6e41939..757d858c68ae 100644 --- a/docs/configure/integration/images-and-assets.mdx +++ b/docs/configure/integration/images-and-assets.mdx @@ -55,6 +55,10 @@ Or even use a configuration object to define the directories: {/* prettier-ignore-end */} + +When using Vite-based frameworks, additional directories may be copied to your build directory because of Vite's own [static asset handling](https://vite.dev/guide/assets#the-public-directory). You can set Vite's `publicDir` option to `false` to disable this behavior. + + ## Reference assets from a CDN Upload your files to an online CDN and reference them. In this example, we’re using a placeholder image service. From 1439a2418aeed7207672e13b933f049fdec70b2c Mon Sep 17 00:00:00 2001 From: Steve Dodier-Lazaro Date: Tue, 30 Dec 2025 14:38:32 +0100 Subject: [PATCH 21/33] Core: Fix internal yarn build command --- scripts/build-package.ts | 124 +++++++++++++++++---------------------- 1 file changed, 55 insertions(+), 69 deletions(-) diff --git a/scripts/build-package.ts b/scripts/build-package.ts index 6f25888a42d0..b65930d348a1 100644 --- a/scripts/build-package.ts +++ b/scripts/build-package.ts @@ -5,12 +5,14 @@ * * You can pass a list of package names to build, or use the `--all` flag to build all packages. * - * You can also pass the `--watch` flag to build in watch mode. + * Pass the `--watch` flag to build in watch mode or `--no-watch` to skip the watch mode prompt. * - * You can also pass the `--prod` flag to build in production mode. + * Pass the `--prod` flag to build in production mode or `--no-prod` to skip the production prompt. * * When you pass no package names, you will be prompted to select which packages to build. */ +// FIXME --all not working +// FIXME --no-watch and --no-prod needed import { join } from 'node:path'; import { exec } from 'child_process'; @@ -25,8 +27,20 @@ import { findMostMatchText } from './utils/diff'; import { getCodeWorkspaces } from './utils/workspace'; async function run() { - const packages = (await getCodeWorkspaces()).filter(({ name }) => name !== '@storybook/code'); - const packageTasks = packages + const packages = (await getCodeWorkspaces()) + .filter(({ name }) => name !== '@storybook/code') + .sort((a) => (a.name === 'storybook' ? -1 : 0)); // Place main package first in option list + + const tasks: Record< + string, + { + name: string; + defaultValue: boolean; + suffix: string; + value?: unknown; + location?: string; + } + > = packages .map((pkg) => { let suffix = pkg.name.replace('@storybook/', ''); if (pkg.name === '@storybook/cli') { @@ -36,7 +50,6 @@ async function run() { ...pkg, suffix, defaultValue: false, - helpText: `build only the ${pkg.name} package`, }; }) .reduce( @@ -44,86 +57,59 @@ async function run() { acc[next.name] = next; return acc; }, - {} as Record< - string, - { name: string; defaultValue: boolean; suffix: string; helpText: string } - > + {} as Record ); - const tasks: Record< - string, - { - name: string; - defaultValue: boolean; - suffix: string; - helpText: string; - value?: any; - location?: string; - } - > = { - watch: { - name: `watch`, - defaultValue: false, - suffix: '--watch', - helpText: 'build on watch mode', - }, - prod: { - name: `prod`, - defaultValue: false, - suffix: '--prod', - helpText: 'build on production mode', - }, - ...packageTasks, - }; - const main = program .version('5.0.0') - .option('--all', `build everything ${picocolors.gray('(all)')}`); + .allowExcessArguments(true) + .option('--all', `build everything ${picocolors.gray('(all)')}`, false) + .option('--watch', `build in watch mode`) + .option('--prod', `build in production mode`) + .option('--no-watch', `do not build in watch mode`) + .option('--no-prod', `do not build in production mode`); + + main.parse(process.argv); - Object.keys(tasks) - .reduce((acc, key) => acc.option(tasks[key].suffix, tasks[key].helpText), main) - .parse(process.argv); + const opts = main.opts(); + let watchMode = opts.watch; + let prodMode = opts.prod; Object.keys(tasks).forEach((key) => { - const opts = program.opts(); - // checks if a flag is passed e.g. yarn build --@storybook/addon-docs --watch - const containsFlag = program.args.includes(tasks[key].suffix); + // checks if a flag is passed e.g. yarn build addon-docs --watch + const containsFlag = main.args.includes(tasks[key].suffix); tasks[key].value = containsFlag || opts.all; }); - let watchMode = process.argv.includes('--watch'); - let prodMode = process.argv.includes('--prod'); - let selection = Object.keys(tasks) - .map((key) => tasks[key]) - .filter((item) => !['watch', 'prod'].includes(item.name) && item.value === true); - - // user has passed invalid package name(s) - try to guess the correct package name(s) - if ((!selection.length && main.args.length >= 1) || selection.length !== main.args.length) { - const suffixList = Object.values(tasks) - .filter((t) => t.name.includes('@storybook')) - .map((t) => t.suffix); - - for (const arg of main.args) { - if (!suffixList.includes(arg)) { - const matchText = findMostMatchText(suffixList, arg); - - if (matchText) { - console.log( - `${picocolors.red('Error')}: ${picocolors.cyan( - arg - )} is not a valid package name, Did you mean ${picocolors.cyan(matchText)}?` - ); - } + let selection = Object.values(tasks).filter((item) => item.value === true); + + // check for invalid package name(s) and try to guess the correct package name(s) + const suffixList = Object.values(tasks).map((t) => t.suffix); + let hasInvalidName = false; + + for (const arg of main.args) { + if (!suffixList.includes(arg)) { + const matchText = findMostMatchText(suffixList, arg); + + if (matchText) { + hasInvalidName = true; + process.stderr.write( + `${picocolors.red('Error')}: ${picocolors.cyan( + arg + )} is not a valid package name, Did you mean ${picocolors.cyan(matchText)}?\n` + ); } } + } + if (hasInvalidName) { process.exit(0); } if (!selection.length) { selection = await prompts( [ - { + watchMode === undefined && { type: 'toggle', name: 'watch', message: 'Start in watch mode', @@ -131,7 +117,7 @@ async function run() { active: 'yes', inactive: 'no', }, - { + prodMode === undefined && { type: 'toggle', name: 'prod', message: 'Start in production mode', @@ -144,7 +130,7 @@ async function run() { message: 'Select the packages to build', name: 'todo', min: 1, - hint: 'You can also run directly with package name like `yarn build core`, or `yarn build --all` for all packages!', + hint: 'You can also run directly with package name like `yarn build storybook`, or `yarn build --all` for all packages!', // @ts-expect-error @types incomplete optionsPerPage: windowSize.height - 3, // 3 lines for extra info choices: packages.map(({ name: key }) => ({ @@ -162,7 +148,7 @@ async function run() { }); } - console.log('Building selected packages...'); + process.stdout.write('Building selected packages...\n'); let lastName = ''; selection.forEach(async (v) => { From fc1d08ed1a9892d02aaf5c4134601e2b94b820cf Mon Sep 17 00:00:00 2001 From: Steve Dodier-Lazaro Date: Tue, 30 Dec 2025 14:47:19 +0100 Subject: [PATCH 22/33] DX: Document yarn build options --- docs/contribute/code.mdx | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/contribute/code.mdx b/docs/contribute/code.mdx index c02ba28efbac..fe23efbcb59a 100644 --- a/docs/contribute/code.mdx +++ b/docs/contribute/code.mdx @@ -102,6 +102,19 @@ Otherwise, if it affects the `Manager` (the outermost Storybook `iframe` where t ![Storybook manager preview](../_assets/addons/manager-preview.png) +The `yarn build` commands accepts arguments to help speed up your development workflow: +* `--all` will cause all packages to be built +* `--watch` will enable watch mode (and skip the watch mode prompt) +* `--prod` will build for production (and skip the production mode prompt) +* individual package names can be passed, without the `@storybook/` prefix, e.g. `storybook`, `addon-docs`, etc. + +For example, to build Storybook and the docs addon in watch mode, run: + +```shell +yarn build --watch storybook addon-docs +``` + + ## Check your work When you're done coding, add documentation and tests as appropriate. That simplifies the PR review process, which means your code will get merged faster. From c9af058a906f2b0650f77ca1b7ab29b93cf9902d Mon Sep 17 00:00:00 2001 From: Steve Dodier-Lazaro Date: Tue, 30 Dec 2025 15:33:46 +0100 Subject: [PATCH 23/33] Remove obsolete comments --- scripts/build-package.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/scripts/build-package.ts b/scripts/build-package.ts index b65930d348a1..ffc1447ff619 100644 --- a/scripts/build-package.ts +++ b/scripts/build-package.ts @@ -11,8 +11,6 @@ * * When you pass no package names, you will be prompted to select which packages to build. */ -// FIXME --all not working -// FIXME --no-watch and --no-prod needed import { join } from 'node:path'; import { exec } from 'child_process'; From fd3bbc57d2c8cbd620d3eb430318837c345aaa70 Mon Sep 17 00:00:00 2001 From: Steve Dodier-Lazaro Date: Tue, 30 Dec 2025 15:34:19 +0100 Subject: [PATCH 24/33] Use correct exit code for failed CLI run --- scripts/build-package.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/build-package.ts b/scripts/build-package.ts index ffc1447ff619..39ec50244a95 100644 --- a/scripts/build-package.ts +++ b/scripts/build-package.ts @@ -101,7 +101,7 @@ async function run() { } if (hasInvalidName) { - process.exit(0); + process.exit(1); } if (!selection.length) { From be9b72b43f8d11c28d2bc6495509fe2b00bd0895 Mon Sep 17 00:00:00 2001 From: Norbert de Langen Date: Wed, 31 Dec 2025 10:29:04 +0100 Subject: [PATCH 25/33] Core: Add try-catch for cross-origin access in Storybook hooks --- code/core/template/stories/preview.ts | 29 ++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/code/core/template/stories/preview.ts b/code/core/template/stories/preview.ts index 3756bc7d73cb..106b8844edc3 100644 --- a/code/core/template/stories/preview.ts +++ b/code/core/template/stories/preview.ts @@ -9,15 +9,30 @@ declare global { } } -// This is used to test the hooks in our E2E tests (look for storybook-hooks.spec.ts) -globalThis.parent.__STORYBOOK_BEFORE_ALL_CALLS__ = 0; -globalThis.parent.__STORYBOOK_BEFORE_ALL_CLEANUP_CALLS__ = 0; +try { + // This is used to test the hooks in our E2E tests (look for storybook-hooks.spec.ts) + + /** + * Wrapped in a try-catch, because accessing properties on globalThis.parent may throw if the + * parent is cross-origin. + */ + globalThis.parent.__STORYBOOK_BEFORE_ALL_CALLS__ = 0; + globalThis.parent.__STORYBOOK_BEFORE_ALL_CLEANUP_CALLS__ = 0; +} catch { + // ignore +} export const beforeAll = async () => { - globalThis.parent.__STORYBOOK_BEFORE_ALL_CALLS__ += 1; - return () => { - globalThis.parent.__STORYBOOK_BEFORE_ALL_CLEANUP_CALLS__ += 1; - }; + let cleanup: () => void = () => {}; + try { + globalThis.parent.__STORYBOOK_BEFORE_ALL_CALLS__ += 1; + cleanup = () => { + globalThis.parent.__STORYBOOK_BEFORE_ALL_CLEANUP_CALLS__ += 1; + }; + } catch { + // ignore + } + return cleanup; }; export const parameters = { From 0a48d272441d00ad4d27d1964c1be6ab2ef99f30 Mon Sep 17 00:00:00 2001 From: Steve Dodier-Lazaro Date: Tue, 30 Dec 2025 15:13:19 +0100 Subject: [PATCH 26/33] UI: Keep preview frame stable in overall layout --- .../src/manager/components/layout/Layout.tsx | 44 +++++++++---------- 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/code/core/src/manager/components/layout/Layout.tsx b/code/core/src/manager/components/layout/Layout.tsx index be3f3ee054de..4398df3c5c89 100644 --- a/code/core/src/manager/components/layout/Layout.tsx +++ b/code/core/src/manager/components/layout/Layout.tsx @@ -169,39 +169,35 @@ export const Layout = ({ managerLayoutState, setManagerLayoutState, hasTab, ...s showPanel={showPanel} > {showPages && {slots.slotPages}} - {isDesktop && ( - <> + <> + {isDesktop && ( {slots.slotSidebar} - - {slots.slotMain} - - {showPanel && ( - - - {slots.slotPanel} - - )} - - )} - - {isMobile && ( - <> + )} + {isMobile && ( - {slots.slotMain} - - - )} + )} + + {slots.slotMain} + + {isDesktop && showPanel && ( + + + {slots.slotPanel} + + )} + {isMobile && } + ); }; From bf55dfba7cc96ff76590dc73296b353829a23d45 Mon Sep 17 00:00:00 2001 From: Gert Hengeveld Date: Mon, 5 Jan 2026 08:44:09 +0100 Subject: [PATCH 27/33] Disable open in isolation keyboard shortcut for docs pages --- 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 68e2e7cc6eb0..3b28cfe8168d 100644 --- a/code/core/src/manager-api/modules/shortcuts.ts +++ b/code/core/src/manager-api/modules/shortcuts.ts @@ -250,6 +250,7 @@ export const init: ModuleFn = ({ store, fullAPI, provider }) => { ui: { enableShortcuts }, storyId, refId, + viewMode, } = store.getState(); if (!enableShortcuts) { return; @@ -401,7 +402,7 @@ export const init: ModuleFn = ({ store, fullAPI, provider }) => { break; } case 'openInIsolation': { - if (storyId) { + if (storyId && viewMode === 'story') { const { previewHref } = fullAPI.getStoryHrefs(storyId, { refId }); window.open(previewHref, '_blank', 'noopener,noreferrer'); } From 381ba170f795c80b1d34c8932299f482c8528bb9 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Tue, 18 Nov 2025 12:01:31 +0100 Subject: [PATCH 28/33] Document api methods --- .../storybook-addons-api-addnotification.md | 27 +++++++ ...torybook-addons-api-getcurrentstorydata.md | 7 ++ .../storybook-addons-api-getstoryhrefs.md | 25 +++++++ .../storybook-addons-api-openineditor.md | 23 ++++++ .../storybook-addons-api-togglefullscreen.md | 12 +++ .../storybook-addons-api-togglepanel.md | 12 +++ docs/addons/addons-api.mdx | 75 +++++++++++++++++++ 7 files changed, 181 insertions(+) create mode 100644 docs/_snippets/storybook-addons-api-addnotification.md create mode 100644 docs/_snippets/storybook-addons-api-getcurrentstorydata.md create mode 100644 docs/_snippets/storybook-addons-api-getstoryhrefs.md create mode 100644 docs/_snippets/storybook-addons-api-openineditor.md create mode 100644 docs/_snippets/storybook-addons-api-togglefullscreen.md create mode 100644 docs/_snippets/storybook-addons-api-togglepanel.md diff --git a/docs/_snippets/storybook-addons-api-addnotification.md b/docs/_snippets/storybook-addons-api-addnotification.md new file mode 100644 index 000000000000..38b3226b912b --- /dev/null +++ b/docs/_snippets/storybook-addons-api-addnotification.md @@ -0,0 +1,27 @@ +```js +import { addons } from '@storybook/manager-api'; +import { CheckIcon } from '@storybook/icons'; + +addons.register('my-organisation/my-addon', (api) => { + // Add a simple notification + api.addNotification({ + id: 'my-notification', + content: { + headline: 'Action completed', + subHeadline: 'Your changes have been saved successfully', + }, + duration: 5000, // 5 seconds + }); + + // Add a notification with an icon + api.addNotification({ + id: 'success-notification', + content: { + headline: 'Success!', + subHeadline: 'Operation completed successfully', + }, + icon: , + duration: 3000, + }); +}); +``` diff --git a/docs/_snippets/storybook-addons-api-getcurrentstorydata.md b/docs/_snippets/storybook-addons-api-getcurrentstorydata.md new file mode 100644 index 000000000000..66e07cbc72f5 --- /dev/null +++ b/docs/_snippets/storybook-addons-api-getcurrentstorydata.md @@ -0,0 +1,7 @@ +```js +addons.register('my-organisation/my-addon', (api) => { + // Get data about the currently selected story + const storyData = api.getCurrentStoryData(); + console.log('Current story:', storyData.id, storyData.title); +}); +``` diff --git a/docs/_snippets/storybook-addons-api-getstoryhrefs.md b/docs/_snippets/storybook-addons-api-getstoryhrefs.md new file mode 100644 index 000000000000..b3388e0d9b5f --- /dev/null +++ b/docs/_snippets/storybook-addons-api-getstoryhrefs.md @@ -0,0 +1,25 @@ +```js +addons.register('my-organisation/my-addon', (api) => { + // Get the manager and preview URLs for a story + const { managerHref, previewHref } = api.getStoryHrefs('button--primary'); + + // Get absolute URLs based on the current manager URL (origin) + api.getStoryHrefs('button--primary', { base: 'origin' }); + + // Get absolute URLs based on the dev server's local network IP address + api.getStoryHrefs('button--primary', { base: 'network' }); + + // Get clean URLs without args or globals + api.getStoryHrefs('button--primary', { inheritArgs: false, inheritGlobals: false }); + + // With args and globals (merged on top of inherited args/globals) as well as a custom query param + // Note that args and globals should be serialized (see `buildArgsParam` from `storybook/internal/router`) + api.getStoryHrefs('button--primary', { queryParams: { args: 'label:Label', globals: 'outline:!true', custom: 'value' } }); + + // Link to a story from an external ref + api.getStoryHrefs('button--primary', { refId: 'external-ref' }); + + // Link to a docs page + api.getStoryHrefs('button--docs', { viewMode: 'docs' }); +}); +``` diff --git a/docs/_snippets/storybook-addons-api-openineditor.md b/docs/_snippets/storybook-addons-api-openineditor.md new file mode 100644 index 000000000000..7f9e36e7baa7 --- /dev/null +++ b/docs/_snippets/storybook-addons-api-openineditor.md @@ -0,0 +1,23 @@ +```js +addons.register('my-organisation/my-addon', (api) => { + // Open a file in the editor + api.openInEditor({ + file: './src/components/Button.tsx', + }); + + // Handle the api response + api + .openInEditor({ + file: './src/components/Button.tsx', + line: 42, + column: 15, + }) + .then((response) => { + if (response.error) { + console.error('Failed to open file:', response.error); + } else { + console.log('File opened successfully'); + } + }); +}); +``` diff --git a/docs/_snippets/storybook-addons-api-togglefullscreen.md b/docs/_snippets/storybook-addons-api-togglefullscreen.md new file mode 100644 index 000000000000..ee11fcf3a3f6 --- /dev/null +++ b/docs/_snippets/storybook-addons-api-togglefullscreen.md @@ -0,0 +1,12 @@ +```js +addons.register('my-organisation/my-addon', (api) => { + // Toggle fullscreen mode + api.toggleFullscreen(); + + // Enable fullscreen + api.toggleFullscreen(true); + + // Disable fullscreen + api.toggleFullscreen(false); +}); +``` diff --git a/docs/_snippets/storybook-addons-api-togglepanel.md b/docs/_snippets/storybook-addons-api-togglepanel.md new file mode 100644 index 000000000000..64f5f54d66f9 --- /dev/null +++ b/docs/_snippets/storybook-addons-api-togglepanel.md @@ -0,0 +1,12 @@ +```js +addons.register('my-organisation/my-addon', (api) => { + // Toggle panel visibility + api.togglePanel(); + + // Show the panel + api.togglePanel(true); + + // Hide the panel + api.togglePanel(false); +}); +``` diff --git a/docs/addons/addons-api.mdx b/docs/addons/addons-api.mdx index e6a2b9bc79c6..d2b518e44adb 100644 --- a/docs/addons/addons-api.mdx +++ b/docs/addons/addons-api.mdx @@ -155,6 +155,25 @@ This method allows you to get the application URL state, including any overridde {/* prettier-ignore-end */} +### api.getStoryHrefs(storyId, options?) + +Get the manager and preview URLs for a story. URLs are relative to the current Storybook, unless `base` is set, or in case of previewHref with `refId` set. + +- `storyId` (required): The ID of the story to get the URLs for +- `options` (optional): + - `base`: Return an absolute href based on the current origin or network address. + - `inheritArgs`: Inherit args from the current URL. If `storyId` matches current story, `inheritArgs` defaults to true. + - `inheritGlobals`: Inherit globals from the current URL. Defaults to true. + - `queryParams`: Additional query params to append to the URL (`args` and `globals` are merged when inheriting). + - `refId`: ID of the ref to get the URL for (for composed Storybooks) + - `viewMode`: The view mode to use, defaults to 'story'. + +{/* prettier-ignore-start */} + + + +{/* prettier-ignore-end */} + ### api.on(eventName, fn) This method allows you to register a handler function called whenever the user navigates between stories. @@ -165,6 +184,62 @@ This method allows you to register a handler function called whenever the user n {/* prettier-ignore-end */} +### api.openInEditor(payload) + +Opens a file in the configured code editor. Useful for "Edit in IDE" functionality in addons. + +- `payload.file`: The file path to open (required) +- `payload.line`: Optional line number to jump to +- `payload.column`: Optional column number to jump to + +Returns a Promise that resolves with information about whether the operation was successful. + +{/* prettier-ignore-start */} + + + +{/* prettier-ignore-end */} + +### api.getCurrentStoryData() + +Returns the current story's data, including its ID, kind, name, and parameters. + +{/* prettier-ignore-start */} + + + +{/* prettier-ignore-end */} + +### api.toggleFullscreen(toggled?) + +Toggles the fullscreen mode of the Storybook UI. Pass `true` to enable fullscreen, `false` to disable, or omit to toggle the current state. + +{/* prettier-ignore-start */} + + + +{/* prettier-ignore-end */} + +### api.togglePanel(toggled?) + +Toggles the visibility of the addon panel. Pass `true` to show the panel, `false` to hide, or omit to toggle the current state. + +{/* prettier-ignore-start */} + + + +{/* prettier-ignore-end */} + +### api.addNotification(notification) + +Displays a notification in the Storybook UI. The notification object should contain `id`, `content`, and optionally `duration` and `icon`. + +{/* prettier-ignore-start */} + + + +{/* prettier-ignore-end */} + ### addons.setConfig(config) This method allows you to override the default Storybook UI configuration (e.g., set up a [theme](../configure/user-interface/theming.mdx) or hide UI elements): From 2e9e27618042e8c9d283aa165e52f2fb826fb7d5 Mon Sep 17 00:00:00 2001 From: Gert Hengeveld Date: Mon, 5 Jan 2026 11:58:43 +0100 Subject: [PATCH 29/33] Add missing openInEditor SubAPI typedefs --- code/core/src/manager-api/root.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/code/core/src/manager-api/root.tsx b/code/core/src/manager-api/root.tsx index 7388657698a9..f6526c27d1ae 100644 --- a/code/core/src/manager-api/root.tsx +++ b/code/core/src/manager-api/root.tsx @@ -105,6 +105,7 @@ export type API = addons.SubAPI & version.SubAPI & url.SubAPI & whatsnew.SubAPI & + openInEditor.SubAPI & Other; interface Other { From 16761a336f8bc33094e9d1cbfda8a27e71ca93fd Mon Sep 17 00:00:00 2001 From: Gert Hengeveld Date: Mon, 5 Jan 2026 13:37:19 +0100 Subject: [PATCH 30/33] Apply suggestions --- code/addons/docs/src/blocks/getStoryHref.ts | 6 ++++-- code/core/src/components/components/utils/getStoryHref.ts | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/code/addons/docs/src/blocks/getStoryHref.ts b/code/addons/docs/src/blocks/getStoryHref.ts index bde3cddbb505..524fbc43f613 100644 --- a/code/addons/docs/src/blocks/getStoryHref.ts +++ b/code/addons/docs/src/blocks/getStoryHref.ts @@ -1,5 +1,7 @@ -// Only for internal use in addon-docs code, because the parent util in `core` cannot be imported. -// Unlike the parent util, this one only returns the preview URL. +/** + * Only for internal use in addon-docs code, because the parent util in `core` cannot be imported. + * Unlike the parent util, this one only returns the preview URL. + */ export const getStoryHref = (storyId: string, additionalParams: Record = {}) => { const baseUrl = globalThis.PREVIEW_URL || 'iframe.html'; const [url, paramsStr] = baseUrl.split('?'); diff --git a/code/core/src/components/components/utils/getStoryHref.ts b/code/core/src/components/components/utils/getStoryHref.ts index 8784c8670667..f2eabf080514 100644 --- a/code/core/src/components/components/utils/getStoryHref.ts +++ b/code/core/src/components/components/utils/getStoryHref.ts @@ -11,7 +11,7 @@ function parseQuery(queryString: string) { return query; } -/** @deprecated Use the api.getStoryHrefs method instead */ +/** @deprecated Use the api.getStoryHrefs method instead (from `storybook/manager-api`) */ export const getStoryHref = ( baseUrl: string, storyId: string, From 1a9134a4f3b74d851679ea56739c5e87595d629d Mon Sep 17 00:00:00 2001 From: Gert Hengeveld Date: Mon, 5 Jan 2026 13:38:43 +0100 Subject: [PATCH 31/33] Add missing guard --- code/core/src/manager-api/modules/shortcuts.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/code/core/src/manager-api/modules/shortcuts.ts b/code/core/src/manager-api/modules/shortcuts.ts index 3b28cfe8168d..0c16b169686d 100644 --- a/code/core/src/manager-api/modules/shortcuts.ts +++ b/code/core/src/manager-api/modules/shortcuts.ts @@ -417,8 +417,10 @@ export const init: ModuleFn = ({ store, fullAPI, provider }) => { // break; // } case 'copyStoryLink': { - const { managerHref } = fullAPI.getStoryHrefs(storyId, { refId }); - copy(managerHref); + if (storyId) { + const { managerHref } = fullAPI.getStoryHrefs(storyId, { refId }); + copy(managerHref); + } break; } default: From 6f128979e2f410a84836c18a059a9fc55db4b1f3 Mon Sep 17 00:00:00 2001 From: Gert Hengeveld Date: Mon, 5 Jan 2026 13:40:50 +0100 Subject: [PATCH 32/33] Fix story parameters --- .../components/preview/Iframe.stories.tsx | 56 ++++++++++--------- 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/code/core/src/manager/components/preview/Iframe.stories.tsx b/code/core/src/manager/components/preview/Iframe.stories.tsx index 2390ca501774..23cd618428bf 100644 --- a/code/core/src/manager/components/preview/Iframe.stories.tsx +++ b/code/core/src/manager/components/preview/Iframe.stories.tsx @@ -30,34 +30,38 @@ const style: CSSProperties = { height: '700px', }; -export const WorkingStory = () => ( -