Skip to content

Commit

Permalink
fix(dashboard): Replace react-nestable with new SortableTree compon…
Browse files Browse the repository at this point in the history
…ent (#8599)

**What**
- Removes `react-nestable` dependency in favour of our own solution based on `@dnd-kit/core`

Resolves CC-217
  • Loading branch information
kasperkristensen authored Aug 18, 2024
1 parent 66c39ef commit 8c784a8
Show file tree
Hide file tree
Showing 15 changed files with 1,267 additions and 334 deletions.
1 change: 0 additions & 1 deletion packages/admin-next/dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@
"react-hook-form": "7.49.1",
"react-i18next": "13.5.0",
"react-jwt": "^1.2.0",
"react-nestable": "^3.0.2",
"react-router-dom": "6.20.1",
"zod": "3.22.4"
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./sortable-tree"
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import {
DroppableContainer,
KeyboardCode,
KeyboardCoordinateGetter,
closestCorners,
getFirstCollision,
} from "@dnd-kit/core"

import type { SensorContext } from "./types"
import { getProjection } from "./utils"

const directions: string[] = [
KeyboardCode.Down,
KeyboardCode.Right,
KeyboardCode.Up,
KeyboardCode.Left,
]

const horizontal: string[] = [KeyboardCode.Left, KeyboardCode.Right]

export const sortableTreeKeyboardCoordinates: (
context: SensorContext,
indentationWidth: number
) => KeyboardCoordinateGetter =
(context, indentationWidth) =>
(
event,
{
currentCoordinates,
context: {
active,
over,
collisionRect,
droppableRects,
droppableContainers,
},
}
) => {
if (directions.includes(event.code)) {
if (!active || !collisionRect) {
return
}

event.preventDefault()

const {
current: { items, offset },
} = context

if (horizontal.includes(event.code) && over?.id) {
const { depth, maxDepth, minDepth } = getProjection(
items,
active.id,
over.id,
offset,
indentationWidth
)

switch (event.code) {
case KeyboardCode.Left:
if (depth > minDepth) {
return {
...currentCoordinates,
x: currentCoordinates.x - indentationWidth,
}
}
break
case KeyboardCode.Right:
if (depth < maxDepth) {
return {
...currentCoordinates,
x: currentCoordinates.x + indentationWidth,
}
}
break
}

return undefined
}

const containers: DroppableContainer[] = []

droppableContainers.forEach((container) => {
if (container?.disabled || container.id === over?.id) {
return
}

const rect = droppableRects.get(container.id)

if (!rect) {
return
}

switch (event.code) {
case KeyboardCode.Down:
if (collisionRect.top < rect.top) {
containers.push(container)
}
break
case KeyboardCode.Up:
if (collisionRect.top > rect.top) {
containers.push(container)
}
break
}
})

const collisions = closestCorners({
active,
collisionRect,
pointerCoordinates: null,
droppableRects,
droppableContainers: containers,
})
let closestId = getFirstCollision(collisions, "id")

if (closestId === over?.id && collisions.length > 1) {
closestId = collisions[1].id
}

if (closestId && over?.id) {
const activeRect = droppableRects.get(active.id)
const newRect = droppableRects.get(closestId)
const newDroppable = droppableContainers.get(closestId)

if (activeRect && newRect && newDroppable) {
const newIndex = items.findIndex(({ id }) => id === closestId)
const newItem = items[newIndex]
const activeIndex = items.findIndex(({ id }) => id === active.id)
const activeItem = items[activeIndex]

if (newItem && activeItem) {
const { depth } = getProjection(
items,
active.id,
closestId,
(newItem.depth - activeItem.depth) * indentationWidth,
indentationWidth
)
const isBelow = newIndex > activeIndex
const modifier = isBelow ? 1 : -1
const offset = 0

const newCoordinates = {
x: newRect.left + depth * indentationWidth,
y: newRect.top + modifier * offset,
}

return newCoordinates
}
}
}
}

return undefined
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import type { UniqueIdentifier } from "@dnd-kit/core"
import { AnimateLayoutChanges, useSortable } from "@dnd-kit/sortable"
import { CSS } from "@dnd-kit/utilities"
import { CSSProperties } from "react"

import { TreeItem, TreeItemProps } from "./tree-item"
import { iOS } from "./utils"

interface SortableTreeItemProps extends TreeItemProps {
id: UniqueIdentifier
}

const animateLayoutChanges: AnimateLayoutChanges = ({
isSorting,
wasDragging,
}) => {
return isSorting || wasDragging ? false : true
}

export function SortableTreeItem({
id,
depth,
disabled,
...props
}: SortableTreeItemProps) {
const {
attributes,
isDragging,
isSorting,
listeners,
setDraggableNodeRef,
setDroppableNodeRef,
transform,
transition,
} = useSortable({
id,
animateLayoutChanges,
disabled,
})
const style: CSSProperties = {
transform: CSS.Translate.toString(transform),
transition,
}

return (
<TreeItem
ref={setDraggableNodeRef}
wrapperRef={setDroppableNodeRef}
style={style}
depth={depth}
ghost={isDragging}
disableSelection={iOS}
disableInteraction={isSorting}
disabled={disabled}
handleProps={{
listeners,
attributes,
}}
{...props}
/>
)
}
Loading

0 comments on commit 8c784a8

Please sign in to comment.