mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 17:35:10 +03:00
chore: migrate row sort in SupabaseGrid to DndKit (#44628)
## Problem We currently have 3 different libraries for drag & drop, two of which are not actively maintained anymore. ## Solution Migrate all usage of the two unmaintained libraries to DndKit. This PR focuses on using DndKit instead of `react-dnd` for the row sorting popover in the table editor ## Screencast https://github.com/user-attachments/assets/9d5cf43c-fbbd-4e5e-8b5a-e9e803b71421 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Enhanced sorting functionality with drag-and-drop support to reorder sort rules. * **Improvements** * Updated drag handle styling for better visual clarity and usability. * Simplified sorting interface architecture. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
1 parent
8923d52511
commit
acb0929abe
3 files changed
+73
-103
No files matched your search
@@ -1,3 +1,17 @@
|
||||
import {
|
||||
closestCenter,
|
||||
DndContext,
|
||||
KeyboardSensor,
|
||||
PointerSensor,
|
||||
useSensor,
|
||||
useSensors,
|
||||
} from '@dnd-kit/core'
|
||||
import {
|
||||
arrayMove,
|
||||
SortableContext,
|
||||
sortableKeyboardCoordinates,
|
||||
verticalListSortingStrategy,
|
||||
} from '@dnd-kit/sortable'
|
||||
import { THRESHOLD_COUNT } from '@supabase/pg-meta'
|
||||
import { keepPreviousData } from '@tanstack/react-query'
|
||||
import { useParams } from 'common'
|
||||
@@ -214,6 +228,13 @@ export const SortPopoverPrimitive = ({
|
||||
}
|
||||
}, [sorts])
|
||||
|
||||
const sensors = useSensors(
|
||||
useSensor(PointerSensor),
|
||||
useSensor(KeyboardSensor, {
|
||||
coordinateGetter: sortableKeyboardCoordinates,
|
||||
})
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<Popover_Shadcn_ modal={false} open={open} onOpenChange={setOpen}>
|
||||
@@ -224,17 +245,35 @@ export const SortPopoverPrimitive = ({
|
||||
</PopoverTrigger_Shadcn_>
|
||||
<PopoverContent_Shadcn_ className="p-0 w-96" side="bottom" align="start">
|
||||
<div className="space-y-2 py-2">
|
||||
{localSorts.map((sort, index) => (
|
||||
<SortRow
|
||||
key={getSortRowKey(sort, index)}
|
||||
index={index}
|
||||
columnName={sort.column}
|
||||
sort={sort}
|
||||
onDelete={onDeleteSort}
|
||||
onToggle={onToggleSort}
|
||||
onDrag={onDragSort}
|
||||
/>
|
||||
))}
|
||||
<DndContext
|
||||
sensors={sensors}
|
||||
collisionDetection={closestCenter}
|
||||
onDragEnd={(result) => {
|
||||
if (result.over == null) return
|
||||
const activeIndex = localSorts.findIndex((sort) => sort.column === result.active.id)
|
||||
const overIndex = localSorts.findIndex((sort) => sort.column === result.over!.id)
|
||||
if (activeIndex === -1 || overIndex === -1) return
|
||||
|
||||
setLocalSorts(arrayMove(localSorts, activeIndex, overIndex))
|
||||
}}
|
||||
>
|
||||
<SortableContext
|
||||
items={localSorts.map((sort) => sort.column)}
|
||||
strategy={verticalListSortingStrategy}
|
||||
>
|
||||
{localSorts.map((sort, index) => (
|
||||
<SortRow
|
||||
key={getSortRowKey(sort, index)}
|
||||
index={index}
|
||||
columnName={sort.column}
|
||||
sort={sort}
|
||||
onDelete={onDeleteSort}
|
||||
onToggle={onToggleSort}
|
||||
onDrag={onDragSort}
|
||||
/>
|
||||
))}
|
||||
</SortableContext>
|
||||
</DndContext>
|
||||
{localSorts.length === 0 && (
|
||||
<div className="space-y-1 px-3">
|
||||
<h5 className="text-xs text-foreground-light">No sorts applied to this view</h5>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { XYCoord } from 'dnd-core'
|
||||
import { Menu, X } from 'lucide-react'
|
||||
import { memo, useRef } from 'react'
|
||||
import { useDrag, useDrop } from 'react-dnd'
|
||||
import { useSortable } from '@dnd-kit/sortable'
|
||||
import { CSS } from '@dnd-kit/utilities'
|
||||
import { GripVertical, X } from 'lucide-react'
|
||||
import { memo } from 'react'
|
||||
import { Button, Switch } from 'ui'
|
||||
|
||||
import type { DragItem, Sort } from '@/components/grid/types'
|
||||
import type { Sort } from '@/components/grid/types'
|
||||
import { useTableEditorTableStateSnapshot } from '@/state/table-editor-table'
|
||||
|
||||
export interface SortRowProps {
|
||||
@@ -20,94 +20,29 @@ const SortRow = ({ index, columnName, sort, onDelete, onToggle, onDrag }: SortRo
|
||||
const snap = useTableEditorTableStateSnapshot()
|
||||
const column = snap.table.columns.find((x) => x.name === columnName)
|
||||
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
const { attributes, listeners, setNodeRef, setActivatorNodeRef, transform, transition } =
|
||||
useSortable({
|
||||
id: columnName,
|
||||
})
|
||||
|
||||
const [{ isDragging }, drag] = useDrag({
|
||||
type: 'sort-row',
|
||||
item: () => {
|
||||
return { key: columnName, index }
|
||||
},
|
||||
collect: (monitor: any) => ({
|
||||
isDragging: monitor.isDragging(),
|
||||
}),
|
||||
})
|
||||
|
||||
const [{ handlerId }, drop] = useDrop({
|
||||
accept: 'sort-row',
|
||||
collect(monitor) {
|
||||
return {
|
||||
handlerId: monitor.getHandlerId(),
|
||||
}
|
||||
},
|
||||
hover(item, monitor) {
|
||||
if (!ref.current) {
|
||||
return
|
||||
}
|
||||
const dragIndex = (item as DragItem).index
|
||||
const hoverIndex = index
|
||||
|
||||
// Don't replace items with themselves
|
||||
if (dragIndex === hoverIndex) {
|
||||
return
|
||||
}
|
||||
|
||||
// Determine rectangle on screen
|
||||
const hoverBoundingRect = ref.current?.getBoundingClientRect()
|
||||
|
||||
// Get vertical middle
|
||||
const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2
|
||||
|
||||
// Determine mouse position
|
||||
const clientOffset = monitor.getClientOffset()
|
||||
|
||||
// Get pixels to the top
|
||||
const hoverClientY = (clientOffset as XYCoord).y - hoverBoundingRect.top
|
||||
|
||||
// Only perform the move when the mouse has crossed half of the items height
|
||||
// When dragging downwards, only move when the cursor is below 50%
|
||||
// When dragging upwards, only move when the cursor is above 50%
|
||||
|
||||
// Dragging downwards
|
||||
if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) {
|
||||
return
|
||||
}
|
||||
|
||||
// Dragging upwards
|
||||
if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {
|
||||
return
|
||||
}
|
||||
|
||||
// Time to actually perform the action
|
||||
moveSort(dragIndex, hoverIndex)
|
||||
|
||||
// Note: we're mutating the monitor item here!
|
||||
// Generally it's better to avoid mutations,
|
||||
// but it's good here for the sake of performance
|
||||
// to avoid expensive index searches.
|
||||
;(item as DragItem).index = hoverIndex
|
||||
},
|
||||
})
|
||||
|
||||
const moveSort = (dragIndex: number, hoverIndex: number) => {
|
||||
if (dragIndex == hoverIndex) return
|
||||
onDrag(dragIndex, hoverIndex)
|
||||
const style = {
|
||||
transform: CSS.Transform.toString(transform),
|
||||
transition,
|
||||
}
|
||||
|
||||
const opacity = isDragging ? 0 : 1
|
||||
drag(drop(ref))
|
||||
|
||||
if (!column) return null
|
||||
|
||||
return (
|
||||
<div
|
||||
className="flex items-center gap-3 px-3"
|
||||
ref={ref}
|
||||
style={{ opacity }}
|
||||
data-handler-id={handlerId}
|
||||
>
|
||||
<span className="transition-color text-foreground-lighter hover:text-foreground-light">
|
||||
<Menu strokeWidth={2} size={16} />
|
||||
</span>
|
||||
<div className="flex items-center gap-3 px-3" ref={setNodeRef} style={style}>
|
||||
<button
|
||||
ref={setActivatorNodeRef}
|
||||
{...attributes}
|
||||
{...listeners}
|
||||
className="opacity-50 hover:opacity-100 transition cursor-grab text-foreground"
|
||||
type="button"
|
||||
>
|
||||
<GripVertical size={16} strokeWidth={1.5} />
|
||||
</button>
|
||||
<div className="grow">
|
||||
<span className="flex grow items-center gap-1 truncate text-sm text-foreground">
|
||||
<span className="text-xs text-foreground-lighter">
|
||||
|
||||
+1
-5
@@ -2,8 +2,6 @@ import { keepPreviousData } from '@tanstack/react-query'
|
||||
import { useParams } from 'common'
|
||||
import { Loader2, X } from 'lucide-react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { DndProvider } from 'react-dnd'
|
||||
import { HTML5Backend } from 'react-dnd-html5-backend'
|
||||
import { Button, SidePanel } from 'ui'
|
||||
|
||||
import { ForeignKey } from '../../ForeignKeySelector/ForeignKeySelector.types'
|
||||
@@ -216,9 +214,7 @@ export const ForeignRowSelector = ({
|
||||
<div className="flex items-center">
|
||||
<RefreshButton tableId={table?.id} isRefetching={isRefetching} />
|
||||
<FilterPopoverPrimitive filters={filters} onApplyFilters={onApplyFilters} />
|
||||
<DndProvider backend={HTML5Backend} context={window}>
|
||||
<SortPopoverPrimitive sorts={sorts} onApplySorts={onApplySorts} />
|
||||
</DndProvider>
|
||||
<SortPopoverPrimitive sorts={sorts} onApplySorts={onApplySorts} />
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-x-3 divide-x">
|
||||
|
||||
Reference in new issue
Block a user