From 5446403dedb9a8b82140145fbbe62bcacf480b22 Mon Sep 17 00:00:00 2001 From: Rebecca Alpert Date: Wed, 26 Aug 2026 11:05:36 -0400 Subject: [PATCH 1/4] feat(Page): Add PageHeader component PageHeader can be used to wrap masthead or hold a third-party custom header. Fixes https://github.com/patternfly/patternfly-react/issues/12624 Assisted-by: Cursor --- .../react-core/src/components/Page/Page.tsx | 5 +- .../src/components/Page/PageHeader.tsx | 28 ++++++++++ .../components/Page/__tests__/Page.test.tsx | 30 +++++++++++ .../Page/__tests__/PageHeader.test.tsx | 33 ++++++++++++ .../src/components/Page/examples/Page.md | 21 +++++++- .../Page/examples/PageHeaderContent.tsx | 52 +++++++++++++++++++ .../react-core/src/components/Page/index.ts | 1 + 7 files changed, 167 insertions(+), 3 deletions(-) create mode 100644 packages/react-core/src/components/Page/PageHeader.tsx create mode 100644 packages/react-core/src/components/Page/__tests__/PageHeader.test.tsx create mode 100644 packages/react-core/src/components/Page/examples/PageHeaderContent.tsx diff --git a/packages/react-core/src/components/Page/Page.tsx b/packages/react-core/src/components/Page/Page.tsx index 034c72f9787..7a2deaba5a7 100644 --- a/packages/react-core/src/components/Page/Page.tsx +++ b/packages/react-core/src/components/Page/Page.tsx @@ -28,7 +28,10 @@ export interface PageProps extends React.HTMLProps { * will handle toggling the visibility of the text in individual isDocked components. */ isDockTextExpanded?: boolean; - /** The horizontal masthead content (e.g. ). When using the docked variant, this content will only render at mobile viewports. */ + /** The horizontal masthead content (e.g. or ). PageHeader is an alternative to Masthead + * and can wrap a Masthead or custom header content. When using the docked variant, this content will only render at + * mobile viewports. + */ masthead?: React.ReactNode; /** @beta Content to render in the vertical dock when variant of docked is used. At mobile viewports, this content will be replaced with the content passed to masthead. */ dockContent?: React.ReactNode; diff --git a/packages/react-core/src/components/Page/PageHeader.tsx b/packages/react-core/src/components/Page/PageHeader.tsx new file mode 100644 index 00000000000..f271ac5e05c --- /dev/null +++ b/packages/react-core/src/components/Page/PageHeader.tsx @@ -0,0 +1,28 @@ +import styles from '@patternfly/react-styles/css/components/Page/page'; +import { css } from '@patternfly/react-styles'; + +export interface PageHeaderProps extends React.HTMLProps { + /** Content rendered inside the page header. This can be a Masthead or custom header content. */ + children?: React.ReactNode; + /** Additional classes added to the page header */ + className?: string; + /** Sets the base component to render. Defaults to div */ + component?: keyof React.JSX.IntrinsicElements; +} + +export const PageHeader: React.FunctionComponent = ({ + className, + children, + component = 'div', + ...props +}: PageHeaderProps) => { + const Component = component as any; + + return ( + + {children} + + ); +}; + +PageHeader.displayName = 'PageHeader'; diff --git a/packages/react-core/src/components/Page/__tests__/Page.test.tsx b/packages/react-core/src/components/Page/__tests__/Page.test.tsx index c97e699685c..98fdd39762f 100644 --- a/packages/react-core/src/components/Page/__tests__/Page.test.tsx +++ b/packages/react-core/src/components/Page/__tests__/Page.test.tsx @@ -9,6 +9,7 @@ import { Nav, NavList, NavItem } from '../../Nav'; import { SkipToContent } from '../../SkipToContent'; import { PageBreadcrumb } from '../PageBreadcrumb'; import { PageGroup } from '../PageGroup'; +import { PageHeader } from '../PageHeader'; import { Masthead } from '../../Masthead'; import styles from '@patternfly/react-styles/css/components/Page/page'; @@ -487,4 +488,33 @@ describe('Page docked variant', () => { const pageDockMain = screen.getByText('Dock content').closest(`.${styles.pageDockMain}`); expect(pageDockMain).toBeInTheDocument(); }); + + test('Renders PageHeader when passed to the masthead prop', () => { + render( + Custom header}> + Custom content + + ); + + const header = screen.getByText('Custom header'); + expect(header).toHaveClass(styles.pageHeader); + expect(header.parentElement).toHaveClass(styles.page); + }); + + test('Renders Masthead inside PageHeader when passed to the masthead prop', () => { + render( + + Logo + + } + > + Custom content + + ); + + expect(screen.getByText('Logo').closest(`.${styles.pageHeader}`)).toBeInTheDocument(); + }); }); diff --git a/packages/react-core/src/components/Page/__tests__/PageHeader.test.tsx b/packages/react-core/src/components/Page/__tests__/PageHeader.test.tsx new file mode 100644 index 00000000000..f4bcfa1490d --- /dev/null +++ b/packages/react-core/src/components/Page/__tests__/PageHeader.test.tsx @@ -0,0 +1,33 @@ +import { render, screen } from '@testing-library/react'; +import styles from '@patternfly/react-styles/css/components/Page/page'; +import { PageHeader } from '../PageHeader'; + +test('Renders children', () => { + render(Header content); + expect(screen.getByText('Header content')).toBeVisible(); +}); + +test(`Renders with class ${styles.pageHeader} by default`, () => { + render(Header content); + expect(screen.getByText('Header content')).toHaveClass(styles.pageHeader); +}); + +test('Renders as a div by default', () => { + render(Header content); + expect(screen.getByText('Header content').tagName).toBe('DIV'); +}); + +test('Renders as a custom component when component is passed', () => { + render(Header content); + expect(screen.getByText('Header content').tagName).toBe('HEADER'); +}); + +test('Renders with custom classes when className is passed', () => { + render(Header content); + expect(screen.getByText('Header content')).toHaveClass('custom-class'); +}); + +test('Renders with spread props', () => { + render(Header content); + expect(screen.getByText('Header content')).toHaveAttribute('id', 'custom-id'); +}); diff --git a/packages/react-core/src/components/Page/examples/Page.md b/packages/react-core/src/components/Page/examples/Page.md index 5f4d491e7a2..ef0b6182428 100644 --- a/packages/react-core/src/components/Page/examples/Page.md +++ b/packages/react-core/src/components/Page/examples/Page.md @@ -3,7 +3,16 @@ id: Page section: components cssPrefix: pf-v6-c-page propComponents: - ['Page', 'PageSidebar', 'PageSidebarBody', 'PageSection', 'PageGroup', 'PageBreadcrumb', 'PageToggleButton'] + [ + 'Page', + 'PageHeader', + 'PageSidebar', + 'PageSidebarBody', + 'PageSection', + 'PageGroup', + 'PageBreadcrumb', + 'PageToggleButton' + ] --- import { useState, useLayoutEffect, useRef } from 'react'; @@ -16,7 +25,7 @@ import pageSectionWidthLimitMaxWidth from '@patternfly/react-tokens/dist/esm/c_p A page will typically contain the following components: -- A `` with a `masthead` prop that often contains a [masthead](/components/masthead) component +- A `` with a `masthead` prop that often contains a [masthead](/components/masthead) or a `` The `` component includes the smaller area that typically contains the `` and a ``. `` represents the main portion of the masthead, and will typically contain a `` or other menu-like components, like a ``. @@ -24,6 +33,14 @@ The `` component includes the smaller area that typically contains - 1 or more `` components inside `` for vertical navigation or other sidebar content - 1 or more `` components +### Page header + +To use a page header instead of passing a [masthead](/components/masthead) directly, pass a `` to the `masthead` property. `` can wrap a `` or custom header content. + +```ts file="./PageHeaderContent.tsx" + +``` + ### Vertical navigation To add a vertical sidebar to a ``, pass a `` component into the `sidebar` property. To render navigation in the sidebar, pass a `` component to ``. diff --git a/packages/react-core/src/components/Page/examples/PageHeaderContent.tsx b/packages/react-core/src/components/Page/examples/PageHeaderContent.tsx new file mode 100644 index 00000000000..6e1b88ceae3 --- /dev/null +++ b/packages/react-core/src/components/Page/examples/PageHeaderContent.tsx @@ -0,0 +1,52 @@ +import { + Page, + PageHeader, + Masthead, + MastheadMain, + MastheadBrand, + MastheadLogo, + MastheadContent, + PageSection, + Toolbar, + ToolbarContent, + ToolbarItem +} from '@patternfly/react-core'; + +export const PageHeaderContent: React.FunctionComponent = () => { + const headerToolbar = ( + + + header-tools + + + ); + + const pageHeader = ( + + + + + + Logo + + + + {headerToolbar} + + + ); + + return ( + + +

