Skip to content

Commit

Permalink
feat(dropdown): Ability to show hierarchical search results (#46) ✨
Browse files Browse the repository at this point in the history
* fix: remove usage of !important to remove the padding of nodes during search to avoid cascading !important in css, it's better to not use it at first.

* feat: add keepTreeOnSearch prop to display results as a tree instead of flatten them
  • Loading branch information
Gregcop1 authored and mrchief committed Feb 15, 2018
1 parent 55e7036 commit 92376d5
Show file tree
Hide file tree
Showing 8 changed files with 55 additions and 11 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,13 @@ Type: `string`

The text to display as placeholder on the search box. Defaults to `Choose...`

### keepTreeOnSearch


Type: `bool`

Displays search results as a tree instead of flatten results

## Styling and Customization

### Default styles
Expand Down
2 changes: 1 addition & 1 deletion src/index.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* component styles */
.hide {
.hide:not(.match-in-children) {
display: none;
}

Expand Down
2 changes: 2 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const cx = cn.bind(styles)
class DropdownTreeSelect extends Component {
static propTypes = {
data: PropTypes.oneOfType([PropTypes.object, PropTypes.array]).isRequired,
keepTreeOnSearch: PropTypes.bool,
placeholderText: PropTypes.string,
showDropdown: PropTypes.bool,
className: PropTypes.string,
Expand Down Expand Up @@ -163,6 +164,7 @@ class DropdownTreeSelect extends Component {
) : (
<Tree
data={this.state.tree}
keepTreeOnSearch={this.props.keepTreeOnSearch}
searchModeOn={this.state.searchModeOn}
onAction={this.onAction}
onCheckboxChange={this.onCheckboxChange}
Expand Down
10 changes: 10 additions & 0 deletions src/tree-manager/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,16 +49,26 @@ class TreeManager {
return matches
}

setChildMatchStatus (id) {
if (id !== undefined) {
const node = this.getNodeById(id)
node.matchInChildren = true
this.setChildMatchStatus(node._parent)
}
}

filterTree (searchTerm) {
const matches = this.getMatches(searchTerm.toLowerCase())

this.tree.forEach(node => {
node.hide = true
node.matchInChildren = false
})

matches.forEach(m => {
const node = this.getNodeById(m)
node.hide = false
this.setChildMatchStatus(node._parent)
})

const allNodesHidden = matches.length === 0
Expand Down
8 changes: 4 additions & 4 deletions src/tree-node/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,6 @@
padding: 4px;
}

.searchModeOn li.node {
padding-left: 0 !important;
}

.toggle {
white-space: pre;
margin-right: 4px;
Expand Down Expand Up @@ -39,3 +35,7 @@
vertical-align: middle;
margin: 0 4px 0 0;
}

.match-in-children.hide .node-label {
opacity: 0.5;
}
13 changes: 8 additions & 5 deletions src/tree-node/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@ import styles from './index.css'
const cx = cn.bind(styles)

const TreeNode = props => {
const { node, onNodeToggle, onCheckboxChange, onAction } = props
const actions = node.actions || []
const { keepTreeOnSearch, node, searchModeOn, onNodeToggle, onCheckboxChange, onAction } = props
const isLeaf = isEmpty(node._children)
const liCx = cx('node', { leaf: isLeaf, tree: !isLeaf, hide: node.hide }, node.className)
const hasMatchInChildren = keepTreeOnSearch && node.matchInChildren
const nodeCx = { leaf: isLeaf, tree: !isLeaf, hide: node.hide, 'match-in-children': hasMatchInChildren }
const liCx = cx('node', nodeCx, node.className)
const toggleCx = cx('toggle', { expanded: !isLeaf && node.expanded, collapsed: !isLeaf && !node.expanded })

return (
<li className={liCx} style={{ paddingLeft: `${node._depth * 20}px` }}>
<li className={liCx} style={keepTreeOnSearch || !searchModeOn ? { paddingLeft: `${node._depth * 20}px` } : {}}>
<i className={toggleCx} onClick={() => onNodeToggle(node._id)} />
<label title={node.title || node.label}>
<input
Expand All @@ -28,7 +29,7 @@ const TreeNode = props => {
/>
<span className="node-label">{node.label}</span>
</label>
{actions.map((a, idx) => (
{(node.actions || []).map((a, idx) => (
<Action key={`action-${idx}`} {...a} actionData={{ action: a.id, node }} onAction={onAction} />
))}
</li>
Expand All @@ -47,6 +48,8 @@ TreeNode.propTypes = {
checked: PropTypes.bool,
expanded: PropTypes.bool
}).isRequired,
keepTreeOnSearch: PropTypes.bool,
searchModeOn: PropTypes.bool,
onNodeToggle: PropTypes.func,
onAction: PropTypes.func,
onCheckboxChange: PropTypes.func
Expand Down
19 changes: 19 additions & 0 deletions src/tree-node/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import { shallow } from 'enzyme'
import { spy } from 'sinon'
import TreeNode from './index'

const hasGap = (wrapper) => {
return !!wrapper.find('li').first().props().style.paddingLeft
}

test('renders tree node', t => {
const node = {
_id: '0-0-0',
Expand All @@ -26,6 +30,7 @@ test('renders tree node', t => {
t.true(wrapper.find('.toggle').exists())
t.true(wrapper.find('label').exists())
t.true(wrapper.find('.checkbox-item').exists())
t.true(hasGap(wrapper))
})

test('notifies checkbox changes', t => {
Expand Down Expand Up @@ -60,3 +65,17 @@ test('notifies node toggle changes', t => {
wrapper.find('.toggle').simulate('click')
t.true(onChange.calledWith('0-0-0'))
})

test('remove gap during search', t => {
const node = {
_id: '0-0-0',
_parent: '0-0',
label: 'item1-1-1',
value: 'value1-1-1',
className: 'cn0-0-0'
}

const wrapper = shallow(<TreeNode node={node} searchModeOn={true} />)

t.false(hasGap(wrapper))
})
5 changes: 4 additions & 1 deletion src/tree/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,16 @@ const shouldRenderNode = (node, searchModeOn, data) => {
}

const getNodes = props => {
const { searchModeOn, data, onAction, onChange, onCheckboxChange, onNodeToggle } = props
const { data, keepTreeOnSearch, searchModeOn, onAction, onChange, onCheckboxChange, onNodeToggle } = props
const items = []
data.forEach((node, key) => {
if (shouldRenderNode(node, searchModeOn, data)) {
items.push(
<TreeNode
keepTreeOnSearch={keepTreeOnSearch}
key={key}
node={node}
searchModeOn={searchModeOn}
onChange={onChange}
onCheckboxChange={onCheckboxChange}
onNodeToggle={onNodeToggle}
Expand All @@ -39,6 +41,7 @@ const Tree = props => {

Tree.propTypes = {
data: PropTypes.object,
keepTreeOnSearch: PropTypes.bool,
searchModeOn: PropTypes.bool,
onChange: PropTypes.func,
onNodeToggle: PropTypes.func,
Expand Down

0 comments on commit 92376d5

Please sign in to comment.