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

feat(Datagrid): add support for multi select filter type #4361

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 @@ -15,6 +15,7 @@ import {
CHECKBOX,
CLEAR_SINGLE_FILTER,
SAVED_FILTERS,
MULTISELECT,
} from './constants';

export const FilterContext = createContext();
Expand All @@ -39,7 +40,7 @@ const EventEmitter = {
const removeFilterItem = (state, index) => state.splice(index, 1);

const updateFilterState = (state, type, value) => {
if (type === CHECKBOX) {
if (type === CHECKBOX || type === MULTISELECT) {
return;
}
if (type === DATE) {
Expand All @@ -61,7 +62,7 @@ export const clearSingleFilter = ({ key, value }, setAllFilters, state) => {
const filterValues = f.value;
const filterType = f.type;
updateFilterState(tempState, filterType, value);
if (filterType === CHECKBOX) {
if (filterType === CHECKBOX || filterType === MULTISELECT) {
/**
When all checkboxes of a group are all unselected the value still exists in the filtersObjectArray
This checks if all the checkboxes are selected = false and removes it from the array
Expand Down Expand Up @@ -123,14 +124,14 @@ const prepareFiltersForTags = (filters, renderDateLabel) => {
formatDateRange(startDate, endDate),
...sharedFilterProps,
});
} else if (type === CHECKBOX) {
value.forEach((checkbox) => {
if (checkbox.selected) {
} else if (type === CHECKBOX || type === MULTISELECT) {
value.forEach((option) => {
if (option.selected) {
tags.push({
key: id,
value: checkbox.value,
value: option.value,
...sharedFilterProps,
onClose: () => handleSingleFilterRemoval(id, checkbox.value),
onClose: () => handleSingleFilterRemoval(id, option.value),
});
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export const NUMBER = 'number';
export const CHECKBOX = 'checkbox';
export const RADIO = 'radio';
export const DROPDOWN = 'dropdown';
export const MULTISELECT = 'multiSelect';

/** Constants for event emitters */
export const CLEAR_FILTERS = 'clearFilters';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
DATE,
DROPDOWN,
INSTANT,
MULTISELECT,
NUMBER,
PANEL,
RADIO,
Expand All @@ -22,6 +23,7 @@ import {
DatePickerInput,
Dropdown,
FormGroup,
MultiSelect,
Layer,
NumberInput,
RadioButton,
Expand Down Expand Up @@ -139,7 +141,7 @@ const useFilters = ({
const index = filterCopy.findIndex(({ id }) => id === column);

const clearCheckbox =
type === CHECKBOX &&
(type === CHECKBOX || type === MULTISELECT) &&
filterCopy[index].value.every(({ selected }) => selected === false);
const clearDate = type === DATE && value.length === 0;
const clearAny = (type === DROPDOWN || type === RADIO) && value === 'Any';
Expand Down Expand Up @@ -344,6 +346,78 @@ const useFilters = ({
/>
);
break;
case MULTISELECT: {
const isStringArray =
components.MultiSelect.items.length &&
typeof components.MultiSelect.items[0] === 'string';
const selectedFilters = filtersState[column]?.value.filter(
(i) => i.selected
);
const filteredItems = components.MultiSelect.items
.map((item) => {
if (
selectedFilters.filter((a) =>
isStringArray ? a.id === item : a.id === item.id
).length
) {
return item;
}
return null;
})
.filter(Boolean);
filter = (
<MultiSelect
{...components.MultiSelect}
selectedItems={filteredItems}
onChange={({ selectedItems }) => {
const allOptions = filtersState[column].value;
// Find selected items from list of options
const foundItems = selectedItems
.map((item) => {
if (
allOptions.filter((option) =>
isStringArray ? option.id === item : option.id === item.id
)
) {
return allOptions.filter((option) =>
isStringArray ? option.id === item : option.id === item.id
)[0];
}
return null;
})
.filter(Boolean);

// Change selected state for those items that have been selected
allOptions.map((a) => (a.selected = false));
foundItems.map((item) => {
const foundOriginalItem = allOptions.filter((a) =>
isStringArray ? a === item : a.id === item.id
);
if (foundOriginalItem && foundOriginalItem.length) {
foundOriginalItem[0].selected = true;
}
});
if (!selectedItems.length) {
allOptions.map((a) => (a.selected = false));
}
setFiltersState({
...filtersState,
[column]: {
value: allOptions,
type,
},
});
applyFilters({
column,
value: [...filtersState[column].value],
type,
});
components.MultiSelect?.onChange?.(selectedItems);
}}
/>
);
break;
}
}

if (isPanel) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright IBM Corp. 2022, 2023
* Copyright IBM Corp. 2022, 2024
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
Expand All @@ -10,6 +10,7 @@ import {
DATE,
DROPDOWN,
FLYOUT,
MULTISELECT,
NUMBER,
PANEL,
RADIO,
Expand Down Expand Up @@ -65,6 +66,15 @@ export const getInitialStateFromFilters = (
value: '',
type,
};
} else if (type === MULTISELECT) {
initialFilterState[column] = {
value: props.MultiSelect.items.map((item) => ({
id: typeof item === 'string' ? item : item.id,
value: typeof item === 'string' ? item : item.text,
selected: false,
})),
type,
};
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import { ARG_TYPES } from '../../utils/getArgTypes';
import { DatagridActions } from '../../utils/DatagridActions';
import { StatusIcon } from '../../../StatusIcon';
import { handleFilterTagLabelText } from '../../utils/handleFilterTagLabelText';
import { getDateFormat } from './Panel.stories';
import { getDateFormat, multiSelectProps } from './Panel.stories';

export default {
title: `${getStoryTitle(Datagrid.displayName)}/Extensions/Flyout`,
Expand Down Expand Up @@ -89,6 +89,7 @@ export const FilteringUsage = ({ defaultGridProps }) => {
{
Header: 'Status',
accessor: 'status',
filter: 'multiSelect',
},
// Shows the date filter example
{
Expand Down Expand Up @@ -252,15 +253,11 @@ const filters = [
},
},
{
type: 'dropdown',
type: 'multiSelect',
column: 'status',
props: {
Dropdown: {
id: 'marital-status-dropdown',
ariaLabel: 'Marital status dropdown',
items: ['relationship', 'complicated', 'single'],
label: 'Marital status',
titleText: 'Marital status',
MultiSelect: {
...multiSelectProps,
},
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,12 @@ export default {
},
},
},
excludeStories: ['FilteringUsage', 'filterProps', 'getDateFormat'],
excludeStories: [
'FilteringUsage',
'filterProps',
'getDateFormat',
'multiSelectProps',
],
};

// This is to show off the View all button in checkboxes
Expand Down Expand Up @@ -100,6 +105,7 @@ export const FilteringUsage = ({ defaultGridProps }) => {
{
Header: 'Status',
accessor: 'status',
filter: 'multiSelect',
},
// Shows the date filter example
{
Expand Down Expand Up @@ -186,6 +192,28 @@ export const getDateFormat = (lang, full) => {
.join('');
};

export const multiSelectProps = {
// items: ['relationship', 'complicated', 'single'],
items: [
{ text: 'relationship', id: 'relationship' },
{ text: 'complicated', id: 'complicated' },
{ text: 'single', id: 'single' },
],
id: 'carbon-multiselect-example',
label: 'Status selection',
titleText: 'Multiselect title',
itemToString: (item) => (item ? item.text : ''),
size: 'md',
type: 'default',
disabled: false,
hideLabel: false,
invalid: false,
warn: false,
open: false,
clearSelectionDescription: 'Total items selected: ',
clearSelectionText: 'To clear selection, press Delete or Backspace,',
};

export const filterProps = {
variation: 'panel',
updateMethod: 'batch',
Expand Down Expand Up @@ -227,15 +255,11 @@ export const filterProps = {
{
filterLabel: 'Status',
filter: {
type: 'dropdown',
type: 'multiSelect',
column: 'status',
props: {
Dropdown: {
id: 'marital-status-dropdown',
['aria-label']: 'Marital status dropdown',
items: ['relationship', 'complicated', 'single'],
label: 'Marital status',
titleText: 'Marital status',
MultiSelect: {
...multiSelectProps,
},
},
},
Expand Down
47 changes: 28 additions & 19 deletions packages/ibm-products/src/components/Datagrid/useFiltering.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,36 @@

import { useMemo } from 'react';
import { FilterFlyout } from './Datagrid/addons/Filtering';
import { BATCH } from './Datagrid/addons/Filtering/constants';
import {
BATCH,
CHECKBOX,
DATE,
MULTISELECT,
NUMBER,
} from './Datagrid/addons/Filtering/constants';

const handleMultiFilter = (rows, id, value) => {
// gets all the items that are selected and returns their value
const selectedItems = value
.filter((item) => item.selected)
.map((item) => item.value);

// if the user removed all checkboxes then display all rows
if (selectedItems.length === 0) {
return rows;
}

return rows.filter((row) => {
const rowValue = row.values[id];
return selectedItems.includes(rowValue);
});
};

const useFiltering = (hooks) => {
/* istanbul ignore next */
const filterTypes = useMemo(
() => ({
date: (rows, id, [startDate, endDate]) => {
[DATE]: (rows, id, [startDate, endDate]) => {
return rows.filter((row) => {
const rowValue = row.values[id];
const startDateObj =
Expand All @@ -34,7 +57,7 @@ const useFiltering = (hooks) => {
}
});
},
number: (rows, id, value) => {
[NUMBER]: (rows, id, value) => {
if (value === '') {
return rows;
}
Expand All @@ -45,22 +68,8 @@ const useFiltering = (hooks) => {
return rowValue === parsedValue;
});
},
checkbox: (rows, id, value) => {
// gets all the items that are selected and returns their value
const selectedItems = value
.filter((item) => item.selected)
.map((item) => item.value);

// if the user removed all checkboxes then display all rows
if (selectedItems.length === 0) {
return rows;
}

return rows.filter((row) => {
const rowValue = row.values[id];
return selectedItems.includes(rowValue);
});
},
[CHECKBOX]: (rows, id, value) => handleMultiFilter(rows, id, value),
[MULTISELECT]: (rows, id, value) => handleMultiFilter(rows, id, value),
}),
[]
);
Expand Down
Loading