-
Notifications
You must be signed in to change notification settings - Fork 4.1k
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
Dropdown enhancements: Better remote API support, item customization #416
Comments
1. Disabling/customizing local filtering
Hm, this might be a use case for the Search module. If there are no options in the list or if searches are potentially unrelated to the readable items, I think it is probably just a search field. I think the purpose of the Dropdown is to show the user a list of options. When searching, they are trimming down the list that is there. With the Dropdown being complex as it is, and the fact that there is a dedicated Search component (awaiting PR, #195), I think these features are best implemented there. Give the search a go on the SUI docs and let me know if you think this more closely suits your use case.
Filtering can be disabled by removing the
This sounds like a good improvement, I'd merge a PR implementing this. Perhaps 1a. Hiding/showing the "Add item" optionCould you give a little more info? Does your use case require showing the "Add item" option but with different logic?
Yep, would take a PR for this as well. 2. Option customizationYep, we pulled back on support for defining your own markup here. The reasoning was that there is no reliable way to "get items" from children. There would need to be some item register mechanism in place for this to work well I believe.
In progress as you noted, #414.
Would take a PR for this as well. It is the way our more recent components work, too. This should really be library wide. Arrays of items should always spread as props on the children they generate. 3. Hiding the dropdown caretGlad it worked. Was this a selection Dropdown or default (menu) style dropdown? 4. Customize "no results" textMerged, thanks! EDIT Added missing 1a response. |
I believe 2, 3, 4 are now addressed, could you confirm? Now, just to resolve 1 and 1a. |
+1 for option to disable local filtering, or at least customize it. Custom filtering is not against the nature of the component, IMHO. I want to use the dropdown component in a similar fashion to @jcarbo . I tried creating a simple CountrySelect component.
|
Could you post some code snippets with the expected behavior for each? What you are describing sounds like features already supported. There are known limitations elsewhere, but I'm seeing those described in this issue. The Dropdown needs more support for the sub component usage. I'd love to add more support with doc site examples. Though, I think I can achieve what I think is being described in this particular issue with the existing component. Disable SearchSearch (local filtering) is enabled by the <Dropdown options={...} />
// or
<Dropdown options={...} search={false} /> Custom Items & Custom SearchWhen using sub components, you are using the controlled component pattern. So you must handle search change, value, and item clicks manually. There is no reliable way (outside of using context) to add event listeners to the child items and filter them for you. This also means you can customize the search however you choose. Here, I've just used a simple "contains" RegEx search. Again, there are issues with this usage (such as keyboard navigation and propType warnings) and improvements that could be made (using const flagOptions = Flag._meta.props.name.map(name => ({
text: _.startCase(name), value: name,
}))
export default class CountrySearch extends Component {
componentWillMount() {
this.setState({ flagItems: this.filterItems(''), value: '' })
}
filterItems = (query) => flagOptions
.filter(({ text }) => new RegExp(query, 'gi').test(text))
.map(({ text, value }) => (
<Dropdown.Item key={value} value={value} onClick={this.handleItemClick}>
<Flag name={value} /> {text}
</Dropdown.Item>
))
handleItemClick = (e, value) => this.setState({
value: _.find(flagOptions, { value }).text,
})
handleSearchChange = (e, query) => {
const flagItems = this.filterItems(query)
this.setState({ flagItems })
}
render() {
const { flagItems, value } = this.state
return (
<Dropdown fluid search selection text={value} onSearchChange={this.handleSearchChange}>
<Dropdown.Menu>
{flagItems}
</Dropdown.Menu>
</Dropdown>
)
}
} Please let me know your exact use case as I would like to add more support to the Dropdown. |
I should note, if you can live without the Flag, you can still customize the search and things are far more simple. This is the current intended use of the Dropdown (or Select), so you also get full keyboard nav and no limitations. Here I search by "starts with": const flagOptions = Flag._meta.props.name.map(name => ({
text: _.startCase(name), value: name,
}))
export default class CountrySearch extends Component {
state = { options: flagOptions }
handleSearchChange = (e, query) => {
this.setState({
options: flagOptions.filter(({ text }) => {
return text.toLowerCase().startsWith(query.toLowerCase())
}),
})
}
render() {
const { options } = this.state
return (
<Select fluid search options={options} onSearchChange={this.handleSearchChange} />
)
}
} |
@levithomason Thank you very much for the examples and your time. Example of Dropdown with filter prop: |
@venelin-mihaylov as @levithomason pointed out, filtering can, without any changes to Stardust as it exists today, be disabled by disabling the // implict
<Dropdown options={...} />
// explicit
<Dropdown options={...} search={false} /> As far as the filter customization, that definitely sounds like a worthwhile solution. Here are my two proposals. Note that these are not mutually exclusive. Internal Filtering<Dropdown search={myFilterFunction} /> This provides an easy way to extend a External FilteringThis is the more traditional React way of doing filtering, and one that I'm in support of because it pulls state upwards and away from child components, allowing you to store more meaningful state toward the top of the application. With this approach there is definitely more manual wiring to do, but it's a worthwhile alternative and not one that should be dismissed just because it's syntactically lengthier. This is the same exact idea that @levithomason had earlier. _onSearchChange = (e, query) => {
this.setState({ filteredOptions: someItems.filter(...) })
}
render () {
const options = this.state.filteredOptions.map(...) // create elements
return (
<Dropdown search selection onSearchChange={this._onSearchChange}>
<Dropdown.Menu>
{options}
</Dropdown.Menu>
</Dropdown>
)
} The distinction between I believe these two options are not mutually exclusive, but if I had to pick one I'd go with the latter due to correctness and extensibility. |
If I can pass a function as a search prop, not just bool, that would be just what I need, thank you very much! As far as I can see, the internal filtering is done in Dropdown.getMenuOptions() The disadvantage of the external search for a country+flag example component is that the keyboard support is not working (and I do not know how to add it back to the controlled component, can it be done relatively easy?) I need not disable search altogether, but customize the internal filtering (disable it in some cases) and still have search prompt and searchText My use case:
|
@venelin-mihaylov you are correct. Currently, search can only be either disabled or enabled, it does not yet support a custom function. The We also discussed supporting keyboard navigation for more advanced dropdown item components by allowing users to pass in an array of React elements, rather than POJOS (Plain Old JavaScript Objects). So, if both of these were to be implemented, it would seem as though the needs delineated by this issue would be resolved :). |
search={func} sounds good to me, looks easy to implement as well. Would you accept a PR? Or you will implement it? Keyboard navigation is very nice to have ..., thank you for considering it. |
I'd accept a PR for the search prop, however, this will not solve either of @venelin-mihaylov's use cases since they both require custom sub component items and search with keyboard navigation. The Dropdown can only handle search behavior and keyboard navigation when using the @venelin-mihaylov has specified: Internal search
Having a flag means using custom Be aware that adding a External search
Again, this requires custom
This requirement is what makes this a Search component (#424) and not a Dropdown. A selection Dropdown is for selecting from a list of options. The Dropdown "search" is only a convenience for quickly finding the item in the list of options. You cannot search the options while also not filtering the items. When a user "searches" for something using a Search (#424) they see a loading state and eventually receive a hard list of the top results. A Search does not allow for filtering the local results. Instead, there are a limited number of "maxResults". If the user needs to "filter the list", they would perform a new search and receives new results, replacing the items in the list. I'll propose a solution anyhow, since I think in React the paradigm is a little different and may support both use cases. ProposalCustom Dropdown SearchLet's add @davezuko's proposed Search and Keyboard Nav with ChildrenWe need to be able to reliably loop the children in order to provide this. I propose we either:
Server Side SearchingThis could be accomplished by extending the proposed search API to allow both enabling search and disabling the search filter function. This sounds confusing and I think the API shows that as well. If <Dropdown
search={null}
onSearchChange={this.handleSearch}
options={this.state.options}
/> Compare this to: <Search
onChange={this.handleSearch}
results={this.state.results}
/> I agree with SUI in the separation here. A Search should never filter the list, but only replace the results on query change. The search selection Dropdown should always filter the list on query change. The Dropdown is just a modern |
FYI I did point out:
Which falls in line with your proposal to allow |
Indeed, I should have specified the array of elements was per your offline suggestion. It is also inline with where we are going with all shorthand props. Shorthand is used to generate children. Shorthand props then should accept a primitive that gets mapped to the most intuitive child prop, a props object used to createElement, or an instance which is cloned. So, we can implement |
I am currently using the children prop of options to fancy render the options ... with that I can custom render options (pass react elements there) and also have keyboard nav. (The only hindrance is that {text} is displayed always first, which is not always appropriate, but's that's a minor thing ...) |
This is good. Though this is using custom items, it is not doing so via JSX in the Dropdown children. It is still using the <Dropdown>
<Dropdown.Menu>
<Dropdown. Item {...} />
</Dropdown.Menu>
</Dropdown> Though the provided example is working, it is not supported usage and will not work in v1. The reason is that <Dropdown.Item text='USA' />
// vs
<Dropdown.Item>USA</Dropdown.Item> I'd recommend refactoring your map function to use only const options = countries.all
.filter(c => c.status === 'assigned' && Flag._meta.props.name.indexOf(c.alpha2.toLowerCase()) !== -1) // eslint-disable-line no-underscore-dangle
.map(c => ({
value: c.alpha2.toLowerCase(),
name: c.name,
- text: c.name,
- children: <Flag name={c.alpha2.toLowerCase()} style={{marginLeft: 5}} />
+ children: (
+ <div>
+ <Flag name={c.alpha2.toLowerCase()} style={{marginLeft: 5}} /> {c.name}
+ </div>
+ )
})) Lastly, I'd remove usage of the |
I've added issues asking for PRs for the following:
|
@jcarbo I believe all issues raised in the OP are now solved. Could you confirm or expound on what's left in this issue? Thanks much. |
This wound up a little long, sorry in advance!
I'm trying to use the dropdown with a remote data source. I'm running into a few interconnected issues so I'm just going to enumerate them here:
1. Disabling/customizing local filtering
I'm using the
onSearchChange
callback to send a request to the server with the search term and it's sending me back filtered results. The issue I'm running into is that the results are being filtered client-side as well:https://github.com/TechnologyAdvice/stardust/blob/master/src/modules/Dropdown/Dropdown.js#L527-L531
In my case, the
text
label for each option is not the criteria that's used server-side, causing results to be filtered out incorrectly. I'd basically just like to skip this filtering since I'm relying on the server to do it.Some possible solutions:
_.filter
.() => true
.text
.Option 2/3 seems to provide more flexibility. The default could be the current functionality since that's what one would expect for the simple text-option case.
1a. Hiding/showing the "Add item" option
There' also a filter function to determine whether the "Add item" option should be shown that's slightly different than the filter above:
That could also be a prop (e.g.
showAdditionOption
or some better name) that's a function that takes the options and the search query and returns a boolean. That would be necessary for my case, but you could imagine using that to doing things like validation or more complex logic.2. Option customization
I want to display a more complex "dropdown item" with some custom styles. I think this could be done with two small (backwards compatible) tweaks:
Dropdown.Item
as a prop so one could pass a custom component. This is already in progress: Breaking Changes: Add component augmentation #414options
array only havingvalue
andtext
keys, treat the object as props to be passed to the object. E.g. Change the snippet here to something like:I tried just rendering my own menu+items but this breaks the keyboard up/down/enter functionality, which relies on an
options
array. That could be something to address in addition or instead of the above.3. Hiding the dropdown caret
This is pretty self-explanatory and might even be possible now - if so, how?Passingundefined
as theicon
property does the trick :)4. Customize "no results" text
Add a prop like
noResultsLabel
analogous toadditionLabel
?The text was updated successfully, but these errors were encountered: