orcs-design-system
Version:
TeamForm's Design System, aka: ORCS
388 lines (380 loc) • 13 kB
JavaScript
import React, { useMemo, useCallback } from "react";
import PropTypes from "prop-types";
import SideNavTeamsSection from "./sections/SideNavTeamsSection";
import SideNavPinnedSection from "./sections/SideNavPinnedSection";
// Hooks
import useSideNavState from "./hooks/useSideNavState";
import useResponsive from "./hooks/useResponsive";
import useResize from "./hooks/useResize";
// Utilities
import { categorizeItems } from "./utils/itemUtils";
// Styled Components
import { SideNavWrapper, SideNavItems, ToggleHandle, ToggleIcon } from "./styles/SideNavV2.styles";
// Components
import ExpandedPanel from "./components/ExpandedPanel";
import ItemSection from "./components/ItemSection";
import CurrentViewSectionPortalTarget from "./components/CurrentViewSectionPortalTarget";
import Icon from "../Icon";
/**
* SideNavV2 - A responsive side navigation component with expandable panels
*
* Features:
* - Responsive design that adapts to mobile and desktop
* - Expandable panels with resizable functionality (desktop: width, mobile: height)
* - Toggle handle to expand/collapse the sidebar (desktop only)
* - Team and pinned item sections (optional custom render props)
* - Current view section portal integration
*
* @param {Object} props - Component props
* @param {Array} props.items - Navigation items to display
* @param {string} props.sideNavHeight - Height of the side navigation
* @param {Array} [props.yourTeams] - Teams to display in the teams section
* @param {Array} [props.pinnedItems] - Pinned items to display
* @param {boolean} [props.startExpanded] - Whether the sidebar should start expanded
* @param {(keepExpanded: boolean) => void} [props.onKeepExpandedChange] - Callback when the sidebar expand state changes (e.g. after toggle)
* @param {Function} [props.renderTeamsSection] - Custom render function for teams section; receives { teams, isExpanded }
* @param {Function} [props.renderPinnedSection] - Custom render function for pinned section; receives { items, isExpanded }
* @returns {JSX.Element} Rendered side navigation
*/
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
const SideNavV2 = _ref => {
let {
items,
sideNavHeight,
yourTeams,
pinnedItems,
startExpanded,
onKeepExpandedChange,
renderTeamsSection,
renderPinnedSection
} = _ref;
const {
expandedItem,
isExpanded,
expandedWidth,
expandedRef,
navItemRefs,
wrapperRef,
handleItemClick,
handleWidthChange,
handleExpandToggle: handleExpandToggleState
} = useSideNavState(items, startExpanded);
const {
isSmallScreen
} = useResponsive();
const handleExpandToggle = useCallback(() => {
handleExpandToggleState();
// notify consumer of expanded state change
const newExpandedState = !isExpanded;
onKeepExpandedChange === null || onKeepExpandedChange === void 0 || onKeepExpandedChange(newExpandedState);
}, [isExpanded, onKeepExpandedChange, handleExpandToggleState]);
// Memoize expensive calculations to prevent unnecessary re-renders
const currentItem = useMemo(() => expandedItem !== null ? items[expandedItem] : null, [expandedItem, items]);
const {
isResizing,
hasResized,
handleResizeStart
} = useResize(expandedRef, isSmallScreen, expandedItem, handleWidthChange, currentItem);
// Memoize categorized items to prevent recalculation on every render
const categorizedItems = useMemo(() => categorizeItems(items), [items]);
const {
topAlignedItems,
pageSpecificItems,
bottomAlignedItems,
allItems
} = categorizedItems;
// Determine toggle button icon based on expanded state
const toggleIcon = useMemo(() => isExpanded ? "chevron-left" : "chevron-right", [isExpanded]);
return /*#__PURE__*/_jsxs(SideNavWrapper, {
ref: wrapperRef,
sideNavHeight: sideNavHeight,
"data-testid": "side-nav-wrapper",
children: [/*#__PURE__*/_jsxs(SideNavItems, {
isExpanded: isExpanded,
"data-testid": "side-nav-items",
children: [/*#__PURE__*/_jsx(ItemSection, {
items: topAlignedItems,
isExpanded: isExpanded,
isSmallScreen: isSmallScreen,
expandedItem: expandedItem,
handleItemClick: handleItemClick,
navItemRefs: navItemRefs
}), /*#__PURE__*/_jsx(ItemSection, {
items: pageSpecificItems,
isExpanded: isExpanded,
isSmallScreen: isSmallScreen,
expandedItem: expandedItem,
handleItemClick: handleItemClick,
navItemRefs: navItemRefs
}), /*#__PURE__*/_jsx(CurrentViewSectionPortalTarget, {}), yourTeams && yourTeams.length > 0 && (renderTeamsSection ? renderTeamsSection({
teams: yourTeams,
isExpanded
}) : /*#__PURE__*/_jsx(SideNavTeamsSection, {
teams: yourTeams,
isExpanded: isExpanded
})), pinnedItems && pinnedItems.length > 0 && (renderPinnedSection ? renderPinnedSection({
items: pinnedItems,
isExpanded
}) : /*#__PURE__*/_jsx(SideNavPinnedSection, {
items: pinnedItems,
isExpanded: isExpanded
})), /*#__PURE__*/_jsx(ItemSection, {
items: bottomAlignedItems,
isExpanded: isExpanded,
isSmallScreen: isSmallScreen,
expandedItem: expandedItem,
handleItemClick: handleItemClick,
navItemRefs: navItemRefs
})]
}), !isSmallScreen && /*#__PURE__*/_jsx(ToggleHandle, {
onClick: handleExpandToggle,
"data-testid": "toggle-handle",
isExpanded: isExpanded,
children: /*#__PURE__*/_jsx(ToggleIcon, {
className: "toggle-icon",
children: /*#__PURE__*/_jsx(Icon, {
icon: ["fas", toggleIcon],
size: "sm",
fontSize: "15px"
})
})
}), allItems.map((item, index) => {
if (item.actionType !== "component") return null;
if (index !== expandedItem || item.hide) return null;
return /*#__PURE__*/_jsx(ExpandedPanel, {
item: item,
expandedItem: expandedItem,
isExpanded: isExpanded,
expandedWidth: expandedWidth,
isSmallScreen: isSmallScreen,
expandedRef: expandedRef,
onResizeStart: handleResizeStart,
onItemClick: handleItemClick,
isResizing: isResizing,
hasResized: hasResized
}, item.name);
})]
});
};
SideNavV2.propTypes = {
sideNavHeight: PropTypes.string.isRequired,
items: PropTypes.arrayOf(PropTypes.shape({
iconName: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
badgeNumber: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
badgeDot: PropTypes.bool,
hide: PropTypes.bool,
large: PropTypes.bool,
bottomAligned: PropTypes.bool,
pageSpecific: PropTypes.bool,
isExpandedByDefault: PropTypes.bool,
isActive: PropTypes.bool,
actionType: PropTypes.oneOf(["component", "link", "button"]).isRequired,
component: PropTypes.elementType,
link: PropTypes.string,
onClick: PropTypes.func,
index: PropTypes.number
})).isRequired,
yourTeams: PropTypes.arrayOf(PropTypes.shape({
avatar: PropTypes.string,
name: PropTypes.string.isRequired,
link: PropTypes.string.isRequired
})),
pinnedItems: PropTypes.arrayOf(PropTypes.shape({
avatar: PropTypes.string,
name: PropTypes.string.isRequired,
link: PropTypes.string.isRequired,
shape: PropTypes.string,
onUnpin: PropTypes.func
})),
startExpanded: PropTypes.bool,
onKeepExpandedChange: PropTypes.func,
renderTeamsSection: PropTypes.func,
renderPinnedSection: PropTypes.func
};
SideNavV2.__docgenInfo = {
"description": "SideNavV2 - A responsive side navigation component with expandable panels\n\nFeatures:\n- Responsive design that adapts to mobile and desktop\n- Expandable panels with resizable functionality (desktop: width, mobile: height)\n- Toggle handle to expand/collapse the sidebar (desktop only)\n- Team and pinned item sections (optional custom render props)\n- Current view section portal integration\n\n@param {Object} props - Component props\n@param {Array} props.items - Navigation items to display\n@param {string} props.sideNavHeight - Height of the side navigation\n@param {Array} [props.yourTeams] - Teams to display in the teams section\n@param {Array} [props.pinnedItems] - Pinned items to display\n@param {boolean} [props.startExpanded] - Whether the sidebar should start expanded\n@param {(keepExpanded: boolean) => void} [props.onKeepExpandedChange] - Callback when the sidebar expand state changes (e.g. after toggle)\n@param {Function} [props.renderTeamsSection] - Custom render function for teams section; receives { teams, isExpanded }\n@param {Function} [props.renderPinnedSection] - Custom render function for pinned section; receives { items, isExpanded }\n@returns {JSX.Element} Rendered side navigation",
"methods": [],
"displayName": "SideNavV2",
"props": {
"sideNavHeight": {
"description": "",
"type": {
"name": "string"
},
"required": true
},
"items": {
"description": "",
"type": {
"name": "arrayOf",
"value": {
"name": "shape",
"value": {
"iconName": {
"name": "string",
"required": true
},
"name": {
"name": "string",
"required": true
},
"badgeNumber": {
"name": "union",
"value": [{
"name": "string"
}, {
"name": "number"
}],
"required": false
},
"badgeDot": {
"name": "bool",
"required": false
},
"hide": {
"name": "bool",
"required": false
},
"large": {
"name": "bool",
"required": false
},
"bottomAligned": {
"name": "bool",
"required": false
},
"pageSpecific": {
"name": "bool",
"required": false
},
"isExpandedByDefault": {
"name": "bool",
"required": false
},
"isActive": {
"name": "bool",
"required": false
},
"actionType": {
"name": "enum",
"value": [{
"value": "\"component\"",
"computed": false
}, {
"value": "\"link\"",
"computed": false
}, {
"value": "\"button\"",
"computed": false
}],
"required": true
},
"component": {
"name": "elementType",
"required": false
},
"link": {
"name": "string",
"required": false
},
"onClick": {
"name": "func",
"required": false
},
"index": {
"name": "number",
"required": false
}
}
}
},
"required": true
},
"yourTeams": {
"description": "",
"type": {
"name": "arrayOf",
"value": {
"name": "shape",
"value": {
"avatar": {
"name": "string",
"required": false
},
"name": {
"name": "string",
"required": true
},
"link": {
"name": "string",
"required": true
}
}
}
},
"required": false
},
"pinnedItems": {
"description": "",
"type": {
"name": "arrayOf",
"value": {
"name": "shape",
"value": {
"avatar": {
"name": "string",
"required": false
},
"name": {
"name": "string",
"required": true
},
"link": {
"name": "string",
"required": true
},
"shape": {
"name": "string",
"required": false
},
"onUnpin": {
"name": "func",
"required": false
}
}
}
},
"required": false
},
"startExpanded": {
"description": "",
"type": {
"name": "bool"
},
"required": false
},
"onKeepExpandedChange": {
"description": "",
"type": {
"name": "func"
},
"required": false
},
"renderTeamsSection": {
"description": "",
"type": {
"name": "func"
},
"required": false
},
"renderPinnedSection": {
"description": "",
"type": {
"name": "func"
},
"required": false
}
}
};
export default SideNavV2;