# TaskDnd

> The drag-and-drop parts that let people move task cards between columns and around other cards, with the drop policy and the move left to the caller.

Source: https://raft-design.botiverse.build/components/task-dnd. Generated from the current release; where it differs from what you remember, this page is right.

TaskDnd is the drag-and-drop wiring behind the [TaskBoard](https://raft-design.botiverse.build/components/task-board.md): a provider, the cards people drag, the places they drop them, the gap a card lands in and the copy of the card that follows the pointer. It decides nothing about tasks. The caller says which drops are allowed and what a move does, such as changing #25 from Todo to In Review.

```tsx
import { TaskBoardComposition } from "#/modules/task-board/compositions/task-board.composition.tsx";
import { TASK_FIXTURES } from "#/modules/task-card/task-card.fixtures.ts";

const DND_TASKS = [TASK_FIXTURES[0], TASK_FIXTURES[2]];

/**
 * Todo + In Progress only so Closed stays empty. Drag onto Closed for blocked
 * empty-column copy; drag onto Done / In Review for allowed empty-column copy.
 */
export function TaskDndComposition() {
  return <TaskBoardComposition tasks={DND_TASKS} blockedDropStatus="closed" expandAll />;
}
```

## When to use

| Situation                                                       | Use                                                                                                                         |
| --------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| Moving a card to another column of a board to change its status | TaskDnd, inside a [TaskBoard](https://raft-design.botiverse.build/components/task-board.md)                                                                  |
| Reordering a card among the cards of a column                   | TaskDnd, with a drop target on each card                                                                                    |
| Changing a status without dragging                              | The status chip on the card, or the property in the task's details ([Tasks](https://raft-design.botiverse.build/patterns/tasks.md#rules)) |
| Reordering composer attachments                                 | The composer's own sortable attachments ([Composer](https://raft-design.botiverse.build/components/composer.md))                                               |
| Resizing a column                                               | [Resizable](https://raft-design.botiverse.build/components/resizable.md)                                                                                      |

## Anatomy

- `TaskDndProvider`: wraps the board and its overlay. It holds `isDropAllowed`, the caller's policy, calls `onMove` when a card is dropped on an allowed target, and passes `accessibility` to dnd-kit. Required.
- `TaskDropZone`: a place a card can be dropped, usually a `TaskBoardColumn` through `render`. It reports `dropState`: `idle`, `allowed` or `blocked`.
- `TaskDraggable`: wraps one [TaskCard](https://raft-design.botiverse.build/components/task-card.md) and makes it draggable. With `dropId` it is also a drop target, so a card can land before or after it.
- `TaskDropPlaceholder`: the gap where the card would land, before or after a card, or in an empty column. It shows only while the drop there is allowed.
- `TaskDragOverlay`: the copy of the card that follows the pointer. Render the dragged card inside it, and nothing when no card is dragged.

## States

| State                  | Look                                                                                                                                  |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| Dragging               | The copy in `TaskDragOverlay` is the only card with `shadow-float` ([TaskCard](https://raft-design.botiverse.build/components/task-card.md#states))                     |
| Over an allowed target | `TaskDropPlaceholder` shows a dashed `line-strong` box with the height of the dragged card                                            |
| Over a blocked target  | No placeholder; the target's `dropState` is `blocked`, and an empty column says why ([TaskBoard](https://raft-design.botiverse.build/components/task-board.md#states)) |
| Dragging turned off    | `disabled` on the provider removes every way to drag; `disabled` on a `TaskDraggable` removes it for that card                        |

## Behavior

- With a mouse, a drag starts once the pointer has moved 5px, so a click on the card isn't a drag.
- On a touch screen, hold the card still for 500ms, then move it: the drag starts on that move. Moving before the hold is up scrolls the board instead ([Dragging on a touch screen](https://raft-design.botiverse.build/foundations/states.md#dragging-on-a-touch-screen)).
- Where the card lands follows the middle of the dragged card: above the middle of the card under it is `before`, below is `after`.
- The card doesn't animate back or into place on drop. The move is the caller's: `onMove` may return a promise, and `onMoveError` receives its rejection, so the caller can undo the change and say what went wrong.

## Accessibility

- The parts wrap dnd-kit, not Base UI. `TaskDraggable` carries dnd-kit's attributes: it is focusable, with a role description and instructions for screen readers.
- Keyboard: Tab to a card, then Space picks it up. ArrowDown and ArrowUp move it one slot down or up its column, before or after each card; ArrowLeft and ArrowRight move it to the nearest drop target in the next column, such as an empty column or the card level with it. Space drops it and Escape cancels. The keys step between drop targets, so a card moves the same way whether or not it leaves its column while it is dragged.
- After a keyboard drop, focus stays where dnd-kit leaves it. Moving focus to the moved card, which has a new place in the DOM, is the caller's job: focus it in `onMove` or `onDragEnd` once the board has rendered the move.
- dnd-kit's own announcements name cards and targets by their ID. Pass `accessibility` to `TaskDndProvider` to replace them: it goes to dnd-kit's `DndContext` as is, with `announcements` (what to say on pick-up, move, drop and cancel) and `screenReaderInstructions` (how to drag, read with each card). Write them in words, such as "Task #25 moved to In Review", or announce each move in a polite live region of your own; the [TaskBoard](https://raft-design.botiverse.build/components/task-board.md#accessibility) composition shows how.
- `TaskDropPlaceholder` is hidden from assistive technology.

## Rules

- Decide in `isDropAllowed` which drops are allowed, so the target says while the card is over it that it can't move there ([TaskBoard](https://raft-design.botiverse.build/components/task-board.md#content)).
- Offer another way to change a status: dragging is one of three ([Tasks](https://raft-design.botiverse.build/patterns/tasks.md#rules)).
- Put the dragged card's own `TaskCard` in `TaskDragOverlay`, so the card under the pointer reads the same as the one in the column.

## API

### TaskDndProvider

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `accessibility` | `{ announcements?: Announcements; container?: Element; restoreFocus?: boolean; screenReaderInstructions?: ScreenReaderInstructions; }` |  |  |
| `children` (required) | `ReactNode` |  |  |
| `disabled` | `boolean` | `false` |  |
| `isDropAllowed` | `(activeId: UniqueIdentifier, overId: UniqueIdentifier, overData: TaskDropData \| undefined) => boolean` | `allowTaskDrop` |  |
| `onDragCancel` | `(event: DragCancelEvent) => void` |  |  |
| `onDragEnd` | `(event: DragEndEvent) => void` |  |  |
| `onDragMove` | `(event: DragMoveEvent) => void` |  |  |
| `onDragOver` | `(event: DragOverEvent) => void` |  |  |
| `onDragStart` | `(event: DragStartEvent) => void` |  |  |
| `onMove` | `(details: TaskMoveDetails) => void \| Promise<void>` |  |  |
| `onMoveError` | `(error: unknown, details: TaskMoveDetails) => void` |  |  |

### TaskDragOverlay

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `children` | `ReactNode` |  |  |

### TaskDraggable

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `disabled` | `boolean` | `false` |  |
| `dropData` | `TaskDropData` |  |  |
| `dropDisabled` | `boolean` | `false` |  |
| `dropId` | `UniqueIdentifier` |  |  |

Also takes the attributes of its element.

### TaskDropPlaceholder

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `placement` | `TaskDropPlacement` |  |  |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### TaskDropZone

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `data` | `TaskDropData` |  |  |
| `disabled` | `boolean` | `false` |  |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

## Changes

- 0.8.1 (Added): `TaskDndProvider` takes `accessibility` (the `TaskDndAccessibility` type), passed through to dnd-kit, so you can translate the screen-reader announcements and drag instructions.
- 0.8.1 (Fixed): Keyboard dragging in `TaskDndProvider` moves a card between drop targets. Before, Space picked the card up but the arrow keys never moved it.
