Chuyển tới nội dung

Pro đang mở bán trước. Cài đặt mở từ tháng 1/2027. Xem Pro

Component

SortableList

Danh sách người dùng sắp xếp lại bằng cách kéo tay nắm của một hàng hoặc bằng bàn phím, như các widget trên dashboard hoặc danh sách ưu tiên. Mỗi hàng có một tay nắm; phần còn lại của hàng chứa nội dung bất kỳ.

  • npm: 1 trực tiếp, 2 tổng
  • registry: 2 trực tiếp, 2 tổng

Xem trước

  • Doanh thu
  • Lượt đăng ký
  • Rời bỏ
  • Lưu lượng

Props

  • label
  • size
  • disabled
  • badges
import { SortableList, SortableListItem } from "@/components/ui/sortable-list"

<SortableList label="Widget trên dashboard" labels={{ handle: "Di chuyển {label}", instructions: "Nhấn Space hoặc Enter để nhấc lên. Dùng phím mũi tên để di chuyển, Space hoặc Enter để thả, Escape để hủy.", pickedUp: "Đã nhấc {label}. Vị trí {position} trên {total}.", moved: "Đã chuyển {label} tới vị trí {position} trên {total}.", dropped: "Đã thả {label} ở vị trí {position} trên {total}.", canceled: "Đã hủy di chuyển. {label} trở lại vị trí {position} trên {total}." }} className="w-80 max-w-full">
  <SortableListItem value="revenue" label="Doanh thu" />
  <SortableListItem value="signups" label="Lượt đăng ký" />
  <SortableListItem value="churn" label="Rời bỏ" />
  <SortableListItem value="traffic" label="Lưu lượng" />
</SortableList>

Cách dùng

Khi nào dùng

  • Cho người dùng tự đặt thứ tự, như các widget trên dashboard hoặc danh sách ưu tiên.
  • Sắp xếp lại một danh sách ngắn bằng cách kéo hoặc bằng bàn phím, lưu mỗi thứ tự mới.

Khi nào không dùng

  • Sắp xếp hàng theo một giá trị như ngày hoặc tên. Dùng Table với các cột sắp xếp được.
  • Hiện các bước theo thứ tự cố định. Dùng Stepper.
  • Chọn nhiều item từ một danh sách. Dùng Checkbox.

Biến thể

size

Chiều cao và padding của hàng: hàng sm cao ít nhất size-element-md cho panel dày, hàng md ít nhất size-element-xl cho nội dung hai dòng.

    • Doanh thu
    • Lượt đăng ký
    • Rời bỏ
    • Lưu lượng

    sm

    • Doanh thu
    • Lượt đăng ký
    • Rời bỏ
    • Lưu lượng

    mdmặc định

import { SortableList, SortableListItem } from "@/components/ui/sortable-list"

<SortableList label="Widget trên dashboard" size="sm" labels={{ handle: "Di chuyển {label}", instructions: "Nhấn Space hoặc Enter để nhấc lên. Dùng phím mũi tên để di chuyển, Space hoặc Enter để thả, Escape để hủy.", pickedUp: "Đã nhấc {label}. Vị trí {position} trên {total}.", moved: "Đã chuyển {label} tới vị trí {position} trên {total}.", dropped: "Đã thả {label} ở vị trí {position} trên {total}.", canceled: "Đã hủy di chuyển. {label} trở lại vị trí {position} trên {total}." }} className="w-80 max-w-full">
  <SortableListItem value="revenue" label="Doanh thu" />
  <SortableListItem value="signups" label="Lượt đăng ký" />
  <SortableListItem value="churn" label="Rời bỏ" />
  <SortableListItem value="traffic" label="Lưu lượng" />
