Skip to content

Commit 2cd371c

Browse files
committedAug 4, 2017
feat: Display droppable placeholder element when tree is empty
BREAKING CHANGES: Trees that are empty now display a placeholder element in their place instead of being simply empty.
1 parent 561ef88 commit 2cd371c

7 files changed

+169
-2
lines changed
 

Diff for: ‎README.md

+1
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ slideRegionSize | number | `100` | | Si
6363
scaffoldBlockPxWidth | number | `44` | | The width of the blocks containing the lines representing the structure of the tree.
6464
isVirtualized | bool | `true` | | Set to false to disable virtualization. __NOTE__: Auto-scrolling while dragging, and scrolling to the `searchFocusOffset` will be disabled.
6565
nodeContentRenderer | any | NodeRendererDefault | | Override the default component for rendering nodes (but keep the scaffolding generator) This is an advanced option for complete customization of the appearance. It is best to copy the component in `node-renderer-default.js` to use as a base, and customize as needed.
66+
placeholderRenderer | any | PlaceholderRendererDefault | | Override the default placeholder component which is displayed when the tree is empty. This is an advanced option, and in most cases should probably be solved with custom CSS instead.
6667

6768
## Data Helper Functions
6869

Diff for: ‎examples/storybooks/tree-to-tree.js

-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import React, { Component } from 'react';
22
import SortableTree from '../../src';
33

