diff --git a/packages/react-core/src/components/Compass/CompassNavContent.tsx b/packages/react-core/src/components/Compass/CompassNavContent.tsx new file mode 100644 index 00000000000..8c27583213b --- /dev/null +++ b/packages/react-core/src/components/Compass/CompassNavContent.tsx @@ -0,0 +1,20 @@ +import styles from '@patternfly/react-styles/css/components/Compass/compass'; +import { css } from '@patternfly/react-styles'; +export interface CompassNavContentProps extends React.HTMLProps { + /** Content of the nav content wrapper. */ + children: React.ReactNode; + /** Additional classes added to the nav content. */ + className?: string; +} + +export const CompassNavContent: React.FunctionComponent = ({ + children, + className, + ...props +}: CompassNavContentProps) => ( +
+ {children} +
+); + +CompassNavContent.displayName = 'CompassNavContent'; diff --git a/packages/react-core/src/components/Compass/CompassNavHome.tsx b/packages/react-core/src/components/Compass/CompassNavHome.tsx new file mode 100644 index 00000000000..8f9410ddc08 --- /dev/null +++ b/packages/react-core/src/components/Compass/CompassNavHome.tsx @@ -0,0 +1,77 @@ +import { useRef } from 'react'; +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 = () => ( + +); + +export interface CompassNavHomeProps extends Omit, 'onClick'> { + /** Content to display in the tooltip. Defaults to "Home". */ + tooltipContent?: React.ReactNode; + /** Click handler for the home button. */ + onClick?: React.MouseEventHandler; + /** Additional classes added to the nav home wrapper. */ + className?: string; + /** Accessible label for the nav home. */ + 'aria-label'?: string; +} + +export const CompassNavHome: React.FunctionComponent = ({ + 'aria-label': ariaLabel = 'Home', + tooltipContent = 'Home', + className, + onClick, + ...props +}: CompassNavHomeProps) => { + const buttonRef = useRef(null); + + return ( +
+ +
+ ); +}; + +CompassNavHome.displayName = 'CompassNavHome'; diff --git a/packages/react-core/src/components/Compass/CompassNavMain.tsx b/packages/react-core/src/components/Compass/CompassNavMain.tsx new file mode 100644 index 00000000000..082e75c61e8 --- /dev/null +++ b/packages/react-core/src/components/Compass/CompassNavMain.tsx @@ -0,0 +1,21 @@ +import styles from '@patternfly/react-styles/css/components/Compass/compass'; +import { css } from '@patternfly/react-styles'; + +export interface CompassNavMainProps extends React.HTMLProps { + /** Content of the nav main section (typically tabs). */ + children: React.ReactNode; + /** Additional classes added to the nav main section. */ + className?: string; +} + +export const CompassNavMain: React.FunctionComponent = ({ + children, + className, + ...props +}: CompassNavMainProps) => ( +
+ {children} +
+); + +CompassNavMain.displayName = 'CompassNavMain'; diff --git a/packages/react-core/src/components/Compass/CompassNavSearch.tsx b/packages/react-core/src/components/Compass/CompassNavSearch.tsx new file mode 100644 index 00000000000..5731b45883c --- /dev/null +++ b/packages/react-core/src/components/Compass/CompassNavSearch.tsx @@ -0,0 +1,70 @@ +import { useRef } from 'react'; +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 = () => ( + +); + +export interface CompassNavSearchProps extends Omit, 'onClick'> { + /** Content to display in the tooltip. Defaults to "Search". */ + tooltipContent?: React.ReactNode; + /** Click handler for the search button. */ + onClick?: React.MouseEventHandler; + /** Additional classes added to the nav search wrapper. */ + className?: string; + /** Accessible label for the nav search. */ + 'aria-label'?: string; +} + +export const CompassNavSearch: React.FunctionComponent = ({ + 'aria-label': ariaLabel = 'Search', + tooltipContent = 'Search', + className, + onClick, + ...props +}: CompassNavSearchProps) => { + const buttonRef = useRef(null); + + return ( +
+ +
+ ); +}; + +CompassNavSearch.displayName = 'CompassNavSearch'; diff --git a/packages/react-core/src/components/Compass/__tests__/CompassNavContent.test.tsx b/packages/react-core/src/components/Compass/__tests__/CompassNavContent.test.tsx new file mode 100644 index 00000000000..eb176b5ccae --- /dev/null +++ b/packages/react-core/src/components/Compass/__tests__/CompassNavContent.test.tsx @@ -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(Test content); + + expect(screen.getByText('Test content')).toBeVisible(); +}); + +test('Renders with custom class name when className prop is provided', () => { + render(Test); + + expect(screen.getByText('Test')).toHaveClass('custom-class'); +}); + +test(`Renders with default ${styles.compassNavContent} class`, () => { + render(Test); + + expect(screen.getByText('Test')).toHaveClass(styles.compassNavContent, { exact: true }); +}); + +test('Renders with additional props spread to the component', () => { + render(Test); + + expect(screen.getByText('Test')).toHaveAccessibleName('Test label'); +}); + +test('Matches the snapshot', () => { + const { asFragment } = render( + +
Nav content wrapper
+
+ ); + + expect(asFragment()).toMatchSnapshot(); +}); diff --git a/packages/react-core/src/components/Compass/__tests__/CompassNavHome.test.tsx b/packages/react-core/src/components/Compass/__tests__/CompassNavHome.test.tsx new file mode 100644 index 00000000000..020d154cfd5 --- /dev/null +++ b/packages/react-core/src/components/Compass/__tests__/CompassNavHome.test.tsx @@ -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(); + + expect(screen.getByRole('button', { name: 'Home' })).toBeVisible(); +}); + +test('Renders with custom aria-label when provided', () => { + render(); + + expect(screen.getByRole('button', { name: 'Custom home' })).toBeVisible(); +}); + +test('Renders with default tooltip content', async () => { + const user = userEvent.setup(); + + render(); + + 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(); + + 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(); + + expect(container.firstChild).toHaveClass('custom-class'); +}); + +test(`Renders with default class`, () => { + const { container } = render(); + + 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(); + + await user.click(screen.getByRole('button', { name: 'Home' })); + + expect(onClick).toHaveBeenCalledTimes(1); +}); + +test('Renders button with plain variant and circle shape', () => { + render(); + + 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(); + + expect(asFragment()).toMatchSnapshot(); +}); + +test('Matches the snapshot with custom props', () => { + const { asFragment } = render( + + ); + + expect(asFragment()).toMatchSnapshot(); +}); diff --git a/packages/react-core/src/components/Compass/__tests__/CompassNavMain.test.tsx b/packages/react-core/src/components/Compass/__tests__/CompassNavMain.test.tsx new file mode 100644 index 00000000000..0242f9ef64c --- /dev/null +++ b/packages/react-core/src/components/Compass/__tests__/CompassNavMain.test.tsx @@ -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(Test content); + + expect(screen.getByText('Test content')).toBeVisible(); +}); + +test('Renders with custom class name when className prop is provided', () => { + render(Test); + + expect(screen.getByText('Test')).toHaveClass('custom-class'); +}); + +test(`Renders with default ${styles.compassNavMain} class`, () => { + render(Test); + + expect(screen.getByText('Test')).toHaveClass(styles.compassNavMain, { exact: true }); +}); + +test('Renders with additional props spread to the component', () => { + render(Test); + + expect(screen.getByText('Test')).toHaveAccessibleName('Test label'); +}); + +test('Matches the snapshot', () => { + const { asFragment } = render( + +
Main tabs content
+
+ ); + + expect(asFragment()).toMatchSnapshot(); +}); diff --git a/packages/react-core/src/components/Compass/__tests__/CompassNavSearch.test.tsx b/packages/react-core/src/components/Compass/__tests__/CompassNavSearch.test.tsx new file mode 100644 index 00000000000..2c5432c9bb2 --- /dev/null +++ b/packages/react-core/src/components/Compass/__tests__/CompassNavSearch.test.tsx @@ -0,0 +1,87 @@ +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { CompassNavSearch } from '../CompassNavSearch'; +import styles from '@patternfly/react-styles/css/components/Compass/compass'; + +test('Renders with default aria-label', () => { + render(); + expect(screen.getByRole('button', { name: 'Search' })).toBeVisible(); +}); + +test('Renders with custom aria-label when provided', () => { + render(); + expect(screen.getByRole('button', { name: 'Custom search' })).toBeVisible(); +}); + +test('Renders with default tooltip content', async () => { + const user = userEvent.setup(); + + render(); + + const button = screen.getByRole('button'); + + user.hover(button); + + await screen.findByRole('tooltip'); + + expect(screen.getByRole('tooltip')).toHaveTextContent('Search'); +}); + +test('Renders with custom tooltip content when provided', async () => { + const user = userEvent.setup(); + + render(); + + 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(); + + expect(container.firstChild).toHaveClass('custom-class'); +}); + +test(`Renders with default class`, () => { + const { container } = render(); + + expect(container.firstChild).toHaveClass(styles.compassNav + '-search', { exact: true }); +}); + +test('Calls onClick handler when button is clicked', async () => { + const user = userEvent.setup(); + const onClick = jest.fn(); + + render(); + + await user.click(screen.getByRole('button', { name: 'Search' })); + + expect(onClick).toHaveBeenCalledTimes(1); +}); + +test('Renders button with plain variant and circle shape', () => { + render(); + + const button = screen.getByRole('button', { name: 'Search' }); + + expect(button).toHaveClass('pf-m-plain'); +}); + +test('Matches the snapshot', () => { + const { asFragment } = render(); + + expect(asFragment()).toMatchSnapshot(); +}); + +test('Matches the snapshot with custom props', () => { + const { asFragment } = render( + + ); + + expect(asFragment()).toMatchSnapshot(); +}); diff --git a/packages/react-core/src/components/Compass/__tests__/__snapshots__/CompassNavContent.test.tsx.snap b/packages/react-core/src/components/Compass/__tests__/__snapshots__/CompassNavContent.test.tsx.snap new file mode 100644 index 00000000000..df1e9a2ddea --- /dev/null +++ b/packages/react-core/src/components/Compass/__tests__/__snapshots__/CompassNavContent.test.tsx.snap @@ -0,0 +1,13 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Matches the snapshot 1`] = ` + +
+
+ Nav content wrapper +
+
+
+`; diff --git a/packages/react-core/src/components/Compass/__tests__/__snapshots__/CompassNavHome.test.tsx.snap b/packages/react-core/src/components/Compass/__tests__/__snapshots__/CompassNavHome.test.tsx.snap new file mode 100644 index 00000000000..4ff0408656d --- /dev/null +++ b/packages/react-core/src/components/Compass/__tests__/__snapshots__/CompassNavHome.test.tsx.snap @@ -0,0 +1,107 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Matches the snapshot 1`] = ` + +
+ +
+
+`; + +exports[`Matches the snapshot with custom props 1`] = ` + +
+ +
+
+`; diff --git a/packages/react-core/src/components/Compass/__tests__/__snapshots__/CompassNavMain.test.tsx.snap b/packages/react-core/src/components/Compass/__tests__/__snapshots__/CompassNavMain.test.tsx.snap new file mode 100644 index 00000000000..53987fbf559 --- /dev/null +++ b/packages/react-core/src/components/Compass/__tests__/__snapshots__/CompassNavMain.test.tsx.snap @@ -0,0 +1,13 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Matches the snapshot 1`] = ` + +
+
+ Main tabs content +
+
+
+`; diff --git a/packages/react-core/src/components/Compass/__tests__/__snapshots__/CompassNavSearch.test.tsx.snap b/packages/react-core/src/components/Compass/__tests__/__snapshots__/CompassNavSearch.test.tsx.snap new file mode 100644 index 00000000000..0bcb2978947 --- /dev/null +++ b/packages/react-core/src/components/Compass/__tests__/__snapshots__/CompassNavSearch.test.tsx.snap @@ -0,0 +1,93 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Matches the snapshot 1`] = ` + + + +`; + +exports[`Matches the snapshot with custom props 1`] = ` + + + +`; diff --git a/packages/react-core/src/components/Compass/examples/CompassDemo.tsx b/packages/react-core/src/components/Compass/examples/CompassDemo.tsx index f2c08e72180..e70a9b15873 100644 --- a/packages/react-core/src/components/Compass/examples/CompassDemo.tsx +++ b/packages/react-core/src/components/Compass/examples/CompassDemo.tsx @@ -1,3 +1,5 @@ +/* eslint-disable no-console */ + import { useRef, useState } from 'react'; import { Compass, @@ -7,6 +9,10 @@ import { CompassMainHeader, CompassPanel, CompassMessageBar, + CompassNavContent, + CompassNavHome, + CompassNavMain, + CompassNavSearch, Hero, Tabs, TabsComponent, @@ -32,49 +38,57 @@ export const CompassBasic: React.FunctionComponent = () => { const navContent = ( <> - - setActiveTab(tabIndex as number)} - component={TabsComponent.nav} - aria-label="Compass navigation tabs" - inset={{ default: 'insetXl' }} - > - Tab 1} - aria-label="Compass tab with subtabs" - /> - Tab 2} /> - Tab 3} /> - Disabled Tab 4} isDisabled /> - + + + console.log('Home')} /> + + setActiveTab(tabIndex as number)} + component={TabsComponent.nav} + aria-label="Compass navigation tabs" + > + Tab 1} + aria-label="Compass tab with subtabs" + /> + Tab 2} /> + Tab 3} /> + Disabled Tab 4} isDisabled /> + + + console.log('Search')} /> + - setActiveSubtab(tabIndex as number)} - aria-label="Compass navigation subtabs" - inset={{ default: 'insetXl' }} - > - -
Subtab 1
- - } - /> - Subtab 2} /> - Disabled Subtab 3} isDisabled /> -
+ + + setActiveSubtab(tabIndex as number)} + aria-label="Compass navigation subtabs" + > + +
Subtab 1
+ + } + /> + Subtab 2} /> + Disabled Subtab 3} isDisabled /> +
+
+
diff --git a/packages/react-core/src/components/Compass/index.ts b/packages/react-core/src/components/Compass/index.ts index aabf250facb..936b03ca5af 100644 --- a/packages/react-core/src/components/Compass/index.ts +++ b/packages/react-core/src/components/Compass/index.ts @@ -7,4 +7,8 @@ export * from './CompassMainHeaderContent'; export * from './CompassMainHeaderTitle'; export * from './CompassMainHeaderToolbar'; export * from './CompassMessageBar'; +export * from './CompassNavContent'; +export * from './CompassNavHome'; +export * from './CompassNavMain'; +export * from './CompassNavSearch'; export * from './CompassPanel';