-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(TreeSelect): add tree select component
- Loading branch information
Showing
25 changed files
with
747 additions
and
12 deletions.
There are no files selected for viewing
Binary file added
BIN
+5.98 KB
...hots__/tree-select-field-test-js-components-tree-select-field-common-1-snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+26.2 KB
..._image_snapshots__/tree-select-test-js-components-tree-select-common-1-snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+25.3 KB
...e_snapshots__/tree-select-test-js-components-tree-select-common-open-1-snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+26 KB
...hots__/tree-select-test-js-components-tree-select-common-remove-item-1-snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+26.3 KB
...shots__/tree-select-test-js-components-tree-select-common-select-all-1-snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+27.7 KB
...hots__/tree-select-test-js-components-tree-select-common-select-item-1-snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+27.6 KB
...ts__/tree-select-test-js-components-tree-select-common-unselect-item-1-snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { baisy } from '../setup/TestSuiter'; | ||
|
||
|
||
const SUITES = [ | ||
baisy.suite('Components/TreeSelectField', 'common'), | ||
]; | ||
|
||
|
||
SUITES.map(suite => { | ||
it(suite.getTestName(), suite.testStory, 20000); | ||
}); | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
import { baisy } from '../setup/TestSuiter'; | ||
|
||
|
||
const expandAllTree = async(iframe) => { | ||
const selectTrigger = await iframe.waitForXPath('(//*[contains(@class,"dropdown-trigger")])[2]'); | ||
await selectTrigger.click(); | ||
|
||
const firstToggle = await iframe.waitForXPath('//*[contains(@class,"dropdown-content")]//*[contains(@class,"toggle collapsed")]'); | ||
await firstToggle.click(); | ||
|
||
const secondToggle = await iframe.waitForXPath('//*[contains(@class,"dropdown-content")]//*[contains(@class,"toggle collapsed")]'); | ||
await secondToggle.click(); | ||
}; | ||
|
||
|
||
const SUITES = [ | ||
baisy.suite('Components/TreeSelect', 'common'), | ||
baisy.suite('Components/TreeSelect', 'common', 'open') | ||
.addRootHeight(500) | ||
.setEnhancer(async (iframe) => { | ||
const selectTrigger = await iframe.waitForXPath('(//*[contains(@class,"dropdown-trigger")])[2]'); | ||
|
||
await selectTrigger.click(); | ||
|
||
const firstToggle = await iframe.waitForXPath('//*[contains(@class,"dropdown-content")]//*[contains(@class,"toggle collapsed")]'); | ||
await firstToggle.click(); | ||
}), | ||
|
||
baisy.suite('Components/TreeSelect', 'common', 'select item') | ||
.addRootHeight(500) | ||
.setEnhancer(async (iframe) => { | ||
await expandAllTree(iframe); | ||
|
||
const lastCheckbox = await iframe.waitForXPath('//*[contains(@class,"dropdown-content")]//*[contains(text(),"Search me too 5")]'); | ||
await lastCheckbox.click(); | ||
}), | ||
|
||
baisy.suite('Components/TreeSelect', 'common', 'select all') | ||
.addRootHeight(500) | ||
.setEnhancer(async (iframe) => { | ||
await expandAllTree(iframe); | ||
|
||
const firstCheckbox = await iframe.waitForXPath('//*[contains(@class,"dropdown-content")]//*[contains(text(),"Search me")]'); | ||
await firstCheckbox.click(); | ||
}), | ||
|
||
baisy.suite('Components/TreeSelect', 'common', 'unselect item') | ||
.addRootHeight(500) | ||
.setEnhancer(async (iframe) => { | ||
await expandAllTree(iframe); | ||
|
||
const firstCheckbox = await iframe.waitForXPath('//*[contains(@class,"dropdown-content")]//*[contains(text(),"Search me")]'); | ||
await firstCheckbox.click(); | ||
|
||
const innerCheckbox = await iframe.waitForXPath('//*[contains(@class,"dropdown-content")]//*[contains(text(),"No one can get me")]'); | ||
await innerCheckbox.click(); | ||
}), | ||
|
||
baisy.suite('Components/TreeSelect', 'common', 'remove item') | ||
.addRootHeight(500) | ||
.setEnhancer(async (iframe) => { | ||
await expandAllTree(iframe); | ||
|
||
const removeButton = await iframe.waitForXPath('//*[contains(@class,"tag-item")]//button'); | ||
await removeButton.click(); | ||
}), | ||
]; | ||
|
||
|
||
SUITES.map(suite => { | ||
it(suite.getTestName(), suite.testStory, 20000); | ||
}); | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
// @flow | ||
import React from 'react'; | ||
import fp from 'lodash/fp'; | ||
import DropdownTreeSelect from 'react-dropdown-tree-select'; | ||
|
||
import { TreeSelectWrapperTag, WRAPPER_CLASS_NAME } from './TreeSelect.theme'; | ||
|
||
type TreeData = { | ||
label: string, | ||
value: string, | ||
checked?: boolean, | ||
children: Array<TreeData>, | ||
} | ||
|
||
type TreeNode = { | ||
label: string, | ||
value: string, | ||
checked?: boolean, | ||
_id?: string, | ||
} | ||
|
||
|
||
type ToggledNode = { | ||
label: string, | ||
value: string, | ||
expanded: boolean, | ||
_id: string, | ||
} | ||
|
||
type TreeSelectProps = { | ||
data: TreeData, | ||
value: TreeNode[], | ||
onChange: (currentNode: TreeNode, selectedNodes: TreeNode[]) => void, | ||
onNodeToggle?: (ToggledNode) => void, | ||
stretch: boolean, | ||
placeholder?: string, | ||
} | ||
|
||
|
||
const getDeepKeys = (obj: Object) => { | ||
let keys = []; | ||
for (const key in obj) { | ||
keys.push(key); | ||
if (typeof obj[key] === 'object') { | ||
const subkeys = getDeepKeys(obj[key]); | ||
keys = keys.concat(subkeys.map((subkey) => { | ||
return `${key}.${subkey}`; | ||
})); | ||
} | ||
} | ||
return keys; | ||
}; | ||
|
||
|
||
const getNodePath = (treeData: TreeData, value: string): null | string[] => { | ||
const treePaths = getDeepKeys(treeData).map(key => key.split('.')); | ||
|
||
let nodePath = null; | ||
|
||
[[], ...treePaths].forEach(path => { | ||
const node = fp.equals(path, []) | ||
? treeData | ||
: fp.get(path, treeData); | ||
|
||
if (node.value === value) { | ||
nodePath = path; | ||
} | ||
}); | ||
|
||
return nodePath; | ||
}; | ||
|
||
class TreeSelect extends React.PureComponent<TreeSelectProps> { | ||
toggledNodes: { [string]: ToggledNode } = {}; | ||
|
||
static defaultProps = { | ||
stretch: true, | ||
value: [], | ||
placeholder: 'Select...', | ||
} | ||
|
||
|
||
getSelectedData = fp.memoize( | ||
({ value, toggledNodes, data }: *) => { | ||
|
||
const selectedData = value | ||
.reduce( | ||
(accum, node: TreeNode) => { | ||
const nodePath = getNodePath(data, node.value); | ||
|
||
if (nodePath === null) return accum; | ||
|
||
return fp.pipe( | ||
fp.set([...nodePath, 'checked'], true), | ||
)(accum); | ||
}, | ||
data, | ||
); | ||
|
||
const expandedData = Object.keys(toggledNodes).reduce( | ||
(accum, key: string) => { | ||
const node = toggledNodes[key]; | ||
const nodePath = getNodePath(data, node.value); | ||
|
||
if (nodePath === null) return accum; | ||
return fp.set([...nodePath, 'expanded'], node.expanded, accum); | ||
}, | ||
selectedData, | ||
); | ||
|
||
return expandedData; | ||
}, | ||
) | ||
|
||
onNodeToggle = (currentNode: ToggledNode) => { | ||
const { onNodeToggle } = this.props; | ||
|
||
onNodeToggle && onNodeToggle(currentNode); | ||
|
||
this.toggledNodes = { | ||
...this.toggledNodes, | ||
[currentNode._id]: currentNode, | ||
}; | ||
} | ||
|
||
render () { | ||
const { stretch, value, data, placeholder, ...rest } = this.props; | ||
|
||
const selectedData = this.getSelectedData({ value, data, toggledNodes: this.toggledNodes }); | ||
|
||
return ( | ||
<TreeSelectWrapperTag tagName="div" stretch={ stretch }> | ||
<DropdownTreeSelect | ||
{ ...rest } | ||
data={ selectedData } | ||
className={ WRAPPER_CLASS_NAME } | ||
onNodeToggle={ this.onNodeToggle } | ||
placeholderText={ placeholder } | ||
/> | ||
</TreeSelectWrapperTag> | ||
); | ||
} | ||
} | ||
|
||
|
||
export { | ||
TreeSelect, | ||
}; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
import React from 'react'; | ||
|
||
const OPTIONS = { | ||
label: 'Search me', | ||
value: 'searchme', | ||
children: [ | ||
{ | ||
label: 'Search me too', | ||
value: 'searchmetoo', | ||
children: [ | ||
{ | ||
label: 'No one can get me', | ||
value: 'anonymous', | ||
}, | ||
], | ||
}, | ||
{ | ||
label: 'Search me too 2', | ||
value: 'searchmetoo2', | ||
}, | ||
{ | ||
label: 'Search me too 3', | ||
value: 'searchmetoo3', | ||
}, | ||
{ | ||
label: 'Search me too 4', | ||
value: 'searchmetoo4', | ||
}, | ||
{ | ||
label: 'Search me too 5', | ||
value: 'searchmetoo5', | ||
}, | ||
], | ||
}; | ||
|
||
|
||
const LONG_OPTIONS = [{ | ||
label: 'Geoglossaceae nontransportation laemodipodiform gluttonously spaework ankylorrhinia zain carlet ironheartedness geoglossaceae nontransportation laemodipodiform gluttonously spaework ankylorrhinia zain carlet ironheartedness topia antiorthodox cerebropedal Sothis whispered basilica idealizer outvalue thwacking unafraid coining nak friskily renishly stringsman', | ||
value: 'ovenlike', | ||
}, { | ||
label: 'backhander unpersecuted platch antisymmetrical fumaroid chromitite Microthelyphonida epigraphically myope supramechanical pageant ankle camphory nitronaphthalene thieve umquhile mornings gynomonoecism unvulgarize rickmatic saltless sternoglossal pungi pronumber', | ||
value: 'serjeant', | ||
}, { | ||
label: 'juju tattlery nonperpetual nonexternal vocabularied umber lichenological repressure unpoled blepharosynechia peragration reduplicature acarid citizenism nongelatinizing splenoptosia unpoisoned tympanic tachogram unhardness dovetail transonic cuinage tributariness', | ||
value: 'wiseheartedly', | ||
}]; | ||
|
||
|
||
export default (asStory) => { | ||
asStory('Components/TreeSelect', module, (story, { Column, StateContainer, TreeSelect }) => { | ||
story | ||
.add('common', () => ( | ||
<Column> | ||
<StateContainer value={ [] }> | ||
{ ({ value, onChange }) => ( | ||
<TreeSelect | ||
value={ value } | ||
data={ OPTIONS } | ||
onChange={ (_, selectedNodes) => onChange(selectedNodes) } | ||
/> | ||
) } | ||
</StateContainer> | ||
<StateContainer value={ [OPTIONS.children[1], OPTIONS.children[2], OPTIONS.children[0].children[0]] }> | ||
{ ({ value, onChange }) => ( | ||
<TreeSelect | ||
value={ value } | ||
data={ OPTIONS } | ||
onChange={ (_, selectedNodes) => onChange(selectedNodes) } | ||
/> | ||
) } | ||
</StateContainer> | ||
<StateContainer value={ [LONG_OPTIONS[0], LONG_OPTIONS[2]] }> | ||
{ ({ value, onChange }) => ( | ||
<TreeSelect | ||
value={ value } | ||
data={ LONG_OPTIONS } | ||
onChange={ (_, selectedNodes) => onChange(selectedNodes) } | ||
/> | ||
) } | ||
</StateContainer> | ||
</Column> | ||
)); | ||
}); | ||
}; |
Oops, something went wrong.