Skip to content

Commit 92376d5

Browse files
Gregcop1mrchief
authored andcommitted
feat(dropdown): Ability to show hierarchical search results (#46) ✨
* 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
1 parent 55e7036 commit 92376d5

File tree

8 files changed

+55
-11
lines changed

8 files changed

+55
-11
lines changed

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,13 @@ Type: `string`
215215

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

218+
### keepTreeOnSearch
219+
220+
221+
Type: `bool`
222+
223+
Displays search results as a tree instead of flatten results
224+
218225
## Styling and Customization
219226

220227
### Default styles

src/index.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* component styles */
2-
.hide {
2+
.hide:not(.match-in-children) {
33
display: none;
44
}
55

src/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ const cx = cn.bind(styles)
1919
class DropdownTreeSelect extends Component {
2020
static propTypes = {
2121
data: PropTypes.oneOfType([PropTypes.object, PropTypes.array]).isRequired,
22+
keepTreeOnSearch: PropTypes.bool,
2223
placeholderText: PropTypes.string,
2324
showDropdown: PropTypes.bool,
2425
className: PropTypes.string,
@@ -163,6 +164,7 @@ class DropdownTreeSelect extends Component {
163164
) : (
164165
<Tree
165166
data={this.state.tree}
167+
keepTreeOnSearch={this.props.keepTreeOnSearch}
166168
searchModeOn={this.state.searchModeOn}
167169
onAction={this.onAction}
168170
onCheckboxChange={this.onCheckboxChange}

src/tree-manager/index.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,16 +49,26 @@ class TreeManager {
4949
return matches
5050
}
5151

52+
setChildMatchStatus (id) {
53+
if (id !== undefined) {
54+
const node = this.getNodeById(id)
55+
node.matchInChildren = true
56+
this.setChildMatchStatus(node._parent)
57+
}
58+
}
59+
5260
filterTree (searchTerm) {
5361
const matches = this.getMatches(searchTerm.toLowerCase())
5462

5563
this.tree.forEach(node => {
5664
node.hide = true
65+
node.matchInChildren = false
5766
})
5867

5968
matches.forEach(m => {
6069
const node = this.getNodeById(m)
6170
node.hide = false
71+
this.setChildMatchStatus(node._parent)
6272
})
6373

6474
const allNodesHidden = matches.length === 0

src/tree-node/index.css

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,6 @@
44
padding: 4px;
55
}
66

7-
.searchModeOn li.node {
8-
padding-left: 0 !important;
9-
}
10-
117
.toggle {
128
white-space: pre;
139
margin-right: 4px;
@@ -39,3 +35,7 @@
3935
vertical-align: middle;
4036
margin: 0 4px 0 0;
4137
}
38+
39+
.match-in-children.hide .node-label {
40+
opacity: 0.5;
41+
}

src/tree-node/index.js

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,15 @@ import styles from './index.css'
88
const cx = cn.bind(styles)
99

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

1718
return (
18-
<li className={liCx} style={{ paddingLeft: `${node._depth * 20}px` }}>
19+
<li className={liCx} style={keepTreeOnSearch || !searchModeOn ? { paddingLeft: `${node._depth * 20}px` } : {}}>
1920
<i className={toggleCx} onClick={() => onNodeToggle(node._id)} />
2021
<label title={node.title || node.label}>
2122
<input
@@ -28,7 +29,7 @@ const TreeNode = props => {
2829
/>
2930
<span className="node-label">{node.label}</span>
3031
</label>
31-
{actions.map((a, idx) => (
32+
{(node.actions || []).map((a, idx) => (
3233
<Action key={`action-${idx}`} {...a} actionData={{ action: a.id, node }} onAction={onAction} />
3334
))}
3435
</li>
@@ -47,6 +48,8 @@ TreeNode.propTypes = {
4748
checked: PropTypes.bool,
4849
expanded: PropTypes.bool
4950
}).isRequired,
51+
keepTreeOnSearch: PropTypes.bool,
52+
searchModeOn: PropTypes.bool,
5053
onNodeToggle: PropTypes.func,
5154
onAction: PropTypes.func,
5255
onCheckboxChange: PropTypes.func

src/tree-node/index.test.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ import { shallow } from 'enzyme'
44
import { spy } from 'sinon'
55
import TreeNode from './index'
66

7+
const hasGap = (wrapper) => {
8+
return !!wrapper.find('li').first().props().style.paddingLeft
9+
}
10+
711
test('renders tree node', t => {
812
const node = {
913
_id: '0-0-0',
@@ -26,6 +30,7 @@ test('renders tree node', t => {
2630
t.true(wrapper.find('.toggle').exists())
2731
t.true(wrapper.find('label').exists())
2832
t.true(wrapper.find('.checkbox-item').exists())
33+
t.true(hasGap(wrapper))
2934
})
3035

3136
test('notifies checkbox changes', t => {
@@ -60,3 +65,17 @@ test('notifies node toggle changes', t => {
6065
wrapper.find('.toggle').simulate('click')
6166
t.true(onChange.calledWith('0-0-0'))
6267
})
68+
69+
test('remove gap during search', t => {
70+
const node = {
71+
_id: '0-0-0',
72+
_parent: '0-0',
73+
label: 'item1-1-1',
74+
value: 'value1-1-1',
75+
className: 'cn0-0-0'
76+
}
77+
78+
const wrapper = shallow(<TreeNode node={node} searchModeOn={true} />)
79+
80+
t.false(hasGap(wrapper))
81+
})

src/tree/index.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,16 @@ const shouldRenderNode = (node, searchModeOn, data) => {
1212
}
1313

1414
const getNodes = props => {
15-
const { searchModeOn, data, onAction, onChange, onCheckboxChange, onNodeToggle } = props
15+
const { data, keepTreeOnSearch, searchModeOn, onAction, onChange, onCheckboxChange, onNodeToggle } = props
1616
const items = []
1717
data.forEach((node, key) => {
1818
if (shouldRenderNode(node, searchModeOn, data)) {
1919
items.push(
2020
<TreeNode
21+
keepTreeOnSearch={keepTreeOnSearch}
2122
key={key}
2223
node={node}
24+
searchModeOn={searchModeOn}
2325
onChange={onChange}
2426
onCheckboxChange={onCheckboxChange}
2527
onNodeToggle={onNodeToggle}
@@ -39,6 +41,7 @@ const Tree = props => {
3941

4042
Tree.propTypes = {
4143
data: PropTypes.object,
44+
keepTreeOnSearch: PropTypes.bool,
4245
searchModeOn: PropTypes.bool,
4346
onChange: PropTypes.func,
4447
onNodeToggle: PropTypes.func,

0 commit comments

Comments
 (0)