dnd-kit sortable typescript

Nochi·2022년 7월 25일
0

React

목록 보기
3/5

타입스크립트 예제가 없으면 내가 만들어주지

설치

yarn add @dnd-kit/core
yarn add @dnd-kit/sortable
yarn add @dnd-kit/utilities

폴더 구조

SortableIndex.tsx

import {
  closestCorners,
  DndContext,
  DragEndEvent,
  DragOverEvent,
  DragOverlay,
  DragStartEvent,
  KeyboardSensor,
  PointerSensor,
  UniqueIdentifier,
  useSensor,
  useSensors,
} from '@dnd-kit/core';
import { arrayMove, sortableKeyboardCoordinates } from '@dnd-kit/sortable';
import styled from '@emotion/styled';
import { useState } from 'react';
import SortableContainer from './Components/SortableContainer';
import SortableItem from './Components/SortableItem';

const SortableIndx = () => {
  const [activeId, setActiveId] = useState<UniqueIdentifier>();
  const [items, setItems] = useState<{
    [key: string]: string[];
  }>({
    root: ['1', '2', '3', '4', ' 5', '6', '7', '8', '9'],
  });

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

  const findContainer = (id: UniqueIdentifier) => {
    if (id in items) {
      return id;
    }

    return Object.keys(items).find((key: string) => items[key].includes(id.toString()));
  };

  const handleDragStart = (event: DragStartEvent) => {
    const { active } = event;
    const { id } = active;

    setActiveId(id);
  };

  const handleDragOver = (event: DragOverEvent) => {
    const { active, over } = event;
    const id = active.id.toString();
    const overId = over?.id;

    if (!overId) return;

    // 컨테이너 찾기
    const activeContainer = findContainer(id);
    const overContainer = findContainer(over?.id);

    if (!activeContainer || !overContainer || activeContainer === overContainer) {
      return;
    }

    setItems((prev) => {
      const activeItems = prev[activeContainer];
      const overItems = prev[overContainer];

      // 각 아이템의 인덱스 찾기
      const activeIndex = activeItems.indexOf(id);
      const overIndex = overItems.indexOf(overId.toString());

      let newIndex;
      if (overId in prev) {
        newIndex = overItems.length + 1;
      } else {
        const activeTop = active.rect.current.translated?.top ?? 0;

        const isBelowLastItem =
          over && overIndex === overItems.length - 1 && activeTop > over.rect.top + over.rect.height;

        const modifier = isBelowLastItem ? 1 : 0;

        newIndex = overIndex >= 0 ? overIndex + modifier : overItems.length + 1;
      }

      return {
        ...prev,
        [activeContainer]: [...prev[activeContainer].filter((item) => item !== active.id)],
        [overContainer]: [
          ...prev[overContainer].slice(0, newIndex),
          items[activeContainer][activeIndex],
          ...prev[overContainer].slice(newIndex, prev[overContainer].length),
        ],
      };
    });
  };

  const handleDragEnd = (event: DragEndEvent) => {
    const { active, over } = event;
    const id = active.id.toString();
    const overId = over?.id;

    if (!overId) return;

    // 컨테이너 찾기
    const activeContainer = findContainer(id);
    const overContainer = findContainer(overId);

    if (!activeContainer || !overContainer || activeContainer !== overContainer) {
      return;
    }

    const activeIndex = items[activeContainer].indexOf(id);
    const overIndex = items[overContainer].indexOf(overId.toString());

    if (activeIndex !== overIndex) {
      setItems((items) => ({
        ...items,
        [overContainer]: arrayMove(items[overContainer], activeIndex, overIndex),
      }));
    }
    setActiveId(undefined);
  };

  return (
    <Wrapper>
      <DndContext
        sensors={sensors}
        collisionDetection={closestCorners}
        onDragStart={handleDragStart}
        onDragOver={handleDragOver}
        onDragEnd={handleDragEnd}
      >
        <SortableContainer id='root' items={items.root} />
        <DragOverlay>{activeId ? <SortableItem id={activeId} /> : null}</DragOverlay>
      </DndContext>
    </Wrapper>
  );
};

export default SortableIndx;

const Wrapper = styled.div`
  dispaly: flex;
  flexdirection: 'row';
`;

SortableContainer.tsx

import { useDroppable } from '@dnd-kit/core';
import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable';
import styled from '@emotion/styled';
import SortableItem from './SortableItem';

interface ISortableContainer {
  id: string;
  items: any;
}

const SortableContainer = ({ id, items }: ISortableContainer) => {
  const { setNodeRef } = useDroppable({ id });
  return (
    <SortableContext id={id} items={items} strategy={verticalListSortingStrategy}>
      <SortableContainerWrapper ref={setNodeRef}>
        {items.map((id: string) => (
          <SortableItem key={id} id={id} />
        ))}
      </SortableContainerWrapper>
    </SortableContext>
  );
};

export default SortableContainer;

const SortableContainerWrapper = styled.div`
  background: "#dadada",
  padding: 10,
  margin: 10,
  flex: 1
`;

SortableItem.tsx

import React from 'react';
import { useSortable } from '@dnd-kit/sortable';
import styled from '@emotion/styled';
import { CSS } from '@dnd-kit/utilities';
import { UniqueIdentifier } from '@dnd-kit/core';

interface IItem {
  id: UniqueIdentifier;
}

const Item = ({ id }: IItem) => {
  return <ItemWrapper>{id}</ItemWrapper>;
};

const SortableItem = ({ id }: { id: UniqueIdentifier }) => {
  const { attributes, listeners, setNodeRef, transform, transition } = useSortable({ id });
  const SortableItemWrapper = styled.div`
    transform: CSS.Transform.toString(${transform}), ${transition};
  `;

  return (
    <SortableItemWrapper ref={setNodeRef} {...attributes} {...listeners}>
      <Item id={id} />
    </SortableItemWrapper>
  );
};

export default SortableItem;

const ItemWrapper = styled.div`
    width: "100%",
    height: 50,
    display: "flex",
    alignItems: "center",
    justifyContent: "center",
    border: "1px solid black",
    margin: "10px 0",
    background: "white"
}
`;

0개의 댓글