useReorder

useReorder({ onMove })는 목록을 끌어서 순서를 바꾸게 합니다. 마우스는 브라우저의 drag and drop으로, 손가락과 펜은 포인터 이벤트로 끕니다. 각 항목에 getItemProps(index)를, 그 안에서 끌기를 시작하는 손잡이 같은 요소에 getHandleProps(index)를 펼쳐 넣으세요. onMove(from, to)는 끈 항목의 인덱스와 그 항목이 놓이는 인덱스를 받습니다.

이 페이지의 내용
import { useReorder } from '@tounsoo/todae';
import { GripVertical } from 'lucide-react';

const reorder = useReorder({ onMove: move });

<ol>
  {items.map((item, index) => (
    <li key={item.id} {...reorder.getItemProps(index)}>
      <span {...reorder.getHandleProps(index)} aria-hidden style={{ cursor: 'grab', touchAction: 'none' }}>
        <GripVertical />
      </span>
      {item.name}
    </li>
  ))}
</ol>

행을 손잡이로 끌어 보세요. Reorder 레시피는 키보드와 끌 수 없는 사람을 위해 이 hook을 메뉴와 함께 씁니다.

Pinned channels

  1. Design

  2. Engineering

  3. Marketing

  4. Sales

  5. Support

손잡이를 누르면 그 항목만 끌 수 있게 되므로, 항목은 손잡이로만 끌 수 있고 항목의 텍스트는 그대로 선택할 수 있습니다. 항목을 끄는 동안 dragging은 그 인덱스이고, drop은 그 항목이 어느 항목 옆, 어느 쪽(before나 after)에 놓일지 알려 주며, 놓아도 옮겨지지 않는 자리에서는 null입니다. 항목에는 스타일을 위해 같은 상태가 data-dragging과 data-drop으로 붙습니다. 놓아서 순서가 바뀔 때만 onMove를 부르고, 항목 밖에 놓으면 아무것도 부르지 않습니다. from은 놓는 순간 끈 항목이 있는 자리이므로, 끄는 동안 목록이 바뀌어도 그 항목을 옮기고, 끄는 항목이 사라지면 끌기가 끝납니다. 파일, 선택한 텍스트, 다른 목록의 항목처럼 이 목록의 손잡이에서 시작하지 않은 끌기는 무시하므로, 거기에 놓을 수 없습니다. 두 getter 모두 두 번째 인자로 직접 넣을 props를 받고 그 핸들러를 자신의 핸들러보다 먼저 실행합니다. 다른 Todae 파트처럼, 직접 넣은 핸들러가 preventDefault()를 부르면 훅의 핸들러는 건너뜁니다.

orientation="horizontal"에서 before는 텍스트가 시작하는 쪽이므로, 오른쪽에서 왼쪽으로 쓰는 페이지에서는 오른쪽입니다.

Escape는 끌기를 취소하고 순서를 그대로 둡니다. 마우스로 끄는 동안에는 브라우저가 Escape를 직접 받아 드롭 없이 끌기를 끝냅니다. 터치로 끄는 동안에는 목록을 감싼 Dialog보다 훅이 먼저 받으므로, 대화상자는 다음 Escape까지 열려 있습니다.

터치

터치 화면의 브라우저는 서로 다릅니다. 길게 누르면 브라우저의 끌기를 시작하는 곳도 있고, 전혀 시작하지 않는 곳도 있습니다. 그래서 손잡이 위의 손가락이나 펜은 대신 포인터 이벤트로, 어디서나 같은 방식으로 끕니다. 손가락이나 펜이 움직이면 항목이 끌리고, drop은 그 아래의 항목을 따라가며, 떼면 놓입니다. 탭은 아무것도 끌지 않고, 펜으로 끌어도 텍스트가 선택되지 않습니다. 손잡이에는 touch-action: none이 필요합니다. 없으면 브라우저가 손가락이나 펜을 페이지 스크롤에 쓰고, 그러면 끌기가 취소됩니다. 터치로 끄는 동안 페이지는 스크롤되지 않으므로, 화면보다 긴 목록에서 항목을 멀리 옮길 때는 메뉴를 쓰세요.

접근성을 지키는 조합

Todae가 맡는 것: 마우스, 손가락, 펜으로 항목을 손잡이로만 끌게 하는 것. 놓일 항목과 쪽, 끈 항목이 놓일 인덱스를 계산하는 것. 다른 곳에서 온 끌기를 무시하는 것. Escape로 끌기를 취소하는 것.

직접 제공할 것: Reorder 레시피의 메뉴처럼 끌지 않고 키보드로도 항목을 옮기는 다른 방법. 브라우저의 drag and drop에는 키보드 방법이 없습니다. 화면 낭독기 사용자는 그 다른 방법을 쓰므로 aria-hidden으로 숨긴 손잡이. 각 항목의 id로 만든 key. 이동의 알림, 그리고 놓일 자리의 선과 끄는 항목의 모양.

주의할 점: 끌기만으로 순서를 바꾸게 하는 것(WCAG 2.2 SC 2.1.1, SC 2.5.7). 버튼이거나 탭 정지인 손잡이. 키보드로는 아무것도 하지 않습니다. 아이콘만 있는 손잡이처럼 24×24 CSS 픽셀보다 작은 손잡이(SC 2.5.8). touch-action: none이 없는 손잡이. 터치 화면에서 누르면 끌리지 않고 페이지가 스크롤됩니다. 터치 끌기는 Chromium의 터치 입력으로 테스트하고, Firefox와 WebKit에서 포인터 이벤트로 확인했으며, 휴대전화에서는 확인하지 않았습니다.

언제 쓰나요

쓸 때: 사람이 직접 순서를 정하는 목록의 항목을 끌게 할 때, 끌지 않고 옮기는 방법과 함께.

쓰지 않을 때: 파일 놓기 → useDropzone. 목록 사이에서 항목 옮기기 → 제품의 drag and drop.

Props

useReorder props
이름타입설명
onMove 필수(from: number, to: number) => void

순서를 바꾸는 놓기에서, 끈 항목의 인덱스와 그 항목이 놓이는 인덱스를 받아 불립니다.

orientation'vertical' | 'horizontal'

항목이 늘어선 축. 기본값은 'vertical'. 가로 목록에서 'before'는 텍스트 방향을 따릅니다.

기본값: 'vertical'

disabledboolean

끌기를 끕니다. `disabled`가 true인 동안에는 진행 중인 끌기를 놓을 수 없습니다.

기본값: false