Skip to content

Commit aa1c85a

Browse files
authored
refactor: extract MultiSelect select-all item helper for reuse (#22206)
1 parent 1186a48 commit aa1c85a

4 files changed

Lines changed: 32 additions & 30 deletions

File tree

packages/react/src/components/MultiSelect/FilterableMultiSelect.tsx

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import {
3838
type MultiSelectSortingProps,
3939
sortingPropTypes,
4040
} from './MultiSelectPropTypes';
41+
import { isSelectAllItem } from './tools/isSelectAllItem';
4142
import ListBox, {
4243
ListBoxSizePropType,
4344
ListBoxTypePropType,
@@ -52,7 +53,7 @@ import { match, keys } from '../../internal/keyboard';
5253
import { mergeRefs } from '../../tools/mergeRefs';
5354
import { deprecate } from '../../prop-types/deprecate';
5455
import { useId } from '../../internal/useId';
55-
import { defaultSortItems, defaultCompareItems } from './tools/sorting';
56+
import { defaultCompareItems, defaultSortItems } from './tools/sorting';
5657
import { usePrefix } from '../../internal/usePrefix';
5758
import { FormContext } from '../FluidForm';
5859
import { useSelection } from '../../internal/Selection';
@@ -385,12 +386,10 @@ export const FilterableMultiSelect = forwardRef(function FilterableMultiSelect<
385386
);
386387

387388
const nonSelectAllItems = useMemo(
388-
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- https://github.com/carbon-design-system/carbon/issues/20452
389-
() => filteredItems.filter((item) => !(item as any).isSelectAll),
389+
() => filteredItems.filter((item) => !isSelectAllItem(item)),
390390
[filteredItems]
391391
);
392-
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- https://github.com/carbon-design-system/carbon/issues/20452
393-
const selectAll = filteredItems.some((item) => (item as any).isSelectAll);
392+
const selectAll = filteredItems.some(isSelectAllItem);
394393

395394
const {
396395
selectedItems: controlledSelectedItems,
@@ -498,8 +497,7 @@ export const FilterableMultiSelect = forwardRef(function FilterableMultiSelect<
498497

499498
// memoize sorted items to reduce unnecessary expensive sort on rerender
500499
const sortedItems = useMemo(() => {
501-
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- https://github.com/carbon-design-system/carbon/issues/20452
502-
const selectAllItem = items.find((item) => (item as any).isSelectAll);
500+
const selectAllItem = items.find(isSelectAllItem);
503501

504502
const selectableRealItems = nonSelectAllItems.filter(
505503
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- https://github.com/carbon-design-system/carbon/issues/20452
@@ -697,7 +695,7 @@ export const FilterableMultiSelect = forwardRef(function FilterableMultiSelect<
697695
return changes;
698696
}
699697
if (changes.selectedItem && changes.selectedItem.disabled !== true) {
700-
if (changes.selectedItem.isSelectAll) {
698+
if (isSelectAllItem(changes.selectedItem)) {
701699
handleSelectAllClick();
702700
} else {
703701
onItemChange(changes.selectedItem);
@@ -707,7 +705,7 @@ export const FilterableMultiSelect = forwardRef(function FilterableMultiSelect<
707705

708706
return { ...changes, highlightedIndex: state.highlightedIndex };
709707
case ItemClick:
710-
if (changes.selectedItem.isSelectAll) {
708+
if (isSelectAllItem(changes.selectedItem)) {
711709
handleSelectAllClick();
712710
} else {
713711
onItemChange(changes.selectedItem);
@@ -842,8 +840,7 @@ export const FilterableMultiSelect = forwardRef(function FilterableMultiSelect<
842840

843841
// exclude the select-all item from the count
844842
const selectedItemsLength = controlledSelectedItems.filter(
845-
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- https://github.com/carbon-design-system/carbon/issues/20452
846-
(item: any) => !(item as any).isSelectAll
843+
(item) => !isSelectAllItem(item)
847844
).length;
848845

849846
const className = cx(
@@ -1083,8 +1080,7 @@ export const FilterableMultiSelect = forwardRef(function FilterableMultiSelect<
10831080
? sortedItems.map((item, index) => {
10841081
let isChecked: boolean;
10851082
let isIndeterminate = false;
1086-
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- https://github.com/carbon-design-system/carbon/issues/20452
1087-
if ((item as any).isSelectAll) {
1083+
if (isSelectAllItem(item)) {
10881084
isChecked = selectAllStatus.checked;
10891085
isIndeterminate = selectAllStatus.indeterminate;
10901086
} else {
@@ -1114,7 +1110,7 @@ export const FilterableMultiSelect = forwardRef(function FilterableMultiSelect<
11141110
key={itemProps.id}
11151111
aria-label={itemText}
11161112
aria-checked={isIndeterminate ? 'mixed' : isChecked}
1117-
isActive={isChecked && !item['isSelectAll']}
1113+
isActive={isChecked && !isSelectAllItem(item)}
11181114
isHighlighted={highlightedIndex === index}
11191115
title={itemText}
11201116
disabled={disabled}

packages/react/src/components/MultiSelect/MultiSelect.tsx

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ import {
3939
MultiSelectSortingProps,
4040
sortingPropTypes,
4141
} from './MultiSelectPropTypes';
42-
import { defaultSortItems, defaultCompareItems } from './tools/sorting';
42+
import { defaultCompareItems, defaultSortItems } from './tools/sorting';
43+
import { isSelectAllItem } from './tools/isSelectAllItem';
4344
import { useSelection } from '../../internal/Selection';
4445
import { useId } from '../../internal/useId';
4546
import { mergeRefs } from '../../tools/mergeRefs';
@@ -344,8 +345,7 @@ export const MultiSelect = React.forwardRef(
344345
});
345346
}, [items]);
346347

347-
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- https://github.com/carbon-design-system/carbon/issues/20452
348-
const selectAll = filteredItems.some((item) => (item as any).isSelectAll);
348+
const selectAll = filteredItems.some(isSelectAllItem);
349349

350350
const prefix = usePrefix();
351351
const { isFluid } = useContext(FormContext);
@@ -702,8 +702,7 @@ export const MultiSelect = React.forwardRef(
702702
selectedItems.map((item) => (item as selectedItemType)?.text);
703703

704704
const selectedItemsLength = selectAll
705-
? // eslint-disable-next-line @typescript-eslint/no-explicit-any -- https://github.com/carbon-design-system/carbon/issues/20452
706-
selectedItems.filter((item: any) => !item.isSelectAll).length
705+
? selectedItems.filter((item) => !isSelectAllItem(item)).length
707706
: selectedItems.length;
708707

709708
// Memoize the value of getMenuProps to avoid an infinite loop
@@ -733,15 +732,15 @@ export const MultiSelect = React.forwardRef(
733732
totalSelectableCount: number;
734733
} => {
735734
const hasIndividualSelections = selectedItems.some(
736-
(selected) => !selected.isSelectAll
735+
(selected) => !isSelectAllItem(selected)
737736
);
738737

739738
const nonSelectAllSelectedCount = selectedItems.filter(
740-
(selected) => !selected.isSelectAll
739+
(selected) => !isSelectAllItem(selected)
741740
).length;
742741

743742
const totalSelectableCount = filteredItems.filter(
744-
(item) => !item.isSelectAll && !item.disabled
743+
(item) => !isSelectAllItem(item) && !item.disabled
745744
).length;
746745

747746
return {
@@ -837,15 +836,13 @@ export const MultiSelect = React.forwardRef(
837836
totalSelectableCount,
838837
} = getSelectionStats(selectedItems, filteredItems);
839838

840-
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- https://github.com/carbon-design-system/carbon/issues/20452
841-
const isChecked = (item as any).isSelectAll
839+
const isChecked = isSelectAllItem(item)
842840
? nonSelectAllSelectedCount === totalSelectableCount &&
843841
totalSelectableCount > 0
844842
: selectedItems.some((selected) => isEqual(selected, item));
845843

846844
const isIndeterminate =
847-
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- https://github.com/carbon-design-system/carbon/issues/20452
848-
(item as any).isSelectAll &&
845+
isSelectAllItem(item) &&
849846
hasIndividualSelections &&
850847
nonSelectAllSelectedCount < totalSelectableCount;
851848

@@ -860,7 +857,7 @@ export const MultiSelect = React.forwardRef(
860857
return (
861858
<ListBox.MenuItem
862859
key={itemProps.id}
863-
isActive={isChecked && !item['isSelectAll']}
860+
isActive={isChecked && !isSelectAllItem(item)}
864861
aria-label={itemText}
865862
aria-checked={isIndeterminate ? 'mixed' : isChecked}
866863
isHighlighted={highlightedIndex === index}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/**
2+
* Copyright IBM Corp. 2026
3+
*
4+
* This source code is licensed under the Apache-2.0 license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
export const isSelectAllItem = (
9+
item: unknown
10+
): item is { isSelectAll?: boolean } =>
11+
typeof item === 'object' && item !== null && 'isSelectAll' in item;

packages/react/src/components/MultiSelect/tools/sorting.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@
66
*/
77

88
import type { CompareItems, SortItemsOptions } from '../MultiSelectPropTypes';
9-
10-
const isSelectAllItem = (item: unknown): item is { isSelectAll?: boolean } =>
11-
typeof item === 'object' && item !== null && 'isSelectAll' in item;
9+
import { isSelectAllItem } from './isSelectAllItem';
1210

1311
/**
1412
* Use `localeCompare` with the `numeric` option enabled to sort two

0 commit comments

Comments
 (0)