Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
db5776e
feat(Compass): add compass nav components
wise-king-sullyman Nov 13, 2025
d02acd6
Merge branch 'main' into add-compass-nav-components
wise-king-sullyman Nov 13, 2025
c55a7a2
refactor to bundle children into CompassNavSearch/Home components
wise-king-sullyman Nov 13, 2025
4b5f5eb
Tweak interfaces to not duplicate props from HTMLDivElement
wise-king-sullyman Nov 13, 2025
40d34e7
update tests
wise-king-sullyman Nov 13, 2025
b8e3088
Add padding to compass demo nav
wise-king-sullyman Nov 13, 2025
90e6e2b
Update icons
wise-king-sullyman Nov 13, 2025
2b0d6fa
Core bump
wise-king-sullyman Nov 13, 2025
ba27a3f
Merge branch 'main' into add-compass-nav-components
wise-king-sullyman Nov 17, 2025
36e2458
Update lockfile
wise-king-sullyman Nov 17, 2025
99d749b
Update packages/react-core/src/components/Compass/CompassNavContent.tsx
wise-king-sullyman Nov 17, 2025
f7c0e43
Update packages/react-core/src/components/Compass/CompassNavHome.tsx
wise-king-sullyman Nov 17, 2025
e9539a1
Update packages/react-core/src/components/Compass/CompassNavMain.tsx
wise-king-sullyman Nov 17, 2025
54ed2b9
Update packages/react-core/src/components/Compass/CompassNavSearch.tsx
wise-king-sullyman Nov 17, 2025
d454281
format
wise-king-sullyman Nov 17, 2025
edc6b88
Fix dep issue introduced during merge conflict resolution
wise-king-sullyman Nov 17, 2025
d5665b5
Add trigger ref to tooltips
wise-king-sullyman Nov 17, 2025
b44d841
Address interface/a11y feedback
wise-king-sullyman Nov 18, 2025
4c8852d
Update packages/react-core/src/components/Compass/CompassNavContent.tsx
wise-king-sullyman Nov 19, 2025
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.22",
"@patternfly/patternfly": "6.5.0-prerelease.26",
"case-anything": "^3.1.2",
"css": "^3.0.0",
"fs-extra": "^11.3.0"
Expand Down
18 changes: 18 additions & 0 deletions packages/react-core/src/components/Compass/CompassNavContent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import styles from '@patternfly/react-styles/css/components/Compass/compass';
import { css } from '@patternfly/react-styles';
export interface CompassNavContentProps extends React.HTMLProps<HTMLDivElement> {
/** Content of the nav content wrapper. */
children: React.ReactNode;
}

export const CompassNavContent: React.FunctionComponent<CompassNavContentProps> = ({
children,
className,
...props
}) => (
<div className={css(styles.compassNavContent, className)} {...props}>
{children}
</div>
);

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

