Skip to content

Commit cbb5f9b

Browse files
committed
feat(Nav): add icon prop to NavExpandable
Align expandable nav sections with NavItem by supporting an optional leading icon. Assisted-by: Cursor
1 parent 4ca2e04 commit cbb5f9b

4 files changed

Lines changed: 114 additions & 0 deletions

File tree

packages/react-core/src/components/Nav/NavExpandable.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ export interface NavExpandableProps
1212
extends Omit<React.DetailedHTMLProps<React.LiHTMLAttributes<HTMLLIElement>, HTMLLIElement>, 'title'>, OUIAProps {
1313
/** Title content shown for the expandable list */
1414
title: React.ReactNode;
15+
/** Icon added before the nav expandable children. */
16+
icon?: React.ReactNode;
1517
/** If defined, screen readers will read this text instead of the list title */
1618
srText?: string;
1719
/** Boolean to pragmatically expand or collapse section */
@@ -85,6 +87,7 @@ class NavExpandable extends Component<NavExpandableProps, NavExpandableState> {
8587
render() {
8688
const {
8789
title,
90+
icon,
8891
srText,
8992
children,
9093
className,
@@ -132,6 +135,7 @@ class NavExpandable extends Component<NavExpandableProps, NavExpandableState> {
132135
tabIndex={isSidebarOpen ? null : -1}
133136
{...buttonProps}
134137
>
138+
{icon && <span className={css(styles.navLinkIcon)}>{icon}</span>}
135139
{typeof title !== 'string' ? (
136140
<span className={css(`${styles.nav}__link-text`)}>{title}</span>
137141
) : (

packages/react-core/src/components/Nav/__tests__/NavExpandable.test.tsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { render, screen } from '@testing-library/react';
22
import '@testing-library/jest-dom';
3+
import styles from '@patternfly/react-styles/css/components/Nav/nav';
34
import { NavExpandable } from '../NavExpandable';
45

56
test('Renders with the inert attribute by default', () => {
@@ -13,3 +14,18 @@ test('Does not render with the inert attribute when isExpanded is true', () => {
1314

1415
expect(screen.getByLabelText('NavExpandable')).not.toHaveAttribute('inert', '');
1516
});
17+
18+
test('Renders icon with navLinkIcon class', () => {
19+
render(
20+
<NavExpandable id="grp-1" title="NavExpandable" icon={<div data-testid="nav-expandable-icon">Icon content</div>} />
21+
);
22+
23+
expect(screen.getByTestId('nav-expandable-icon').parentElement).toHaveClass(styles.navLinkIcon);
24+
});
25+
26+
test('Does not render icon wrapper when icon is not provided', () => {
27+
render(<NavExpandable id="grp-1" title="NavExpandable" />);
28+
29+
const button = screen.getByRole('button', { name: 'NavExpandable' });
30+
expect(button.querySelector(`.${styles.navLinkIcon}`)).toBeNull();
31+
});

packages/react-core/src/components/Nav/examples/Nav.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,12 @@ A flyout should be a `Menu` component. Press `space` or `right arrow` to open a
8181

8282
```
8383

84+
### Expandable with icons
85+
86+
```ts file="./NavExpandableIcons.tsx"
87+
88+
```
89+
8490

8591
## Types
8692

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import { useState } from 'react';
2+
import { Nav, NavExpandable, NavItem, NavList } from '@patternfly/react-core';
3+
import CubeIcon from '@patternfly/react-icons/dist/esm/icons/cube-icon';
4+
import FolderIcon from '@patternfly/react-icons/dist/esm/icons/folder-icon';
5+
6+
export const NavExpandableIcons: React.FunctionComponent = () => {
7+
const [activeGroup, setActiveGroup] = useState('nav-expandable-icon-group-1');
8+
const [activeItem, setActiveItem] = useState('nav-expandable-icon-group-1_item-1');
9+
10+
const onSelect = (
11+
_event: React.FormEvent<HTMLInputElement>,
12+
result: { itemId: number | string; groupId: number | string }
13+
) => {
14+
setActiveGroup(result.groupId as string);
15+
setActiveItem(result.itemId as string);
16+
};
17+
18+
const onToggle = (
19+
_event: React.MouseEvent<HTMLButtonElement>,
20+
result: { groupId: number | string; isExpanded: boolean }
21+
) => {
22+
// eslint-disable-next-line no-console
23+
console.log(`Group ${result.groupId} expanded? ${result.isExpanded}`);
24+
};
25+
26+
return (
27+
<Nav onSelect={onSelect} onToggle={onToggle} aria-label="Expandable with icons global">
28+
<NavList>
29+
<NavExpandable
30+
title="Expandable Group 1"
31+
icon={<CubeIcon />}
32+
groupId="nav-expandable-icon-group-1"
33+
isActive={activeGroup === 'nav-expandable-icon-group-1'}
34+
isExpanded
35+
>
36+
<NavItem
37+
preventDefault
38+
id="expandable-icon-1"
39+
to="#expandable-icon-1"
40+
groupId="nav-expandable-icon-group-1"
41+
itemId="nav-expandable-icon-group-1_item-1"
42+
isActive={activeItem === 'nav-expandable-icon-group-1_item-1'}
43+
>
44+
Subnav 1 Link 1
45+
</NavItem>
46+
<NavItem
47+
preventDefault
48+
id="expandable-icon-2"
49+
to="#expandable-icon-2"
50+
groupId="nav-expandable-icon-group-1"
51+
itemId="nav-expandable-icon-group-1_item-2"
52+
isActive={activeItem === 'nav-expandable-icon-group-1_item-2'}
53+
>
54+
Subnav 1 Link 2
55+
</NavItem>
56+
</NavExpandable>
57+
<NavExpandable
58+
title="Expandable Group 2"
59+
icon={<FolderIcon />}
60+
groupId="nav-expandable-icon-group-2"
61+
isActive={activeGroup === 'nav-expandable-icon-group-2'}
62+
isExpanded
63+
>
64+
<NavItem
65+
preventDefault
66+
id="expandable-icon-3"
67+
to="#expandable-icon-3"
68+
groupId="nav-expandable-icon-group-2"
69+
itemId="nav-expandable-icon-group-2_item-1"
70+
isActive={activeItem === 'nav-expandable-icon-group-2_item-1'}
71+
>
72+
Subnav 2 Link 1
73+
</NavItem>
74+
<NavItem
75+
preventDefault
76+
id="expandable-icon-4"
77+
to="#expandable-icon-4"
78+
groupId="nav-expandable-icon-group-2"
79+
itemId="nav-expandable-icon-group-2_item-2"
80+
isActive={activeItem === 'nav-expandable-icon-group-2_item-2'}
81+
>
82+
Subnav 2 Link 2
83+
</NavItem>
84+
</NavExpandable>
85+
</NavList>
86+
</Nav>
87+
);
88+
};

0 commit comments

Comments
 (0)