Sortable
sortable拖拽排序 · @dnd-kit headless + 键盘可拖(Space 抓起/方向键移动) + 手柄/整项两式 + 横竖向 · 受控 onChange(arrayMove)
用法
列设置 · 手柄拖拽(垂直)
受控 items + onChange 回吐新顺序;handle 时仅左侧手柄可拖,键盘可达。
拖拽调整列顺序(手柄拖动 / 聚焦手柄后 Space 抓起 · 方向键移动 · Space 放下)
- 订单编号唯一标识
- 客户名称来自客户主数据
- 订单金额含税
- 订单状态枚举
- 负责人当前跟单人
- 创建时间可排序
当前顺序:订单编号 → 客户名称 → 订单金额 → 订单状态 → 负责人 → 创建时间
tsx
const [fields, setFields] = useState(initialFields);
<Sortable
items={fields}
onChange={setFields}
handle
renderItem={(f) => (
<div className="flex items-center justify-between gap-3">
<span className="font-medium text-foreground">{f.label}</span>
<span className="shrink-0 text-xs text-muted">{f.hint}</span>
</div>
)}
/>整项可拖(无手柄)
handle={false} 时整行可拖,适合行内无交互元素的简单列表。
拖拽调整列顺序(手柄拖动 / 聚焦手柄后 Space 抓起 · 方向键移动 · Space 放下)
- 订单编号唯一标识
- 客户名称来自客户主数据
- 订单金额含税
- 订单状态枚举
- 负责人当前跟单人
- 创建时间可排序
当前顺序:订单编号 → 客户名称 → 订单金额 → 订单状态 → 负责人 → 创建时间
tsx
const [fields, setFields] = useState(initialFields);
<Sortable
items={fields}
onChange={setFields}
renderItem={(f) => <span className="font-medium text-foreground">{f.label}</span>}
/>横向排序(orientation)
orientation="horizontal" 横向排列,适合看板列 / 筛选标签。
横向拖拽排序(看板列 / 筛选标签)
- 待处理
- 进行中
- 已完成
- 已归档
tsx
const [tags, setTags] = useState(initialTags);
<Sortable
items={tags}
orientation="horizontal"
onChange={setTags}
renderItem={(t) => <span className="font-medium text-foreground">{t.name}</span>}
/>何时用
单列表内拖拽改顺序(表格列设置、标签排序、表单字段排序),受控数据 + onChange 回吐 arrayMove 后的新数组。要跨多列流转(看板/任务流)用 Kanban;要节点画布连线编排用 Flow。
导入
ts
import { Sortable } from "@hulianui/ui"Props
SortableProps<T> 泛型。
| 名称 | 类型 | 默认 | 说明 |
|---|---|---|---|
| items * | T[] | — | 受控数据数组;拖拽后由你据 onChange 写回 state |
| getId | (item: T) => UniqueIdentifier | 读 item.id | 取每项稳定 id,须列表内唯一且稳定 |
| orientation | "vertical"|"horizontal" | "vertical" | 排列方向 |
| handle | boolean | false | true=仅左侧手柄可拖(触屏/含交互元素的行推荐);false=整项可拖 |
| className | string | — | 容器类名 |
Events
| 事件 | 类型 | 说明 |
|---|---|---|
| onChange * | (items: T[]) => void | 顺序变化回调(拖拽或键盘移动均触发),参数是 arrayMove 后的新数组 |
Slots
| 插槽 | 类型 | 说明 |
|---|---|---|
| renderItem * | (item: T, state: { dragging: boolean }) => ReactNode | 渲染单项的渲染函数;state.dragging 表示该项正被拖拽 |
禁忌 / 坑
- 受控组件:
onChange不会替你改 state,必须自己把新数组写回(onChange={setItems})。 getId返回的 id 必须稳定唯一;用数组下标当 id 会在重排后错乱。- 行内含按钮/链接等交互元素时设
handle,否则整项可拖会拦截子元素的点击/拖拽(参考 [[dnd-kit-draggable-container-guard-interactive-children]])。
相关
Table · Book3D · ProTable · PricingTable · JsonViewer · EditableTable
Playground
拖拽调整列顺序(手柄拖动 / 聚焦手柄后 Space 抓起 · 方向键移动 · Space 放下)
- 订单编号唯一标识
- 客户名称来自客户主数据
- 订单金额含税
- 订单状态枚举
- 负责人当前跟单人
- 创建时间可排序
当前顺序:订单编号 → 客户名称 → 订单金额 → 订单状态 → 负责人 → 创建时间
const [items, setItems] = useState(fields);
<Sortable
items={items}
onChange={setItems}
handle={true}
renderItem={(f) => <span>{f.label}</span>}
/>