4-
54
class App extends Component {
65
constructor(props) {
76
super(props);

Diff for: ‎src/placeholder-renderer-default.js

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
import styles from './placeholder-renderer-default.scss';
4+
5+
const PlaceholderRendererDefault = ({ isOver, canDrop }) =>
6+
<div
7+
className={
8+
styles.placeholder +
9+
(canDrop ? ` ${styles.placeholderLandingPad}` : '') +
10+
(canDrop && !isOver ? ` ${styles.placeholderCancelPad}` : '')
11+
}
12+
/>;
13+
14+
PlaceholderRendererDefault.defaultProps = {
15+
isOver: false,
16+
canDrop: false,
17+
};
18+
19+
PlaceholderRendererDefault.propTypes = {
20+
isOver: PropTypes.bool,
21+
canDrop: PropTypes.bool,
22+
};
23+
24+
export default PlaceholderRendererDefault;

Diff for: ‎src/placeholder-renderer-default.scss

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
.placeholder {
2+
position: relative;
3+
height: 68px;
4+
max-width: 300px;
5+
padding: 10px;
6+
7+
&,
8+
& > * {
9+
box-sizing: border-box;
10+
}
11+
12+
&::before {
13+
border: 3px dashed #d9d9d9;
14+
content: '';
15+
position: absolute;
16+
top: 5px;
17+
right: 5px;
18+
bottom: 5px;
19+
left: 5px;
20+
z-index: -1;
21+
}
22+
}
23+
24+
/**
25+
* The outline of where the element will go if dropped, displayed while dragging
26+
*/
27+
.placeholderLandingPad {
28+
border: none !important;
29+
box-shadow: none !important;
30+
outline: none !important;
31+
32+
* {
33+
opacity: 0 !important;
34+
}
35+
36+
&::before {
37+
background-color: lightblue;
38+
border-color: white;
39+
}
40+
}
41+
42+
/**
43+
* Alternate appearance of the landing pad when the dragged location is invalid
44+
*/
45+
.placeholderCancelPad {
46+
@extend .placeholderLandingPad;
47+
48+
&::before {
49+
background-color: #e6a8ad;
50+
}
51+
}

Diff for: ‎src/react-sortable-tree.js

+23-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import withScrolling, {
1515
import 'react-virtualized/styles.css';
1616
import TreeNode from './tree-node';
1717
import NodeRendererDefault from './node-renderer-default';
18+
import TreePlaceholder from './tree-placeholder';
19+
import PlaceholderRendererDefault from './placeholder-renderer-default';
1820
import {
1921
walk,
2022
getFlatDataFromTree,
@@ -34,6 +36,7 @@ import {
3436
dndWrapRoot,
3537
dndWrapSource,
3638
dndWrapTarget,
39+
dndWrapPlaceholder,
3740
} from './utils/drag-and-drop-utils';
3841
import styles from './react-sortable-tree.scss';
3942

@@ -56,6 +59,10 @@ class ReactSortableTree extends Component {
5659
treeIdCounter += 1;
5760
this.dndType = dndType || this.treeId;
5861
this.nodeContentRenderer = dndWrapSource(nodeContentRenderer, this.dndType);
62+
this.treePlaceholderRenderer = dndWrapPlaceholder(
63+
TreePlaceholder,
64+
this.dndType
65+
);
5966
this.treeNodeRenderer = dndWrapTarget(TreeNode, this.dndType);
6067

6168
// Prepare scroll-on-drag options for this list
@@ -492,7 +499,15 @@ class ReactSortableTree extends Component {
492499

493500
let containerStyle = style;
494501
let list;
495-
if (isVirtualized) {
502+
if (rows.length < 1) {
503+
const Placeholder = this.treePlaceholderRenderer;
504+
const PlaceholderContent = this.props.placeholderRenderer;
505+
list = (
506+
<Placeholder treeId={this.treeId} drop={this.drop}>
507+
<PlaceholderContent />
508+
</Placeholder>
509+
);
510+
} else if (isVirtualized) {
496511
containerStyle = { height: '100%', ...containerStyle };
497512

498513
const ScrollZoneVirtualList = this.scrollZoneVirtualList;
@@ -625,6 +640,12 @@ ReactSortableTree.propTypes = {
625640
// It is best to copy the component in `node-renderer-default.js` to use as a base, and customize as needed.
626641
nodeContentRenderer: PropTypes.func,
627642

643+
// Override the default component for rendering an empty tree
644+
// This is an advanced option for complete customization of the appearance.
645+
// It is best to copy the component in `placeholder-renderer-default.js` to use as a base,
646+
// and customize as needed.
647+
placeholderRenderer: PropTypes.func,
648+
628649
// Determine the unique key used to identify each node and
629650
// generate the `path` array passed in callbacks.
630651
// By default, returns the index in the tree (omitting hidden nodes).
@@ -661,6 +682,7 @@ ReactSortableTree.defaultProps = {
661682
isVirtualized: true,
662683
maxDepth: null,
663684
nodeContentRenderer: NodeRendererDefault,
685+
placeholderRenderer: PlaceholderRendererDefault,
664686
onMoveNode: null,
665687
onVisibilityToggle: null,
666688
reactVirtualizedListProps: {},

Diff for: ‎src/tree-placeholder.js

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import React, { Children, cloneElement } from 'react';
2+
import PropTypes from 'prop-types';
3+
4+
const TreePlaceholder = ({
5+
children,
6+
connectDropTarget,
7+
treeId,
8+
drop,
9+
...otherProps
10+
}) =>
11+
connectDropTarget(
12+
<div>
13+
{Children.map(children, child =>
14+
cloneElement(child, {
15+
...otherProps,
16+
})
17+
)}
18+
</div>
19+
);
20+
21+
TreePlaceholder.defaultProps = {
22+
canDrop: false,
23+
draggedNode: null,
24+
};
25+
26+
TreePlaceholder.propTypes = {
27+
children: PropTypes.node.isRequired,
28+
29+
// Drop target
30+
connectDropTarget: PropTypes.func.isRequired,
31+
isOver: PropTypes.bool.isRequired,
32+
canDrop: PropTypes.bool,
33+
draggedNode: PropTypes.shape({}),
34+
treeId: PropTypes.string.isRequired,
35+
drop: PropTypes.func.isRequired,
36+
};
37+
38+
export default TreePlaceholder;

Diff for: ‎src/utils/drag-and-drop-utils.js

+32
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,24 @@ const nodeDropTarget = {
174174
canDrop,
175175
};
176176

177+
const placeholderDropTarget = {
178+
drop(dropTargetProps, monitor) {
179+
const { node, path, treeIndex } = monitor.getItem();
180+
const result = {
181+
node,
182+
path,
183+
treeIndex,
184+
treeId: dropTargetProps.treeId,
185+
minimumTreeIndex: 0,
186+
depth: 0,
187+
};
188+
189+
dropTargetProps.drop(result);
190+
191+
return result;
192+
},
193+
};
194+
177195
function nodeDragSourcePropInjection(connect, monitor) {
178196
return {
179197
connectDragSource: connect.dragSource(),
@@ -193,6 +211,16 @@ function nodeDropTargetPropInjection(connect, monitor) {
193211
};
194212
}
195213

214+
function placeholderPropInjection(connect, monitor) {
215+
const dragged = monitor.getItem();
216+
return {
217+
connectDropTarget: connect.dropTarget(),
218+
isOver: monitor.isOver(),
219+
canDrop: monitor.canDrop(),
220+
draggedNode: dragged ? dragged.node : null,
221+
};
222+
}
223+
196224
export function dndWrapSource(el, type) {
197225
return dragSource(type, nodeDragSource, nodeDragSourcePropInjection)(el);
198226
}
@@ -201,6 +229,10 @@ export function dndWrapTarget(el, type) {
201229
return dropTarget(type, nodeDropTarget, nodeDropTargetPropInjection)(el);
202230
}
203231

232+
export function dndWrapPlaceholder(el, type) {
233+
return dropTarget(type, placeholderDropTarget, placeholderPropInjection)(el);
234+
}
235+
204236
export function dndWrapRoot(el) {
205237
return dragDropContext(HTML5Backend)(el);
206238
}

0 commit comments

Comments
 (0)