Skip to content
Merged
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.5.0-prerelease.9",
"@patternfly/patternfly": "6.5.0-prerelease.11",
"case-anything": "^3.1.2",
"css": "^3.0.0",
"fs-extra": "^11.3.0"
Expand Down
107 changes: 107 additions & 0 deletions packages/react-core/src/components/Compass/Compass.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { Drawer, DrawerContent, DrawerProps } from '../Drawer';
import styles from '@patternfly/react-styles/css/components/Compass/compass';
import { css } from '@patternfly/react-styles';

import compassBackgroundImageLight from '@patternfly/react-tokens/dist/esm/c_compass_BackgroundImage_light';
import compassBackgroundImageDark from '@patternfly/react-tokens/dist/esm/c_compass_BackgroundImage_dark';

export interface CompassProps extends React.HTMLProps<HTMLDivElement> {
/** Additional classes added to the compass. */
className?: string;
/** Content placed at the top of the layout */
header?: React.ReactNode;
Comment on lines +11 to +12
Copy link
Contributor

Choose a reason for hiding this comment

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

For these "area" props (header, footer, main, side panels), would we want to have wrapper containers introduced (CompassFooter, CompassMain, etc) that would be suggested to be passed in?

Or do we just want people to pass whatever they want into them, using the other Compass components in this PR when necessary?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think we want to leave these wrapper containers to the Compass since that controls the actual layout, and allows a user some flexibility if they want the layout but don't want the specific structure imposed by the typical child components. Those components (CompassHeader, CompassMessageBar, CompassContent, etc) are more opinionated/helper components to help achieve the same PoC style.

/** Flag indicating if the header is expanded */
isHeaderExpanded?: boolean;
/** Content placed at the horizontal start of the layout, before the main content */
sidebarStart?: React.ReactNode;
/** Flag indicating if the start sidebar is expanded */
isSidebarStartExpanded?: boolean;
Copy link
Contributor

Choose a reason for hiding this comment

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

Might be missing something, but setting these expanded props to false doesn't do anything in the examples added. It removes the expanded class, but the content is still visible.

Copy link
Contributor

Choose a reason for hiding this comment

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

At least one design file showed these blocks sliding in and out of the layout like drawers, so Katie added support for it via these props & .pf-m-expanded, but we haven't implemented any styles yet.

/** Content placed at the center of the layout */
main?: React.ReactNode;
/** Content placed at the horizontal end of the layout, after the main content */
sidebarEnd?: React.ReactNode;
/** Flag indicating if the end sidebar is expanded */
isSidebarEndExpanded?: boolean;
/** Content placed at the bottom of the layout */
footer?: React.ReactNode;
/** Flag indicating if the footer is expanded */
isFooterExpanded?: boolean;
/** Content rendered in an optional drawer wrapping the layout */
drawerContent?: React.ReactNode;
/** Additional props passed to the drawer */
drawerProps?: DrawerProps;
/** Light theme background image path of the compass */
backgroundSrcLight?: string;
/** Dark theme background image path of the compass */
backgroundSrcDark?: string;
}

