Skip to content

Commit

Permalink
Merge branch 'main' into radio-tile-phase-3-updates
Browse files Browse the repository at this point in the history
  • Loading branch information
tw15egan authored Apr 19, 2024
2 parents a654509 + a353cf3 commit cc7d15d
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 46 deletions.
9 changes: 9 additions & 0 deletions .all-contributorsrc
Original file line number Diff line number Diff line change
Expand Up @@ -1472,6 +1472,15 @@
]
},
{
"login": "jesnajoseijk",
"name": "jesnajoseijk",
"avatar_url": "https://avatars.githubusercontent.com/u/38346258?v=4",
"profile": "https://github.com/jesnajoseijk",
"contributions": [
"code"
]
},
{
"login": "Jawahars",
"name": "Jawahar S",
"avatar_url": "https://avatars.githubusercontent.com/u/4353146?v=4",
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,7 @@ check out our [Contributing Guide](/.github/CONTRIBUTING.md) and our
<td align="center"><a href="https://github.com/mranjana"><img src="https://avatars.githubusercontent.com/u/91003483?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Anjana M R</b></sub></a><br /><a href="https://github.com/carbon-design-system/carbon/commits?author=mranjana" title="Code">💻</a></td>
<td align="center"><a href="https://cuppajoey.com/"><img src="https://avatars.githubusercontent.com/u/14837881?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Joseph Schultz</b></sub></a><br /><a href="https://github.com/carbon-design-system/carbon/commits?author=cuppajoey" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/anjaly0606"><img src="https://avatars.githubusercontent.com/u/99959496?v=4?s=100" width="100px;" alt=""/><br /><sub><b>anjaly0606</b></sub></a><br /><a href="https://github.com/carbon-design-system/carbon/commits?author=anjaly0606" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/jesnajoseijk"><img src="https://avatars.githubusercontent.com/u/38346258?v=4?s=100" width="100px;" alt=""/><br /><sub><b>jesnajoseijk</b></sub></a><br /><a href="https://github.com/carbon-design-system/carbon/commits?author=jesnajoseijk" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/Jawahars"><img src="https://avatars.githubusercontent.com/u/4353146?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jawahar S</b></sub></a><br /><a href="https://github.com/carbon-design-system/carbon/commits?author=Jawahars" title="Code">💻</a></td>
</tr>
<tr>
Expand Down
113 changes: 67 additions & 46 deletions packages/react/src/components/MultiSelect/MultiSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,13 @@ import {
} from 'downshift';
import isEqual from 'lodash.isequal';
import PropTypes, { ReactNodeLike } from 'prop-types';
import React, { ForwardedRef, useContext, useRef, useState } from 'react';
import React, {
ForwardedRef,
useContext,
useRef,
useState,
useMemo,
} from 'react';
import ListBox, {
ListBoxSize,
ListBoxType,
Expand Down Expand Up @@ -373,14 +379,28 @@ const MultiSelect = React.forwardRef(
selectedItems: selected,
});

// Filter out items with an object having undefined values
const filteredItems = useMemo(() => {
return items.filter((item) => {
if (typeof item === 'object' && item !== null) {
for (const key in item) {
if (Object.hasOwn(item, key) && item[key] === undefined) {
return false; // Return false if any property has an undefined value
}
}
}
return true; // Return true if item is not an object with undefined values
});
}, [items]);

const selectProps: UseSelectProps<ItemType> = {
...downshiftProps,
stateReducer,
isOpen,
itemToString: (items) => {
itemToString: (filteredItems) => {
return (
(Array.isArray(items) &&
items
(Array.isArray(filteredItems) &&
filteredItems
.map(function (item) {
return itemToString(item);
})
Expand All @@ -389,7 +409,7 @@ const MultiSelect = React.forwardRef(
);
},
selectedItem: controlledSelectedItems,
items,
items: filteredItems,
isItemDisabled(item, _index) {
return (item as any).disabled;
},
Expand Down Expand Up @@ -627,7 +647,7 @@ const MultiSelect = React.forwardRef(

const itemsSelectedText =
selectedItems.length > 0 &&
selectedItems.map((item) => (item as selectedItemType).text);
selectedItems.map((item) => (item as selectedItemType)?.text);

return (
<div className={wrapperClasses}>
Expand Down Expand Up @@ -701,46 +721,47 @@ const MultiSelect = React.forwardRef(
<ListBox.Menu {...getMenuProps()}>
{isOpen &&
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
sortItems!(items, sortOptions as SortItemsOptions<ItemType>).map(
(item, index) => {
const isChecked =
selectedItems.filter((selected) => isEqual(selected, item))
.length > 0;

const itemProps = getItemProps({
item,
// we don't want Downshift to set aria-selected for us
// we also don't want to set 'false' for reader verbosity's sake
['aria-selected']: isChecked,
});
const itemText = itemToString(item);

return (
<ListBox.MenuItem
key={itemProps.id}
isActive={isChecked}
aria-label={itemText}
isHighlighted={highlightedIndex === index}
title={itemText}
disabled={itemProps['aria-disabled']}
{...itemProps}>
<div className={`${prefix}--checkbox-wrapper`}>
<span
title={useTitleInItem ? itemText : undefined}
className={`${prefix}--checkbox-label`}
data-contained-checkbox-state={isChecked}
id={`${itemProps.id}__checkbox`}>
{itemToElement ? (
<ItemToElement key={itemProps.id} {...item} />
) : (
itemText
)}
</span>
</div>
</ListBox.MenuItem>
);
}
)}
sortItems!(
filteredItems,
sortOptions as SortItemsOptions<ItemType>
).map((item, index) => {
const isChecked =
selectedItems.filter((selected) => isEqual(selected, item))
.length > 0;

const itemProps = getItemProps({
item,
// we don't want Downshift to set aria-selected for us
// we also don't want to set 'false' for reader verbosity's sake
['aria-selected']: isChecked,
});
const itemText = itemToString(item);

return (
<ListBox.MenuItem
key={itemProps.id}
isActive={isChecked}
aria-label={itemText}
isHighlighted={highlightedIndex === index}
title={itemText}
disabled={itemProps['aria-disabled']}
{...itemProps}>
<div className={`${prefix}--checkbox-wrapper`}>
<span
title={useTitleInItem ? itemText : undefined}
className={`${prefix}--checkbox-label`}
data-contained-checkbox-state={isChecked}
id={`${itemProps.id}__checkbox`}>
{itemToElement ? (
<ItemToElement key={itemProps.id} {...item} />
) : (
itemText
)}
</span>
</div>
</ListBox.MenuItem>
);
})}
</ListBox.Menu>
{itemsCleared && (
<span aria-live="assertive" aria-label={clearAnnouncement} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,27 @@ describe('MultiSelect', () => {
await expect(container).toHaveNoACViolations('MultiSelect');
});
});
it('does not render items with undefined values', async () => {
const items = [{ text: 'joey' }, { text: 'johnny' }, { text: undefined }];
const label = 'test-label';
render(
<MultiSelect
id="custom-id"
label={label}
items={items}
itemToString={(item) => (item ? item.text : '')}
/>
);

const labelNode = screen.getByRole('combobox');
await userEvent.click(labelNode);

expect(screen.getByRole('option', { name: 'joey' })).toBeInTheDocument();
expect(screen.getByRole('option', { name: 'johnny' })).toBeInTheDocument();
expect(
screen.queryByRole('option', { name: 'undefined' })
).not.toBeInTheDocument();
});

it('should initially render with a given label', () => {
const items = generateItems(4, generateGenericItem);
Expand Down

0 comments on commit cc7d15d

Please sign in to comment.