Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/react-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
"tslib": "^2.8.1"
},
"devDependencies": {
"@patternfly/patternfly": "6.6.0-prerelease.39",
"@patternfly/patternfly": "6.6.0-prerelease.40",
"case-anything": "^3.1.2",
"css": "^3.0.0",
"fs-extra": "^11.3.3"
Expand Down
11 changes: 10 additions & 1 deletion packages/react-core/src/components/Page/Page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,10 @@ export interface PageProps extends React.HTMLProps<HTMLDivElement> {
breadcrumbProps?: PageBreadcrumbProps;
/** Enables children to fill the available vertical space. Child page sections or groups that should fill should be passed the isFilled property. */
isContentFilled?: boolean;
/** Flag indicating the page should render without the content area background and overflow scroll. */
isPlain?: boolean;
/** Content rendered inside the page footer */
footer?: React.ReactNode;
}

export interface PageState {
Expand All @@ -141,7 +145,8 @@ class Page extends Component<PageProps, PageState> {
mainComponent: 'main',
getBreakpoint,
getVerticalBreakpoint,
mainRef: undefined
mainRef: undefined,
isPlain: false
};
mainRef = this.props?.mainRef ? this.props.mainRef : createRef<HTMLDivElement>();
pageRef = createRef<HTMLDivElement>();
Expand Down Expand Up @@ -284,6 +289,8 @@ class Page extends Component<PageProps, PageState> {
isContentFilled,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
mainRef,
isPlain,
footer,
...rest
} = this.props;
const { mobileView, mobileIsSidebarOpen, desktopIsSidebarOpen, width, height } = this.state;
Expand Down Expand Up @@ -367,6 +374,7 @@ class Page extends Component<PageProps, PageState> {
width !== null && `pf-m-breakpoint-${getBreakpoint(width)}`,
height !== null && `pf-m-height-breakpoint-${getVerticalBreakpoint(height)}`,
sidebar === null && styles.modifiers.noSidebar,
isPlain && styles.modifiers.plain,
className
)}
>
Expand Down Expand Up @@ -397,6 +405,7 @@ class Page extends Component<PageProps, PageState> {
</div>
)}
{!notificationDrawer && main}
{footer && footer}
</div>
</PageContextProvider>
);
Expand Down
28 changes: 28 additions & 0 deletions packages/react-core/src/components/Page/PageFooter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import styles from '@patternfly/react-styles/css/components/Page/page';
import { css } from '@patternfly/react-styles';

export interface PageFooterProps extends React.HTMLProps<HTMLElement> {
/** Content rendered inside the footer */
children?: React.ReactNode;
/** Additional classes added to the footer */
className?: string;
/** Sets the base component to render. Defaults to footer */
component?: keyof React.JSX.IntrinsicElements;
}

export const PageFooter: React.FunctionComponent<PageFooterProps> = ({
className,
children,
component = 'footer',
...props
}: PageFooterProps) => {
const Component = component as any;

return (
<Component {...props} className={css(styles.pageFooter, className)}>
{children}
</Component>
);
};

PageFooter.displayName = 'PageFooter';
46 changes: 46 additions & 0 deletions packages/react-core/src/components/Page/__tests__/Page.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { PageHeader } from '../PageHeader';
import { Masthead } from '../../Masthead';

import styles from '@patternfly/react-styles/css/components/Page/page';
import { PageFooter } from '../PageFooter';

