diff --git a/docs/app/Examples/modules/Dropdown/States/DisabledItem.js b/docs/app/Examples/modules/Dropdown/States/DisabledItem.js
new file mode 100644
index 0000000000..80b9a3ea7f
--- /dev/null
+++ b/docs/app/Examples/modules/Dropdown/States/DisabledItem.js
@@ -0,0 +1,15 @@
+import _ from 'lodash'
+import faker from 'faker'
+import React, { Component } from 'react'
+import { Dropdown } from 'stardust'
+
+const options = _.times(10, (i) => {
+ const name = faker.name.findName()
+ return { text: name, value: _.snakeCase(name), disabled: i % 3 == 0 }
+})
+
+const DropdownItemDisabledExample = () => (
+
+)
+
+export default DropdownItemDisabledExample
diff --git a/docs/app/Examples/modules/Dropdown/index.js b/docs/app/Examples/modules/Dropdown/index.js
index 2a8a5c6200..cf5782d5c7 100644
--- a/docs/app/Examples/modules/Dropdown/index.js
+++ b/docs/app/Examples/modules/Dropdown/index.js
@@ -43,9 +43,14 @@ const DropdownExamples = () => (
+
)
diff --git a/src/modules/Dropdown/Dropdown.js b/src/modules/Dropdown/Dropdown.js
index e48abbcc01..7013f7f7df 100644
--- a/src/modules/Dropdown/Dropdown.js
+++ b/src/modules/Dropdown/Dropdown.js
@@ -386,9 +386,10 @@ export default class Dropdown extends Component {
selectHighlightedItem = (e) => {
const { multiple, onAddItem, options } = this.props
const value = _.get(this.getSelectedItem(), 'value')
+ const disabled = _.get(this.getSelectedItem(), 'disabled')
// prevent selecting null if there was no selected item value
- if (!value) return
+ if (!value || disabled) return
// notify the onAddItem prop if this is a new value
if (onAddItem && !_.some(options, { text: value })) onAddItem(value)
@@ -631,6 +632,9 @@ export default class Dropdown extends Component {
const options = this.getMenuOptions()
const lastIndex = options.length - 1
+ // Prevent infinite loop
+ if (_.every(options, 'disabled')) return
+
// next is after last, wrap to beginning
// next is before first, wrap to end
let nextIndex = selectedIndex + offset
@@ -638,6 +642,9 @@ export default class Dropdown extends Component {
else if (nextIndex < 0) nextIndex = lastIndex
this.setState({ selectedIndex: nextIndex })
+
+ if (options[nextIndex].disabled) return this.moveSelectionBy(offset)
+
this.scrollSelectedItemIntoView()
}
diff --git a/src/modules/Dropdown/DropdownItem.js b/src/modules/Dropdown/DropdownItem.js
index 329b4d7ebd..d3331aca0e 100644
--- a/src/modules/Dropdown/DropdownItem.js
+++ b/src/modules/Dropdown/DropdownItem.js
@@ -16,6 +16,7 @@ function DropdownItem(props) {
active,
children,
className,
+ disabled,
description,
icon,
onClick,
@@ -25,11 +26,18 @@ function DropdownItem(props) {
} = props
const handleClick = (e) => {
+ if (props.disabled) {
+ e.stopPropagation()
+ e.nativeEvent.stopImmediatePropagation()
+ return
+ }
+
if (onClick) onClick(e, value)
}
const classes = cx(
useKeyOnly(active, 'active'),
+ useKeyOnly(disabled, 'disabled'),
useKeyOnly(selected, 'selected'),
'item',
className,
@@ -78,6 +86,9 @@ DropdownItem.propTypes = {
/** Additional text with less emphasis. */
description: PropTypes.string,
+ /** A dropdown item can be disabled. */
+ disabled: PropTypes.bool,
+
/** Add an icon to the item. */
icon: PropTypes.string,
diff --git a/test/specs/modules/Dropdown/Dropdown-test.js b/test/specs/modules/Dropdown/Dropdown-test.js
index 9392707147..e5fc059278 100644
--- a/test/specs/modules/Dropdown/Dropdown-test.js
+++ b/test/specs/modules/Dropdown/Dropdown-test.js
@@ -193,6 +193,29 @@ describe('Dropdown Component', () => {
.find('.selected')
.should.contain.text('a2')
})
+ it('skips over disabled items', () => {
+ const opts = [
+ { text: 'a1', value: 'a1' },
+ { text: 'skip this one', value: 'skip this one', disabled: true },
+ { text: 'a2', value: 'a2' },
+ ]
+ // search for 'a'
+ wrapperMount()
+ .simulate('click')
+ .find('input.search')
+ .simulate('change', { target: { value: 'a' } })
+
+ wrapper
+ .find('.selected')
+ .should.contain.text('a1')
+
+ // move selection down
+ domEvent.keyDown(document, { key: 'ArrowDown' })
+
+ wrapper
+ .find('.selected')
+ .should.contain.text('a2')
+ })
it('scrolls the selected item into view', () => {
// get enough options to make the menu scrollable
const opts = getOptions(20)
@@ -260,6 +283,23 @@ describe('Dropdown Component', () => {
item.should.have.prop('active', true)
})
+ it('does not become active on enter when disabled', () => {
+ const disabledOptions = _.map(options, (o) => ({ ...o, disabled: true }))
+ const item = wrapperMount()
+ .simulate('click')
+ .find('DropdownItem')
+ .at(0)
+
+ // initial item props
+ item.should.have.prop('selected', true)
+ item.should.have.prop('active', false)
+
+ // attempt to make active
+ domEvent.keyDown(document, { key: 'Enter' })
+
+ item.should.have.prop('active', false)
+ dropdownMenuIsOpen()
+ })
it('closes the menu', () => {
wrapperMount()
.simulate('click')