</SortableList>
<SortableList label="Widget trên dashboard" labels={{ handle: "Di chuyển {label}", instructions: "Nhấn Space hoặc Enter để nhấc lên. Dùng phím mũi tên để di chuyển, Space hoặc Enter để thả, Escape để hủy.", pickedUp: "Đã nhấc {label}. Vị trí {position} trên {total}.", moved: "Đã chuyển {label} tới vị trí {position} trên {total}.", dropped: "Đã thả {label} ở vị trí {position} trên {total}.", canceled: "Đã hủy di chuyển. {label} trở lại vị trí {position} trên {total}." }} className="w-80 max-w-full">
  <SortableListItem value="revenue" label="Doanh thu" />
  <SortableListItem value="signups" label="Lượt đăng ký" />
  <SortableListItem value="churn" label="Rời bỏ" />
  <SortableListItem value="traffic" label="Lưu lượng" />
</SortableList>

Ví dụ

Widget trên dashboard

  • Doanh thu
  • Lượt đăng ký
  • Rời bỏ
  • Lưu lượng
import { SortableList, SortableListItem } from "@/components/ui/sortable-list"

<SortableList label="Widget trên dashboard" className="w-80">
  <SortableListItem value="revenue" label="Doanh thu" />
  <SortableListItem value="signups" label="Lượt đăng ký" />
  <SortableListItem value="churn" label="Rời bỏ" />
  <SortableListItem value="traffic" label="Lưu lượng" />
</SortableList>

Hàng có nội dung

Children thay label trong hàng; label vẫn đặt tên cho tay nắm.

  • Sửa email thanh toánKhẩn
  • Rút gọn onboardingTiếp theo
  • Xuất CSVĐể sau
import { Badge } from "@/components/ui/badge"
import { SortableList, SortableListItem } from "@/components/ui/sortable-list"

<SortableList label="Ưu tiên" className="w-96">
  <SortableListItem value="billing" label="Sửa email thanh toán">
    <span className="flex-1 truncate">Sửa email thanh toán</span>
    <Badge variant="destructive">Khẩn</Badge>
  </SortableListItem>
  <SortableListItem value="onboarding" label="Rút gọn onboarding">
    <span className="flex-1 truncate">Rút gọn onboarding</span>
    <Badge variant="info">Tiếp theo</Badge>
  </SortableListItem>
  <SortableListItem value="export" label="Xuất CSV">
    <span className="flex-1 truncate">Xuất CSV</span>
    <Badge>Để sau</Badge>
  </SortableListItem>
</SortableList>

Nhỏ, có một hàng khóa

Hàng bị tắt giữ vị trí trong thứ tự nhưng tay nắm không hoạt động.

  • Tên
  • Email
  • Vai trò
  • Ngày tham gia
import { SortableList, SortableListItem } from "@/components/ui/sortable-list"

<SortableList label="Cột" size="sm" className="w-64">
  <SortableListItem value="name" label="Tên" disabled />
  <SortableListItem value="email" label="Email" />
  <SortableListItem value="role" label="Vai trò" />
  <SortableListItem value="joined" label="Ngày tham gia" />
</SortableList>

Nên và không nên

Nên

  • Lưu thứ tự mới ngay khi onValueChange được gọi, để tải lại trang vẫn giữ nó.
  • Giữ mỗi hàng trong một hoặc hai dòng, để dễ theo dõi hàng đang di chuyển.
  • Đặt link và button trong nội dung; chỉ tay nắm mới bắt đầu kéo.

Không nên

  • Đừng cho sắp xếp lại khi thứ tự đến từ một phép sắp xếp, như theo ngày. Dùng Table với các cột sắp xếp được.
  • Đừng lồng một SortableList trong một SortableList khác.
  • Đừng ẩn tay nắm; đó là cách duy nhất để di chuyển một hàng trên cảm ứng và bằng bàn phím.

Props

