A lightweight and fast control to render a select component that can display hierarchical tree data. In addition, the control shows the selection in pills and allows user to search the options for quick filtering and selection. Also supports displaying partially selected nodes.
- Screenshot
- Demo
- Install
- Usage
- Props
- Styling and Customization
- Performance
- FAQ
- Doing more with HOCs
- Development
- License
Online demo: https://dowjones.github.io/react-dropdown-tree-select/#/story/with-vanilla-styles
Online demo: https://dowjones.github.io/react-dropdown-tree-select/#/story/with-bootstrap-styles
Online demo: https://dowjones.github.io/react-dropdown-tree-select/#/story/with-material-design-styles
Online demo: https://dowjones.github.io/react-dropdown-tree-select/#/story/simple-select
npm i react-dropdown-tree-select
// or if using yarn
yarn add react-dropdown-tree-select
You can import the standalone UMD build from a CDN such as:
<script src="https://unpkg.com/react-dropdown-tree-select/dist/react-dropdown-tree-select.js"></script>
<link href="https://unpkg.com/react-dropdown-tree-select/dist/styles.css" rel="stylesheet">
Note: Above example will always fetch the latest version. To fetch a specific version, use https://unpkg.com/react-dropdown-tree-select@<version>/dist/...
Visit unpkg.com to see other options.
In order to avoid version conflicts in your project, you must specify and install react, react-dom as peer dependencies. Note that NPM doesn't install peer dependencies automatically. Instead it will show you a warning message with instructions on how to install them.
If you're using the UMD builds, you'd also need to install the peer dependencies in your application:
<script src="https://unpkg.com/react/dist/react.js"></script>
<script src="https://unpkg.com/react-dom/dist/react-dom.js"></script>
import React from 'react'
import ReactDOM from 'react-dom'
import DropdownTreeSelect from 'react-dropdown-tree-select'
import 'react-dropdown-tree-select/dist/styles.css'
const data = {
label: 'search me',
value: 'searchme',
children: [
{
label: 'search me too',
value: 'searchmetoo',
children: [
{
label: 'No one can get me',
value: 'anonymous'
}
]
}
]
}
const onChange = (currentNode, selectedNodes) => {
console.log('onChange::', currentNode, selectedNodes)
}
const onAction = ({ action, node }) => {
console.log(`onAction:: [${action}]`, node)
}
const onNodeToggle = currentNode => {
console.log('onNodeToggle::', currentNode)
}
ReactDOM.render(<DropdownTreeSelect data={data} onChange={onChange} onAction={onAction} onNodeToggle={onNodeToggle} />, document.body) // in real world, you'd want to render to an element, instead of body.
Type: string
Additional classname for container. The container renders with a default classname of react-dropdown-tree-select
.
Type: bool
Clear the input search if a node has been selected/unselected.
Type: function
Fires when a node change event occurs. Currently the following actions trigger a node change:
- Checkbox click which checks/unchecks the item
- Closing of pill (which unchecks the corresponding checkbox item)
Calls the handler with the current node object and all selected nodes (if any). Example:
function onChange(currentNode, selectedNodes) {
// currentNode: { label, value, children, expanded, checked, className, ...extraProps }
// selectedNodes: [{ label, value, children, expanded, checked, className, ...extraProps }]
}
return <DropdownTreeSelect data={data} onChange={onChange} />
Type: function
Fires when a node is expanded or collapsed.
Calls the handler with the current node object. Example:
function onNodeToggle(currentNode) {
// currentNode: { label, value, children, expanded, checked, className, ...extraProps }
}
return <DropdownTreeSelect data={data} onNodeToggle={onNodeToggle} />
Type: Object
or Array
Data for rendering the tree select items. The object requires the following structure:
{
label, // required: Checkbox label
value, // required: Checkbox value
children, // optional: Array of child objects
checked, // optional: Initial state of checkbox. if true, checkbox is selected and corresponding pill is rendered.
disabled, // optional: Selectable state of checkbox. if true, the checkbox is disabled and the node is not selectable.
expanded, // optional: If true, the node is expanded (children of children nodes are not expanded by default unless children nodes also have expanded: true).
className, // optional: Additional css class for the node. This is helpful to style the nodes your way
tagClassName, // optional: Css class for the corresponding tag. Use this to add custom style the pill corresponding to the node.
actions, // optional: An array of extra action on the node (such as displaying an info icon or any custom icons/elements)
dataset, // optional: Allows data-* attributes to be set on the node and tag elements
... // optional: Any extra properties that you'd like to receive during `onChange` event
}
The action
object requires the following structure:
{
className, // required: CSS class for the node. e.g. `fa fa-info`
onAction, // required: Fired on click of the action. The event handler receives `action` object as well as the `node` object.
title, // optional: HTML tooltip text
text, // optional: Any text to be displayed. This is helpful to pass ligatures if you're using ligature fonts
... // optional: Any extra properties that you'd like to receive during `onChange` event
}
An array renders a tree with multiple root level items whereas an object renders a tree with a single root element (e.g. a Select All
root node).
Type: string
The text to display as placeholder on the search box. Defaults to Choose...
Type: string
The text to display when the search does not find results in the content list. Defaults to No matches found
Type: bool
Displays search results as a tree instead of flattened results
Type: bool
(default: false
)
Turns the dropdown into a simple, single select dropdown. If you pass tree data, only immediate children are picked, grandchildren nodes are ignored. Defaults to false
.
Type: bool
(default: false
)
If set to true, shows checkboxes in a partial state when one, but not all of their children are selected. Allows styling of partially selected nodes as well, by using :indeterminate pseudo class. Simply add desired styles to .node.partial .checkbox-item:indeterminate { ... }
in your CSS.
The component brings minimal styles for bare-bones functional rendering. It is kept purposefully minimal so that user can style/customize it completely to suit their needs.
If you're using a bundler like webpack, make sure you configure webpack to import the default styles. To do so, simply add this rule to your webpack config:
// allow webpack to import/bundle styles from node_modules for this component
module: {
rules: [
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: [
{
loader: 'css-loader'
}
]
}),
include: /node_modules[/\\]react-dropdown-tree-select/
}
]
}
You can import and place a style link directly by referencing it from a CDN.
<link href="https://unpkg.com/react-dropdown-tree-select/dist/styles.css" rel="stylesheet">
Note: Above example will always fetch the latest version. To fetch a specific version, use https://unpkg.com/react-dropdown-tree-select@<version>/dist/styles.css
. Visit unpkg.com to see other options.
You can reference the files from node_modules/react-dropdown-tree-select/dist/styles.css
to include in your own bundle via gulp or any other bundlers you have.
Once you import default styles, it is easy to add/override the provided styles to match popular frameworks. Checkout /docs
folder for some examples.
- The tree creates a flat list of nodes from hierarchical tree data to perform searches that are linear in time irrespective of the tree depth or size.
- It also memoizes each search term, so subsequent searches are instantaneous (almost).
- Last but not the least, the search employs progressive filtering technique where subsequent searches are performed on the previous search set. E.g., say the tree has 4000 nodes altogether and the user wants to filter nodes that contain the text: "2002". As the user enters each key press the search goes like this:
key press : 2-----20-----200-----2002
| | | |
search set: 967 834 49 7
The search for "20" happens against the previously matched set of 967 as opposed to all 4000 nodes; "200" happens against 834 nodes and so on.
The tree debounces key presses to avoid costly search calculations. The default duration is 100ms.
The dropdown renders only visible content and skips any nodes that are going to hidden from the user. E.g., if a parent node is not expanded, there is no point in rendering children since they will not be visible anyway.
Planned feature: Use react-virtualized to take this to the next level.
The tree tries to minimize the DOM manipulations as much as possible. E.g., during searching, the non-matching nodes are simply hidden
and css adjusted on remaining to create the perception of a new filtered list.
Node toggling also achieves the expand/collapse effect by manipulating css classes instead of creating new tree with filtered out nodes.
The default placeholder is Choose...
. If you want to change this to something else, you can use placeholderText
property to set it.
<DropdownTreeSelect placeholderText="Search" />
Easy style customization is one of the design goals of this component. Every visual aspect of this dropdown can be tweaked without going through extensive hacks. E.g., to change how disabled nodes appear:
.node .fa-ban {
color: #ccc;
}
The css classes needed to overide can be found by inspecting the component via developer tools (chrome/safari/ie) or firebug (firefox). You can also inspect the source code or look in examples.
Absolutely not! Simply do not import the styles (webpack) or include it in your html (link tags). Roughly, this is the HTML/CSS skeleton rendered by the component:
div.react-dropdown-tree-select
div.dropdown
a.dropdown-trigger
span
ul.tag-list
li.tag-item
input
div.dropdown-content
ul.root
li.node.tree
i.toggle.collapsed
label
input.checkbox-item
span.node-label
Write your own styles from scratch or copy existing styles and tweak them. Then include your own custom styles in your project.
💡 Pro tip: Leverage node's className
, tagClassName
or action's className
prop to emit your own class name. Then override/add css propeties in your class. Remember that last person wins in CSS (unless specificity or !important
is involved). Often times, this may suffice and may be easier then writing all the styles from the ground up.
If you believe this aspect can be improved further, feel free to raise an issue.
Find more questions and their answers in the issue tracker. If it still doesn't answer your questions, please raise an issue.
Clone the git repo and install dependencies.
npm i
// or
yarn install
You can then run following scripts for local development
npm run demo // local demo, watches and rebuilds on changes
npm test // test your changes
npm lint // fixes anything that can be fixed and reports remaining errors
npm run test:cov // test coverage
Released 2017 by Hrusikesh Panda @ Dow Jones