import { memo, useContext, useEffect, useRef, useState, cloneElement, Children, isValidElement } from 'react'; import styles from '@patternfly/react-styles/css/components/DualListSelector/dual-list-selector'; import { css } from '@patternfly/react-styles'; import { DualListSelectorTreeItemData } from './DualListSelectorTree'; import { Badge } from '../Badge'; import RhMicronsCaretDownIcon from '@patternfly/react-icons/dist/esm/icons/rh-microns-caret-down-icon'; import { flattenTree } from './treeUtils'; import { DualListSelectorListContext } from './DualListSelectorContext'; import { useHasAnimations } from '../../helpers'; export interface DualListSelectorTreeItemProps extends React.HTMLProps { /** Content rendered inside the dual list selector. */ children?: React.ReactNode; /** Additional classes applied to the dual list selector. */ className?: string; /** Flag indicating this option is expanded by default. */ defaultExpanded?: boolean; /** Flag indicating this option has a badge. */ hasBadge?: boolean; /** Callback fired when an option is checked. */ onOptionCheck?: ( event: React.MouseEvent | React.ChangeEvent | React.KeyboardEvent, isChecked: boolean, itemData: DualListSelectorTreeItemData ) => void; /** ID of the option. */ id: string; /** Text of the option. */ text: string; /** Flag indicating if this open is checked. */ isChecked?: boolean; /** Additional properties to pass to the option checkbox. */ checkProps?: any; /** Additional properties to pass to the option badge. */ badgeProps?: any; /** Raw data of the option. */ itemData?: DualListSelectorTreeItemData; /** Flag indicating whether the component is disabled. */ isDisabled?: boolean; /** Flag indicating the DualListSelector tree should utilize memoization to help render large data sets. */ useMemo?: boolean; /** Flag indicating whether a tree dual list selector has animations. This will always render * nested dual list selector items rather than dynamically rendering them. This prop will be removed in * the next breaking change release in favor of defaulting to always-rendered items. */ hasAnimations?: boolean; } const DualListSelectorTreeItemBase: React.FunctionComponent = ({ onOptionCheck, children, className, id, text, defaultExpanded, hasBadge, isChecked, checkProps, badgeProps, itemData, isDisabled = false, hasAnimations: hasAnimationsProp, // eslint-disable-next-line @typescript-eslint/no-unused-vars useMemo, ...props }: DualListSelectorTreeItemProps) => { const ref = useRef(null); const [isExpanded, setIsExpanded] = useState(defaultExpanded || false); const { setFocusedOption } = useContext(DualListSelectorListContext); const hasAnimations = useHasAnimations(hasAnimationsProp); useEffect(() => { setIsExpanded(defaultExpanded); }, [defaultExpanded]); const clonedChildren = Children.map( children, (child) => isValidElement(child) && cloneElement(child as React.ReactElement, { inert: isExpanded ? undefined : '' }) ); return (
  • { onOptionCheck && onOptionCheck(evt, !isChecked, itemData); setFocusedOption(id); } } > {children && (
    { if (children) { setIsExpanded(!isExpanded); } e.stopPropagation(); }} onKeyDown={(e: React.KeyboardEvent) => { if (e.key === ' ' || e.key === 'Enter') { (document.activeElement as HTMLElement).click(); e.preventDefault(); } }} tabIndex={-1} >
    )} ) => { onOptionCheck && onOptionCheck(evt, !isChecked, itemData); setFocusedOption(id); }} onClick={(evt: React.MouseEvent) => evt.stopPropagation()} onKeyDown={(e: React.KeyboardEvent) => { if (e.key === ' ' || e.key === 'Enter') { onOptionCheck && onOptionCheck(e, !isChecked, itemData); setFocusedOption(id); e.preventDefault(); } }} ref={(elem) => { elem && (elem.indeterminate = isChecked === null); }} checked={isChecked || false} tabIndex={-1} {...checkProps} /> {text} {hasBadge && children && ( {flattenTree((children as React.ReactElement).props.data).length} )}
    {(isExpanded || hasAnimations) && clonedChildren}
  • ); }; export const DualListSelectorTreeItem = memo(DualListSelectorTreeItemBase, (prevProps, nextProps) => { if (!nextProps.useMemo) { return false; } if ( prevProps.className !== nextProps.className || prevProps.text !== nextProps.text || prevProps.id !== nextProps.id || prevProps.defaultExpanded !== nextProps.defaultExpanded || prevProps.checkProps !== nextProps.checkProps || prevProps.hasBadge !== nextProps.hasBadge || prevProps.badgeProps !== nextProps.badgeProps || prevProps.isChecked !== nextProps.isChecked || prevProps.itemData !== nextProps.itemData ) { return false; } return true; }); DualListSelectorTreeItem.displayName = 'DualListSelectorTreeItem';