package.src.components.Page.Page.tsx Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of react-core Show documentation
Show all versions of react-core Show documentation
This library provides a set of common React components for use with the PatternFly reference implementation.
The newest version!
import * as React from 'react';
import styles from '@patternfly/react-styles/css/components/Page/page';
import { css } from '@patternfly/react-styles';
import globalBreakpointXl from '@patternfly/react-tokens/dist/esm/global_breakpoint_xl';
import { debounce, canUseDOM } from '../../helpers/util';
import { Drawer, DrawerContent, DrawerContentBody, DrawerPanelContent } from '../Drawer';
import { PageBreadcrumbProps } from './PageBreadcrumb';
import { PageGroup, PageGroupProps } from './PageGroup';
import { getResizeObserver } from '../../helpers/resizeObserver';
import { formatBreakpointMods, getBreakpoint, getVerticalBreakpoint } from '../../helpers/util';
import { PageContextProvider } from './PageContext';
export enum PageLayouts {
vertical = 'vertical',
horizontal = 'horizontal'
}
export interface PageProps extends React.HTMLProps {
/** Content rendered inside the main section of the page layout (e.g. ) */
children?: React.ReactNode;
/** Additional classes added to the page layout */
className?: string;
/** Header component (e.g. ) */
header?: React.ReactNode;
/** Sidebar component for a side nav (e.g. ) */
sidebar?: React.ReactNode;
/** Notification drawer component for an optional notification drawer (e.g. ) */
notificationDrawer?: React.ReactNode;
/** Flag indicating Notification drawer in expanded */
isNotificationDrawerExpanded?: boolean;
/** Flag indicating if breadcrumb width should be limited */
isBreadcrumbWidthLimited?: boolean;
/** Callback when notification drawer panel is finished expanding. */
onNotificationDrawerExpand?: (event: KeyboardEvent | React.MouseEvent | React.TransitionEvent) => void;
/** Skip to content component for the page */
skipToContent?: React.ReactElement;
/** Sets the value for role on the element */
role?: string;
/** an id to use for the [role="main"] element */
mainContainerId?: string;
/** tabIndex to use for the [role="main"] element, null to unset it */
mainTabIndex?: number | null;
/**
* If true, manages the sidebar open/close state and there is no need to pass the isSidebarOpen boolean into
* the sidebar component or add a callback onSidebarToggle function into the Masthead component
*/
isManagedSidebar?: boolean;
/** Flag indicating if tertiary nav width should be limited */
isTertiaryNavWidthLimited?: boolean;
/**
* If true, the managed sidebar is initially open for desktop view
*/
defaultManagedSidebarIsOpen?: boolean;
/**
* Can add callback to be notified when resize occurs, for example to set the sidebar isSidebarOpen prop to false for a width < 768px
* Returns object { mobileView: boolean, windowSize: number }
*/
onPageResize?: ((event: MouseEvent | TouchEvent | React.KeyboardEvent, object: any) => void) | null;
/**
* The page resize observer uses the breakpoints returned from this function when adding the pf-m-breakpoint-[default|sm|md|lg|xl|2xl] class
* You can override the default getBreakpoint function to return breakpoints at different sizes than the default
* You can view the default getBreakpoint function here:
* https://github.com/patternfly/patternfly-react/blob/main/packages/react-core/src/helpers/util.ts
*/
getBreakpoint?: (width: number | null) => 'default' | 'sm' | 'md' | 'lg' | 'xl' | '2xl';
/**
* The page resize observer uses the breakpoints returned from this function when adding the pf-m-breakpoint-[default|sm|md|lg|xl|2xl] class
* You can override the default getVerticalBreakpoint function to return breakpoints at different sizes than the default
* You can view the default getVerticalBreakpoint function here:
* https://github.com/patternfly/patternfly-react/blob/main/packages/react-core/src/helpers/util.ts
*/
getVerticalBreakpoint?: (height: number | null) => 'default' | 'sm' | 'md' | 'lg' | 'xl' | '2xl';
/** Breadcrumb component for the page */
breadcrumb?: React.ReactNode;
/** Tertiary nav component for the page */
tertiaryNav?: React.ReactNode;
/** Accessible label, can be used to name main section */
mainAriaLabel?: string;
/** Flag indicating if the tertiaryNav should be in a group */
isTertiaryNavGrouped?: boolean;
/** Flag indicating if the breadcrumb should be in a group */
isBreadcrumbGrouped?: boolean;
/** Additional content of the group */
additionalGroupedContent?: React.ReactNode;
/** HTML component used as main component of the page. Defaults to 'main', only pass in 'div' if another 'main' element already exists. */
mainComponent?: 'main' | 'div';
/** Additional props of the group */
groupProps?: PageGroupProps;
/** Additional props of the breadcrumb */
breadcrumbProps?: PageBreadcrumbProps;
}
export interface PageState {
desktopIsSidebarOpen: boolean;
mobileIsSidebarOpen: boolean;
mobileView: boolean;
width: number;
height: number;
}
class Page extends React.Component {
static displayName = 'Page';
static defaultProps: PageProps = {
isManagedSidebar: false,
isBreadcrumbWidthLimited: false,
defaultManagedSidebarIsOpen: true,
mainTabIndex: -1,
isNotificationDrawerExpanded: false,
onNotificationDrawerExpand: () => null,
mainComponent: 'main',
getBreakpoint,
getVerticalBreakpoint
};
mainRef = React.createRef();
pageRef = React.createRef();
observer: any = () => {};
constructor(props: PageProps) {
super(props);
const { isManagedSidebar, defaultManagedSidebarIsOpen } = props;
const managedSidebarOpen = !isManagedSidebar ? true : defaultManagedSidebarIsOpen;
this.state = {
desktopIsSidebarOpen: managedSidebarOpen,
mobileIsSidebarOpen: false,
mobileView: false,
width: null,
height: null
};
}
componentDidMount() {
const { isManagedSidebar, onPageResize } = this.props;
if (isManagedSidebar || onPageResize) {
this.observer = getResizeObserver(this.pageRef.current, this.handleResize);
const currentRef = this.mainRef.current;
if (currentRef) {
currentRef.addEventListener('mousedown', this.handleMainClick);
currentRef.addEventListener('touchstart', this.handleMainClick);
}
// Initial check if should be shown
this.resize();
}
}
componentWillUnmount() {
const { isManagedSidebar, onPageResize } = this.props;
if (isManagedSidebar || onPageResize) {
this.observer();
const currentRef = this.mainRef.current;
if (currentRef) {
currentRef.removeEventListener('mousedown', this.handleMainClick);
currentRef.removeEventListener('touchstart', this.handleMainClick);
}
}
}
getWindowWidth = () => {
if (canUseDOM) {
return this.pageRef.current ? this.pageRef.current.clientWidth : window.innerWidth;
} else {
return 1200;
}
};
isMobile = () =>
// eslint-disable-next-line radix
this.getWindowWidth() < Number.parseInt(globalBreakpointXl.value, 10);
resize = (_event?: MouseEvent | TouchEvent | React.KeyboardEvent) => {
const { onPageResize } = this.props;
const mobileView = this.isMobile();
if (onPageResize) {
onPageResize(_event, { mobileView, windowSize: this.getWindowWidth() });
}
if (mobileView !== this.state.mobileView) {
this.setState({ mobileView });
}
if (this.pageRef?.current) {
const currentWidth = this.pageRef.current.clientWidth;
const currentHeight = this.pageRef.current.clientHeight;
if (this.state.width !== currentWidth) {
this.setState({ width: currentWidth });
}
if (this.state.height !== currentHeight) {
this.setState({ height: currentHeight });
}
}
};
handleResize = debounce(this.resize, 250);
handleMainClick = () => {
if (this.isMobile() && this.state.mobileIsSidebarOpen && this.mainRef.current) {
this.setState({ mobileIsSidebarOpen: false });
}
};
onSidebarToggleMobile = () => {
this.setState((prevState) => ({
mobileIsSidebarOpen: !prevState.mobileIsSidebarOpen
}));
};
onSidebarToggleDesktop = () => {
this.setState((prevState) => ({
desktopIsSidebarOpen: !prevState.desktopIsSidebarOpen
}));
};
render() {
const {
breadcrumb,
isBreadcrumbWidthLimited,
className,
children,
header,
sidebar,
notificationDrawer,
isNotificationDrawerExpanded,
onNotificationDrawerExpand,
isTertiaryNavWidthLimited,
skipToContent,
role,
mainContainerId,
isManagedSidebar,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
defaultManagedSidebarIsOpen,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
onPageResize,
getBreakpoint,
getVerticalBreakpoint,
mainAriaLabel,
mainTabIndex,
mainComponent,
tertiaryNav,
isTertiaryNavGrouped,
isBreadcrumbGrouped,
additionalGroupedContent,
groupProps,
breadcrumbProps,
...rest
} = this.props;
const { mobileView, mobileIsSidebarOpen, desktopIsSidebarOpen, width, height } = this.state;
const context = {
isManagedSidebar,
onSidebarToggle: mobileView ? this.onSidebarToggleMobile : this.onSidebarToggleDesktop,
isSidebarOpen: mobileView ? mobileIsSidebarOpen : desktopIsSidebarOpen,
width,
height,
getBreakpoint,
getVerticalBreakpoint
};
let nav = null;
if (tertiaryNav && isTertiaryNavWidthLimited) {
nav = (
{tertiaryNav}
);
} else if (tertiaryNav) {
nav = {tertiaryNav};
}
const crumb = breadcrumb ? (
{isBreadcrumbWidthLimited ? {breadcrumb} : breadcrumb}
) : null;
const isGrouped = isTertiaryNavGrouped || isBreadcrumbGrouped || additionalGroupedContent;
const group = isGrouped ? (
{isTertiaryNavGrouped && nav}
{isBreadcrumbGrouped && crumb}
{additionalGroupedContent}
) : null;
const Component: keyof JSX.IntrinsicElements = mainComponent;
const main = (
{group}
{!isTertiaryNavGrouped && nav}
{!isBreadcrumbGrouped && crumb}
{children}
);
const panelContent = {notificationDrawer} ;
return (
{skipToContent}
{header}
{sidebar}
{notificationDrawer && (
onNotificationDrawerExpand(event)}>
{main}
)}
{!notificationDrawer && main}
);
}
}
export { Page };