Page header example section 1

+
+ +

Page header example section 2 with secondary variant styling

+
+ +

Page header example section 3

+
+
+ ); +}; diff --git a/packages/react-core/src/components/Page/index.ts b/packages/react-core/src/components/Page/index.ts index 5afe6f03b36..55f94c2bffe 100644 --- a/packages/react-core/src/components/Page/index.ts +++ b/packages/react-core/src/components/Page/index.ts @@ -2,6 +2,7 @@ export * from './Page'; export * from './PageBody'; export * from './PageBreadcrumb'; export * from './PageGroup'; +export * from './PageHeader'; export * from './PageSidebar'; export * from './PageSidebarBody'; export * from './PageSection'; From 23cd908470516c8c11a21bd1ec2f47af8f0f35fe Mon Sep 17 00:00:00 2001 From: Rebecca Alpert Date: Thu, 27 Aug 2026 14:07:10 -0400 Subject: [PATCH 2/4] Remove masthead wrapping to reflect Core --- .../react-core/src/components/Page/Page.tsx | 2 +- .../src/components/Page/PageHeader.tsx | 2 +- .../src/components/Page/__tests__/Page.test.tsx | 17 ----------------- .../src/components/Page/examples/Page.md | 2 +- 4 files changed, 3 insertions(+), 20 deletions(-) diff --git a/packages/react-core/src/components/Page/Page.tsx b/packages/react-core/src/components/Page/Page.tsx index 7a2deaba5a7..595ac6a5c07 100644 --- a/packages/react-core/src/components/Page/Page.tsx +++ b/packages/react-core/src/components/Page/Page.tsx @@ -29,7 +29,7 @@ export interface PageProps extends React.HTMLProps { */ isDockTextExpanded?: boolean; /** The horizontal masthead content (e.g. or ). PageHeader is an alternative to Masthead - * and can wrap a Masthead or custom header content. When using the docked variant, this content will only render at + * and should only be used to wrap custom header content. When using the docked variant, this content will only render at * mobile viewports. */ masthead?: React.ReactNode; diff --git a/packages/react-core/src/components/Page/PageHeader.tsx b/packages/react-core/src/components/Page/PageHeader.tsx index f271ac5e05c..50eeefe08b0 100644 --- a/packages/react-core/src/components/Page/PageHeader.tsx +++ b/packages/react-core/src/components/Page/PageHeader.tsx @@ -2,7 +2,7 @@ import styles from '@patternfly/react-styles/css/components/Page/page'; import { css } from '@patternfly/react-styles'; export interface PageHeaderProps extends React.HTMLProps { - /** Content rendered inside the page header. This can be a Masthead or custom header content. */ + /** Content rendered inside the page header. This should be custom header content, rather than the PatternFly Masthead. */ children?: React.ReactNode; /** Additional classes added to the page header */ className?: string; diff --git a/packages/react-core/src/components/Page/__tests__/Page.test.tsx b/packages/react-core/src/components/Page/__tests__/Page.test.tsx index 98fdd39762f..bb76d3408dc 100644 --- a/packages/react-core/src/components/Page/__tests__/Page.test.tsx +++ b/packages/react-core/src/components/Page/__tests__/Page.test.tsx @@ -500,21 +500,4 @@ describe('Page docked variant', () => { expect(header).toHaveClass(styles.pageHeader); expect(header.parentElement).toHaveClass(styles.page); }); - - test('Renders Masthead inside PageHeader when passed to the masthead prop', () => { - render( - - Logo - - } - > - Custom content -
- ); - - expect(screen.getByText('Logo').closest(`.${styles.pageHeader}`)).toBeInTheDocument(); - }); }); diff --git a/packages/react-core/src/components/Page/examples/Page.md b/packages/react-core/src/components/Page/examples/Page.md index ef0b6182428..943ea3acd73 100644 --- a/packages/react-core/src/components/Page/examples/Page.md +++ b/packages/react-core/src/components/Page/examples/Page.md @@ -35,7 +35,7 @@ The `` component includes the smaller area that typically contains ### Page header -To use a page header instead of passing a [masthead](/components/masthead) directly, pass a `` to the `masthead` property. `` can wrap a `` or custom header content. +To use a page header instead of passing a [masthead](/components/masthead) directly, pass a `` to the `masthead` property. `` should only be used to wrap custom header content. ```ts file="./PageHeaderContent.tsx" From a399a4658d52641c393297b99ec02eda67377d20 Mon Sep 17 00:00:00 2001 From: Rebecca Alpert Date: Thu, 27 Aug 2026 14:07:10 -0400 Subject: [PATCH 3/4] Update versions --- packages/react-core/package.json | 2 +- packages/react-docs/package.json | 2 +- packages/react-icons/package.json | 2 +- packages/react-styles/package.json | 2 +- packages/react-tokens/package.json | 2 +- yarn.lock | 18 +++++++++--------- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/react-core/package.json b/packages/react-core/package.json index 656a87c6ec3..cd0a8749ae0 100644 --- a/packages/react-core/package.json +++ b/packages/react-core/package.json @@ -54,7 +54,7 @@ "tslib": "^2.8.1" }, "devDependencies": { - "@patternfly/patternfly": "6.6.0-prerelease.20", + "@patternfly/patternfly": "6.6.0-prerelease.39", "case-anything": "^3.1.2", "css": "^3.0.0", "fs-extra": "^11.3.3" diff --git a/packages/react-docs/package.json b/packages/react-docs/package.json index 5523c77d741..7f367583d8f 100644 --- a/packages/react-docs/package.json +++ b/packages/react-docs/package.json @@ -23,7 +23,7 @@ "test:a11y": "patternfly-a11y --config patternfly-a11y.config" }, "dependencies": { - "@patternfly/patternfly": "6.6.0-prerelease.20", + "@patternfly/patternfly": "6.6.0-prerelease.39", "@patternfly/react-charts": "workspace:^", "@patternfly/react-code-editor": "workspace:^", "@patternfly/react-core": "workspace:^", diff --git a/packages/react-icons/package.json b/packages/react-icons/package.json index 297ec4b959c..018c9991ee5 100644 --- a/packages/react-icons/package.json +++ b/packages/react-icons/package.json @@ -38,7 +38,7 @@ "@fortawesome/free-brands-svg-icons": "^5.15.4", "@fortawesome/free-regular-svg-icons": "^5.15.4", "@fortawesome/free-solid-svg-icons": "^5.15.4", - "@patternfly/patternfly": "6.6.0-prerelease.20", + "@patternfly/patternfly": "6.6.0-prerelease.39", "@rhds/icons": "^2.2.0", "fs-extra": "^11.3.3" }, diff --git a/packages/react-styles/package.json b/packages/react-styles/package.json index 8dd1cf585e2..7c2b0a5fb71 100644 --- a/packages/react-styles/package.json +++ b/packages/react-styles/package.json @@ -19,7 +19,7 @@ "clean": "rimraf dist css" }, "devDependencies": { - "@patternfly/patternfly": "6.6.0-prerelease.20", + "@patternfly/patternfly": "6.6.0-prerelease.39", "change-case": "^5.4.4", "fs-extra": "^11.3.3" }, diff --git a/packages/react-tokens/package.json b/packages/react-tokens/package.json index e1794978b7a..9d873fe7e60 100644 --- a/packages/react-tokens/package.json +++ b/packages/react-tokens/package.json @@ -30,7 +30,7 @@ }, "devDependencies": { "@adobe/css-tools": "^4.4.4", - "@patternfly/patternfly": "6.6.0-prerelease.20", + "@patternfly/patternfly": "6.6.0-prerelease.39", "fs-extra": "^11.3.3" } } diff --git a/yarn.lock b/yarn.lock index 224de12951b..35bef1a5ca6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5070,10 +5070,10 @@ __metadata: languageName: node linkType: hard -"@patternfly/patternfly@npm:6.6.0-prerelease.20": - version: 6.6.0-prerelease.20 - resolution: "@patternfly/patternfly@npm:6.6.0-prerelease.20" - checksum: 10c0/6bcc0015bb994ff29b6bdb66b7b83e8e91d8ea94278264c985bf40326b084c6c44606e4224f348e646d0e20b9545c4ffa4de6a1d33f697bf5f6f76fa74202951 +"@patternfly/patternfly@npm:6.6.0-prerelease.39": + version: 6.6.0-prerelease.39 + resolution: "@patternfly/patternfly@npm:6.6.0-prerelease.39" + checksum: 10c0/e3ad085429507c23912bf50b84a178c9b2f05fe58dbbecce9eb76be9f70eb4b116e81a611f5eaa8dc388d4081ae5f36ab09725268041e5557a85c20cb07200c3 languageName: node linkType: hard @@ -5171,7 +5171,7 @@ __metadata: version: 0.0.0-use.local resolution: "@patternfly/react-core@workspace:packages/react-core" dependencies: - "@patternfly/patternfly": "npm:6.6.0-prerelease.20" + "@patternfly/patternfly": "npm:6.6.0-prerelease.39" "@patternfly/react-icons": "workspace:^" "@patternfly/react-styles": "workspace:^" "@patternfly/react-tokens": "workspace:^" @@ -5192,7 +5192,7 @@ __metadata: resolution: "@patternfly/react-docs@workspace:packages/react-docs" dependencies: "@patternfly/documentation-framework": "npm:^6.40.0" - "@patternfly/patternfly": "npm:6.6.0-prerelease.20" + "@patternfly/patternfly": "npm:6.6.0-prerelease.39" "@patternfly/patternfly-a11y": "npm:5.2.1" "@patternfly/react-charts": "workspace:^" "@patternfly/react-code-editor": "workspace:^" @@ -5232,7 +5232,7 @@ __metadata: "@fortawesome/free-brands-svg-icons": "npm:^5.15.4" "@fortawesome/free-regular-svg-icons": "npm:^5.15.4" "@fortawesome/free-solid-svg-icons": "npm:^5.15.4" - "@patternfly/patternfly": "npm:6.6.0-prerelease.20" + "@patternfly/patternfly": "npm:6.6.0-prerelease.39" "@rhds/icons": "npm:^2.2.0" fs-extra: "npm:^11.3.3" tslib: "npm:^2.8.1" @@ -5319,7 +5319,7 @@ __metadata: version: 0.0.0-use.local resolution: "@patternfly/react-styles@workspace:packages/react-styles" dependencies: - "@patternfly/patternfly": "npm:6.6.0-prerelease.20" + "@patternfly/patternfly": "npm:6.6.0-prerelease.39" change-case: "npm:^5.4.4" fs-extra: "npm:^11.3.3" languageName: unknown @@ -5361,7 +5361,7 @@ __metadata: resolution: "@patternfly/react-tokens@workspace:packages/react-tokens" dependencies: "@adobe/css-tools": "npm:^4.4.4" - "@patternfly/patternfly": "npm:6.6.0-prerelease.20" + "@patternfly/patternfly": "npm:6.6.0-prerelease.39" fs-extra: "npm:^11.3.3" languageName: unknown linkType: soft From 10b6577dd776ba5d341dda811b2f55ff10a83e7c Mon Sep 17 00:00:00 2001 From: Rebecca Alpert Date: Mon, 31 Aug 2026 13:15:03 -0400 Subject: [PATCH 4/4] Address feedback Co-authored-by: Eric Olkowski <70952936+thatblindgeye@users.noreply.github.com> --- .../src/components/Page/PageHeader.tsx | 4 +- .../Page/__tests__/PageHeader.test.tsx | 8 +-- .../Page/examples/PageHeaderContent.tsx | 49 +++---------------- 3 files changed, 14 insertions(+), 47 deletions(-) diff --git a/packages/react-core/src/components/Page/PageHeader.tsx b/packages/react-core/src/components/Page/PageHeader.tsx index 50eeefe08b0..e2a5df417c1 100644 --- a/packages/react-core/src/components/Page/PageHeader.tsx +++ b/packages/react-core/src/components/Page/PageHeader.tsx @@ -6,14 +6,14 @@ export interface PageHeaderProps extends React.HTMLProps { children?: React.ReactNode; /** Additional classes added to the page header */ className?: string; - /** Sets the base component to render. Defaults to div */ + /** Sets the base component to render. Defaults to header */ component?: keyof React.JSX.IntrinsicElements; } export const PageHeader: React.FunctionComponent = ({ className, children, - component = 'div', + component = 'header', ...props }: PageHeaderProps) => { const Component = component as any; diff --git a/packages/react-core/src/components/Page/__tests__/PageHeader.test.tsx b/packages/react-core/src/components/Page/__tests__/PageHeader.test.tsx index f4bcfa1490d..80dd18483c3 100644 --- a/packages/react-core/src/components/Page/__tests__/PageHeader.test.tsx +++ b/packages/react-core/src/components/Page/__tests__/PageHeader.test.tsx @@ -9,17 +9,17 @@ test('Renders children', () => { test(`Renders with class ${styles.pageHeader} by default`, () => { render(Header content); - expect(screen.getByText('Header content')).toHaveClass(styles.pageHeader); + expect(screen.getByText('Header content')).toHaveClass(styles.pageHeader, { exact: true }); }); test('Renders as a div by default', () => { render(Header content); - expect(screen.getByText('Header content').tagName).toBe('DIV'); + expect(screen.getByText('Header content').tagName).toBe('HEADER'); }); test('Renders as a custom component when component is passed', () => { - render(Header content); - expect(screen.getByText('Header content').tagName).toBe('HEADER'); + render(Header content); + expect(screen.getByText('Header content').tagName).toBe('DIV'); }); test('Renders with custom classes when className is passed', () => { diff --git a/packages/react-core/src/components/Page/examples/PageHeaderContent.tsx b/packages/react-core/src/components/Page/examples/PageHeaderContent.tsx index 6e1b88ceae3..27b0be187dd 100644 --- a/packages/react-core/src/components/Page/examples/PageHeaderContent.tsx +++ b/packages/react-core/src/components/Page/examples/PageHeaderContent.tsx @@ -1,51 +1,18 @@ -import { - Page, - PageHeader, - Masthead, - MastheadMain, - MastheadBrand, - MastheadLogo, - MastheadContent, - PageSection, - Toolbar, - ToolbarContent, - ToolbarItem -} from '@patternfly/react-core'; +import { Page, PageHeader, PageSection } from '@patternfly/react-core'; export const PageHeaderContent: React.FunctionComponent = () => { - const headerToolbar = ( - - - header-tools - - - ); - - const pageHeader = ( - - - - - - Logo - - - - {headerToolbar} - - - ); + const pageHeader = Page header; return ( - -

Page header example section 1

+ +

Page header example section 1

- -

Page header example section 2 with secondary variant styling

+ +

Page header example section 2 with secondary variant styling

- -

Page header example section 3

+ +

Page header example section 3

);