Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(DualListSelector): added empty state to composable examples #8480

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { SearchInput } from '../SearchInput';
* such as sorting, can also be passed into this sub-component.
*/

export interface DualListSelectorPaneProps {
export interface DualListSelectorPaneProps extends Omit<React.HTMLProps<HTMLDivElement>, '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. */
Expand Down Expand Up @@ -65,6 +65,8 @@ export interface DualListSelectorPaneProps {
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<DualListSelectorPaneProps> = ({
Expand All @@ -87,6 +89,7 @@ export const DualListSelectorPane: React.FunctionComponent<DualListSelectorPaneP
filterOption,
id = getUniqueId('dual-list-selector-pane'),
isDisabled = false,
listMinHeight,
...props
}: DualListSelectorPaneProps) => {
const [input, setInput] = React.useState('');
Expand Down Expand Up @@ -198,12 +201,21 @@ export const DualListSelectorPane: React.FunctionComponent<DualListSelectorPaneP
displayOption={displayOption}
id={`${id}-list`}
isDisabled={isDisabled}
{...(listMinHeight && {
style: { '--pf-c-dual-list-selector__menu--MinHeight': listMinHeight } as React.CSSProperties
})}
>
{children}
</DualListSelectorListWrapper>
)}
{isTree && (
<DualListSelectorListWrapper aria-labelledby={`${id}-status`} id={`${id}-list`}>
<DualListSelectorListWrapper
aria-labelledby={`${id}-status`}
id={`${id}-list`}
{...(listMinHeight && {
style: { '--pf-c-dual-list-selector__menu--MinHeight': listMinHeight } as React.CSSProperties
})}
>
{options.length > 0 ? (
<DualListSelectorList>
<DualListSelectorTree
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import AngleLeftIcon from '@patternfly/react-icons/dist/esm/icons/angle-left-ico
import AngleDoubleRightIcon from '@patternfly/react-icons/dist/esm/icons/angle-double-right-icon';
import AngleRightIcon from '@patternfly/react-icons/dist/esm/icons/angle-right-icon';
import PficonSortCommonAscIcon from '@patternfly/react-icons/dist/esm/icons/pficon-sort-common-asc-icon';
import SearchIcon from '@patternfly/react-icons/dist/esm/icons/search-icon';

## Examples

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,20 @@ import {
DualListSelectorListItem,
DualListSelectorControlsWrapper,
DualListSelectorControl,
SearchInput
SearchInput,
Title,
EmptyState,
EmptyStateVariant,
EmptyStateIcon,
EmptyStateBody,
EmptyStatePrimary
} from '@patternfly/react-core';
import AngleDoubleLeftIcon from '@patternfly/react-icons/dist/esm/icons/angle-double-left-icon';
import AngleLeftIcon from '@patternfly/react-icons/dist/esm/icons/angle-left-icon';
import AngleDoubleRightIcon from '@patternfly/react-icons/dist/esm/icons/angle-double-right-icon';
import AngleRightIcon from '@patternfly/react-icons/dist/esm/icons/angle-right-icon';
import PficonSortCommonAscIcon from '@patternfly/react-icons/dist/esm/icons/pficon-sort-common-asc-icon';
import SearchIcon from '@patternfly/react-icons/dist/esm/icons/search-icon';

interface Option {
text: string;
Expand Down Expand Up @@ -86,25 +93,23 @@ export const DualListSelectorComposable: React.FunctionComponent = () => {
}
};

// builds a search input - used in each dual list selector pane
const buildSearchInput = (isAvailable: boolean) => {
const onChange = (value: string) => {
isAvailable ? setAvailableFilter(value) : setChosenFilter(value);
const toFilter = isAvailable ? [...availableOptions] : [...chosenOptions];
toFilter.forEach(option => {
option.isVisible = value === '' || option.text.toLowerCase().includes(value.toLowerCase());
});
};

return (
<SearchInput
value={isAvailable ? availableFilter : chosenFilter}
onChange={onChange}
onClear={() => onChange('')}
/>
);
const onFilterChange = (value: string, isAvailable: boolean) => {
isAvailable ? setAvailableFilter(value) : setChosenFilter(value);
const toFilter = isAvailable ? [...availableOptions] : [...chosenOptions];
toFilter.forEach(option => {
option.isVisible = value === '' || option.text.toLowerCase().includes(value.toLowerCase());
});
};

// builds a search input - used in each dual list selector pane
const buildSearchInput = (isAvailable: boolean) => (
<SearchInput
value={isAvailable ? availableFilter : chosenFilter}
onChange={value => onFilterChange(value, isAvailable)}
onClear={() => onFilterChange('', isAvailable)}
/>
);

// builds a sort control - passed to both dual list selector panes
const buildSort = (isAvailable: boolean) => {
const onSort = () => {
Expand Down Expand Up @@ -132,6 +137,21 @@ export const DualListSelectorComposable: React.FunctionComponent = () => {
);
};

const buildEmptyState = (isAvailable: boolean) => (
<EmptyState variant={EmptyStateVariant.small}>
<EmptyStateIcon icon={SearchIcon} />
<Title headingLevel="h4" size="md">
No results found
</Title>
<EmptyStateBody>No results match the filter criteria. Clear all filters and try again.</EmptyStateBody>
<EmptyStatePrimary>
<Button variant="link" onClick={() => onFilterChange('', isAvailable)}>
Clear all filters
</Button>
</EmptyStatePrimary>
</EmptyState>
);

return (
<DualListSelector>
<DualListSelectorPane
Expand All @@ -141,21 +161,27 @@ export const DualListSelectorComposable: React.FunctionComponent = () => {
} options selected`}
searchInput={buildSearchInput(true)}
actions={[buildSort(true)]}
listMinHeight="300px"
>
<DualListSelectorList>
{availableOptions.map((option, index) =>
option.isVisible ? (
<DualListSelectorListItem
key={index}
isSelected={option.selected}
id={`composable-available-option-${index}`}
onOptionSelect={e => onOptionSelect(e, index, false)}
>
{option.text}
</DualListSelectorListItem>
) : null
)}
</DualListSelectorList>
{availableFilter !== '' &&
availableOptions.filter(option => option.isVisible).length === 0 &&
buildEmptyState(true)}
{availableOptions.filter(option => option.isVisible).length > 0 && (
<DualListSelectorList>
{availableOptions.map((option, index) =>
option.isVisible ? (
<DualListSelectorListItem
key={index}
isSelected={option.selected}
id={`composable-available-option-${index}`}
onOptionSelect={e => onOptionSelect(e, index, false)}
>
{option.text}
</DualListSelectorListItem>
) : null
)}
</DualListSelectorList>
)}
</DualListSelectorPane>
<DualListSelectorControlsWrapper>
<DualListSelectorControl
Expand Down Expand Up @@ -199,21 +225,25 @@ export const DualListSelectorComposable: React.FunctionComponent = () => {
searchInput={buildSearchInput(false)}
actions={[buildSort(false)]}
isChosen
listMinHeight="300px"
>
<DualListSelectorList>
{chosenOptions.map((option, index) =>
option.isVisible ? (
<DualListSelectorListItem
key={index}
isSelected={option.selected}
id={`composable-chosen-option-${index}`}
onOptionSelect={e => onOptionSelect(e, index, true)}
>
{option.text}
</DualListSelectorListItem>
) : null
)}
</DualListSelectorList>
{chosenFilter !== '' && chosenOptions.filter(option => option.isVisible).length === 0 && buildEmptyState(false)}
{chosenOptions.filter(option => option.isVisible).length > 0 && (
<DualListSelectorList>
{chosenOptions.map((option, index) =>
option.isVisible ? (
<DualListSelectorListItem
key={index}
isSelected={option.selected}
id={`composable-chosen-option-${index}`}
onOptionSelect={e => onOptionSelect(e, index, true)}
>
{option.text}
</DualListSelectorListItem>
) : null
)}
</DualListSelectorList>
)}
</DualListSelectorPane>
</DualListSelector>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,20 @@ import {
DualListSelectorControl,
DualListSelectorTree,
DualListSelectorTreeItemData,
SearchInput
SearchInput,
Title,
Button,
EmptyState,
EmptyStateVariant,
EmptyStateIcon,
EmptyStateBody,
EmptyStatePrimary
} from '@patternfly/react-core';
import AngleDoubleLeftIcon from '@patternfly/react-icons/dist/esm/icons/angle-double-left-icon';
import AngleLeftIcon from '@patternfly/react-icons/dist/esm/icons/angle-left-icon';
import AngleDoubleRightIcon from '@patternfly/react-icons/dist/esm/icons/angle-double-right-icon';
import AngleRightIcon from '@patternfly/react-icons/dist/esm/icons/angle-right-icon';
import SearchIcon from '@patternfly/react-icons/dist/esm/icons/search-icon';

interface FoodNode {
id: string;
Expand Down Expand Up @@ -235,19 +243,37 @@ export const DualListSelectorComposableTree: React.FunctionComponent<ExampleProp
isChosen ? chosenLeafIds.includes(id) : !chosenLeafIds.includes(id)
).length;
const status = `${numSelected} of ${numOptions} options selected`;
const filterApplied = isChosen ? chosenFilter !== '' : availableFilter !== '';
return (
<DualListSelectorPane
title={isChosen ? 'Chosen' : 'Available'}
status={status}
searchInput={buildSearchInput(isChosen)}
isChosen={isChosen}
listMinHeight="300px"
>
<DualListSelectorList>
<DualListSelectorTree
data={options}
onOptionCheck={(e, isChecked, itemData) => onOptionCheck(e, isChecked, itemData, isChosen)}
/>
</DualListSelectorList>
{filterApplied && options.length === 0 && (
<EmptyState variant={EmptyStateVariant.small}>
<EmptyStateIcon icon={SearchIcon} />
<Title headingLevel="h4" size="md">
No results found
</Title>
<EmptyStateBody>No results match the filter criteria. Clear all filters and try again.</EmptyStateBody>
<EmptyStatePrimary>
<Button variant="link" onClick={() => (isChosen ? setChosenFilter('') : setAvailableFilter(''))}>
Clear all filters
</Button>
</EmptyStatePrimary>
</EmptyState>
)}
{options.length > 0 && (
<DualListSelectorList>
<DualListSelectorTree
data={options}
onOptionCheck={(e, isChecked, itemData) => onOptionCheck(e, isChecked, itemData, isChosen)}
/>
</DualListSelectorList>
)}
</DualListSelectorPane>
);
};
Expand Down