import type * as React from 'react'; import { useState } from 'react'; import VerticalNav from './VerticalNav'; import VerticalNavItemLabel from './VerticalNavItemLabel'; import { StatusIcon, StatusType } from './StatusIcon'; import classNames from 'classnames'; import useId from '../utilities/useId'; export type VerticalNavItemComponent = React.ReactElement | any | ((...args: any[]) => any); export interface VerticalNavItemProps { /** * Internal prop passed from the parent VerticalNav * @hide-prop This gets passed through from the parent VerticalNav to a nested VerticalNav */ _selectedId?: string; /** * Additional classes to be added to the root element */ className?: string; /** * When provided, this will render the passed in component. This is useful when * integrating with React Router's `` or using your own custom component. */ component?: VerticalNavItemComponent; /** * Whether or not the item's sub-navigation is in a collapsed state by default */ defaultCollapsed?: boolean; /** * Called when the link is clicked, with the following arguments: * [`SyntheticEvent`](https://facebook.github.io/react/docs/events.html), * `id`, `url`. * This takes precedence over the `VerticalNav` `onLinkClick` prop */ onClick?: (evt: React.MouseEvent | React.KeyboardEvent, id: string, url: string) => any; /** * Called when this item's subnav is collapsed or expanded, with the * following arguments: `id`, `collapsed` */ onSubnavToggle?: (id: string, collapsed: boolean) => any; /** * Optional identifier. This can be handy if you're passing in an * `onClick` handler. A unique ID will be generated if one isn't provided. */ id?: string; /** * An array of nested `VerticalNavItem` data objects to be rendered in a * sub-navigation list. */ items?: any[]; /** * Text to render for this nav item */ label: React.ReactNode; /** * A URL to navigate to if this item is a link */ url?: string; /** * If this item is currently selected */ selected?: boolean; /** * @ignore * Internal: Used for indicating component status in the SideNav on design.cms.gov. * Adds a status icon next to the label when used. */ _status?: StatusType; } export const VerticalNavItem = (props: VerticalNavItemProps): React.ReactElement => { const rootId = useId('vertical-nav-item--', props.id); const subnavId = `${rootId}__subnav`; const [collapsed, setCollapsed] = useState(props.defaultCollapsed); /** * Note: This event handler will only get called when the VerticalNavItemLabel * is a link or plain text */ const handleClick = (evt: React.MouseEvent | React.KeyboardEvent): void => { if (props.onClick) { props.onClick(evt, rootId, props.url); } }; const handleToggleClick = (): void => { setCollapsed(!collapsed); if (props.onSubnavToggle) { props.onSubnavToggle(rootId, collapsed); } }; const hasSubnav = (): boolean => Boolean(props.items && props.items.length > 0); /** * Called when VerticalNavItemLabel is clicked. Since the "label" could be * a link, subnav toggle button, or plain text, we use this method to * determine what action to take and which event to actually fire. * @param {Object} SyntheticEvent */ const handleLabelClick = (evt: React.MouseEvent | React.KeyboardEvent): void => { if (hasSubnav()) { return handleToggleClick(); } return handleClick(evt); }; /** * Checks if a descendant is selected * @param {Array} children - The nested items * @return {Boolean} */ const childIsSelected = (children): boolean => { if (children && children.length) { return children.some((child: any) => { return child.id === props._selectedId || childIsSelected(child.items); }); } return false; }; /** * Check if this item is selected or if it is a parent of a selected item * @return {Boolean} */ const isSelected = (): boolean => { if (props.selected) return props.selected; if (props._selectedId && hasSubnav()) { return childIsSelected(props.items); } return false; }; const subnavItems = (): any => { if (props.url) { // Since the VerticalNavItemLabel will just toggle the subnav, we // add a link to the top of the subnav for this item. Otherwise there // wouldn't be a way to actually visit its URL const item = Object.assign({}, props); delete item.items; return [item].concat(props.items); } return props.items; }; const classes = classNames('ds-c-vertical-nav__item', props.className); return (
  • {props.label} ) : ( props.label ) } hasSubnav={hasSubnav()} onClick={handleLabelClick} selected={isSelected()} subnavId={subnavId} url={props.url} /> {hasSubnav() && ( )}
  • ); }; VerticalNavItem.defaultProps = { defaultCollapsed: false, }; export default VerticalNavItem;