diff --git a/packages/ibm-products/src/components/Datagrid/Datagrid/addons/Filtering/FilterProvider.js b/packages/ibm-products/src/components/Datagrid/Datagrid/addons/Filtering/FilterProvider.js index e62511f65d..26399ee358 100644 --- a/packages/ibm-products/src/components/Datagrid/Datagrid/addons/Filtering/FilterProvider.js +++ b/packages/ibm-products/src/components/Datagrid/Datagrid/addons/Filtering/FilterProvider.js @@ -15,6 +15,7 @@ import { CHECKBOX, CLEAR_SINGLE_FILTER, SAVED_FILTERS, + MULTISELECT, } from './constants'; export const FilterContext = createContext(); @@ -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) { @@ -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 @@ -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), }); } }); diff --git a/packages/ibm-products/src/components/Datagrid/Datagrid/addons/Filtering/constants.js b/packages/ibm-products/src/components/Datagrid/Datagrid/addons/Filtering/constants.js index 192277536a..e0a6103b45 100644 --- a/packages/ibm-products/src/components/Datagrid/Datagrid/addons/Filtering/constants.js +++ b/packages/ibm-products/src/components/Datagrid/Datagrid/addons/Filtering/constants.js @@ -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'; diff --git a/packages/ibm-products/src/components/Datagrid/Datagrid/addons/Filtering/hooks/useFilters.js b/packages/ibm-products/src/components/Datagrid/Datagrid/addons/Filtering/hooks/useFilters.js index a9ace46bd4..5898ab4f28 100644 --- a/packages/ibm-products/src/components/Datagrid/Datagrid/addons/Filtering/hooks/useFilters.js +++ b/packages/ibm-products/src/components/Datagrid/Datagrid/addons/Filtering/hooks/useFilters.js @@ -11,6 +11,7 @@ import { DATE, DROPDOWN, INSTANT, + MULTISELECT, NUMBER, PANEL, RADIO, @@ -22,6 +23,7 @@ import { DatePickerInput, Dropdown, FormGroup, + MultiSelect, Layer, NumberInput, RadioButton, @@ -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'; @@ -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 = ( + { + 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) { diff --git a/packages/ibm-products/src/components/Datagrid/Datagrid/addons/Filtering/utils.js b/packages/ibm-products/src/components/Datagrid/Datagrid/addons/Filtering/utils.js index 45e115f781..e2a7496fda 100644 --- a/packages/ibm-products/src/components/Datagrid/Datagrid/addons/Filtering/utils.js +++ b/packages/ibm-products/src/components/Datagrid/Datagrid/addons/Filtering/utils.js @@ -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. @@ -10,6 +10,7 @@ import { DATE, DROPDOWN, FLYOUT, + MULTISELECT, NUMBER, PANEL, RADIO, @@ -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, + }; } }; diff --git a/packages/ibm-products/src/components/Datagrid/Extensions/Filtering/Flyout.stories.js b/packages/ibm-products/src/components/Datagrid/Extensions/Filtering/Flyout.stories.js index 069176967c..f7b15af03a 100644 --- a/packages/ibm-products/src/components/Datagrid/Extensions/Filtering/Flyout.stories.js +++ b/packages/ibm-products/src/components/Datagrid/Extensions/Filtering/Flyout.stories.js @@ -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`, @@ -89,6 +89,7 @@ export const FilteringUsage = ({ defaultGridProps }) => { { Header: 'Status', accessor: 'status', + filter: 'multiSelect', }, // Shows the date filter example { @@ -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, }, }, }, diff --git a/packages/ibm-products/src/components/Datagrid/Extensions/Filtering/Panel.stories.js b/packages/ibm-products/src/components/Datagrid/Extensions/Filtering/Panel.stories.js index 117029ec55..5eff43cba1 100644 --- a/packages/ibm-products/src/components/Datagrid/Extensions/Filtering/Panel.stories.js +++ b/packages/ibm-products/src/components/Datagrid/Extensions/Filtering/Panel.stories.js @@ -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 @@ -100,6 +105,7 @@ export const FilteringUsage = ({ defaultGridProps }) => { { Header: 'Status', accessor: 'status', + filter: 'multiSelect', }, // Shows the date filter example { @@ -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', @@ -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, }, }, }, diff --git a/packages/ibm-products/src/components/Datagrid/useFiltering.js b/packages/ibm-products/src/components/Datagrid/useFiltering.js index c6e834e80a..711e5450ef 100644 --- a/packages/ibm-products/src/components/Datagrid/useFiltering.js +++ b/packages/ibm-products/src/components/Datagrid/useFiltering.js @@ -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 = @@ -34,7 +57,7 @@ const useFiltering = (hooks) => { } }); }, - number: (rows, id, value) => { + [NUMBER]: (rows, id, value) => { if (value === '') { return rows; } @@ -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), }), [] );