Sortable
Lets users sort an array of sibling elements via drag and drop.
Functionality based on dndkit's useSortable,
which is implemented as a basic use-case component, and allows for:
- horizontal, vertical and grid sort
- a11y controls
(!) This version of the component is a primitive and not to be used by itself directly in core. It is meant as a utility layer so as to not repeat code relevant to interacting with useSortable.
All implementations require:
- the
.Rootlevel component to be passed an array ofsortableIdson which to apply the sorting. Additionally, requires anonSortChangefunction to call so that the implementation receives the new order of items to manually render appropriately. - the
.Itemlevel component to be passed in a unique (to its siblings)id, the same id which coresponds to it from thesortableIdsarray. - the
.Handlelevel component to be passed in the sametargetIdas theidof the.Itemit controls.
The entire Sortable.Item can act as a draggable Item button itself by pasing the isDragHandle prop to it, however, it is usually prefered to control Sortable.Item via a nested Sortable.Handle button so as to allow for any content in the Item itself and avoid nested buttons which is invalid HTML.
Example usage
const sortableItems = [
{ text: 'A', id: 1, disabled: true, hasHandle: true },
{ text: 'B', id: 2, disabled: true },
{ text: 'C', id: 3, hasHandle: true },
{ text: 'D', id: 'd' }
]
const [sortableIdsCurrentOrder, setSortableIdsCurrentOrder] = React.useState(
sortableItems.map(({ id }) => id)
)
<Sortable.Root
sortableIds={sortableIdsCurrentOrder}
onSortChange={({ order }) => {
setSortableIdsCurrentOrder(order)
}}
>
{sortableIdsCurrentOrder.map((sortedId) => {
const sortableItem = sortableItems.find(({ id }) => id === sortedId)
if (!sortableItem) return null
return (
<Sortable.Item
key={sortableItem.id}
id={sortableItem.id}
disabled={!sortableItem.hasHandle && sortableItem.disabled}
isDragHandle={!sortableItem.hasHandle}
asChild
>
<div className="flex">
{sortableItem.hasHandle && (
<Sortable.Handle
targetId={sortableItem.id}
disabled={sortableItem.hasHandle && sortableItem.disabled}
/>
)}
{sortableItem.text}
</div>
</Sortable.Item>
)
})}
</Sortable.Root>
API Reference
| Prop | Type | Default | Required |
|---|---|---|---|
onSortChange | (onSortChangeData: { order: UniqueIdentifier[]; oldIndex: number; newIndex: number; }) => void | - | |
sortableIds | (string | number)[] | - |
| Prop | Type | Default | Required |
|---|---|---|---|
asChild | boolean | | - |
className | string | - | - |
disabled | boolean | - | - |
id | string | number | - | |
isDragHandle | boolean | | - |
style | Pick<HTMLAttributes<HTMLDivElement>, "style"> | - | - |
| Prop | Type | Default | Required |
|---|---|---|---|
appearance | "simple" | "outline" | "solid" | Partial<Record<Breakpoint, "simple" | "outline" | "solid">> | - | - |
as | JSX.IntrinsicElements | - | - |
hasTooltip | boolean | - | - |
href | string | - | - |
isDragging | boolean | - | - |
isRounded | boolean | - | - |
label | string | drag handle | - |
onClick | MouseEventHandler<HTMLElement> | - | - |
size | "xs" & Partial<Record<Breakpoint, "sm" | "md" | "lg">> | "sm" & Partial<Record<Breakpoint, "sm" | "md" | "lg">> | "md" & Partial<Record<Breakpoint, "sm" | "md" | "lg">> | "lg" & Partial<Record<Breakpoint, "sm" | "md" | "lg">> | Partial<Record<Breakpoint, "xs" | "sm" | "md" | "lg">> & "sm" | Partial<Record<Breakpoint, "xs" | "sm" | "md" | "lg">> & "md" | Partial<Record<Breakpoint, "xs" | "sm" | "md" | "lg">> & "lg" | Partial<Record<Breakpoint, "xs" | "sm" | "md" | "lg">> & Partial<Record<Breakpoint, "sm" | "md" | "lg">> | "sm" | "md" | "lg" | - | - |
targetId | string | number | - | |
theme | "neutral" | "primary" | "primaryDark" | "success" | "warning" | "danger" | "white" | Partial<Record<Breakpoint, "neutral" | "primary" | "primaryDark" | "success" | "warning" | "danger" | "white">> | - | - |
tooltipSide | "left" | "right" | "top" | "bottom" | - | - |