UNPKG

@carbon/react

Version:

React components for the Carbon Design System

164 lines (154 loc) 5.41 kB
/** * Copyright IBM Corp. 2016, 2023 * * This source code is licensed under the Apache-2.0 license found in the * LICENSE file in the root directory of this source tree. */ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var iconsReact = require('@carbon/icons-react'); var cx = require('classnames'); var PropTypes = require('prop-types'); var React = require('react'); var SideNavIcon = require('./SideNavIcon.js'); var keys = require('../../internal/keyboard/keys.js'); var match = require('../../internal/keyboard/match.js'); var usePrefix = require('../../internal/usePrefix.js'); var SideNav = require('./SideNav.js'); var _ChevronDown; const SideNavMenu = /*#__PURE__*/React.forwardRef(({ className: customClassName, children, defaultExpanded = false, isActive = false, large = false, renderIcon: IconElement, isSideNavExpanded, tabIndex, title }, ref) => { const { isRail } = React.useContext(SideNav.SideNavContext); const prefix = usePrefix.usePrefix(); const [isExpanded, setIsExpanded] = React.useState(defaultExpanded); const [prevExpanded, setPrevExpanded] = React.useState(defaultExpanded); const className = cx({ [`${prefix}--side-nav__item`]: true, [`${prefix}--side-nav__item--active`]: isActive || hasActiveDescendant(children) && !isExpanded, [`${prefix}--side-nav__item--icon`]: IconElement, [`${prefix}--side-nav__item--large`]: large, [customClassName]: !!customClassName }); if (!isSideNavExpanded && isExpanded && isRail) { setIsExpanded(false); setPrevExpanded(true); } else if (isSideNavExpanded && prevExpanded && isRail) { setIsExpanded(true); setPrevExpanded(false); } return ( /*#__PURE__*/ // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions React.createElement("li", { className: className, onKeyDown: event => { if (match.match(event, keys.Escape)) { setIsExpanded(false); } } }, /*#__PURE__*/React.createElement("button", { "aria-expanded": isExpanded, className: `${prefix}--side-nav__submenu`, onClick: () => { setIsExpanded(!isExpanded); }, ref: ref, type: "button", tabIndex: tabIndex === undefined ? !isSideNavExpanded && !isRail ? -1 : 0 : tabIndex }, IconElement && /*#__PURE__*/React.createElement(SideNavIcon.default, null, /*#__PURE__*/React.createElement(IconElement, null)), /*#__PURE__*/React.createElement("span", { className: `${prefix}--side-nav__submenu-title` }, title), /*#__PURE__*/React.createElement(SideNavIcon.default, { className: `${prefix}--side-nav__submenu-chevron`, small: true }, _ChevronDown || (_ChevronDown = /*#__PURE__*/React.createElement(iconsReact.ChevronDown, { size: 20 })))), /*#__PURE__*/React.createElement("ul", { className: `${prefix}--side-nav__menu` }, children)) ); }); SideNavMenu.displayName = 'SideNavMenu'; SideNavMenu.propTypes = { /** * Provide <SideNavMenuItem>'s inside of the `SideNavMenu` */ children: PropTypes.node, /** * Provide an optional class to be applied to the containing node */ className: PropTypes.string, /** * Specify whether the menu should default to expanded. By default, it will * be closed. */ defaultExpanded: PropTypes.bool, /** * Specify whether the `SideNavMenu` is "active". `SideNavMenu` should be * considered active if one of its menu items are a link for the current * page. */ isActive: PropTypes.bool, /** * Property to indicate if the side nav container is open (or not). Use to * keep local state and styling in step with the SideNav expansion state. */ isSideNavExpanded: PropTypes.bool, /** * Specify if this is a large variation of the SideNavMenu */ large: PropTypes.bool, /** * A component used to render an icon. */ renderIcon: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), /** * Optional prop to specify the tabIndex of the button. If undefined, it will be applied default validation */ tabIndex: PropTypes.number, /** * Provide the text for the overall menu name */ title: PropTypes.string.isRequired }; /** Defining the children parameter with the type ReactNode | ReactNode[]. This allows for various possibilities: a single element, an array of elements, or null or undefined. **/ function hasActiveDescendant(children) { if (Array.isArray(children)) { return children.some(child => { if (! /*#__PURE__*/React.isValidElement(child)) { return false; } /** Explicitly defining the expected prop types (isActive and 'aria-current) for the children to ensure type safety when accessing their props. **/ const props = child.props; if (props.isActive === true || props['aria-current'] || props.children instanceof Array && hasActiveDescendant(props.children)) { return true; } return false; }); } // We use React.isValidElement(child) to check if the child is a valid React element before accessing its props if (/*#__PURE__*/React.isValidElement(children)) { const props = children.props; if (props.isActive === true || props['aria-current']) { return true; } } return false; } exports.SideNavMenu = SideNavMenu; exports.default = SideNavMenu;