export const Compass: React.FunctionComponent<CompassProps> = ({
className,
header,
isHeaderExpanded = true,
sidebarStart,
isSidebarStartExpanded = true,
main,
sidebarEnd,
isSidebarEndExpanded = true,
footer,
isFooterExpanded = true,
drawerContent,
drawerProps,
backgroundSrcLight,
backgroundSrcDark,
...props
}) => {
const hasDrawer = drawerContent !== undefined;

const backgroundImageStyles: { [key: string]: string } = {};
if (backgroundSrcLight) {
backgroundImageStyles[compassBackgroundImageLight.name] = `url(${backgroundSrcLight})`;
}
if (backgroundSrcDark) {
backgroundImageStyles[compassBackgroundImageDark.name] = `url(${backgroundSrcDark})`;
}

const compassContent = (
<div className={css(styles.compass, className)} {...props} style={{ ...props.style, ...backgroundImageStyles }}>
<div
className={css(styles.compassHeader, isHeaderExpanded && 'pf-m-expanded')}
{...(!isHeaderExpanded && { inert: 'true' })}
>
{header}
</div>
<div
className={css(styles.compassSidebar, styles.modifiers.start, isSidebarStartExpanded && 'pf-m-expanded')}
{...(!isSidebarStartExpanded && { inert: 'true' })}
>
{sidebarStart}
</div>
<div className={css(styles.compassMain)}>{main}</div>
<div
className={css(styles.compassSidebar, styles.modifiers.end, isSidebarEndExpanded && 'pf-m-expanded')}
{...(!isSidebarEndExpanded && { inert: 'true' })}
>
{sidebarEnd}
</div>
<div
className={css(styles.compassFooter, isFooterExpanded && 'pf-m-expanded')}
{...(!isFooterExpanded && { inert: 'true' })}
>
{footer}
</div>
</div>
);

if (hasDrawer) {
return (
<Drawer {...drawerProps}>
<DrawerContent panelContent={drawerContent}>{compassContent}</DrawerContent>
</Drawer>
);
}

return compassContent;
};

Compass.displayName = 'Compass';
42 changes: 42 additions & 0 deletions packages/react-core/src/components/Compass/CompassContent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Drawer, DrawerContent, DrawerProps } from '../Drawer';
import styles from '@patternfly/react-styles/css/components/Compass/compass';
import { css } from '@patternfly/react-styles';

interface CompassContentProps extends React.HTMLProps<HTMLDivElement> {
/** Content of the main compass area. Typically one or more CompassPanel components. */
children: React.ReactNode;
/** Additional classes added to the CompassContent */
className?: string;
/** Content rendered in an optional drawer wrapping the CompassContent */
drawerContent?: React.ReactNode;
/** Additional props passed to the drawer */
drawerProps?: DrawerProps;
}

export const CompassContent: React.FunctionComponent<CompassContentProps> = ({
children,
className,
drawerProps,
drawerContent,
...props
}) => {
const hasDrawer = drawerContent !== undefined;

const compassContent = (
<div className={css(styles.compassContent, className)} {...props}>
{children}
</div>
);

if (hasDrawer) {
return (
<Drawer {...drawerProps}>
<DrawerContent panelContent={drawerContent}>{compassContent}</DrawerContent>
</Drawer>
);
}

return compassContent;
};

CompassContent.displayName = 'CompassContent';
21 changes: 21 additions & 0 deletions packages/react-core/src/components/Compass/CompassHeader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import styles from '@patternfly/react-styles/css/components/Compass/compass';
import { css } from '@patternfly/react-styles';

interface CompassHeaderProps {
/** Content of the logo area */
logo?: React.ReactNode;
/** Content of the navigation area */
nav?: React.ReactNode;
/** Content of the profile area */
profile?: React.ReactNode;
Comment on lines +6 to +10
Copy link
Contributor

Choose a reason for hiding this comment

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

Since these are all optional, we should conditionally render their elements below.

Copy link
Contributor Author

@kmcfaul kmcfaul Oct 29, 2025

Choose a reason for hiding this comment

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

wdyt @mcoker?

Should the logo/nav/profile wrapping divs be present regardless if content is pass to the respective prop? I can't remember if the wrapping divs would effect the spacing or if the nav was vertically aligned independent of the other two divs (or the logo div at least).

Copy link
Contributor

Choose a reason for hiding this comment

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

I think if we want to allow someone to pass children to <CompassHeader> and whatever they pass takes up the whole layout, yeah we would conditionally render them. I made logo/nav/profile optional in the core docs assuming you could do this. Though if they pass any one of the three, I think we need to render all 3 for the positioning/centering to work.

Copy link
Contributor

Choose a reason for hiding this comment

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

Would it be worth an issue in the followup epic for tweaking the styles so that all 3 don't have to be rendered for the positioning to work?

This isn't a blocker by any means, just thinking if we can reduce empty nodes in the DOM it'd be great.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

A user can already pass children directly to header in Compass to achieve this, which was why I wasn't sure if we needed the conditional rendering for this component specifically since we'd want all 3 divs if any one div was passed.

}

