@carbon-labs/react-ui-shell 0.72.0 → 0.74.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.
@@ -19,14 +19,15 @@ const Profile = /*#__PURE__*/React__default.forwardRef(function Profile({
19
19
  children,
20
20
  renderIcon: IconElement,
21
21
  ...rest
22
- }) {
22
+ }, ref) {
23
23
  const prefix = usePrefix();
24
24
  const className = cx({
25
25
  [`${prefix}--profile`]: true,
26
26
  [customClassName]: !!customClassName
27
27
  });
28
28
  return /*#__PURE__*/React__default.createElement(HeaderPopover, _extends({
29
- align: "bottom-right",
29
+ ref: ref,
30
+ align: "bottom-end",
30
31
  className: className
31
32
  }, rest), /*#__PURE__*/React__default.createElement(HeaderPopoverButton, {
32
33
  align: "bottom",
@@ -58,13 +59,14 @@ const ProfileUserInfo = /*#__PURE__*/React__default.forwardRef(function ProfileU
58
59
  name,
59
60
  email,
60
61
  ...rest
61
- }) {
62
+ }, ref) {
62
63
  const prefix = usePrefix();
63
64
  const className = cx({
64
65
  [`${prefix}--profile-user-info`]: true,
65
66
  [customClassName]: !!customClassName
66
67
  });
67
68
  return /*#__PURE__*/React__default.createElement("div", {
69
+ ref: ref,
68
70
  className: className
69
71
  }, /*#__PURE__*/React__default.createElement(UserAvatar, _extends({
70
72
  size: "lg",
@@ -95,13 +97,14 @@ ProfileUserInfo.propTypes = {
95
97
  const ProfileReadOnly = /*#__PURE__*/React__default.forwardRef(function ProfileReadOnly({
96
98
  className: customClassName,
97
99
  items
98
- }) {
100
+ }, ref) {
99
101
  const prefix = usePrefix();
100
102
  const className = cx({
101
103
  [`${prefix}--profile-read-only`]: true,
102
104
  [customClassName]: !!customClassName
103
105
  });
104
106
  return /*#__PURE__*/React__default.createElement("div", {
107
+ ref: ref,
105
108
  className: className
106
109
  }, items?.map((item, index) => /*#__PURE__*/React__default.createElement("div", {
107
110
  className: `${prefix}--profile-read-only__items`,
@@ -9,12 +9,14 @@ import { TranslateWithId } from '../types/common';
9
9
  export declare enum SIDE_NAV_TYPE {
10
10
  DEFAULT = "default",
11
11
  RAIL = "rail",
12
- PANEL = "panel"
12
+ RAIL_PANEL = "panel"
13
13
  }
14
14
  export type TranslationKey = keyof typeof translationIds;
15
15
  export declare const translationIds: {
16
16
  readonly 'collapse.sidenav': "collapse.sidenav";
17
17
  readonly 'expand.sidenav': "expand.sidenav";
18
+ readonly 'enable.autoexpand': "enable.autoexpand";
19
+ readonly 'disable.autoexpand': "disable.autoexpand";
18
20
  };
19
21
  export interface SideNavProps extends ComponentProps<'nav'>, TranslateWithId<TranslationKey> {
20
22
  expanded?: boolean;
@@ -38,6 +40,7 @@ export interface SideNavProps extends ComponentProps<'nav'>, TranslateWithId<Tra
38
40
  isTreeview?: boolean;
39
41
  }
40
42
  interface SideNavContextData {
43
+ autoExpand?: boolean;
41
44
  expanded?: boolean;
42
45
  isRail?: boolean;
43
46
  navType?: SIDE_NAV_TYPE;
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  import { extends as _extends } from '../_virtual/_rollupPluginBabelHelpers.js';
9
- import React__default, { createContext, useState, useRef, isValidElement, useEffect } from 'react';
9
+ import React__default, { createContext, useState, useRef, isValidElement, useCallback, useEffect } from 'react';
10
10
  import cx from 'classnames';
11
11
  import PropTypes from 'prop-types';
12
12
  import { AriaLabelPropType } from '../prop-types/AriaPropTypes.js';
@@ -19,22 +19,28 @@ import { useWindowEvent } from '../internal/useEvent.js';
19
19
  import { useDelayedState } from '../internal/useDelayedState.js';
20
20
  import { breakpoints } from '../node_modules/@carbon/layout/es/index.js';
21
21
  import { useMatchMedia } from '../internal/useMatchMedia.js';
22
- import { SidePanelClose, SidePanelOpen } from '@carbon/icons-react';
22
+ import { PinFilled, Pin, SidePanelClose, SidePanelOpen } from '@carbon/icons-react';
23
23
  import { SideNavToggle } from './SideNavToggle.js';
24
+ import { SideNavDivider } from '@carbon/react';
24
25
 
26
+ var _SideNavDivider;
25
27
  let SIDE_NAV_TYPE = /*#__PURE__*/function (SIDE_NAV_TYPE) {
26
28
  SIDE_NAV_TYPE["DEFAULT"] = "default";
27
29
  SIDE_NAV_TYPE["RAIL"] = "rail";
28
- SIDE_NAV_TYPE["PANEL"] = "panel";
30
+ SIDE_NAV_TYPE["RAIL_PANEL"] = "panel";
29
31
  return SIDE_NAV_TYPE;
30
32
  }({});
31
33
  const translationIds = {
32
34
  'collapse.sidenav': 'collapse.sidenav',
33
- 'expand.sidenav': 'expand.sidenav'
35
+ 'expand.sidenav': 'expand.sidenav',
36
+ 'enable.autoexpand': 'enable.autoexpand',
37
+ 'disable.autoexpand': 'disable.autoexpand'
34
38
  };
35
39
  const defaultTranslations = {
36
- [translationIds['collapse.sidenav']]: 'Collapse',
37
- [translationIds['expand.sidenav']]: 'Expand'
40
+ [translationIds['collapse.sidenav']]: 'Unpin',
41
+ [translationIds['expand.sidenav']]: 'Pin open',
42
+ [translationIds['enable.autoexpand']]: 'Enable auto-expand',
43
+ [translationIds['disable.autoexpand']]: 'Disable auto-expand'
38
44
  };
39
45
  const defaultTranslateWithId = id => defaultTranslations[id];
40
46
  const SideNavContext = /*#__PURE__*/createContext({});
@@ -69,13 +75,16 @@ function SideNavRenderFunction({
69
75
  const {
70
76
  current: controlled
71
77
  } = useRef(expandedProp !== undefined);
78
+ const [autoExpand, setAutoExpand] = useState(false);
72
79
  const [expandedState, setExpandedState] = useDelayedState(defaultExpanded);
73
80
  const [expandedViaHoverState, setExpandedViaHoverState] = useDelayedState(defaultExpanded);
81
+ const [pinned, setPinned] = useState(false);
74
82
  const expanded = controlled ? expandedProp : expandedState;
75
83
  const sideNavRef = useRef(null);
76
84
  const navRef = useMergedRefs([sideNavRef, ref]);
77
85
  const [currentPrimaryMenu, setCurrentPrimaryMenu] = useState();
78
- const sideNavToggleText = expandedState ? t('collapse.sidenav') : t('expand.sidenav');
86
+ const pinText = pinned ? t('collapse.sidenav') : t('expand.sidenav');
87
+ const autoExpandText = autoExpand ? t('disable.autoexpand') : t('enable.autoexpand');
79
88
  const handleToggle = (event, value = !expanded) => {
80
89
  if (!controlled) {
81
90
  setExpandedState(value, enterDelayMs);
@@ -96,8 +105,9 @@ function SideNavRenderFunction({
96
105
  [`${prefix}--side-nav--expanded`]: expanded || expandedViaHoverState,
97
106
  [`${prefix}--side-nav--collapsed`]: !expanded && isFixedNav,
98
107
  [`${prefix}--side-nav--hide-rail-breakpoint-down-${hideRailBreakpointDown}`]: hideRailBreakpointDown,
99
- [`${prefix}--side-nav--rail`]: isRail,
100
- [`${prefix}--side-nav--panel`]: navType === SIDE_NAV_TYPE.PANEL,
108
+ [`${prefix}--side-nav--rail`]: isRail || autoExpand,
109
+ [`${prefix}--side-nav--panel`]: navType === SIDE_NAV_TYPE.RAIL_PANEL,
110
+ [`${prefix}--side-nav--pinned`]: pinned,
101
111
  [`${prefix}--side-nav--ux`]: isChildOfHeader,
102
112
  [`${prefix}--side-nav--hidden`]: !isPersistent,
103
113
  [`${prefix}--side-nav--collapsible`]: isCollapsible,
@@ -128,6 +138,15 @@ function SideNavRenderFunction({
128
138
  return child;
129
139
  });
130
140
  const eventHandlers = {};
141
+ const resetNodeTabIndices = useCallback(() => {
142
+ const items = sideNavRef?.current?.querySelectorAll('[tabIndex="0"]') ?? [];
143
+ items.forEach(item => {
144
+ if (item.classList.contains(`${prefix}--side-nav__toggle`) || item.classList.contains(`${prefix}--side-nav__back-button`) || item.closest(`.${prefix}--side-nav__slot-item`) || item.classList.contains(`${prefix}--side-nav__link`) && item.closest('ul')?.getAttribute('aria-label') === ariaLabel) {
145
+ return;
146
+ }
147
+ item.tabIndex = -1;
148
+ });
149
+ }, [prefix, ariaLabel]);
131
150
  const treeWalkerRef = useRef(null);
132
151
  useEffect(() => {
133
152
  if (internalIsTreeview) {
@@ -147,7 +166,7 @@ function SideNavRenderFunction({
147
166
  });
148
167
  resetNodeTabIndices();
149
168
  }
150
- }, [prefix, internalIsTreeview]);
169
+ }, [prefix, internalIsTreeview, resetNodeTabIndices]);
151
170
  const smMediaQuery = `(min-width: ${breakpoints.sm.width})`;
152
171
  const isSm = useMatchMedia(smMediaQuery);
153
172
  useEffect(() => {
@@ -156,7 +175,7 @@ function SideNavRenderFunction({
156
175
  const slotElement = sideNavRef?.current.querySelector(`.${prefix}--side-nav__slot`);
157
176
  const firstElement = sideNavRef?.current?.querySelector('a, button');
158
177
  const currentElement = sideNavRef?.current?.querySelector(`.cds--side-nav__link--current`);
159
- if (navType == SIDE_NAV_TYPE.PANEL || expanded) {
178
+ if (navType == SIDE_NAV_TYPE.RAIL_PANEL || expanded) {
160
179
  if (isSm && backButton) {
161
180
  backButton.tabIndex = 0;
162
181
  const firstElementAfterBack = backButton.nextElementSibling?.querySelector('a, button');
@@ -176,7 +195,7 @@ function SideNavRenderFunction({
176
195
  }
177
196
  }
178
197
  }
179
- }, [expanded]);
198
+ }, [expanded, currentPrimaryMenu, isSm, navType, prefix]);
180
199
 
181
200
  /**
182
201
  * Returns the parent SideNavMenu, if node is actually inside one.
@@ -192,12 +211,12 @@ function SideNavRenderFunction({
192
211
  }
193
212
  if (addFocusListeners) {
194
213
  eventHandlers.onFocus = event => {
195
- if (!event.currentTarget.contains(event.relatedTarget) && isRail) {
214
+ if (!event.currentTarget.contains(event.relatedTarget) && (isRail || autoExpand)) {
196
215
  handleToggle(event, true);
197
216
  }
198
217
  };
199
218
  eventHandlers.onBlur = event => {
200
- if (navType === SIDE_NAV_TYPE.PANEL) {
219
+ if (navType === SIDE_NAV_TYPE.RAIL_PANEL) {
201
220
  return;
202
221
  }
203
222
  if (!event.currentTarget.contains(event.relatedTarget)) {
@@ -325,7 +344,7 @@ function SideNavRenderFunction({
325
344
  }
326
345
  };
327
346
  }
328
- if (addMouseListeners && isRail) {
347
+ if (addMouseListeners && !pinned && (isRail || autoExpand)) {
329
348
  eventHandlers.onMouseEnter = () => {
330
349
  handleToggle(true, true);
331
350
  };
@@ -335,6 +354,9 @@ function SideNavRenderFunction({
335
354
  handleToggle(false, false);
336
355
  };
337
356
  eventHandlers.onClick = () => {
357
+ if (autoExpand) {
358
+ return;
359
+ }
338
360
  //if delay is enabled, and user intentionally clicks it to see it expanded immediately
339
361
  setExpandedState(true);
340
362
  setExpandedViaHoverState(true);
@@ -345,7 +367,7 @@ function SideNavRenderFunction({
345
367
  const target = event.target;
346
368
  const isNavItemClick = target.closest(`.${prefix}--side-nav a, .${prefix}--side-nav button`);
347
369
  const isInRail = isNavItemClick?.closest(`.${prefix}--side-nav--rail`);
348
- if (isNavItemClick && !isNavItemClick.classList.contains(`${prefix}--side-nav__submenu`) && !isNavItemClick.classList.contains(`${prefix}--side-nav__back-button`)) {
370
+ if (isNavItemClick && !isNavItemClick.classList.contains(`${prefix}--side-nav__submenu`) && !isNavItemClick.classList.contains(`${prefix}--side-nav__back-button`) && !isNavItemClick.classList.contains(`${prefix}--side-nav__toggle`)) {
349
371
  isInRail ? handleToggle(false, false) : onSideNavBlur?.();
350
372
  }
351
373
  });
@@ -359,15 +381,6 @@ function SideNavRenderFunction({
359
381
  });
360
382
  const lgMediaQuery = `(min-width: ${breakpoints.lg.width})`;
361
383
  const isLg = useMatchMedia(lgMediaQuery);
362
- function resetNodeTabIndices() {
363
- const items = sideNavRef?.current?.querySelectorAll('[tabIndex="0"]') ?? [];
364
- items.forEach(item => {
365
- if (item.classList.contains(`${prefix}--side-nav__toggle`) || item.classList.contains(`${prefix}--side-nav__back-button`) || item.closest(`.${prefix}--side-nav__slot-item`) || item.classList.contains(`${prefix}--side-nav__link`) && item.closest('ul')?.getAttribute('aria-label') === ariaLabel) {
366
- return;
367
- }
368
- item.tabIndex = -1;
369
- });
370
- }
371
384
 
372
385
  // ensure that changes are in sync with internal treeview prop
373
386
  useEffect(() => {
@@ -382,13 +395,22 @@ function SideNavRenderFunction({
382
395
  setInternalIsTreeview(value);
383
396
  }
384
397
  };
385
- const SideNavToggleButton = /*#__PURE__*/React__default.createElement(SideNavToggle, {
386
- className: !expandedState ? `${prefix}--side-nav__toggle--collapsed` : '',
387
- renderIcon: expandedState ? SidePanelClose : SidePanelOpen,
388
- onClick: () => setExpandedState(!expandedState)
389
- }, sideNavToggleText);
398
+ const handlePinClick = () => {
399
+ setPinned(!pinned);
400
+ if (!autoExpand) {
401
+ setExpandedState(!pinned);
402
+ }
403
+ };
404
+ const handleAutoExpand = () => {
405
+ if (pinned) {
406
+ return;
407
+ }
408
+ setExpandedState(!autoExpand);
409
+ setAutoExpand(!autoExpand);
410
+ };
390
411
  return /*#__PURE__*/React__default.createElement(SideNavContext.Provider, {
391
412
  value: {
413
+ autoExpand,
392
414
  expanded,
393
415
  isRail,
394
416
  navType,
@@ -397,7 +419,7 @@ function SideNavRenderFunction({
397
419
  currentPrimaryMenu,
398
420
  setCurrentPrimaryMenu
399
421
  }
400
- }, isFixedNav || hideOverlay || navType === SIDE_NAV_TYPE.PANEL ? null :
422
+ }, isFixedNav || hideOverlay || navType === SIDE_NAV_TYPE.RAIL_PANEL ? null :
401
423
  /*#__PURE__*/
402
424
  // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
403
425
  React__default.createElement("div", {
@@ -408,10 +430,17 @@ function SideNavRenderFunction({
408
430
  tabIndex: -1,
409
431
  ref: navRef,
410
432
  className: `${prefix}--side-nav__navigation ${className}`,
411
- inert: !isRail && navType !== SIDE_NAV_TYPE.PANEL && !(expanded || isLg) ? -1 : undefined
412
- }, accessibilityLabel, eventHandlers, other), childrenToRender, navType === SIDE_NAV_TYPE.PANEL && (expandedState ? SideNavToggleButton : /*#__PURE__*/React__default.createElement("div", {
433
+ inert: !isRail && navType !== SIDE_NAV_TYPE.RAIL_PANEL && !(expanded || isLg) ? -1 : undefined
434
+ }, accessibilityLabel, eventHandlers, other), childrenToRender, navType === SIDE_NAV_TYPE.RAIL_PANEL && /*#__PURE__*/React__default.createElement("ul", {
413
435
  className: `${prefix}--side-nav__toggle-container`
414
- }, SideNavToggleButton))));
436
+ }, _SideNavDivider || (_SideNavDivider = /*#__PURE__*/React__default.createElement(SideNavDivider, null)), /*#__PURE__*/React__default.createElement(SideNavToggle, {
437
+ onClick: handlePinClick,
438
+ renderIcon: pinned ? PinFilled : Pin
439
+ }, pinText), /*#__PURE__*/React__default.createElement(SideNavToggle, {
440
+ disabled: pinned,
441
+ renderIcon: expandedState ? SidePanelClose : SidePanelOpen,
442
+ onClick: handleAutoExpand
443
+ }, autoExpandText))));
415
444
  }
416
445
  const SideNav = /*#__PURE__*/React__default.forwardRef(SideNavRenderFunction);
417
446
  SideNav.displayName = 'SideNav';
@@ -12,7 +12,7 @@ import React__default, { forwardRef, useContext } from 'react';
12
12
  import Link, { LinkPropTypes } from './Link.js';
13
13
  import { SideNavItem, SideNavLinkText, SideNavIcon } from '@carbon/react';
14
14
  import { usePrefix } from '../internal/usePrefix.js';
15
- import { SideNavContext } from './SideNav.js';
15
+ import { SideNavContext, SIDE_NAV_TYPE } from './SideNav.js';
16
16
  import { SideNavLinkPopover } from './SideNavLinkPopover.js';
17
17
 
18
18
  const SideNavLink = /*#__PURE__*/forwardRef(function SideNavLink({
@@ -39,7 +39,7 @@ const SideNavLink = /*#__PURE__*/forwardRef(function SideNavLink({
39
39
  const SideNavLinkIcon = IconElement && /*#__PURE__*/React__default.createElement(SideNavIcon, {
40
40
  small: true
41
41
  }, /*#__PURE__*/React__default.createElement(IconElement, null));
42
- if (!expanded && navType === 'panel') {
42
+ if (!expanded && navType === SIDE_NAV_TYPE.RAIL_PANEL) {
43
43
  return /*#__PURE__*/React__default.createElement(SideNavLinkPopover, _extends({
44
44
  align: "right",
45
45
  label: children
@@ -62,6 +62,7 @@ const SideNavMenu = /*#__PURE__*/React__default.forwardRef(function SideNavMenu(
62
62
  const [prevExpanded, setPrevExpanded] = useState(defaultExpanded);
63
63
  const [isSecondaryOpen, setSecondaryOpen] = useState(defaultExpanded);
64
64
  const {
65
+ autoExpand,
65
66
  currentPrimaryMenu,
66
67
  setCurrentPrimaryMenu
67
68
  } = useContext(SideNavContext);
@@ -148,7 +149,7 @@ const SideNavMenu = /*#__PURE__*/React__default.forwardRef(function SideNavMenu(
148
149
  return false;
149
150
  }
150
151
  useEffect(() => {
151
- if (navType == SIDE_NAV_TYPE.PANEL) {
152
+ if (navType == SIDE_NAV_TYPE.RAIL_PANEL) {
152
153
  // grab first link to redirect if clicked when not expanded
153
154
  if (!firstLink?.current && listRef?.current) {
154
155
  const firstLinkElement = listRef.current.querySelector(`.${prefix}--side-nav__menu-item a`);
@@ -282,7 +283,7 @@ const SideNavMenu = /*#__PURE__*/React__default.forwardRef(function SideNavMenu(
282
283
  }, [currentPrimaryMenu]);
283
284
  // reset to opened/collapsed menu state when Panel SideNav is toggled
284
285
  useEffect(() => {
285
- if (navType == SIDE_NAV_TYPE.PANEL && !sideNavExpanded) {
286
+ if (navType == SIDE_NAV_TYPE.RAIL_PANEL && !sideNavExpanded) {
286
287
  setIsExpanded(false);
287
288
  }
288
289
 
@@ -319,7 +320,7 @@ const SideNavMenu = /*#__PURE__*/React__default.forwardRef(function SideNavMenu(
319
320
  }
320
321
 
321
322
  // only when sidenav is panel view
322
- if (navType == SIDE_NAV_TYPE.PANEL && !isExpanded && firstLink.current && !sideNavExpanded) {
323
+ if (navType == SIDE_NAV_TYPE.RAIL_PANEL && !isExpanded && firstLink.current && !sideNavExpanded) {
323
324
  setOpenPopover(!openPopover);
324
325
  // window.location.href = firstLink.current;
325
326
  } else if (isSm || !primary || currentPrimaryMenu !== uniqueId) {
@@ -332,7 +333,7 @@ const SideNavMenu = /*#__PURE__*/React__default.forwardRef(function SideNavMenu(
332
333
  ref: menuRef,
333
334
  type: "button",
334
335
  tabIndex: isTreeview ? -1 : 0
335
- }, IconElement && /*#__PURE__*/React__default.createElement(SideNavIcon, null, /*#__PURE__*/React__default.createElement(IconElement, null)), !sideNavExpanded && navType == SIDE_NAV_TYPE.PANEL && /*#__PURE__*/React__default.createElement("div", {
336
+ }, IconElement && /*#__PURE__*/React__default.createElement(SideNavIcon, null, /*#__PURE__*/React__default.createElement(IconElement, null)), !autoExpand && !sideNavExpanded && navType == SIDE_NAV_TYPE.RAIL_PANEL && /*#__PURE__*/React__default.createElement("div", {
336
337
  className: `${prefix}--side-nav--panel-submenu-caret-container`
337
338
  }, /*#__PURE__*/React__default.createElement("div", {
338
339
  className: `${prefix}--side-nav--panel-submenu-caret`
@@ -362,7 +363,7 @@ const SideNavMenu = /*#__PURE__*/React__default.forwardRef(function SideNavMenu(
362
363
  className: `${prefix}--side-nav__menu`,
363
364
  role: "group"
364
365
  }, childrenToRender));
365
- return navType == SIDE_NAV_TYPE.PANEL && !sideNavExpanded ? /*#__PURE__*/React__default.createElement(SideNavFlyoutMenu, {
366
+ return navType == SIDE_NAV_TYPE.RAIL_PANEL && !sideNavExpanded ? /*#__PURE__*/React__default.createElement(SideNavFlyoutMenu, {
366
367
  selected: active,
367
368
  className: `${prefix}--side-nav-flyout-menu`,
368
369
  title: title,
@@ -4,29 +4,54 @@
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, { ReactNode } from 'react';
8
- interface SideNavToggleProps {
7
+ import PropTypes from 'prop-types';
8
+ import { ComponentType, ElementType, ForwardedRef, ReactNode } from 'react';
9
+ import { LinkProps } from './Link';
10
+ export type SideNavToggleProps<E extends ElementType> = LinkProps<E> & {
9
11
  /**
10
- * Specify an optional className to be applied to the button node
12
+ * Required props for the accessibility label
11
13
  */
12
- className?: string;
14
+ 'aria-label'?: string;
15
+ /**
16
+ * Required props for the accessibility label
17
+ */
18
+ 'aria-labelledby'?: string;
13
19
  /**
14
20
  * Specify the text content for the link
15
21
  */
16
- children: ReactNode;
22
+ children?: ReactNode;
23
+ /**
24
+ * Provide an optional class to be applied to the containing node
25
+ */
26
+ className?: string;
27
+ /**
28
+ * Specify whether the link is the current page
29
+ */
30
+ isActive?: boolean;
31
+ /**
32
+ * Property to indicate if the side nav container is open (or not). Use to
33
+ * keep local state and styling in step with the SideNav expansion state.
34
+ */
35
+ isSideNavExpanded?: boolean;
17
36
  /**
18
- * Provide an optional function to be called when the item is clicked.
37
+ * Specify if this is a large variation of the SideNavLink
19
38
  */
20
- onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
39
+ large?: boolean;
21
40
  /**
22
- * A custom icon to render next to the SideNavToggle title. This can be a function returning JSX or JSX itself.
41
+ * Provide an icon to render in the side navigation link. Should be a React class.
23
42
  */
24
- renderIcon?: React.ComponentType;
43
+ renderIcon?: ComponentType;
25
44
  /**
26
- * The tabIndex for the button element.
27
- * If not specified, the default validation will be applied.
45
+ * Optional prop to specify the tabIndex of the button. If undefined, it will be applied default validation
28
46
  */
29
47
  tabIndex?: number;
48
+ };
49
+ export interface SideNavLinkComponent {
50
+ (props: SideNavToggleProps<'button'> & {
51
+ ref?: ForwardedRef<HTMLButtonElement>;
52
+ }): JSX.Element | null;
53
+ displayName?: string;
54
+ propTypes?: Partial<Record<keyof SideNavToggleProps<any>, PropTypes.Validator<any>>>;
30
55
  }
31
- export declare const SideNavToggle: React.ForwardRefExoticComponent<SideNavToggleProps & React.RefAttributes<HTMLElement>>;
56
+ export declare const SideNavToggle: SideNavLinkComponent;
32
57
  export default SideNavToggle;
@@ -8,47 +8,79 @@
8
8
  import { extends as _extends } from '../_virtual/_rollupPluginBabelHelpers.js';
9
9
  import cx from 'classnames';
10
10
  import PropTypes from 'prop-types';
11
- import React__default from 'react';
12
- import { SideNavIcon } from '@carbon/react';
11
+ import React__default, { forwardRef, useContext } from 'react';
12
+ import { LinkPropTypes } from './Link.js';
13
+ import { SideNavItem, SideNavLinkText, SideNavIcon } from '@carbon/react';
13
14
  import { usePrefix } from '../internal/usePrefix.js';
15
+ import { SideNavContext, SIDE_NAV_TYPE } from './SideNav.js';
16
+ import { SideNavLinkPopover } from './SideNavLinkPopover.js';
14
17
 
15
- const SideNavToggle = /*#__PURE__*/React__default.forwardRef(function SideNavToggle({
18
+ const SideNavToggle = /*#__PURE__*/forwardRef(function SideNavToggle({
19
+ children,
16
20
  className: customClassName,
21
+ disabled,
17
22
  renderIcon: IconElement,
23
+ large = false,
18
24
  tabIndex,
19
- children,
20
25
  ...rest
21
26
  }, ref) {
27
+ const {
28
+ expanded,
29
+ navType
30
+ } = useContext(SideNavContext);
22
31
  const prefix = usePrefix();
23
- return /*#__PURE__*/React__default.createElement("button", _extends({
24
- className: cx(customClassName, {
25
- [`${prefix}--side-nav__toggle`]: true
26
- }),
32
+ const className = cx({
33
+ [`${prefix}--side-nav__toggle`]: true,
34
+ [`${prefix}--side-nav__toggle--disabled`]: disabled,
35
+ [customClassName]: !!customClassName
36
+ });
37
+ const SideNavLinkIcon = IconElement && /*#__PURE__*/React__default.createElement(SideNavIcon, {
38
+ small: true
39
+ }, /*#__PURE__*/React__default.createElement(IconElement, null));
40
+ if (!expanded && navType === SIDE_NAV_TYPE.RAIL_PANEL) {
41
+ return /*#__PURE__*/React__default.createElement(SideNavLinkPopover, _extends({
42
+ align: "right",
43
+ className: className,
44
+ label: children
45
+ }, rest), SideNavLinkIcon);
46
+ }
47
+ return /*#__PURE__*/React__default.createElement(SideNavItem, {
48
+ large: large
49
+ }, /*#__PURE__*/React__default.createElement("button", _extends({
50
+ className: className,
27
51
  ref: ref,
28
52
  type: "button",
29
- tabIndex: tabIndex ?? 0
30
- }, rest), IconElement && /*#__PURE__*/React__default.createElement(SideNavIcon, null, /*#__PURE__*/React__default.createElement(IconElement, null)), /*#__PURE__*/React__default.createElement("span", {
31
- className: `${prefix}--side-nav__toggle-text`
32
- }, children));
53
+ tabIndex: tabIndex ?? 0,
54
+ disabled: disabled
55
+ }, rest), SideNavLinkIcon, /*#__PURE__*/React__default.createElement(SideNavLinkText, null, children)));
33
56
  });
34
57
  SideNavToggle.displayName = 'SideNavToggle';
35
58
  SideNavToggle.propTypes = {
59
+ ...LinkPropTypes,
36
60
  /**
37
- * Specify the text content for the toggle
61
+ * Specify the text content for the link
38
62
  */
39
63
  children: PropTypes.node,
40
64
  /**
41
- * Specify an optional className to be applied to the button node
65
+ * Provide an optional class to be applied to the containing node
42
66
  */
43
67
  className: PropTypes.string,
44
68
  /**
45
- * Provide an optional function to be called when clicked
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
46
79
  */
47
- onClick: PropTypes.func,
80
+ large: PropTypes.bool,
48
81
  /**
49
- * Pass in a custom icon to render next to the `SideNavToggle` title
82
+ * Provide an icon to render in the side navigation link. Should be a React class.
50
83
  */
51
- // @ts-expect-error - PropTypes are unable to cover this case.
52
84
  renderIcon: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
53
85
  /**
54
86
  * Optional prop to specify the tabIndex of the button. If undefined, it will be applied default validation
@@ -21,14 +21,15 @@ const Profile = /*#__PURE__*/React__default.forwardRef(function Profile({
21
21
  children,
22
22
  renderIcon: IconElement,
23
23
  ...rest
24
- }) {
24
+ }, ref) {
25
25
  const prefix = usePrefix.usePrefix();
26
26
  const className = cx({
27
27
  [`${prefix}--profile`]: true,
28
28
  [customClassName]: !!customClassName
29
29
  });
30
30
  return /*#__PURE__*/React__default.createElement(HeaderPopover.HeaderPopover, _rollupPluginBabelHelpers.extends({
31
- align: "bottom-right",
31
+ ref: ref,
32
+ align: "bottom-end",
32
33
  className: className
33
34
  }, rest), /*#__PURE__*/React__default.createElement(HeaderPopover.HeaderPopoverButton, {
34
35
  align: "bottom",
@@ -60,13 +61,14 @@ const ProfileUserInfo = /*#__PURE__*/React__default.forwardRef(function ProfileU
60
61
  name,
61
62
  email,
62
63
  ...rest
63
- }) {
64
+ }, ref) {
64
65
  const prefix = usePrefix.usePrefix();
65
66
  const className = cx({
66
67
  [`${prefix}--profile-user-info`]: true,
67
68
  [customClassName]: !!customClassName
68
69
  });
69
70
  return /*#__PURE__*/React__default.createElement("div", {
71
+ ref: ref,
70
72
  className: className
71
73
  }, /*#__PURE__*/React__default.createElement(UserAvatar.UserAvatar, _rollupPluginBabelHelpers.extends({
72
74
  size: "lg",
@@ -97,13 +99,14 @@ ProfileUserInfo.propTypes = {
97
99
  const ProfileReadOnly = /*#__PURE__*/React__default.forwardRef(function ProfileReadOnly({
98
100
  className: customClassName,
99
101
  items
100
- }) {
102
+ }, ref) {
101
103
  const prefix = usePrefix.usePrefix();
102
104
  const className = cx({
103
105
  [`${prefix}--profile-read-only`]: true,
104
106
  [customClassName]: !!customClassName
105
107
  });
106
108
  return /*#__PURE__*/React__default.createElement("div", {
109
+ ref: ref,
107
110
  className: className
108
111
  }, items?.map((item, index) => /*#__PURE__*/React__default.createElement("div", {
109
112
  className: `${prefix}--profile-read-only__items`,
@@ -9,12 +9,14 @@ import { TranslateWithId } from '../types/common';
9
9
  export declare enum SIDE_NAV_TYPE {
10
10
  DEFAULT = "default",
11
11
  RAIL = "rail",
12
- PANEL = "panel"
12
+ RAIL_PANEL = "panel"
13
13
  }
14
14
  export type TranslationKey = keyof typeof translationIds;
15
15
  export declare const translationIds: {
16
16
  readonly 'collapse.sidenav': "collapse.sidenav";
17
17
  readonly 'expand.sidenav': "expand.sidenav";
18
+ readonly 'enable.autoexpand': "enable.autoexpand";
19
+ readonly 'disable.autoexpand': "disable.autoexpand";
18
20
  };
19
21
  export interface SideNavProps extends ComponentProps<'nav'>, TranslateWithId<TranslationKey> {
20
22
  expanded?: boolean;
@@ -38,6 +40,7 @@ export interface SideNavProps extends ComponentProps<'nav'>, TranslateWithId<Tra
38
40
  isTreeview?: boolean;
39
41
  }
40
42
  interface SideNavContextData {
43
+ autoExpand?: boolean;
41
44
  expanded?: boolean;
42
45
  isRail?: boolean;
43
46
  navType?: SIDE_NAV_TYPE;
@@ -23,20 +23,26 @@ var index = require('../node_modules/@carbon/layout/es/index.js');
23
23
  var useMatchMedia = require('../internal/useMatchMedia.js');
24
24
  var iconsReact = require('@carbon/icons-react');
25
25
  var SideNavToggle = require('./SideNavToggle.js');
26
+ var react = require('@carbon/react');
26
27
 
28
+ var _SideNavDivider;
27
29
  let SIDE_NAV_TYPE = /*#__PURE__*/function (SIDE_NAV_TYPE) {
28
30
  SIDE_NAV_TYPE["DEFAULT"] = "default";
29
31
  SIDE_NAV_TYPE["RAIL"] = "rail";
30
- SIDE_NAV_TYPE["PANEL"] = "panel";
32
+ SIDE_NAV_TYPE["RAIL_PANEL"] = "panel";
31
33
  return SIDE_NAV_TYPE;
32
34
  }({});
33
35
  const translationIds = {
34
36
  'collapse.sidenav': 'collapse.sidenav',
35
- 'expand.sidenav': 'expand.sidenav'
37
+ 'expand.sidenav': 'expand.sidenav',
38
+ 'enable.autoexpand': 'enable.autoexpand',
39
+ 'disable.autoexpand': 'disable.autoexpand'
36
40
  };
37
41
  const defaultTranslations = {
38
- [translationIds['collapse.sidenav']]: 'Collapse',
39
- [translationIds['expand.sidenav']]: 'Expand'
42
+ [translationIds['collapse.sidenav']]: 'Unpin',
43
+ [translationIds['expand.sidenav']]: 'Pin open',
44
+ [translationIds['enable.autoexpand']]: 'Enable auto-expand',
45
+ [translationIds['disable.autoexpand']]: 'Disable auto-expand'
40
46
  };
41
47
  const defaultTranslateWithId = id => defaultTranslations[id];
42
48
  const SideNavContext = /*#__PURE__*/React__default.createContext({});
@@ -71,13 +77,16 @@ function SideNavRenderFunction({
71
77
  const {
72
78
  current: controlled
73
79
  } = React__default.useRef(expandedProp !== undefined);
80
+ const [autoExpand, setAutoExpand] = React__default.useState(false);
74
81
  const [expandedState, setExpandedState] = useDelayedState.useDelayedState(defaultExpanded);
75
82
  const [expandedViaHoverState, setExpandedViaHoverState] = useDelayedState.useDelayedState(defaultExpanded);
83
+ const [pinned, setPinned] = React__default.useState(false);
76
84
  const expanded = controlled ? expandedProp : expandedState;
77
85
  const sideNavRef = React__default.useRef(null);
78
86
  const navRef = useMergedRefs.useMergedRefs([sideNavRef, ref]);
79
87
  const [currentPrimaryMenu, setCurrentPrimaryMenu] = React__default.useState();
80
- const sideNavToggleText = expandedState ? t('collapse.sidenav') : t('expand.sidenav');
88
+ const pinText = pinned ? t('collapse.sidenav') : t('expand.sidenav');
89
+ const autoExpandText = autoExpand ? t('disable.autoexpand') : t('enable.autoexpand');
81
90
  const handleToggle = (event, value = !expanded) => {
82
91
  if (!controlled) {
83
92
  setExpandedState(value, enterDelayMs);
@@ -98,8 +107,9 @@ function SideNavRenderFunction({
98
107
  [`${prefix}--side-nav--expanded`]: expanded || expandedViaHoverState,
99
108
  [`${prefix}--side-nav--collapsed`]: !expanded && isFixedNav,
100
109
  [`${prefix}--side-nav--hide-rail-breakpoint-down-${hideRailBreakpointDown}`]: hideRailBreakpointDown,
101
- [`${prefix}--side-nav--rail`]: isRail,
102
- [`${prefix}--side-nav--panel`]: navType === SIDE_NAV_TYPE.PANEL,
110
+ [`${prefix}--side-nav--rail`]: isRail || autoExpand,
111
+ [`${prefix}--side-nav--panel`]: navType === SIDE_NAV_TYPE.RAIL_PANEL,
112
+ [`${prefix}--side-nav--pinned`]: pinned,
103
113
  [`${prefix}--side-nav--ux`]: isChildOfHeader,
104
114
  [`${prefix}--side-nav--hidden`]: !isPersistent,
105
115
  [`${prefix}--side-nav--collapsible`]: isCollapsible,
@@ -130,6 +140,15 @@ function SideNavRenderFunction({
130
140
  return child;
131
141
  });
132
142
  const eventHandlers = {};
143
+ const resetNodeTabIndices = React__default.useCallback(() => {
144
+ const items = sideNavRef?.current?.querySelectorAll('[tabIndex="0"]') ?? [];
145
+ items.forEach(item => {
146
+ if (item.classList.contains(`${prefix}--side-nav__toggle`) || item.classList.contains(`${prefix}--side-nav__back-button`) || item.closest(`.${prefix}--side-nav__slot-item`) || item.classList.contains(`${prefix}--side-nav__link`) && item.closest('ul')?.getAttribute('aria-label') === ariaLabel) {
147
+ return;
148
+ }
149
+ item.tabIndex = -1;
150
+ });
151
+ }, [prefix, ariaLabel]);
133
152
  const treeWalkerRef = React__default.useRef(null);
134
153
  React__default.useEffect(() => {
135
154
  if (internalIsTreeview) {
@@ -149,7 +168,7 @@ function SideNavRenderFunction({
149
168
  });
150
169
  resetNodeTabIndices();
151
170
  }
152
- }, [prefix, internalIsTreeview]);
171
+ }, [prefix, internalIsTreeview, resetNodeTabIndices]);
153
172
  const smMediaQuery = `(min-width: ${index.breakpoints.sm.width})`;
154
173
  const isSm = useMatchMedia.useMatchMedia(smMediaQuery);
155
174
  React__default.useEffect(() => {
@@ -158,7 +177,7 @@ function SideNavRenderFunction({
158
177
  const slotElement = sideNavRef?.current.querySelector(`.${prefix}--side-nav__slot`);
159
178
  const firstElement = sideNavRef?.current?.querySelector('a, button');
160
179
  const currentElement = sideNavRef?.current?.querySelector(`.cds--side-nav__link--current`);
161
- if (navType == SIDE_NAV_TYPE.PANEL || expanded) {
180
+ if (navType == SIDE_NAV_TYPE.RAIL_PANEL || expanded) {
162
181
  if (isSm && backButton) {
163
182
  backButton.tabIndex = 0;
164
183
  const firstElementAfterBack = backButton.nextElementSibling?.querySelector('a, button');
@@ -178,7 +197,7 @@ function SideNavRenderFunction({
178
197
  }
179
198
  }
180
199
  }
181
- }, [expanded]);
200
+ }, [expanded, currentPrimaryMenu, isSm, navType, prefix]);
182
201
 
183
202
  /**
184
203
  * Returns the parent SideNavMenu, if node is actually inside one.
@@ -194,12 +213,12 @@ function SideNavRenderFunction({
194
213
  }
195
214
  if (addFocusListeners) {
196
215
  eventHandlers.onFocus = event => {
197
- if (!event.currentTarget.contains(event.relatedTarget) && isRail) {
216
+ if (!event.currentTarget.contains(event.relatedTarget) && (isRail || autoExpand)) {
198
217
  handleToggle(event, true);
199
218
  }
200
219
  };
201
220
  eventHandlers.onBlur = event => {
202
- if (navType === SIDE_NAV_TYPE.PANEL) {
221
+ if (navType === SIDE_NAV_TYPE.RAIL_PANEL) {
203
222
  return;
204
223
  }
205
224
  if (!event.currentTarget.contains(event.relatedTarget)) {
@@ -327,7 +346,7 @@ function SideNavRenderFunction({
327
346
  }
328
347
  };
329
348
  }
330
- if (addMouseListeners && isRail) {
349
+ if (addMouseListeners && !pinned && (isRail || autoExpand)) {
331
350
  eventHandlers.onMouseEnter = () => {
332
351
  handleToggle(true, true);
333
352
  };
@@ -337,6 +356,9 @@ function SideNavRenderFunction({
337
356
  handleToggle(false, false);
338
357
  };
339
358
  eventHandlers.onClick = () => {
359
+ if (autoExpand) {
360
+ return;
361
+ }
340
362
  //if delay is enabled, and user intentionally clicks it to see it expanded immediately
341
363
  setExpandedState(true);
342
364
  setExpandedViaHoverState(true);
@@ -347,7 +369,7 @@ function SideNavRenderFunction({
347
369
  const target = event.target;
348
370
  const isNavItemClick = target.closest(`.${prefix}--side-nav a, .${prefix}--side-nav button`);
349
371
  const isInRail = isNavItemClick?.closest(`.${prefix}--side-nav--rail`);
350
- if (isNavItemClick && !isNavItemClick.classList.contains(`${prefix}--side-nav__submenu`) && !isNavItemClick.classList.contains(`${prefix}--side-nav__back-button`)) {
372
+ if (isNavItemClick && !isNavItemClick.classList.contains(`${prefix}--side-nav__submenu`) && !isNavItemClick.classList.contains(`${prefix}--side-nav__back-button`) && !isNavItemClick.classList.contains(`${prefix}--side-nav__toggle`)) {
351
373
  isInRail ? handleToggle(false, false) : onSideNavBlur?.();
352
374
  }
353
375
  });
@@ -361,15 +383,6 @@ function SideNavRenderFunction({
361
383
  });
362
384
  const lgMediaQuery = `(min-width: ${index.breakpoints.lg.width})`;
363
385
  const isLg = useMatchMedia.useMatchMedia(lgMediaQuery);
364
- function resetNodeTabIndices() {
365
- const items = sideNavRef?.current?.querySelectorAll('[tabIndex="0"]') ?? [];
366
- items.forEach(item => {
367
- if (item.classList.contains(`${prefix}--side-nav__toggle`) || item.classList.contains(`${prefix}--side-nav__back-button`) || item.closest(`.${prefix}--side-nav__slot-item`) || item.classList.contains(`${prefix}--side-nav__link`) && item.closest('ul')?.getAttribute('aria-label') === ariaLabel) {
368
- return;
369
- }
370
- item.tabIndex = -1;
371
- });
372
- }
373
386
 
374
387
  // ensure that changes are in sync with internal treeview prop
375
388
  React__default.useEffect(() => {
@@ -384,13 +397,22 @@ function SideNavRenderFunction({
384
397
  setInternalIsTreeview(value);
385
398
  }
386
399
  };
387
- const SideNavToggleButton = /*#__PURE__*/React__default.createElement(SideNavToggle.SideNavToggle, {
388
- className: !expandedState ? `${prefix}--side-nav__toggle--collapsed` : '',
389
- renderIcon: expandedState ? iconsReact.SidePanelClose : iconsReact.SidePanelOpen,
390
- onClick: () => setExpandedState(!expandedState)
391
- }, sideNavToggleText);
400
+ const handlePinClick = () => {
401
+ setPinned(!pinned);
402
+ if (!autoExpand) {
403
+ setExpandedState(!pinned);
404
+ }
405
+ };
406
+ const handleAutoExpand = () => {
407
+ if (pinned) {
408
+ return;
409
+ }
410
+ setExpandedState(!autoExpand);
411
+ setAutoExpand(!autoExpand);
412
+ };
392
413
  return /*#__PURE__*/React__default.createElement(SideNavContext.Provider, {
393
414
  value: {
415
+ autoExpand,
394
416
  expanded,
395
417
  isRail,
396
418
  navType,
@@ -399,7 +421,7 @@ function SideNavRenderFunction({
399
421
  currentPrimaryMenu,
400
422
  setCurrentPrimaryMenu
401
423
  }
402
- }, isFixedNav || hideOverlay || navType === SIDE_NAV_TYPE.PANEL ? null :
424
+ }, isFixedNav || hideOverlay || navType === SIDE_NAV_TYPE.RAIL_PANEL ? null :
403
425
  /*#__PURE__*/
404
426
  // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
405
427
  React__default.createElement("div", {
@@ -410,10 +432,17 @@ function SideNavRenderFunction({
410
432
  tabIndex: -1,
411
433
  ref: navRef,
412
434
  className: `${prefix}--side-nav__navigation ${className}`,
413
- inert: !isRail && navType !== SIDE_NAV_TYPE.PANEL && !(expanded || isLg) ? -1 : undefined
414
- }, accessibilityLabel, eventHandlers, other), childrenToRender, navType === SIDE_NAV_TYPE.PANEL && (expandedState ? SideNavToggleButton : /*#__PURE__*/React__default.createElement("div", {
435
+ inert: !isRail && navType !== SIDE_NAV_TYPE.RAIL_PANEL && !(expanded || isLg) ? -1 : undefined
436
+ }, accessibilityLabel, eventHandlers, other), childrenToRender, navType === SIDE_NAV_TYPE.RAIL_PANEL && /*#__PURE__*/React__default.createElement("ul", {
415
437
  className: `${prefix}--side-nav__toggle-container`
416
- }, SideNavToggleButton))));
438
+ }, _SideNavDivider || (_SideNavDivider = /*#__PURE__*/React__default.createElement(react.SideNavDivider, null)), /*#__PURE__*/React__default.createElement(SideNavToggle.SideNavToggle, {
439
+ onClick: handlePinClick,
440
+ renderIcon: pinned ? iconsReact.PinFilled : iconsReact.Pin
441
+ }, pinText), /*#__PURE__*/React__default.createElement(SideNavToggle.SideNavToggle, {
442
+ disabled: pinned,
443
+ renderIcon: expandedState ? iconsReact.SidePanelClose : iconsReact.SidePanelOpen,
444
+ onClick: handleAutoExpand
445
+ }, autoExpandText))));
417
446
  }
418
447
  const SideNav = /*#__PURE__*/React__default.forwardRef(SideNavRenderFunction);
419
448
  SideNav.displayName = 'SideNav';
@@ -43,7 +43,7 @@ const SideNavLink = /*#__PURE__*/React__default.forwardRef(function SideNavLink(
43
43
  const SideNavLinkIcon = IconElement && /*#__PURE__*/React__default.createElement(react.SideNavIcon, {
44
44
  small: true
45
45
  }, /*#__PURE__*/React__default.createElement(IconElement, null));
46
- if (!expanded && navType === 'panel') {
46
+ if (!expanded && navType === SideNav.SIDE_NAV_TYPE.RAIL_PANEL) {
47
47
  return /*#__PURE__*/React__default.createElement(SideNavLinkPopover.SideNavLinkPopover, _rollupPluginBabelHelpers.extends({
48
48
  align: "right",
49
49
  label: children
@@ -64,6 +64,7 @@ const SideNavMenu = /*#__PURE__*/React__default.forwardRef(function SideNavMenu(
64
64
  const [prevExpanded, setPrevExpanded] = React__default.useState(defaultExpanded);
65
65
  const [isSecondaryOpen, setSecondaryOpen] = React__default.useState(defaultExpanded);
66
66
  const {
67
+ autoExpand,
67
68
  currentPrimaryMenu,
68
69
  setCurrentPrimaryMenu
69
70
  } = React__default.useContext(SideNav.SideNavContext);
@@ -150,7 +151,7 @@ const SideNavMenu = /*#__PURE__*/React__default.forwardRef(function SideNavMenu(
150
151
  return false;
151
152
  }
152
153
  React__default.useEffect(() => {
153
- if (navType == SideNav.SIDE_NAV_TYPE.PANEL) {
154
+ if (navType == SideNav.SIDE_NAV_TYPE.RAIL_PANEL) {
154
155
  // grab first link to redirect if clicked when not expanded
155
156
  if (!firstLink?.current && listRef?.current) {
156
157
  const firstLinkElement = listRef.current.querySelector(`.${prefix}--side-nav__menu-item a`);
@@ -284,7 +285,7 @@ const SideNavMenu = /*#__PURE__*/React__default.forwardRef(function SideNavMenu(
284
285
  }, [currentPrimaryMenu]);
285
286
  // reset to opened/collapsed menu state when Panel SideNav is toggled
286
287
  React__default.useEffect(() => {
287
- if (navType == SideNav.SIDE_NAV_TYPE.PANEL && !sideNavExpanded) {
288
+ if (navType == SideNav.SIDE_NAV_TYPE.RAIL_PANEL && !sideNavExpanded) {
288
289
  setIsExpanded(false);
289
290
  }
290
291
 
@@ -321,7 +322,7 @@ const SideNavMenu = /*#__PURE__*/React__default.forwardRef(function SideNavMenu(
321
322
  }
322
323
 
323
324
  // only when sidenav is panel view
324
- if (navType == SideNav.SIDE_NAV_TYPE.PANEL && !isExpanded && firstLink.current && !sideNavExpanded) {
325
+ if (navType == SideNav.SIDE_NAV_TYPE.RAIL_PANEL && !isExpanded && firstLink.current && !sideNavExpanded) {
325
326
  setOpenPopover(!openPopover);
326
327
  // window.location.href = firstLink.current;
327
328
  } else if (isSm || !primary || currentPrimaryMenu !== uniqueId) {
@@ -334,7 +335,7 @@ const SideNavMenu = /*#__PURE__*/React__default.forwardRef(function SideNavMenu(
334
335
  ref: menuRef,
335
336
  type: "button",
336
337
  tabIndex: isTreeview ? -1 : 0
337
- }, IconElement && /*#__PURE__*/React__default.createElement(react.SideNavIcon, null, /*#__PURE__*/React__default.createElement(IconElement, null)), !sideNavExpanded && navType == SideNav.SIDE_NAV_TYPE.PANEL && /*#__PURE__*/React__default.createElement("div", {
338
+ }, IconElement && /*#__PURE__*/React__default.createElement(react.SideNavIcon, null, /*#__PURE__*/React__default.createElement(IconElement, null)), !autoExpand && !sideNavExpanded && navType == SideNav.SIDE_NAV_TYPE.RAIL_PANEL && /*#__PURE__*/React__default.createElement("div", {
338
339
  className: `${prefix}--side-nav--panel-submenu-caret-container`
339
340
  }, /*#__PURE__*/React__default.createElement("div", {
340
341
  className: `${prefix}--side-nav--panel-submenu-caret`
@@ -364,7 +365,7 @@ const SideNavMenu = /*#__PURE__*/React__default.forwardRef(function SideNavMenu(
364
365
  className: `${prefix}--side-nav__menu`,
365
366
  role: "group"
366
367
  }, childrenToRender));
367
- return navType == SideNav.SIDE_NAV_TYPE.PANEL && !sideNavExpanded ? /*#__PURE__*/React__default.createElement(SideNavFlyoutMenu.SideNavFlyoutMenu, {
368
+ return navType == SideNav.SIDE_NAV_TYPE.RAIL_PANEL && !sideNavExpanded ? /*#__PURE__*/React__default.createElement(SideNavFlyoutMenu.SideNavFlyoutMenu, {
368
369
  selected: active,
369
370
  className: `${prefix}--side-nav-flyout-menu`,
370
371
  title: title,
@@ -4,29 +4,54 @@
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, { ReactNode } from 'react';
8
- interface SideNavToggleProps {
7
+ import PropTypes from 'prop-types';
8
+ import { ComponentType, ElementType, ForwardedRef, ReactNode } from 'react';
9
+ import { LinkProps } from './Link';
10
+ export type SideNavToggleProps<E extends ElementType> = LinkProps<E> & {
9
11
  /**
10
- * Specify an optional className to be applied to the button node
12
+ * Required props for the accessibility label
11
13
  */
12
- className?: string;
14
+ 'aria-label'?: string;
15
+ /**
16
+ * Required props for the accessibility label
17
+ */
18
+ 'aria-labelledby'?: string;
13
19
  /**
14
20
  * Specify the text content for the link
15
21
  */
16
- children: ReactNode;
22
+ children?: ReactNode;
23
+ /**
24
+ * Provide an optional class to be applied to the containing node
25
+ */
26
+ className?: string;
27
+ /**
28
+ * Specify whether the link is the current page
29
+ */
30
+ isActive?: boolean;
31
+ /**
32
+ * Property to indicate if the side nav container is open (or not). Use to
33
+ * keep local state and styling in step with the SideNav expansion state.
34
+ */
35
+ isSideNavExpanded?: boolean;
17
36
  /**
18
- * Provide an optional function to be called when the item is clicked.
37
+ * Specify if this is a large variation of the SideNavLink
19
38
  */
20
- onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
39
+ large?: boolean;
21
40
  /**
22
- * A custom icon to render next to the SideNavToggle title. This can be a function returning JSX or JSX itself.
41
+ * Provide an icon to render in the side navigation link. Should be a React class.
23
42
  */
24
- renderIcon?: React.ComponentType;
43
+ renderIcon?: ComponentType;
25
44
  /**
26
- * The tabIndex for the button element.
27
- * If not specified, the default validation will be applied.
45
+ * Optional prop to specify the tabIndex of the button. If undefined, it will be applied default validation
28
46
  */
29
47
  tabIndex?: number;
48
+ };
49
+ export interface SideNavLinkComponent {
50
+ (props: SideNavToggleProps<'button'> & {
51
+ ref?: ForwardedRef<HTMLButtonElement>;
52
+ }): JSX.Element | null;
53
+ displayName?: string;
54
+ propTypes?: Partial<Record<keyof SideNavToggleProps<any>, PropTypes.Validator<any>>>;
30
55
  }
31
- export declare const SideNavToggle: React.ForwardRefExoticComponent<SideNavToggleProps & React.RefAttributes<HTMLElement>>;
56
+ export declare const SideNavToggle: SideNavLinkComponent;
32
57
  export default SideNavToggle;
@@ -13,46 +13,78 @@ var _rollupPluginBabelHelpers = require('../_virtual/_rollupPluginBabelHelpers.j
13
13
  var cx = require('classnames');
14
14
  var PropTypes = require('prop-types');
15
15
  var React__default = require('react');
16
+ var Link = require('./Link.js');
16
17
  var react = require('@carbon/react');
17
18
  var usePrefix = require('../internal/usePrefix.js');
19
+ var SideNav = require('./SideNav.js');
20
+ var SideNavLinkPopover = require('./SideNavLinkPopover.js');
18
21
 
19
22
  const SideNavToggle = /*#__PURE__*/React__default.forwardRef(function SideNavToggle({
23
+ children,
20
24
  className: customClassName,
25
+ disabled,
21
26
  renderIcon: IconElement,
27
+ large = false,
22
28
  tabIndex,
23
- children,
24
29
  ...rest
25
30
  }, ref) {
31
+ const {
32
+ expanded,
33
+ navType
34
+ } = React__default.useContext(SideNav.SideNavContext);
26
35
  const prefix = usePrefix.usePrefix();
27
- return /*#__PURE__*/React__default.createElement("button", _rollupPluginBabelHelpers.extends({
28
- className: cx(customClassName, {
29
- [`${prefix}--side-nav__toggle`]: true
30
- }),
36
+ const className = cx({
37
+ [`${prefix}--side-nav__toggle`]: true,
38
+ [`${prefix}--side-nav__toggle--disabled`]: disabled,
39
+ [customClassName]: !!customClassName
40
+ });
41
+ const SideNavLinkIcon = IconElement && /*#__PURE__*/React__default.createElement(react.SideNavIcon, {
42
+ small: true
43
+ }, /*#__PURE__*/React__default.createElement(IconElement, null));
44
+ if (!expanded && navType === SideNav.SIDE_NAV_TYPE.RAIL_PANEL) {
45
+ return /*#__PURE__*/React__default.createElement(SideNavLinkPopover.SideNavLinkPopover, _rollupPluginBabelHelpers.extends({
46
+ align: "right",
47
+ className: className,
48
+ label: children
49
+ }, rest), SideNavLinkIcon);
50
+ }
51
+ return /*#__PURE__*/React__default.createElement(react.SideNavItem, {
52
+ large: large
53
+ }, /*#__PURE__*/React__default.createElement("button", _rollupPluginBabelHelpers.extends({
54
+ className: className,
31
55
  ref: ref,
32
56
  type: "button",
33
- tabIndex: tabIndex ?? 0
34
- }, rest), IconElement && /*#__PURE__*/React__default.createElement(react.SideNavIcon, null, /*#__PURE__*/React__default.createElement(IconElement, null)), /*#__PURE__*/React__default.createElement("span", {
35
- className: `${prefix}--side-nav__toggle-text`
36
- }, children));
57
+ tabIndex: tabIndex ?? 0,
58
+ disabled: disabled
59
+ }, rest), SideNavLinkIcon, /*#__PURE__*/React__default.createElement(react.SideNavLinkText, null, children)));
37
60
  });
38
61
  SideNavToggle.displayName = 'SideNavToggle';
39
62
  SideNavToggle.propTypes = {
63
+ ...Link.LinkPropTypes,
40
64
  /**
41
- * Specify the text content for the toggle
65
+ * Specify the text content for the link
42
66
  */
43
67
  children: PropTypes.node,
44
68
  /**
45
- * Specify an optional className to be applied to the button node
69
+ * Provide an optional class to be applied to the containing node
46
70
  */
47
71
  className: PropTypes.string,
48
72
  /**
49
- * Provide an optional function to be called when clicked
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
50
83
  */
51
- onClick: PropTypes.func,
84
+ large: PropTypes.bool,
52
85
  /**
53
- * Pass in a custom icon to render next to the `SideNavToggle` title
86
+ * Provide an icon to render in the side navigation link. Should be a React class.
54
87
  */
55
- // @ts-expect-error - PropTypes are unable to cover this case.
56
88
  renderIcon: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
57
89
  /**
58
90
  * Optional prop to specify the tabIndex of the button. If undefined, it will be applied default validation
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@carbon-labs/react-ui-shell",
3
- "version": "0.72.0",
3
+ "version": "0.74.0",
4
4
  "publishConfig": {
5
5
  "access": "public",
6
6
  "provenance": true
@@ -42,5 +42,5 @@
42
42
  "dependencies": {
43
43
  "@ibm/telemetry-js": "^1.9.1"
44
44
  },
45
- "gitHead": "97cdff827945118076e8326b994e16ca6d036501"
45
+ "gitHead": "fd1fd1e946fc16c0cac7e637512ce1423085b9d9"
46
46
  }
@@ -19,9 +19,21 @@ div:has(.#{$prefix}--side-nav--panel) ~ .#{$prefix}--content {
19
19
  transition: $fast-02 motion(exit, productive);
20
20
  }
21
21
 
22
- .#{$prefix}--side-nav--panel.#{$prefix}--side-nav--expanded
22
+ .#{$prefix}--side-nav--panel:not(
23
+ .#{$prefix}--side-nav--rail
24
+ ).#{$prefix}--side-nav--expanded
23
25
  ~ .#{$prefix}--content,
24
- div:has(.#{$prefix}--side-nav--panel.#{$prefix}--side-nav--expanded)
26
+ .#{$prefix}--side-nav--panel.#{$prefix}--side-nav--pinned.#{$prefix}--side-nav--expanded
27
+ ~ .#{$prefix}--content,
28
+ div:has(
29
+ .#{$prefix}--side-nav--panel:not(
30
+ .#{$prefix}--side-nav--rail
31
+ ).#{$prefix}--side-nav--expanded
32
+ )
33
+ ~ .#{$prefix}--content,
34
+ div:has(
35
+ .#{$prefix}--side-nav--panel.#{$prefix}--side-nav--pinned.#{$prefix}--side-nav--expanded
36
+ )
25
37
  ~ .#{$prefix}--content {
26
38
  margin-inline-start: convert.to-rem(256px);
27
39
  transition: $moderate-01 motion(standard, productive);
@@ -350,6 +350,7 @@ div:has(.#{$prefix}--header)
350
350
  display: flex;
351
351
  overflow: visible;
352
352
  flex-direction: column;
353
+ padding-block-end: $spacing-11;
353
354
  }
354
355
 
355
356
  .#{$prefix}--side-nav__items,
@@ -374,14 +375,14 @@ div:has(.#{$prefix}--header)
374
375
 
375
376
  // Side Nav Toggle
376
377
  .#{$prefix}--side-nav__toggle {
377
- @include button-reset.reset($width: true);
378
- @include type-style('heading-compact-01');
378
+ @include button-reset.reset($width: false);
379
+ @include type-style('label-01');
379
380
  @include focus-outline('reset');
380
381
 
381
382
  display: flex;
382
383
  align-items: center;
383
384
  padding: 0 $spacing-05;
384
- block-size: $spacing-09;
385
+ block-size: $spacing-07;
385
386
  color: $text-secondary;
386
387
  transition: color $duration-fast-02, background-color $duration-fast-02,
387
388
  outline $duration-fast-02;
@@ -396,6 +397,15 @@ div:has(.#{$prefix}--header)
396
397
  }
397
398
  }
398
399
 
400
+ .#{$prefix}--side-nav__toggle--disabled {
401
+ color: $text-disabled;
402
+ pointer-events: none;
403
+
404
+ svg {
405
+ fill: $icon-disabled;
406
+ }
407
+ }
408
+
399
409
  .#{$prefix}--side-nav__toggle-text {
400
410
  @include text-overflow();
401
411
 
@@ -562,6 +572,10 @@ div:has(.#{$prefix}--header)
562
572
  .#{$prefix}--side-nav__item {
563
573
  overflow: visible;
564
574
  }
575
+
576
+ .#{$prefix}--side-nav__toggle {
577
+ inline-size: 100%;
578
+ }
565
579
  }
566
580
 
567
581
  .#{$prefix}--side-nav__toggle-container {
@@ -569,10 +583,7 @@ div:has(.#{$prefix}--header)
569
583
  background: $background;
570
584
  inline-size: 100%;
571
585
  inset-block-end: 0;
572
-
573
- .#{$prefix}--side-nav__toggle.#{$prefix}--side-nav__toggle--collapsed:hover {
574
- background: $background-hover;
575
- }
586
+ white-space: nowrap;
576
587
  }
577
588
 
578
589
  //----------------------------------------------------------------------------
@@ -588,6 +599,11 @@ div:has(.#{$prefix}--header)
588
599
  box-shadow: $spacing-02 0 convert.to-rem(6px) convert.to-rem(-3px) $shadow;
589
600
  }
590
601
 
602
+ .#{$prefix}--side-nav--rail.#{$prefix}--side-nav--expanded.#{$prefix}--side-nav--pinned {
603
+ border-inline-end: 1px solid $border-subtle;
604
+ box-shadow: none;
605
+ }
606
+
591
607
  @each $breakpoint in ('sm', 'md', 'lg', 'xlg', 'max') {
592
608
  .#{$prefix}--side-nav--rail.#{$prefix}--side-nav--hide-rail-breakpoint-down-#{$breakpoint} {
593
609
  @include breakpoint-down($breakpoint) {