diff --git a/docs/data/data-grid/master-detail/master-detail.md b/docs/data/data-grid/master-detail/master-detail.md index 679a57852f155..0af556b2bd039 100644 --- a/docs/data/data-grid/master-detail/master-detail.md +++ b/docs/data/data-grid/master-detail/master-detail.md @@ -135,6 +135,31 @@ This approach can also be used to change the location of the toggle column, as s As any ordinary cell renderer, the `value` prop is also available, and it corresponds to the state of the row: `true` when expanded and `false` when collapsed. ::: +## Custom header for detail panel column + +To render a custom header for the detail panel column, use the [`renderHeader`](/x/react-data-grid/column-header/#custom-header-renderer) property in the column definition. +This property receives a `GridRenderHeaderParams` object that contains `colDef` (the column definition) and `field`. +The following example demonstrates how to render a custom header for the detail panel column: + +```tsx +const columns = [ + { + ...GRID_DETAIL_PANEL_TOGGLE_COL_DEF, + renderHeader: (params) => ( +
+ {params.colDef.headerName} + +
+ ), + }, + //... other columns +]; +``` + +:::info +For a more advanced example check out the [Expand or collapse all detail panels](/x/react-data-grid/row-recipes/#expand-or-collapse-all-detail-panels) recipe. +::: + ## Disable detail panel content scroll By default, the detail panel has a width that is the sum of the widths of all columns. @@ -153,6 +178,7 @@ Notice that the toggle column is pinned to make sure that it will always be visi More examples of how to customize the detail panel: - [One expanded detail panel at a time](/x/react-data-grid/row-recipes/#one-expanded-detail-panel-at-a-time) +- [Expand or collapse all detail panels](/x/react-data-grid/row-recipes/#expand-or-collapse-all-detail-panels) ## apiRef diff --git a/docs/data/data-grid/row-recipes/DetailPanelCollapseAll.tsx.preview b/docs/data/data-grid/row-recipes/DetailPanelCollapseAll.tsx.preview new file mode 100644 index 0000000000000..d89161b50e8d3 --- /dev/null +++ b/docs/data/data-grid/row-recipes/DetailPanelCollapseAll.tsx.preview @@ -0,0 +1,6 @@ + \ No newline at end of file diff --git a/docs/data/data-grid/row-recipes/DetailPanelExpandCollapseAll.js b/docs/data/data-grid/row-recipes/DetailPanelExpandCollapseAll.js new file mode 100644 index 0000000000000..cd48306cf1ba8 --- /dev/null +++ b/docs/data/data-grid/row-recipes/DetailPanelExpandCollapseAll.js @@ -0,0 +1,134 @@ +import * as React from 'react'; +import Box from '@mui/material/Box'; +import IconButton from '@mui/material/IconButton'; +import UnfoldLessIcon from '@mui/icons-material/UnfoldLess'; +import UnfoldMoreIcon from '@mui/icons-material/UnfoldMore'; +import { + DataGridPro, + useGridApiContext, + useGridSelector, + gridRowsLookupSelector, + gridDetailPanelExpandedRowIdsSelector, + gridDetailPanelExpandedRowsContentCacheSelector, + GRID_DETAIL_PANEL_TOGGLE_COL_DEF, +} from '@mui/x-data-grid-pro'; +import { + randomCreatedDate, + randomCurrency, + randomEmail, + randomPrice, +} from '@mui/x-data-grid-generator'; + +export default function DetailPanelExpandCollapseAll() { + const getDetailPanelContent = React.useCallback( + ({ row }) => {`Order #${row.id}`}, + [], + ); + + const getDetailPanelHeight = React.useCallback(() => 50, []); + + return ( +
+ +
+ ); +} + +function CustomDetailPanelHeader() { + const apiRef = useGridApiContext(); + + const expandedRowIds = useGridSelector( + apiRef, + gridDetailPanelExpandedRowIdsSelector, + ); + const rowsWithDetailPanels = useGridSelector( + apiRef, + gridDetailPanelExpandedRowsContentCacheSelector, + ); + + const noDetailPanelsOpen = expandedRowIds.length === 0; + + const expandOrCollapseAll = () => { + const dataRowIdToModelLookup = gridRowsLookupSelector(apiRef); + const allRowIdsWithDetailPanels = Object.keys(rowsWithDetailPanels).map((key) => + apiRef.current.getRowId(dataRowIdToModelLookup[key]), + ); + + apiRef.current.setExpandedDetailPanels( + noDetailPanelsOpen ? allRowIdsWithDetailPanels : [], + ); + }; + + const Icon = noDetailPanelsOpen ? UnfoldMoreIcon : UnfoldLessIcon; + + return ( + + + + ); +} + +const columns = [ + { + ...GRID_DETAIL_PANEL_TOGGLE_COL_DEF, + renderHeader: () => , + }, + { field: 'id', headerName: 'Order ID' }, + { field: 'customer', headerName: 'Customer', width: 200 }, + { field: 'date', type: 'date', headerName: 'Placed at' }, + { field: 'currency', headerName: 'Currency' }, + { field: 'total', type: 'number', headerName: 'Total' }, +]; + +const rows = [ + { + id: 1, + customer: 'Matheus', + email: randomEmail(), + date: randomCreatedDate(), + currency: randomCurrency(), + total: randomPrice(1, 1000), + }, + { + id: 2, + customer: 'Olivier', + email: randomEmail(), + date: randomCreatedDate(), + currency: randomCurrency(), + total: randomPrice(1, 1000), + }, + { + id: 3, + customer: 'Flavien', + email: randomEmail(), + date: randomCreatedDate(), + currency: randomCurrency(), + total: randomPrice(1, 1000), + }, + { + id: 4, + customer: 'Danail', + email: randomEmail(), + date: randomCreatedDate(), + currency: randomCurrency(), + total: randomPrice(1, 1000), + }, + { + id: 5, + customer: 'Alexandre', + email: randomEmail(), + date: randomCreatedDate(), + currency: randomCurrency(), + total: randomPrice(1, 1000), + }, +]; diff --git a/docs/data/data-grid/row-recipes/DetailPanelExpandCollapseAll.tsx b/docs/data/data-grid/row-recipes/DetailPanelExpandCollapseAll.tsx new file mode 100644 index 0000000000000..a85385042f8c7 --- /dev/null +++ b/docs/data/data-grid/row-recipes/DetailPanelExpandCollapseAll.tsx @@ -0,0 +1,138 @@ +import * as React from 'react'; +import Box from '@mui/material/Box'; +import IconButton from '@mui/material/IconButton'; +import UnfoldLessIcon from '@mui/icons-material/UnfoldLess'; +import UnfoldMoreIcon from '@mui/icons-material/UnfoldMore'; +import { + DataGridPro, + GridColDef, + GridRowsProp, + GridRowParams, + useGridApiContext, + useGridSelector, + gridRowsLookupSelector, + gridDetailPanelExpandedRowIdsSelector, + gridDetailPanelExpandedRowsContentCacheSelector, + GRID_DETAIL_PANEL_TOGGLE_COL_DEF, + GridRowId, +} from '@mui/x-data-grid-pro'; +import { + randomCreatedDate, + randomCurrency, + randomEmail, + randomPrice, +} from '@mui/x-data-grid-generator'; + +export default function DetailPanelExpandCollapseAll() { + const getDetailPanelContent = React.useCallback( + ({ row }: GridRowParams) => {`Order #${row.id}`}, + [], + ); + + const getDetailPanelHeight = React.useCallback(() => 50, []); + + return ( +
+ +
+ ); +} + +function CustomDetailPanelHeader() { + const apiRef = useGridApiContext(); + + const expandedRowIds = useGridSelector( + apiRef, + gridDetailPanelExpandedRowIdsSelector, + ); + const rowsWithDetailPanels = useGridSelector( + apiRef, + gridDetailPanelExpandedRowsContentCacheSelector, + ); + + const noDetailPanelsOpen = expandedRowIds.length === 0; + + const expandOrCollapseAll = () => { + const dataRowIdToModelLookup = gridRowsLookupSelector(apiRef); + const allRowIdsWithDetailPanels: GridRowId[] = Object.keys( + rowsWithDetailPanels, + ).map((key) => apiRef.current.getRowId(dataRowIdToModelLookup[key])); + + apiRef.current.setExpandedDetailPanels( + noDetailPanelsOpen ? allRowIdsWithDetailPanels : [], + ); + }; + + const Icon = noDetailPanelsOpen ? UnfoldMoreIcon : UnfoldLessIcon; + + return ( + + + + ); +} + +const columns: GridColDef[] = [ + { + ...GRID_DETAIL_PANEL_TOGGLE_COL_DEF, + renderHeader: () => , + }, + { field: 'id', headerName: 'Order ID' }, + { field: 'customer', headerName: 'Customer', width: 200 }, + { field: 'date', type: 'date', headerName: 'Placed at' }, + { field: 'currency', headerName: 'Currency' }, + { field: 'total', type: 'number', headerName: 'Total' }, +]; + +const rows: GridRowsProp = [ + { + id: 1, + customer: 'Matheus', + email: randomEmail(), + date: randomCreatedDate(), + currency: randomCurrency(), + total: randomPrice(1, 1000), + }, + { + id: 2, + customer: 'Olivier', + email: randomEmail(), + date: randomCreatedDate(), + currency: randomCurrency(), + total: randomPrice(1, 1000), + }, + { + id: 3, + customer: 'Flavien', + email: randomEmail(), + date: randomCreatedDate(), + currency: randomCurrency(), + total: randomPrice(1, 1000), + }, + { + id: 4, + customer: 'Danail', + email: randomEmail(), + date: randomCreatedDate(), + currency: randomCurrency(), + total: randomPrice(1, 1000), + }, + { + id: 5, + customer: 'Alexandre', + email: randomEmail(), + date: randomCreatedDate(), + currency: randomCurrency(), + total: randomPrice(1, 1000), + }, +]; diff --git a/docs/data/data-grid/row-recipes/DetailPanelExpandCollapseAll.tsx.preview b/docs/data/data-grid/row-recipes/DetailPanelExpandCollapseAll.tsx.preview new file mode 100644 index 0000000000000..d89161b50e8d3 --- /dev/null +++ b/docs/data/data-grid/row-recipes/DetailPanelExpandCollapseAll.tsx.preview @@ -0,0 +1,6 @@ + \ No newline at end of file diff --git a/docs/data/data-grid/row-recipes/row-recipes.md b/docs/data/data-grid/row-recipes/row-recipes.md index b032a14754a4a..6404572a316ca 100644 --- a/docs/data/data-grid/row-recipes/row-recipes.md +++ b/docs/data/data-grid/row-recipes/row-recipes.md @@ -13,3 +13,15 @@ By default, the [Master detail ](/x/react-data-grid/mas However, you can [control the expanded detail panels](/x/react-data-grid/master-detail/#controlling-expanded-detail-panels) to have only one detail panel expanded at a time. {{"demo": "DetailPanelOneExpandedRow.js", "bg": "inline", "defaultCodeOpen": false}} + +## Expand or collapse all detail panels + +The following demo shows how to create a custom header element that expands or collapses all detail panels at once. + +Here's how it works: + +The custom header uses `gridRowsLookupSelector` to find all rows with a detail panel. +It checks the status of open panels using the [`useGridSelector` hook](/x/react-data-grid/state/#with-usegridselector) to access the grid's state. +When clicked, it uses [`setExpandedDetailPanels`](/x/api/data-grid/grid-api/#grid-api-prop-setExpandedDetailPanels) from the [Grid API](/x/react-data-grid/api-object/#how-to-use-the-api-object) to expand or collapse all detail panels. + +{{"demo": "DetailPanelExpandCollapseAll.js", "bg": "inline", "defaultCodeOpen": false}}