export const CompassHeader: React.FunctionComponent<CompassHeaderProps> = ({ logo, nav, profile }) => (
<>
<div className={css(`${styles.compass}__logo`)}>{logo}</div>
<div className={css(styles.compassNav)}>{nav}</div>
<div className={css(styles.compassProfile)}>{profile}</div>
</>
);

CompassHeader.displayName = 'CompassHeader';
87 changes: 87 additions & 0 deletions packages/react-core/src/components/Compass/CompassHero.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import styles from '@patternfly/react-styles/css/components/Compass/compass';
import { css } from '@patternfly/react-styles';

import compassHeroBackgroundImageLight from '@patternfly/react-tokens/dist/esm/c_compass__hero_BackgroundImage_light';
import compassHeroBackgroundImageDark from '@patternfly/react-tokens/dist/esm/c_compass__hero_BackgroundImage_dark';
import compassHeroGradientStop1Light from '@patternfly/react-tokens/dist/esm/c_compass__hero_gradient_stop_1_light';
import compassHeroGradientStop2Light from '@patternfly/react-tokens/dist/esm/c_compass__hero_gradient_stop_2_light';
import compassHeroGradientStop3Light from '@patternfly/react-tokens/dist/esm/c_compass__hero_gradient_stop_3_light';
import compassHeroGradientStop1Dark from '@patternfly/react-tokens/dist/esm/c_compass__hero_gradient_stop_1_dark';
import compassHeroGradientStop2Dark from '@patternfly/react-tokens/dist/esm/c_compass__hero_gradient_stop_2_dark';
import compassHeroGradientStop3Dark from '@patternfly/react-tokens/dist/esm/c_compass__hero_gradient_stop_3_dark';

interface CompassHeroProps extends Omit<React.HTMLProps<HTMLDivElement>, 'content'> {
/** Content of the hero */
children?: React.ReactNode;
/** Additional classes added to the hero */
className?: string;
/** Light theme background image path of the hero */
backgroundSrcLight?: string;
/** Dark theme background image path of the hero */
backgroundSrcDark?: string;
/** Light theme gradient of the hero */
gradientLight?: {
stop1?: string;
stop2?: string;
stop3?: string;
};
/** Dark theme gradient of the hero */
gradientDark?: {
stop1?: string;
stop2?: string;
stop3?: string;
};
}

export const CompassHero: React.FunctionComponent<CompassHeroProps> = ({
className,
children,
backgroundSrcLight,
backgroundSrcDark,
gradientLight,
gradientDark,
...props
}) => {
const backgroundImageStyles: { [key: string]: string } = {};
if (backgroundSrcLight) {
backgroundImageStyles[compassHeroBackgroundImageLight.name] = `url(${backgroundSrcLight})`;
}
if (backgroundSrcDark) {
backgroundImageStyles[compassHeroBackgroundImageDark.name] = `url(${backgroundSrcDark})`;
}

if (gradientLight) {
if (gradientLight.stop1) {
backgroundImageStyles[compassHeroGradientStop1Light.name] = gradientLight.stop1;
}
if (gradientLight.stop2) {
backgroundImageStyles[compassHeroGradientStop2Light.name] = gradientLight.stop2;
}
if (gradientLight.stop3) {
backgroundImageStyles[compassHeroGradientStop3Light.name] = gradientLight.stop3;
}
}
if (gradientDark) {
if (gradientDark.stop1) {
backgroundImageStyles[compassHeroGradientStop1Dark.name] = gradientDark.stop1;
}
if (gradientDark.stop2) {
backgroundImageStyles[compassHeroGradientStop2Dark.name] = gradientDark.stop2;
}
if (gradientDark.stop3) {
backgroundImageStyles[compassHeroGradientStop3Dark.name] = gradientDark.stop3;
}
}

return (
<div
className={css(styles.compassPanel, styles.compassHero, className)}
style={{ ...props.style, ...backgroundImageStyles }}
{...props}
>
<div className={css(styles.compassHeroBody)}>{children}</div>
</div>
);
};

