UNPKG

orcs-design-system

Version:
327 lines (324 loc) 15.4 kB
import _defineProperty from "@babel/runtime/helpers/defineProperty"; function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } import React, { useMemo } from "react"; import styled, { css as styledCss } from "styled-components"; import { css } from "@styled-system/css"; import { themeGet } from "@styled-system/theme-get"; import PropTypes from "prop-types"; import Icon from "../Icon"; import Popover from "../Popover"; import { BREAKPOINTS } from "./constants/sideNav"; import sharedNavItemHoverStyles, { sharedNavItemActiveStylesProps, sharedMenuItemContainerStyles } from "./styles/sharedHoverStyles"; // Use constants for NavItem consistency and maintainability import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; const ICON_COLUMN_WIDTH = "12px"; const MOBILE_ITEM_WIDTH = "30px"; const ITEM_HEIGHT = "30px"; // Constrain icon width to prevent alignment issues with variable-width icons const IconWrapper = /*#__PURE__*/styled.span.withConfig({ displayName: "IconWrapper", componentId: "sc-1qz1q0h-0" })(["display:inline-flex;align-items:center;justify-content:center;width:", ";flex-shrink:0;"], ICON_COLUMN_WIDTH); const sharedNavItemStyles = props => styledCss(["cursor:pointer;padding:4px;text-decoration:none;border-radius:", ";width:", ";height:", ";position:relative;border:none;path{fill:", ";}font-family:", ";", " background-color:transparent;font-size:1.4rem;font-weight:", ";", " @media screen and (max-width:", "px){width:", ";height:", ";display:flex;align-items:center;justify-content:center;-webkit-tap-highlight-color:transparent;&:active{background-color:transparent;}}&:focus{outline:0;}"], themeGet("radii.2"), MOBILE_ITEM_WIDTH, ITEM_HEIGHT, themeGet("colors.greyDarker"), themeGet("fonts.main"), props.isExpanded ? styledCss(["display:grid;grid-template-columns:", ";place-items:center;"], ICON_COLUMN_WIDTH) : styledCss(["display:flex;align-items:center;justify-content:center;"]), themeGet("fontWeights.1"), sharedNavItemHoverStyles, BREAKPOINTS.SMALL_SCREEN, MOBILE_ITEM_WIDTH, ITEM_HEIGHT); const expandedNavItemStyles = props => styledCss(["display:grid;width:100%;height:", ";grid-template-columns:", " 1fr;gap:4px;justify-items:start;align-items:center;padding:4px 9px;.nav-item-text{font-size:", ";line-height:1;padding-left:4px;font-weight:", ";color:", ";text-align:left;justify-self:start;}&:hover .nav-item-text,&:focus .nav-item-text{color:", ";}&:hover path,&:focus path{fill:", ";}@media screen and (max-width:", "px){width:", ";height:", ";display:flex;align-items:center;justify-content:center;}"], ITEM_HEIGHT, ICON_COLUMN_WIDTH, themeGet("fontSizes.1"), themeGet("fontWeights.1"), props.active ? themeGet("colors.greyDarkest") : themeGet("colors.greyDarker"), themeGet("colors.primary"), themeGet("colors.primary"), BREAKPOINTS.SMALL_SCREEN, MOBILE_ITEM_WIDTH, ITEM_HEIGHT); const variantActiveStyles = { expandableItem: () => styledCss(["color:", ";path{fill:", ";}&:hover .nav-item-text,&:focus .nav-item-text{color:", ";}path{fill:", ";}&:hover path,&:focus path{fill:", ";}"], themeGet("colors.greyDarkest"), themeGet("colors.greyDarkest"), themeGet("colors.primary"), themeGet("colors.greyDarkest"), themeGet("colors.greyDarkest")), default: styledCss(["", ""], props => sharedNavItemActiveStylesProps(props)) }; const getActiveStyles = props => { if (!props.active) return ""; if (props["aria-expanded"]) { return variantActiveStyles.expandableItem(props); } return variantActiveStyles.default; }; const SideNavItemLink = /*#__PURE__*/styled.div.withConfig({ displayName: "SideNavItemLink", componentId: "sc-1qz1q0h-1" })(["width:100%;& > a{", " ", " ", " background-color:transparent !important;&:hover,&:focus,&:active{background-color:transparent !important;}}"], props => sharedNavItemStyles(props), props => props.isExpanded ? expandedNavItemStyles(props) : "", getActiveStyles); const SideNavItem = /*#__PURE__*/styled("button").withConfig({ displayName: "SideNavItem", componentId: "sc-1qz1q0h-2" })(["", " ", " ", " background-color:transparent !important;&:hover,&:focus,&:active{background-color:transparent !important;}"], props => sharedNavItemStyles(props), props => props.isExpanded ? expandedNavItemStyles(props) : "", getActiveStyles); const BadgeNumber = /*#__PURE__*/styled("div").withConfig({ displayName: "BadgeNumber", componentId: "sc-1qz1q0h-3" })(props => css({ position: "absolute", height: "16px", width: "16px", bg: themeGet("colors.danger")(props), fontSize: "1rem", fontWeight: themeGet("fontWeights.2")(props), color: themeGet("colors.white")(props), borderRadius: "50%", top: "50%", right: "0", transform: "translateY(-50%)", display: "flex", alignItems: "center", justifyContent: "center" })); const BadgeDot = /*#__PURE__*/styled("div").withConfig({ displayName: "BadgeDot", componentId: "sc-1qz1q0h-4" })(props => css({ position: "absolute", height: "8px", width: "8px", bg: themeGet("colors.primary")(props), borderRadius: "50%", top: "50%", right: "0", transform: "translateY(-50%)" })); const SideNavItemWrapper = /*#__PURE__*/styled.div.withConfig({ displayName: "SideNavItemWrapper", componentId: "sc-1qz1q0h-5" })(["", " position:relative;&:nth-child(1 of .bottom-aligned){margin-top:", ";}", " ", " @media screen and (max-width:", "px){", "}", " @media screen and (max-width:", "px){", "}"], sharedMenuItemContainerStyles, props => props.bottomAligned && "auto", props => props.isExpandable && styledCss(["&.active,&:has(a.active),&:has(button.active){background-color:transparent;}&:hover{background-color:transparent;}"]), props => props.isActive && props.isLink && !props.isExpandable && styledCss(["background-color:", ";"], themeGet("colors.primaryLightest")), BREAKPOINTS.SMALL_SCREEN, props => props.isActive && props.isLink && !props.isExpandable && styledCss(["background-color:", ";path{fill:", ";}path[stroke]{stroke:", ";}&:hover path,&:focus path,&:active path{fill:", ";}&:hover path[stroke],&:focus path[stroke],&:active path[stroke]{stroke:", ";}"], themeGet("colors.primaryLightest"), themeGet("colors.greyDarkest"), themeGet("colors.greyDarkest"), themeGet("colors.greyDarkest"), themeGet("colors.greyDarkest")), props => props.isActive && props.isExpandable && styledCss(["&::after{content:\"\";position:absolute;right:-9px;top:0;bottom:0;width:3px;background-color:", ";z-index:5;}"], themeGet("colors.primary")), BREAKPOINTS.SMALL_SCREEN, props => props.isActive && props.isExpandable && styledCss(["&::after{content:\"\";position:absolute;top:0;left:50%;width:", ";height:0;border-top:3px solid ", ";transform:translate(-50%,-10px);z-index:10;}"], MOBILE_ITEM_WIDTH, themeGet("colors.primary"))); /** * NavItem - A navigation item component for the SideNavV2 * * Renders either a button or link-based navigation item with support for: * - Icons with optional badges * - Expandable/collapsible states * - Active state styling * - Responsive behavior * - Accessibility features * * @param {Object} props - Component props * @param {Object} props.item - Navigation item data * @param {React.ElementType} [props.Component] - Component to render for link items * @param {boolean} props.isActive - Whether the item is currently active * @param {Function} props.handleItemClick - Click handler function * @param {Object} props.navItemRefs - Ref object for managing item references * @param {boolean} props.isSmallScreen - Whether currently on a small screen * @param {boolean} props.isExpanded - Whether the navigation is expanded * @returns {JSX.Element} Rendered navigation item */ const NavItem = _ref => { let { item, Component, isActive, handleItemClick, navItemRefs, isSmallScreen, isExpanded } = _ref; const renderedNavItem = useMemo(() => { const accessibilityProps = _objectSpread(_objectSpread({}, item.actionType === "component" && { "aria-expanded": isActive ? "true" : "false" }), {}, { "aria-label": item.name }); if (item.actionType === "link") { return /*#__PURE__*/_jsx(SideNavItemLink, { active: isActive, bottomAligned: item.bottomAligned, isExpanded: isExpanded, onClick: () => handleItemClick(item), ref: el => navItemRefs.current[item.index] = el, children: /*#__PURE__*/_jsxs(Component, { item: item, children: [/*#__PURE__*/_jsx(IconWrapper, { iconName: item.iconName, children: /*#__PURE__*/_jsx(Icon, { icon: ["far", item.iconName], fixedWidth: true }) }), isExpanded && !isSmallScreen && /*#__PURE__*/_jsx("span", { className: "nav-item-text", children: item.name })] }) }, item.index); } else { return /*#__PURE__*/_jsxs(SideNavItem, _objectSpread(_objectSpread({ active: isActive, onClick: () => handleItemClick(item), bottomAligned: item.bottomAligned, isExpanded: isExpanded }, accessibilityProps), {}, { ref: el => navItemRefs.current[item.index] = el, children: [item.badgeNumber && /*#__PURE__*/_jsx(BadgeNumber, { isExpanded: isExpanded, children: item.badgeNumber }), item.badgeDot && /*#__PURE__*/_jsx(BadgeDot, { isExpanded: isExpanded }), /*#__PURE__*/_jsx(IconWrapper, { iconName: item.iconName, children: /*#__PURE__*/_jsx(Icon, { icon: ["far", item.iconName], fixedWidth: true }) }), isExpanded && !isSmallScreen && /*#__PURE__*/_jsx("span", { className: "nav-item-text", children: item.name })] }), item.index); } }, [item, isActive, isExpanded, isSmallScreen, handleItemClick, navItemRefs]); return isSmallScreen || !isExpanded ? /*#__PURE__*/_jsx(SideNavItemWrapper, { className: item.bottomAligned && "bottom-aligned", bottomAligned: item.bottomAligned, isActive: isActive, isLink: item.actionType === "link", isExpandable: item.actionType === "component", children: /*#__PURE__*/_jsx(Popover, { text: item.name, direction: isSmallScreen ? "top" : "right", textAlign: "center", width: "fit-content", tabIndex: "-1", children: renderedNavItem }) }) : /*#__PURE__*/_jsx(SideNavItemWrapper, { className: item.bottomAligned && "bottom-aligned", bottomAligned: item.bottomAligned, isActive: isActive, isLink: item.actionType === "link", isExpandable: item.actionType === "component", children: renderedNavItem }); }; NavItem.propTypes = { item: PropTypes.shape({ index: PropTypes.number, iconName: PropTypes.string.isRequired, name: PropTypes.string.isRequired, actionType: PropTypes.oneOf(["component", "link", "button"]).isRequired, bottomAligned: PropTypes.bool, hide: PropTypes.bool, component: PropTypes.elementType, onClick: PropTypes.func, isActive: PropTypes.bool, badgeNumber: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), badgeDot: PropTypes.bool }).isRequired, Component: PropTypes.elementType, isActive: PropTypes.bool, handleItemClick: PropTypes.func.isRequired, navItemRefs: PropTypes.object, isSmallScreen: PropTypes.bool, isExpanded: PropTypes.bool }; NavItem.__docgenInfo = { "description": "NavItem - A navigation item component for the SideNavV2\n\nRenders either a button or link-based navigation item with support for:\n- Icons with optional badges\n- Expandable/collapsible states\n- Active state styling\n- Responsive behavior\n- Accessibility features\n\n@param {Object} props - Component props\n@param {Object} props.item - Navigation item data\n@param {React.ElementType} [props.Component] - Component to render for link items\n@param {boolean} props.isActive - Whether the item is currently active\n@param {Function} props.handleItemClick - Click handler function\n@param {Object} props.navItemRefs - Ref object for managing item references\n@param {boolean} props.isSmallScreen - Whether currently on a small screen\n@param {boolean} props.isExpanded - Whether the navigation is expanded\n@returns {JSX.Element} Rendered navigation item", "methods": [], "displayName": "NavItem", "props": { "item": { "description": "", "type": { "name": "shape", "value": { "index": { "name": "number", "required": false }, "iconName": { "name": "string", "required": true }, "name": { "name": "string", "required": true }, "actionType": { "name": "enum", "value": [{ "value": "\"component\"", "computed": false }, { "value": "\"link\"", "computed": false }, { "value": "\"button\"", "computed": false }], "required": true }, "bottomAligned": { "name": "bool", "required": false }, "hide": { "name": "bool", "required": false }, "component": { "name": "elementType", "required": false }, "onClick": { "name": "func", "required": false }, "isActive": { "name": "bool", "required": false }, "badgeNumber": { "name": "union", "value": [{ "name": "string" }, { "name": "number" }], "required": false }, "badgeDot": { "name": "bool", "required": false } } }, "required": true }, "Component": { "description": "", "type": { "name": "elementType" }, "required": false }, "isActive": { "description": "", "type": { "name": "bool" }, "required": false }, "handleItemClick": { "description": "", "type": { "name": "func" }, "required": true }, "navItemRefs": { "description": "", "type": { "name": "object" }, "required": false }, "isSmallScreen": { "description": "", "type": { "name": "bool" }, "required": false }, "isExpanded": { "description": "", "type": { "name": "bool" }, "required": false } } }; export default NavItem;