1- import { useState , useMemo } from 'react' ;
1+ import { useState , useMemo , useRef } from 'react' ;
22import { Checkbox } from '../ui/Checkbox' ;
33import type { GridColDef , GridRowModel } from '../../types' ;
44
@@ -9,6 +9,7 @@ export interface ColumnVisibilityPanelProps<R extends GridRowModel = GridRowMode
99 onVisibilityChange : ( field : string , isVisible : boolean ) => void ;
1010 onShowAll : ( ) => void ;
1111 onHideAll : ( ) => void ;
12+ onColumnReorder ?: ( fromField : string , toField : string ) => void ;
1213}
1314
1415function SearchIcon ( ) {
@@ -20,11 +21,32 @@ function SearchIcon() {
2021 ) ;
2122}
2223
24+ function DragHandleIcon ( ) {
25+ return (
26+ < svg
27+ className = "ogx-column-visibility-panel__drag-handle"
28+ width = "16"
29+ height = "16"
30+ viewBox = "0 0 24 24"
31+ fill = "currentColor"
32+ >
33+ < circle cx = "9" cy = "5" r = "1.5" />
34+ < circle cx = "15" cy = "5" r = "1.5" />
35+ < circle cx = "9" cy = "12" r = "1.5" />
36+ < circle cx = "15" cy = "12" r = "1.5" />
37+ < circle cx = "9" cy = "19" r = "1.5" />
38+ < circle cx = "15" cy = "19" r = "1.5" />
39+ </ svg >
40+ ) ;
41+ }
42+
2343export function ColumnVisibilityPanel < R extends GridRowModel = GridRowModel > (
2444 props : ColumnVisibilityPanelProps < R >
2545) {
26- const { columns, visibleColumns, onVisibilityChange, onShowAll, onHideAll } = props ;
46+ const { columns, visibleColumns, onVisibilityChange, onShowAll, onHideAll, onColumnReorder } = props ;
2747 const [ searchQuery , setSearchQuery ] = useState ( '' ) ;
48+ const [ dragOverField , setDragOverField ] = useState < string | null > ( null ) ;
49+ const dragFieldRef = useRef < string | null > ( null ) ;
2850
2951 const hideableColumns = useMemo ( ( ) => columns . filter ( col => col . hideable !== false ) , [ columns ] ) ;
3052 const allVisible = hideableColumns . every ( col => visibleColumns . has ( col . field ) ) ;
@@ -45,6 +67,41 @@ export function ColumnVisibilityPanel<R extends GridRowModel = GridRowModel>(
4567 }
4668 } ;
4769
70+ // ── Drag handlers ──────────────────────────────────────────────────────────
71+ const handleDragStart = ( field : string ) => ( e : React . DragEvent ) => {
72+ dragFieldRef . current = field ;
73+ e . dataTransfer . effectAllowed = 'move' ;
74+ // Use a ghost image offset so the handle looks natural
75+ e . dataTransfer . setDragImage ( e . currentTarget . closest ( '.ogx-column-visibility-panel__item' ) as HTMLElement , 20 , 20 ) ;
76+ } ;
77+
78+ const handleDragOver = ( field : string ) => ( e : React . DragEvent ) => {
79+ e . preventDefault ( ) ;
80+ e . dataTransfer . dropEffect = 'move' ;
81+ if ( field !== dragFieldRef . current ) {
82+ setDragOverField ( field ) ;
83+ }
84+ } ;
85+
86+ const handleDragLeave = ( ) => {
87+ setDragOverField ( null ) ;
88+ } ;
89+
90+ const handleDrop = ( toField : string ) => ( e : React . DragEvent ) => {
91+ e . preventDefault ( ) ;
92+ const fromField = dragFieldRef . current ;
93+ setDragOverField ( null ) ;
94+ dragFieldRef . current = null ;
95+ if ( fromField && fromField !== toField && onColumnReorder ) {
96+ onColumnReorder ( fromField , toField ) ;
97+ }
98+ } ;
99+
100+ const handleDragEnd = ( ) => {
101+ setDragOverField ( null ) ;
102+ dragFieldRef . current = null ;
103+ } ;
104+
48105 return (
49106 < div className = "ogx-column-visibility-panel" >
50107 < div className = "ogx-column-visibility-panel__search-section" >
@@ -66,21 +123,47 @@ export function ColumnVisibilityPanel<R extends GridRowModel = GridRowModel>(
66123 { filteredColumns . map ( col => {
67124 const isVisible = visibleColumns . has ( col . field ) ;
68125 const isHideable = col . hideable !== false ;
126+ const isDragOver = dragOverField === col . field ;
127+ const canReorder = ! ! onColumnReorder && ! searchQuery ;
69128
70129 return (
71- < label
130+ < div
72131 key = { col . field }
73- className = { `ogx-column-visibility-panel__item ${ ! isHideable ? 'ogx-column-visibility-panel__item--disabled' : '' } ` }
132+ className = { [
133+ 'ogx-column-visibility-panel__item' ,
134+ ! isHideable && 'ogx-column-visibility-panel__item--disabled' ,
135+ isDragOver && 'ogx-column-visibility-panel__item--drag-over' ,
136+ ] . filter ( Boolean ) . join ( ' ' ) }
137+ draggable = { canReorder }
138+ onDragOver = { canReorder ? handleDragOver ( col . field ) : undefined }
139+ onDragLeave = { canReorder ? handleDragLeave : undefined }
140+ onDrop = { canReorder ? handleDrop ( col . field ) : undefined }
141+ onDragEnd = { canReorder ? handleDragEnd : undefined }
74142 >
75- < Checkbox
76- checked = { isVisible }
77- onChange = { ( e ) => onVisibilityChange ( col . field , e . target . checked ) }
78- disabled = { ! isHideable }
79- />
80- < span className = "ogx-column-visibility-panel__label" >
81- { col . headerName || col . field }
82- </ span >
83- </ label >
143+ { canReorder && (
144+ < span
145+ className = "ogx-column-visibility-panel__drag-handle-wrapper"
146+ draggable
147+ onDragStart = { handleDragStart ( col . field ) }
148+ title = "Drag to reorder"
149+ >
150+ < DragHandleIcon />
151+ </ span >
152+ ) }
153+ < label
154+ className = "ogx-column-visibility-panel__item-label"
155+ style = { { display : 'flex' , alignItems : 'center' , gap : 'var(--ogx-spacing-md)' , flex : 1 , cursor : isHideable ? 'pointer' : 'not-allowed' } }
156+ >
157+ < Checkbox
158+ checked = { isVisible }
159+ onChange = { ( e ) => onVisibilityChange ( col . field , e . target . checked ) }
160+ disabled = { ! isHideable }
161+ />
162+ < span className = "ogx-column-visibility-panel__label" >
163+ { col . headerName || col . field }
164+ </ span >
165+ </ label >
166+ </ div >
84167 ) ;
85168 } ) }
86169 </ div >
0 commit comments