Skip to content

Commit de5b2ef

Browse files
feat(Page): Add PageHeader component (#12632)
PageHeader can be used to hold a third-party custom header. Fixes #12624 Co-authored-by: Eric Olkowski <70952936+thatblindgeye@users.noreply.github.com>
1 parent 60807bf commit de5b2ef

13 files changed

Lines changed: 131 additions & 17 deletions

File tree

packages/react-core/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@
5454
"tslib": "^2.8.1"
5555
},
5656
"devDependencies": {
57-
"@patternfly/patternfly": "6.6.0-prerelease.20",
57+
"@patternfly/patternfly": "6.6.0-prerelease.39",
5858
"case-anything": "^3.1.2",
5959
"css": "^3.0.0",
6060
"fs-extra": "^11.3.3"

packages/react-core/src/components/Page/Page.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,10 @@ export interface PageProps extends React.HTMLProps<HTMLDivElement> {
2828
* will handle toggling the visibility of the text in individual isDocked components.
2929
*/
3030
isDockTextExpanded?: boolean;
31-
/** The horizontal masthead content (e.g. <Masthead />). When using the docked variant, this content will only render at mobile viewports. */
31+
/** The horizontal masthead content (e.g. <Masthead /> or <PageHeader />). PageHeader is an alternative to Masthead
32+
* and should only be used to wrap custom header content. When using the docked variant, this content will only render at
33+
* mobile viewports.
34+
*/
3235
masthead?: React.ReactNode;
3336
/** @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. */
3437
dockContent?: React.ReactNode;
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import styles from '@patternfly/react-styles/css/components/Page/page';
2+
import { css } from '@patternfly/react-styles';
3+
4+
export interface PageHeaderProps extends React.HTMLProps<HTMLElement> {
5+
/** Content rendered inside the page header. This should be custom header content, rather than the PatternFly Masthead. */
6+
children?: React.ReactNode;
7+
/** Additional classes added to the page header */
8+
className?: string;
9+
/** Sets the base component to render. Defaults to header */
10+
component?: keyof React.JSX.IntrinsicElements;
11+
}
12+
13+
export const PageHeader: React.FunctionComponent<PageHeaderProps> = ({
14+
className,
15+
children,
16+
component = 'header',
17+
...props
18+
}: PageHeaderProps) => {
19+
const Component = component as any;
20+
21+
return (
22+
<Component {...props} className={css(styles.pageHeader, className)}>
23+
{children}
24+
</Component>
25+
);
26+
};
27+
28+
PageHeader.displayName = 'PageHeader';

packages/react-core/src/components/Page/__tests__/Page.test.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { Nav, NavList, NavItem } from '../../Nav';
99
import { SkipToContent } from '../../SkipToContent';
1010
import { PageBreadcrumb } from '../PageBreadcrumb';
1111
import { PageGroup } from '../PageGroup';
12+
import { PageHeader } from '../PageHeader';
1213
import { Masthead } from '../../Masthead';
1314

1415
import styles from '@patternfly/react-styles/css/components/Page/page';
@@ -487,4 +488,16 @@ describe('Page docked variant', () => {
487488
const pageDockMain = screen.getByText('Dock content').closest(`.${styles.pageDockMain}`);
488489
expect(pageDockMain).toBeInTheDocument();
489490
});
491+
492+
test('Renders PageHeader when passed to the masthead prop', () => {
493+
render(
494+
<Page {...props} masthead={<PageHeader>Custom header</PageHeader>}>
495+
<PageSection>Custom content</PageSection>
496+
</Page>
497+
);
498+
499+
const header = screen.getByText('Custom header');
500+
expect(header).toHaveClass(styles.pageHeader);
501+
expect(header.parentElement).toHaveClass(styles.page);
502+
});
490503
});
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { render, screen } from '@testing-library/react';
2+
import styles from '@patternfly/react-styles/css/components/Page/page';
3+
import { PageHeader } from '../PageHeader';
4+
5+
test('Renders children', () => {
6+
render(<PageHeader>Header content</PageHeader>);
7+
expect(screen.getByText('Header content')).toBeVisible();
8+
});
9+
10+
test(`Renders with class ${styles.pageHeader} by default`, () => {
11+
render(<PageHeader>Header content</PageHeader>);
12+
expect(screen.getByText('Header content')).toHaveClass(styles.pageHeader, { exact: true });
13+
});
14+
15+
test('Renders as a div by default', () => {
16+
render(<PageHeader>Header content</PageHeader>);
17+
expect(screen.getByText('Header content').tagName).toBe('HEADER');
18+
});
19+
20+
test('Renders as a custom component when component is passed', () => {
21+
render(<PageHeader component="div">Header content</PageHeader>);
22+
expect(screen.getByText('Header content').tagName).toBe('DIV');
23+
});
24+
25+
test('Renders with custom classes when className is passed', () => {
26+
render(<PageHeader className="custom-class">Header content</PageHeader>);
27+
expect(screen.getByText('Header content')).toHaveClass('custom-class');
28+
});
29+
30+
test('Renders with spread props', () => {
31+
render(<PageHeader id="custom-id">Header content</PageHeader>);
32+
expect(screen.getByText('Header content')).toHaveAttribute('id', 'custom-id');
33+
});