TênKiểuGiá trịMặc địnhMô tả
label bắt buộcstring--Trên SortableList: tên của danh sách cho trình đọc màn hình, như Widget trên dashboard. Trên SortableListItem: tên của hàng trong tay nắm và trong các thông báo; cũng là nội dung của hàng khi không có children.
valuestring[] on SortableList, string on SortableListItem--Trên SortableList: các value của item theo thứ tự, cho danh sách được kiểm soát; item không có trong đó theo sau theo thứ tự viết. Trên SortableListItem: định danh hàng trong thứ tự. Bắt buộc trên mọi item.
defaultValuestring[]--Thứ tự ban đầu, cho danh sách không kiểm soát. Không có nó, các hàng bắt đầu theo thứ tự viết.
onValueChange(value: string[]) => void--Được gọi với thứ tự mới sau khi thả, và sau mỗi lần di chuyển bằng bàn phím.
sizeenumsm | mdmdChiều cao và padding của hàng: hàng sm cao ít nhất size-element-md cho panel dày, hàng md ít nhất size-element-xl cho nội dung hai dòng.
disabledboolean-falseĐặt trên SortableList hoặc SortableListItem. Tắt các tay nắm; nội dung giữ nguyên.
labels{ handle?: string; instructions?: string; pickedUp?: string; moved?: string; dropped?: string; canceled?: string }--Văn bản cho tên tay nắm, hướng dẫn bàn phím và các thông báo, với {label}, {position} và {total} được điền vào. Mặc định: EN `Move {label}`, `Picked up {label}. Position {position} of {total}.` và tương tự. Truyền văn bản tiếng Việt trên trang tiếng Việt.

Slot

TênBắt buộcMô tả
contentkhôngChildren của SortableListItem: nội dung bất kỳ sau tay nắm, như một icon, một tên và một Badge.

Cấu tạo

PhầnMô tả
rootPhần bao quanh danh sách và live region của nó.
listul chứa các hàng.
sortableItemMột hàng: tay nắm, rồi nội dung. Một surface có viền khi đứng yên; nổi lên với ring và bóng vừa khi đang di chuyển.
handleButton tay nắm 24px ở đầu hàng.
iconIcon tay nắm bên trong button, 16px.
hitArea::before trong suốt mở rộng tay nắm tới size-target-min.
contentNội dung bất kỳ sau tay nắm; là label khi không có children.

Khả năng truy cập

Role: list

PhímHành động
TabChuyển tới tay nắm của hàng tiếp theo.
Space / EnterNhấc hàng đang focus lên, hoặc thả hàng đang nhấc ở vị trí hiện tại.
ArrowUp / ArrowDownDi chuyển hàng đang nhấc một vị trí. ArrowLeft và ArrowRight làm tương tự.
Home / EndDi chuyển hàng đang nhấc tới đầu hoặc cuối.
EscapeHủy di chuyển và đặt hàng về chỗ đã nhấc lên.
  • Một ul được đặt tên bằng label. Mỗi tay nắm là một button có tên EN `Move {label}`, VI `Di chuyển {label}`, và được mô tả bằng hướng dẫn bàn phím.
  • Tay nắm đang nhấc có aria-pressed="true" và hàng đặt data-dragging.
  • Mỗi lần nhấc, di chuyển, thả và hủy được thông báo trong một live region với vị trí mới của hàng, như EN `Revenue moved to position 2 of 4.`
  • Chuyển focus khỏi một tay nắm đang nhấc sẽ thả hàng ở vị trí hiện tại.
  • Kéo bằng chuột và cảm ứng chỉ bắt đầu trên tay nắm, nên nội dung có thể chứa link và button. Tay nắm đạt size-target-min trên cảm ứng, qua ::before trong suốt.

Cài đặt

npx werewolf-ui add sortable-list

Chạy npx werewolf-ui init một lần trong app trước khi thêm component. Init cài token, thêm CSS import và luật cho agent. Add cài component này cùng các dependency và giữ nguyên theme của bạn. Xem Cài đặt để biết yêu cầu và cách cài qua registry.