const CompassHomeIcon = () => (
<svg
width="1em"
height="1em"
className="pf-v6-svg"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
d="M8.33268 13.334H11.666"
stroke="currentcolor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M1.66602 6.66602L9.73102 2.63351C9.89994 2.54905 10.0988 2.54905 10.2677 2.63351L18.3327 6.66602"
stroke="currentcolor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M16.6673 9.16602V15.4993C16.6673 16.6039 15.7719 17.4993 14.6673 17.4993H5.33398C4.22941 17.4993 3.33398 16.6039 3.33398 15.4993V9.16602"
stroke="currentcolor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);

export interface CompassNavHomeProps extends Omit<React.HTMLProps<HTMLDivElement>, 'onClick'> {
/** Content to display in the tooltip. Defaults to "Home". */
tooltipContent?: React.ReactNode;
/** Click handler for the home button. */
onClick?: React.MouseEventHandler<HTMLButtonElement>;
}

export const CompassNavHome: React.FunctionComponent<CompassNavHomeProps> = ({
'aria-label': ariaLabel = 'Home',
tooltipContent = 'Home',
className,
onClick,
...props
}) => (
<div className={css(styles.compassNav + '-home', className)} {...props}>
<Tooltip content={tooltipContent}>
<Button isCircle variant="plain" icon={<CompassHomeIcon />} aria-label={ariaLabel} onClick={onClick} />
</Tooltip>
</div>
);

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

export interface CompassNavMainProps extends React.HTMLProps<HTMLDivElement> {
/** Content of the nav main section (typically tabs). */
children: React.ReactNode;
}

export const CompassNavMain: React.FunctionComponent<CompassNavMainProps> = ({ children, className, ...props }) => (
<div className={css(styles.compassNavMain, className)} {...props}>
{children}
</div>
);

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

const CompassSearchIcon = () => (
<svg
width="1em"
height="1em"
className="pf-v6-svg"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
d="M14.166 14.166L17.4993 17.4993"
stroke="currentcolor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M2.5 9.16667C2.5 12.8486 5.48477 15.8333 9.16667 15.8333C11.0108 15.8333 12.6801 15.0846 13.887 13.8744C15.0897 12.6685 15.8333 11.0044 15.8333 9.16667C15.8333 5.48477 12.8486 2.5 9.16667 2.5C5.48477 2.5 2.5 5.48477 2.5 9.16667Z"
stroke="currentcolor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);

export interface CompassNavSearchProps extends Omit<React.HTMLProps<HTMLDivElement>, 'onClick'> {
/** Content to display in the tooltip. Defaults to "Search". */
tooltipContent?: React.ReactNode;
/** Click handler for the search button. */
onClick?: React.MouseEventHandler<HTMLButtonElement>;
}

export const CompassNavSearch: React.FunctionComponent<CompassNavSearchProps> = ({
'aria-label': ariaLabel = 'Search',
tooltipContent = 'Search',
className,
onClick,
...props
}) => (
<div className={css(styles.compassNav + '-search', className)} {...props}>
<Tooltip content={tooltipContent}>
<Button isCircle variant="plain" icon={<CompassSearchIcon />} aria-label={ariaLabel} onClick={onClick} />
</Tooltip>
</div>
);

CompassNavSearch.displayName = 'CompassNavSearch';
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { render, screen } from '@testing-library/react';
import { CompassNavContent } from '../CompassNavContent';
import styles from '@patternfly/react-styles/css/components/Compass/compass';

test('Renders with children', () => {
render(<CompassNavContent>Test content</CompassNavContent>);

expect(screen.getByText('Test content')).toBeVisible();
});

test('Renders with custom class name when className prop is provided', () => {
render(<CompassNavContent className="custom-class">Test</CompassNavContent>);

expect(screen.getByText('Test')).toHaveClass('custom-class');
});

test(`Renders with default ${styles.compassNavContent} class`, () => {
render(<CompassNavContent>Test</CompassNavContent>);

expect(screen.getByText('Test')).toHaveClass(styles.compassNavContent, { exact: true });
});

test('Renders with additional props spread to the component', () => {
render(<CompassNavContent aria-label="Test label">Test</CompassNavContent>);

expect(screen.getByText('Test')).toHaveAccessibleName('Test label');
});

test('Matches the snapshot', () => {
const { asFragment } = render(
<CompassNavContent>
<div>Nav content wrapper</div>
</CompassNavContent>
);

expect(asFragment()).toMatchSnapshot();
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { CompassNavHome } from '../CompassNavHome';
import styles from '@patternfly/react-styles/css/components/Compass/compass';

test('Renders with default aria-label', () => {
render(<CompassNavHome />);

expect(screen.getByRole('button', { name: 'Home' })).toBeVisible();
});

test('Renders with custom aria-label when provided', () => {
render(<CompassNavHome aria-label="Custom home" />);

expect(screen.getByRole('button', { name: 'Custom home' })).toBeVisible();
});

test('Renders with default tooltip content', async () => {
const user = userEvent.setup();

render(<CompassNavHome />);

const button = screen.getByRole('button');

user.hover(button);

await screen.findByRole('tooltip');

expect(screen.getByRole('tooltip')).toHaveTextContent('Home');
});

test('Renders with custom tooltip content when provided', async () => {
const user = userEvent.setup();

render(<CompassNavHome tooltipContent="Custom tooltip" />);

const button = screen.getByRole('button');

user.hover(button);

await screen.findByRole('tooltip');
expect(screen.getByRole('tooltip')).toHaveTextContent('Custom tooltip');
});

test('Renders with custom class name when className prop is provided', () => {
const { container } = render(<CompassNavHome className="custom-class" />);

expect(container.firstChild).toHaveClass('custom-class');
});

test(`Renders with default class`, () => {
const { container } = render(<CompassNavHome />);

expect(container.firstChild).toHaveClass(styles.compassNav + '-home', { exact: true });
});

test('Calls onClick handler when button is clicked', async () => {
const user = userEvent.setup();
const onClick = jest.fn();

render(<CompassNavHome onClick={onClick} />);

await user.click(screen.getByRole('button', { name: 'Home' }));

expect(onClick).toHaveBeenCalledTimes(1);
});

test('Renders button with plain variant and circle shape', () => {
render(<CompassNavHome />);

const button = screen.getByRole('button', { name: 'Home' });

expect(button).toHaveClass('pf-m-plain');
expect(button).toHaveClass('pf-m-circle');
});

test('Matches the snapshot', () => {
const { asFragment } = render(<CompassNavHome />);

expect(asFragment()).toMatchSnapshot();
});

test('Matches the snapshot with custom props', () => {
const { asFragment } = render(
<CompassNavHome aria-label="Custom home" tooltipContent="Go home" className="custom-class" />
);

expect(asFragment()).toMatchSnapshot();
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { render, screen } from '@testing-library/react';
import { CompassNavMain } from '../CompassNavMain';
import styles from '@patternfly/react-styles/css/components/Compass/compass';

test('Renders with children', () => {
render(<CompassNavMain>Test content</CompassNavMain>);

expect(screen.getByText('Test content')).toBeVisible();
});

test('Renders with custom class name when className prop is provided', () => {
render(<CompassNavMain className="custom-class">Test</CompassNavMain>);

expect(screen.getByText('Test')).toHaveClass('custom-class');
});

test(`Renders with default ${styles.compassNavMain} class`, () => {
render(<CompassNavMain>Test</CompassNavMain>);

expect(screen.getByText('Test')).toHaveClass(styles.compassNavMain, { exact: true });
});

test('Renders with additional props spread to the component', () => {
render(<CompassNavMain aria-label="Test label">Test</CompassNavMain>);

expect(screen.getByText('Test')).toHaveAccessibleName('Test label');
});

test('Matches the snapshot', () => {
const { asFragment } = render(
<CompassNavMain>
<div>Main tabs content</div>
</CompassNavMain>
);

expect(asFragment()).toMatchSnapshot();
});
Loading
Loading