2024-04-29 11:08:51 +08:00
import React , { createContext , useContext , useState } from 'react' ;
import type { DragEndEvent , DragOverEvent , UniqueIdentifier } from '@dnd-kit/core' ;
import {
closestCenter ,
DndContext ,
DragOverlay ,
PointerSensor ,
useSensor ,
useSensors ,
} from '@dnd-kit/core' ;
import { restrictToHorizontalAxis } from '@dnd-kit/modifiers' ;
import {
arrayMove ,
horizontalListSortingStrategy ,
SortableContext ,
useSortable ,
} from '@dnd-kit/sortable' ;
import { Table } from 'antd' ;
import type { TableColumnsType } from 'antd' ;
interface DataType {
key : string ;
name : string ;
gender : string ;
age : number ;
email : string ;
address : string ;
}
interface HeaderCellProps extends React . HTMLAttributes < HTMLTableCellElement > {
id : string ;
}
interface BodyCellProps extends React . HTMLAttributes < HTMLTableCellElement > {
id : string ;
}
type DragIndexState = {
active : UniqueIdentifier ;
over : UniqueIdentifier | undefined ;
direction ? : 'left' | 'right' ;
} ;
const DragIndexContext = createContext < DragIndexState > ( { active : - 1 , over : - 1 } ) ;
const dragActiveStyle = ( dragState : DragIndexState , id : string ) = > {
const { active , over , direction } = dragState ;
// drag active style
let style = { } ;
if ( active && active === id ) {
style = { backgroundColor : 'gray' , opacity : 0.5 } ;
}
// dragover dashed style
else if ( over && id === over && active !== over ) {
style =
direction === 'right'
? { borderRight : '1px dashed gray' }
: { borderLeft : '1px dashed gray' } ;
}
return style ;
} ;
const TableBodyCell = ( props : BodyCellProps ) = > {
const dragState = useContext < DragIndexState > ( DragIndexContext ) ;
return (
< td
{ . . . props }
style = { {
. . . props . style ,
. . . dragActiveStyle ( dragState , props . id ) ,
} }
/ >
) ;
} ;
const TableHeaderCell = ( props : HeaderCellProps ) = > {
const dragState = useContext ( DragIndexContext ) ;
const { attributes , listeners , setNodeRef , isDragging } = useSortable ( {
id : props.id ,
} ) ;
const style : React.CSSProperties = {
. . . props . style ,
cursor : 'move' ,
. . . ( isDragging ? { position : 'relative' , zIndex : 9999 , userSelect : 'none' } : { } ) ,
. . . dragActiveStyle ( dragState , props . id ) ,
} ;
return < th { ...props } ref = { setNodeRef } style = { style } { ...attributes } { ...listeners } / > ;
} ;
const dataSource : DataType [ ] = [
{
key : '1' ,
name : 'John Brown' ,
gender : 'male' ,
age : 32 ,
email : 'John Brown@example.com' ,
address : 'London No. 1 Lake Park' ,
} ,
{
key : '2' ,
name : 'Jim Green' ,
gender : 'female' ,
age : 42 ,
email : 'jimGreen@example.com' ,
address : 'London No. 1 Lake Park' ,
} ,
{
key : '3' ,
name : 'Joe Black' ,
gender : 'female' ,
age : 32 ,
email : 'JoeBlack@example.com' ,
address : 'Sidney No. 1 Lake Park' ,
} ,
{
key : '4' ,
name : 'George Hcc' ,
gender : 'male' ,
age : 20 ,
email : 'george@example.com' ,
address : 'Sidney No. 1 Lake Park' ,
} ,
] ;
const baseColumns : TableColumnsType < DataType > = [
{
title : 'Name' ,
dataIndex : 'name' ,
} ,
{
title : 'Gender' ,
dataIndex : 'gender' ,
} ,
{
title : 'Age' ,
dataIndex : 'age' ,
} ,
{
title : 'Email' ,
dataIndex : 'email' ,
} ,
{
title : 'Address' ,
dataIndex : 'address' ,
} ,
] ;
const App : React.FC = ( ) = > {
const [ dragIndex , setDragIndex ] = useState < DragIndexState > ( { active : - 1 , over : - 1 } ) ;
const [ columns , setColumns ] = useState ( ( ) = >
baseColumns . map ( ( column , i ) = > ( {
. . . column ,
key : ` ${ i } ` ,
onHeaderCell : ( ) = > ( {
id : ` ${ i } ` ,
} ) ,
onCell : ( ) = > ( {
id : ` ${ i } ` ,
} ) ,
} ) ) ,
) ;
const sensors = useSensors (
useSensor ( PointerSensor , {
activationConstraint : {
// https://docs.dndkit.com/api-documentation/sensors/pointer#activation-constraints
distance : 1 ,
} ,
} ) ,
) ;
const onDragEnd = ( { active , over } : DragEndEvent ) = > {
if ( active . id !== over ? . id ) {
setColumns ( ( prev ) = > {
const activeIndex = prev . findIndex ( ( i ) = > i . key === active . id ) ;
const overIndex = prev . findIndex ( ( i ) = > i . key === over ? . id ) ;
return arrayMove ( prev , activeIndex , overIndex ) ;
} ) ;
}
setDragIndex ( { active : - 1 , over : - 1 } ) ;
} ;
2024-04-30 12:21:12 +08:00
const onDragOver = ( { active , over } : DragOverEvent ) = > {
2024-04-29 11:08:51 +08:00
const activeIndex = columns . findIndex ( ( i ) = > i . key === active . id ) ;
const overIndex = columns . findIndex ( ( i ) = > i . key === over ? . id ) ;
setDragIndex ( {
active : active.id ,
over : over?.id ,
direction : overIndex > activeIndex ? 'right' : 'left' ,
} ) ;
} ;
return (
< DndContext
sensors = { sensors }
modifiers = { [ restrictToHorizontalAxis ] }
onDragEnd = { onDragEnd }
2024-04-30 12:21:12 +08:00
onDragOver = { onDragOver }
2024-04-29 11:08:51 +08:00
collisionDetection = { closestCenter }
>
< SortableContext items = { columns . map ( ( i ) = > i . key ) } strategy = { horizontalListSortingStrategy } >
< DragIndexContext.Provider value = { dragIndex } >
< Table
components = { {
header : {
cell : TableHeaderCell ,
} ,
body : {
cell : TableBodyCell ,
} ,
} }
rowKey = "key"
columns = { columns }
dataSource = { dataSource }
/ >
< / DragIndexContext.Provider >
< / SortableContext >
< DragOverlay >
{ /* TODO: Since the tableheadercell custom component uses antd to render the cell style, it is currently not possible to copy the same component as tableheadercell. */ }
< th style = { { backgroundColor : 'gray' , padding : 16 } } >
{ columns [ columns . findIndex ( ( i ) = > i . key === dragIndex . active ) ] ? . title as React . ReactNode }
< / th >
< / DragOverlay >
< / DndContext >
) ;
} ;
export default App ;