@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
package/README.md
CHANGED
|
@@ -1,5 +1,84 @@
|
|
|
1
1
|
# @carbon-labs/react-ui-shell
|
|
2
2
|
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+
|
|
6
|
+
The `@carbon-labs/react-ui-shell` package extends UI Shell components from
|
|
7
|
+
`@carbon/react`, providing additional enhancements while maintaining
|
|
8
|
+
compatibility.
|
|
9
|
+
|
|
10
|
+
## 📦 Getting started
|
|
11
|
+
|
|
12
|
+
To install `@carbon-labs/react-ui-shell` in your project, you will need to run
|
|
13
|
+
the following command using [npm](https://www.npmjs.com/):
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install -S @carbon-labs/react-ui-shell @carbon/react
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
If you prefer [Yarn](https://yarnpkg.com/en/), use the following command
|
|
20
|
+
instead:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
yarn add @carbon-labs/react-ui-shell @carbon/react
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## ⚡ Usage
|
|
27
|
+
|
|
28
|
+
To use this package you will need to import components from both `@carbon/react`
|
|
29
|
+
and `@carbon-labs/react-ui-shell`o compose the UI Shell. The following
|
|
30
|
+
components are provided by `@carbon-labs/react-ui-shell`:
|
|
31
|
+
|
|
32
|
+
- `SideNav`
|
|
33
|
+
- `SideNavItems`
|
|
34
|
+
- `SideNavMenu`
|
|
35
|
+
- `SideNavMenuItem`
|
|
36
|
+
- `HeaderPanel`
|
|
37
|
+
|
|
38
|
+
```jsx
|
|
39
|
+
import { SideNav } from '@carbon-labs/react-ui-shell/es/index';
|
|
40
|
+
import { Header } from '@carbon/react';
|
|
41
|
+
|
|
42
|
+
function MyComponent() {
|
|
43
|
+
return (
|
|
44
|
+
<Header>
|
|
45
|
+
<SideNav>...</SideNav>>
|
|
46
|
+
</Header>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### SCSS
|
|
52
|
+
|
|
53
|
+
Import the styles from `@carbon/react` and `@carbon-labs/react-ui-shell` in your
|
|
54
|
+
stylesheet:
|
|
55
|
+
|
|
56
|
+
```scss
|
|
57
|
+
@use '@carbon/react' with (
|
|
58
|
+
$font-path: '@ibm/plex'
|
|
59
|
+
);
|
|
60
|
+
@use '@carbon-labs/react-ui-shell/scss/ui-shell';
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Storybook
|
|
64
|
+
|
|
65
|
+
You can explore the available components, see different configuration options,
|
|
66
|
+
and learn how to compose them in
|
|
67
|
+
[Storybook](https://labs.carbondesignsystem.com/?path=/docs/react_components-uishell--overview).
|
|
68
|
+
|
|
69
|
+
## 🙌 Contributing
|
|
70
|
+
|
|
71
|
+
Want to contribute to `@carbon-labs`? Read through the Carbon Labs
|
|
72
|
+
[contribution section](https://pages.github.ibm.com/carbon/ibm-products/contributing/carbon-labs/#carbon-labs-in-code)
|
|
73
|
+
before diving into our developer guide:
|
|
74
|
+
|
|
75
|
+
- [Developer Guide](https://github.com/carbon-design-system/carbon-labs/blob/main/docs/developing.md)
|
|
76
|
+
|
|
77
|
+
## 📝 License
|
|
78
|
+
|
|
79
|
+
Licensed under the
|
|
80
|
+
[Apache 2.0 License](https://github.com/carbon-design-system/carbon-labs/blob/main/LICENSE).
|
|
81
|
+
|
|
3
82
|
## <picture><source height="20" width="20" media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/ibm-telemetry/telemetry-js/main/docs/images/ibm-telemetry-dark.svg"><source height="20" width="20" media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/ibm-telemetry/telemetry-js/main/docs/images/ibm-telemetry-light.svg"><img height="20" width="20" alt="IBM Telemetry" src="https://raw.githubusercontent.com/ibm-telemetry/telemetry-js/main/docs/images/ibm-telemetry-light.svg"></picture> IBM Telemetry
|
|
4
83
|
|
|
5
84
|
This package uses IBM Telemetry to collect de-identified and anonymized metrics
|
|
@@ -34,10 +34,14 @@ export interface SideNavProps extends ComponentProps<'nav'>, TranslateWithId<Tra
|
|
|
34
34
|
isCollapsible?: boolean;
|
|
35
35
|
hideOverlay?: boolean;
|
|
36
36
|
navType: SIDE_NAV_TYPE;
|
|
37
|
+
isTreeview: boolean;
|
|
37
38
|
}
|
|
38
39
|
interface SideNavContextData {
|
|
40
|
+
expanded?: boolean;
|
|
39
41
|
isRail?: boolean;
|
|
40
42
|
navType?: SIDE_NAV_TYPE;
|
|
43
|
+
isTreeview?: boolean;
|
|
44
|
+
setIsTreeview?: (value: boolean) => void;
|
|
41
45
|
}
|
|
42
46
|
export declare const SideNavContext: React.Context<SideNavContextData>;
|
|
43
47
|
export declare const SideNav: React.ForwardRefExoticComponent<Omit<SideNavProps, "ref"> & React.RefAttributes<HTMLElement>>;
|
package/es/components/SideNav.js
CHANGED
|
@@ -6,13 +6,13 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { extends as _extends } from '../_virtual/_rollupPluginBabelHelpers.js';
|
|
9
|
-
import React, { createContext, useRef, isValidElement, useEffect } from 'react';
|
|
9
|
+
import React, { createContext, useState, useRef, isValidElement, useEffect } from 'react';
|
|
10
10
|
import cx from '../_virtual/index.js';
|
|
11
11
|
import PropTypes from 'prop-types';
|
|
12
12
|
import { AriaLabelPropType } from '../prop-types/AriaPropTypes.js';
|
|
13
13
|
import { CARBON_SIDENAV_ITEMS } from './_utils.js';
|
|
14
14
|
import { usePrefix } from '../internal/usePrefix.js';
|
|
15
|
-
import { ArrowUp, ArrowDown, Home, End,
|
|
15
|
+
import { Escape, ArrowUp, ArrowDown, Home, End, Tab } from '../internal/keyboard/keys.js';
|
|
16
16
|
import { match, matches } from '../internal/keyboard/match.js';
|
|
17
17
|
import { useMergedRefs } from '../internal/useMergedRefs.js';
|
|
18
18
|
import { useWindowEvent } from '../internal/useEvent.js';
|
|
@@ -52,6 +52,7 @@ function SideNavRenderFunction(_ref, ref) {
|
|
|
52
52
|
isFixedNav = false,
|
|
53
53
|
isRail,
|
|
54
54
|
isPersistent = true,
|
|
55
|
+
isTreeview: isTreeviewProp,
|
|
55
56
|
navType = SIDE_NAV_TYPE.DEFAULT,
|
|
56
57
|
addFocusListeners = true,
|
|
57
58
|
addMouseListeners = true,
|
|
@@ -63,6 +64,7 @@ function SideNavRenderFunction(_ref, ref) {
|
|
|
63
64
|
translateWithId: t = defaultTranslateWithId,
|
|
64
65
|
...other
|
|
65
66
|
} = _ref;
|
|
67
|
+
const [internalIsTreeview, setInternalIsTreeview] = useState(isTreeviewProp ?? false);
|
|
66
68
|
const prefix = usePrefix();
|
|
67
69
|
const {
|
|
68
70
|
current: controlled
|
|
@@ -114,7 +116,10 @@ function SideNavRenderFunction(_ref, ref) {
|
|
|
114
116
|
// avoid spreading `isSideNavExpanded` to non-Carbon UI Shell children
|
|
115
117
|
return /*#__PURE__*/React.cloneElement(childJsxElement, {
|
|
116
118
|
...(CARBON_SIDENAV_ITEMS.includes(childJsxElement.type?.displayName ?? childJsxElement.type?.name) ? {
|
|
117
|
-
isSideNavExpanded: currentExpansionState
|
|
119
|
+
isSideNavExpanded: currentExpansionState,
|
|
120
|
+
...(childJsxElement.type?.displayName === 'SideNavItems' && {
|
|
121
|
+
accessibilityLabel: accessibilityLabel
|
|
122
|
+
})
|
|
118
123
|
} : {})
|
|
119
124
|
});
|
|
120
125
|
}
|
|
@@ -123,26 +128,28 @@ function SideNavRenderFunction(_ref, ref) {
|
|
|
123
128
|
const eventHandlers = {};
|
|
124
129
|
const treeWalkerRef = useRef(null);
|
|
125
130
|
useEffect(() => {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
131
|
+
if (internalIsTreeview) {
|
|
132
|
+
treeWalkerRef.current = treeWalkerRef.current ?? document.createTreeWalker(sideNavRef?.current, NodeFilter.SHOW_ELEMENT, {
|
|
133
|
+
acceptNode: function (node) {
|
|
134
|
+
if (!(node instanceof Element)) {
|
|
135
|
+
return NodeFilter.FILTER_SKIP;
|
|
136
|
+
}
|
|
137
|
+
if (node.classList.contains(`${prefix}--side-nav__divider`)) {
|
|
138
|
+
return NodeFilter.FILTER_REJECT;
|
|
139
|
+
}
|
|
140
|
+
if (node.matches(`li.${prefix}--side-nav__item`) || node.matches(`li.${prefix}--side-nav__menu-item`)) {
|
|
141
|
+
return NodeFilter.FILTER_ACCEPT;
|
|
142
|
+
}
|
|
129
143
|
return NodeFilter.FILTER_SKIP;
|
|
130
144
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}
|
|
137
|
-
return NodeFilter.FILTER_SKIP;
|
|
145
|
+
});
|
|
146
|
+
resetNodeTabIndices();
|
|
147
|
+
const firstElement = sideNavRef?.current?.querySelector('a, button');
|
|
148
|
+
if (firstElement) {
|
|
149
|
+
firstElement.tabIndex = 0;
|
|
138
150
|
}
|
|
139
|
-
});
|
|
140
|
-
resetNodeTabIndices();
|
|
141
|
-
const firstElement = sideNavRef?.current?.querySelector('a, button');
|
|
142
|
-
if (firstElement) {
|
|
143
|
-
firstElement.tabIndex = 0;
|
|
144
151
|
}
|
|
145
|
-
}, [prefix]);
|
|
152
|
+
}, [prefix, internalIsTreeview]);
|
|
146
153
|
|
|
147
154
|
/**
|
|
148
155
|
* Returns the parent SideNavMenu, if node is actually inside one.
|
|
@@ -174,98 +181,111 @@ function SideNavRenderFunction(_ref, ref) {
|
|
|
174
181
|
}
|
|
175
182
|
};
|
|
176
183
|
eventHandlers.onKeyDown = event => {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
// @ts-ignore - `matches` doesn't like the object syntax without missing properties
|
|
184
|
-
{
|
|
185
|
-
code: 'KeyA'
|
|
186
|
-
}])) {
|
|
187
|
-
event.preventDefault();
|
|
188
|
-
}
|
|
189
|
-
treeWalker.currentNode = event.target.closest(`li`) ?? treeWalker?.currentNode;
|
|
190
|
-
let nextFocusNode = null;
|
|
191
|
-
if (match(event, ArrowUp)) {
|
|
192
|
-
const parentNode = parentSideNavMenu(treeWalker.currentNode);
|
|
193
|
-
let previousSideNavMenu = parentNode?.previousElementSibling;
|
|
194
|
-
|
|
195
|
-
// skip the divider
|
|
196
|
-
if (previousSideNavMenu?.classList.contains(`${prefix}--side-nav__divider`)) {
|
|
197
|
-
previousSideNavMenu = previousSideNavMenu?.previousElementSibling;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// when previous sibling is open, go to its last item
|
|
201
|
-
if (previousSideNavMenu?.getAttribute('aria-expanded') == 'true') {
|
|
202
|
-
nextFocusNode = treeWalker.previousNode();
|
|
203
|
-
} else {
|
|
204
|
-
nextFocusNode = treeWalker.previousSibling();
|
|
205
|
-
|
|
206
|
-
// first item in the menu, go back up to SideNavMenu button
|
|
207
|
-
if (nextFocusNode == null) {
|
|
208
|
-
nextFocusNode = parentNode;
|
|
184
|
+
// close menu
|
|
185
|
+
if (match(event, Escape)) {
|
|
186
|
+
if (expanded && !isFixedNav) {
|
|
187
|
+
resetNodeTabIndices();
|
|
188
|
+
if (onSideNavBlur) {
|
|
189
|
+
onSideNavBlur();
|
|
209
190
|
}
|
|
210
191
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
nextFocusNode = treeWalker.nextSibling();
|
|
215
|
-
} else {
|
|
216
|
-
nextFocusNode = treeWalker.nextNode();
|
|
192
|
+
handleToggle(event, false);
|
|
193
|
+
if (href) {
|
|
194
|
+
window.location.href = href;
|
|
217
195
|
}
|
|
218
196
|
}
|
|
219
197
|
|
|
220
|
-
//
|
|
221
|
-
if (
|
|
222
|
-
|
|
223
|
-
|
|
198
|
+
// Treeview keyboard navigation
|
|
199
|
+
if (treeWalkerRef?.current && internalIsTreeview) {
|
|
200
|
+
const treeWalker = treeWalkerRef.current;
|
|
201
|
+
event.stopPropagation();
|
|
202
|
+
|
|
203
|
+
// stops page from scrolling
|
|
204
|
+
if (matches(event, [ArrowUp, ArrowDown, Home, End,
|
|
205
|
+
// @ts-ignore - `matches` doesn't like the object syntax without missing properties
|
|
206
|
+
{
|
|
207
|
+
code: 'KeyA'
|
|
208
|
+
}])) {
|
|
209
|
+
event.preventDefault();
|
|
224
210
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
211
|
+
treeWalker.currentNode = event.target.closest(`li`) ?? treeWalker?.currentNode;
|
|
212
|
+
let nextFocusNode = null;
|
|
213
|
+
if (match(event, ArrowUp)) {
|
|
214
|
+
const parentNode = parentSideNavMenu(treeWalker.currentNode);
|
|
215
|
+
let previousSideNavMenu = parentNode?.previousElementSibling;
|
|
216
|
+
|
|
217
|
+
// skip the divider
|
|
218
|
+
if (previousSideNavMenu?.classList.contains(`${prefix}--side-nav__divider`)) {
|
|
219
|
+
previousSideNavMenu = previousSideNavMenu?.previousElementSibling;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// when previous sibling is open, go to its last item
|
|
223
|
+
if (previousSideNavMenu?.getAttribute('aria-expanded') == 'true') {
|
|
224
|
+
const allItems = previousSideNavMenu.querySelectorAll(`.${prefix}--side-nav__item`);
|
|
225
|
+
const lastMenu = allItems[allItems.length - 1];
|
|
226
|
+
if (lastMenu && lastMenu.getAttribute('aria-expanded') == 'false') {
|
|
227
|
+
nextFocusNode = lastMenu;
|
|
228
|
+
} else {
|
|
229
|
+
nextFocusNode = treeWalker.previousNode();
|
|
230
|
+
}
|
|
231
|
+
} else {
|
|
232
|
+
nextFocusNode = treeWalker.previousSibling();
|
|
233
|
+
|
|
234
|
+
// first item in the menu, go back up to SideNavMenu button
|
|
235
|
+
if (nextFocusNode == null) {
|
|
236
|
+
nextFocusNode = parentNode;
|
|
237
|
+
}
|
|
231
238
|
}
|
|
232
239
|
}
|
|
233
|
-
if (match(event,
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
node.tabIndex = 0;
|
|
240
|
-
node?.focus();
|
|
240
|
+
if (match(event, ArrowDown)) {
|
|
241
|
+
if (treeWalker.currentNode.getAttribute('aria-expanded') == 'false') {
|
|
242
|
+
nextFocusNode = treeWalker.nextSibling();
|
|
243
|
+
if (!nextFocusNode) {
|
|
244
|
+
const parent = parentSideNavMenu(treeWalker.currentNode);
|
|
245
|
+
nextFocusNode = parent?.nextElementSibling;
|
|
241
246
|
}
|
|
247
|
+
} else {
|
|
248
|
+
nextFocusNode = treeWalker.nextNode();
|
|
242
249
|
}
|
|
243
250
|
}
|
|
244
|
-
}
|
|
245
251
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
252
|
+
// Home/End functionality
|
|
253
|
+
if (matches(event, [Home, End])) {
|
|
254
|
+
if (!sideNavRef?.current) {
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
const allItems = Array.from(sideNavRef.current.querySelectorAll('a, button'));
|
|
258
|
+
if (match(event, Home)) {
|
|
259
|
+
const firstElement = allItems[0];
|
|
260
|
+
if (firstElement) {
|
|
261
|
+
firstElement.tabIndex = 0;
|
|
262
|
+
firstElement?.focus();
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
if (match(event, End)) {
|
|
266
|
+
const allItems = Array.from(sideNavRef.current.querySelectorAll('li'));
|
|
267
|
+
const lastVisibleItem = allItems.reverse().find(item => getComputedStyle(item).visibility !== 'hidden');
|
|
268
|
+
if (lastVisibleItem) {
|
|
269
|
+
const node = lastVisibleItem.querySelector('button') ?? lastVisibleItem.querySelector('a');
|
|
270
|
+
if (node) {
|
|
271
|
+
node.tabIndex = 0;
|
|
272
|
+
node?.focus();
|
|
273
|
+
}
|
|
274
|
+
}
|
|
254
275
|
}
|
|
255
276
|
}
|
|
256
|
-
}
|
|
257
277
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
if (
|
|
262
|
-
|
|
278
|
+
// focus on the focusable element within the node
|
|
279
|
+
if (nextFocusNode && nextFocusNode !== event.target) {
|
|
280
|
+
resetNodeTabIndices();
|
|
281
|
+
if (nextFocusNode instanceof HTMLElement) {
|
|
282
|
+
const node = nextFocusNode.querySelector('button') ?? nextFocusNode.querySelector('a');
|
|
283
|
+
if (node) {
|
|
284
|
+
node.tabIndex = 0;
|
|
285
|
+
node?.focus();
|
|
286
|
+
}
|
|
263
287
|
}
|
|
264
288
|
}
|
|
265
|
-
handleToggle(event, false);
|
|
266
|
-
if (href) {
|
|
267
|
-
window.location.href = href;
|
|
268
|
-
}
|
|
269
289
|
}
|
|
270
290
|
};
|
|
271
291
|
}
|
|
@@ -304,9 +324,27 @@ function SideNavRenderFunction(_ref, ref) {
|
|
|
304
324
|
item.tabIndex = -1;
|
|
305
325
|
});
|
|
306
326
|
}
|
|
327
|
+
|
|
328
|
+
// ensure that changes are in sync with internal treeview prop
|
|
329
|
+
useEffect(() => {
|
|
330
|
+
if (isTreeviewProp !== undefined) {
|
|
331
|
+
setInternalIsTreeview(isTreeviewProp);
|
|
332
|
+
}
|
|
333
|
+
}, [isTreeviewProp]);
|
|
334
|
+
|
|
335
|
+
// prevent changes if prop is passed in
|
|
336
|
+
const setIsTreeview = value => {
|
|
337
|
+
if (isTreeviewProp === undefined) {
|
|
338
|
+
setInternalIsTreeview(value);
|
|
339
|
+
}
|
|
340
|
+
};
|
|
307
341
|
return /*#__PURE__*/React.createElement(SideNavContext.Provider, {
|
|
308
342
|
value: {
|
|
309
|
-
isRail
|
|
343
|
+
isRail,
|
|
344
|
+
navType,
|
|
345
|
+
expanded: expanded,
|
|
346
|
+
isTreeview: internalIsTreeview,
|
|
347
|
+
setIsTreeview
|
|
310
348
|
}
|
|
311
349
|
}, isFixedNav || hideOverlay ? null :
|
|
312
350
|
/*#__PURE__*/
|
|
@@ -315,7 +353,7 @@ function SideNavRenderFunction(_ref, ref) {
|
|
|
315
353
|
className: overlayClassName,
|
|
316
354
|
onClick: onOverlayClick
|
|
317
355
|
}), /*#__PURE__*/React.createElement("nav", _extends({
|
|
318
|
-
role:
|
|
356
|
+
role: 'navigation',
|
|
319
357
|
tabIndex: -1,
|
|
320
358
|
ref: navRef,
|
|
321
359
|
className: `${prefix}--side-nav__navigation ${className}`,
|
|
@@ -6,6 +6,11 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import React from 'react';
|
|
8
8
|
export interface SideNavItemsProps {
|
|
9
|
+
/**
|
|
10
|
+
* Object to provide an aria-label to the component when used in treeview,
|
|
11
|
+
* to ensure it meets a11y requirements.
|
|
12
|
+
*/
|
|
13
|
+
accessibilityLabel: object;
|
|
9
14
|
/**
|
|
10
15
|
* Provide a single icon as the child to `SideNavIcon` to render in the
|
|
11
16
|
* container
|
|
@@ -5,18 +5,25 @@
|
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import { extends as _extends } from '../_virtual/_rollupPluginBabelHelpers.js';
|
|
8
9
|
import cx from '../_virtual/index.js';
|
|
9
10
|
import PropTypes from 'prop-types';
|
|
10
|
-
import React from 'react';
|
|
11
|
+
import React, { useContext, useRef, useEffect } from 'react';
|
|
11
12
|
import { CARBON_SIDENAV_ITEMS } from './_utils.js';
|
|
12
13
|
import { usePrefix } from '../internal/usePrefix.js';
|
|
14
|
+
import { SideNavContext } from './SideNav.js';
|
|
13
15
|
|
|
14
16
|
const SideNavItems = _ref => {
|
|
15
17
|
let {
|
|
16
18
|
className: customClassName,
|
|
17
19
|
children,
|
|
18
|
-
isSideNavExpanded
|
|
20
|
+
isSideNavExpanded,
|
|
21
|
+
accessibilityLabel: accessibilityLabel
|
|
19
22
|
} = _ref;
|
|
23
|
+
const {
|
|
24
|
+
isTreeview
|
|
25
|
+
} = useContext(SideNavContext);
|
|
26
|
+
const listRef = useRef(null); // Adjust type if necessary
|
|
20
27
|
const prefix = usePrefix();
|
|
21
28
|
const className = cx([`${prefix}--side-nav__items`], customClassName);
|
|
22
29
|
const childrenWithExpandedState = React.Children.map(children, child => {
|
|
@@ -36,9 +43,22 @@ const SideNavItems = _ref => {
|
|
|
36
43
|
});
|
|
37
44
|
}
|
|
38
45
|
});
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
// set SideNavLink's role without needing to extend original component
|
|
48
|
+
if (isTreeview && listRef.current) {
|
|
49
|
+
const sideNavItem = listRef.current.querySelectorAll(`.${prefix}--side-nav__item a`);
|
|
50
|
+
sideNavItem.forEach(e => {
|
|
51
|
+
if (!e.hasAttribute('role')) {
|
|
52
|
+
e.setAttribute('role', 'treeitem');
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}, [isTreeview]);
|
|
57
|
+
return /*#__PURE__*/React.createElement("ul", _extends({}, isTreeview && accessibilityLabel, {
|
|
58
|
+
ref: listRef,
|
|
59
|
+
className: className,
|
|
60
|
+
role: isTreeview ? 'tree' : ''
|
|
61
|
+
}), childrenWithExpandedState);
|
|
42
62
|
};
|
|
43
63
|
SideNavItems.displayName = 'SideNavItems';
|
|
44
64
|
SideNavItems.propTypes = {
|