Skip to content

Commit 45d7064

Browse files
committed
feat: Add new props for search, keyboard navigation, and scrolling
The new prop searchTerm can be used to set the initial search term or fully control the search semantics for a higher-order component. The new prop onSearchChange allows specification of a callback for when the search term changes or the search mode is activated or deactivated. The new prop disableKeyboardNavigation allows disabling all actions associated with a key down event in the search input box. The new prop pageSize can be used to control the size of the scroll view before scrolling to near the bottom is required to show more nodes.
1 parent 1e891d0 commit 45d7064

File tree

6 files changed

+111
-6
lines changed

6 files changed

+111
-6
lines changed

README.md

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,14 @@ A lightweight and fast control to render a select component that can display hie
4141
- [Usage](#usage)
4242
- [Props](#props)
4343
- [className](#classname)
44+
- [searchTerm](#searchterm)
4445
- [clearSearchOnChange](#clearsearchonchange)
4546
- [onChange](#onchange)
4647
- [onNodeToggle](#onnodetoggle)
4748
- [onAction](#onaction)
4849
- [onFocus](#onfocus)
4950
- [onBlur](#onblur)
51+
- [onSearchChange](#onsearchchange)
5052
- [data](#data)
5153
- [texts](#texts)
5254
- [keepTreeOnSearch](#keeptreeonsearch)
@@ -57,6 +59,7 @@ A lightweight and fast control to render a select component that can display hie
5759
- [hierarchical](#hierarchical)
5860
- [simpleSelect](#simpleselect)
5961
- [radioSelect](#radioselect)
62+
- [pageSize](#pagesize)
6063
- [showPartiallySelected](#showpartiallyselected)
6164
- [showDropdown](#showdropdown)
6265
- [initial](#initial)
@@ -66,7 +69,8 @@ A lightweight and fast control to render a select component that can display hie
6669
- [searchPredicate](#searchpredicate)
6770
- [inlineSearchInput](#inlinesearchinput)
6871
- [tabIndex](#tabIndex)
69-
- [disablePoppingOnBackspace](#disablePoppingOnBackspace)
72+
- [disablePoppingOnBackspace](#disablepoppingonbackspace)
73+
- [disableKeyboardNavigation](#disablekeyboardnavigation)
7074
- [Styling and Customization](#styling-and-customization)
7175
- [Using default styles](#default-styles)
7276
- [Customizing with Bootstrap, Material Design styles](#customizing-styles)
@@ -188,6 +192,12 @@ Type: `string`
188192

189193
Additional classname for container. The container renders with a default classname of `react-dropdown-tree-select`.
190194

195+
### searchTerm
196+
197+
Type: `string`
198+
199+
Initializes or adjusts the active search term. Set to an empty string or `undefined` to turn search mode off.
200+
191201
### clearSearchOnChange
192202

193203
Type: `bool`
@@ -256,6 +266,24 @@ Type: `function`
256266

257267
Fires when input box loses focus or the dropdown arrow is clicked again (and the dropdown collapses). This is helpful for setting `dirty` or `touched` flags with forms.
258268

269+
### onSearchChange
270+
271+
Type: `function`
272+
273+
Called when the search input box is changed with the current search term. This can be fired either through user input or automatically due to `clearSearchOnChange`. Example:
274+
275+
```jsx
276+
function onSearchChange(searchTerm: str) {
277+
if (searchTerm) {
278+
console.log('New search term is', searchTerm)
279+
} else {
280+
console.log('Search mode has been disabled')
281+
}
282+
}
283+
284+
return <DropdownTreeSelect data={data} onSearchChange={onSearchChange} />
285+
```
286+
259287
### data
260288

261289
Type: `Object` or `Array`
@@ -361,6 +389,12 @@ Like `simpleSelect`, you can only select one value; but keeps the tree/children
361389

362390
⚠️ If multiple nodes in data are selected - by setting either `checked` or `isDefaultValue`, only the first visited node stays selected.
363391

392+
### pageSize
393+
394+
Type: `number` (default: `100`)
395+
396+
Customize the number of nodes displayed in the tree before a scroll to near the bottom is required to load additional nodes.
397+
364398
### showPartiallySelected
365399

366400
Type: `bool` (default: `false`)
@@ -428,6 +462,12 @@ Type: `bool` (default: `false`)
428462

429463
`disablePoppingOnBackspace=true` attribute indicates that when a user triggers a 'backspace' keyDown in the empty search bar, the tree will not deselect nodes.
430464

465+
### disableKeyboardNavigation
466+
467+
Type: `bool` (default: `false`)
468+
469+
`disableKeyboardNavigation=true` prevents keyboard navigation actions from being taken on the nodes when the user triggers a keyDown in the search bar. This restores standard input box semantics.
470+
431471
## Styling and Customization
432472

433473
### Default styles

docs/src/stories/Options/index.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@ class WithOptions extends PureComponent {
1111
super(props)
1212

1313
this.state = {
14+
searchTerm: '',
1415
clearSearchOnChange: false,
1516
keepTreeOnSearch: false,
1617
keepOpenOnSelect: false,
1718
mode: 'multiSelect',
19+
pageSize: undefined,
1820
inlineSearchInput: false,
1921
showPartiallySelected: false,
2022
disabled: false,
@@ -39,12 +41,18 @@ class WithOptions extends PureComponent {
3941
this.setState({ [value]: !this.state[value] })
4042
}
4143

44+
onSearchChange = searchTerm => {
45+
this.setState({ searchTerm: searchTerm })
46+
}
47+
4248
render() {
4349
const {
50+
searchTerm,
4451
clearSearchOnChange,
4552
keepTreeOnSearch,
4653
keepOpenOnSelect,
4754
mode,
55+
pageSize,
4856
showPartiallySelected,
4957
disabled,
5058
readOnly,
@@ -105,6 +113,26 @@ class WithOptions extends PureComponent {
105113
onChange={e => this.setState({ inlineSearchPlaceholder: e.target.value })}
106114
/>
107115
</div>
116+
<div style={{ marginBottom: '10px' }}>
117+
<label htmlFor="searchTerm">Search term: </label>
118+
<input
119+
id="searchTerm"
120+
type="text"
121+
value={searchTerm}
122+
onChange={e => this.setState({ searchTerm: e.target.value })}
123+
/>
124+
</div>
125+
<div style={{ marginBottom: '10px' }}>
126+
<label htmlFor="pageSize">Page size ({pageSize || 100}): </label>
127+
<input
128+
id="pageSize"
129+
type="range"
130+
min={50}
131+
max={1000}
132+
value={pageSize || 100}
133+
onChange={e => this.setState({ pageSize: e.target.valueAsNumber || undefined })}
134+
/>
135+
</div>
108136
<Checkbox
109137
label="Inline Search Input"
110138
value="inlineSearchInput"
@@ -142,13 +170,16 @@ class WithOptions extends PureComponent {
142170
<DropdownTreeSelect
143171
id="rdts"
144172
data={data}
173+
searchTerm={searchTerm}
145174
onChange={this.onChange}
146175
onAction={this.onAction}
147176
onNodeToggle={this.onNodeToggle}
177+
onSearchChange={this.onSearchChange}
148178
clearSearchOnChange={clearSearchOnChange}
149179
keepTreeOnSearch={keepTreeOnSearch}
150180
keepOpenOnSelect={keepOpenOnSelect}
151181
mode={mode}
182+
pageSize={pageSize}
152183
showPartiallySelected={showPartiallySelected}
153184
disabled={disabled}
154185
readOnly={readOnly}

docs/src/stories/Simple/index.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ const onBlur = () => {
2323
console.log('onBlur')
2424
}
2525

26+
const onSearchChange = searchTerm => {
27+
console.log('onSearchChange::', searchTerm)
28+
}
29+
2630
const Simple = () => (
2731
<div>
2832
<h1>Basic component</h1>
@@ -45,6 +49,7 @@ const Simple = () => (
4549
onNodeToggle={onNodeToggle}
4650
onFocus={onFocus}
4751
onBlur={onBlur}
52+
onSearchChange={onSearchChange}
4853
className="demo"
4954
/>
5055
</div>

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
2-
"name": "react-dropdown-tree-select",
3-
"version": "0.0.0-semantic-release",
2+
"name": "@gandhis1/react-dropdown-tree-select",
3+
"version": "2.7.1-fork-2",
44
"description": "Lightweight, customizable and fast Dropdown Tree Select component for React",
55
"keywords": [
66
"react",

src/index.js

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { getAriaLabel } from './a11y'
2323
class DropdownTreeSelect extends Component {
2424
static propTypes = {
2525
data: PropTypes.oneOfType([PropTypes.object, PropTypes.array]).isRequired,
26+
searchTerm: PropTypes.string,
2627
clearSearchOnChange: PropTypes.bool,
2728
keepTreeOnSearch: PropTypes.bool,
2829
keepChildrenOnSearch: PropTypes.bool,
@@ -41,7 +42,9 @@ class DropdownTreeSelect extends Component {
4142
onNodeToggle: PropTypes.func,
4243
onFocus: PropTypes.func,
4344
onBlur: PropTypes.func,
45+
onSearchChange: PropTypes.func,
4446
mode: PropTypes.oneOf(['multiSelect', 'simpleSelect', 'radioSelect', 'hierarchical']),
47+
pageSize: PropTypes.number,
4548
showPartiallySelected: PropTypes.bool,
4649
disabled: PropTypes.bool,
4750
readOnly: PropTypes.bool,
@@ -50,18 +53,21 @@ class DropdownTreeSelect extends Component {
5053
inlineSearchInput: PropTypes.bool,
5154
tabIndex: PropTypes.number,
5255
disablePoppingOnBackspace: PropTypes.bool,
56+
disableKeyboardNavigation: PropTypes.bool,
5357
}
5458

5559
static defaultProps = {
5660
onAction: () => {},
5761
onFocus: () => {},
5862
onBlur: () => {},
5963
onChange: () => {},
64+
onSearchChange: _ => {},
6065
texts: {},
6166
showDropdown: 'default',
6267
inlineSearchInput: false,
6368
tabIndex: 0,
6469
disablePoppingOnBackspace: false,
70+
disableKeyboardNavigation: true,
6571
}
6672

6773
constructor(props) {
@@ -73,7 +79,7 @@ class DropdownTreeSelect extends Component {
7379
this.clientId = props.id || clientIdGenerator.get(this)
7480
}
7581

76-
initNewProps = ({ data, mode, showDropdown, showPartiallySelected, searchPredicate }) => {
82+
initNewProps = ({ data, searchTerm, mode, showDropdown, showPartiallySelected, searchPredicate }) => {
7783
this.treeManager = new TreeManager({
7884
data,
7985
mode,
@@ -86,7 +92,12 @@ class DropdownTreeSelect extends Component {
8692
if (currentFocusNode) {
8793
currentFocusNode._focused = true
8894
}
95+
const searchTermChanged = searchTerm !== prevState.searchTerm
96+
if (this.searchInput && searchTermChanged) {
97+
this.searchInput.value = searchTerm
98+
}
8999
return {
100+
searchModeOn: searchTermChanged,
90101
showDropdown: /initial|always/.test(showDropdown) || prevState.showDropdown === true,
91102
...this.treeManager.getTreeAndTags(),
92103
}
@@ -96,7 +107,10 @@ class DropdownTreeSelect extends Component {
96107
resetSearchState = () => {
97108
// clear the search criteria and avoid react controlled/uncontrolled warning
98109
if (this.searchInput) {
99-
this.searchInput.value = ''
110+
if (this.searchInput.value !== '') {
111+
this.props.onSearchChange(this.searchInput.value)
112+
this.searchInput.value = ''
113+
}
100114
}
101115

102116
return {
@@ -154,6 +168,7 @@ class DropdownTreeSelect extends Component {
154168
this.props.keepChildrenOnSearch
155169
)
156170
const searchModeOn = value.length > 0
171+
this.props.onSearchChange(value)
157172

158173
this.setState({
159174
tree,
@@ -238,6 +253,10 @@ class DropdownTreeSelect extends Component {
238253
}
239254

240255
onKeyboardKeyDown = e => {
256+
if (this.props.disableKeyboardNavigation) {
257+
return // Will fire the default action
258+
}
259+
241260
const { readOnly, mode, disablePoppingOnBackspace } = this.props
242261
const { showDropdown, tags, searchModeOn, currentFocus } = this.state
243262
const tm = this.treeManager
@@ -360,6 +379,7 @@ class DropdownTreeSelect extends Component {
360379
onCheckboxChange={this.onCheckboxChange}
361380
onNodeToggle={this.onNodeToggle}
362381
mode={mode}
382+
pageSize={this.props.pageSize}
363383
showPartiallySelected={this.props.showPartiallySelected}
364384
{...commonProps}
365385
/>

types/react-dropdown-tree-select.d.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// tslint:disable:interface-name
2-
declare module 'react-dropdown-tree-select' {
2+
declare module '@gandhis1/react-dropdown-tree-select' {
33
import * as React from 'react'
44

55
export type TreeData = Object | TreeNodeProps[]
@@ -10,6 +10,8 @@ declare module 'react-dropdown-tree-select' {
1010

1111
export interface DropdownTreeSelectProps {
1212
data: TreeData
13+
/** Initialize the search input with the specified term and search the nodes */
14+
searchTerm?: str
1315
/** Clear the input search if a node has been selected/unselected */
1416
clearSearchOnChange?: boolean
1517
/** Displays search results as a tree instead of flattened results */
@@ -54,6 +56,9 @@ declare module 'react-dropdown-tree-select' {
5456
* This is helpful for setting dirty or touched flags with forms
5557
*/
5658
onBlur?: () => void
59+
/** Fires when search input is modified.
60+
*/
61+
onSearchChange?: (searchTerm: str) => void
5762
/** Defines how the dropdown is rendered / behaves
5863
*
5964
* - multiSelect
@@ -79,6 +84,8 @@ declare module 'react-dropdown-tree-select' {
7984
*
8085
* */
8186
mode?: Mode
87+
/** The size (in nodes) of a single page in the infinite scroll component. */
88+
pageSize?: number
8289
/** If set to true, shows checkboxes in a partial state when one, but not all of their children are selected.
8390
* Allows styling of partially selected nodes as well, by using :indeterminate pseudo class.
8491
* Simply add desired styles to .node.partial .checkbox-item:indeterminate { ... } in your CSS
@@ -103,6 +110,8 @@ declare module 'react-dropdown-tree-select' {
103110
* search bar, the tree will not deselect nodes.
104111
*/
105112
disablePoppingOnBackspace?: boolean
113+
/** dsiableKeyboardNavigation will disable keyboard navigation of the tree */
114+
dsiableKeyboardNavigation?: boolean
106115
}
107116

108117
export interface DropdownTreeSelectState {

0 commit comments

Comments
 (0)