CompassHero.displayName = 'CompassHero';
43 changes: 43 additions & 0 deletions packages/react-core/src/components/Compass/CompassMainHeader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Flex, FlexItem } from '../../layouts/Flex';
import { CompassPanel } from './CompassPanel';
import styles from '@patternfly/react-styles/css/components/Compass/compass';
import { css } from '@patternfly/react-styles';

interface CompassMainHeaderProps extends Omit<React.HTMLProps<HTMLDivElement>, 'title'> {
/** Additional classes added to the main header */
className?: string;
/** Styled title. If title or toolbar is provided, the children will be ignored. */
title?: React.ReactNode;
/** Styled toolbar. If title or toolbar is provided, the children will be ignored. */
toolbar?: React.ReactNode;
/** Custom main header content. To opt into a default styling, use the title and toolbar props instead. */
children?: React.ReactNode;
}

export const CompassMainHeader: React.FunctionComponent<CompassMainHeaderProps> = ({
className,
title,
toolbar,
children,
...props
}) => {
const _content =
title !== undefined || toolbar !== undefined ? (
<CompassPanel>
<Flex alignItems={{ default: 'alignItemsCenter' }}>
<FlexItem grow={{ default: 'grow' }}>{title}</FlexItem>
{toolbar && <FlexItem>{toolbar}</FlexItem>}
</Flex>
</CompassPanel>
) : (
children
);

return (
<div className={css(`${styles.compass}__main-header`, className)} {...props}>
{_content}
</div>
);
};

CompassMainHeader.displayName = 'CompassMainHeader';
21 changes: 21 additions & 0 deletions packages/react-core/src/components/Compass/CompassMessageBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import styles from '@patternfly/react-styles/css/components/Compass/compass';
import { css } from '@patternfly/react-styles';

interface CompassMessageBarProps extends React.HTMLProps<HTMLDivElement> {
/** Content of the message bar. Typically a @patternfly/chatbot MessageBar component. */
children?: React.ReactNode;
/** Additional classes added to the message bar */
className?: string;
}

export const CompassMessageBar: React.FunctionComponent<CompassMessageBarProps> = ({
children,
className,
...props
}) => (
<div className={css(styles.compassMessageBar, className)} {...props}>
{children}
</div>
);

CompassMessageBar.displayName = 'CompassMessageBar';
51 changes: 51 additions & 0 deletions packages/react-core/src/components/Compass/CompassPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import styles from '@patternfly/react-styles/css/components/Compass/compass';
import { css } from '@patternfly/react-styles';

interface CompassPanelProps extends React.HTMLProps<HTMLDivElement> {
/** Content of the panel. */
children: React.ReactNode;
/** Additional classes added to the panel. */
className?: string;
/** Indicates the panel should have a pill border radius */
isPill?: boolean;
Comment on lines +9 to +10
Copy link
Contributor

Choose a reason for hiding this comment

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

Just thinking of it, but as part of the followup maybe we can go through any shared props amongst all the new compass stuff and make sure the verbiage is the same/similar.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Opened #12106

/** Indicates the panel should expand to fill the available height */
isFullHeight?: boolean;
/** Indicates the panel should scroll its overflow */
isScrollable?: boolean;
/** Indicates the panel should have no border */
hasNoBorder?: boolean;
/** Indicates the panel should have no padding */
hasNoPadding?: boolean;
/** Indicates the panel should have a "thinking" animation */
isThinking?: boolean;
}

export const CompassPanel: React.FunctionComponent<CompassPanelProps> = ({
children,
className,
isPill,
hasNoBorder,
hasNoPadding,
isThinking,
isFullHeight,
isScrollable,
...props
}) => (
<div
className={css(
styles.compassPanel,
isPill && styles.modifiers.pill,
hasNoBorder && styles.modifiers.noBorder,
hasNoPadding && styles.modifiers.noPadding,
isThinking && 'pf-v6-m-thinking',
isFullHeight && styles.modifiers.fullHeight,
isScrollable && styles.modifiers.scrollable,
className
)}
{...props}
>
{children}
</div>
);

CompassPanel.displayName = 'CompassPanel';
Loading
Loading