Files
supabase/apps/studio/components/grid/SupabaseGrid.tsx
T
Gildas Garcia 1c228ad18c Chore dnd kit supabase grid columns sort (#44654)
## 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 column
reordering in the table editor

## Screencast


https://github.com/user-attachments/assets/54fb36f4-5671-42e2-9698-2ae928a69f55

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Column drag-and-drop rebuilt with a live drag preview and improved
pointer/keyboard handling

* **Improvements**
* More reliable column reordering with refined move/freeze/unfreeze
behavior and stable index recomputation
* Standardized column type/format display and simplified grid rendering

* **Accessibility**
  * Added accessible labeling for column actions controls

* **Tests**
* End-to-end locator updated to target the column actions button
deterministically
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-04-08 16:09:18 +02:00

123 lines
4.1 KiB
TypeScript

import { keepPreviousData } from '@tanstack/react-query'
import { useParams } from 'common'
import { PropsWithChildren, useRef } from 'react'
import { DataGridHandle } from 'react-data-grid'
import { useIsTableFilterBarEnabled } from '../interfaces/App/FeaturePreview/FeaturePreviewContext'
import { Shortcuts } from './components/common/Shortcuts'
import { Footer } from './components/footer/Footer'
import { Grid } from './components/grid/Grid'
import { Header, HeaderProps } from './components/header/Header'
import { HeaderNew } from './components/header/HeaderNew'
import { useTableFilter } from './hooks/useTableFilter'
import { useTableSort } from './hooks/useTableSort'
import { validateMsSqlSorting } from './MsSqlValidation'
import { GridProps } from './types'
import { formatGridDataWithOperationValues } from './utils/queueOperationUtils'
import { isMsSqlForeignTable } from '@/data/table-editor/table-editor-types'
import { useTableRowsQuery } from '@/data/table-rows/table-rows-query'
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
import { RoleImpersonationState } from '@/lib/role-impersonation'
import { EMPTY_ARR } from '@/lib/void'
import { useRoleImpersonationStateSnapshot } from '@/state/role-impersonation-state'
import { useTableEditorStateSnapshot } from '@/state/table-editor'
import { QueuedOperation } from '@/state/table-editor-operation-queue.types'
import { useTableEditorTableStateSnapshot } from '@/state/table-editor-table'
export const SupabaseGrid = ({
customHeader,
gridProps,
children,
}: PropsWithChildren<
Pick<HeaderProps, 'customHeader'> & {
gridProps?: GridProps
}
>) => {
const { id: _id } = useParams()
const tableId = _id ? Number(_id) : undefined
const { data: project } = useSelectedProjectQuery()
const tableEditorSnap = useTableEditorStateSnapshot()
const snap = useTableEditorTableStateSnapshot()
const preflightCheck = !tableEditorSnap.tablesToIgnorePreflightCheck.includes(tableId ?? -1)
const gridRef = useRef<DataGridHandle>(null)
const newFilterBarEnabled = useIsTableFilterBarEnabled()
const { filters } = useTableFilter()
const { sorts, onApplySorts } = useTableSort()
const roleImpersonationState = useRoleImpersonationStateSnapshot()
const msSqlWarning = isMsSqlForeignTable(snap.originalTable)
? validateMsSqlSorting({ filters, sorts, table: snap.originalTable })
: { warning: null }
const tableQueriesEnabled = msSqlWarning.warning === null
const {
data,
error,
isSuccess,
isError,
isPending: isLoading,
isRefetching,
} = useTableRowsQuery(
{
projectRef: project?.ref,
tableId,
sorts,
filters,
page: snap.page,
preflightCheck,
limit: tableEditorSnap.rowsPerPage,
roleImpersonationState: roleImpersonationState as RoleImpersonationState,
},
{
placeholderData: keepPreviousData,
enabled: tableQueriesEnabled,
retry: (_, error: any) => {
const doesNotExistError = error && error.message?.includes('does not exist')
if (doesNotExistError) onApplySorts([])
return false
},
}
)
const operations = (tableEditorSnap.operationQueue.operations as QueuedOperation[]).filter(
(op) => op.tableId === tableId
)
const baseRows = data?.rows ?? EMPTY_ARR
const rows = formatGridDataWithOperationValues({ operations, rows: baseRows })
const HeaderComponent = newFilterBarEnabled ? HeaderNew : Header
return (
<div className="sb-grid h-full flex flex-col">
<HeaderComponent
customHeader={customHeader}
isRefetching={isRefetching}
tableQueriesEnabled={tableQueriesEnabled}
/>
{msSqlWarning.warning !== null && <msSqlWarning.Component />}
{children || (
<>
<Grid
ref={gridRef}
{...gridProps}
rows={rows}
error={error}
isDisabled={!tableQueriesEnabled}
isLoading={isLoading}
isSuccess={isSuccess}
isError={isError}
/>
<Footer enableForeignRowsQuery={tableQueriesEnabled} />
<Shortcuts gridRef={gridRef} rows={rows} />
</>
)}
</div>
)
}