diff --git a/code/core/src/manager-api/modules/layout.ts b/code/core/src/manager-api/modules/layout.ts index f8d053773926..23a444fa8837 100644 --- a/code/core/src/manager-api/modules/layout.ts +++ b/code/core/src/manager-api/modules/layout.ts @@ -2,6 +2,7 @@ import { SET_CONFIG } from 'storybook/internal/core-events'; import type { API_Layout, API_LayoutCustomisations, + API_LayoutOptions, API_PanelPositions, API_UI, } from 'storybook/internal/types'; @@ -192,6 +193,38 @@ const getRecentVisibleSizes = (layoutState: API_Layout) => { }; }; +const applyLayoutOptions = ( + layoutState: API_Layout, + options: API_LayoutOptions | undefined, + singleStory: boolean +) => { + const { showPanel, showSidebar, ...layoutOptions } = options ?? {}; + const layoutKeys = Object.keys(layoutState) as (keyof API_Layout)[]; + const nextLayoutState = toMerged(layoutState, pick(layoutOptions, layoutKeys)) as API_Layout; + + if (showSidebar === false) { + nextLayoutState.recentVisibleSizes = getRecentVisibleSizes(nextLayoutState); + nextLayoutState.navSize = 0; + } else if (showSidebar === true && !singleStory) { + nextLayoutState.navSize = nextLayoutState.recentVisibleSizes.navSize; + } + + if (showPanel === false) { + nextLayoutState.recentVisibleSizes = getRecentVisibleSizes(nextLayoutState); + nextLayoutState.bottomPanelHeight = 0; + nextLayoutState.rightPanelWidth = 0; + } else if (showPanel === true) { + nextLayoutState.bottomPanelHeight = nextLayoutState.recentVisibleSizes.bottomPanelHeight; + nextLayoutState.rightPanelWidth = nextLayoutState.recentVisibleSizes.rightPanelWidth; + } + + if (singleStory) { + nextLayoutState.navSize = 0; + } + + return nextLayoutState; +}; + export const init: ModuleFn = ({ store, provider, singleStory }) => { const api = { toggleFullscreen(nextState?: boolean) { @@ -444,13 +477,14 @@ export const init: ModuleFn = ({ store, provider, singleStory return { ...defaultLayoutState, - layout: { - ...toMerged( - defaultLayoutState.layout, - pick(options, Object.keys(defaultLayoutState.layout)) - ), - ...(singleStory && { navSize: 0 }), - }, + layout: applyLayoutOptions( + defaultLayoutState.layout, + { + ...options.layout, + ...pick(options, Object.keys(defaultLayoutState.layout)), + }, + !!singleStory + ), layoutCustomisations: { ...defaultLayoutState.layoutCustomisations, ...(layoutCustomisations ?? {}), @@ -513,12 +547,14 @@ export const init: ModuleFn = ({ store, provider, singleStory return; } - const updatedLayout = { - ...layout, - ...(options.layout || {}), - ...pick(options, Object.keys(layout)), - ...(singleStory && { navSize: 0 }), - }; + const updatedLayout = applyLayoutOptions( + layout, + { + ...options.layout, + ...pick(options, Object.keys(layout)), + }, + !!singleStory + ); const updatedUi = { ...ui, diff --git a/code/core/src/manager-api/tests/layout.test.ts b/code/core/src/manager-api/tests/layout.test.ts index 30fd16e0e4ad..73b2c5526c78 100644 --- a/code/core/src/manager-api/tests/layout.test.ts +++ b/code/core/src/manager-api/tests/layout.test.ts @@ -486,6 +486,44 @@ describe('layout API', () => { expect(getLastSetStateArgs()[0].selectedPanel).toEqual(panelName); }); + + it('should hide the panel when layout.showPanel is false', () => { + layoutApi.setSizes({ + bottomPanelHeight: 200, + rightPanelWidth: 250, + }); + + layoutApi.setOptions({ layout: { showPanel: false } }); + + expect(currentState.layout.bottomPanelHeight).toBe(0); + expect(currentState.layout.rightPanelWidth).toBe(0); + expect(currentState.layout.recentVisibleSizes.bottomPanelHeight).toBe(200); + expect(currentState.layout.recentVisibleSizes.rightPanelWidth).toBe(250); + + layoutApi.togglePanel(true); + + expect(currentState.layout.bottomPanelHeight).toBe(200); + expect(currentState.layout.rightPanelWidth).toBe(250); + }); + }); + + describe('getInitialOptions', () => { + it('should apply layout.showPanel from the initial config', () => { + (provider.getConfig as Mock).mockReturnValue({ + layout: { showPanel: false }, + }); + + const { state } = initLayout({ + store, + provider, + singleStory: false, + } as unknown as ModuleArgs); + + expect(state.layout.bottomPanelHeight).toBe(0); + expect(state.layout.rightPanelWidth).toBe(0); + expect(state.layout.recentVisibleSizes.bottomPanelHeight).toBe(300); + expect(state.layout.recentVisibleSizes.rightPanelWidth).toBe(400); + }); }); describe('state getters', () => { diff --git a/code/core/src/types/modules/addons.ts b/code/core/src/types/modules/addons.ts index 31af4e84b326..ef83aae3ad62 100644 --- a/code/core/src/types/modules/addons.ts +++ b/code/core/src/types/modules/addons.ts @@ -2,7 +2,7 @@ import type { FC, PropsWithChildren, ReactElement, ReactNode } from 'react'; import type { RenderData as RouterData } from '../../router/types.ts'; import type { ThemeVars } from '../../theming/types.ts'; -import type { API_LayoutCustomisations, API_SidebarOptions } from './api.ts'; +import type { API_LayoutCustomisations, API_LayoutOptions, API_SidebarOptions } from './api.ts'; import type { API_HashEntry, API_StoryEntry } from './api-stories.ts'; import type { Args, @@ -479,6 +479,7 @@ export interface Addon_ToolbarConfig { } export interface Addon_Config { theme?: ThemeVars; + layout?: API_LayoutOptions; layoutCustomisations?: { showPanel?: API_LayoutCustomisations['showPanel']; showSidebar?: API_LayoutCustomisations['showSidebar']; diff --git a/code/core/src/types/modules/api.ts b/code/core/src/types/modules/api.ts index 0e94d96699b6..75e0916502d9 100644 --- a/code/core/src/types/modules/api.ts +++ b/code/core/src/types/modules/api.ts @@ -95,6 +95,11 @@ export interface API_Layout { showToolbar: boolean; } +export interface API_LayoutOptions extends Partial { + showPanel?: boolean; + showSidebar?: boolean; +} + export interface API_LayoutCustomisations { showPanel?: (state: State, defaultValue: boolean) => boolean | undefined; showSidebar?: (state: State, defaultValue: boolean) => boolean | undefined;