packages/react-core/src/components/Page/examples/Page.md

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,16 @@ id: Page
33
section: components
44
cssPrefix: pf-v6-c-page
55
propComponents:
6-
['Page', 'PageSidebar', 'PageSidebarBody', 'PageSection', 'PageGroup', 'PageBreadcrumb', 'PageToggleButton']
6+
[
7+
'Page',
8+
'PageHeader',
9+
'PageSidebar',
10+
'PageSidebarBody',
11+
'PageSection',
12+
'PageGroup',
13+
'PageBreadcrumb',
14+
'PageToggleButton'
15+
]
716
---
817

918
import { useState, useLayoutEffect, useRef } from 'react';
@@ -16,14 +25,22 @@ import pageSectionWidthLimitMaxWidth from '@patternfly/react-tokens/dist/esm/c_p
1625

1726
A page will typically contain the following components:
1827

19-
- A `<Page>` with a `masthead` prop that often contains a [masthead](/components/masthead) component
28+
- A `<Page>` with a `masthead` prop that often contains a [masthead](/components/masthead) or a `<PageHeader>`
2029

2130
The `<MastheadMain>` component includes the smaller area that typically contains the `<MastheadToggle>` and a `<MastheadLogo>`. `<MastheadContent>` represents the main portion of the masthead, and will typically contain a `<Toolbar>` or other menu-like components, like a `<Dropdown>`.
2231

2332
- Mastheads contain a `<MastheadMain>` component, which includes the `<MastheadToggle>`, a `<MastheadLogo>`, and the page's toolbar (via `<MastheadContent>`.) The `<MastheadToggle>` component contains a `<PageToggleButton>`, and the `<MastheadLogo>` component contains a `<MastheadBrand>`.
2433
- 1 or more `<PageSidebarBody>` components inside `<PageSidebar>` for vertical navigation or other sidebar content
2534
- 1 or more `<PageSection>` components
2635

36+
### Page header
37+
38+
To use a page header instead of passing a [masthead](/components/masthead) directly, pass a `<PageHeader>` to the `masthead` property. `<PageHeader>` should only be used to wrap custom header content.
39+
40+
```ts file="./PageHeaderContent.tsx"
41+
42+
```
43+
2744
### Vertical navigation
2845

2946
To add a vertical sidebar to a `<Page>`, pass a `<PageSidebar>` component into the `sidebar` property. To render navigation in the sidebar, pass a `<PageSidebarBody>` component to `<PageSidebar>`.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { Page, PageHeader, PageSection } from '@patternfly/react-core';
2+
3+
export const PageHeaderContent: React.FunctionComponent = () => {
4+
const pageHeader = <PageHeader>Page header</PageHeader>;
5+
6+
return (
7+
<Page masthead={pageHeader}>
8+
<PageSection aria-labelledby="header-example-section-1">
9+
<h2 id="header-example-section-1">Page header example section 1</h2>
10+
</PageSection>
11+
<PageSection variant="secondary" aria-labelledby="header-example-section-2">
12+
<h2 id="header-example-section-2">Page header example section 2 with secondary variant styling</h2>
13+
</PageSection>
14+
<PageSection aria-labelledby="header-example-section-3">
15+
<h2 id="header-example-section-3">Page header example section 3</h2>
16+
</PageSection>
17+
</Page>
18+
);
19+
};

packages/react-core/src/components/Page/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ export * from './Page';
22
export * from './PageBody';
33
export * from './PageBreadcrumb';
44
export * from './PageGroup';
5+
export * from './PageHeader';
56
export * from './PageSidebar';
67
export * from './PageSidebarBody';
78
export * from './PageSection';

packages/react-docs/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
"test:a11y": "patternfly-a11y --config patternfly-a11y.config"
2424
},
2525
"dependencies": {
26-
"@patternfly/patternfly": "6.6.0-prerelease.20",
26+
"@patternfly/patternfly": "6.6.0-prerelease.39",
2727
"@patternfly/react-charts": "workspace:^",
2828
"@patternfly/react-code-editor": "workspace:^",
2929
"@patternfly/react-core": "workspace:^",

packages/react-icons/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
"@fortawesome/free-brands-svg-icons": "^5.15.4",
3939
"@fortawesome/free-regular-svg-icons": "^5.15.4",
4040
"@fortawesome/free-solid-svg-icons": "^5.15.4",
41-
"@patternfly/patternfly": "6.6.0-prerelease.20",
41+
"@patternfly/patternfly": "6.6.0-prerelease.39",
4242
"@rhds/icons": "^2.2.0",
4343
"fs-extra": "^11.3.3"
4444
},

0 commit comments

Comments
 (0)