import React, { useMemo } from 'react'; import { cx } from '@leafygreen-ui/emotion'; import { useIdAllocator } from '@leafygreen-ui/hooks'; import { isComponentGlyph } from '@leafygreen-ui/icon'; import { isComponentType } from '@leafygreen-ui/lib'; import { SideNavGroupCollapsed } from '../SideNavGroupCollapsed'; import { SideNavGroupOpen } from '../SideNavGroupOpen'; import { listItemStyle } from './SideNavGroup.styles'; import { SideNavGroupProps } from './SideNavGroup.types'; /** * # SideNavGroup * * ``` Back to Home * ``` * * @param props.className Class name that will be applied to the root-level element. * @param props.header Content that will be rendered as the component's header * If a string is provided, it will be rendered with default styling as a header tag. * @param props.children Class name that will be applied to the component's header. * @param props.collapsible Determines whether or not the Group can be collapsed. @defaultValue false * @param props.initialCollapsed Determines whether or not the Group is open by default. @defaultValue true */ function SideNavGroup({ header, children, collapsible = false, initialCollapsed = true, glyph, className, hasActiveItem, indentLevel = 0, ...rest }: SideNavGroupProps) { const [open, setOpen] = React.useState(!initialCollapsed); const menuGroupLabelId = useIdAllocator({ prefix: 'menu-group-label-id' }); // Iterate over `children` and render them appropriately const renderedChildren = useMemo(() => { const checkForNestedGroups = (children: React.ReactNode) => { return React.Children.map(children, child => { if ((child as React.ReactElement)?.props?.children) { checkForNestedGroups((child as React.ReactElement).props.children); return child; } if ( isComponentType(child, 'SideNavGroup') || isComponentType(child, 'SideNavItem') ) { return React.cloneElement(child, { indentLevel: indentLevel + 1, }); } return child; }); }; return checkForNestedGroups(children); }, [children, indentLevel]); // compute whether this group is active const isActiveGroup: boolean = useMemo(() => { if (hasActiveItem != null) { return hasActiveItem; } const checkForActiveNestedItems = (children: React.ReactNode): boolean => { let foundActiveChild = false; React.Children.forEach(children, child => { if (isComponentType(child, 'SideNavItem') && child.props.active) { foundActiveChild = true; setOpen(true); } else if ((child as React.ReactElement)?.props?.children) { checkForActiveNestedItems( (child as React.ReactElement).props.children, ); } }); return foundActiveChild; }; return checkForActiveNestedItems(children); }, [hasActiveItem, children]); // render the provided glyph with appropriate aria roles const accessibleGlyph = glyph && isComponentGlyph(glyph) ? React.cloneElement(glyph, { className: glyph.props.className, role: 'presentation', 'data-testid': 'side-nav-group-header-icon', }) : null; // generate shared props for collapsible and static headers const groupHeaderProps = { 'data-testid': 'side-nav-group-header-label', id: menuGroupLabelId, }; const sharedProps = { groupHeaderProps, indentLevel, menuGroupLabelId, accessibleGlyph, isActiveGroup, header, }; return (
  • {collapsible ? ( // collapsed, items in menu are hidden by default but can be toggled open on click <> {renderedChildren} ) : ( // not collapsible, all items in menu are visible {renderedChildren} )}
  • ); } SideNavGroup.displayName = 'SideNavGroup'; export default SideNavGroup;