From 0fd9304fd8c73a44758a6116252130960a0b2133 Mon Sep 17 00:00:00 2001 From: Andrew Cherniavskyi Date: Fri, 4 Oct 2024 13:28:13 +0200 Subject: [PATCH 1/4] add demo --- .../components/CustomColumnsPanel.js | 178 ++++++++++++++++ .../components/CustomColumnsPanel.tsx | 198 ++++++++++++++++++ .../components/CustomColumnsPanel.tsx.preview | 8 + docs/data/data-grid/components/components.md | 6 + 4 files changed, 390 insertions(+) create mode 100644 docs/data/data-grid/components/CustomColumnsPanel.js create mode 100644 docs/data/data-grid/components/CustomColumnsPanel.tsx create mode 100644 docs/data/data-grid/components/CustomColumnsPanel.tsx.preview diff --git a/docs/data/data-grid/components/CustomColumnsPanel.js b/docs/data/data-grid/components/CustomColumnsPanel.js new file mode 100644 index 0000000000000..99404506decb4 --- /dev/null +++ b/docs/data/data-grid/components/CustomColumnsPanel.js @@ -0,0 +1,178 @@ +import * as React from 'react'; +import Box from '@mui/material/Box'; +import { + DataGrid, + gridColumnVisibilityModelSelector, + useGridApiContext, + useGridRootProps, + isLeaf, + gridColumnLookupSelector, + useGridSelector, + GridColumnsPanel, +} from '@mui/x-data-grid'; +import Checkbox from '@mui/material/Checkbox'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import Stack from '@mui/material/Stack'; + +const getColumnGroupLeaves = (group, callback) => { + group.children.forEach((child) => { + if (isLeaf(child)) { + callback(child.field); + } else { + getColumnGroupLeaves(child, callback); + } + }); +}; + +function ColumnGroup({ group, columnLookup, apiRef, columnVisibilityModel }) { + const leaves = React.useMemo(() => { + const fields = []; + getColumnGroupLeaves(group, (field) => fields.push(field)); + return fields; + }, [group]); + + const { isGroupChecked, isGroupIndeterminate } = React.useMemo(() => { + return { + isGroupChecked: leaves.every( + (field) => columnVisibilityModel[field] !== false, + ), + isGroupIndeterminate: + leaves.some((field) => columnVisibilityModel[field] === false) && + !leaves.every((field) => columnVisibilityModel[field] === false), + }; + }, [columnVisibilityModel, leaves]); + + const toggleColumnGroup = (checked) => { + const newColumnVisibilityModel = { + ...columnVisibilityModel, + }; + getColumnGroupLeaves(group, (field) => { + newColumnVisibilityModel[field] = checked; + }); + apiRef.current.setColumnVisibilityModel(newColumnVisibilityModel); + }; + + const toggleColumn = (field, checked) => { + apiRef.current.setColumnVisibility(field, checked); + }; + + return ( +
+ + } + label={group.headerName ?? group.groupId} + onChange={(_, newValue) => toggleColumnGroup(newValue)} + /> + + {group.children.map((child) => { + return isLeaf(child) ? ( + + + } + label={columnLookup[child.field].headerName ?? child.field} + onChange={(_, newValue) => toggleColumn(child.field, newValue)} + /> + + ) : ( + + ); + })} + +
+ ); +} + +function ColumnsPanel(props) { + const apiRef = useGridApiContext(); + const rootProps = useGridRootProps(); + const columnLookup = useGridSelector(apiRef, gridColumnLookupSelector); + const columnVisibilityModel = useGridSelector( + apiRef, + gridColumnVisibilityModelSelector, + ); + + const columnGroupingModel = rootProps.columnGroupingModel; + + if (!columnGroupingModel) { + return ; + } + + return ( + + {columnGroupingModel.map((group) => ( + + ))} + + ); +} + +const columnGroupingModel = [ + { + groupId: 'Internal', + description: '', + children: [{ field: 'id' }], + }, + { + groupId: 'character', + description: 'Information about the character', + headerName: 'Basic info', + children: [ + { + groupId: 'naming', + headerName: 'Names', + children: [{ field: 'lastName' }, { field: 'firstName' }], + }, + { field: 'age' }, + ], + }, +]; + +const columns = [ + { field: 'id', headerName: 'ID', width: 150 }, + { field: 'firstName', headerName: 'First name', width: 150 }, + { field: 'lastName', headerName: 'Last name', width: 150 }, + { field: 'age', headerName: 'Age', type: 'number', width: 110 }, +]; + +const rows = [ + { id: 1, lastName: 'Snow', firstName: 'Jon', age: 35 }, + { id: 2, lastName: 'Lannister', firstName: 'Cersei', age: 42 }, + { id: 3, lastName: 'Lannister', firstName: 'Jaime', age: 45 }, + { id: 4, lastName: 'Stark', firstName: 'Arya', age: 16 }, + { id: 5, lastName: 'Targaryen', firstName: 'Daenerys', age: null }, + { id: 6, lastName: 'Melisandre', firstName: null, age: 150 }, + { id: 7, lastName: 'Clifford', firstName: 'Ferrara', age: 44 }, + { id: 8, lastName: 'Frances', firstName: 'Rossini', age: 36 }, + { id: 9, lastName: 'Roxie', firstName: 'Harvey', age: 65 }, +]; + +export default function CustomColumnsPanel() { + return ( + + + + ); +} diff --git a/docs/data/data-grid/components/CustomColumnsPanel.tsx b/docs/data/data-grid/components/CustomColumnsPanel.tsx new file mode 100644 index 0000000000000..a06c94be86c79 --- /dev/null +++ b/docs/data/data-grid/components/CustomColumnsPanel.tsx @@ -0,0 +1,198 @@ +import * as React from 'react'; +import Box from '@mui/material/Box'; +import { + DataGrid, + GridColDef, + GridColumnGroup, + GridColumnGroupingModel, + GridColumnVisibilityModel, + gridColumnVisibilityModelSelector, + useGridApiContext, + useGridRootProps, + isLeaf, + GridApi, + gridColumnLookupSelector, + GridColumnLookup, + useGridSelector, + GridColumnsPanelProps, + GridColumnsPanel, +} from '@mui/x-data-grid'; +import Checkbox from '@mui/material/Checkbox'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import Stack from '@mui/material/Stack'; + +const getColumnGroupLeaves = ( + group: GridColumnGroup, + callback: (field: string) => void, +) => { + group.children.forEach((child) => { + if (isLeaf(child)) { + callback(child.field); + } else { + getColumnGroupLeaves(child, callback); + } + }); +}; + +function ColumnGroup({ + group, + columnLookup, + apiRef, + columnVisibilityModel, +}: { + group: GridColumnGroup; + columnLookup: GridColumnLookup; + apiRef: React.MutableRefObject; + columnVisibilityModel: GridColumnVisibilityModel; +}) { + const leaves = React.useMemo(() => { + const fields: string[] = []; + getColumnGroupLeaves(group, (field) => fields.push(field)); + return fields; + }, [group]); + + const { isGroupChecked, isGroupIndeterminate } = React.useMemo(() => { + return { + isGroupChecked: leaves.every( + (field) => columnVisibilityModel[field] !== false, + ), + isGroupIndeterminate: + leaves.some((field) => columnVisibilityModel[field] === false) && + !leaves.every((field) => columnVisibilityModel[field] === false), + }; + }, [columnVisibilityModel, leaves]); + + const toggleColumnGroup = (checked: boolean) => { + const newColumnVisibilityModel = { + ...columnVisibilityModel, + }; + getColumnGroupLeaves(group, (field) => { + newColumnVisibilityModel[field] = checked; + }); + apiRef.current.setColumnVisibilityModel(newColumnVisibilityModel); + }; + + const toggleColumn = (field: string, checked: boolean) => { + apiRef.current.setColumnVisibility(field, checked); + }; + + return ( +
+ + } + label={group.headerName ?? group.groupId} + onChange={(_, newValue) => toggleColumnGroup(newValue)} + /> + + {group.children.map((child) => { + return isLeaf(child) ? ( + + + } + label={columnLookup[child.field].headerName ?? child.field} + onChange={(_, newValue) => toggleColumn(child.field, newValue)} + /> + + ) : ( + + ); + })} + +
+ ); +} + +function ColumnsPanel(props: GridColumnsPanelProps) { + const apiRef = useGridApiContext(); + const rootProps = useGridRootProps(); + const columnLookup = useGridSelector(apiRef, gridColumnLookupSelector); + const columnVisibilityModel = useGridSelector( + apiRef, + gridColumnVisibilityModelSelector, + ); + + const columnGroupingModel = rootProps.columnGroupingModel; + + if (!columnGroupingModel) { + return ; + } + + return ( + + {columnGroupingModel.map((group) => ( + + ))} + + ); +} + +const columnGroupingModel: GridColumnGroupingModel = [ + { + groupId: 'Internal', + description: '', + children: [{ field: 'id' }], + }, + { + groupId: 'character', + description: 'Information about the character', + headerName: 'Basic info', + children: [ + { + groupId: 'naming', + headerName: 'Names', + children: [{ field: 'lastName' }, { field: 'firstName' }], + }, + { field: 'age' }, + ], + }, +]; + +const columns: GridColDef[] = [ + { field: 'id', headerName: 'ID', width: 150 }, + { field: 'firstName', headerName: 'First name', width: 150 }, + { field: 'lastName', headerName: 'Last name', width: 150 }, + { field: 'age', headerName: 'Age', type: 'number', width: 110 }, +]; + +const rows = [ + { id: 1, lastName: 'Snow', firstName: 'Jon', age: 35 }, + { id: 2, lastName: 'Lannister', firstName: 'Cersei', age: 42 }, + { id: 3, lastName: 'Lannister', firstName: 'Jaime', age: 45 }, + { id: 4, lastName: 'Stark', firstName: 'Arya', age: 16 }, + { id: 5, lastName: 'Targaryen', firstName: 'Daenerys', age: null }, + { id: 6, lastName: 'Melisandre', firstName: null, age: 150 }, + { id: 7, lastName: 'Clifford', firstName: 'Ferrara', age: 44 }, + { id: 8, lastName: 'Frances', firstName: 'Rossini', age: 36 }, + { id: 9, lastName: 'Roxie', firstName: 'Harvey', age: 65 }, +]; + +export default function CustomColumnsPanel() { + return ( + + + + ); +} diff --git a/docs/data/data-grid/components/CustomColumnsPanel.tsx.preview b/docs/data/data-grid/components/CustomColumnsPanel.tsx.preview new file mode 100644 index 0000000000000..b3d1c3fc21dc7 --- /dev/null +++ b/docs/data/data-grid/components/CustomColumnsPanel.tsx.preview @@ -0,0 +1,8 @@ + \ No newline at end of file diff --git a/docs/data/data-grid/components/components.md b/docs/data/data-grid/components/components.md index af1eb59c460f9..7100d4b943b60 100644 --- a/docs/data/data-grid/components/components.md +++ b/docs/data/data-grid/components/components.md @@ -36,6 +36,12 @@ function CustomPagination() { ::: +### Columns panel + +In the following demo, the columns panel is replaced with a custom component that represents the [column groups](/x/react-data-grid/column-groups/) as a nested list. + +{{"demo": "CustomColumnsPanel.js", "bg": "inline"}} + ### Column menu As mentioned above, the column menu is a component slot that can be recomposed easily and customized on each column as in the demo below. From 0a1098770261ac07e4620b1329d09f0d9c592198 Mon Sep 17 00:00:00 2001 From: Andrew Cherniavskyi Date: Mon, 7 Oct 2024 22:49:57 +0200 Subject: [PATCH 2/4] open columns panel on mount --- docs/data/data-grid/components/CustomColumnsPanel.js | 7 +++++++ docs/data/data-grid/components/CustomColumnsPanel.tsx | 7 +++++++ .../data-grid/components/CustomColumnsPanel.tsx.preview | 6 ++++++ 3 files changed, 20 insertions(+) diff --git a/docs/data/data-grid/components/CustomColumnsPanel.js b/docs/data/data-grid/components/CustomColumnsPanel.js index 99404506decb4..f1b0928ab4495 100644 --- a/docs/data/data-grid/components/CustomColumnsPanel.js +++ b/docs/data/data-grid/components/CustomColumnsPanel.js @@ -9,6 +9,7 @@ import { gridColumnLookupSelector, useGridSelector, GridColumnsPanel, + GridPreferencePanelsValue, } from '@mui/x-data-grid'; import Checkbox from '@mui/material/Checkbox'; import FormControlLabel from '@mui/material/FormControlLabel'; @@ -172,6 +173,12 @@ export default function CustomColumnsPanel() { disableRowSelectionOnClick columnGroupingModel={columnGroupingModel} slots={{ columnsPanel: ColumnsPanel }} + initialState={{ + preferencePanel: { + open: true, + openedPanelValue: GridPreferencePanelsValue.columns, + }, + }} /> ); diff --git a/docs/data/data-grid/components/CustomColumnsPanel.tsx b/docs/data/data-grid/components/CustomColumnsPanel.tsx index a06c94be86c79..c10ed51a04119 100644 --- a/docs/data/data-grid/components/CustomColumnsPanel.tsx +++ b/docs/data/data-grid/components/CustomColumnsPanel.tsx @@ -16,6 +16,7 @@ import { useGridSelector, GridColumnsPanelProps, GridColumnsPanel, + GridPreferencePanelsValue, } from '@mui/x-data-grid'; import Checkbox from '@mui/material/Checkbox'; import FormControlLabel from '@mui/material/FormControlLabel'; @@ -192,6 +193,12 @@ export default function CustomColumnsPanel() { disableRowSelectionOnClick columnGroupingModel={columnGroupingModel} slots={{ columnsPanel: ColumnsPanel }} + initialState={{ + preferencePanel: { + open: true, + openedPanelValue: GridPreferencePanelsValue.columns, + }, + }} /> ); diff --git a/docs/data/data-grid/components/CustomColumnsPanel.tsx.preview b/docs/data/data-grid/components/CustomColumnsPanel.tsx.preview index b3d1c3fc21dc7..4b909c52b3d72 100644 --- a/docs/data/data-grid/components/CustomColumnsPanel.tsx.preview +++ b/docs/data/data-grid/components/CustomColumnsPanel.tsx.preview @@ -5,4 +5,10 @@ disableRowSelectionOnClick columnGroupingModel={columnGroupingModel} slots={{ columnsPanel: ColumnsPanel }} + initialState={{ + preferencePanel: { + open: true, + openedPanelValue: GridPreferencePanelsValue.columns, + }, + }} /> \ No newline at end of file From f2cf3820fbfb667366d74c358e70bf7082cc9d20 Mon Sep 17 00:00:00 2001 From: Andrew Cherniavskyi Date: Mon, 7 Oct 2024 23:05:20 +0200 Subject: [PATCH 3/4] make columns panel more compact --- .../data-grid/components/CustomColumnsPanel.js | 17 +++++++++++++---- .../data-grid/components/CustomColumnsPanel.tsx | 17 +++++++++++++---- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/docs/data/data-grid/components/CustomColumnsPanel.js b/docs/data/data-grid/components/CustomColumnsPanel.js index f1b0928ab4495..bac2bd9121b1c 100644 --- a/docs/data/data-grid/components/CustomColumnsPanel.js +++ b/docs/data/data-grid/components/CustomColumnsPanel.js @@ -61,18 +61,27 @@ function ColumnGroup({ group, columnLookup, apiRef, columnVisibilityModel }) {
+ } label={group.headerName ?? group.groupId} onChange={(_, newValue) => toggleColumnGroup(newValue)} /> - + {group.children.map((child) => { return isLeaf(child) ? ( + } label={columnLookup[child.field].headerName ?? child.field} onChange={(_, newValue) => toggleColumn(child.field, newValue)} @@ -109,7 +118,7 @@ function ColumnsPanel(props) { } return ( - + {columnGroupingModel.map((group) => ( + } label={group.headerName ?? group.groupId} onChange={(_, newValue) => toggleColumnGroup(newValue)} /> - + {group.children.map((child) => { return isLeaf(child) ? ( + } label={columnLookup[child.field].headerName ?? child.field} onChange={(_, newValue) => toggleColumn(child.field, newValue)} @@ -129,7 +138,7 @@ function ColumnsPanel(props: GridColumnsPanelProps) { } return ( - + {columnGroupingModel.map((group) => ( Date: Tue, 8 Oct 2024 15:13:09 +0200 Subject: [PATCH 4/4] increase offset for nested items --- docs/data/data-grid/components/CustomColumnsPanel.js | 2 +- docs/data/data-grid/components/CustomColumnsPanel.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/data/data-grid/components/CustomColumnsPanel.js b/docs/data/data-grid/components/CustomColumnsPanel.js index bac2bd9121b1c..82f4089edd8ce 100644 --- a/docs/data/data-grid/components/CustomColumnsPanel.js +++ b/docs/data/data-grid/components/CustomColumnsPanel.js @@ -71,7 +71,7 @@ function ColumnGroup({ group, columnLookup, apiRef, columnVisibilityModel }) { label={group.headerName ?? group.groupId} onChange={(_, newValue) => toggleColumnGroup(newValue)} /> - + {group.children.map((child) => { return isLeaf(child) ? ( diff --git a/docs/data/data-grid/components/CustomColumnsPanel.tsx b/docs/data/data-grid/components/CustomColumnsPanel.tsx index 11b26812d241b..1516ba3e1cf7b 100644 --- a/docs/data/data-grid/components/CustomColumnsPanel.tsx +++ b/docs/data/data-grid/components/CustomColumnsPanel.tsx @@ -91,7 +91,7 @@ function ColumnGroup({ label={group.headerName ?? group.groupId} onChange={(_, newValue) => toggleColumnGroup(newValue)} /> - + {group.children.map((child) => { return isLeaf(child) ? (