@carbon-labs/react-ui-shell 0.19.0 → 0.21.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 CHANGED
@@ -31,6 +31,7 @@ components are provided by `@carbon-labs/react-ui-shell`:
31
31
 
32
32
  - `SideNav`
33
33
  - `SideNavItems`
34
+ - `SideNavLink`
34
35
  - `SideNavMenu`
35
36
  - `SideNavMenuItem`
36
37
  - `HeaderPanel`
@@ -158,7 +158,9 @@ function SideNavRenderFunction(_ref, ref) {
158
158
  */
159
159
  function parentSideNavMenu(node) {
160
160
  const parentNode = node.parentElement?.closest(`.${prefix}--side-nav__item`);
161
- if (parentNode) return parentNode;
161
+ if (parentNode) {
162
+ return parentNode;
163
+ }
162
164
  return node;
163
165
  }
164
166
  if (addFocusListeners) {
@@ -340,9 +342,9 @@ function SideNavRenderFunction(_ref, ref) {
340
342
  };
341
343
  return /*#__PURE__*/React.createElement(SideNavContext.Provider, {
342
344
  value: {
345
+ expanded,
343
346
  isRail,
344
347
  navType,
345
- expanded: expanded,
346
348
  isTreeview: internalIsTreeview,
347
349
  setIsTreeview
348
350
  }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Copyright IBM Corp. 2025
3
+ *
4
+ * This source code is licensed under the Apache-2.0 license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+ import { ComponentType, ElementType, ForwardedRef, ReactNode, WeakValidationMap } from 'react';
8
+ import { LinkProps } from './Link';
9
+ export type SideNavLinkProps<E extends ElementType> = LinkProps<E> & {
10
+ /**
11
+ * Required props for the accessibility label
12
+ */
13
+ 'aria-label'?: string;
14
+ /**
15
+ * Required props for the accessibility label
16
+ */
17
+ 'aria-labelledby'?: string;
18
+ /**
19
+ * Specify the text content for the link
20
+ */
21
+ children?: ReactNode;
22
+ /**
23
+ * Provide an optional class to be applied to the containing node
24
+ */
25
+ className?: string;
26
+ /**
27
+ * Specify whether the link is the current page
28
+ */
29
+ isActive?: boolean;
30
+ /**
31
+ * Property to indicate if the side nav container is open (or not). Use to
32
+ * keep local state and styling in step with the SideNav expansion state.
33
+ */
34
+ isSideNavExpanded?: boolean;
35
+ /**
36
+ * Specify if this is a large variation of the SideNavLink
37
+ */
38
+ large?: boolean;
39
+ /**
40
+ * Provide an icon to render in the side navigation link. Should be a React class.
41
+ */
42
+ renderIcon?: ComponentType;
43
+ /**
44
+ * Optional prop to specify the tabIndex of the button. If undefined, it will be applied default validation
45
+ */
46
+ tabIndex?: number;
47
+ };
48
+ export interface SideNavLinkComponent {
49
+ <E extends ElementType = 'a'>(props: SideNavLinkProps<E> & {
50
+ ref?: ForwardedRef<ElementType>;
51
+ }): JSX.Element | null;
52
+ displayName?: string;
53
+ propTypes?: WeakValidationMap<SideNavLinkProps<any>>;
54
+ }
55
+ export declare const SideNavLink: SideNavLinkComponent;
56
+ export declare const createCustomSideNavLink: (element: any) => (props: any) => import("react/jsx-runtime").JSX.Element;
57
+ export default SideNavLink;
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Copyright IBM Corp. 2024
3
+ *
4
+ * This source code is licensed under the Apache-2.0 license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+ import { extends as _extends } from '../_virtual/_rollupPluginBabelHelpers.js';
9
+ import cx from '../_virtual/index.js';
10
+ import PropTypes from 'prop-types';
11
+ import React, { forwardRef, useContext } from 'react';
12
+ import Link, { LinkPropTypes } from './Link.js';
13
+ import { SideNavItem, SideNavLinkText, SideNavIcon } from '@carbon/react';
14
+ import { usePrefix } from '../internal/usePrefix.js';
15
+ import { SideNavContext } from './SideNav.js';
16
+ import { SideNavLinkPopover } from './SideNavLinkPopover.js';
17
+
18
+ const SideNavLink = /*#__PURE__*/forwardRef(function SideNavLink(_ref, ref) {
19
+ let {
20
+ children,
21
+ className: customClassName,
22
+ renderIcon: IconElement,
23
+ isActive,
24
+ isSideNavExpanded,
25
+ large = false,
26
+ tabIndex,
27
+ ...rest
28
+ } = _ref;
29
+ const {
30
+ expanded,
31
+ isRail,
32
+ navType
33
+ } = useContext(SideNavContext);
34
+ const prefix = usePrefix();
35
+ const className = cx({
36
+ [`${prefix}--side-nav__link`]: true,
37
+ [`${prefix}--side-nav__link--current`]: isActive,
38
+ [customClassName]: !!customClassName
39
+ });
40
+ const SideNavLinkIcon = IconElement && /*#__PURE__*/React.createElement(SideNavIcon, {
41
+ small: true
42
+ }, /*#__PURE__*/React.createElement(IconElement, null));
43
+ if (!expanded && navType === 'panel') {
44
+ return /*#__PURE__*/React.createElement(SideNavLinkPopover, _extends({
45
+ align: "right",
46
+ label: children
47
+ }, rest), SideNavLinkIcon);
48
+ }
49
+ return /*#__PURE__*/React.createElement(SideNavItem, {
50
+ large: large
51
+ }, /*#__PURE__*/React.createElement(Link, _extends({}, rest, {
52
+ className: className,
53
+ ref: ref,
54
+ tabIndex: tabIndex === undefined ? !isSideNavExpanded && !isRail ? -1 : 0 : tabIndex
55
+ }), SideNavLinkIcon, /*#__PURE__*/React.createElement(SideNavLinkText, null, children)));
56
+ });
57
+ SideNavLink.displayName = 'SideNavLink';
58
+ SideNavLink.propTypes = {
59
+ ...LinkPropTypes,
60
+ /**
61
+ * Specify the text content for the link
62
+ */
63
+ children: PropTypes.node,
64
+ /**
65
+ * Provide an optional class to be applied to the containing node
66
+ */
67
+ className: PropTypes.string,
68
+ /**
69
+ * Specify whether the link is the current page
70
+ */
71
+ isActive: PropTypes.bool,
72
+ /**
73
+ * Property to indicate if the side nav container is open (or not). Use to
74
+ * keep local state and styling in step with the SideNav expansion state.
75
+ */
76
+ isSideNavExpanded: PropTypes.bool,
77
+ /**
78
+ * Specify if this is a large variation of the SideNavLink
79
+ */
80
+ large: PropTypes.bool,
81
+ /**
82
+ * Provide an icon to render in the side navigation link. Should be a React class.
83
+ */
84
+ // @ts-expect-error - PropTypes are unable to cover this case.
85
+ renderIcon: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
86
+ /**
87
+ * Optional prop to specify the tabIndex of the button. If undefined, it will be applied default validation
88
+ */
89
+ tabIndex: PropTypes.number
90
+ };
91
+
92
+ export { SideNavLink, SideNavLink as default };
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Copyright IBM Corp. 2016, 2023
3
+ *
4
+ * This source code is licensed under the Apache-2.0 license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+ import PropTypes from 'prop-types';
8
+ import React, { ReactNode } from 'react';
9
+ import { IconButtonAlignment, ButtonSize } from '@carbon/react';
10
+ interface IconButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
11
+ /**
12
+ * Specify how the trigger should align with the tooltip
13
+ */
14
+ align?: IconButtonAlignment;
15
+ /**
16
+ * **Experimental**: Will attempt to automatically align the tooltip
17
+ */
18
+ autoAlign?: boolean;
19
+ /**
20
+ * Optionally specify an href for your IconButton to become an `<a>` element
21
+ */
22
+ href?: string;
23
+ /**
24
+ * Provide an icon or asset to be rendered inside of the IconButton
25
+ */
26
+ children?: ReactNode;
27
+ /**
28
+ * Specify an optional className to be added to your Button
29
+ */
30
+ className?: string;
31
+ /**
32
+ * Determines whether the tooltip should close when inner content is activated (click, Enter or Space)
33
+ */
34
+ closeOnActivation?: boolean;
35
+ /**
36
+ * Specify whether the tooltip should be open when it first renders
37
+ */
38
+ defaultOpen?: boolean;
39
+ /**
40
+ * Specify whether the Button should be disabled, or not
41
+ */
42
+ disabled?: boolean;
43
+ /**
44
+ * Specify the duration in milliseconds to delay before displaying the tooltip
45
+ */
46
+ enterDelayMs?: number;
47
+ /**
48
+ * Specify whether the IconButton is currently selected
49
+ */
50
+ isSelected?: boolean;
51
+ /**
52
+ * Provide the label to be rendered inside of the Tooltip. The label will use
53
+ * `aria-labelledby` and will fully describe the child node that is provided.
54
+ * This means that if you have text in the child node it will not be
55
+ * announced to the screen reader.
56
+ */
57
+ label: ReactNode;
58
+ /**
59
+ * Specify the duration in milliseconds to delay before hiding the tooltip
60
+ */
61
+ leaveDelayMs?: number;
62
+ /**
63
+ * Specify the size of the Button. Defaults to `md`.
64
+ */
65
+ size?: ButtonSize;
66
+ /**
67
+ * Specify an optional className to be added to your Tooltip wrapper
68
+ */
69
+ wrapperClasses?: string;
70
+ }
71
+ export declare function SideNavLinkPopover({ className, children, ...rest }: IconButtonProps): import("react/jsx-runtime").JSX.Element;
72
+ export declare namespace SideNavLinkPopover {
73
+ var propTypes: {
74
+ /**
75
+ * Specify how the trigger should align with the tooltip
76
+ */
77
+ align: PropTypes.Requireable<string>;
78
+ /**
79
+ * **Experimental**: Will attempt to automatically align the tooltip
80
+ */
81
+ autoAlign: PropTypes.Requireable<boolean>;
82
+ /**
83
+ * Provide an icon or asset to be rendered inside of the IconButton
84
+ */
85
+ children: PropTypes.Requireable<PropTypes.ReactNodeLike>;
86
+ /**
87
+ * Specify an optional className to be added to your Button
88
+ */
89
+ className: PropTypes.Requireable<string>;
90
+ /**
91
+ * Determines whether the tooltip should close when inner content is activated (click, Enter or Space)
92
+ */
93
+ closeOnActivation: PropTypes.Requireable<boolean>;
94
+ /**
95
+ * Specify whether the tooltip should be open when it first renders
96
+ */
97
+ defaultOpen: PropTypes.Requireable<boolean>;
98
+ /**
99
+ * Specify whether the Button should be disabled, or not
100
+ */
101
+ disabled: PropTypes.Requireable<boolean>;
102
+ /**
103
+ * Specify the duration in milliseconds to delay before displaying the tooltip
104
+ */
105
+ enterDelayMs: PropTypes.Requireable<number>;
106
+ /**
107
+ * Optionally specify an href for your IconButton to become an `<a>` element
108
+ */
109
+ href: PropTypes.Requireable<string>;
110
+ /**
111
+ * Specify whether the IconButton is currently selected
112
+ */
113
+ isSelected: PropTypes.Requireable<boolean>;
114
+ /**
115
+ * Provide the label to be rendered inside of the Tooltip. The label will use
116
+ * `aria-labelledby` and will fully describe the child node that is provided.
117
+ * This means that if you have text in the child node it will not be
118
+ * announced to the screen reader.
119
+ */
120
+ label: PropTypes.Validator<NonNullable<PropTypes.ReactNodeLike>>;
121
+ /**
122
+ * Specify the duration in milliseconds to delay before hiding the tooltip
123
+ */
124
+ leaveDelayMs: PropTypes.Requireable<number>;
125
+ /**
126
+ * Specify the size of the Button. Defaults to `md`.
127
+ */
128
+ size: PropTypes.Requireable<string>;
129
+ /**
130
+ * Specify an optional className to be added to your Tooltip wrapper
131
+ */
132
+ wrapperClasses: PropTypes.Requireable<string>;
133
+ };
134
+ }
135
+ export default SideNavLinkPopover;
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Copyright IBM Corp. 2024
3
+ *
4
+ * This source code is licensed under the Apache-2.0 license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+ import { extends as _extends } from '../_virtual/_rollupPluginBabelHelpers.js';
9
+ import cx from '../_virtual/index.js';
10
+ import PropTypes from 'prop-types';
11
+ import React from 'react';
12
+ import { IconButton } from '@carbon/react';
13
+ import { usePrefix } from '../internal/usePrefix.js';
14
+
15
+ function SideNavLinkPopover(_ref) {
16
+ let {
17
+ className,
18
+ children,
19
+ ...rest
20
+ } = _ref;
21
+ const prefix = usePrefix();
22
+ return /*#__PURE__*/React.createElement(IconButton, _extends({
23
+ className: cx(className, `${prefix}--side-nav-link-popover`),
24
+ kind: "ghost"
25
+ }, rest), children);
26
+ }
27
+ SideNavLinkPopover.propTypes = {
28
+ /**
29
+ * Specify how the trigger should align with the tooltip
30
+ */
31
+ align: PropTypes.oneOf(['top', 'top-start', 'top-end', 'bottom', 'bottom-start', 'bottom-end', 'left', 'left-end', 'left-start', 'right', 'right-end', 'right-start']),
32
+ /**
33
+ * **Experimental**: Will attempt to automatically align the tooltip
34
+ */
35
+ autoAlign: PropTypes.bool,
36
+ /**
37
+ * Provide an icon or asset to be rendered inside of the IconButton
38
+ */
39
+ children: PropTypes.node,
40
+ /**
41
+ * Specify an optional className to be added to your Button
42
+ */
43
+ className: PropTypes.string,
44
+ /**
45
+ * Determines whether the tooltip should close when inner content is activated (click, Enter or Space)
46
+ */
47
+ closeOnActivation: PropTypes.bool,
48
+ /**
49
+ * Specify whether the tooltip should be open when it first renders
50
+ */
51
+ defaultOpen: PropTypes.bool,
52
+ /**
53
+ * Specify whether the Button should be disabled, or not
54
+ */
55
+ disabled: PropTypes.bool,
56
+ /**
57
+ * Specify the duration in milliseconds to delay before displaying the tooltip
58
+ */
59
+ enterDelayMs: PropTypes.number,
60
+ /**
61
+ * Optionally specify an href for your IconButton to become an `<a>` element
62
+ */
63
+ href: PropTypes.string,
64
+ /**
65
+ * Specify whether the IconButton is currently selected
66
+ */
67
+
68
+ isSelected: PropTypes.bool,
69
+ /**
70
+ * Provide the label to be rendered inside of the Tooltip. The label will use
71
+ * `aria-labelledby` and will fully describe the child node that is provided.
72
+ * This means that if you have text in the child node it will not be
73
+ * announced to the screen reader.
74
+ */
75
+ label: PropTypes.node.isRequired,
76
+ /**
77
+ * Specify the duration in milliseconds to delay before hiding the tooltip
78
+ */
79
+ leaveDelayMs: PropTypes.number,
80
+ /**
81
+ * Specify the size of the Button. Defaults to `md`.
82
+ */
83
+ size: PropTypes.oneOf(['sm', 'md', 'lg']),
84
+ /**
85
+ * Specify an optional className to be added to your Tooltip wrapper
86
+ */
87
+ wrapperClasses: PropTypes.string
88
+ };
89
+
90
+ export { SideNavLinkPopover, SideNavLinkPopover as default };
@@ -96,23 +96,6 @@ const SideNavMenu = /*#__PURE__*/React.forwardRef(function SideNavMenu(_ref, ref
96
96
 
97
97
  // if depth is more than 0, that means its nested, thus we set treeview mode
98
98
  setIsTreeview?.(true);
99
- if (isTreeview) {
100
- const calcButtonOffset = () => {
101
- // menu with icon
102
- if (children && IconElement) {
103
- return depth + 3;
104
- }
105
-
106
- // menu without icon
107
- if (children) {
108
- return depth * 4;
109
- }
110
- return depth;
111
- };
112
- if (buttonRef.current) {
113
- buttonRef.current.style.paddingLeft = `${calcButtonOffset()}rem`;
114
- }
115
- }
116
99
  }, [isTreeview]);
117
100
 
118
101
  /**
@@ -4,7 +4,7 @@
4
4
  * This source code is licensed under the Apache-2.0 license found in the
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
- import React, { ElementType, ComponentProps } from 'react';
7
+ import React, { ElementType, ComponentProps, ComponentType } from 'react';
8
8
  import Link from './Link';
9
9
  export interface SideNavMenuItemProps extends ComponentProps<typeof Link> {
10
10
  /**
@@ -15,11 +15,6 @@ export interface SideNavMenuItemProps extends ComponentProps<typeof Link> {
15
15
  * Provide an optional class to be applied to the containing node
16
16
  */
17
17
  className?: string;
18
- /**
19
- * **Note:** this is controlled by the parent SideNavMenu component, do not set manually.
20
- * SideNavMenu depth to determine spacing
21
- */
22
- depth?: number;
23
18
  /**
24
19
  * Optionally specify whether the link is "active". An active link is one that
25
20
  * has an href that is the same as the current page. Can also pass in
@@ -34,5 +29,9 @@ export interface SideNavMenuItemProps extends ComponentProps<typeof Link> {
34
29
  * Optional component to render instead of default Link
35
30
  */
36
31
  as?: ElementType;
32
+ /**
33
+ * Provide an icon to render in the side navigation link. Should be a React class.
34
+ */
35
+ renderIcon?: ComponentType;
37
36
  }
38
37
  export declare const SideNavMenuItem: React.ForwardRefExoticComponent<Omit<SideNavMenuItemProps, "ref"> & React.RefAttributes<HTMLElement>>;
@@ -8,11 +8,10 @@
8
8
  import { extends as _extends } from '../_virtual/_rollupPluginBabelHelpers.js';
9
9
  import cx from '../_virtual/index.js';
10
10
  import PropTypes from 'prop-types';
11
- import React, { useContext, useRef, useEffect } from 'react';
12
- import { SideNavLinkText } from '@carbon/react';
11
+ import React, { useContext } from 'react';
12
+ import { SideNavIcon, SideNavLinkText } from '@carbon/react';
13
13
  import Link from './Link.js';
14
14
  import { usePrefix } from '../internal/usePrefix.js';
15
- import { useMergedRefs } from '../internal/useMergedRefs.js';
16
15
  import { SideNavContext } from './SideNav.js';
17
16
 
18
17
  const SideNavMenuItem = /*#__PURE__*/React.forwardRef(function SideNavMenuItem(props, ref) {
@@ -20,30 +19,19 @@ const SideNavMenuItem = /*#__PURE__*/React.forwardRef(function SideNavMenuItem(p
20
19
  const {
21
20
  children,
22
21
  className: customClassName,
23
- depth: propDepth,
24
22
  as: Component = Link,
25
23
  isActive,
24
+ renderIcon: IconElement,
26
25
  ...rest
27
26
  } = props;
28
27
  const {
29
28
  isTreeview
30
29
  } = useContext(SideNavContext);
31
30
  const className = cx(`${prefix}--side-nav__menu-item`, customClassName);
32
- const depth = propDepth;
33
31
  const linkClassName = cx({
34
32
  [`${prefix}--side-nav__link`]: true,
35
33
  [`${prefix}--side-nav__link--current`]: isActive
36
34
  });
37
- const linkRef = useRef(null);
38
- const itemRef = useMergedRefs([linkRef, ref]);
39
- useEffect(() => {
40
- const calcLinkOffset = () => {
41
- return 4 + Math.max(0, depth - 1) * 1;
42
- };
43
- if (linkRef.current) {
44
- linkRef.current.style.paddingLeft = `${calcLinkOffset()}rem`;
45
- }
46
- }, [isTreeview]);
47
35
  return /*#__PURE__*/React.createElement("li", {
48
36
  className: className
49
37
  }, /*#__PURE__*/React.createElement(Component, _extends({}, rest, {
@@ -51,11 +39,17 @@ const SideNavMenuItem = /*#__PURE__*/React.forwardRef(function SideNavMenuItem(p
51
39
  role: isTreeview ? 'treeitem' : undefined,
52
40
  className: linkClassName,
53
41
  tabIndex: isTreeview ? -1 : 0,
54
- ref: itemRef
55
- }), /*#__PURE__*/React.createElement(SideNavLinkText, null, children)));
42
+ ref: ref
43
+ }), IconElement && /*#__PURE__*/React.createElement(SideNavIcon, {
44
+ small: true
45
+ }, /*#__PURE__*/React.createElement(IconElement, null)), /*#__PURE__*/React.createElement(SideNavLinkText, null, children)));
56
46
  });
57
47
  SideNavMenuItem.displayName = 'SideNavMenuItem';
58
48
  SideNavMenuItem.propTypes = {
49
+ /**
50
+ * Optional component to render instead of default Link
51
+ */
52
+ as: PropTypes.elementType,
59
53
  /**
60
54
  * Specify the children to be rendered inside of the `SideNavMenuItem`
61
55
  */
@@ -64,11 +58,6 @@ SideNavMenuItem.propTypes = {
64
58
  * Provide an optional class to be applied to the containing node
65
59
  */
66
60
  className: PropTypes.string,
67
- /**
68
- * **Note:** this is controlled by the parent SideNavMenu component, do not set manually.
69
- * SideNavMenu depth to determine spacing
70
- */
71
- depth: PropTypes.number,
72
61
  /**
73
62
  * Optionally provide an href for the underlying li`
74
63
  */
@@ -80,9 +69,9 @@ SideNavMenuItem.propTypes = {
80
69
  */
81
70
  isActive: PropTypes.bool,
82
71
  /**
83
- * Optional component to render instead of default Link
72
+ * Provide an icon to render in the side navigation link. Should be a React class.
84
73
  */
85
- as: PropTypes.elementType
74
+ renderIcon: PropTypes.oneOfType([PropTypes.func, PropTypes.object])
86
75
  };
87
76
 
88
77
  export { SideNavMenuItem };
package/es/index.d.ts CHANGED
@@ -8,6 +8,8 @@
8
8
  */
9
9
  export { SideNav, SIDE_NAV_TYPE } from './components/SideNav.js';
10
10
  export { SideNavItems } from './components/SideNavItems.js';
11
+ export { SideNavLink } from './components/SideNavLink.js';
12
+ export { SideNavLinkPopover } from './components/SideNavLinkPopover.js';
11
13
  export { SideNavMenu } from './components/SideNavMenu.js';
12
14
  export { SideNavMenuItem } from './components/SideNavMenuItem.js';
13
15
  export { HeaderPanel } from './components/HeaderPanel';
package/es/index.js CHANGED
@@ -7,6 +7,8 @@
7
7
 
8
8
  export { SIDE_NAV_TYPE, SideNav } from './components/SideNav.js';
9
9
  export { SideNavItems } from './components/SideNavItems.js';
10
+ export { SideNavLink } from './components/SideNavLink.js';
11
+ export { SideNavLinkPopover } from './components/SideNavLinkPopover.js';
10
12
  export { SideNavMenu } from './components/SideNavMenu.js';
11
13
  export { SideNavMenuItem } from './components/SideNavMenuItem.js';
12
14
  export { HeaderPanel } from './components/HeaderPanel.js';
@@ -160,7 +160,9 @@ function SideNavRenderFunction(_ref, ref) {
160
160
  */
161
161
  function parentSideNavMenu(node) {
162
162
  const parentNode = node.parentElement?.closest(`.${prefix}--side-nav__item`);
163
- if (parentNode) return parentNode;
163
+ if (parentNode) {
164
+ return parentNode;
165
+ }
164
166
  return node;
165
167
  }
166
168
  if (addFocusListeners) {
@@ -342,9 +344,9 @@ function SideNavRenderFunction(_ref, ref) {
342
344
  };
343
345
  return /*#__PURE__*/React.createElement(SideNavContext.Provider, {
344
346
  value: {
347
+ expanded,
345
348
  isRail,
346
349
  navType,
347
- expanded: expanded,
348
350
  isTreeview: internalIsTreeview,
349
351
  setIsTreeview
350
352
  }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Copyright IBM Corp. 2025
3
+ *
4
+ * This source code is licensed under the Apache-2.0 license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+ import { ComponentType, ElementType, ForwardedRef, ReactNode, WeakValidationMap } from 'react';
8
+ import { LinkProps } from './Link';
9
+ export type SideNavLinkProps<E extends ElementType> = LinkProps<E> & {
10
+ /**
11
+ * Required props for the accessibility label
12
+ */
13
+ 'aria-label'?: string;
14
+ /**
15
+ * Required props for the accessibility label
16
+ */
17
+ 'aria-labelledby'?: string;
18
+ /**
19
+ * Specify the text content for the link
20
+ */
21
+ children?: ReactNode;
22
+ /**
23
+ * Provide an optional class to be applied to the containing node
24
+ */
25
+ className?: string;
26
+ /**
27
+ * Specify whether the link is the current page
28
+ */
29
+ isActive?: boolean;
30
+ /**
31
+ * Property to indicate if the side nav container is open (or not). Use to
32
+ * keep local state and styling in step with the SideNav expansion state.
33
+ */
34
+ isSideNavExpanded?: boolean;
35
+ /**
36
+ * Specify if this is a large variation of the SideNavLink
37
+ */
38
+ large?: boolean;
39
+ /**
40
+ * Provide an icon to render in the side navigation link. Should be a React class.
41
+ */
42
+ renderIcon?: ComponentType;
43
+ /**
44
+ * Optional prop to specify the tabIndex of the button. If undefined, it will be applied default validation
45
+ */
46
+ tabIndex?: number;
47
+ };
48
+ export interface SideNavLinkComponent {
49
+ <E extends ElementType = 'a'>(props: SideNavLinkProps<E> & {
50
+ ref?: ForwardedRef<ElementType>;
51
+ }): JSX.Element | null;
52
+ displayName?: string;
53
+ propTypes?: WeakValidationMap<SideNavLinkProps<any>>;
54
+ }
55
+ export declare const SideNavLink: SideNavLinkComponent;
56
+ export declare const createCustomSideNavLink: (element: any) => (props: any) => import("react/jsx-runtime").JSX.Element;
57
+ export default SideNavLink;
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Copyright IBM Corp. 2024
3
+ *
4
+ * This source code is licensed under the Apache-2.0 license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+ 'use strict';
9
+
10
+ Object.defineProperty(exports, '__esModule', { value: true });
11
+
12
+ var _rollupPluginBabelHelpers = require('../_virtual/_rollupPluginBabelHelpers.js');
13
+ var index = require('../_virtual/index.js');
14
+ var PropTypes = require('prop-types');
15
+ var React = require('react');
16
+ var Link = require('./Link.js');
17
+ var react = require('@carbon/react');
18
+ var usePrefix = require('../internal/usePrefix.js');
19
+ var SideNav = require('./SideNav.js');
20
+ var SideNavLinkPopover = require('./SideNavLinkPopover.js');
21
+
22
+ const SideNavLink = /*#__PURE__*/React.forwardRef(function SideNavLink(_ref, ref) {
23
+ let {
24
+ children,
25
+ className: customClassName,
26
+ renderIcon: IconElement,
27
+ isActive,
28
+ isSideNavExpanded,
29
+ large = false,
30
+ tabIndex,
31
+ ...rest
32
+ } = _ref;
33
+ const {
34
+ expanded,
35
+ isRail,
36
+ navType
37
+ } = React.useContext(SideNav.SideNavContext);
38
+ const prefix = usePrefix.usePrefix();
39
+ const className = index.default({
40
+ [`${prefix}--side-nav__link`]: true,
41
+ [`${prefix}--side-nav__link--current`]: isActive,
42
+ [customClassName]: !!customClassName
43
+ });
44
+ const SideNavLinkIcon = IconElement && /*#__PURE__*/React.createElement(react.SideNavIcon, {
45
+ small: true
46
+ }, /*#__PURE__*/React.createElement(IconElement, null));
47
+ if (!expanded && navType === 'panel') {
48
+ return /*#__PURE__*/React.createElement(SideNavLinkPopover.SideNavLinkPopover, _rollupPluginBabelHelpers.extends({
49
+ align: "right",
50
+ label: children
51
+ }, rest), SideNavLinkIcon);
52
+ }
53
+ return /*#__PURE__*/React.createElement(react.SideNavItem, {
54
+ large: large
55
+ }, /*#__PURE__*/React.createElement(Link.default, _rollupPluginBabelHelpers.extends({}, rest, {
56
+ className: className,
57
+ ref: ref,
58
+ tabIndex: tabIndex === undefined ? !isSideNavExpanded && !isRail ? -1 : 0 : tabIndex
59
+ }), SideNavLinkIcon, /*#__PURE__*/React.createElement(react.SideNavLinkText, null, children)));
60
+ });
61
+ SideNavLink.displayName = 'SideNavLink';
62
+ SideNavLink.propTypes = {
63
+ ...Link.LinkPropTypes,
64
+ /**
65
+ * Specify the text content for the link
66
+ */
67
+ children: PropTypes.node,
68
+ /**
69
+ * Provide an optional class to be applied to the containing node
70
+ */
71
+ className: PropTypes.string,
72
+ /**
73
+ * Specify whether the link is the current page
74
+ */
75
+ isActive: PropTypes.bool,
76
+ /**
77
+ * Property to indicate if the side nav container is open (or not). Use to
78
+ * keep local state and styling in step with the SideNav expansion state.
79
+ */
80
+ isSideNavExpanded: PropTypes.bool,
81
+ /**
82
+ * Specify if this is a large variation of the SideNavLink
83
+ */
84
+ large: PropTypes.bool,
85
+ /**
86
+ * Provide an icon to render in the side navigation link. Should be a React class.
87
+ */
88
+ // @ts-expect-error - PropTypes are unable to cover this case.
89
+ renderIcon: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
90
+ /**
91
+ * Optional prop to specify the tabIndex of the button. If undefined, it will be applied default validation
92
+ */
93
+ tabIndex: PropTypes.number
94
+ };
95
+
96
+ exports.SideNavLink = SideNavLink;
97
+ exports.default = SideNavLink;
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Copyright IBM Corp. 2016, 2023
3
+ *
4
+ * This source code is licensed under the Apache-2.0 license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+ import PropTypes from 'prop-types';
8
+ import React, { ReactNode } from 'react';
9
+ import { IconButtonAlignment, ButtonSize } from '@carbon/react';
10
+ interface IconButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
11
+ /**
12
+ * Specify how the trigger should align with the tooltip
13
+ */
14
+ align?: IconButtonAlignment;
15
+ /**
16
+ * **Experimental**: Will attempt to automatically align the tooltip
17
+ */
18
+ autoAlign?: boolean;
19
+ /**
20
+ * Optionally specify an href for your IconButton to become an `<a>` element
21
+ */
22
+ href?: string;
23
+ /**
24
+ * Provide an icon or asset to be rendered inside of the IconButton
25
+ */
26
+ children?: ReactNode;
27
+ /**
28
+ * Specify an optional className to be added to your Button
29
+ */
30
+ className?: string;
31
+ /**
32
+ * Determines whether the tooltip should close when inner content is activated (click, Enter or Space)
33
+ */
34
+ closeOnActivation?: boolean;
35
+ /**
36
+ * Specify whether the tooltip should be open when it first renders
37
+ */
38
+ defaultOpen?: boolean;
39
+ /**
40
+ * Specify whether the Button should be disabled, or not
41
+ */
42
+ disabled?: boolean;
43
+ /**
44
+ * Specify the duration in milliseconds to delay before displaying the tooltip
45
+ */
46
+ enterDelayMs?: number;
47
+ /**
48
+ * Specify whether the IconButton is currently selected
49
+ */
50
+ isSelected?: boolean;
51
+ /**
52
+ * Provide the label to be rendered inside of the Tooltip. The label will use
53
+ * `aria-labelledby` and will fully describe the child node that is provided.
54
+ * This means that if you have text in the child node it will not be
55
+ * announced to the screen reader.
56
+ */
57
+ label: ReactNode;
58
+ /**
59
+ * Specify the duration in milliseconds to delay before hiding the tooltip
60
+ */
61
+ leaveDelayMs?: number;
62
+ /**
63
+ * Specify the size of the Button. Defaults to `md`.
64
+ */
65
+ size?: ButtonSize;
66
+ /**
67
+ * Specify an optional className to be added to your Tooltip wrapper
68
+ */
69
+ wrapperClasses?: string;
70
+ }
71
+ export declare function SideNavLinkPopover({ className, children, ...rest }: IconButtonProps): import("react/jsx-runtime").JSX.Element;
72
+ export declare namespace SideNavLinkPopover {
73
+ var propTypes: {
74
+ /**
75
+ * Specify how the trigger should align with the tooltip
76
+ */
77
+ align: PropTypes.Requireable<string>;
78
+ /**
79
+ * **Experimental**: Will attempt to automatically align the tooltip
80
+ */
81
+ autoAlign: PropTypes.Requireable<boolean>;
82
+ /**
83
+ * Provide an icon or asset to be rendered inside of the IconButton
84
+ */
85
+ children: PropTypes.Requireable<PropTypes.ReactNodeLike>;
86
+ /**
87
+ * Specify an optional className to be added to your Button
88
+ */
89
+ className: PropTypes.Requireable<string>;
90
+ /**
91
+ * Determines whether the tooltip should close when inner content is activated (click, Enter or Space)
92
+ */
93
+ closeOnActivation: PropTypes.Requireable<boolean>;
94
+ /**
95
+ * Specify whether the tooltip should be open when it first renders
96
+ */
97
+ defaultOpen: PropTypes.Requireable<boolean>;
98
+ /**
99
+ * Specify whether the Button should be disabled, or not
100
+ */
101
+ disabled: PropTypes.Requireable<boolean>;
102
+ /**
103
+ * Specify the duration in milliseconds to delay before displaying the tooltip
104
+ */
105
+ enterDelayMs: PropTypes.Requireable<number>;
106
+ /**
107
+ * Optionally specify an href for your IconButton to become an `<a>` element
108
+ */
109
+ href: PropTypes.Requireable<string>;
110
+ /**
111
+ * Specify whether the IconButton is currently selected
112
+ */
113
+ isSelected: PropTypes.Requireable<boolean>;
114
+ /**
115
+ * Provide the label to be rendered inside of the Tooltip. The label will use
116
+ * `aria-labelledby` and will fully describe the child node that is provided.
117
+ * This means that if you have text in the child node it will not be
118
+ * announced to the screen reader.
119
+ */
120
+ label: PropTypes.Validator<NonNullable<PropTypes.ReactNodeLike>>;
121
+ /**
122
+ * Specify the duration in milliseconds to delay before hiding the tooltip
123
+ */
124
+ leaveDelayMs: PropTypes.Requireable<number>;
125
+ /**
126
+ * Specify the size of the Button. Defaults to `md`.
127
+ */
128
+ size: PropTypes.Requireable<string>;
129
+ /**
130
+ * Specify an optional className to be added to your Tooltip wrapper
131
+ */
132
+ wrapperClasses: PropTypes.Requireable<string>;
133
+ };
134
+ }
135
+ export default SideNavLinkPopover;
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Copyright IBM Corp. 2024
3
+ *
4
+ * This source code is licensed under the Apache-2.0 license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+ 'use strict';
9
+
10
+ Object.defineProperty(exports, '__esModule', { value: true });
11
+
12
+ var _rollupPluginBabelHelpers = require('../_virtual/_rollupPluginBabelHelpers.js');
13
+ var index = require('../_virtual/index.js');
14
+ var PropTypes = require('prop-types');
15
+ var React = require('react');
16
+ var react = require('@carbon/react');
17
+ var usePrefix = require('../internal/usePrefix.js');
18
+
19
+ function SideNavLinkPopover(_ref) {
20
+ let {
21
+ className,
22
+ children,
23
+ ...rest
24
+ } = _ref;
25
+ const prefix = usePrefix.usePrefix();
26
+ return /*#__PURE__*/React.createElement(react.IconButton, _rollupPluginBabelHelpers.extends({
27
+ className: index.default(className, `${prefix}--side-nav-link-popover`),
28
+ kind: "ghost"
29
+ }, rest), children);
30
+ }
31
+ SideNavLinkPopover.propTypes = {
32
+ /**
33
+ * Specify how the trigger should align with the tooltip
34
+ */
35
+ align: PropTypes.oneOf(['top', 'top-start', 'top-end', 'bottom', 'bottom-start', 'bottom-end', 'left', 'left-end', 'left-start', 'right', 'right-end', 'right-start']),
36
+ /**
37
+ * **Experimental**: Will attempt to automatically align the tooltip
38
+ */
39
+ autoAlign: PropTypes.bool,
40
+ /**
41
+ * Provide an icon or asset to be rendered inside of the IconButton
42
+ */
43
+ children: PropTypes.node,
44
+ /**
45
+ * Specify an optional className to be added to your Button
46
+ */
47
+ className: PropTypes.string,
48
+ /**
49
+ * Determines whether the tooltip should close when inner content is activated (click, Enter or Space)
50
+ */
51
+ closeOnActivation: PropTypes.bool,
52
+ /**
53
+ * Specify whether the tooltip should be open when it first renders
54
+ */
55
+ defaultOpen: PropTypes.bool,
56
+ /**
57
+ * Specify whether the Button should be disabled, or not
58
+ */
59
+ disabled: PropTypes.bool,
60
+ /**
61
+ * Specify the duration in milliseconds to delay before displaying the tooltip
62
+ */
63
+ enterDelayMs: PropTypes.number,
64
+ /**
65
+ * Optionally specify an href for your IconButton to become an `<a>` element
66
+ */
67
+ href: PropTypes.string,
68
+ /**
69
+ * Specify whether the IconButton is currently selected
70
+ */
71
+
72
+ isSelected: PropTypes.bool,
73
+ /**
74
+ * Provide the label to be rendered inside of the Tooltip. The label will use
75
+ * `aria-labelledby` and will fully describe the child node that is provided.
76
+ * This means that if you have text in the child node it will not be
77
+ * announced to the screen reader.
78
+ */
79
+ label: PropTypes.node.isRequired,
80
+ /**
81
+ * Specify the duration in milliseconds to delay before hiding the tooltip
82
+ */
83
+ leaveDelayMs: PropTypes.number,
84
+ /**
85
+ * Specify the size of the Button. Defaults to `md`.
86
+ */
87
+ size: PropTypes.oneOf(['sm', 'md', 'lg']),
88
+ /**
89
+ * Specify an optional className to be added to your Tooltip wrapper
90
+ */
91
+ wrapperClasses: PropTypes.string
92
+ };
93
+
94
+ exports.SideNavLinkPopover = SideNavLinkPopover;
95
+ exports.default = SideNavLinkPopover;
@@ -98,23 +98,6 @@ const SideNavMenu = /*#__PURE__*/React.forwardRef(function SideNavMenu(_ref, ref
98
98
 
99
99
  // if depth is more than 0, that means its nested, thus we set treeview mode
100
100
  setIsTreeview?.(true);
101
- if (isTreeview) {
102
- const calcButtonOffset = () => {
103
- // menu with icon
104
- if (children && IconElement) {
105
- return depth + 3;
106
- }
107
-
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`;
116
- }
117
- }
118
101
  }, [isTreeview]);
119
102
 
120
103
  /**
@@ -4,7 +4,7 @@
4
4
  * This source code is licensed under the Apache-2.0 license found in the
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
- import React, { ElementType, ComponentProps } from 'react';
7
+ import React, { ElementType, ComponentProps, ComponentType } from 'react';
8
8
  import Link from './Link';
9
9
  export interface SideNavMenuItemProps extends ComponentProps<typeof Link> {
10
10
  /**
@@ -15,11 +15,6 @@ export interface SideNavMenuItemProps extends ComponentProps<typeof Link> {
15
15
  * Provide an optional class to be applied to the containing node
16
16
  */
17
17
  className?: string;
18
- /**
19
- * **Note:** this is controlled by the parent SideNavMenu component, do not set manually.
20
- * SideNavMenu depth to determine spacing
21
- */
22
- depth?: number;
23
18
  /**
24
19
  * Optionally specify whether the link is "active". An active link is one that
25
20
  * has an href that is the same as the current page. Can also pass in
@@ -34,5 +29,9 @@ export interface SideNavMenuItemProps extends ComponentProps<typeof Link> {
34
29
  * Optional component to render instead of default Link
35
30
  */
36
31
  as?: ElementType;
32
+ /**
33
+ * Provide an icon to render in the side navigation link. Should be a React class.
34
+ */
35
+ renderIcon?: ComponentType;
37
36
  }
38
37
  export declare const SideNavMenuItem: React.ForwardRefExoticComponent<Omit<SideNavMenuItemProps, "ref"> & React.RefAttributes<HTMLElement>>;
@@ -14,7 +14,6 @@ var React = require('react');
14
14
  var react = require('@carbon/react');
15
15
  var Link = require('./Link.js');
16
16
  var usePrefix = require('../internal/usePrefix.js');
17
- var useMergedRefs = require('../internal/useMergedRefs.js');
18
17
  var SideNav = require('./SideNav.js');
19
18
 
20
19
  const SideNavMenuItem = /*#__PURE__*/React.forwardRef(function SideNavMenuItem(props, ref) {
@@ -22,30 +21,19 @@ const SideNavMenuItem = /*#__PURE__*/React.forwardRef(function SideNavMenuItem(p
22
21
  const {
23
22
  children,
24
23
  className: customClassName,
25
- depth: propDepth,
26
24
  as: Component = Link.default,
27
25
  isActive,
26
+ renderIcon: IconElement,
28
27
  ...rest
29
28
  } = props;
30
29
  const {
31
30
  isTreeview
32
31
  } = React.useContext(SideNav.SideNavContext);
33
32
  const className = index.default(`${prefix}--side-nav__menu-item`, customClassName);
34
- const depth = propDepth;
35
33
  const linkClassName = index.default({
36
34
  [`${prefix}--side-nav__link`]: true,
37
35
  [`${prefix}--side-nav__link--current`]: isActive
38
36
  });
39
- const linkRef = React.useRef(null);
40
- const itemRef = useMergedRefs.useMergedRefs([linkRef, ref]);
41
- React.useEffect(() => {
42
- const calcLinkOffset = () => {
43
- return 4 + Math.max(0, depth - 1) * 1;
44
- };
45
- if (linkRef.current) {
46
- linkRef.current.style.paddingLeft = `${calcLinkOffset()}rem`;
47
- }
48
- }, [isTreeview]);
49
37
  return /*#__PURE__*/React.createElement("li", {
50
38
  className: className
51
39
  }, /*#__PURE__*/React.createElement(Component, _rollupPluginBabelHelpers.extends({}, rest, {
@@ -53,11 +41,17 @@ const SideNavMenuItem = /*#__PURE__*/React.forwardRef(function SideNavMenuItem(p
53
41
  role: isTreeview ? 'treeitem' : undefined,
54
42
  className: linkClassName,
55
43
  tabIndex: isTreeview ? -1 : 0,
56
- ref: itemRef
57
- }), /*#__PURE__*/React.createElement(react.SideNavLinkText, null, children)));
44
+ ref: ref
45
+ }), IconElement && /*#__PURE__*/React.createElement(react.SideNavIcon, {
46
+ small: true
47
+ }, /*#__PURE__*/React.createElement(IconElement, null)), /*#__PURE__*/React.createElement(react.SideNavLinkText, null, children)));
58
48
  });
59
49
  SideNavMenuItem.displayName = 'SideNavMenuItem';
60
50
  SideNavMenuItem.propTypes = {
51
+ /**
52
+ * Optional component to render instead of default Link
53
+ */
54
+ as: PropTypes.elementType,
61
55
  /**
62
56
  * Specify the children to be rendered inside of the `SideNavMenuItem`
63
57
  */
@@ -66,11 +60,6 @@ SideNavMenuItem.propTypes = {
66
60
  * Provide an optional class to be applied to the containing node
67
61
  */
68
62
  className: PropTypes.string,
69
- /**
70
- * **Note:** this is controlled by the parent SideNavMenu component, do not set manually.
71
- * SideNavMenu depth to determine spacing
72
- */
73
- depth: PropTypes.number,
74
63
  /**
75
64
  * Optionally provide an href for the underlying li`
76
65
  */
@@ -82,9 +71,9 @@ SideNavMenuItem.propTypes = {
82
71
  */
83
72
  isActive: PropTypes.bool,
84
73
  /**
85
- * Optional component to render instead of default Link
74
+ * Provide an icon to render in the side navigation link. Should be a React class.
86
75
  */
87
- as: PropTypes.elementType
76
+ renderIcon: PropTypes.oneOfType([PropTypes.func, PropTypes.object])
88
77
  };
89
78
 
90
79
  exports.SideNavMenuItem = SideNavMenuItem;
package/lib/index.d.ts CHANGED
@@ -8,6 +8,8 @@
8
8
  */
9
9
  export { SideNav, SIDE_NAV_TYPE } from './components/SideNav.js';
10
10
  export { SideNavItems } from './components/SideNavItems.js';
11
+ export { SideNavLink } from './components/SideNavLink.js';
12
+ export { SideNavLinkPopover } from './components/SideNavLinkPopover.js';
11
13
  export { SideNavMenu } from './components/SideNavMenu.js';
12
14
  export { SideNavMenuItem } from './components/SideNavMenuItem.js';
13
15
  export { HeaderPanel } from './components/HeaderPanel';
package/lib/index.js CHANGED
@@ -9,6 +9,8 @@
9
9
 
10
10
  var SideNav = require('./components/SideNav.js');
11
11
  var SideNavItems = require('./components/SideNavItems.js');
12
+ var SideNavLink = require('./components/SideNavLink.js');
13
+ var SideNavLinkPopover = require('./components/SideNavLinkPopover.js');
12
14
  var SideNavMenu = require('./components/SideNavMenu.js');
13
15
  var SideNavMenuItem = require('./components/SideNavMenuItem.js');
14
16
  var HeaderPanel = require('./components/HeaderPanel.js');
@@ -18,6 +20,8 @@ var HeaderPanel = require('./components/HeaderPanel.js');
18
20
  exports.SIDE_NAV_TYPE = SideNav.SIDE_NAV_TYPE;
19
21
  exports.SideNav = SideNav.SideNav;
20
22
  exports.SideNavItems = SideNavItems.SideNavItems;
23
+ exports.SideNavLink = SideNavLink.SideNavLink;
24
+ exports.SideNavLinkPopover = SideNavLinkPopover.SideNavLinkPopover;
21
25
  exports.SideNavMenu = SideNavMenu.SideNavMenu;
22
26
  exports.SideNavMenuItem = SideNavMenuItem.SideNavMenuItem;
23
27
  exports.HeaderPanel = HeaderPanel.HeaderPanel;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@carbon-labs/react-ui-shell",
3
- "version": "0.19.0",
3
+ "version": "0.21.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": "91dde120d99da5e4454f7411733aeabe037e222f"
36
+ "gitHead": "30877a143d6909d98efd0ec5e6aef7fbf2b074be"
37
37
  }
@@ -38,6 +38,7 @@ div:has(.#{$prefix}--header)
38
38
  //----------------------------------------------------------------------------
39
39
  // Treeview Side-nav
40
40
  //----------------------------------------------------------------------------
41
+
41
42
  .#{$prefix}--side-nav__icon:not(.#{$prefix}--side-nav__submenu-chevron) {
42
43
  margin-inline-end: $spacing-05;
43
44
  }
@@ -62,10 +63,69 @@ div:has(.#{$prefix}--header)
62
63
  }
63
64
  }
64
65
 
66
+ // SideNavMenu
67
+ // Level 2
68
+ // without icon
69
+ .#{$prefix}--side-nav__submenu
70
+ + .#{$prefix}--side-nav__menu
71
+ > .#{$prefix}--side-nav__item
72
+ .#{$prefix}--side-nav__submenu {
73
+ padding-inline-start: $spacing-10;
74
+ }
75
+
76
+ // with icon
77
+ .#{$prefix}--side-nav__submenu
78
+ + .#{$prefix}--side-nav__menu
79
+ > .#{$prefix}--side-nav__item--icon
80
+ .#{$prefix}--side-nav__submenu {
81
+ padding-inline-start: $spacing-09;
82
+ }
83
+
84
+ // Nested SideNavMenuItem
85
+ // Level 2
86
+ // without icon
87
+ .#{$prefix}--side-nav__item.#{$prefix}--side-nav__item--icon
88
+ a.#{$prefix}--side-nav__link {
89
+ padding-inline-start: $spacing-10;
90
+ }
91
+
92
+ // with icon
93
+ .#{$prefix}--side-nav__item.#{$prefix}--side-nav__item--icon
94
+ a.#{$prefix}--side-nav__link:has(.#{$prefix}--side-nav__icon) {
95
+ padding-inline-start: $spacing-09;
96
+ }
97
+
98
+ // Level 3
99
+ // without icon parent
100
+ .#{$prefix}--side-nav__menu
101
+ .#{$prefix}--side-nav__menu
102
+ a.#{$prefix}--side-nav__link {
103
+ padding-inline-start: $spacing-11;
104
+ }
105
+
106
+ // with icon parent
107
+ .#{$prefix}--side-nav__menu
108
+ .#{$prefix}--side-nav__item.#{$prefix}--side-nav__item--icon
109
+ a.#{$prefix}--side-nav__link {
110
+ padding-inline-start: $spacing-12;
111
+ }
112
+
113
+ //----------------------------------------------------------------------------
65
114
  // Side-nav Panel
66
115
  //----------------------------------------------------------------------------
67
116
  .#{$prefix}--side-nav--panel {
68
117
  z-index: 7999; /* needs to be below header */
118
+ overflow: visible;
119
+
120
+ .#{$prefix}--side-nav__items {
121
+ display: flex;
122
+ flex-direction: column;
123
+ }
124
+
125
+ .#{$prefix}--side-nav__items,
126
+ .#{$prefix}--side-nav__items:hover {
127
+ overflow: visible;
128
+ }
69
129
 
70
130
  .#{$prefix}--side-nav__icon:not(.#{$prefix}--side-nav__submenu-chevron) {
71
131
  margin-inline-end: $spacing-05;
@@ -82,6 +142,7 @@ div:has(.#{$prefix}--header)
82
142
  padding-inline-start: $spacing-10;
83
143
  }
84
144
 
145
+ // Side Nav Toggle
85
146
  .#{$prefix}--side-nav__toggle {
86
147
  @include button-reset.reset($width: true);
87
148
  @include type-style('heading-compact-01');
@@ -110,4 +171,15 @@ div:has(.#{$prefix}--header)
110
171
 
111
172
  text-align: start;
112
173
  }
174
+
175
+ // Side Nav Link Popover
176
+ .#{$prefix}--side-nav-link-popover.#{$prefix}--btn--icon-only {
177
+ padding: 0;
178
+ block-size: $spacing-07;
179
+ min-block-size: initial;
180
+
181
+ .#{$prefix}--side-nav__icon:not(.#{$prefix}--side-nav__submenu-chevron) {
182
+ margin-inline-end: 0;
183
+ }
184
+ }
113
185
  }