const props = {
'aria-label': 'Page layout',
Expand Down Expand Up @@ -520,4 +521,49 @@ describe('Page docked variant', () => {
expect(header).toHaveClass(styles.pageHeader);
expect(header.parentElement).toHaveClass(styles.page);
});

test('Renders PageFooter when passed to the footer prop', () => {
render(
<Page {...props} footer={<PageFooter>Custom footer</PageFooter>}>
<PageSection>Custom content</PageSection>
</Page>
);

const footer = screen.getByRole('contentinfo');
expect(footer).toHaveClass(styles.pageFooter);
expect(footer.parentElement).toHaveClass(styles.page);
});

test(`Renders with ${styles.modifiers.plain} when isPlain is true`, () => {
render(
<Page {...props} isPlain data-testid="page">
<PageSection>Custom content</PageSection>
</Page>
);

const page = screen.getByTestId('page');
expect(page).toHaveClass(styles.modifiers.plain);
});

test(`Does not render with ${styles.modifiers.plain} when isPlain is false`, () => {
render(
<Page {...props} isPlain={false} data-testid="page">
<PageSection>Custom content</PageSection>
</Page>
);

const page = screen.getByTestId('page');
expect(page).not.toHaveClass(styles.modifiers.plain);
});

test(`Does not render with ${styles.modifiers.plain} when isPlain is not passed`, () => {
render(
<Page {...props} data-testid="page">
<PageSection>Custom content</PageSection>
</Page>
);

const page = screen.getByTestId('page');
expect(page).not.toHaveClass(styles.modifiers.plain);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { render, screen } from '@testing-library/react';
import styles from '@patternfly/react-styles/css/components/Page/page';
import { PageFooter } from '../PageFooter';

test('Renders children', () => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's also add the "Renders without children" test

render(<PageFooter>Footer content</PageFooter>);
expect(screen.getByRole('contentinfo')).toBeVisible();
});

test('Renders without children', () => {
render(<PageFooter data-testid="footer" />);
expect(screen.getByTestId('footer')).toBeVisible();
});

test(`Renders with class ${styles.pageFooter} by default`, () => {
render(<PageFooter>Footer content</PageFooter>);
expect(screen.getByRole('contentinfo')).toHaveClass(styles.pageFooter, { exact: true });
});

test('Renders as a footer by default', () => {
render(<PageFooter>Footer content</PageFooter>);
expect(screen.getByRole('contentinfo').tagName).toBe('FOOTER');
});

test('Renders as a custom component when component is passed', () => {
render(<PageFooter component="div">Footer content</PageFooter>);
expect(screen.getByText('Footer content').tagName).toBe('DIV');
});

test('Renders with custom classes when className is passed', () => {
render(<PageFooter className="custom-class">Footer content</PageFooter>);
expect(screen.getByRole('contentinfo')).toHaveClass('custom-class');
});

test('Renders with spread props', () => {
render(<PageFooter id="custom-id">Footer content</PageFooter>);
expect(screen.getByRole('contentinfo')).toHaveAttribute('id', 'custom-id');
});
8 changes: 6 additions & 2 deletions packages/react-core/src/components/Page/examples/Page.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,15 @@ The `<MastheadMain>` component includes the smaller area that typically contains
- 1 or more `<PageSidebarBody>` components inside `<PageSidebar>` for vertical navigation or other sidebar content
- 1 or more `<PageSection>` components

### Page header
### Page header and footer

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.

```ts file="./PageHeaderContent.tsx"
`<PageFooter>` can be passed to the `footer` property, and should be used to wrap custom footer content.

When using a custom `<PageHeader>` and `<PageFooter>`, the `isPlain` flag should be passed to `<Page>`. This will remove styling on the main container, the height constraints on the page wrapper (so it can grow beyond the viewport), and remove the scroll management from the content section so the window will be what scrolls.

```ts file="./PageHeaderAndFooterContent.tsx"

```

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { Page, PageHeader, PageSection } from '@patternfly/react-core';
import { Page, PageHeader, PageFooter, PageSection } from '@patternfly/react-core';

export const PageHeaderContent: React.FunctionComponent = () => {
export const PageHeaderAndFooterContent: React.FunctionComponent = () => {
const pageHeader = <PageHeader>Page header</PageHeader>;
const pageFooter = <PageFooter>Page footer</PageFooter>;

return (
<Page masthead={pageHeader}>
<Page isPlain masthead={pageHeader} footer={pageFooter}>
<PageSection aria-labelledby="header-example-section-1">
<h2 id="header-example-section-1">Page header example section 1</h2>
</PageSection>
Expand Down
1 change: 1 addition & 0 deletions packages/react-core/src/components/Page/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export * from './Page';
export * from './PageBody';
export * from './PageBreadcrumb';
export * from './PageFooter';
export * from './PageGroup';
export * from './PageHeader';
export * from './PageSidebar';
Expand Down
2 changes: 1 addition & 1 deletion packages/react-docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"test:a11y": "patternfly-a11y --config patternfly-a11y.config"
},
"dependencies": {
"@patternfly/patternfly": "6.6.0-prerelease.39",
"@patternfly/patternfly": "6.6.0-prerelease.40",
"@patternfly/react-charts": "workspace:^",
"@patternfly/react-code-editor": "workspace:^",
"@patternfly/react-core": "workspace:^",
Expand Down
2 changes: 1 addition & 1 deletion packages/react-icons/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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.39",
"@patternfly/patternfly": "6.6.0-prerelease.40",
"@rhds/icons": "^2.3.1",
"fs-extra": "^11.3.3"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/react-styles/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"clean": "rimraf dist css"
},
"devDependencies": {
"@patternfly/patternfly": "6.6.0-prerelease.39",
"@patternfly/patternfly": "6.6.0-prerelease.40",
"change-case": "^5.4.4",
"fs-extra": "^11.3.3"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/react-tokens/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
},
"devDependencies": {
"@adobe/css-tools": "^4.4.4",
"@patternfly/patternfly": "6.6.0-prerelease.39",
"@patternfly/patternfly": "6.6.0-prerelease.40",
"fs-extra": "^11.3.3"
}
}
18 changes: 9 additions & 9 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5070,10 +5070,10 @@ __metadata:
languageName: node
linkType: hard

"@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
"@patternfly/patternfly@npm:6.6.0-prerelease.40":
version: 6.6.0-prerelease.40
resolution: "@patternfly/patternfly@npm:6.6.0-prerelease.40"
checksum: 10c0/33401d343f40467a94265965c25c8b40b16154d3bed3e4aab860662fae04947266778180ce8a076431b611420c913abedc672b9a3c26917635ef458bcb5c6826
languageName: node
linkType: hard

Expand Down Expand Up @@ -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.39"
"@patternfly/patternfly": "npm:6.6.0-prerelease.40"
"@patternfly/react-icons": "workspace:^"
"@patternfly/react-styles": "workspace:^"
"@patternfly/react-tokens": "workspace:^"
Expand All @@ -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.39"
"@patternfly/patternfly": "npm:6.6.0-prerelease.40"
"@patternfly/patternfly-a11y": "npm:5.2.1"
"@patternfly/react-charts": "workspace:^"
"@patternfly/react-code-editor": "workspace:^"
Expand Down Expand Up @@ -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.39"
"@patternfly/patternfly": "npm:6.6.0-prerelease.40"
"@rhds/icons": "npm:^2.3.1"
fs-extra: "npm:^11.3.3"
tslib: "npm:^2.8.1"
Expand Down Expand Up @@ -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.39"
"@patternfly/patternfly": "npm:6.6.0-prerelease.40"
change-case: "npm:^5.4.4"
fs-extra: "npm:^11.3.3"
languageName: unknown
Expand Down Expand Up @@ -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.39"
"@patternfly/patternfly": "npm:6.6.0-prerelease.40"
fs-extra: "npm:^11.3.3"
languageName: unknown
linkType: soft
Expand Down
Loading