Skip to content

Commit

Permalink
feat(Datagrid): add support for multi select filter type (#4361)
Browse files Browse the repository at this point in the history
* chore: custom filter exploration continued

* fix(DataGrid): add support for selectrow and nestedrow

* chore: custom filter exploration

* feat(Datagrid): add multi select option

* chore: remove onClearFilters from prop types

* chore: revert copyright year

* chore: add multi select example to flyout

* chore: revert nested/selection changes from branch

* chore: update gallery config

---------

Co-authored-by: Ratheesh Rajan <[email protected]>
  • Loading branch information
matthewgallo and Ratheeshrajan authored Feb 20, 2024
1 parent 984da57 commit 1a1c488
Show file tree
Hide file tree
Showing 7 changed files with 160 additions and 44 deletions.
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

0 comments on commit 1a1c488

Please sign in to comment.