Skip to content

Commit ac6e193

Browse files
KenanYusufMBilalShaficherniavskii
authored
[docs] Add a recipe for drag and drop row grouping (#17638)
Signed-off-by: Kenan Yusuf <kenan.m.yusuf@gmail.com> Co-authored-by: Bilal Shafi <bilalshafidev@gmail.com> Co-authored-by: Andrew Cherniavskii <andrew.cherniavskii@gmail.com>
1 parent d8dcbed commit ac6e193

File tree

12 files changed

+744
-100
lines changed

12 files changed

+744
-100
lines changed
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
import * as React from 'react';
2+
import {
3+
DataGridPremium,
4+
Toolbar,
5+
ToolbarButton,
6+
useGridSelector,
7+
useGridApiContext,
8+
gridColumnLookupSelector,
9+
gridColumnReorderDragColSelector,
10+
gridRowGroupingSanitizedModelSelector,
11+
useGridApiRef,
12+
useKeepGroupedColumnsHidden,
13+
} from '@mui/x-data-grid-premium';
14+
import { useDemoData } from '@mui/x-data-grid-generator';
15+
import Typography from '@mui/material/Typography';
16+
import Chip from '@mui/material/Chip';
17+
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
18+
import MoveUpIcon from '@mui/icons-material/MoveUp';
19+
import { styled } from '@mui/material/styles';
20+
21+
const StyledToolbar = styled(Toolbar)(({ theme }) => ({
22+
justifyContent: 'flex-start',
23+
overflow: 'auto',
24+
padding: theme.spacing(1, 1.5),
25+
gap: theme.spacing(0.75),
26+
}));
27+
28+
function CustomToolbar() {
29+
const apiRef = useGridApiContext();
30+
const rowGroupingModel = useGridSelector(
31+
apiRef,
32+
gridRowGroupingSanitizedModelSelector,
33+
);
34+
const columnsLookup = useGridSelector(apiRef, gridColumnLookupSelector);
35+
const draggedColumn = useGridSelector(apiRef, gridColumnReorderDragColSelector);
36+
const [draggedChip, setDraggedChip] = React.useState(null);
37+
38+
const handleToolbarDragOver = (event) => {
39+
event.preventDefault();
40+
41+
if (
42+
draggedColumn &&
43+
!rowGroupingModel.includes(draggedColumn) &&
44+
columnsLookup[draggedColumn].groupable
45+
) {
46+
apiRef.current.addRowGroupingCriteria(draggedColumn);
47+
}
48+
};
49+
50+
const handleToolbarDragLeave = (event) => {
51+
event.preventDefault();
52+
53+
if (draggedColumn && !event.currentTarget.contains(event.relatedTarget)) {
54+
apiRef.current.removeRowGroupingCriteria(draggedColumn);
55+
}
56+
};
57+
58+
const handleChipDragStart = (field) => (event) => {
59+
setDraggedChip(field);
60+
event.dataTransfer.effectAllowed = 'move';
61+
};
62+
63+
const handleChipDragEnd = () => {
64+
setDraggedChip(null);
65+
};
66+
67+
const handleChipDragOver = (targetField) => (event) => {
68+
event.preventDefault();
69+
70+
const draggedField = draggedChip || draggedColumn;
71+
if (!draggedField || draggedField === targetField) {
72+
return;
73+
}
74+
75+
const currentIndex = rowGroupingModel.indexOf(draggedField);
76+
const targetIndex = rowGroupingModel.indexOf(targetField);
77+
78+
if (currentIndex === -1 || targetIndex === -1) {
79+
return;
80+
}
81+
82+
if (currentIndex !== targetIndex) {
83+
const newModel = [...rowGroupingModel];
84+
newModel.splice(currentIndex, 1);
85+
newModel.splice(targetIndex, 0, draggedField);
86+
apiRef.current.setRowGroupingModel(newModel);
87+
}
88+
};
89+
90+
const moveRowGroup = (field, position) => {
91+
if (position < 0 || position > rowGroupingModel.length) {
92+
return;
93+
}
94+
95+
const currentIndex = rowGroupingModel.indexOf(field);
96+
const newModel = [...rowGroupingModel];
97+
newModel.splice(currentIndex, 1);
98+
newModel.splice(position, 0, field);
99+
apiRef.current.setRowGroupingModel(newModel);
100+
};
101+
102+
const removeRowGroup = (field) => {
103+
if (columnsLookup[field].groupable) {
104+
apiRef.current.removeRowGroupingCriteria(field);
105+
}
106+
};
107+
108+
return (
109+
<StyledToolbar
110+
aria-label="Row grouping"
111+
onDragOver={handleToolbarDragOver}
112+
onDragLeave={handleToolbarDragLeave}
113+
>
114+
<MoveUpIcon fontSize="small" color="action" sx={{ mr: 0.75 }} />
115+
{rowGroupingModel.length > 0 ? (
116+
<React.Fragment>
117+
{rowGroupingModel.map((field, index) => {
118+
const isDraggedField = draggedChip === field || draggedColumn === field;
119+
const isGroupable = columnsLookup[field].groupable;
120+
const label = columnsLookup[field].headerName ?? field;
121+
122+
return (
123+
<React.Fragment key={field}>
124+
{index > 0 && <ChevronRightIcon fontSize="small" color="action" />}
125+
<ToolbarButton
126+
id={field}
127+
render={({
128+
children,
129+
color,
130+
size,
131+
ref,
132+
onKeyDown,
133+
...chipProps
134+
}) => {
135+
const handleKeyDown = (event) => {
136+
if (event.key === 'ArrowRight' && event.shiftKey) {
137+
moveRowGroup(field, index + 1);
138+
} else if (event.key === 'ArrowLeft' && event.shiftKey) {
139+
moveRowGroup(field, index - 1);
140+
} else {
141+
onKeyDown?.(event);
142+
}
143+
};
144+
145+
return (
146+
<Chip
147+
{...chipProps}
148+
ref={ref}
149+
label={label}
150+
sx={{ cursor: 'grab', opacity: isDraggedField ? 0.5 : 1 }}
151+
onDelete={() => removeRowGroup(field)}
152+
deleteIcon={!isGroupable ? <span /> : undefined}
153+
onKeyDown={handleKeyDown}
154+
onDragStart={handleChipDragStart(field)}
155+
onDragEnd={handleChipDragEnd}
156+
onDragOver={handleChipDragOver(field)}
157+
draggable
158+
/>
159+
);
160+
}}
161+
/>
162+
</React.Fragment>
163+
);
164+
})}
165+
</React.Fragment>
166+
) : (
167+
<Typography variant="body2" color="textSecondary" sx={{ flex: 1 }} noWrap>
168+
Drag columns here to create row groups
169+
</Typography>
170+
)}
171+
</StyledToolbar>
172+
);
173+
}
174+
175+
export default function GridToolbarRowGrouping() {
176+
const apiRef = useGridApiRef();
177+
178+
const { data, loading } = useDemoData({
179+
dataSet: 'Commodity',
180+
rowLength: 100,
181+
maxColumns: 10,
182+
});
183+
184+
const initialState = useKeepGroupedColumnsHidden({
185+
apiRef,
186+
initialState: {
187+
rowGrouping: {
188+
model: ['status', 'commodity'],
189+
},
190+
},
191+
});
192+
193+
return (
194+
<div style={{ height: 400, width: '100%' }}>
195+
<DataGridPremium
196+
{...data}
197+
apiRef={apiRef}
198+
loading={loading}
199+
initialState={initialState}
200+
slots={{ toolbar: CustomToolbar }}
201+
showToolbar
202+
/>
203+
</div>
204+
);
205+
}

0 commit comments

Comments
 (0)