Skip to content

Commit

Permalink
feat: Add new props for search, keyboard navigation, and scrolling
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
gandhis1 committed Aug 10, 2022
1 parent 1e891d0 commit 29d1fd7
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 3 deletions.
42 changes: 41 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,14 @@ A lightweight and fast control to render a select component that can display hie
- [Usage](#usage)
- [Props](#props)
- [className](#classname)
- [searchTerm](#searchterm)
- [clearSearchOnChange](#clearsearchonchange)
- [onChange](#onchange)
- [onNodeToggle](#onnodetoggle)
- [onAction](#onaction)
- [onFocus](#onfocus)
- [onBlur](#onblur)
- [onSearchChange](#onsearchchange)
- [data](#data)
- [texts](#texts)
- [keepTreeOnSearch](#keeptreeonsearch)
Expand All @@ -57,6 +59,7 @@ A lightweight and fast control to render a select component that can display hie
- [hierarchical](#hierarchical)
- [simpleSelect](#simpleselect)
- [radioSelect](#radioselect)
- [pageSize](#pagesize)
- [showPartiallySelected](#showpartiallyselected)
- [showDropdown](#showdropdown)
- [initial](#initial)
Expand All @@ -66,7 +69,8 @@ A lightweight and fast control to render a select component that can display hie
- [searchPredicate](#searchpredicate)
- [inlineSearchInput](#inlinesearchinput)
- [tabIndex](#tabIndex)
- [disablePoppingOnBackspace](#disablePoppingOnBackspace)
- [disablePoppingOnBackspace](#disablepoppingonbackspace)
- [disableKeyboardNavigation](#disablekeyboardnavigation)
- [Styling and Customization](#styling-and-customization)
- [Using default styles](#default-styles)
- [Customizing with Bootstrap, Material Design styles](#customizing-styles)
Expand Down Expand Up @@ -188,6 +192,12 @@ Type: `string`

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

### searchTerm

Type: `string`

Initializes or adjusts the active search term. Set to an empty string or `undefined` to turn search mode off.

### clearSearchOnChange

Type: `bool`
Expand Down Expand Up @@ -256,6 +266,24 @@ Type: `function`

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.

### onSearchChange

Type: `function`

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:

```jsx
function onSearchChange(searchTerm: str) {
if (searchTerm) {
console.log('New search term is', searchTerm)
} else {
console.log('Search mode has been disabled')
}
}

return <DropdownTreeSelect data={data} onSearchChange={onSearchChange} />
```

### data

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

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

### pageSize

Type: `number` (default: `100`)

Customize the number of nodes displayed in the tree before a scroll to near the bottom is required to load additional nodes.

### showPartiallySelected

Type: `bool` (default: `false`)
Expand Down Expand Up @@ -428,6 +462,12 @@ Type: `bool` (default: `false`)

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

### disableKeyboardNavigation

Type: `bool` (default: `false`)

`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.

## Styling and Customization

### Default styles
Expand Down
31 changes: 31 additions & 0 deletions docs/src/stories/Options/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@ class WithOptions extends PureComponent {
super(props)

this.state = {
searchTerm: '',
clearSearchOnChange: false,
keepTreeOnSearch: false,
keepOpenOnSelect: false,
mode: 'multiSelect',
pageSize: undefined,
inlineSearchInput: false,
showPartiallySelected: false,
disabled: false,
Expand All @@ -39,12 +41,18 @@ class WithOptions extends PureComponent {
this.setState({ [value]: !this.state[value] })
}

onSearchChange = searchTerm => {
this.setState({ searchTerm: searchTerm })
}

render() {
const {
searchTerm,
clearSearchOnChange,
keepTreeOnSearch,
keepOpenOnSelect,
mode,
pageSize,
showPartiallySelected,
disabled,
readOnly,
Expand Down Expand Up @@ -105,6 +113,26 @@ class WithOptions extends PureComponent {
onChange={e => this.setState({ inlineSearchPlaceholder: e.target.value })}
/>
</div>
<div style={{ marginBottom: '10px' }}>
<label htmlFor="searchTerm">Search term: </label>
<input
id="searchTerm"
type="text"
value={searchTerm}
onChange={e => this.setState({ searchTerm: e.target.value })}
/>
</div>
<div style={{ marginBottom: '10px' }}>
<label htmlFor="pageSize">Page size ({pageSize || 100}): </label>
<input
id="pageSize"
type="range"
min={50}
max={1000}
value={pageSize || 100}
onChange={e => this.setState({ pageSize: e.target.valueAsNumber || undefined })}
/>
</div>
<Checkbox
label="Inline Search Input"
value="inlineSearchInput"
Expand Down Expand Up @@ -142,13 +170,16 @@ class WithOptions extends PureComponent {
<DropdownTreeSelect
id="rdts"
data={data}
searchTerm={searchTerm}
onChange={this.onChange}
onAction={this.onAction}
onNodeToggle={this.onNodeToggle}
onSearchChange={this.onSearchChange}
clearSearchOnChange={clearSearchOnChange}
keepTreeOnSearch={keepTreeOnSearch}
keepOpenOnSelect={keepOpenOnSelect}
mode={mode}
pageSize={pageSize}
showPartiallySelected={showPartiallySelected}
disabled={disabled}
readOnly={readOnly}
Expand Down
5 changes: 5 additions & 0 deletions docs/src/stories/Simple/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ const onBlur = () => {
console.log('onBlur')
}

const onSearchChange = searchTerm => {
console.log('onSearchChange::', searchTerm)
}

const Simple = () => (
<div>
<h1>Basic component</h1>
Expand All @@ -45,6 +49,7 @@ const Simple = () => (
onNodeToggle={onNodeToggle}
onFocus={onFocus}
onBlur={onBlur}
onSearchChange={onSearchChange}
className="demo"
/>
</div>
Expand Down
24 changes: 22 additions & 2 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { getAriaLabel } from './a11y'
class DropdownTreeSelect extends Component {
static propTypes = {
data: PropTypes.oneOfType([PropTypes.object, PropTypes.array]).isRequired,
searchTerm: PropTypes.string,
clearSearchOnChange: PropTypes.bool,
keepTreeOnSearch: PropTypes.bool,
keepChildrenOnSearch: PropTypes.bool,
Expand All @@ -41,7 +42,9 @@ class DropdownTreeSelect extends Component {
onNodeToggle: PropTypes.func,
onFocus: PropTypes.func,
onBlur: PropTypes.func,
onSearchChange: PropTypes.func,
mode: PropTypes.oneOf(['multiSelect', 'simpleSelect', 'radioSelect', 'hierarchical']),
pageSize: PropTypes.number,
showPartiallySelected: PropTypes.bool,
disabled: PropTypes.bool,
readOnly: PropTypes.bool,
Expand All @@ -50,18 +53,21 @@ class DropdownTreeSelect extends Component {
inlineSearchInput: PropTypes.bool,
tabIndex: PropTypes.number,
disablePoppingOnBackspace: PropTypes.bool,
disableKeyboardNavigation: PropTypes.bool,
}

static defaultProps = {
onAction: () => {},
onFocus: () => {},
onBlur: () => {},
onChange: () => {},
onSearchChange: _ => {},
texts: {},
showDropdown: 'default',
inlineSearchInput: false,
tabIndex: 0,
disablePoppingOnBackspace: false,
disableKeyboardNavigation: true,
}

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

initNewProps = ({ data, mode, showDropdown, showPartiallySelected, searchPredicate }) => {
initNewProps = ({ data, searchTerm, mode, showDropdown, showPartiallySelected, searchPredicate }) => {
this.treeManager = new TreeManager({
data,
mode,
Expand All @@ -86,7 +92,12 @@ class DropdownTreeSelect extends Component {
if (currentFocusNode) {
currentFocusNode._focused = true
}
const searchTermChanged = searchTerm !== prevState.searchTerm
if (this.searchInput && searchTermChanged) {
this.searchInput.value = searchTerm
}
return {
searchModeOn: searchTermChanged,
showDropdown: /initial|always/.test(showDropdown) || prevState.showDropdown === true,
...this.treeManager.getTreeAndTags(),
}
Expand All @@ -96,7 +107,10 @@ class DropdownTreeSelect extends Component {
resetSearchState = () => {
// clear the search criteria and avoid react controlled/uncontrolled warning
if (this.searchInput) {
this.searchInput.value = ''
if (this.searchInput.value !== '') {
this.props.onSearchChange(this.searchInput.value)
this.searchInput.value = ''
}
}

return {
Expand Down Expand Up @@ -154,6 +168,7 @@ class DropdownTreeSelect extends Component {
this.props.keepChildrenOnSearch
)
const searchModeOn = value.length > 0
this.props.onSearchChange(value)

this.setState({
tree,
Expand Down Expand Up @@ -238,6 +253,10 @@ class DropdownTreeSelect extends Component {
}

onKeyboardKeyDown = e => {
if (this.props.disableKeyboardNavigation) {
return // Will fire the default action
}

const { readOnly, mode, disablePoppingOnBackspace } = this.props
const { showDropdown, tags, searchModeOn, currentFocus } = this.state
const tm = this.treeManager
Expand Down Expand Up @@ -360,6 +379,7 @@ class DropdownTreeSelect extends Component {
onCheckboxChange={this.onCheckboxChange}
onNodeToggle={this.onNodeToggle}
mode={mode}
pageSize={this.props.pageSize}
showPartiallySelected={this.props.showPartiallySelected}
{...commonProps}
/>
Expand Down
9 changes: 9 additions & 0 deletions types/react-dropdown-tree-select.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ declare module 'react-dropdown-tree-select' {

export interface DropdownTreeSelectProps {
data: TreeData
/** Initialize the search input with the specified term and search the nodes */
searchTerm?: str
/** Clear the input search if a node has been selected/unselected */
clearSearchOnChange?: boolean
/** Displays search results as a tree instead of flattened results */
Expand Down Expand Up @@ -54,6 +56,9 @@ declare module 'react-dropdown-tree-select' {
* This is helpful for setting dirty or touched flags with forms
*/
onBlur?: () => void
/** Fires when search input is modified.
*/
onSearchChange?: (searchTerm: str) => void
/** Defines how the dropdown is rendered / behaves
*
* - multiSelect
Expand All @@ -79,6 +84,8 @@ declare module 'react-dropdown-tree-select' {
*
* */
mode?: Mode
/** The size (in nodes) of a single page in the infinite scroll component. */
pageSize?: number
/** 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
Expand All @@ -103,6 +110,8 @@ declare module 'react-dropdown-tree-select' {
* search bar, the tree will not deselect nodes.
*/
disablePoppingOnBackspace?: boolean
/** dsiableKeyboardNavigation will disable keyboard navigation of the tree */
dsiableKeyboardNavigation?: boolean
}

export interface DropdownTreeSelectState {
Expand Down

0 comments on commit 29d1fd7

Please sign in to comment.