Skip to content

Commit

Permalink
update composability based on PR review
Browse files Browse the repository at this point in the history
  • Loading branch information
mfrances17 committed Feb 8, 2024
1 parent 6d6d2f8 commit 0ceff02
Show file tree
Hide file tree
Showing 9 changed files with 80 additions and 491 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ import { DualListSelectorContext } from './DualListSelectorContext';
export interface DualListSelectorProps {
/** Additional classes applied to the dual list selector. */
className?: string;
/** Id of the dual list selector. */
/** ID of the dual list selector. */
id?: string;
/** Flag indicating if the dual list selector uses trees instead of simple lists */
/** Flag indicating if the dual list selector uses trees instead of simple lists. */
isTree?: boolean;
/** Content to be rendered in the dual list selector. Panes & controls will not be built dynamically when children are provided. */
/** Content to be rendered in the dual list selector. */
children?: React.ReactNode;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { handleArrows } from '../../../helpers';
/** Acts as the container for the DualListSelectorControl sub-components. */

export interface DualListSelectorControlsWrapperProps extends React.HTMLProps<HTMLDivElement> {
/** Anything that can be rendered inside of the wrapper. */
/** Content to be rendered inside of the controls wrapper. */
children?: React.ReactNode;
/** Additional classes added to the wrapper. */
className?: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { DualListSelectorListContext } from './DualListSelectorContext';
/** Acts as the container for DualListSelectorListItem sub-components. */

export interface DualListSelectorListProps extends React.HTMLProps<HTMLUListElement> {
/** Content rendered inside the dual list selector list */
/** Content rendered inside the dual list selector list. */
children?: React.ReactNode;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ export interface DualListSelectorListItemProps extends React.HTMLProps<HTMLLIEle
orderIndex?: number;
/** @hide Forwarded ref */
innerRef?: React.RefObject<HTMLLIElement>;
/** Flag indicating this item is draggable for reordering */
/** Flag indicating this item is draggable for reordering. */
isDraggable?: boolean;
/** Accessible label for the draggable button on draggable list items */
/** Accessible label for the draggable button on draggable list items. */
draggableButtonAriaLabel?: string;
/** Flag indicating if the dual list selector is in a disabled state */
/** Flag indicating if the dual list selector is in a disabled state. */
isDisabled?: boolean;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export interface DualListSelectorListWrapperProps extends React.HTMLProps<HTMLDi
className?: string;
/** Anything that can be rendered inside of the list */
children?: React.ReactNode;
/** Id of the dual list selector list */
/** ID of the dual list selector list */
id?: string;
/** Accessibly label for the list */
'aria-labelledby': string;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
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 { DualListSelectorPaneContext } from './DualListSelectorContext';
import { SearchInput } from '../../../components/SearchInput';
import cssMenuMinHeight from '@patternfly/react-tokens/dist/esm/c_dual_list_selector__menu_MinHeight';

Expand All @@ -29,43 +27,12 @@ export interface DualListSelectorPaneProps extends Omit<React.HTMLProps<HTMLDivE
searchInput?: React.ReactNode;
/** Actions to place above the pane. */
actions?: React.ReactNode[];
/** Id of the pane. */
/** 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<HTMLInputElement> | 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<HTMLInputElement>) => void;
/** @hide A callback for when the search input value for changes. To be used when isSearchable is true. */
onSearchInputChanged?: (event: React.FormEvent<HTMLInputElement>, value: string) => void;
/** @hide Callback for search input clear button */
onSearchInputClear?: (event: React.SyntheticEvent<HTMLButtonElement>) => 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;
}
Expand All @@ -77,169 +44,51 @@ export const DualListSelectorPane: React.FunctionComponent<DualListSelectorPaneP
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<HTMLInputElement>, newValue: string) => {
let filtered: React.ReactNode[];
if (isTree) {
filtered = options
.map((opt) => Object.assign({}, opt))
.filter((item) => filterInput(item as React.ReactNode[] & 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 DualListSelectorTreeItemData & 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 (
<div
className={css(styles.dualListSelectorPane, isChosen ? styles.modifiers.chosen : 'pf-m-available', className)}
{...props}
>
{title && (
<div className={css(styles.dualListSelectorHeader)}>
<div className={`${styles.dualListSelector}__title`}>
<div className={css(styles.dualListSelectorTitleText)}>{title}</div>
</div>
</div>
)}
{(actions || searchInput || isSearchable) && (
<div className={css(styles.dualListSelectorTools)}>
{(isSearchable || searchInput) && (
<div className={css(styles.dualListSelectorToolsFilter)}>
{searchInput ? (
searchInput
) : (
<SearchInput
onChange={isDisabled ? undefined : onChange}
onClear={
onSearchInputClear
? onSearchInputClear
: (e) => onChange(e as React.FormEvent<HTMLInputElement>, '')
}
isDisabled={isDisabled}
aria-label={searchInputAriaLabel}
/>
)}
</div>
)}
{actions && <div className={css(styles.dualListSelectorToolsActions)}>{actions}</div>}
}: DualListSelectorPaneProps) => (
<div
className={css(styles.dualListSelectorPane, isChosen ? styles.modifiers.chosen : 'pf-m-available', className)}
{...props}
>
{title && (
<div className={css(styles.dualListSelectorHeader)}>
<div className={`${styles.dualListSelector}__title`}>
<div className={css(styles.dualListSelectorTitleText)}>{title}</div>
</div>
)}
{status && (
<div className={css(styles.dualListSelectorStatus)}>
<div className={css(styles.dualListSelectorStatusText)} id={`${id}-status`}>
{status}
</div>
)}
{(actions || searchInput) && (
<div className={css(styles.dualListSelectorTools)}>
{searchInput && (
<div className={css(styles.dualListSelectorToolsFilter)}>
{searchInput ? searchInput : <SearchInput isDisabled={isDisabled} />}
</div>
</div>
)}
<DualListSelectorPaneContext.Provider value={{ isChosen }}>
{!isTree && (
<DualListSelectorListWrapper
aria-labelledby={`${id}-status`}
options={options}
selectedOptions={selectedOptions}
onOptionSelect={(
e: React.MouseEvent | React.ChangeEvent | React.KeyboardEvent,
index: number,
id: string
) => onOptionSelect(e, index, isChosen, id)}
displayOption={displayOption}
id={`${id}-list`}
isDisabled={isDisabled}
{...(listMinHeight && {
style: { [cssMenuMinHeight.name]: listMinHeight } as React.CSSProperties
})}
>
{children}
</DualListSelectorListWrapper>
)}
{isTree && (
<DualListSelectorListWrapper
aria-labelledby={`${id}-status`}
id={`${id}-list`}
{...(listMinHeight && {
style: { [cssMenuMinHeight.name]: listMinHeight } as React.CSSProperties
})}
>
{options.length > 0 ? (
<DualListSelectorList>
<DualListSelectorTree
data={
isSearchable
? (options
.map((opt) => Object.assign({}, opt))
.filter((item) =>
filterInput(item as React.ReactNode[] & DualListSelectorTreeItemData, input)
) as React.ReactNode[] & DualListSelectorTreeItemData[])
: (options as React.ReactNode[] & DualListSelectorTreeItemData[])
}
onOptionCheck={onOptionCheck}
id={`${id}-tree`}
isDisabled={isDisabled}
/>
</DualListSelectorList>
) : (
children
)}
</DualListSelectorListWrapper>
)}
</DualListSelectorPaneContext.Provider>
</div>
);
};
{actions && <div className={css(styles.dualListSelectorToolsActions)}>{actions}</div>}
</div>
)}
{status && (
<div className={css(styles.dualListSelectorStatus)}>
<div className={css(styles.dualListSelectorStatusText)} id={`${id}-status`}>
{status}
</div>
</div>
)}
<DualListSelectorPaneContext.Provider value={{ isChosen }}>
<DualListSelectorListWrapper
aria-labelledby={`${id}-status`}
id={`${id}-list`}
{...(listMinHeight && {
style: { [cssMenuMinHeight.name]: listMinHeight } as React.CSSProperties
})}
>
{children}
</DualListSelectorListWrapper>
</DualListSelectorPaneContext.Provider>
</div>
);
DualListSelectorPane.displayName = 'DualListSelectorPane';
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,26 @@ export interface DualListSelectorTreeItemData {
className?: string;
/** Flag indicating this option is expanded by default. */
defaultExpanded?: boolean;
/** Flag indicating this option has a badge */
/** Flag indicating this option has a badge. */
hasBadge?: boolean;
/** Callback fired when an option is checked */
/** Callback fired when an option is checked. */
onOptionCheck?: (
event: React.MouseEvent | React.ChangeEvent<HTMLInputElement> | React.KeyboardEvent,
isChecked: boolean,
isChosen: boolean,
itemData: DualListSelectorTreeItemData
) => void;
/** ID of the option */
/** ID of the option. */
id: string;
/** Text of the option */
/** Text of the option. */
text: string;
/** Parent id of an option */
/** Parent ID of an option. */
parentId?: string;
/** Checked state of the option */
/** Checked state of the option. */
isChecked: boolean;
/** Additional properties to pass to the option checkbox */
/** Additional properties to pass to the option checkbox. */
checkProps?: any;
/** Additional properties to pass to the option badge */
/** Additional properties to pass to the option badge. */
badgeProps?: any;
/** Flag indicating whether the component is disabled. */
isDisabled?: boolean;
Expand All @@ -40,19 +40,19 @@ export interface DualListSelectorTreeItemData {
*/

export interface DualListSelectorTreeProps extends Omit<React.HTMLProps<HTMLUListElement>, 'data'> {
/** Data of the tree view */
/** Data of the tree view. */
data: DualListSelectorTreeItemData[] | (() => DualListSelectorTreeItemData[]);
/** ID of the tree view */
/** ID of the tree view. */
id?: string;
/** @hide Flag indicating if the list is nested */
/** @hide Flag indicating if the list is nested. */
isNested?: boolean;
/** Flag indicating if all options should have badges */
/** Flag indicating if all options should have badges. */
hasBadges?: boolean;
/** Sets the default expanded behavior */
/** Sets the default expanded behavior. */
defaultAllExpanded?: boolean;
/** Flag indicating if the dual list selector tree is in the disabled state */
/** Flag indicating if the dual list selector tree is in the disabled state. */
isDisabled?: boolean;
/** Callback fired when an option is checked */
/** Callback fired when an option is checked. */
onOptionCheck?: (
event: React.MouseEvent | React.ChangeEvent<HTMLInputElement> | React.KeyboardEvent,
isChecked: boolean,
Expand Down
Loading

0 comments on commit 0ceff02

Please sign in to comment.