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

Autocomplete filter #787

Merged
merged 1 commit into from
Nov 28, 2023
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
104 changes: 104 additions & 0 deletions packages/common/src/components/Filter/AutocompleteFilter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import React, { useState } from 'react';

import {
Select,
SelectOption,
SelectOptionObject,
SelectVariant,
ToolbarChip,
ToolbarFilter,
} from '@patternfly/react-core';

import { FilterTypeProps } from './types';

/**
* Typeahead filter inspired by Console's AutocompleteInput.

Check warning on line 15 in packages/common/src/components/Filter/AutocompleteFilter.tsx

View workflow job for this annotation

GitHub Actions / Run linter and tests

Unknown word: "Typeahead"
*
* **Key features**:<br>
* 1) match the search pattern in the entire string (not only prefix)<br>
* 2) reset the filter after selection - the selected item is visible in the chip list

*
* **Enum contract**:<br>
* 1) enum values passed as supportedValues param have id prop the same as the label prop.<br>
* 2) values in supportedValues are unique (no de-duplication similar to EnumFilter).
*
* [<img src="static/media/src/components-stories/assets/github-logo.svg"><i class="fi fi-brands-github">
* <font color="green">View component source on GitHub</font>](https://github.com/kubev2v/forklift-console-plugin/blob/main/packages/common/src/components/Filter/AutocompleteFilter.tsx)
*/
export const AutocompleteFilter = ({
selectedFilters = [],
onFilterUpdate,
supportedValues = [],
title,
placeholderLabel,
filterId,
showFilter = true,
}: FilterTypeProps) => {
const [isExpanded, setExpanded] = useState(false);
const validSupported = supportedValues.filter(({ label, id }) => id === label);
const validSelected = selectedFilters.filter(
(filterId) => validSupported.filter(({ id }) => id === filterId).length > 0,
);

const deleteFilter = (label: string | ToolbarChip | SelectOptionObject): void =>
onFilterUpdate(validSelected.filter((filterLabel) => filterLabel !== label));

const hasFilter = (label: string | SelectOptionObject): boolean =>
!!validSelected.find((filterLabel) => filterLabel === label);

const addFilter = (label: string | SelectOptionObject): void => {
if (typeof label === 'string') {
onFilterUpdate([...validSelected, label]);
}
};

const options = validSupported.map(({ label }) => <SelectOption key={label} value={label} />);

const onFilter = (_, textInput) => {
if (textInput === '') {
return options;
}

return options.filter((child) =>
child.props.value.toString().toLowerCase().includes(textInput.toLowerCase()),
);
};

return (
<ToolbarFilter
key={filterId}
chips={validSelected}
deleteChip={(category, option) => deleteFilter(option)}
deleteChipGroup={() => onFilterUpdate([])}
categoryName={title}
showToolbarItem={showFilter}
>
<Select
variant={SelectVariant.typeahead}
aria-label={placeholderLabel}
onSelect={(event, option, isPlaceholder) => {
if (isPlaceholder) {
return;
}
// behave as Console's AutocompleteInput used i.e. to filter pods by label
if (!hasFilter(option)) {
addFilter(option);
}
setExpanded(false);
}}
// intentionally keep the selections list empty
// the select should pretend to be stateless
// the selection is stored outside in a new chip
selections={[]}
placeholderText={placeholderLabel}
isOpen={isExpanded}
onToggle={setExpanded}
onFilter={onFilter}
shouldResetOnSelect={true}
>
{options}
</Select>
</ToolbarFilter>
);
};
1 change: 1 addition & 0 deletions packages/common/src/components/Filter/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// @index(['./*', /__/g], f => `export * from '${f.path}';`)
export * from './AutocompleteFilter';
export * from './DateFilter';
export * from './DateRangeFilter';
export * from './EnumFilter';
Expand Down
8 changes: 8 additions & 0 deletions packages/common/src/components/FilterGroup/matchers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import jsonpath from 'jsonpath';

import { areSameDayInUTCZero, isInClosedRange, ResourceField } from '../../utils';
import {
AutocompleteFilter,
DateFilter,
DateRangeFilter,
EnumFilter,
Expand Down Expand Up @@ -109,6 +110,11 @@ const searchableGroupedEnumMatcher = {
matchValue: enumMatcher.matchValue,
};

const autocompleteFilterMatcher = {
filterType: 'autocomplete',
matchValue: enumMatcher.matchValue,
};

const dateMatcher = {
filterType: 'date',
matchValue: (value: string) => (filter: string) => areSameDayInUTCZero(value, filter),
Expand All @@ -125,6 +131,7 @@ const sliderMatcher = {
};

export const defaultValueMatchers: ValueMatcher[] = [
autocompleteFilterMatcher,
freetextMatcher,
enumMatcher,
groupedEnumMatcher,
Expand All @@ -142,6 +149,7 @@ export const defaultSupportedFilters: Record<string, FilterRenderer> = {
groupedEnum: GroupedEnumFilter,
slider: SwitchFilter,
searchableGroupedEnum: SearchableGroupedEnumFilter,
autocomplete: AutocompleteFilter,
};

/**
Expand Down