Задать вопрос
@rgdzv

Как правильно использовать хуки useSortable / useDraggable из библиотеки dnd-kit в React приложении?

Делаю простой калькулятор на React. Для реализации drag-and-drop использую библиотеку dnd-kit. Проблему можно увидеть на гифке снизу: при перетаскивании элемента калькулятора из левой области в область для дропа отсутствует анимация перетаскивания, хотя элемент может быть дропнут в область для дропа. В самой же области для дропа анимация перетаскивания прекрасно работает и элементы могут быть поменяны местами.

Мне необходимо чтобы работала анимация перетаскивания, когда я перетаскиваю элемент из левой области в область для дропа.

635f8d823a1e2304358220.gif

Код для компонента App:

const App: FC = () => {

    const [selected, setSelected] = useState('Constructor')

    const [droppedElems, setDroppedElems] = useState<CalcElemListInterface[]>([])

    const handleActiveSwitcher = (id: string) => {
        setSelected(id)
    }

    const deleteDroppedElem = (item: CalcElemListInterface) => {
        const filtered = [...droppedElems].filter(elem => elem.id !== item.id)
        setDroppedElems(filtered)
    }

    const leftFieldStyles = cn(styles.left, {
        [styles.hidden]: selected === 'Runtime'
    })

    const calcElementsList = calcElemListArray.map((item) => {

        const index = droppedElems.findIndex(elem => elem.id === item.id)
        const layoutDisabledStyle = index !== -1

        return (
            <CalcElemLayout 
                key={item.id} 
                id={item.id} 
                item={item}
                layoutDisabledStyle={layoutDisabledStyle}
            />
        )
    })

    const handleDragEnd = (event: DragEndEvent) => {

        const { id, list }  = event.active.data.current as CalcElemListInterface
        const elem = {id, list}

        if (event.over && event.over.id === 'droppable') {
            setDroppedElems((prev) => {
                return [...prev, elem]
            })
        }
    }

    return (
        <div className={styles.layout}>
            <div className={styles.top}>
                <Switcher
                    selected={selected}
                    handleActiveSwitcher={handleActiveSwitcher}
                />
            </div>
            <DndContext
                onDragEnd={handleDragEnd}
            >
                <div className={styles.content}>
                    <div className={leftFieldStyles}>
                        {calcElementsList}
                    </div>
                    <DropElemLayout
                        deleteDroppedElem={deleteDroppedElem}
                        selected={selected}
                        droppedElems={droppedElems}
                        setDroppedElems={setDroppedElems}
                    />
                </div>
            </DndContext>
        </div>
    )
}


Код для области дропа:

const DropElemLayout: FC<DropElemLayoutInterface> = ({ selected, droppedElems, deleteDroppedElem, setDroppedElems }) => {

    const { isOver, setNodeRef } = useDroppable({
        id: 'droppable'
    })

    const sensors = useSensors(
        useSensor(PointerSensor),
        useSensor(KeyboardSensor, {
            coordinateGetter: sortableKeyboardCoordinates,
        })
    )

    const style = {
        backgroundColor: (isOver && !droppedElems.length) ? '#F0F9FF' : undefined,
    }

    const droppedRuntimeElemList = droppedElems.map((item) => {

        const layoutEnabledStyle = droppedElems.length ? true : false

        return (
            <CalcElemLayout 
                key={item.id}
                id={item.id}
                item={item}
                deleteDroppedElem={deleteDroppedElem} 
                selected={selected}
                layoutEnabledStyle={layoutEnabledStyle}
            />
        )
    })

    const droppedElemList = !droppedElems.length
        ?
            <div className={styles.rightContent}>
                <Icon name="#drop"/>
                <p>Перетащите сюда</p>
                <span>любой элемент</span>
                <span>из левой панели</span>
            </div>
        :
            droppedRuntimeElemList

    const className = !droppedElems.length ? styles.right : styles.left

    const handleDragEnd = (event: DragEndEvent) => {
        if (event.active.id !== event.over?.id) {
            setDroppedElems((items: CalcElemListInterface[]) => {
                const oldIndex = items.findIndex(item => item.id === event.active?.id)
                const newIndex = items.findIndex(item => item.id === event.over?.id)
                return arrayMove(items, oldIndex, newIndex)
            })
        }
    }

    return (
        <DndContext
            onDragEnd={handleDragEnd} 
            sensors={sensors} 
            collisionDetection={closestCenter}
        >
            <div 
                ref={setNodeRef} 
                className={className}  
                style={style}
            >
                <SortableContext
                    items={droppedElems}
                    strategy={verticalListSortingStrategy}
                >
                    {droppedElemList}
                </SortableContext>
            </div>
        </DndContext>
    )
}


Код для перетаскиваемого элемента:

const CalcElemLayout: FC<CalcElemLayoutInterface> = ({ item, id, deleteDroppedElem, selected, layoutDisabledStyle, layoutEnabledStyle }) => {

    const { current } = useAppSelector(state => state.calculator)

    // const { attributes, listeners, setNodeRef, transform, isDragging } = useDraggable({
    //     id: id,
    //     data: {...item},
    //     disabled: selected === 'Runtime'
    // })

    const {
        attributes,
        listeners,
        setNodeRef,
        transform,
        transition,
        isDragging
    } = useSortable({
        id: id,
        data: {...item},
        disabled: selected === 'Runtime'
    })

    const style = {
        transform: CSS.Translate.toString(transform),
        transition: transition
    } 

    const handleDeleteDroppedElem = () => {
        deleteDroppedElem?.(item)
    }

    const doubleClickCondition = selected === 'Constructor' ? handleDeleteDroppedElem : undefined

    const layoutStyle = cn(styles.elemLayout, {
        [styles.operators]: item.id === 'operators',
        [styles.digits]: item.id === 'digits',
        [styles.equal]: item.id === 'equal',
        [styles.disabled]: layoutDisabledStyle,
        [styles.enabled]: layoutEnabledStyle,
    })

    const buttonList = item.list?.map(elem => (
        <Button 
            key={elem.name} 
            elem={elem.name}
            selected={selected!}
        />
    ))

    const resultStyle = cn(styles.result, {
        [styles.minified]: current.length >= 10
    })

    const elemList = item.id === 'result'
        ? 
            <div className={resultStyle}>{current}</div>
        :
            buttonList

    const overlayStyle = {  
        opacity: '0.5',
    }

    return (
        <>
            <div 
                ref={setNodeRef} 
                className={layoutStyle}
                onDoubleClick={doubleClickCondition}
                style={style}
                {...attributes}
                {...listeners}
            >
                {elemList}
            </div>
        </>
    )
}
  • Вопрос задан
  • 638 просмотров
Подписаться 1 Средний Комментировать
Пригласить эксперта
Ответы на вопрос 1
@Korvux
Можно подобным образом

<DragOverlay dropAnimation={null}>
    {activeBlock && <BlockLayout block={activeBlock} />}
 </DragOverlay>
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы