diff --git a/src/components/DropList/DropList.Combobox.jsx b/src/components/DropList/DropList.Combobox.jsx
index 2b6a1d475..81c92a535 100644
--- a/src/components/DropList/DropList.Combobox.jsx
+++ b/src/components/DropList/DropList.Combobox.jsx
@@ -84,6 +84,7 @@ function Combobox({
return stateReducerCommon({
changes,
closeOnSelection,
+ items,
selectedItems,
state,
type: `${VARIANTS.COMBOBOX}.${type}`,
@@ -113,7 +114,17 @@ function Combobox({
key: generateListItemKey(item, index),
withMultipleSelection,
renderCustomListItem,
- ...getItemProps({ item, index }),
+ isDisabled: item.isDisabled,
+ ...getItemProps({
+ item,
+ index,
+ onClick: event => {
+ if (item.isDisabled) {
+ event.nativeEvent.preventDownshiftDefault = true
+ return
+ }
+ },
+ }),
}
return
diff --git a/src/components/DropList/DropList.ListItem.jsx b/src/components/DropList/DropList.ListItem.jsx
index c8facd714..fefaba47a 100644
--- a/src/components/DropList/DropList.ListItem.jsx
+++ b/src/components/DropList/DropList.ListItem.jsx
@@ -22,6 +22,7 @@ const ListItem = forwardRef(
withMultipleSelection,
isSelected,
renderCustomListItem,
+ isDisabled,
...itemProps
},
ref
@@ -49,6 +50,7 @@ const ListItem = forwardRef(
return classNames(
'DropListItem',
isSelected && 'is-selected',
+ isDisabled && 'is-disabled',
highlightedIndex === index && 'is-highlighted',
withMultipleSelection && 'with-multiple-selection',
isString(extraClassNames) && extraClassNames
@@ -67,6 +69,7 @@ const ListItem = forwardRef(
isSelected,
isHighlighted: highlightedIndex === index,
withMultipleSelection,
+ isDisabled,
})}
)
diff --git a/src/components/DropList/DropList.Select.jsx b/src/components/DropList/DropList.Select.jsx
index c995f48c2..ea73e661c 100644
--- a/src/components/DropList/DropList.Select.jsx
+++ b/src/components/DropList/DropList.Select.jsx
@@ -79,6 +79,7 @@ function Select({
return stateReducerCommon({
changes,
closeOnSelection,
+ items,
selectedItems,
state,
type: `${VARIANTS.SELECT}.${type}`,
@@ -100,7 +101,17 @@ function Select({
key: generateListItemKey(item, index),
withMultipleSelection,
renderCustomListItem,
- ...getItemProps({ item, index }),
+ isDisabled: item.isDisabled,
+ ...getItemProps({
+ item,
+ index,
+ onClick: event => {
+ if (item.isDisabled) {
+ event.nativeEvent.preventDownshiftDefault = true
+ return
+ }
+ },
+ }),
}
return
diff --git a/src/components/DropList/DropList.css.js b/src/components/DropList/DropList.css.js
index 5e5f818f0..9c8759382 100644
--- a/src/components/DropList/DropList.css.js
+++ b/src/components/DropList/DropList.css.js
@@ -113,6 +113,13 @@ export const ListItemUI = styled('li')`
background-color: ${getColor('blue.100')};
}
}
+
+ &.is-disabled,
+ &.with-multiple-selection.is-disabled {
+ color: ${getColor('charcoal.200')};
+ background-color: transparent;
+ cursor: default;
+ }
`
export const EmptyListUI = styled('div')`
diff --git a/src/components/DropList/DropList.downshift.common.js b/src/components/DropList/DropList.downshift.common.js
index c4f147c16..7a6e4a698 100644
--- a/src/components/DropList/DropList.downshift.common.js
+++ b/src/components/DropList/DropList.downshift.common.js
@@ -1,6 +1,10 @@
import { useSelect, useCombobox } from 'downshift'
import { isObject } from '../../utilities/is'
-import { findItemInArray, getItemContentKeyName } from './DropList.utils'
+import {
+ findItemInArray,
+ getEnabledItemIndex,
+ getItemContentKeyName,
+} from './DropList.utils'
import { OPEN_ACTION_ORIGIN, VARIANTS } from './DropList.constants'
const { SELECT, COMBOBOX } = VARIANTS
@@ -8,6 +12,7 @@ const { SELECT, COMBOBOX } = VARIANTS
export function stateReducerCommon({
changes,
closeOnSelection,
+ items,
selectedItems,
state,
type,
@@ -67,6 +72,34 @@ export function stateReducerCommon({
return { ...changes, inputValue: '' }
}
+ case `${COMBOBOX}.${useCombobox.stateChangeTypes.InputKeyDownArrowUp}`:
+ case `${SELECT}.${useSelect.stateChangeTypes.MenuKeyDownArrowUp}`: {
+ const { highlightedIndex } = changes
+
+ return {
+ ...changes,
+ highlightedIndex: getEnabledItemIndex({
+ highlightedIndex,
+ items,
+ arrowKey: 'UP',
+ }),
+ }
+ }
+
+ case `${COMBOBOX}.${useCombobox.stateChangeTypes.InputKeyDownArrowDown}`:
+ case `${SELECT}.${useSelect.stateChangeTypes.MenuKeyDownArrowDown}`: {
+ const { highlightedIndex } = changes
+
+ return {
+ ...changes,
+ highlightedIndex: getEnabledItemIndex({
+ highlightedIndex,
+ items,
+ arrowKey: 'DOWN',
+ }),
+ }
+ }
+
default:
return changes
}
diff --git a/src/components/DropList/DropList.jsx b/src/components/DropList/DropList.jsx
index d4f4e2937..ee329cbeb 100644
--- a/src/components/DropList/DropList.jsx
+++ b/src/components/DropList/DropList.jsx
@@ -153,6 +153,9 @@ function DropListManager({
}
function handleSelectedItemChange({ selectedItem }) {
+ if (selectedItem.isDisabled) {
+ return
+ }
if (withMultipleSelection) {
if (selectedItem) {
const { remove } = selectedItem
@@ -251,6 +254,7 @@ const itemShape = PropTypes.shape({
label: requiredItemPropsCheck,
value: requiredItemPropsCheck,
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+ isDisabled: PropTypes.bool,
})
const dividerShape = PropTypes.shape({
type: PropTypes.oneOf(['divider', 'Divider']).isRequired,
diff --git a/src/components/DropList/DropList.stories.mdx b/src/components/DropList/DropList.stories.mdx
index 284b3ca7b..2888adef1 100644
--- a/src/components/DropList/DropList.stories.mdx
+++ b/src/components/DropList/DropList.stories.mdx
@@ -15,6 +15,7 @@ import {
plainItems,
regularItems,
simpleGroupedItems,
+ disabledItems,
} from '../../utilities/specs/dropdown.specs'
(
@@ -426,6 +429,77 @@ function renderCustomListItem({
+- Disabled List Items: Sometimes you might need to render the list items that would be disabled, for that you can pass `isDisbaled` flag with an item.
+
+
+
+
+
+
+
### Multiple selection
Depending on your use case, you might want to set `closeOnSelection = false` when multiple selection is enabled.
diff --git a/src/components/DropList/DropList.test.js b/src/components/DropList/DropList.test.js
index 386d41b2f..54580f30e 100644
--- a/src/components/DropList/DropList.test.js
+++ b/src/components/DropList/DropList.test.js
@@ -890,4 +890,157 @@ describe('Selection', () => {
).toBeTruthy()
})
})
+
+ describe('disabled items', () => {
+ const items = dedupedRegularItems.map((item, index) => ({
+ ...item,
+ isDisabled: index % 2 === 0,
+ }))
+
+ test('should set an item as disabled and do not allow to select it (select)', async () => {
+ const onSelect = jest.fn()
+ const { getByText } = render(
+
}
+ isMenuOpen
+ />
+ )
+
+ expect(getByText(regularItems[2].label).parentElement).toHaveClass(
+ 'is-disabled'
+ )
+ expect(getByText(regularItems[1].label).parentElement).not.toHaveClass(
+ 'is-disabled'
+ )
+
+ user.click(getByText(regularItems[2].label))
+
+ await waitFor(() => {
+ expect(onSelect).not.toHaveBeenCalled()
+ })
+ })
+
+ test('should set an item as disabled and do not allow to select it (combobox)', async () => {
+ const onSelect = jest.fn()
+ const { getByText } = render(
+
}
+ isMenuOpen
+ variant="combobox"
+ />
+ )
+
+ expect(getByText(regularItems[2].label).parentElement).toHaveClass(
+ 'is-disabled'
+ )
+
+ user.click(getByText(regularItems[2].label))
+
+ await waitFor(() => {
+ expect(onSelect).not.toHaveBeenCalled()
+ })
+ })
+
+ test('should set an item as disabled and do not allow to select it with custom list', async () => {
+ const onSelect = jest.fn()
+ const { getByText } = render(
+
}
+ isMenuOpen
+ renderCustomListItem={({ item, isDisabled }) => (
+
{item.label}
+ )}
+ />
+ )
+
+ const exampleItem = getByText(regularItems[2].label)
+
+ expect(exampleItem.parentElement).toHaveClass('is-disabled')
+
+ user.click(exampleItem)
+
+ await waitFor(() => {
+ expect(onSelect).not.toHaveBeenCalled()
+ expect(exampleItem).toHaveClass('is-disabled')
+ })
+ })
+
+ test('should skip disabled items when navigating down', async () => {
+ const { getByPlaceholderText, getByText } = render(
+
}
+ isMenuOpen
+ variant="combobox"
+ />
+ )
+
+ user.type(getByPlaceholderText('Search'), '{arrowdown}')
+
+ await waitFor(() => {
+ expect(getByText(regularItems[0].label).parentElement).not.toHaveClass(
+ 'is-highlighted'
+ )
+ expect(getByText(regularItems[1].label).parentElement).toHaveClass(
+ 'is-highlighted'
+ )
+ })
+
+ user.type(getByPlaceholderText('Search'), '{arrowdown}')
+
+ await waitFor(() => {
+ expect(getByText(regularItems[1].label).parentElement).not.toHaveClass(
+ 'is-highlighted'
+ )
+ expect(getByText(regularItems[2].label).parentElement).not.toHaveClass(
+ 'is-highlighted'
+ )
+ expect(getByText(regularItems[3].label).parentElement).toHaveClass(
+ 'is-highlighted'
+ )
+ })
+ })
+
+ test('should skip disabled items when navigating up', async () => {
+ const { getByPlaceholderText, getByText } = render(
+
}
+ isMenuOpen
+ variant="combobox"
+ />
+ )
+
+ user.type(getByPlaceholderText('Search'), '{arrowup}')
+
+ await waitFor(() => {
+ expect(getByText(regularItems[0].label).parentElement).not.toHaveClass(
+ 'is-highlighted'
+ )
+ expect(
+ getByText(regularItems[regularItems.length - 2].label).parentElement
+ ).toHaveClass('is-highlighted')
+ })
+
+ user.type(getByPlaceholderText('Search'), '{arrowup}')
+
+ await waitFor(() => {
+ expect(
+ getByText(regularItems[regularItems.length - 2].label).parentElement
+ ).not.toHaveClass('is-highlighted')
+ expect(
+ getByText(regularItems[regularItems.length - 3].label).parentElement
+ ).not.toHaveClass('is-highlighted')
+ expect(
+ getByText(regularItems[regularItems.length - 4].label).parentElement
+ ).toHaveClass('is-highlighted')
+ })
+ })
+ })
})
diff --git a/src/components/DropList/DropList.utils.js b/src/components/DropList/DropList.utils.js
index 79bab2f37..c518a6efc 100644
--- a/src/components/DropList/DropList.utils.js
+++ b/src/components/DropList/DropList.utils.js
@@ -188,3 +188,33 @@ export function requiredItemPropsCheck(props, propName, componentName) {
)
}
}
+
+export function getEnabledItemIndex({ highlightedIndex, items, arrowKey }) {
+ let enabledItemIndex = 0
+
+ if (arrowKey === 'UP') {
+ for (let index = items.length - 1; index >= 0; index--) {
+ if (
+ (highlightedIndex === 0 && !items[index].isDisabled) ||
+ (index <= highlightedIndex && !items[index].isDisabled)
+ ) {
+ enabledItemIndex = index
+ break
+ }
+ }
+ }
+
+ if (arrowKey === 'DOWN') {
+ for (let index = 0; index < items.length; index++) {
+ if (
+ (highlightedIndex === items.length - 1 && !items[index].isDisabled) ||
+ (index >= highlightedIndex && !items[index].isDisabled)
+ ) {
+ enabledItemIndex = index
+ break
+ }
+ }
+ }
+
+ return enabledItemIndex
+}
diff --git a/src/utilities/specs/dropdown.specs.js b/src/utilities/specs/dropdown.specs.js
index b9a9db241..13e4686e7 100644
--- a/src/utilities/specs/dropdown.specs.js
+++ b/src/utilities/specs/dropdown.specs.js
@@ -74,6 +74,14 @@ export const groupAndDividerItems = [
export const regularItems = ItemSpec.generate(15)
+export const disabledItems = ItemSpec.generate(10).map((item, index) => {
+ if (index % 2 === 0) {
+ item.isDisabled = true
+ }
+
+ return item
+})
+
export const plainItems = [
'hello',
'hola',