import React, { useMemo, useRef } from 'react';
import { cx } from '@leafygreen-ui/emotion';
import { isComponentGlyph } from '@leafygreen-ui/icon';
import { useUsingKeyboardContext } from '@leafygreen-ui/leafygreen-provider';
import { AriaCurrentValue, isComponentType } from '@leafygreen-ui/lib';
import {
InferredPolymorphic,
useInferredPolymorphic,
} from '@leafygreen-ui/polymorphic';
import { AccessibleGlyph } from '../AccessibleGlyph';
import {
getIndentLevelStyle,
typographyStyle,
useSideNavContext,
} from '../SideNav';
import {
activeBaseStyle,
activeThemeStyle,
baseStyle,
disabledStyle,
focusedDisabledStyle,
focusedDisabledThemeStyle,
focusedStyle,
focusedThemeStyle,
glyphWrapperStyle,
liStyle,
nestedChildrenStyles,
nestedUlStyle,
sideNavItemClassName,
themeStyle,
} from './SideNavItem.styles';
import { BaseSideNavItemProps } from './SideNavItem.types';
/**
* TODO: TSDoc
*
* ```
Back to Home
* ```
*
### Component Props
@param props.active Whether or not the component should be rendered in an active state.
@param props.disabled Whether or not the component should be rendered in a disabled state.
@param props.ariaCurrentValue The aria-current attribute value set when the component is active.
@param props.className Class name that will be applied to the root-level element.
@param props.children Content that will be rendered inside the root-level element.
@param props.indentLevel Change the indentation. Will not work if `` is a child of ``
*
### Optional Polymorphic Props
@param props.href When provided, the component will be rendered as an anchor element. This and
* other additional props will be spread on the anchor element.
@param props.as When provided, the component will be rendered as the component or html tag indicated
* by this prop. Other additional props will be spread on the anchor element.
*/
const SideNavItem = InferredPolymorphic(
(props, forwardedRef) => {
const {
as,
active = false,
disabled = false,
ariaCurrentValue = AriaCurrentValue.Page,
indentLevel = 1,
className,
children,
onClick: onClickProp,
glyph,
...restProps
} = props;
const { Component, rest } = useInferredPolymorphic(as, restProps, 'button');
const { usingKeyboard } = useUsingKeyboardContext();
const { baseFontSize, theme, darkMode } = useSideNavContext();
const hasNestedChildren = useRef(false);
const onClick = disabled
? (e: React.MouseEvent) => {
e.nativeEvent.stopImmediatePropagation();
e.preventDefault();
}
: (e: React.MouseEvent) => {
onClickProp?.(e);
};
const accessibleGlyph =
glyph && isComponentGlyph(glyph)
? React.cloneElement(glyph, { 'aria-hidden': true })
: null;
const { hasNestedItems, renderedNestedItems } = useMemo(() => {
const renderedNestedItems: Array = [];
let hasNestedItems = false; // Whether this item has nested descendants
React.Children.forEach(children, (child, index) => {
if (
(child != null && isComponentType(child, 'SideNavItem')) ||
isComponentType(child, 'SideNavGroup')
) {
hasNestedItems = true;
if (active || hasActiveNestedItems(children)) {
renderedNestedItems.push(
React.cloneElement(child, {
indentLevel: indentLevel + 1,
key: index,
}),
);
}
}
});
// Recursive function to determine if a SideNavItem has an active descendant
function hasActiveNestedItems(children: React.ReactNode): boolean {
let hasActiveDescendant = false;
React.Children.forEach(children, child => {
if (hasActiveDescendant) return;
else if (
isComponentType(child, 'SideNavItem') &&
child.props.active
) {
hasActiveDescendant = true;
} else if (
(child as React.ReactElement)?.props?.children &&
typeof (child as React.ReactElement).props.children == 'object'
) {
hasActiveDescendant = hasActiveNestedItems(
(child as React.ReactElement).props.children,
);
}
});
return hasActiveDescendant;
}
return { hasNestedItems, renderedNestedItems };
}, [children, active, indentLevel]);
const renderedChildren = useMemo(() => {
const renderedChildren: Array = [];
React.Children.forEach(children, child => {
if (!child) {
return null;
}
if (
isComponentType(child, 'SideNavItem') ||
isComponentType(child, 'SideNavGroup')
) {
return null;
}
renderedChildren.push(child);
});
return renderedChildren;
}, [children]);
return (
1,
},
className,
)}
aria-current={active ? ariaCurrentValue : AriaCurrentValue.Unset}
aria-disabled={disabled}
ref={forwardedRef}
onClick={onClick}
>
{accessibleGlyph && (
)}
{renderedChildren}
{hasNestedItems && (
)}
);
},
);
SideNavItem.displayName = 'SideNavItem';
export default SideNavItem;