|
| 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