Skip to content

Commit

Permalink
feat: update sortable
Browse files Browse the repository at this point in the history
  • Loading branch information
sadmann7 committed May 11, 2024
1 parent a9817fe commit 6c420b9
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 60 deletions.
2 changes: 1 addition & 1 deletion src/app/_components/hook-form-demo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export function HookFormDemo() {
{fields.map((field, index) => (
<SortableItem
key={field.id}
id={field.id}
value={field.id}
className="py-0.5"
asChild
>
Expand Down
95 changes: 51 additions & 44 deletions src/app/data-table/_components/data-table-demo.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"use client"

import * as React from "react"
import { DragEndEvent } from "@dnd-kit/core"
import {
CaretSortIcon,
ChevronDownIcon,
Expand Down Expand Up @@ -32,6 +33,7 @@ import {
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import { Input } from "@/components/ui/input"
import { Skeleton } from "@/components/ui/skeleton"
import {
Sortable,
SortableDragHandle,
Expand Down Expand Up @@ -175,6 +177,18 @@ export const columns: ColumnDef<Payment>[] = [
)
},
},
{
id: "sortable",
cell: ({ cell }) => (
<SortableItem value={cell.id} asChild>
<SortableDragHandle variant="ghost" size="icon" className="size-8">
<DragHandleDots2Icon className="size-4" aria-hidden="true" />
</SortableDragHandle>
</SortableItem>
),
enableSorting: false,
enableHiding: false,
},
]

export function DataTableDemo() {
Expand Down Expand Up @@ -245,49 +259,42 @@ export function DataTableDemo() {
</DropdownMenu>
</div>
<div className="rounded-md border">
<Sortable value={data} onValueChange={setData}>
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<TableHead key={header.id}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext()
)}
</TableHead>
)
})}
</TableRow>
))}
</TableHeader>
<TableBody>
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<TableHead key={header.id}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext()
)}
</TableHead>
)
})}
</TableRow>
))}
</TableHeader>
<TableBody>
<Sortable value={data} onValueChange={setData}>
{table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row) => (
<SortableItem key={row.id} id={row.id} asChild>
<TableRow data-state={row.getIsSelected() && "selected"}>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext()
)}
</TableCell>
))}
<SortableDragHandle variant="ghost" size="icon" asChild>
<TableCell>
<DragHandleDots2Icon
className="size-4"
aria-hidden="true"
/>
</TableCell>
</SortableDragHandle>
</TableRow>
</SortableItem>
<TableRow
key={row.id}
data-state={row.getIsSelected() && "selected"}
>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext()
)}
</TableCell>
))}
</TableRow>
))
) : (
<TableRow>
Expand All @@ -299,9 +306,9 @@ export function DataTableDemo() {
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</Sortable>
</Sortable>
</TableBody>
</Table>
</div>
<div className="flex items-center justify-end space-x-2 py-4">
<div className="flex-1 text-sm text-muted-foreground">
Expand Down
52 changes: 37 additions & 15 deletions src/components/ui/sortable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type {
UniqueIdentifier,
} from "@dnd-kit/core"
import {
closestCenter,
defaultDropAnimationSideEffects,
DndContext,
DragOverlay,
Expand All @@ -27,6 +28,8 @@ import {
SortableContext,
sortableKeyboardCoordinates,
useSortable,
verticalListSortingStrategy,
type SortableContextProps,
} from "@dnd-kit/sortable"
import { CSS } from "@dnd-kit/utilities"
import { Slot, type SlotProps } from "@radix-ui/react-slot"
Expand All @@ -35,11 +38,8 @@ import { composeRefs } from "@/lib/compose-refs"
import { cn } from "@/lib/utils"
import { Button, type ButtonProps } from "@/components/ui/button"

interface WithId {
id: UniqueIdentifier
}

interface SortableProps<TData extends WithId> extends DndContextProps {
interface SortableProps<TData extends { id: UniqueIdentifier }>
extends DndContextProps {
/**
* An array of data items that the sortable component will render.
* @example
Expand Down Expand Up @@ -68,6 +68,20 @@ interface SortableProps<TData extends WithId> extends DndContextProps {
*/
onMove?: (event: { activeIndex: number; overIndex: number }) => void

/**
* An array of modifiers that will be used to modify the behavior of the sortable component.
* @default [restrictToVerticalAxis, restrictToParentElement]
* @type Modifier[]
*/
modifiers?: DndContextProps["modifiers"]

/**
* A sorting strategy that will be used to determine the new order of the data items.
* @default verticalListSortingStrategy
* @type SortableContextProps["strategy"]
*/
strategy?: SortableContextProps["strategy"]

/**
* An optional React node that is rendered on top of the sortable component.
* It can be used to display additional information or controls.
Expand All @@ -79,10 +93,12 @@ interface SortableProps<TData extends WithId> extends DndContextProps {
overlay?: React.ReactNode | null
}

function Sortable<TData extends WithId>({
function Sortable<TData extends { id: UniqueIdentifier }>({
value,
onValueChange,
collisionDetection = closestCenter,
modifiers = [restrictToVerticalAxis, restrictToParentElement],
strategy = verticalListSortingStrategy,
onMove,
children,
overlay,
Expand All @@ -106,8 +122,8 @@ function Sortable<TData extends WithId>({
onDragStart={({ active }) => setActiveId(active.id)}
onDragEnd={({ active, over }) => {
if (over && active.id !== over?.id) {
const activeIndex = value.findIndex(({ id }) => id === active.id)
const overIndex = value.findIndex(({ id }) => id === over.id)
const activeIndex = value.findIndex((item) => item.id === active.id)
const overIndex = value.findIndex((item) => item.id === over.id)

if (onMove) {
onMove({ activeIndex, overIndex })
Expand All @@ -118,10 +134,15 @@ function Sortable<TData extends WithId>({
setActiveId(null)
}}
onDragCancel={() => setActiveId(null)}
collisionDetection={collisionDetection}
{...props}
>
<SortableContext items={value}>{children}</SortableContext>
<SortableOverlay activeId={activeId}>{overlay}</SortableOverlay>
<SortableContext items={value} strategy={strategy}>
{children}
</SortableContext>
{overlay ? (
<SortableOverlay activeId={activeId}>{overlay}</SortableOverlay>
) : null}
</DndContext>
)
}
Expand Down Expand Up @@ -150,7 +171,7 @@ function SortableOverlay({
return (
<DragOverlay dropAnimation={dropAnimation} {...props}>
{activeId ? (
<SortableItem id={activeId} asChild>
<SortableItem value={activeId} asChild>
{children}
</SortableItem>
) : null}
Expand Down Expand Up @@ -178,20 +199,21 @@ function useSortableItem() {
return context
}

interface SortableItemProps extends Omit<SlotProps, "id">, WithId {
interface SortableItemProps extends SlotProps {
value: UniqueIdentifier
asChild?: boolean
}

const SortableItem = React.forwardRef<HTMLDivElement, SortableItemProps>(
({ asChild, className, id, ...props }, ref) => {
({ asChild, className, value, ...props }, ref) => {
const {
attributes,
isDragging,
listeners,
setNodeRef,
transform,
transition,
} = useSortable({ id })
isDragging,
} = useSortable({ id: value })

const context = React.useMemo(
() => ({
Expand Down

0 comments on commit 6c420b9

Please sign in to comment.