-
Notifications
You must be signed in to change notification settings - Fork 4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix(Dropdown): fix multiple problems #1795
Changes from 6 commits
0163591
642fe6a
d547dd0
f6539f0
2854e18
6c8b203
b51fd58
b626d98
002f411
2fa2fbd
a69584f
21ec3cf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -533,7 +533,7 @@ export default class Dropdown extends Component { | |
if (move === undefined) return | ||
e.preventDefault() | ||
this.moveSelectionBy(move) | ||
if (!multiple) this.makeSelectedItemActive(e) | ||
if (!multiple) this.makeSelectedItemActive(e, false) | ||
} | ||
|
||
openOnSpace = (e) => { | ||
|
@@ -559,7 +559,7 @@ export default class Dropdown extends Component { | |
this.open(e) | ||
} | ||
|
||
makeSelectedItemActive = (e) => { | ||
makeSelectedItemActive = (e, resetQuery = true) => { | ||
const { open } = this.state | ||
const { multiple, onAddItem } = this.props | ||
const item = this.getSelectedItem() | ||
|
@@ -577,10 +577,10 @@ export default class Dropdown extends Component { | |
if (multiple) { | ||
// state value may be undefined | ||
const newValue = _.union(this.state.value, [value]) | ||
this.setValue(newValue) | ||
this.setValue(newValue, resetQuery) | ||
this.handleChange(e, newValue) | ||
} else { | ||
this.setValue(value) | ||
this.setValue(value, resetQuery) | ||
this.handleChange(e, value) | ||
} | ||
} | ||
|
@@ -659,7 +659,20 @@ export default class Dropdown extends Component { | |
|
||
if (!search) return this.toggle(e) | ||
if (open) return | ||
if (searchQuery.length >= minCharacters || minCharacters === 1) this.open(e) | ||
if (searchQuery.length >= minCharacters || minCharacters === 1) { | ||
this.open(e) | ||
return | ||
} | ||
if (this.searchRef) this.searchRef.focus() | ||
} | ||
|
||
handleIconClick = e => { | ||
debug('handleIconClick()', e) | ||
|
||
_.invoke(this.props, 'onClick', e, this.props) | ||
// prevent handleClick() | ||
e.stopPropagation() | ||
this.toggle(e) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't want to split this to separate method, however I don't see any other way. The initial idea was to compare There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep, I'd much rather have a separate explicit click handler here. This is OK. We are doing something entirely different when the icon is clicked. |
||
} | ||
|
||
handleItemClick = (e, item) => { | ||
|
@@ -859,12 +872,12 @@ export default class Dropdown extends Component { | |
// Setters | ||
// ---------------------------------------- | ||
|
||
setValue = (value) => { | ||
setValue = (value, resetQuery = true) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure that the idea with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we should split this into two methods. There are also enough use cases to warrant seprating them I think: componentWillMountThis calls We don't need to clear the query on mount, but we do need to set the selected index. I think this is duplicating work, but at the least, we don't need to clear the query on mount. componentWillReceivePropsThis calls makeSelectedItemActiveThis method currently takes a
removeItemOnBackspaceCurrently resets the query but is not necessary, there is no query at this point. Also, if the user enters a query and moves the cursor to the begining of the query and then presses backspace, we wouldn't want to clear the query either:
I know we don't support this yet, but we technically could/should in the future. handleItemClickThis currently resets the query when an menu item is made active, however, I have always found this to be a usability issue with If we separate handleLabelRemoveThis will currently clear the query also but probably never should have done so. Here I'm again selecting states with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @levithomason Thanks for a detailed feedback, I'm fully agree with your suggestions. Will make a changes soon There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that we should extract behaviour of |
||
debug('setValue()') | ||
debug('value', value) | ||
const newState = { | ||
const newState = resetQuery ? { | ||
searchQuery: '', | ||
} | ||
} : {} | ||
|
||
const { multiple, search } = this.props | ||
if (multiple && search && this.searchRef) this.searchRef.focus() | ||
|
@@ -960,6 +973,17 @@ export default class Dropdown extends Component { | |
this.scrollSelectedItemIntoView() | ||
} | ||
|
||
// ---------------------------------------- | ||
// Overrides | ||
// ---------------------------------------- | ||
|
||
handleIconOverrides = predefinedProps => ({ | ||
onClick: (e) => { | ||
_.invoke(predefinedProps, 'onClick', e, predefinedProps) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please take an attention to this line, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see the issue here, however, I think this is going to be rare enough of an edge case to leave as-is for now. We shall see :) |
||
this.handleIconClick(e) | ||
}, | ||
}) | ||
|
||
// ---------------------------------------- | ||
// Refs | ||
// ---------------------------------------- | ||
|
@@ -1275,7 +1299,9 @@ export default class Dropdown extends Component { | |
{this.renderSearchInput()} | ||
{this.renderSearchSizer()} | ||
{trigger || this.renderText()} | ||
{Icon.create(icon)} | ||
{Icon.create(icon, { | ||
overrideProps: this.handleIconOverrides, | ||
})} | ||
{this.renderMenu()} | ||
</ElementType> | ||
) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,11 +18,12 @@ const shorthandComponentName = ShorthandComponent => { | |
* @param {object} options | ||
* @param {string} options.propKey The name of the shorthand prop. | ||
* @param {string|function} options.ShorthandComponent The component that should be rendered from the shorthand value. | ||
* @param {function} options.mapValueToProps A function that maps a primitive value to the Component props | ||
* @param {boolean} [options.alwaysPresent] Whether or not the shorthand exists by default. | ||
* @param {function} options.mapValueToProps A function that maps a primitive value to the Component props. | ||
* @param {Object} [options.requiredProps={}] Props required to render the component. | ||
* @param {Object} [options.shorthandDefaultProps] Default props for the shorthand component. | ||
* @param {Object} [options.shorthandOverrideProps] Override props for the shorthand component. | ||
* @param {boolean} [options.alwaysPresent] Whether or not the shorthand exists by default | ||
* @param {boolean} [options.strictAssert] Selects an assertion method, `contain` will be used if true. | ||
*/ | ||
export default (Component, options = {}) => { | ||
const { | ||
|
@@ -32,9 +33,11 @@ export default (Component, options = {}) => { | |
ShorthandComponent, | ||
shorthandDefaultProps = {}, | ||
shorthandOverrideProps = {}, | ||
strictAssert = true, | ||
requiredProps = {}, | ||
} = options | ||
const { assertRequired } = helpers('implementsShorthandProp', Component) | ||
const assertMethod = strictAssert ? 'contain' : 'containMatchingElement' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why? |
||
|
||
describe(`${propKey} shorthand prop (common)`, () => { | ||
assertRequired(Component, 'a `Component`') | ||
|
@@ -50,7 +53,7 @@ export default (Component, options = {}) => { | |
}) | ||
const element = createElement(Component, { ...requiredProps, [propKey]: value }) | ||
|
||
shallow(element).should.contain(shorthandElement) | ||
shallow(element).should[assertMethod](shorthandElement) | ||
} | ||
|
||
if (alwaysPresent || Component.defaultProps && Component.defaultProps[propKey]) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@levithomason this method does not make me happy, do you have ideas how we can refactor it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, I don't think so. React has no focus mechanism so we'll have to use a ref or querySelector here. Since this section of logic is only reachable if this is a closed search dropdown, I think it makes sense.
What specifically are you not happy with and perhaps I can recommend better?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Too many conditions for one method as I think.