import React, { ForwardedRef, forwardRef, useRef, useState } from 'react'; import { Transition } from 'react-transition-group'; import { validateAriaLabelProps } from '@leafygreen-ui/a11y'; import { css, cx } from '@leafygreen-ui/emotion'; import { useEventListener, useIdAllocator } from '@leafygreen-ui/hooks'; import LeafyGreenProvider, { useDarkMode, useUsingKeyboardContext, } from '@leafygreen-ui/leafygreen-provider'; import { keyMap } from '@leafygreen-ui/lib'; import { useUpdatedBaseFontSize } from '@leafygreen-ui/typography'; import { CollapseToggle } from '../CollapseToggle'; import { collapsedNavStyles, collapsedStateStyles, collapseDuration, expandedStateStyles, hoverNavStyles, innerNavWrapperStyle, listStyles, listWrapperStyle, navBaseStyles, navThemeStyles, outerContainerCollapsedStyle, outerContainerStyle, sideNavClassName, sideNavWidth, ulStyleOverrides, } from './SideNav.styles'; import { SideNavProps } from './SideNav.types'; import SideNavContext from './SideNavContext'; const sideNavSelector = `.${sideNavClassName}`; export { sideNavSelector }; /** * The SideNav component should be used for any area of our product that has a navigational structure. * * ``` Back to Home * ``` * * @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.baseFontSize Determines the base font size for the menu items. * @param props.widthOverride Provides an override for the SideNav width. * @param props.collapsed Allows consuming applications to control the collapsed state of the navigation. * @param props.setCollapsed Consuming application's collapsed-state management controller */ const SideNav = forwardRef( ( { className, children, id: idProp, baseFontSize: baseFontSizeProp, widthOverride, collapsed: controlledCollapsed, setCollapsed: setControlledCollapsed = () => {}, darkMode: darkModeProp, ...rest }: SideNavProps, forwardedRef: ForwardedRef, ) => { const { Provider: SideNavProvider } = SideNavContext; const [uncontrolledCollapsed, uncontrolledSetCollapsed] = useState(false); const baseFontSize = useUpdatedBaseFontSize(baseFontSizeProp); const { usingKeyboard } = useUsingKeyboardContext(); const { darkMode, theme } = useDarkMode(darkModeProp); const transitionRef = useRef(null); const [hover, setHover] = useState(false); const [focus, setFocus] = useState(false); const navId = useIdAllocator({ prefix: 'side-nav', id: idProp }); const [portalContainer, setPortalContainer] = useState(null); const width = typeof widthOverride === 'number' ? widthOverride : sideNavWidth; const collapsed = typeof controlledCollapsed === 'boolean' ? controlledCollapsed : uncontrolledCollapsed; const setCollapsed = typeof controlledCollapsed === 'boolean' ? setControlledCollapsed : uncontrolledSetCollapsed; // We visually expand the navigation when a user focuses on an element within the navigation // while navigating via keyboard. const focusExpand = usingKeyboard && focus; // Nav element should have a programmatically-determinable label validateAriaLabelProps(rest, 'SideNav'); // Global event listener for toggling the navigation. useEventListener( 'keypress', e => { const disabledTagNames = ['INPUT', 'TEXTAREA'] as const; // Disable toggling the side navigation when a user is typing in an input. // The typing for useEventListener doesn't seem to like using event.target, // so we disable this here. // @ts-expect-error const shouldToggle = disabledTagNames.includes(e.target?.tagName); if (e.key === keyMap.BracketLeft && !shouldToggle) { setCollapsed(curr => !curr); } }, { options: { passive: true, }, }, ); return ( {state => (
setHover(false)} > { setCollapsed(curr => !curr); setHover(false); }} // This prevents any strange flickering while the navigation is transitioning. hideTooltip={ ['entering', 'exiting'].includes(state) || undefined } />
)}
); }, ); SideNav.displayName = 'SideNav'; export default SideNav;