dnd-dynamic-tree
v0.1.2
Published
[![npm version](https://badge.fury.io/js/dnd-dynamic-tree.svg)](https://www.npmjs.org/package/dnd-dynamic-tree) [![npm](https://img.shields.io/npm/dt/dnd-dynamic-tree.svg)](https://www.npmjs.org/package/dnd-dynamic-tree) [![MIT](https://img.shields.io/dub
Downloads
4
Maintainers
Readme
dnd-dynamic-tree
Here's how it could look like (visuals are completely customizable via css though) Normal Look Folder Look
Play around in examples to check the API and see what it can do.
Features
- Custom trigger element
- Custom drag Element
- Multi nth level submenu support
- Automatic State update and Postion generation
- Specific props to each dnd item
- Auto positioning of each dnd-item
- Equbied with all the dnd-kit helpers
- Written in TypeScript 🤙
Install
npm install dnd-dynamic-tree @dnd-kit/core @dnd-kit/sortable @dnd-kit/utilities
How-to use
Check out the Storybook for code samples and play around. You could also play with it on CodeSandbox
Shortly, you need to render:
<SortableTree
items={/* array of your tree items */}
onItemsChanged={/* callback when items are reordered */}
TreeItemComponent={/* component that renders a single tree item */}
/>
And TreeItemComponent
is usually your data wrapped in SimpleTreeItemWrapper
or FolderTreeItemWrapper
:
React.forwardRef((props, ref) => (
<SimpleTreeItemWrapper {...props} ref={ref}>
<div>{props.item.value}</div>
</SimpleTreeItemWrapper>
));
Note that wrapping in forwardRef
and passing ref
to SimpleTreeItemWrapper
is very important!
Examples
Here's the very minimal code to add a Sortable Tree. You shouldn't use it as is in your project, but it could be easier to grasp what's going on.
export const Minimal = () => { const [items, setItems] = useState(initialMinimalData); return ( <SortableTree items={items} onItemsChanged={setItems} { /* * You need to pass the component rendering a single item via TreeItemComponent props. * This component will receive the data via `props.item`. * In this example we inline the component, but in reality you should extract it into a const. */ ...{} } TreeItemComponent={React.forwardRef((props, ref) => ( <SimpleTreeItemWrapper {...props} ref={ref}> {/* HERE GOES THE ACTUAL CONTENT OF YOUR COMPONENT */} <div>{props.item.id}</div> </SimpleTreeItemWrapper> ))} /> ); }; /* * Configure the tree data. */ const initialMinimalData = [ { id: '1', children: [{ id: '4' }, { id: '5' }] }, { id: '2' }, { id: '3' }, ];
Here's the minimal viable example that you could potentially copy&paste to your project to start from.
export const MinimalViable = () => { const [items, setItems] = useState(initialViableMinimalData); return ( <SortableTree items={items} onItemsChanged={setItems} TreeItemComponent={MinimalTreeItemComponent} /> ); }; type MinimalTreeItemData = { value: string; }; /* * Here's the component that will render a single row of your tree */ const MinimalTreeItemComponent = React.forwardRef< HTMLDivElement, TreeItemComponentProps<MinimalTreeItemData> >((props, ref) => ( /* you could also use FolderTreeItemWrapper if you want to show vertical lines. */ <SimpleTreeItemWrapper {...props} ref={ref}> <div>{props.item.value}</div> </SimpleTreeItemWrapper> )); /* * Configure the tree data. */ const initialViableMinimalData: TreeItems<MinimalTreeItemData> = [ { id: '1', value: 'Jane', children: [ { id: '4', value: 'John' }, { id: '5', value: 'Sally' }, ], }, { id: '2', value: 'Fred', children: [{ id: '6', value: 'Eugene' }] }, { id: '3', value: 'Helen', canHaveChildren: false }, ];
API
Data configuration (each TreeItem element could define them):
canHaveChildren
- Default:true
.If set to
false
, prevents any node from being dragged into the current one.Also accepts a function:
(dragItem) => bool
which could conditionally determine if a certain item could be a children of a nodedisableSorting
- Default:false
. If set totrue
, prevents node from being dragged (i.e. it can't be sorted or moved to another node)
Tree configuration (props of <SortableTree>
)
items
- mandatory, items shown in a treeonItemsChanged
- mandatory, callback that is called when dragging of certain item is finished. You should preserve new state and adjust the value ofitems
prop as needed.TreeItemComponent
- mandatory, component that renders a single tree row.indentationWidth
- optional, padding used for childrenpointerSensorOptions
- optional, configures the condition when item dragging starts. Defaults to:{ "activationConstraint": { "distance": 3 } }
disableSorting
- optional, you could set this totrue
to completely disable the sortingkeepGhostInPlace
- optional, you could set this totrue
to keep the Node that you are dragging in it's original place in a Tree. Check VSCode sample to see it in action.dndContextProps
- optional, override any prop of underlying DndContext.sortableProps
- optional, override any prop that is passed to underlying useSortable hook.
TreeItemWrapper configuration (props of <SimpleTreeItemWrapper>
and <FolderTreeItemWrapper>
)
manualDrag
- Default:false
. Set totrue
if you want tree item to be draggable ONLY from dragHandle.showDragHandle
- optional, set tofalse
if you want to hide default dragHandle and show your own instead. Use<div {...props.handleProps}>DRAG_ME</div>
for your own drag handle.
FAQ
- If you want to disable animation completely, you need to do the following:
- Pass
null
asdropAnimation
prop (this disables the actual 'drop' animation for the Node that was dragged). - Pass
{ animateLayoutChanges: () => false }
tosortableProps
(this disables the animation of all other nodes that were not dragged)
- Pass
Troubleshooting
- If your dragged item is shown at the end of a list, make sure you that:
- You wrapped your
TreeItem
component inReact.forwardRef
and passing theref
toSimpleTreeItemWrapper
- You pass the
styles
prop fromTreeItem
toSimpleTreeItemWrapper
- You wrapped your