orcs-design-system
Version:
TeamForm's Design System, aka: ORCS
327 lines (324 loc) • 15.4 kB
JavaScript
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;