Skip to content

Commit 4f8c828

Browse files
haloclineCopilot
andauthored
[aries-core] NavMenu - Added ability to support grouping child NavItems with subheadings (#5700)
* Created reusable NavigationPanel * Unique keys for sessionStorage * Progress towards supporting subheadings * Progress towards supporting subheadings * Adjusted NavItem label color at rest and hover + padding for icon alignment * Adjusted NavItem label color at active * Adjusted NavItem label color at focus * modified group border treatment * Adjust label indentation levels * Created NavGroup * Created NavGroup * Added tests * Updated documentation * Removed unused imports * fixed diagram * Update shared/aries-core/src/js/components/core/NavigationMenu/NavGroup/NavGroup.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Cleaned up indentation type * Ensure nav items have unique ids * removed assignment where equivalent * Use id instead of label * Memoized assignUniqueIds * Fixed test to be async * removed unnecessary role * Prettier * Using item.id as identifiers * Added a missing item.id * Fixed all id usage * fixed import * cleanups * fixed type * type update --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 5e99731 commit 4f8c828

File tree

13 files changed

+1016
-335
lines changed

13 files changed

+1016
-335
lines changed

shared/aries-core/src/js/components/core/NavigationMenu/DOCUMENTATION.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ export type NavItemType = {
184184
url?: string; // Navigation URL
185185
icon?: React.ReactNode; // Display icon
186186
children?: NavItemType[]; // Nested items
187+
type?: 'group' | 'item'; // Optional grouping type
187188
};
188189
```
189190

@@ -254,6 +255,42 @@ const hierarchicalNavItems: NavItemType[] = [
254255
/>;
255256
```
256257

258+
### Grouped Navigation Menu
259+
260+
```tsx
261+
const groupedNavItems: NavItemType[] = [
262+
{
263+
label: 'Components',
264+
children: [
265+
{
266+
label: 'Layout',
267+
type: 'group',
268+
children: [
269+
{ label: 'Box', url: '/components/box' },
270+
{ label: 'Grid', url: '/components/grid' },
271+
{ label: 'Layer', url: '/components/layer' },
272+
],
273+
},
274+
{
275+
label: 'Controls',
276+
type: 'group',
277+
children: [
278+
{ label: 'Button', url: '/components/button' },
279+
{ label: 'CheckBox', url: '/components/checkbox' },
280+
],
281+
},
282+
],
283+
},
284+
];
285+
286+
<NavigationMenu
287+
items={groupedNavItems}
288+
open={isOpen}
289+
title="Grouped Components"
290+
onSelect={handleItemSelect}
291+
/>;
292+
```
293+
257294
### Navigation Menu with Custom Header
258295

259296
```tsx
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { Box, Text } from 'grommet';
2+
import { NavItemWithLevel } from '../NavList';
3+
4+
const indentation: Record<number, string> = {
5+
1: 'medium',
6+
2: 'large',
7+
};
8+
9+
interface GroupHeadingProps {
10+
id: string;
11+
item: NavItemWithLevel;
12+
}
13+
14+
export const GroupHeading = ({ id, item }: GroupHeadingProps) => {
15+
return (
16+
<Box
17+
direction="row"
18+
pad={{
19+
left: item.level ? indentation[item.level] : 'small',
20+
top: 'xxsmall',
21+
bottom: '5xsmall',
22+
}}
23+
role="presentation"
24+
>
25+
<Text
26+
id={id}
27+
size="xsmall"
28+
weight="bold"
29+
role="heading"
30+
aria-level={3} // TODO: Consider making this dynamic based on nesting level or allowing it to be passed in as a prop for more flexibility.
31+
>
32+
{item.label}
33+
</Text>
34+
</Box>
35+
);
36+
};
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { Box } from 'grommet';
2+
import { NavItemWithLevel } from '../NavList';
3+
import { GroupHeading } from './GroupHeading';
4+
5+
interface NavGroupProps {
6+
item: NavItemWithLevel;
7+
render: (item: NavItemWithLevel) => React.ReactNode;
8+
defaultItemProps?: { [key: string]: any };
9+
}
10+
11+
export const NavGroup = ({ item, render, defaultItemProps }: NavGroupProps) => {
12+
const headerId = `${item.id}-heading`;
13+
14+
return (
15+
<Box>
16+
<GroupHeading id={headerId} item={item} />
17+
<Box
18+
role="group"
19+
aria-labelledby={headerId}
20+
border={{ side: 'bottom', color: 'border-weak' }}
21+
>
22+
{item.children?.map(child => (
23+
<Box
24+
key={child.id}
25+
{...defaultItemProps}
26+
>
27+
{render(child)}
28+
</Box>
29+
))}
30+
</Box>
31+
</Box>
32+
);
33+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './NavGroup';
Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,29 @@
11
import React from 'react';
22
import { Box, Text } from 'grommet';
33

4-
const labelColor = 'text-strong';
4+
const labelColor = 'text';
55
interface ItemLabelProps {
66
icon: React.ReactNode;
77
label: string;
8+
color?: string;
89
}
910

10-
export const ItemLabel = ({ icon, label }: ItemLabelProps) => {
11+
export const ItemLabel = ({ icon, label, color = labelColor }: ItemLabelProps) => {
1112
const adjIcon = React.isValidElement(icon)
1213
? React.cloneElement(icon as React.ReactElement<{ color?: string }>, {
13-
color: labelColor,
14+
color,
1415
})
1516
: icon;
1617

1718
return (
1819
<Box direction="row" gap="xxsmall" flex>
19-
<Box
20-
pad={{ top: '5xsmall' }} // aligning icon with label text
20+
{adjIcon && <Box
21+
pad={{ top: '4xsmall' }} // aligning icon with label text
2122
flex={false}
2223
>
2324
{adjIcon}
24-
</Box>
25-
<Text color={labelColor}>{label}</Text>
25+
</Box>}
26+
<Text color={color}>{label}</Text>
2627
</Box>
2728
);
2829
};

shared/aries-core/src/js/components/core/NavigationMenu/NavItem/NavItem.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
1-
import React from 'react';
21
import { Button } from 'grommet';
32
import { ItemContainer } from './ItemContainer';
43
import { ItemLabel } from './ItemLabel';
54

65
export type NavItemType = {
6+
id?: string; // Added optional id for better key management
77
label: string;
88
url?: string;
99
icon?: React.ReactNode;
1010
children?: NavItemType[];
11+
type?: 'group' | 'item';
1112
};
1213

1314
interface NavItemProps {
@@ -61,7 +62,7 @@ export const NavItem = ({
6162
ref={ref as any}
6263
{...(rest as any)}
6364
>
64-
{({ hover }) => {
65+
{({ hover, focus }) => {
6566
return (
6667
<ItemContainer
6768
active={active as boolean | undefined}
@@ -71,6 +72,7 @@ export const NavItem = ({
7172
<ItemLabel
7273
icon={icon as React.ReactNode}
7374
label={label as string}
75+
color={hover || focus || active ? 'text-strong' : 'text'}
7476
/>
7577
{actions as React.ReactNode}
7678
</ItemContainer>

0 commit comments

Comments
 (0)