diff --git a/workspaces/theme/.changeset/fix-sidebar-page-viewport-gap.md b/workspaces/theme/.changeset/fix-sidebar-page-viewport-gap.md new file mode 100644 index 00000000000..157019a224c --- /dev/null +++ b/workspaces/theme/.changeset/fix-sidebar-page-viewport-gap.md @@ -0,0 +1,5 @@ +--- +'@red-hat-developer-hub/backstage-plugin-theme': patch +--- + +Fixed empty space and background mismatch on short NFS pages: BackstageSidebarPage fills the viewport and becomes a column flex container so BUI Containers can grow, and content wells use mainSectionBackgroundColor to match the article/content area diff --git a/workspaces/theme/plugins/theme/src/utils/createComponents.test.ts b/workspaces/theme/plugins/theme/src/utils/createComponents.test.ts index 950dc0f1306..0b9f98de669 100644 --- a/workspaces/theme/plugins/theme/src/utils/createComponents.test.ts +++ b/workspaces/theme/plugins/theme/src/utils/createComponents.test.ts @@ -15,6 +15,7 @@ */ import type { ThemeConfig } from '../types'; +import { customDarkTheme } from '../darkTheme'; import { createComponents, type Components } from './createComponents'; interface TestCase { @@ -85,4 +86,69 @@ describe('createComponents', () => { expect(actual).toEqual(testCase.expected); }); }); + + it('sets BackstageSidebarPage minHeight to fill the viewport', () => { + const actual = createComponents({}); + expect(actual.BackstageSidebarPage?.styleOverrides?.root).toEqual( + expect.objectContaining({ + minHeight: '100vh', + display: 'flex', + flexDirection: 'column', + }), + ); + }); + + it('stretches main with mainSectionBackgroundColor inside the page inset', () => { + const actual = createComponents({ palette: customDarkTheme() }); + const root = actual.BackstageSidebarPage?.styleOverrides?.root as + | Record + | undefined; + const desktop = root?.['@media (min-width: 600px)'] as + | Record + | undefined; + expect( + desktop?.["& > [class*='MuiLinearProgress-root'], & > main"], + ).toEqual( + expect.objectContaining({ + backgroundColor: '#292929', + minHeight: 'calc(100vh - 2 * 1.5rem)', + maxHeight: 'calc(100vh - 2 * 1.5rem)', + }), + ); + }); + + it('paints BUI content Containers with mainSectionBackgroundColor', () => { + const actual = createComponents({ palette: customDarkTheme() }); + const root = actual.BackstageSidebarPage?.styleOverrides?.root as + | Record + | undefined; + const desktop = root?.['@media (min-width: 600px)'] as + | Record + | undefined; + expect( + desktop?.["& > [class*='bui-Container']:not([class*='bui-Header'])"], + ).toEqual( + expect.objectContaining({ + backgroundColor: '#292929', + }), + ); + }); + + it('grows BackstageContent article to fill the flex column', () => { + const actual = createComponents({ palette: customDarkTheme() }); + const root = actual.BackstageSidebarPage?.styleOverrides?.root as + | Record + | undefined; + const desktop = root?.['@media (min-width: 600px)'] as + | Record + | undefined; + expect( + desktop?.['& > article, & > [class*="BackstageContent-root"]'], + ).toEqual( + expect.objectContaining({ + flex: 1, + backgroundColor: '#292929', + }), + ); + }); }); diff --git a/workspaces/theme/plugins/theme/src/utils/createComponents.ts b/workspaces/theme/plugins/theme/src/utils/createComponents.ts index e94c90787c3..6f313f41d46 100644 --- a/workspaces/theme/plugins/theme/src/utils/createComponents.ts +++ b/workspaces/theme/plugins/theme/src/utils/createComponents.ts @@ -754,6 +754,15 @@ export const createComponents = (themeConfig: ThemeConfig): Components => { components.BackstageSidebarPage = { styleOverrides: { root: { + // Fill the viewport so short pages don't leave a gap below the shell. + // App root wrappers (e.g. ApplicationDrawer) can collapse to content + // height; without a min-height here the page-inset background stops + // early and body/html shows through (RHDHBUGS-3498). + minHeight: '100vh', + // Let BUI Container's flex: 1 grow into the remaining viewport below + // PluginHeader / Header slots (those slots set flex: none). + display: 'flex', + flexDirection: 'column', // Controls the page inset as in PF6 -- only in desktop view '@media (min-width: 600px)': { backgroundColor: @@ -773,9 +782,25 @@ export const createComponents = (themeConfig: ThemeConfig): Components => { clipPath: 'rect(0 100% 100% 0 round 1rem)', // Emulate the PatternFly 6 page inset using a margin margin: general.pageInset, + // Fill the inset well so short pages use mainSectionBackgroundColor + // (#292929) instead of leaving a pageInset (#151515) band below content. + backgroundColor: general.mainSectionBackgroundColor, + minHeight: `calc(100vh - 2 * ${general.pageInset})`, // Prevent overflow in the main container due to the margin maxHeight: `calc(100vh - 2 * ${general.pageInset})`, }, + // NFS / BUI pages use Container instead of
. Match the content + // well color (same token as BackstageContent) and rely on flex: 1 + // from BUI rather than 100vh so PluginHeader siblings are not overflowed. + "& > [class*='bui-Container']:not([class*='bui-Header'])": { + backgroundColor: general.mainSectionBackgroundColor, + }, + // Settings and other pages render BackstageContent as
. + // Grow it to fill the flex column so pageInset doesn't show as a band. + '& > article, & > [class*="BackstageContent-root"]': { + flex: 1, + backgroundColor: general.mainSectionBackgroundColor, + }, // Prevent TechDocs double scrollbar: the page-inset max-height puts //
's scrollbar at the same position as the ToC sidebar scrollbar. // Letting
expand moves the scroll to the parent root instead.