gatsby-theme-carbon 4.0.6 → 4.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/gatsby-node.mjs +6 -1
- package/package.json +1 -1
- package/src/components/LeftNav/LeftNav.js +74 -30
- package/src/components/LeftNav/LeftNav.module.scss +1 -1
- package/src/components/LeftNav/LeftNavTree.js +158 -0
- package/src/components/LeftNav/LeftNavTree.module.scss +163 -0
- package/src/components/NextPrevious/NextPrevious.js +24 -7
- package/src/util/NavItems.js +4 -0
- package/src/util/NavTree.js +19 -0
package/gatsby-node.mjs
CHANGED
|
@@ -103,9 +103,14 @@ export const createSchemaCustomization = ({ actions, schema }) => {
|
|
|
103
103
|
title: String
|
|
104
104
|
path: String!
|
|
105
105
|
}
|
|
106
|
+
type NavItemsSubLevelYaml {
|
|
107
|
+
title: String
|
|
108
|
+
path: String
|
|
109
|
+
pages: [NavItemsYamlPage]
|
|
110
|
+
}
|
|
106
111
|
type NavItemsYaml implements Node {
|
|
107
112
|
title: String!
|
|
108
|
-
pages: [
|
|
113
|
+
pages: [NavItemsSubLevelYaml]!
|
|
109
114
|
hasDivider: Boolean
|
|
110
115
|
}`,
|
|
111
116
|
schema.buildObjectType({
|
package/package.json
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, {
|
|
2
|
+
useContext,
|
|
3
|
+
useRef,
|
|
4
|
+
useEffect,
|
|
5
|
+
useCallback,
|
|
6
|
+
useState,
|
|
7
|
+
} from 'react';
|
|
2
8
|
import { SideNav, SideNavItems } from '@carbon/react';
|
|
3
9
|
import { useNavItems } from '../../util/NavItems';
|
|
4
10
|
|
|
@@ -9,6 +15,7 @@ import LeftNavResourceLinks from './ResourceLinks';
|
|
|
9
15
|
import LeftNavWrapper from './LeftNavWrapper';
|
|
10
16
|
import * as styles from './LeftNav.module.scss';
|
|
11
17
|
import useMetadata from '../../util/hooks/useMetadata';
|
|
18
|
+
import LeftNavTree from './LeftNavTree';
|
|
12
19
|
|
|
13
20
|
const LeftNav = (props) => {
|
|
14
21
|
const {
|
|
@@ -18,24 +25,57 @@ const LeftNav = (props) => {
|
|
|
18
25
|
toggleNavState,
|
|
19
26
|
} = useContext(NavContext);
|
|
20
27
|
|
|
28
|
+
const [isTreeView, setIsTreeView] = useState();
|
|
29
|
+
|
|
21
30
|
const sideNavRef = useRef();
|
|
22
31
|
const sideNavListRef = useRef();
|
|
23
32
|
|
|
33
|
+
const navItems = useNavItems();
|
|
34
|
+
|
|
35
|
+
const hasNestedLevels = useCallback(() => {
|
|
36
|
+
let nestedLevels = false;
|
|
37
|
+
|
|
38
|
+
navItems.forEach((navItem) => {
|
|
39
|
+
navItem.pages?.forEach((levelTwoNavItem) => {
|
|
40
|
+
if (levelTwoNavItem.pages && levelTwoNavItem.pages.length > 1) {
|
|
41
|
+
nestedLevels = true;
|
|
42
|
+
}
|
|
43
|
+
// if it is branch node with only one leaf node, convert it to a leaf node
|
|
44
|
+
else if (levelTwoNavItem.pages && levelTwoNavItem.pages.length) {
|
|
45
|
+
levelTwoNavItem.path = levelTwoNavItem.pages[0].path;
|
|
46
|
+
levelTwoNavItem.pages = null;
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
return nestedLevels;
|
|
51
|
+
}, [navItems]);
|
|
52
|
+
|
|
24
53
|
useEffect(() => {
|
|
25
|
-
|
|
26
|
-
}, []);
|
|
54
|
+
setIsTreeView(hasNestedLevels());
|
|
55
|
+
}, [navItems]);
|
|
27
56
|
|
|
28
57
|
useEffect(() => {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
58
|
+
if (!isTreeView) {
|
|
59
|
+
sideNavListRef.current =
|
|
60
|
+
sideNavRef.current.querySelector('.sidenav-list');
|
|
61
|
+
}
|
|
62
|
+
}, [isTreeView]);
|
|
63
|
+
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
if (!isTreeView) {
|
|
66
|
+
sideNavListRef.current.addEventListener('scroll', (e) => {
|
|
67
|
+
setLeftNavScrollTop(e.target.scrollTop);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}, [setLeftNavScrollTop, isTreeView]);
|
|
33
71
|
|
|
34
72
|
useEffect(() => {
|
|
35
|
-
if (
|
|
36
|
-
sideNavListRef
|
|
73
|
+
if (!isTreeView) {
|
|
74
|
+
if (leftNavScrollTop >= 0 && !sideNavListRef?.current.scrollTop) {
|
|
75
|
+
sideNavListRef.current.scrollTop = leftNavScrollTop;
|
|
76
|
+
}
|
|
37
77
|
}
|
|
38
|
-
}, [leftNavScrollTop]);
|
|
78
|
+
}, [leftNavScrollTop, isTreeView]);
|
|
39
79
|
|
|
40
80
|
const getLeftNavClassNames = () => {
|
|
41
81
|
if (props.theme === 'dark') {
|
|
@@ -44,7 +84,6 @@ const LeftNav = (props) => {
|
|
|
44
84
|
return styles.sideNavWhite;
|
|
45
85
|
};
|
|
46
86
|
|
|
47
|
-
const navItems = useNavItems();
|
|
48
87
|
const { navigationStyle } = useMetadata();
|
|
49
88
|
|
|
50
89
|
const closeSwitcher = () => {
|
|
@@ -57,25 +96,30 @@ const LeftNav = (props) => {
|
|
|
57
96
|
expanded={leftNavIsOpen}
|
|
58
97
|
onClick={closeSwitcher}
|
|
59
98
|
onKeyPress={closeSwitcher}>
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
{
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
99
|
+
{isTreeView ? (
|
|
100
|
+
<LeftNavTree items={navItems} theme={props.theme} />
|
|
101
|
+
) : (
|
|
102
|
+
<SideNav
|
|
103
|
+
ref={sideNavRef}
|
|
104
|
+
aria-label="Side navigation"
|
|
105
|
+
expanded={navigationStyle ? leftNavIsOpen : true}
|
|
106
|
+
defaultExpanded={!navigationStyle}
|
|
107
|
+
isPersistent={!navigationStyle}
|
|
108
|
+
className={getLeftNavClassNames()}>
|
|
109
|
+
<SideNavItems className="sidenav-list">
|
|
110
|
+
{typeof isTreeView !== 'undefined' &&
|
|
111
|
+
navItems.map((item, i) => (
|
|
112
|
+
<LeftNavItem
|
|
113
|
+
items={item.pages}
|
|
114
|
+
category={item.title}
|
|
115
|
+
key={i}
|
|
116
|
+
hasDivider={item.hasDivider}
|
|
117
|
+
/>
|
|
118
|
+
))}
|
|
119
|
+
<LeftNavResourceLinks />
|
|
120
|
+
</SideNavItems>
|
|
121
|
+
</SideNav>
|
|
122
|
+
)}
|
|
79
123
|
</LeftNavWrapper>
|
|
80
124
|
);
|
|
81
125
|
};
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import React, { memo, useCallback, useEffect, useState } from 'react';
|
|
2
|
+
import { useLocation } from '@reach/router';
|
|
3
|
+
import { Theme, TreeNode, TreeView } from '@carbon/react';
|
|
4
|
+
import { Link } from 'gatsby';
|
|
5
|
+
|
|
6
|
+
import cx from 'classnames';
|
|
7
|
+
import slugify from 'slugify';
|
|
8
|
+
import PropTypes from 'prop-types';
|
|
9
|
+
import * as styles from './LeftNavTree.module.scss';
|
|
10
|
+
import { dfs } from '../../util/NavTree';
|
|
11
|
+
|
|
12
|
+
import LeftNavResourceLinks from './ResourceLinks';
|
|
13
|
+
|
|
14
|
+
const LeftNavTree = ({ items, theme }) => {
|
|
15
|
+
const [itemNodes, setItemNodes] = useState([]);
|
|
16
|
+
const [treeActiveItem, setTreeActiveItem] = useState({});
|
|
17
|
+
const [activePath, setActivePath] = useState('');
|
|
18
|
+
const location = useLocation();
|
|
19
|
+
|
|
20
|
+
const themeValue = theme === 'dark' ? 'g100' : theme;
|
|
21
|
+
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
const newItemNodeArray = [];
|
|
24
|
+
// method to set isBranch value for branch nodes
|
|
25
|
+
const assignNodeType = (item) => {
|
|
26
|
+
// branch node with more than 1 leaf nodes
|
|
27
|
+
if (item.pages && item.pages.length > 1) {
|
|
28
|
+
item.isBranch = true;
|
|
29
|
+
item.pages.forEach((SubNavItem, i) => assignNodeType(SubNavItem));
|
|
30
|
+
}
|
|
31
|
+
// if it is branch node with only one leaf node, convert it to a leaf node
|
|
32
|
+
else if (item.pages && item.pages.length) {
|
|
33
|
+
item.path = item.pages[0].path;
|
|
34
|
+
item.pages = null;
|
|
35
|
+
}
|
|
36
|
+
return item;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
items.forEach((item) => {
|
|
40
|
+
newItemNodeArray.push(assignNodeType(item));
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// Create hierarchical, unique node ids for all nodes in the nav tree
|
|
44
|
+
dfs(newItemNodeArray, (evalNode) => {
|
|
45
|
+
if (!evalNode.parentNodeId) {
|
|
46
|
+
evalNode.parentNodeId = 'left_nav_tree';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Combine the parent's node id with the current node's id
|
|
50
|
+
const currentNodeId = evalNode.title
|
|
51
|
+
.toLocaleLowerCase()
|
|
52
|
+
.replace(/\s/g, '');
|
|
53
|
+
evalNode.id = `${evalNode.parentNodeId}_${currentNodeId}`;
|
|
54
|
+
|
|
55
|
+
// Set the parent node id of each child of this node to the newly generated id
|
|
56
|
+
evalNode.pages?.forEach((child) => {
|
|
57
|
+
child.parentNodeId = evalNode.id;
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
return false;
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
setItemNodes(newItemNodeArray);
|
|
64
|
+
}, [items]);
|
|
65
|
+
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
const stripTrailingSlash = (str) =>
|
|
68
|
+
str.endsWith('/') ? str.slice(0, -1) : str;
|
|
69
|
+
setActivePath(stripTrailingSlash(location.pathname));
|
|
70
|
+
}, [location.pathname]);
|
|
71
|
+
|
|
72
|
+
const getItemPath = (item) =>
|
|
73
|
+
item.path || slugify(item.title, { lower: true, strict: true });
|
|
74
|
+
|
|
75
|
+
const removeHashAndQuery = (path) => path?.split('?')?.[0]?.split('#')?.[0];
|
|
76
|
+
|
|
77
|
+
const isTreeNodeActive = useCallback(
|
|
78
|
+
(node) => getItemPath(node) === removeHashAndQuery(activePath),
|
|
79
|
+
[activePath]
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
const activeNode = dfs(itemNodes, isTreeNodeActive);
|
|
84
|
+
setTreeActiveItem(activeNode);
|
|
85
|
+
}, [isTreeNodeActive, itemNodes]);
|
|
86
|
+
|
|
87
|
+
const isTreeNodeExpanded = (node) =>
|
|
88
|
+
!!dfs([node], (evalNode) =>
|
|
89
|
+
evalNode.pages?.some((page) => page.id === treeActiveItem?.id)
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
function renderTree({ nodes }) {
|
|
93
|
+
if (!nodes) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
return nodes.map((node) => {
|
|
97
|
+
let label = node.title;
|
|
98
|
+
|
|
99
|
+
if (node.path) {
|
|
100
|
+
label = (
|
|
101
|
+
<Link
|
|
102
|
+
to={node.path}
|
|
103
|
+
className={styles.anchor}
|
|
104
|
+
// tabIndex={visible ? 0 : '-1'}
|
|
105
|
+
>
|
|
106
|
+
{node.title}
|
|
107
|
+
</Link>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<TreeNode
|
|
113
|
+
id={node.id}
|
|
114
|
+
key={node.id}
|
|
115
|
+
label={label}
|
|
116
|
+
value={node.title}
|
|
117
|
+
isExpanded={isTreeNodeExpanded(node)}
|
|
118
|
+
className={cx({
|
|
119
|
+
'cds--tree-node--active': node.id === treeActiveItem?.id,
|
|
120
|
+
'cds--tree-node--selected': node.id === treeActiveItem?.id,
|
|
121
|
+
[styles.divider]: node.hasDivider,
|
|
122
|
+
})}
|
|
123
|
+
onSelect={() => {
|
|
124
|
+
node.path && setTreeActiveItem(node);
|
|
125
|
+
}}>
|
|
126
|
+
{node.isBranch &&
|
|
127
|
+
renderTree({
|
|
128
|
+
nodes: node.pages,
|
|
129
|
+
})}
|
|
130
|
+
</TreeNode>
|
|
131
|
+
);
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return (
|
|
136
|
+
<Theme className={styles.container} theme={themeValue}>
|
|
137
|
+
<TreeView label="Side navigation" hideLabel>
|
|
138
|
+
{renderTree({ nodes: itemNodes })}
|
|
139
|
+
<LeftNavResourceLinks />
|
|
140
|
+
</TreeView>
|
|
141
|
+
</Theme>
|
|
142
|
+
);
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
LeftNavTree.propTypes = {
|
|
146
|
+
items: PropTypes.arrayOf(
|
|
147
|
+
PropTypes.shape({
|
|
148
|
+
title: PropTypes.string.isRequired,
|
|
149
|
+
path: PropTypes.string,
|
|
150
|
+
pages: PropTypes.array,
|
|
151
|
+
hasDivider: PropTypes.bool,
|
|
152
|
+
})
|
|
153
|
+
),
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const areEqual = (prevProps, nextProps) => true;
|
|
157
|
+
|
|
158
|
+
export default memo(LeftNavTree, areEqual);
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
@use '@carbon/react/scss/breakpoint' as breakpoint;
|
|
2
|
+
@use '@carbon/react/scss/spacing' as spacing;
|
|
3
|
+
@use '@carbon/react/scss/theme' as theme;
|
|
4
|
+
@use '@carbon/react/scss/type' as type;
|
|
5
|
+
@use '@carbon/react/scss/utilities/convert' as convert;
|
|
6
|
+
@use '@carbon/react/scss/zone';
|
|
7
|
+
|
|
8
|
+
.container {
|
|
9
|
+
height: 100%;
|
|
10
|
+
margin: spacing.$spacing-10 0;
|
|
11
|
+
background-color: theme.$background;
|
|
12
|
+
|
|
13
|
+
:global(.cds--tree) {
|
|
14
|
+
overflow: auto;
|
|
15
|
+
height: calc(100% - spacing.$spacing-10);
|
|
16
|
+
background-color: theme.$background;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
:global(.cds--tree-node) {
|
|
20
|
+
background-color: theme.$background;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// fix left alignment because we are hiding the left icon
|
|
24
|
+
:global(.cds--tree-leaf-node) {
|
|
25
|
+
padding-left: spacing.$spacing-05;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// label font and cursor overrides
|
|
29
|
+
:global(.cds--tree-node__label) {
|
|
30
|
+
position: relative;
|
|
31
|
+
cursor: pointer;
|
|
32
|
+
@include type.type-style('heading-01');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// override g10 selected background
|
|
36
|
+
:global(.cds--tree-node--selected > .cds--tree-node__label),
|
|
37
|
+
:global(.cds--tree-node--selected > .cds--tree-node__label::before) {
|
|
38
|
+
background-color: transparent;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// bold selected labels and make darker
|
|
42
|
+
:global(.cds--tree-node[aria-expanded='true'] > .cds--tree-node__label) {
|
|
43
|
+
color: theme.$text-primary;
|
|
44
|
+
@include type.type-style('heading-01');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// override g10 hover background
|
|
48
|
+
:global(.cds--tree-node__label:hover),
|
|
49
|
+
:global(.cds--tree-node--selected > .cds--tree-node__label:hover) {
|
|
50
|
+
background-color: theme.$layer-02;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// child labels have normal font weight
|
|
54
|
+
:global(.cds--tree-node__children .cds--tree-node__label) {
|
|
55
|
+
@include type.type-style('body-compact-01');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// move all div padding to the anchor
|
|
59
|
+
:global(.cds--tree-node:not(.cds--tree-parent-node) .cds--tree-node__label) {
|
|
60
|
+
padding-top: 0;
|
|
61
|
+
padding-bottom: 0;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// anchor links fill entire label box
|
|
65
|
+
.anchor {
|
|
66
|
+
width: calc(100% + spacing.$spacing-05);
|
|
67
|
+
padding: 0.375rem spacing.$spacing-05;
|
|
68
|
+
margin-left: -(spacing.$spacing-05);
|
|
69
|
+
color: theme.$text-secondary;
|
|
70
|
+
text-decoration: none;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// anchor links are darker on hover
|
|
74
|
+
.anchor:hover {
|
|
75
|
+
color: theme.$text-primary;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// nested anchor link
|
|
79
|
+
:global(.cds--tree-node__children) .anchor {
|
|
80
|
+
width: calc(100% + spacing.$spacing-07);
|
|
81
|
+
padding-left: spacing.$spacing-07;
|
|
82
|
+
margin-left: -(spacing.$spacing-07);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// have anchor selected match label selected
|
|
86
|
+
:global(.cds--tree-node--selected > .cds--tree-node__label) .anchor {
|
|
87
|
+
position: relative;
|
|
88
|
+
background: theme.$background-selected;
|
|
89
|
+
color: theme.$text-primary;
|
|
90
|
+
@include type.type-style('heading-01');
|
|
91
|
+
|
|
92
|
+
&::before {
|
|
93
|
+
position: absolute;
|
|
94
|
+
top: 0;
|
|
95
|
+
left: 0;
|
|
96
|
+
width: 0.25rem;
|
|
97
|
+
height: 100%;
|
|
98
|
+
background-color: theme.$border-interactive;
|
|
99
|
+
content: '';
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// nested 2 deep anchor link
|
|
104
|
+
:global(.cds--tree-node__children > li > .cds--tree-node__children) .anchor {
|
|
105
|
+
width: calc(100% + spacing.$spacing-09);
|
|
106
|
+
padding-left: spacing.$spacing-09;
|
|
107
|
+
margin-left: -(spacing.$spacing-09);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// expand toggle click target
|
|
111
|
+
:global(.cds--tree-parent-node__toggle) {
|
|
112
|
+
position: absolute;
|
|
113
|
+
top: 0;
|
|
114
|
+
right: 0;
|
|
115
|
+
display: block;
|
|
116
|
+
width: 100%;
|
|
117
|
+
height: 100%;
|
|
118
|
+
margin-top: 0;
|
|
119
|
+
margin-right: 0;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// custom icon
|
|
123
|
+
:global(.cds--tree-parent-node__toggle::after) {
|
|
124
|
+
position: absolute;
|
|
125
|
+
top: spacing.$spacing-03;
|
|
126
|
+
right: spacing.$spacing-05;
|
|
127
|
+
display: block;
|
|
128
|
+
width: convert.rem(16px);
|
|
129
|
+
height: convert.rem(16px);
|
|
130
|
+
background-color: theme.$icon-primary;
|
|
131
|
+
mask: url("data:image/svg+xml,%3Csvg focusable='false' preserveAspectRatio='xMidYMid meet' xmlns='http://www.w3.org/2000/svg' fill='currentColor' width='16' height='16' viewBox='0 0 16 16' aria-hidden='true' class='cds--tree-node__icon'%3E%3Cpath d='M8 11L3 6 3.7 5.3 8 9.6 12.3 5.3 13 6z'%3E%3C/path%3E%3C/svg%3E%0A");
|
|
132
|
+
content: '';
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// hide original icon
|
|
136
|
+
:global(.cds--tree-parent-node__toggle-icon) {
|
|
137
|
+
display: none;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// parent tree node open
|
|
141
|
+
:global(.cds--tree-node[aria-expanded='true']) {
|
|
142
|
+
> * > :global(.cds--tree-parent-node__toggle::after) {
|
|
143
|
+
transform: rotate(180deg);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// divider styles
|
|
148
|
+
.divider {
|
|
149
|
+
position: relative;
|
|
150
|
+
padding-bottom: spacing.$spacing-05;
|
|
151
|
+
margin-bottom: spacing.$spacing-05;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.divider::after {
|
|
155
|
+
content: '';
|
|
156
|
+
position: absolute;
|
|
157
|
+
left: spacing.$spacing-05;
|
|
158
|
+
right: spacing.$spacing-05;
|
|
159
|
+
bottom: 0;
|
|
160
|
+
height: 1px;
|
|
161
|
+
background-color: var(--cds-ui-03, #e0e0e0);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
@@ -17,6 +17,10 @@ const useNavigationList = () => {
|
|
|
17
17
|
pages {
|
|
18
18
|
title
|
|
19
19
|
path
|
|
20
|
+
pages {
|
|
21
|
+
path
|
|
22
|
+
title
|
|
23
|
+
}
|
|
20
24
|
}
|
|
21
25
|
}
|
|
22
26
|
}
|
|
@@ -28,12 +32,25 @@ const useNavigationList = () => {
|
|
|
28
32
|
`);
|
|
29
33
|
|
|
30
34
|
return [
|
|
31
|
-
edges.flatMap(({ node }) =>
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
edges.flatMap(({ node }) => {
|
|
36
|
+
const navArr = [];
|
|
37
|
+
node.pages.forEach((page) => {
|
|
38
|
+
if (page.pages?.length) {
|
|
39
|
+
page.pages.forEach((sublevelPage) => {
|
|
40
|
+
navArr.push({
|
|
41
|
+
...sublevelPage,
|
|
42
|
+
category: `${node.title}: ${page.title}`,
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
} else {
|
|
46
|
+
navArr.push({
|
|
47
|
+
...page,
|
|
48
|
+
category: node.title,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
return navArr;
|
|
53
|
+
}),
|
|
37
54
|
pathPrefix,
|
|
38
55
|
];
|
|
39
56
|
};
|
|
@@ -75,7 +92,7 @@ const useNavigationItems = ({ tabs, location }) => {
|
|
|
75
92
|
: unPrefixedPathname.replace(/\/$/, ''); // removes the last syalash
|
|
76
93
|
|
|
77
94
|
const navIndex = navigationList.findIndex((item) =>
|
|
78
|
-
item.path
|
|
95
|
+
item.path?.includes(currentNavigationItem)
|
|
79
96
|
);
|
|
80
97
|
|
|
81
98
|
return {
|
package/src/util/NavItems.js
CHANGED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Iterates through all nodes in a tree and stops if a returnFunction() condition is met
|
|
3
|
+
* @param {{[key]: any, pages: object[]}[]} nodes tree
|
|
4
|
+
* @returns {void}
|
|
5
|
+
*/
|
|
6
|
+
export const dfs = (nodes, returnFunction) => {
|
|
7
|
+
let node;
|
|
8
|
+
const stack = [];
|
|
9
|
+
stack.push(...nodes);
|
|
10
|
+
while (stack.length > 0) {
|
|
11
|
+
node = stack.pop();
|
|
12
|
+
if (returnFunction(node)) {
|
|
13
|
+
return node;
|
|
14
|
+
}
|
|
15
|
+
node.pages?.forEach((item) => {
|
|
16
|
+
stack.push(item);
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
};
|