UNPKG

orcs-design-system

Version:
388 lines (380 loc) 13 kB
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;