import * as React from 'react'; import styles from '@patternfly/react-styles/css/components/DualListSelector/dual-list-selector'; import { css } from '@patternfly/react-styles'; import { DualListSelectorTree, DualListSelectorTreeItemData } from './DualListSelectorTree'; import { getUniqueId } from '../../helpers'; import { DualListSelectorListWrapper } from './DualListSelectorListWrapper'; import { DualListSelectorContext, DualListSelectorPaneContext } from './DualListSelectorContext'; import { DualListSelectorList } from './DualListSelectorList'; import { SearchInput } from '../SearchInput'; /** Acts as the container for a list of options that are either available or chosen, * depending on the pane type (available or chosen). A search input and other actions, * such as sorting, can also be passed into this sub-component. */ export interface DualListSelectorPaneProps extends Omit, 'title'> { /** Additional classes applied to the dual list selector pane. */ className?: string; /** A dual list selector list or dual list selector tree to be rendered in the pane. */ children?: React.ReactNode; /** Flag indicating if this pane is the chosen pane. */ isChosen?: boolean; /** Status to display above the pane. */ status?: string; /** Title of the pane. */ title?: React.ReactNode; /** A search input placed above the list at the top of the pane, before actions. */ searchInput?: React.ReactNode; /** Actions to place above the pane. */ actions?: React.ReactNode[]; /** Id of the pane. */ id?: string; /** @hide Options to list in the pane. */ options?: React.ReactNode[]; /** @hide Options currently selected in the pane. */ selectedOptions?: string[] | number[]; /** @hide Callback for when an option is selected. Optionally used only when options prop is provided. */ onOptionSelect?: ( event: React.MouseEvent | React.ChangeEvent | React.KeyboardEvent, index: number, isChosen: boolean, id?: string, itemData?: any, parentData?: any ) => void; /** @hide Callback for when a tree option is checked. Optionally used only when options prop is provided. */ onOptionCheck?: ( event: React.MouseEvent | React.ChangeEvent | React.KeyboardEvent, isChecked: boolean, itemData: DualListSelectorTreeItemData ) => void; /** @hide Flag indicating a dynamically built search bar should be included above the pane. */ isSearchable?: boolean; /** Flag indicating whether the component is disabled. */ isDisabled?: boolean; /** Callback for search input. To be used when isSearchable is true. */ onSearch?: (event: React.ChangeEvent) => void; /** @hide A callback for when the search input value for changes. To be used when isSearchable is true. */ onSearchInputChanged?: (event: React.FormEvent, value: string) => void; /** @hide Callback for search input clear button */ onSearchInputClear?: (event: React.SyntheticEvent) => void; /** @hide Filter function for custom filtering based on search string. To be used when isSearchable is true. */ filterOption?: (option: React.ReactNode, input: string) => boolean; /** @hide Accessible label for the search input. To be used when isSearchable is true. */ searchInputAriaLabel?: string; /** @hide Callback for updating the filtered options in DualListSelector. To be used when isSearchable is true. */ onFilterUpdate?: (newFilteredOptions: React.ReactNode[], paneType: string, isSearchReset: boolean) => void; /** Minimum height of the list of options rendered in the pane. **/ listMinHeight?: string; } export const DualListSelectorPane: React.FunctionComponent = ({ isChosen = false, className = '', status = '', actions, searchInput, children, onOptionSelect, onOptionCheck, title = '', options = [], selectedOptions = [], isSearchable = false, searchInputAriaLabel = '', onFilterUpdate, onSearchInputChanged, onSearchInputClear, filterOption, id = getUniqueId('dual-list-selector-pane'), isDisabled = false, listMinHeight, ...props }: DualListSelectorPaneProps) => { const [input, setInput] = React.useState(''); const { isTree } = React.useContext(DualListSelectorContext); // only called when search input is dynamically built const onChange = (e: React.FormEvent, newValue: string) => { let filtered: React.ReactNode[]; if (isTree) { filtered = options .map((opt) => Object.assign({}, opt)) .filter((item) => filterInput(item as unknown as DualListSelectorTreeItemData, newValue)); } else { filtered = options.filter((option) => { if (displayOption(option)) { return option; } }); } onFilterUpdate(filtered, isChosen ? 'chosen' : 'available', newValue === ''); if (onSearchInputChanged) { onSearchInputChanged(e, newValue); } setInput(newValue); }; // only called when options are passed via options prop and isTree === true const filterInput = (item: DualListSelectorTreeItemData, input: string): boolean => { if (filterOption) { return filterOption(item as unknown as React.ReactNode, input); } else { if (item.text.toLowerCase().includes(input.toLowerCase()) || input === '') { return true; } } if (item.children) { return ( (item.children = item.children .map((opt) => Object.assign({}, opt)) .filter((child) => filterInput(child, input))).length > 0 ); } }; // only called when options are passed via options prop and isTree === false const displayOption = (option: React.ReactNode) => { if (filterOption) { return filterOption(option, input); } else { return option.toString().toLowerCase().includes(input.toLowerCase()); } }; return (
{title && (
{title}
)} {(actions || searchInput || isSearchable) && (
{(isSearchable || searchInput) && (
{searchInput ? ( searchInput ) : ( onChange(e as React.FormEvent, '') } isDisabled={isDisabled} aria-label={searchInputAriaLabel} /> )}
)} {actions &&
{actions}
}
)} {status && (
{status}
)} {!isTree && ( onOptionSelect(e, index, isChosen, id)} displayOption={displayOption} id={`${id}-list`} isDisabled={isDisabled} {...(listMinHeight && { style: { '--pf-v5-c-dual-list-selector__menu--MinHeight': listMinHeight } as React.CSSProperties })} > {children} )} {isTree && ( {options.length > 0 ? ( Object.assign({}, opt)) .filter((item) => filterInput(item as unknown as DualListSelectorTreeItemData, input) ) as unknown as DualListSelectorTreeItemData[]) : (options as unknown as DualListSelectorTreeItemData[]) } onOptionCheck={onOptionCheck} id={`${id}-tree`} isDisabled={isDisabled} /> ) : ( children )} )}
); }; DualListSelectorPane.displayName = 'DualListSelectorPane';