@carbon-labs/react-ui-shell 0.15.0 → 0.17.0
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/README.md +79 -0
- package/es/components/SideNav.d.ts +4 -0
- package/es/components/SideNav.js +134 -96
- package/es/components/SideNavItems.d.ts +5 -0
- package/es/components/SideNavItems.js +25 -5
- package/es/components/SideNavMenu.js +90 -52
- package/es/components/SideNavMenuItem.js +9 -5
- package/lib/components/SideNav.d.ts +4 -0
- package/lib/components/SideNav.js +132 -94
- package/lib/components/SideNavItems.d.ts +5 -0
- package/lib/components/SideNavItems.js +24 -4
- package/lib/components/SideNavMenu.js +89 -51
- package/lib/components/SideNavMenuItem.js +8 -4
- package/package.json +2 -2
- package/scss/styles/_side-nav.scss +15 -1
|
@@ -30,16 +30,21 @@ const SideNavMenu = /*#__PURE__*/React.forwardRef(function SideNavMenu(_ref, ref
|
|
|
30
30
|
large = false,
|
|
31
31
|
renderIcon: IconElement,
|
|
32
32
|
isSideNavExpanded,
|
|
33
|
-
tabIndex,
|
|
34
33
|
title
|
|
35
34
|
} = _ref;
|
|
36
35
|
const depth = propDepth;
|
|
37
36
|
const {
|
|
38
|
-
|
|
37
|
+
isTreeview,
|
|
38
|
+
expanded,
|
|
39
|
+
navType,
|
|
40
|
+
isRail,
|
|
41
|
+
setIsTreeview
|
|
39
42
|
} = React.useContext(SideNav.SideNavContext);
|
|
43
|
+
const sideNavExpanded = expanded;
|
|
40
44
|
const prefix = usePrefix.usePrefix();
|
|
41
45
|
const [isExpanded, setIsExpanded] = React.useState(defaultExpanded);
|
|
42
46
|
const [active, setActive] = React.useState(isActive);
|
|
47
|
+
const firstLink = React.useRef(null);
|
|
43
48
|
const [prevExpanded, setPrevExpanded] = React.useState(defaultExpanded);
|
|
44
49
|
const className = index.default({
|
|
45
50
|
[`${prefix}--side-nav__item`]: true,
|
|
@@ -82,23 +87,35 @@ const SideNavMenu = /*#__PURE__*/React.forwardRef(function SideNavMenu(_ref, ref
|
|
|
82
87
|
return child;
|
|
83
88
|
});
|
|
84
89
|
React.useEffect(() => {
|
|
85
|
-
if (
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
+
if (navType == SideNav.SIDE_NAV_TYPE.PANEL) {
|
|
91
|
+
// grab first link to redirect if clicked when not expanded
|
|
92
|
+
if (!firstLink?.current && listRef?.current) {
|
|
93
|
+
const firstLinkElement = listRef.current.querySelector(`.${prefix}--side-nav__menu-item a`);
|
|
94
|
+
firstLink.current = firstLinkElement?.getAttribute('href') ?? '';
|
|
90
95
|
}
|
|
96
|
+
}
|
|
97
|
+
if (depth === 0) return;
|
|
98
|
+
|
|
99
|
+
// if depth is more than 0, that means its nested, thus we set treeview mode
|
|
100
|
+
setIsTreeview?.(true);
|
|
101
|
+
if (isTreeview) {
|
|
102
|
+
const calcButtonOffset = () => {
|
|
103
|
+
// menu with icon
|
|
104
|
+
if (children && IconElement) {
|
|
105
|
+
return depth + 3;
|
|
106
|
+
}
|
|
91
107
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
108
|
+
// menu without icon
|
|
109
|
+
if (children) {
|
|
110
|
+
return depth * 4;
|
|
111
|
+
}
|
|
112
|
+
return depth;
|
|
113
|
+
};
|
|
114
|
+
if (buttonRef.current) {
|
|
115
|
+
buttonRef.current.style.paddingLeft = `${calcButtonOffset()}rem`;
|
|
95
116
|
}
|
|
96
|
-
return depth;
|
|
97
|
-
};
|
|
98
|
-
if (buttonRef.current) {
|
|
99
|
-
buttonRef.current.style.paddingLeft = `${calcButtonOffset()}rem`;
|
|
100
117
|
}
|
|
101
|
-
}, []);
|
|
118
|
+
}, [isTreeview]);
|
|
102
119
|
|
|
103
120
|
/**
|
|
104
121
|
* Returns the parent SideNavMenu, if node is actually inside one.
|
|
@@ -114,56 +131,71 @@ const SideNavMenu = /*#__PURE__*/React.forwardRef(function SideNavMenu(_ref, ref
|
|
|
114
131
|
if (match.match(event, keys.Escape)) {
|
|
115
132
|
setIsExpanded(false);
|
|
116
133
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
event.
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
134
|
+
if (isTreeview) {
|
|
135
|
+
const node = event.target;
|
|
136
|
+
const isMenu = node.hasAttribute('aria-expanded');
|
|
137
|
+
const isExpanded = node.getAttribute('aria-expanded');
|
|
138
|
+
const parent = parentSideNavMenu(node);
|
|
139
|
+
if (match.match(event, keys.ArrowLeft)) {
|
|
140
|
+
event.stopPropagation();
|
|
141
|
+
if (isMenu) {
|
|
142
|
+
// collapse menu
|
|
143
|
+
if (isExpanded == 'true') {
|
|
144
|
+
setIsExpanded(false);
|
|
127
145
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
146
|
+
// go to previous level's side nav menu button
|
|
147
|
+
} else {
|
|
148
|
+
// since we're in a menu, it finds its own <li>, we go up one more
|
|
149
|
+
const previousMenu = parentSideNavMenu(parent);
|
|
150
|
+
const button = previousMenu.querySelector('button');
|
|
151
|
+
button.tabIndex = 0;
|
|
152
|
+
button?.focus();
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// go to side nav menu button
|
|
156
|
+
} else if (parent) {
|
|
157
|
+
const button = parent.querySelector('button');
|
|
133
158
|
button.tabIndex = 0;
|
|
134
159
|
button?.focus();
|
|
135
160
|
}
|
|
136
|
-
|
|
137
|
-
// go to side nav menu button
|
|
138
|
-
} else if (parent) {
|
|
139
|
-
const button = parent.querySelector('button');
|
|
140
|
-
button.tabIndex = 0;
|
|
141
|
-
button?.focus();
|
|
142
161
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
event.stopPropagation();
|
|
162
|
+
if (match.match(event, keys.ArrowRight)) {
|
|
163
|
+
event.stopPropagation();
|
|
146
164
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
165
|
+
// expand menu
|
|
166
|
+
if (isMenu) {
|
|
167
|
+
setIsExpanded(true);
|
|
150
168
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
169
|
+
// if already expanded, focus on first element
|
|
170
|
+
if (isExpanded == 'true') {
|
|
171
|
+
let nextNode = node.nextElementSibling?.querySelector('a, button');
|
|
172
|
+
if (nextNode) {
|
|
173
|
+
nextNode.tabIndex = 0;
|
|
174
|
+
nextNode.focus();
|
|
175
|
+
}
|
|
157
176
|
}
|
|
158
177
|
}
|
|
159
178
|
}
|
|
160
179
|
}
|
|
161
180
|
}
|
|
181
|
+
|
|
182
|
+
// save expanded state before SideNav collapse
|
|
183
|
+
const [lastExpandedState, setLastExpandedState] = React.useState(isExpanded);
|
|
184
|
+
|
|
185
|
+
// reset when SideNav is panel
|
|
186
|
+
React.useEffect(() => {
|
|
187
|
+
if (navType == SideNav.SIDE_NAV_TYPE.PANEL && !sideNavExpanded) {
|
|
188
|
+
setLastExpandedState(isExpanded);
|
|
189
|
+
setIsExpanded(false);
|
|
190
|
+
} else {
|
|
191
|
+
setIsExpanded(lastExpandedState);
|
|
192
|
+
}
|
|
193
|
+
}, [sideNavExpanded]);
|
|
162
194
|
return (
|
|
163
195
|
/*#__PURE__*/
|
|
164
196
|
// eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
|
|
165
197
|
React.createElement("li", {
|
|
166
|
-
role:
|
|
198
|
+
role: isTreeview ? 'treeitem' : undefined,
|
|
167
199
|
"aria-expanded": isExpanded,
|
|
168
200
|
className: className,
|
|
169
201
|
ref: listRef,
|
|
@@ -172,11 +204,17 @@ const SideNavMenu = /*#__PURE__*/React.forwardRef(function SideNavMenu(_ref, ref
|
|
|
172
204
|
"aria-expanded": isExpanded,
|
|
173
205
|
className: buttonClassName,
|
|
174
206
|
onClick: () => {
|
|
175
|
-
|
|
207
|
+
// only when sidenav is panel view
|
|
208
|
+
if (navType == SideNav.SIDE_NAV_TYPE.PANEL && !isExpanded && firstLink.current && !sideNavExpanded) {
|
|
209
|
+
window.location.href = firstLink.current;
|
|
210
|
+
} else {
|
|
211
|
+
setIsExpanded(!isExpanded);
|
|
212
|
+
setLastExpandedState(!isExpanded);
|
|
213
|
+
}
|
|
176
214
|
},
|
|
177
215
|
ref: menuRef,
|
|
178
216
|
type: "button",
|
|
179
|
-
tabIndex: -1
|
|
217
|
+
tabIndex: isTreeview ? -1 : 0
|
|
180
218
|
}, IconElement && /*#__PURE__*/React.createElement(react.SideNavIcon, null, /*#__PURE__*/React.createElement(IconElement, null)), /*#__PURE__*/React.createElement("span", {
|
|
181
219
|
className: `${prefix}--side-nav__submenu-title`
|
|
182
220
|
}, title), /*#__PURE__*/React.createElement(react.SideNavIcon, {
|
|
@@ -15,6 +15,7 @@ var react = require('@carbon/react');
|
|
|
15
15
|
var Link = require('./Link.js');
|
|
16
16
|
var usePrefix = require('../internal/usePrefix.js');
|
|
17
17
|
var useMergedRefs = require('../internal/useMergedRefs.js');
|
|
18
|
+
var SideNav = require('./SideNav.js');
|
|
18
19
|
|
|
19
20
|
const SideNavMenuItem = /*#__PURE__*/React.forwardRef(function SideNavMenuItem(props, ref) {
|
|
20
21
|
const prefix = usePrefix.usePrefix();
|
|
@@ -26,6 +27,9 @@ const SideNavMenuItem = /*#__PURE__*/React.forwardRef(function SideNavMenuItem(p
|
|
|
26
27
|
isActive,
|
|
27
28
|
...rest
|
|
28
29
|
} = props;
|
|
30
|
+
const {
|
|
31
|
+
isTreeview
|
|
32
|
+
} = React.useContext(SideNav.SideNavContext);
|
|
29
33
|
const className = index.default(`${prefix}--side-nav__menu-item`, customClassName);
|
|
30
34
|
const depth = propDepth;
|
|
31
35
|
const linkClassName = index.default({
|
|
@@ -41,14 +45,14 @@ const SideNavMenuItem = /*#__PURE__*/React.forwardRef(function SideNavMenuItem(p
|
|
|
41
45
|
if (linkRef.current) {
|
|
42
46
|
linkRef.current.style.paddingLeft = `${calcLinkOffset()}rem`;
|
|
43
47
|
}
|
|
44
|
-
}, []);
|
|
48
|
+
}, [isTreeview]);
|
|
45
49
|
return /*#__PURE__*/React.createElement("li", {
|
|
46
|
-
role: "treeitem",
|
|
47
|
-
"aria-selected": isActive ? 'true' : 'false',
|
|
48
50
|
className: className
|
|
49
51
|
}, /*#__PURE__*/React.createElement(Component, _rollupPluginBabelHelpers.extends({}, rest, {
|
|
52
|
+
"aria-selected": isActive ? 'true' : 'false',
|
|
53
|
+
role: isTreeview ? 'treeitem' : undefined,
|
|
50
54
|
className: linkClassName,
|
|
51
|
-
tabIndex: -1,
|
|
55
|
+
tabIndex: isTreeview ? -1 : 0,
|
|
52
56
|
ref: itemRef
|
|
53
57
|
}), /*#__PURE__*/React.createElement(react.SideNavLinkText, null, children)));
|
|
54
58
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@carbon-labs/react-ui-shell",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public",
|
|
6
6
|
"provenance": true
|
|
@@ -33,5 +33,5 @@
|
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"@ibm/telemetry-js": "^1.9.1"
|
|
35
35
|
},
|
|
36
|
-
"gitHead": "
|
|
36
|
+
"gitHead": "b9ed739e909accebbb96349c611213832b779c84"
|
|
37
37
|
}
|
|
@@ -48,12 +48,20 @@ div:has(.#{$prefix}--header)
|
|
|
48
48
|
font-weight: 600;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
.#{$prefix}--side-nav__icon > svg,
|
|
52
|
+
.#{$prefix}--side-nav__submenu-chevron > svg {
|
|
53
|
+
fill: $icon-primary;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.#{$prefix}--side-nav__link:hover,
|
|
58
|
+
.#{$prefix}--side-nav__submenu:hover {
|
|
59
|
+
.#{$prefix}--side-nav__icon > svg,
|
|
51
60
|
.#{$prefix}--side-nav__submenu-chevron > svg {
|
|
52
61
|
fill: $icon-primary;
|
|
53
62
|
}
|
|
54
63
|
}
|
|
55
64
|
|
|
56
|
-
//----------------------------------------------------------------------------
|
|
57
65
|
// Side-nav Panel
|
|
58
66
|
//----------------------------------------------------------------------------
|
|
59
67
|
.#{$prefix}--side-nav--panel {
|
|
@@ -63,6 +71,12 @@ div:has(.#{$prefix}--header)
|
|
|
63
71
|
margin-inline-end: $spacing-05;
|
|
64
72
|
}
|
|
65
73
|
|
|
74
|
+
.#{$prefix}--side-nav__item.#{$prefix}--side-nav__link:hover {
|
|
75
|
+
.#{$prefix}--side-nav__icon > svg {
|
|
76
|
+
fill: $icon-primary;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
66
80
|
.#{$prefix}--side-nav__item.#{$prefix}--side-nav__item--icon
|
|
67
81
|
a.#{$prefix}--side-nav__link {
|
|
68
82
|
padding-inline-start: $spacing-10;
|