import React , { ReactNode , useRef , useMemo } from 'react' ; import PropTypes from 'prop-types' ; import { transparentize } from 'polished' ; import { AriaCurrentValue , createDataProp , isComponentType , } from '@leafygreen-ui/lib' ; import { useUsingKeyboardContext } from '@leafygreen-ui/leafygreen-provider' ; import Box , { ExtendableBox } from '@leafygreen-ui/box' ; import { uiColors } from '@leafygreen-ui/palette' ; import { css , cx } from '@leafygreen-ui/emotion' ; import { spacing , fontFamilies } from '@leafygreen-ui/tokens' ; import CollapsedSideNavItem from './CollapsedSideNavItem' ; import { useSideNavContext } from './SideNavContext' ; import { getIndentLevelStyle } from './styles' ; const sideNavItemContainer = createDataProp ( 'side-nav-item-container' ) ; const defaultStyle = css `
  // Unset defaults
  margin: 0;
  appearance: none;
  background: none;
  border: none;
  cursor: pointer;

  // Layout
  width: 100%;
  min-height: 32px;
  padding: ${ spacing [ 1 ] }px ${ spacing [ 3 ] }px;
  box-sizing: border-box;
  display: flex;
  align-items: center;

  // Typography
  font-family: ${ fontFamilies . default };
  font-weight: normal;
  text-align: left;
  text-decoration: none;
  text-transform: capitalize;
  color: ${ uiColors . gray . dark2 };

  // Stateful transitions
  transition: background-color 150ms ease-in-out;
  background-color: ${ transparentize ( 100 , uiColors . gray . light3 ) };

  &:hover {
    background-color: ${ uiColors . gray . light2 };
    text-decoration: none;
  }

  &:focus {
    text-decoration: none;
    outline: none;
  }

  &::-moz-focus-inner {
    border: 0;
  }
` ; const typographyStyle = { [ 14 ] : css `
    font-size: 14px;
    line-height: 20px;
  ` , [ 16 ] : css `
    font-size: 16px;
    line-height: 24px;
  ` , } ; const activeStyle = css `
  cursor: default;
  font-weight: bold;
  text-decoration: none;

  &,
  &:hover {
    color: ${ uiColors . green . dark3 };
    background-color: ${ uiColors . green . light3 };
  }
` ; const disabledStyle = css `
  pointer-events: none;
  background-color: transparent;
  font-weight: normal;

  &,
  &:hover {
    color: ${ uiColors . gray . light1 };
    background-color: ${ transparentize ( 100 , uiColors . gray . light3 ) };
  }
` ; const focusedStyle = css `
  position: relative;

  &:before {
    transition: all 150ms ease-in-out;
    content: '';
    transform: scaleY(0.3);
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    width: 3px;
    height: 20px;
    margin: auto;
    background-color: ${ uiColors . focus };
    opacity: 0;
    border-radius: 0 3px 3px 0;
  }

  &:focus {
    text-decoration: none;
    color: ${ uiColors . blue . dark3 };
    background-color: ${ uiColors . blue . light2 };

    &:before {
      opacity: 1;
      transform: scaleY(1);
    }
  }
` ; const focusedDisabledStyle = css `
  &:focus {
    color: ${ uiColors . blue . light1 };
  }
` ; const glyphWrapper = css `
  margin-right: ${ spacing [ 2 ] }px;
  display: inline-flex;
  align-items: center;
` ; const nestedChildrenStyles = css `
  display: flex;
  flex-direction: column;
  align-items: flex-start;
` ; export interface SideNavItemProps { /**
     * Whether or not the component should be rendered in an active state.
     *
     * default: `false`
     */ active ? : boolean ; /**
     * Whether or not the component should be rendered in a disabled state.
     *
     * default: `false`
     */ disabled ? : boolean ; /**
     * The aria-current attribute value set when the component is active.
     *
     * default: `"page"`
     */ ariaCurrentValue ? : AriaCurrentValue ; /**
     * Class name that will be applied to the root-level element.
     */ className ? : string ; /**
     * Content that will be rendered inside the root-level element.
     */ children ? : ReactNode ; /**
     * When provided, the component will be rendered as an anchor element with the passed href value.
     */ href ? : string ; /**
     * The event handler function for the 'onclick' event. Receives the associated `event` object as the first argument.
     */ onClick ? : React . MouseEventHandler ; /**
     * Icon that's rendered in the item.
     */ glyph ? : React . ReactNode ; indentLevel ? : number ; isParentActive ? : boolean ; } param props . active param props . disabled param props . ariaCurrentValue param props . className param props . children param props . href param props . as const SideNavItem = React . forwardRef ( ( props , forwardRef ) => { const { active = false , disabled = false , ariaCurrentValue = AriaCurrentValue . Page , indentLevel = 1 , className , children , onClick : onClickProp , glyph , ... rest } = props ; const { usingKeyboard : showFocus } = useUsingKeyboardContext ( ) ; const { baseFontSize = 14 } = useSideNavContext ( ) ; const hasNestedChildren = useRef ( false ) ; const onClick = disabled ? ( e ) => { e . nativeEvent . stopImmediatePropagation ( ) ; e . preventDefault ( ) ; } : ( e ) => { onClickProp ?. ( e ) ; } ; const accessibleGlyph = ( glyph && isComponentType ( glyph , 'Glyph' ) ) || isComponentType ( glyph , 'Icon' ) ? React . cloneElement ( glyph , { 'aria-hidden' : true } ) : null ; const { hasNestedItems , renderedNestedItems } = useMemo ( ( ) => { const renderedNestedItems = [ ] ; let hasNestedItems = false ; 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 , } ) ) ; } } } ) ; function hasActiveNestedItems ( children ) { 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 = [ ] ; 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 ( < li className = { css `
        width: 100%;
      ` } > < Box as = { props . href ? 'a' : 'button' } { ... rest } { ... sideNavItemContainer . prop } className = { cx ( defaultStyle , typographyStyle [ baseFontSize ] , { [ activeStyle ] : active , [ disabledStyle ] : disabled , [ focusedStyle ] : showFocus , [ focusedDisabledStyle ] : showFocus && disabled , [ nestedChildrenStyles ] : hasNestedChildren . current , [ getIndentLevelStyle ( indentLevel ) ] : indentLevel > 1 , } , className ) } aria - current = { active ? ariaCurrentValue : AriaCurrentValue . Unset } aria - disabled = { disabled } ref = { forwardRef } onClick = { onClick } > { accessibleGlyph && ( < span className = { glyphWrapper } > { accessibleGlyph } < CollapsedSideNavItem active = { active } > { accessibleGlyph } < /CollapsedSideNavItem> < /span>)} { renderedChildren } < /Box> { hasNestedItems && ( < ul className = { css `
            list-style: none;
            padding-inline-start: 0;
          ` } > { renderedNestedItems } < /ul>)} < /li>); } ) ; SideNavItem . displayName = 'SideNavItem' ; SideNavItem . propTypes = { active : PropTypes . bool , disabled : PropTypes . bool , className : PropTypes . string , ariaCurrentValue : PropTypes . oneOf ( Object . values ( AriaCurrentValue ) ) , children : PropTypes . node , href : PropTypes . string , } ; export default SideNavItem ;