From 1ac64ba17a25565d3e186cd80b04218f41b84be7 Mon Sep 17 00:00:00 2001 From: Matheus Wichman Date: Wed, 9 Mar 2022 15:12:56 -0300 Subject: [PATCH] [DataGrid] Add support for margin between rows (#3848) --- docs/data/data-grid/rows/RowMarginGrid.js | 34 ++++ docs/data/data-grid/rows/RowMarginGrid.tsx | 34 ++++ .../data-grid/rows/RowMarginGrid.tsx.preview | 10 + docs/data/data-grid/rows/rows.md | 31 +++ .../api-docs/data-grid/data-grid-pro.json | 5 + docs/pages/api-docs/data-grid/data-grid.json | 5 + docs/pages/api-docs/data-grid/grid-api.md | 1 + .../data-grid/grid-row-class-name-params.js | 7 + .../data-grid/grid-row-class-name-params.md | 22 +++ .../data-grid/grid-row-spacing-params.js | 7 + .../data-grid/grid-row-spacing-params.md | 20 ++ docs/pages/x/api/data-grid/data-grid-pro.json | 5 + docs/pages/x/api/data-grid/data-grid.json | 5 + docs/pages/x/api/data-grid/grid-api.md | 1 + .../data-grid/grid-row-class-name-params.js | 7 + .../data-grid/grid-row-class-name-params.md | 22 +++ .../api/data-grid/grid-row-spacing-params.js | 7 + .../api/data-grid/grid-row-spacing-params.md | 20 ++ .../api/buildInterfacesDocumentation.ts | 2 + .../api-docs/data-grid/data-grid-pro-pt.json | 4 +- .../api-docs/data-grid/data-grid-pro-zh.json | 4 +- .../api-docs/data-grid/data-grid-pro.json | 4 +- .../api-docs/data-grid/data-grid-pt.json | 4 +- .../api-docs/data-grid/data-grid-zh.json | 4 +- .../api-docs/data-grid/data-grid.json | 4 +- .../src/DataGridPro/DataGridPro.tsx | 13 +- .../components/DataGridProVirtualScroller.tsx | 22 +-- .../x-data-grid/src/DataGrid/DataGrid.tsx | 13 +- .../src/DataGrid/useDataGridProps.ts | 1 + .../x-data-grid/src/components/GridRow.tsx | 32 +++- .../GridVirtualScrollerRenderZone.tsx | 2 + .../src/hooks/features/rows/useGridRows.ts | 14 ++ .../hooks/features/rows/useGridRowsMeta.ts | 49 +++-- .../virtualization/useGridVirtualScroller.tsx | 2 +- .../x-data-grid/src/models/api/gridRowApi.ts | 6 + .../src/models/api/gridRowsMetaApi.ts | 7 + .../x-data-grid/src/models/api/gridSortApi.ts | 1 + .../grid/x-data-grid/src/models/gridRows.ts | 11 +- .../src/models/params/gridRowParams.ts | 31 ++- .../src/models/props/DataGridProps.ts | 24 ++- .../src/tests/rows.DataGrid.test.tsx | 181 ++++++++++++++++-- scripts/x-data-grid-pro.exports.json | 5 +- scripts/x-data-grid.exports.json | 5 +- 43 files changed, 624 insertions(+), 64 deletions(-) create mode 100644 docs/data/data-grid/rows/RowMarginGrid.js create mode 100644 docs/data/data-grid/rows/RowMarginGrid.tsx create mode 100644 docs/data/data-grid/rows/RowMarginGrid.tsx.preview create mode 100644 docs/pages/api-docs/data-grid/grid-row-class-name-params.js create mode 100644 docs/pages/api-docs/data-grid/grid-row-class-name-params.md create mode 100644 docs/pages/api-docs/data-grid/grid-row-spacing-params.js create mode 100644 docs/pages/api-docs/data-grid/grid-row-spacing-params.md create mode 100644 docs/pages/x/api/data-grid/grid-row-class-name-params.js create mode 100644 docs/pages/x/api/data-grid/grid-row-class-name-params.md create mode 100644 docs/pages/x/api/data-grid/grid-row-spacing-params.js create mode 100644 docs/pages/x/api/data-grid/grid-row-spacing-params.md diff --git a/docs/data/data-grid/rows/RowMarginGrid.js b/docs/data/data-grid/rows/RowMarginGrid.js new file mode 100644 index 0000000000000..bc2b54c4bb97f --- /dev/null +++ b/docs/data/data-grid/rows/RowMarginGrid.js @@ -0,0 +1,34 @@ +import * as React from 'react'; +import { DataGrid, gridClasses } from '@mui/x-data-grid'; +import { useDemoData } from '@mui/x-data-grid-generator'; +import { grey } from '@mui/material/colors'; + +export default function RowMarginGrid() { + const { data } = useDemoData({ + dataSet: 'Commodity', + rowLength: 200, + maxColumns: 6, + }); + + const getRowSpacing = React.useCallback((params) => { + return { + top: params.isFirstVisible ? 0 : 5, + bottom: params.isLastVisible ? 0 : 5, + }; + }, []); + + return ( +
+ + theme.palette.mode === 'light' ? grey[200] : grey[800], + }, + }} + /> +
+ ); +} diff --git a/docs/data/data-grid/rows/RowMarginGrid.tsx b/docs/data/data-grid/rows/RowMarginGrid.tsx new file mode 100644 index 0000000000000..155e0fe62b6aa --- /dev/null +++ b/docs/data/data-grid/rows/RowMarginGrid.tsx @@ -0,0 +1,34 @@ +import * as React from 'react'; +import { DataGrid, GridRowSpacingParams, gridClasses } from '@mui/x-data-grid'; +import { useDemoData } from '@mui/x-data-grid-generator'; +import { grey } from '@mui/material/colors'; + +export default function RowMarginGrid() { + const { data } = useDemoData({ + dataSet: 'Commodity', + rowLength: 200, + maxColumns: 6, + }); + + const getRowSpacing = React.useCallback((params: GridRowSpacingParams) => { + return { + top: params.isFirstVisible ? 0 : 5, + bottom: params.isLastVisible ? 0 : 5, + }; + }, []); + + return ( +
+ + theme.palette.mode === 'light' ? grey[200] : grey[800], + }, + }} + /> +
+ ); +} diff --git a/docs/data/data-grid/rows/RowMarginGrid.tsx.preview b/docs/data/data-grid/rows/RowMarginGrid.tsx.preview new file mode 100644 index 0000000000000..2442bed7d4577 --- /dev/null +++ b/docs/data/data-grid/rows/RowMarginGrid.tsx.preview @@ -0,0 +1,10 @@ + + theme.palette.mode === 'light' ? grey[200] : grey[800], + }, + }} +/> \ No newline at end of file diff --git a/docs/data/data-grid/rows/rows.md b/docs/data/data-grid/rows/rows.md index bac1fded63c2c..82e0c0ab9e73a 100644 --- a/docs/data/data-grid/rows/rows.md +++ b/docs/data/data-grid/rows/rows.md @@ -100,6 +100,37 @@ If you need some rows to have different row heights this can be achieved using t > > ``` +## Row spacing + +You can use the `getRowSpacing` prop to increase the spacing between rows. +This prop is called with a [`GridRowSpacingParams`](/api/data-grid/grid-row-spacing-params/) object. + +```tsx +const getRowSpacing = React.useCallback((params: GridRowSpacingParams) => { + return { + top: params.isFirstVisible ? 0 : 5, + bottom: params.isLastVisible ? 0 : 5, + }; +}, []); +``` + +{{"demo": "RowMarginGrid.js", "bg": "inline", "defaultCodeOpen": false}} + +By default, setting `getRowSpacing` will change the `marginXXX` CSS properties of each row. +To add a border instead, set `rowSpacingType` to `"border"` and customize the color and style. + +```tsx + +``` + +> ⚠ Adding a bottom margin or border to rows that also have a [detail panel](/components/data-grid/group-pivot/#master-detail) is not recommended because the detail panel relays on the bottom margin to work. +> As an alternative, only use the top spacing to define the space between rows. +> It will be easier to always increase the next row spacing not matter if the detail panel is expanded or not, but you can use `gridDetailPanelExpandedRowIdsSelector` to only do when open. + ## Styling rows You can check the [styling rows](/components/data-grid/style/#styling-rows) section for more information. diff --git a/docs/pages/api-docs/data-grid/data-grid-pro.json b/docs/pages/api-docs/data-grid/data-grid-pro.json index 52e46b213e334..7857d5f48ca37 100644 --- a/docs/pages/api-docs/data-grid/data-grid-pro.json +++ b/docs/pages/api-docs/data-grid/data-grid-pro.json @@ -80,6 +80,7 @@ "getRowClassName": { "type": { "name": "func" } }, "getRowHeight": { "type": { "name": "func" } }, "getRowId": { "type": { "name": "func" } }, + "getRowSpacing": { "type": { "name": "func" } }, "getTreeDataPath": { "type": { "name": "func" } }, "groupingColDef": { "type": { "name": "union", "description": "func
| object" } }, "headerHeight": { "type": { "name": "number" }, "default": "56" }, @@ -177,6 +178,10 @@ }, "rowGroupingModel": { "type": { "name": "arrayOf", "description": "Array<string>" } }, "rowHeight": { "type": { "name": "number" }, "default": "52" }, + "rowSpacingType": { + "type": { "name": "enum", "description": "'border'
| 'margin'" }, + "default": "\"margin\"" + }, "rowsPerPageOptions": { "type": { "name": "arrayOf", "description": "Array<number>" }, "default": "[25, 50, 100]" diff --git a/docs/pages/api-docs/data-grid/data-grid.json b/docs/pages/api-docs/data-grid/data-grid.json index f88ebb7968fde..28fa65b352575 100644 --- a/docs/pages/api-docs/data-grid/data-grid.json +++ b/docs/pages/api-docs/data-grid/data-grid.json @@ -58,6 +58,7 @@ "getRowClassName": { "type": { "name": "func" } }, "getRowHeight": { "type": { "name": "func" } }, "getRowId": { "type": { "name": "func" } }, + "getRowSpacing": { "type": { "name": "func" } }, "headerHeight": { "type": { "name": "number" }, "default": "56" }, "hideFooter": { "type": { "name": "bool" } }, "hideFooterPagination": { "type": { "name": "bool" } }, @@ -130,6 +131,10 @@ "rowBuffer": { "type": { "name": "number" }, "default": "3" }, "rowCount": { "type": { "name": "number" } }, "rowHeight": { "type": { "name": "number" }, "default": "52" }, + "rowSpacingType": { + "type": { "name": "enum", "description": "'border'
| 'margin'" }, + "default": "\"margin\"" + }, "rowsPerPageOptions": { "type": { "name": "arrayOf", "description": "Array<number>" }, "default": "[25, 50, 100]" diff --git a/docs/pages/api-docs/data-grid/grid-api.md b/docs/pages/api-docs/data-grid/grid-api.md index be8e702d9fb1c..a31d7fbe26855 100644 --- a/docs/pages/api-docs/data-grid/grid-api.md +++ b/docs/pages/api-docs/data-grid/grid-api.md @@ -43,6 +43,7 @@ import { GridApi } from '@mui/x-data-grid-pro'; | getRowElement | (id: GridRowId) => HTMLDivElement \| null | Gets the underlying DOM element for a row at the given `id`. | | getRowIdFromRowIndex | (index: number) => GridRowId | Gets the `GridRowId` of a row at a specific index.
The index is based on the sorted but unfiltered row list. | | getRowIndex | (id: GridRowId) => number | Gets the row index of a row with a given id.
The index is based on the sorted but unfiltered row list. | +| getRowIndexRelativeToCurrentPage | (id: GridRowId) => number | Gets the index of a row relative to the rows that are visible in the current page. | | getRowMode | (id: GridRowId) => GridRowMode | Gets the mode of a row. | | getRowModels | () => Map<GridRowId, GridRowModel> | Gets the full set of rows as Map<GridRowId, GridRowModel>. | | getRowNode | (id: GridRowId) => GridRowTreeNodeConfig \| null | Gets the row node from the internal tree structure. | diff --git a/docs/pages/api-docs/data-grid/grid-row-class-name-params.js b/docs/pages/api-docs/data-grid/grid-row-class-name-params.js new file mode 100644 index 0000000000000..45ba69c0e60cf --- /dev/null +++ b/docs/pages/api-docs/data-grid/grid-row-class-name-params.js @@ -0,0 +1,7 @@ +import * as React from 'react'; +import MarkdownDocs from '@mui/monorepo/docs/src/modules/components/MarkdownDocs'; +import { demos, docs, demoComponents } from './grid-row-class-name-params.md?@mui/markdown'; + +export default function Page() { + return ; +} diff --git a/docs/pages/api-docs/data-grid/grid-row-class-name-params.md b/docs/pages/api-docs/data-grid/grid-row-class-name-params.md new file mode 100644 index 0000000000000..04093a7afcd45 --- /dev/null +++ b/docs/pages/api-docs/data-grid/grid-row-class-name-params.md @@ -0,0 +1,22 @@ +# GridRowClassNameParams Interface + +

Object passed as parameter in the row `getRowClassName` callback prop.

+ +## Import + +```js +import { GridRowClassNameParams } from '@mui/x-data-grid-pro'; +// or +import { GridRowClassNameParams } from '@mui/x-data-grid'; +``` + +## Properties + +| Name | Type | Description | +| :-------------------------------------------- | :-------------------------------------------------------------------------------- | :--------------------------------------------------------- | +| columns | GridColumns | All grid columns. | +| getValue | (id: GridRowId, field: string) => GridCellValue | Get the cell value of a row and field. | +| id | GridRowId | The grid row id. | +| isFirstVisible | boolean | Whether this row is the first visible or not. | +| isLastVisible | boolean | Whether this row is the last visible or not. | +| row | R | The row model of the row that the current cell belongs to. | diff --git a/docs/pages/api-docs/data-grid/grid-row-spacing-params.js b/docs/pages/api-docs/data-grid/grid-row-spacing-params.js new file mode 100644 index 0000000000000..e6f32fbc96b7a --- /dev/null +++ b/docs/pages/api-docs/data-grid/grid-row-spacing-params.js @@ -0,0 +1,7 @@ +import * as React from 'react'; +import MarkdownDocs from '@mui/monorepo/docs/src/modules/components/MarkdownDocs'; +import { demos, docs, demoComponents } from './grid-row-spacing-params.md?@mui/markdown'; + +export default function Page() { + return ; +} diff --git a/docs/pages/api-docs/data-grid/grid-row-spacing-params.md b/docs/pages/api-docs/data-grid/grid-row-spacing-params.md new file mode 100644 index 0000000000000..dfd2af3910251 --- /dev/null +++ b/docs/pages/api-docs/data-grid/grid-row-spacing-params.md @@ -0,0 +1,20 @@ +# GridRowSpacingParams Interface + +

Object passed as parameter in the row `getRowSpacing` callback prop.

+ +## Import + +```js +import { GridRowSpacingParams } from '@mui/x-data-grid-pro'; +// or +import { GridRowSpacingParams } from '@mui/x-data-grid'; +``` + +## Properties + +| Name | Type | Description | +| :-------------------------------------------- | :------------------------------------------ | :-------------------------------------------- | +| id | GridRowId | The row id. | +| isFirstVisible | boolean | Whether this row is the first visible or not. | +| isLastVisible | boolean | Whether this row is the last visible or not. | +| model | GridRowModel | The row model. | diff --git a/docs/pages/x/api/data-grid/data-grid-pro.json b/docs/pages/x/api/data-grid/data-grid-pro.json index 129b5908d3ccf..2f9e329d91e24 100644 --- a/docs/pages/x/api/data-grid/data-grid-pro.json +++ b/docs/pages/x/api/data-grid/data-grid-pro.json @@ -80,6 +80,7 @@ "getRowClassName": { "type": { "name": "func" } }, "getRowHeight": { "type": { "name": "func" } }, "getRowId": { "type": { "name": "func" } }, + "getRowSpacing": { "type": { "name": "func" } }, "getTreeDataPath": { "type": { "name": "func" } }, "groupingColDef": { "type": { "name": "union", "description": "func
| object" } }, "headerHeight": { "type": { "name": "number" }, "default": "56" }, @@ -177,6 +178,10 @@ }, "rowGroupingModel": { "type": { "name": "arrayOf", "description": "Array<string>" } }, "rowHeight": { "type": { "name": "number" }, "default": "52" }, + "rowSpacingType": { + "type": { "name": "enum", "description": "'border'
| 'margin'" }, + "default": "\"margin\"" + }, "rowsPerPageOptions": { "type": { "name": "arrayOf", "description": "Array<number>" }, "default": "[25, 50, 100]" diff --git a/docs/pages/x/api/data-grid/data-grid.json b/docs/pages/x/api/data-grid/data-grid.json index 675cb4819ef31..b3f43f48727f4 100644 --- a/docs/pages/x/api/data-grid/data-grid.json +++ b/docs/pages/x/api/data-grid/data-grid.json @@ -58,6 +58,7 @@ "getRowClassName": { "type": { "name": "func" } }, "getRowHeight": { "type": { "name": "func" } }, "getRowId": { "type": { "name": "func" } }, + "getRowSpacing": { "type": { "name": "func" } }, "headerHeight": { "type": { "name": "number" }, "default": "56" }, "hideFooter": { "type": { "name": "bool" } }, "hideFooterPagination": { "type": { "name": "bool" } }, @@ -130,6 +131,10 @@ "rowBuffer": { "type": { "name": "number" }, "default": "3" }, "rowCount": { "type": { "name": "number" } }, "rowHeight": { "type": { "name": "number" }, "default": "52" }, + "rowSpacingType": { + "type": { "name": "enum", "description": "'border'
| 'margin'" }, + "default": "\"margin\"" + }, "rowsPerPageOptions": { "type": { "name": "arrayOf", "description": "Array<number>" }, "default": "[25, 50, 100]" diff --git a/docs/pages/x/api/data-grid/grid-api.md b/docs/pages/x/api/data-grid/grid-api.md index be8e702d9fb1c..a31d7fbe26855 100644 --- a/docs/pages/x/api/data-grid/grid-api.md +++ b/docs/pages/x/api/data-grid/grid-api.md @@ -43,6 +43,7 @@ import { GridApi } from '@mui/x-data-grid-pro'; | getRowElement | (id: GridRowId) => HTMLDivElement \| null | Gets the underlying DOM element for a row at the given `id`. | | getRowIdFromRowIndex | (index: number) => GridRowId | Gets the `GridRowId` of a row at a specific index.
The index is based on the sorted but unfiltered row list. | | getRowIndex | (id: GridRowId) => number | Gets the row index of a row with a given id.
The index is based on the sorted but unfiltered row list. | +| getRowIndexRelativeToCurrentPage | (id: GridRowId) => number | Gets the index of a row relative to the rows that are visible in the current page. | | getRowMode | (id: GridRowId) => GridRowMode | Gets the mode of a row. | | getRowModels | () => Map<GridRowId, GridRowModel> | Gets the full set of rows as Map<GridRowId, GridRowModel>. | | getRowNode | (id: GridRowId) => GridRowTreeNodeConfig \| null | Gets the row node from the internal tree structure. | diff --git a/docs/pages/x/api/data-grid/grid-row-class-name-params.js b/docs/pages/x/api/data-grid/grid-row-class-name-params.js new file mode 100644 index 0000000000000..45ba69c0e60cf --- /dev/null +++ b/docs/pages/x/api/data-grid/grid-row-class-name-params.js @@ -0,0 +1,7 @@ +import * as React from 'react'; +import MarkdownDocs from '@mui/monorepo/docs/src/modules/components/MarkdownDocs'; +import { demos, docs, demoComponents } from './grid-row-class-name-params.md?@mui/markdown'; + +export default function Page() { + return ; +} diff --git a/docs/pages/x/api/data-grid/grid-row-class-name-params.md b/docs/pages/x/api/data-grid/grid-row-class-name-params.md new file mode 100644 index 0000000000000..04093a7afcd45 --- /dev/null +++ b/docs/pages/x/api/data-grid/grid-row-class-name-params.md @@ -0,0 +1,22 @@ +# GridRowClassNameParams Interface + +

Object passed as parameter in the row `getRowClassName` callback prop.

+ +## Import + +```js +import { GridRowClassNameParams } from '@mui/x-data-grid-pro'; +// or +import { GridRowClassNameParams } from '@mui/x-data-grid'; +``` + +## Properties + +| Name | Type | Description | +| :-------------------------------------------- | :-------------------------------------------------------------------------------- | :--------------------------------------------------------- | +| columns | GridColumns | All grid columns. | +| getValue | (id: GridRowId, field: string) => GridCellValue | Get the cell value of a row and field. | +| id | GridRowId | The grid row id. | +| isFirstVisible | boolean | Whether this row is the first visible or not. | +| isLastVisible | boolean | Whether this row is the last visible or not. | +| row | R | The row model of the row that the current cell belongs to. | diff --git a/docs/pages/x/api/data-grid/grid-row-spacing-params.js b/docs/pages/x/api/data-grid/grid-row-spacing-params.js new file mode 100644 index 0000000000000..e6f32fbc96b7a --- /dev/null +++ b/docs/pages/x/api/data-grid/grid-row-spacing-params.js @@ -0,0 +1,7 @@ +import * as React from 'react'; +import MarkdownDocs from '@mui/monorepo/docs/src/modules/components/MarkdownDocs'; +import { demos, docs, demoComponents } from './grid-row-spacing-params.md?@mui/markdown'; + +export default function Page() { + return ; +} diff --git a/docs/pages/x/api/data-grid/grid-row-spacing-params.md b/docs/pages/x/api/data-grid/grid-row-spacing-params.md new file mode 100644 index 0000000000000..dfd2af3910251 --- /dev/null +++ b/docs/pages/x/api/data-grid/grid-row-spacing-params.md @@ -0,0 +1,20 @@ +# GridRowSpacingParams Interface + +

Object passed as parameter in the row `getRowSpacing` callback prop.

+ +## Import + +```js +import { GridRowSpacingParams } from '@mui/x-data-grid-pro'; +// or +import { GridRowSpacingParams } from '@mui/x-data-grid'; +``` + +## Properties + +| Name | Type | Description | +| :-------------------------------------------- | :------------------------------------------ | :-------------------------------------------- | +| id | GridRowId | The row id. | +| isFirstVisible | boolean | Whether this row is the first visible or not. | +| isLastVisible | boolean | Whether this row is the last visible or not. | +| model | GridRowModel | The row model. | diff --git a/docs/scripts/api/buildInterfacesDocumentation.ts b/docs/scripts/api/buildInterfacesDocumentation.ts index 2165170ef3b53..267c079689e06 100644 --- a/docs/scripts/api/buildInterfacesDocumentation.ts +++ b/docs/scripts/api/buildInterfacesDocumentation.ts @@ -57,6 +57,8 @@ const OTHER_GRID_INTERFACES_WITH_DEDICATED_PAGES = [ // Params 'GridCellParams', 'GridRowParams', + 'GridRowClassNameParams', + 'GridRowSpacingParams', // Others 'GridColDef', diff --git a/docs/translations/api-docs/data-grid/data-grid-pro-pt.json b/docs/translations/api-docs/data-grid/data-grid-pro-pt.json index 0cd5ef1957541..550c05d4b5f7d 100644 --- a/docs/translations/api-docs/data-grid/data-grid-pro-pt.json +++ b/docs/translations/api-docs/data-grid/data-grid-pro-pt.json @@ -44,9 +44,10 @@ "getCellClassName": "Function that applies CSS classes dynamically on cells.

Signature:
function(params: GridCellParams) => string
params: With all properties from GridCellParams.
returns (string): The CSS class to apply to the cell.", "getDetailPanelContent": "Function that returns the element to render in row detail.

Signature:
function(params: GridRowParams) => JSX.Element
params: With all properties from GridRowParams.
returns (JSX.Element): The row detail element.", "getDetailPanelHeight": "Function that returns the height of the row detail panel.

Signature:
function(params: GridRowParams) => number
params: With all properties from GridRowParams.
returns (number): The height in pixels.", - "getRowClassName": "Function that applies CSS classes dynamically on rows.

Signature:
function(params: GridRowParams) => string
params: With all properties from GridRowParams.
returns (string): The CSS class to apply to the row.", + "getRowClassName": "Function that applies CSS classes dynamically on rows.

Signature:
function(params: GridRowClassNameParams) => string
params: With all properties from GridRowClassNameParams.
returns (string): The CSS class to apply to the row.", "getRowHeight": "Function that sets the row height per row.

Signature:
function(params: GridRowHeightParams) => GridRowHeightReturnValue
params: With all properties from GridRowHeightParams.
returns (GridRowHeightReturnValue): The row height value. If null or undefined then the default row height is applied.", "getRowId": "Return the id of a given GridRowModel.", + "getRowSpacing": "Function that allows to specify the spacing between rows.

Signature:
function(params: GridRowSpacingParams) => GridRowSpacing
params: With all properties from GridRowSpacingParams.
returns (GridRowSpacing): The row spacing values.", "getTreeDataPath": "Determines the path of a row in the tree data. For instance, a row with the path ["A", "B"] is the child of the row with the path ["A"]. Note that all paths must contain at least one element.

Signature:
function(row: GridRowModel) => Array<string>
row: The row from which we want the path.
returns (Array): The path to the row.", "groupingColDef": "The grouping column used by the tree data.", "headerHeight": "Set the height in pixel of the column headers in the grid.", @@ -111,6 +112,7 @@ "rowGroupingModel": "Set the row grouping model of the grid.", "rowHeight": "Set the height in pixel of a row in the grid.", "rows": "Set of rows of type GridRowsProp.", + "rowSpacingType": "Sets the type of space between rows added by getRowSpacing.", "rowsPerPageOptions": "Select the pageSize dynamically using the component UI.", "rowThreshold": "Number of rows from the rowBuffer that can be visible before a new slice is rendered.", "scrollbarSize": "Override the height/width of the grid inner scrollbar.", diff --git a/docs/translations/api-docs/data-grid/data-grid-pro-zh.json b/docs/translations/api-docs/data-grid/data-grid-pro-zh.json index 0cd5ef1957541..550c05d4b5f7d 100644 --- a/docs/translations/api-docs/data-grid/data-grid-pro-zh.json +++ b/docs/translations/api-docs/data-grid/data-grid-pro-zh.json @@ -44,9 +44,10 @@ "getCellClassName": "Function that applies CSS classes dynamically on cells.

Signature:
function(params: GridCellParams) => string
params: With all properties from GridCellParams.
returns (string): The CSS class to apply to the cell.", "getDetailPanelContent": "Function that returns the element to render in row detail.

Signature:
function(params: GridRowParams) => JSX.Element
params: With all properties from GridRowParams.
returns (JSX.Element): The row detail element.", "getDetailPanelHeight": "Function that returns the height of the row detail panel.

Signature:
function(params: GridRowParams) => number
params: With all properties from GridRowParams.
returns (number): The height in pixels.", - "getRowClassName": "Function that applies CSS classes dynamically on rows.

Signature:
function(params: GridRowParams) => string
params: With all properties from GridRowParams.
returns (string): The CSS class to apply to the row.", + "getRowClassName": "Function that applies CSS classes dynamically on rows.

Signature:
function(params: GridRowClassNameParams) => string
params: With all properties from GridRowClassNameParams.
returns (string): The CSS class to apply to the row.", "getRowHeight": "Function that sets the row height per row.

Signature:
function(params: GridRowHeightParams) => GridRowHeightReturnValue
params: With all properties from GridRowHeightParams.
returns (GridRowHeightReturnValue): The row height value. If null or undefined then the default row height is applied.", "getRowId": "Return the id of a given GridRowModel.", + "getRowSpacing": "Function that allows to specify the spacing between rows.

Signature:
function(params: GridRowSpacingParams) => GridRowSpacing
params: With all properties from GridRowSpacingParams.
returns (GridRowSpacing): The row spacing values.", "getTreeDataPath": "Determines the path of a row in the tree data. For instance, a row with the path ["A", "B"] is the child of the row with the path ["A"]. Note that all paths must contain at least one element.

Signature:
function(row: GridRowModel) => Array<string>
row: The row from which we want the path.
returns (Array): The path to the row.", "groupingColDef": "The grouping column used by the tree data.", "headerHeight": "Set the height in pixel of the column headers in the grid.", @@ -111,6 +112,7 @@ "rowGroupingModel": "Set the row grouping model of the grid.", "rowHeight": "Set the height in pixel of a row in the grid.", "rows": "Set of rows of type GridRowsProp.", + "rowSpacingType": "Sets the type of space between rows added by getRowSpacing.", "rowsPerPageOptions": "Select the pageSize dynamically using the component UI.", "rowThreshold": "Number of rows from the rowBuffer that can be visible before a new slice is rendered.", "scrollbarSize": "Override the height/width of the grid inner scrollbar.", diff --git a/docs/translations/api-docs/data-grid/data-grid-pro.json b/docs/translations/api-docs/data-grid/data-grid-pro.json index 0cd5ef1957541..550c05d4b5f7d 100644 --- a/docs/translations/api-docs/data-grid/data-grid-pro.json +++ b/docs/translations/api-docs/data-grid/data-grid-pro.json @@ -44,9 +44,10 @@ "getCellClassName": "Function that applies CSS classes dynamically on cells.

Signature:
function(params: GridCellParams) => string
params: With all properties from GridCellParams.
returns (string): The CSS class to apply to the cell.", "getDetailPanelContent": "Function that returns the element to render in row detail.

Signature:
function(params: GridRowParams) => JSX.Element
params: With all properties from GridRowParams.
returns (JSX.Element): The row detail element.", "getDetailPanelHeight": "Function that returns the height of the row detail panel.

Signature:
function(params: GridRowParams) => number
params: With all properties from GridRowParams.
returns (number): The height in pixels.", - "getRowClassName": "Function that applies CSS classes dynamically on rows.

Signature:
function(params: GridRowParams) => string
params: With all properties from GridRowParams.
returns (string): The CSS class to apply to the row.", + "getRowClassName": "Function that applies CSS classes dynamically on rows.

Signature:
function(params: GridRowClassNameParams) => string
params: With all properties from GridRowClassNameParams.
returns (string): The CSS class to apply to the row.", "getRowHeight": "Function that sets the row height per row.

Signature:
function(params: GridRowHeightParams) => GridRowHeightReturnValue
params: With all properties from GridRowHeightParams.
returns (GridRowHeightReturnValue): The row height value. If null or undefined then the default row height is applied.", "getRowId": "Return the id of a given GridRowModel.", + "getRowSpacing": "Function that allows to specify the spacing between rows.

Signature:
function(params: GridRowSpacingParams) => GridRowSpacing
params: With all properties from GridRowSpacingParams.
returns (GridRowSpacing): The row spacing values.", "getTreeDataPath": "Determines the path of a row in the tree data. For instance, a row with the path ["A", "B"] is the child of the row with the path ["A"]. Note that all paths must contain at least one element.

Signature:
function(row: GridRowModel) => Array<string>
row: The row from which we want the path.
returns (Array): The path to the row.", "groupingColDef": "The grouping column used by the tree data.", "headerHeight": "Set the height in pixel of the column headers in the grid.", @@ -111,6 +112,7 @@ "rowGroupingModel": "Set the row grouping model of the grid.", "rowHeight": "Set the height in pixel of a row in the grid.", "rows": "Set of rows of type GridRowsProp.", + "rowSpacingType": "Sets the type of space between rows added by getRowSpacing.", "rowsPerPageOptions": "Select the pageSize dynamically using the component UI.", "rowThreshold": "Number of rows from the rowBuffer that can be visible before a new slice is rendered.", "scrollbarSize": "Override the height/width of the grid inner scrollbar.", diff --git a/docs/translations/api-docs/data-grid/data-grid-pt.json b/docs/translations/api-docs/data-grid/data-grid-pt.json index 5dab3c6d6b2f5..0685b87428a35 100644 --- a/docs/translations/api-docs/data-grid/data-grid-pt.json +++ b/docs/translations/api-docs/data-grid/data-grid-pt.json @@ -30,9 +30,10 @@ "filterModel": "Set the filter model of the grid.", "getCellClassName": "Function that applies CSS classes dynamically on cells.

Signature:
function(params: GridCellParams) => string
params: With all properties from GridCellParams.
returns (string): The CSS class to apply to the cell.", "getDetailPanelContent": "Function that returns the element to render in row detail.

Signature:
function(params: GridRowParams) => JSX.Element
params: With all properties from GridRowParams.
returns (JSX.Element): The row detail element.", - "getRowClassName": "Function that applies CSS classes dynamically on rows.

Signature:
function(params: GridRowParams) => string
params: With all properties from GridRowParams.
returns (string): The CSS class to apply to the row.", + "getRowClassName": "Function that applies CSS classes dynamically on rows.

Signature:
function(params: GridRowClassNameParams) => string
params: With all properties from GridRowClassNameParams.
returns (string): The CSS class to apply to the row.", "getRowHeight": "Function that sets the row height per row.

Signature:
function(params: GridRowHeightParams) => GridRowHeightReturnValue
params: With all properties from GridRowHeightParams.
returns (GridRowHeightReturnValue): The row height value. If null or undefined then the default row height is applied.", "getRowId": "Return the id of a given GridRowModel.", + "getRowSpacing": "Function that allows to specify the spacing between rows.

Signature:
function(params: GridRowSpacingParams) => GridRowSpacing
params: With all properties from GridRowSpacingParams.
returns (GridRowSpacing): The row spacing values.", "headerHeight": "Set the height in pixel of the column headers in the grid.", "hideFooter": "If true, the footer component is hidden.", "hideFooterPagination": "If true, the pagination component in the footer is hidden.", @@ -83,6 +84,7 @@ "rowCount": "Set the total number of rows, if it is different from the length of the value rows prop. If some rows have children (for instance in the tree data), this number represents the amount of top level rows.", "rowHeight": "Set the height in pixel of a row in the grid.", "rows": "Set of rows of type GridRowsProp.", + "rowSpacingType": "Sets the type of space between rows added by getRowSpacing.", "rowsPerPageOptions": "Select the pageSize dynamically using the component UI.", "rowThreshold": "Number of rows from the rowBuffer that can be visible before a new slice is rendered.", "scrollbarSize": "Override the height/width of the grid inner scrollbar.", diff --git a/docs/translations/api-docs/data-grid/data-grid-zh.json b/docs/translations/api-docs/data-grid/data-grid-zh.json index 5dab3c6d6b2f5..0685b87428a35 100644 --- a/docs/translations/api-docs/data-grid/data-grid-zh.json +++ b/docs/translations/api-docs/data-grid/data-grid-zh.json @@ -30,9 +30,10 @@ "filterModel": "Set the filter model of the grid.", "getCellClassName": "Function that applies CSS classes dynamically on cells.

Signature:
function(params: GridCellParams) => string
params: With all properties from GridCellParams.
returns (string): The CSS class to apply to the cell.", "getDetailPanelContent": "Function that returns the element to render in row detail.

Signature:
function(params: GridRowParams) => JSX.Element
params: With all properties from GridRowParams.
returns (JSX.Element): The row detail element.", - "getRowClassName": "Function that applies CSS classes dynamically on rows.

Signature:
function(params: GridRowParams) => string
params: With all properties from GridRowParams.
returns (string): The CSS class to apply to the row.", + "getRowClassName": "Function that applies CSS classes dynamically on rows.

Signature:
function(params: GridRowClassNameParams) => string
params: With all properties from GridRowClassNameParams.
returns (string): The CSS class to apply to the row.", "getRowHeight": "Function that sets the row height per row.

Signature:
function(params: GridRowHeightParams) => GridRowHeightReturnValue
params: With all properties from GridRowHeightParams.
returns (GridRowHeightReturnValue): The row height value. If null or undefined then the default row height is applied.", "getRowId": "Return the id of a given GridRowModel.", + "getRowSpacing": "Function that allows to specify the spacing between rows.

Signature:
function(params: GridRowSpacingParams) => GridRowSpacing
params: With all properties from GridRowSpacingParams.
returns (GridRowSpacing): The row spacing values.", "headerHeight": "Set the height in pixel of the column headers in the grid.", "hideFooter": "If true, the footer component is hidden.", "hideFooterPagination": "If true, the pagination component in the footer is hidden.", @@ -83,6 +84,7 @@ "rowCount": "Set the total number of rows, if it is different from the length of the value rows prop. If some rows have children (for instance in the tree data), this number represents the amount of top level rows.", "rowHeight": "Set the height in pixel of a row in the grid.", "rows": "Set of rows of type GridRowsProp.", + "rowSpacingType": "Sets the type of space between rows added by getRowSpacing.", "rowsPerPageOptions": "Select the pageSize dynamically using the component UI.", "rowThreshold": "Number of rows from the rowBuffer that can be visible before a new slice is rendered.", "scrollbarSize": "Override the height/width of the grid inner scrollbar.", diff --git a/docs/translations/api-docs/data-grid/data-grid.json b/docs/translations/api-docs/data-grid/data-grid.json index 5dab3c6d6b2f5..0685b87428a35 100644 --- a/docs/translations/api-docs/data-grid/data-grid.json +++ b/docs/translations/api-docs/data-grid/data-grid.json @@ -30,9 +30,10 @@ "filterModel": "Set the filter model of the grid.", "getCellClassName": "Function that applies CSS classes dynamically on cells.

Signature:
function(params: GridCellParams) => string
params: With all properties from GridCellParams.
returns (string): The CSS class to apply to the cell.", "getDetailPanelContent": "Function that returns the element to render in row detail.

Signature:
function(params: GridRowParams) => JSX.Element
params: With all properties from GridRowParams.
returns (JSX.Element): The row detail element.", - "getRowClassName": "Function that applies CSS classes dynamically on rows.

Signature:
function(params: GridRowParams) => string
params: With all properties from GridRowParams.
returns (string): The CSS class to apply to the row.", + "getRowClassName": "Function that applies CSS classes dynamically on rows.

Signature:
function(params: GridRowClassNameParams) => string
params: With all properties from GridRowClassNameParams.
returns (string): The CSS class to apply to the row.", "getRowHeight": "Function that sets the row height per row.

Signature:
function(params: GridRowHeightParams) => GridRowHeightReturnValue
params: With all properties from GridRowHeightParams.
returns (GridRowHeightReturnValue): The row height value. If null or undefined then the default row height is applied.", "getRowId": "Return the id of a given GridRowModel.", + "getRowSpacing": "Function that allows to specify the spacing between rows.

Signature:
function(params: GridRowSpacingParams) => GridRowSpacing
params: With all properties from GridRowSpacingParams.
returns (GridRowSpacing): The row spacing values.", "headerHeight": "Set the height in pixel of the column headers in the grid.", "hideFooter": "If true, the footer component is hidden.", "hideFooterPagination": "If true, the pagination component in the footer is hidden.", @@ -83,6 +84,7 @@ "rowCount": "Set the total number of rows, if it is different from the length of the value rows prop. If some rows have children (for instance in the tree data), this number represents the amount of top level rows.", "rowHeight": "Set the height in pixel of a row in the grid.", "rows": "Set of rows of type GridRowsProp.", + "rowSpacingType": "Sets the type of space between rows added by getRowSpacing.", "rowsPerPageOptions": "Select the pageSize dynamically using the component UI.", "rowThreshold": "Number of rows from the rowBuffer that can be visible before a new slice is rendered.", "scrollbarSize": "Override the height/width of the grid inner scrollbar.", diff --git a/packages/grid/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx b/packages/grid/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx index 3067e8e7f2cc6..d6f2a57b75381 100644 --- a/packages/grid/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx +++ b/packages/grid/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx @@ -305,7 +305,7 @@ DataGridProRaw.propTypes = { getDetailPanelHeight: PropTypes.func, /** * Function that applies CSS classes dynamically on rows. - * @param {GridRowParams} params With all properties from [[GridRowParams]]. + * @param {GridRowClassNameParams} params With all properties from [[GridRowClassNameParams]]. * @returns {string} The CSS class to apply to the row. */ getRowClassName: PropTypes.func, @@ -319,6 +319,12 @@ DataGridProRaw.propTypes = { * Return the id of a given [[GridRowModel]]. */ getRowId: PropTypes.func, + /** + * Function that allows to specify the spacing between rows. + * @param {GridRowSpacingParams} params With all properties from [[GridRowSpacingParams]]. + * @returns {GridRowSpacing} The row spacing values. + */ + getRowSpacing: PropTypes.func, /** * Determines the path of a row in the tree data. * For instance, a row with the path ["A", "B"] is the child of the row with the path ["A"]. @@ -725,6 +731,11 @@ DataGridProRaw.propTypes = { * Set of rows of type [[GridRowsProp]]. */ rows: PropTypes.arrayOf(PropTypes.object).isRequired, + /** + * Sets the type of space between rows added by `getRowSpacing`. + * @default "margin" + */ + rowSpacingType: PropTypes.oneOf(['border', 'margin']), /** * Select the pageSize dynamically using the component UI. * @default [25, 50, 100] diff --git a/packages/grid/x-data-grid-pro/src/components/DataGridProVirtualScroller.tsx b/packages/grid/x-data-grid-pro/src/components/DataGridProVirtualScroller.tsx index 22cc5529ab101..e0b4966a22f09 100644 --- a/packages/grid/x-data-grid-pro/src/components/DataGridProVirtualScroller.tsx +++ b/packages/grid/x-data-grid-pro/src/components/DataGridProVirtualScroller.tsx @@ -13,7 +13,6 @@ import { GridRowId, } from '@mui/x-data-grid'; import { - useCurrentPageRows, GridVirtualScroller, GridVirtualScrollerContent, GridVirtualScrollerRenderZone, @@ -150,7 +149,6 @@ const DataGridProVirtualScroller = React.forwardRef< const { className, disableVirtualization, ...other } = props; const apiRef = useGridApiContext(); const rootProps = useGridRootProps(); - const currentPage = useCurrentPageRows(apiRef, rootProps); const visibleColumnFields = useGridSelector(apiRef, gridVisibleColumnFieldsSelector); const expandedRowIds = useGridSelector(apiRef, gridDetailPanelExpandedRowIdsSelector); const detailPanelsContent = useGridSelector( @@ -240,17 +238,6 @@ const DataGridProVirtualScroller = React.forwardRef< minHeight: contentProps.style.minHeight, }; - const rowsLookup = React.useMemo(() => { - if (rootProps.getDetailPanelContent == null) { - return null; - } - - return currentPage.rows.reduce>((acc, { id }, index) => { - acc[id] = index; - return acc; - }, {}); - }, [currentPage.rows, rootProps.getDetailPanelContent]); - const getDetailPanels = () => { const panels: React.ReactNode[] = []; @@ -266,12 +253,15 @@ const DataGridProVirtualScroller = React.forwardRef< const content = detailPanelsContent[id]; // Check if the id exists in the current page - const exists = rowsLookup![id] !== undefined; + const rowIndex = apiRef.current.getRowIndexRelativeToCurrentPage(id); + const exists = rowIndex !== undefined; if (React.isValidElement(content) && exists) { const height = detailPanelsHeights[id]; - const rowIndex = rowsLookup![id]; - const top = rowsMeta.positions[rowIndex] + apiRef.current.unstable_getRowHeight(id); + const sizes = apiRef.current.unstable_getRowInternalSizes(id); + const spacingTop = sizes?.spacingTop || 0; + const top = + rowsMeta.positions[rowIndex] + apiRef.current.unstable_getRowHeight(id) + spacingTop; panels.push( & GridRowProps) { const ariaRowIndex = index + 2; // 1 for the header row and 1 as it's 1-based const apiRef = useGridApiContext(); const rootProps = useGridRootProps(); + const currentPage = useCurrentPageRows(apiRef, rootProps); const columnsTotalWidth = useGridSelector(apiRef, gridColumnsTotalWidthSelector); const { hasScrollX, hasScrollY } = apiRef.current.getRootDimensions() ?? { hasScrollX: false, @@ -186,14 +189,35 @@ function GridRow(props: React.HTMLAttributes & GridRowProps) { ); const style = { + ...styleProp, maxHeight: rowHeight, minHeight: rowHeight, - ...styleProp, }; - const rowClassName = - typeof rootProps.getRowClassName === 'function' && - rootProps.getRowClassName(apiRef.current.getRowParams(rowId)); + const sizes = apiRef.current.unstable_getRowInternalSizes(rowId); + + if (sizes?.spacingTop) { + const property = rootProps.rowSpacingType === 'border' ? 'borderTopWidth' : 'marginTop'; + style[property] = sizes.spacingTop; + } + + if (sizes?.spacingBottom) { + const property = rootProps.rowSpacingType === 'border' ? 'borderBottomWidth' : 'marginBottom'; + style[property] = sizes.spacingBottom; + } + + let rowClassName: string | null = null; + + if (typeof rootProps.getRowClassName === 'function') { + const indexRelativeToCurrentPage = index - currentPage.range!.firstRowIndex; + const rowParams: GridRowClassNameParams = { + ...apiRef.current.getRowParams(rowId), + isFirstVisible: indexRelativeToCurrentPage === 0, + isLastVisible: indexRelativeToCurrentPage === currentPage.rows.length - 1, + }; + + rowClassName = rootProps.getRowClassName(rowParams); + } const cells: JSX.Element[] = []; diff --git a/packages/grid/x-data-grid/src/components/virtualization/GridVirtualScrollerRenderZone.tsx b/packages/grid/x-data-grid/src/components/virtualization/GridVirtualScrollerRenderZone.tsx index d1f8d31e12add..bc5bad793fe62 100644 --- a/packages/grid/x-data-grid/src/components/virtualization/GridVirtualScrollerRenderZone.tsx +++ b/packages/grid/x-data-grid/src/components/virtualization/GridVirtualScrollerRenderZone.tsx @@ -24,6 +24,8 @@ const VirtualScrollerRenderZoneRoot = styled('div', { overridesResolver: (props, styles) => styles.virtualScrollerRenderZone, })({ position: 'absolute', + display: 'flex', // Prevents margin collapsing when using `getRowSpacing` + flexDirection: 'column', }); const GridVirtualScrollerRenderZone = React.forwardRef< diff --git a/packages/grid/x-data-grid/src/hooks/features/rows/useGridRows.ts b/packages/grid/x-data-grid/src/hooks/features/rows/useGridRows.ts index c939e4ba4624d..66e43fcf53d90 100644 --- a/packages/grid/x-data-grid/src/hooks/features/rows/useGridRows.ts +++ b/packages/grid/x-data-grid/src/hooks/features/rows/useGridRows.ts @@ -22,6 +22,7 @@ import { } from './gridRowsSelector'; import { GridSignature, useGridApiEventHandler } from '../../utils/useGridApiEventHandler'; import { GridStateInitializer } from '../../utils/useGridInitializeState'; +import { useCurrentPageRows } from '../../utils/useCurrentPageRows'; import { GridRowsInternalCacheState, GridRowInternalCacheValue, @@ -144,12 +145,24 @@ export const useGridRows = ( const logger = useGridLogger(apiRef, 'useGridRows'); const rowsCache = React.useRef(apiRef.current.state.rowsCache); // To avoid listing rowsCache as useEffect dep + const currentPage = useCurrentPageRows(apiRef, props); const getRow = React.useCallback( (id) => gridRowsLookupSelector(apiRef)[id] ?? null, [apiRef], ); + const lookup = React.useMemo( + () => + currentPage.rows.reduce>((acc, { id }, index) => { + acc[id] = index; + return acc; + }, {}), + [currentPage.rows], + ); + + const getRowIndexRelativeToCurrentPage = React.useCallback((id) => lookup[id], [lookup]); + const throttledRowsChange = React.useCallback( (newState: GridRowsInternalCacheState, throttle: boolean) => { const run = () => { @@ -390,6 +403,7 @@ export const useGridRows = ( updateRows, setRowChildrenExpansion, getRowNode, + getRowIndexRelativeToCurrentPage, }; useGridApiMethod(apiRef, rowApi, 'GridRowApi'); diff --git a/packages/grid/x-data-grid/src/hooks/features/rows/useGridRowsMeta.ts b/packages/grid/x-data-grid/src/hooks/features/rows/useGridRowsMeta.ts index 142bb18f53f0a..566debfe4de66 100644 --- a/packages/grid/x-data-grid/src/hooks/features/rows/useGridRowsMeta.ts +++ b/packages/grid/x-data-grid/src/hooks/features/rows/useGridRowsMeta.ts @@ -2,7 +2,7 @@ import * as React from 'react'; import { GridApiCommunity } from '../../../models/api/gridApiCommunity'; import { GridRowsMetaApi } from '../../../models/api/gridRowsMetaApi'; import { DataGridProcessedProps } from '../../../models/props/DataGridProps'; -import { getCurrentPageRows } from '../../utils/useCurrentPageRows'; +import { useCurrentPageRows } from '../../utils/useCurrentPageRows'; import { useGridApiMethod } from '../../utils/useGridApiMethod'; import { GridRowId } from '../../../models/gridRows'; import { useGridSelector } from '../../utils/useGridSelector'; @@ -32,28 +32,27 @@ export const rowsMetaStateInitializer: GridStateInitializer = (state) => ({ */ export const useGridRowsMeta = ( apiRef: React.MutableRefObject, - props: Pick, + props: Pick< + DataGridProcessedProps, + 'getRowHeight' | 'getRowSpacing' | 'pagination' | 'paginationMode' + >, ): void => { - const { getRowHeight, pagination, paginationMode } = props; + const { getRowHeight, getRowSpacing } = props; const rowsHeightLookup = React.useRef<{ - [key: GridRowId]: { value: number; isResized: boolean }; + [key: GridRowId]: { value: number; isResized: boolean; sizes: Record }; }>({}); const rowHeight = useGridSelector(apiRef, gridDensityRowHeightSelector); const filterState = useGridSelector(apiRef, gridFilterStateSelector); const paginationState = useGridSelector(apiRef, gridPaginationSelector); const sortingState = useGridSelector(apiRef, gridSortingStateSelector); + const currentPage = useCurrentPageRows(apiRef, props); const hydrateRowsMeta = React.useCallback(() => { - const { rows } = getCurrentPageRows(apiRef, { - pagination, - paginationMode, - }); - apiRef.current.setState((state) => { const positions: number[] = []; const densityFactor = gridDensityFactorSelector(state, apiRef.current.instanceId); const currentRowHeight = gridDensityRowHeightSelector(state, apiRef.current.instanceId); - const currentPageTotalHeight = rows.reduce((acc: number, row) => { + const currentPageTotalHeight = currentPage.rows.reduce((acc: number, row) => { positions.push(acc); let baseRowHeight: number; @@ -72,16 +71,33 @@ export const useGridRowsMeta = ( } } - const heights = apiRef.current.unstable_applyPreProcessors( + // We use an object to make simple to check if a height is already added or not + const initialHeights: Record = { base: baseRowHeight }; + + if (getRowSpacing) { + const index = apiRef.current.getRowIndexRelativeToCurrentPage(row.id); + + const spacing = getRowSpacing({ + ...row, + isFirstVisible: index === 0, + isLastVisible: index === currentPage.rows.length - 1, + }); + + initialHeights.spacingTop = spacing.top ?? 0; + initialHeights.spacingBottom = spacing.bottom ?? 0; + } + + const sizes = apiRef.current.unstable_applyPreProcessors( 'rowHeight', - { base: baseRowHeight }, // We use an object to make simple to check if a size was already added or not + initialHeights, row, ) as Record; - const finalRowHeight = Object.values(heights).reduce((acc2, value) => acc2 + value, 0); + const finalRowHeight = Object.values(sizes).reduce((acc2, value) => acc2 + value, 0); rowsHeightLookup.current[row.id] = { value: baseRowHeight, + sizes, isResized, }; @@ -94,16 +110,20 @@ export const useGridRowsMeta = ( }; }); apiRef.current.forceUpdate(); - }, [apiRef, pagination, paginationMode, getRowHeight]); + }, [apiRef, currentPage.rows, getRowSpacing, getRowHeight]); const getTargetRowHeight = (rowId: GridRowId): number => rowsHeightLookup.current[rowId]?.value || rowHeight; + const getRowInternalSizes = (rowId: GridRowId): Record | undefined => + rowsHeightLookup.current[rowId]?.sizes; + const setRowHeight = React.useCallback( (id: GridRowId, height: number) => { rowsHeightLookup.current[id] = { value: height, isResized: true, + sizes: { ...rowsHeightLookup.current[id].sizes, base: height }, }; hydrateRowsMeta(); }, @@ -132,6 +152,7 @@ export const useGridRowsMeta = ( const rowsMetaApi: GridRowsMetaApi = { unstable_getRowHeight: getTargetRowHeight, + unstable_getRowInternalSizes: getRowInternalSizes, unstable_setRowHeight: setRowHeight, }; diff --git a/packages/grid/x-data-grid/src/hooks/features/virtualization/useGridVirtualScroller.tsx b/packages/grid/x-data-grid/src/hooks/features/virtualization/useGridVirtualScroller.tsx index 04d2300e755c9..3bee337cf5a0d 100644 --- a/packages/grid/x-data-grid/src/hooks/features/virtualization/useGridVirtualScroller.tsx +++ b/packages/grid/x-data-grid/src/hooks/features/virtualization/useGridVirtualScroller.tsx @@ -332,7 +332,7 @@ export const useGridVirtualScroller = (props: UseGridVirtualScrollerProps) => { firstColumnToRender={firstColumnToRender} lastColumnToRender={lastColumnToRender} selected={isSelected} - index={currentPage.range.firstRowIndex + nextRenderContext.firstRowIndex! + i} + index={currentPage.range.firstRowIndex + firstRowToRender + i} containerWidth={availableSpace} isLastVisible={lastVisibleRowIndex} {...(typeof getRowProps === 'function' ? getRowProps(id, model) : {})} diff --git a/packages/grid/x-data-grid/src/models/api/gridRowApi.ts b/packages/grid/x-data-grid/src/models/api/gridRowApi.ts index 7e1d70fae38aa..d539bbda40bb9 100644 --- a/packages/grid/x-data-grid/src/models/api/gridRowApi.ts +++ b/packages/grid/x-data-grid/src/models/api/gridRowApi.ts @@ -47,4 +47,10 @@ export interface GridRowApi { * @param {boolean} isExpanded A boolean indicating if the row must be expanded or collapsed. */ setRowChildrenExpansion: (id: GridRowId, isExpanded: boolean) => void; + /** + * Gets the index of a row relative to the rows that are visible in the current page. + * @param {GridRowId} id The row id. + * @returns {number} The index of the row. + */ + getRowIndexRelativeToCurrentPage: (id: GridRowId) => number; } diff --git a/packages/grid/x-data-grid/src/models/api/gridRowsMetaApi.ts b/packages/grid/x-data-grid/src/models/api/gridRowsMetaApi.ts index 082b14148d94f..dfe5b63682083 100644 --- a/packages/grid/x-data-grid/src/models/api/gridRowsMetaApi.ts +++ b/packages/grid/x-data-grid/src/models/api/gridRowsMetaApi.ts @@ -11,6 +11,13 @@ export interface GridRowsMetaApi { * @ignore - do not document. */ unstable_getRowHeight: (id: GridRowId) => number; + /** + * Gets all sizes that compose the total height that the given row takes. + * @param {GridRowId} id The id of the row. + * @returns {Record} The object containing the sizes. + * @ignore - do not document. + */ + unstable_getRowInternalSizes: (id: GridRowId) => Record | undefined; /** * Updates the base height of a row. * @param {GridRowId} id The id of the row. diff --git a/packages/grid/x-data-grid/src/models/api/gridSortApi.ts b/packages/grid/x-data-grid/src/models/api/gridSortApi.ts index eb9d8f529fd66..234c08b401d27 100644 --- a/packages/grid/x-data-grid/src/models/api/gridSortApi.ts +++ b/packages/grid/x-data-grid/src/models/api/gridSortApi.ts @@ -53,6 +53,7 @@ export interface GridSortApi { * The index is based on the sorted but unfiltered row list. * @param {GridRowId} id The `GridRowId` of the row. * @returns {number} The index of the row. + * @deprecated Use `getRowIndexRelativeToCurrentPage` instead. */ getRowIndex: (id: GridRowId) => number; } diff --git a/packages/grid/x-data-grid/src/models/gridRows.ts b/packages/grid/x-data-grid/src/models/gridRows.ts index 4a1a734a263fb..1fe1948388629 100644 --- a/packages/grid/x-data-grid/src/models/gridRows.ts +++ b/packages/grid/x-data-grid/src/models/gridRows.ts @@ -80,7 +80,16 @@ export type GridRowsLookup = Record; */ export type GridRowId = string | number; -export type GridRowEntry = { id: GridRowId; model: GridRowModel }; +export interface GridRowEntry { + /** + * The row id. + */ + id: GridRowId; + /** + * The row model. + */ + model: GridRowModel; +} /** * The function to retrieve the id of a [[GridRowModel]]. diff --git a/packages/grid/x-data-grid/src/models/params/gridRowParams.ts b/packages/grid/x-data-grid/src/models/params/gridRowParams.ts index 1e63c2f76af25..9e112f38d9220 100644 --- a/packages/grid/x-data-grid/src/models/params/gridRowParams.ts +++ b/packages/grid/x-data-grid/src/models/params/gridRowParams.ts @@ -28,8 +28,24 @@ export interface GridRowParams { getValue: (id: GridRowId, field: string) => GridCellValue; } +interface GridRowVisibilityParams { + /** + * Whether this row is the first visible or not. + */ + isFirstVisible: boolean; + /** + * Whether this row is the last visible or not. + */ + isLastVisible: boolean; +} + +/** + * Object passed as parameter in the row `getRowClassName` callback prop. + */ +export interface GridRowClassNameParams extends GridRowParams, GridRowVisibilityParams {} + /** - * Object passed as parameter in the row getRowHeight callback. + * Object passed as parameter in the row `getRowHeight` callback prop. */ export interface GridRowHeightParams extends GridRowEntry { /** @@ -42,3 +58,16 @@ export interface GridRowHeightParams extends GridRowEntry { * The getRowHeight return value. */ export type GridRowHeightReturnValue = number | null | undefined; + +/** + * Object passed as parameter in the row `getRowSpacing` callback prop. + */ +export interface GridRowSpacingParams extends GridRowEntry, GridRowVisibilityParams {} + +/** + * The getRowSpacing return value. + */ +export interface GridRowSpacing { + top?: number; + bottom?: number; +} diff --git a/packages/grid/x-data-grid/src/models/props/DataGridProps.ts b/packages/grid/x-data-grid/src/models/props/DataGridProps.ts index 08fd45b9a9df9..bad18fded27e6 100644 --- a/packages/grid/x-data-grid/src/models/props/DataGridProps.ts +++ b/packages/grid/x-data-grid/src/models/props/DataGridProps.ts @@ -15,7 +15,14 @@ import { GridApiCommunity } from '../api/gridApiCommunity'; import type { GridColumnTypesRecord } from '../colDef'; import type { GridColumns } from '../colDef/gridColDef'; import { GridClasses } from '../../constants/gridClasses'; -import { GridRowHeightParams, GridRowHeightReturnValue, GridRowParams } from '../params'; +import { + GridRowHeightParams, + GridRowHeightReturnValue, + GridRowParams, + GridRowSpacing, + GridRowSpacingParams, + GridRowClassNameParams, +} from '../params'; import { GridCellParams } from '../params/gridCellParams'; import { GridFilterModel } from '../gridFilterModel'; import { GridInputSelectionModel, GridSelectionModel } from '../gridSelectionModel'; @@ -267,6 +274,11 @@ export interface DataGridPropsWithDefaultValues { * @default [25, 50, 100] */ rowsPerPageOptions: number[]; + /** + * Sets the type of space between rows added by `getRowSpacing`. + * @default "margin" + */ + rowSpacingType: 'margin' | 'border'; /** * If `true`, the right border of the cells are displayed. * @default false @@ -348,16 +360,22 @@ export interface DataGridPropsWithoutDefaultValue extends CommonProps { getCellClassName?: (params: GridCellParams) => string; /** * Function that applies CSS classes dynamically on rows. - * @param {GridRowParams} params With all properties from [[GridRowParams]]. + * @param {GridRowClassNameParams} params With all properties from [[GridRowClassNameParams]]. * @returns {string} The CSS class to apply to the row. */ - getRowClassName?: (params: GridRowParams) => string; + getRowClassName?: (params: GridRowClassNameParams) => string; /** * Function that sets the row height per row. * @param {GridRowHeightParams} params With all properties from [[GridRowHeightParams]]. * @returns {GridRowHeightReturnValue} The row height value. If `null` or `undefined` then the default row height is applied. */ getRowHeight?: (params: GridRowHeightParams) => GridRowHeightReturnValue; + /** + * Function that allows to specify the spacing between rows. + * @param {GridRowSpacingParams} params With all properties from [[GridRowSpacingParams]]. + * @returns {GridRowSpacing} The row spacing values. + */ + getRowSpacing?: (params: GridRowSpacingParams) => GridRowSpacing; /** * Function that returns the element to render in row detail. * @param {GridRowParams} params With all properties from [[GridRowParams]]. diff --git a/packages/grid/x-data-grid/src/tests/rows.DataGrid.test.tsx b/packages/grid/x-data-grid/src/tests/rows.DataGrid.test.tsx index 1313fd0e56cf6..7a9cf4a3554d7 100644 --- a/packages/grid/x-data-grid/src/tests/rows.DataGrid.test.tsx +++ b/packages/grid/x-data-grid/src/tests/rows.DataGrid.test.tsx @@ -1,10 +1,17 @@ import * as React from 'react'; // @ts-ignore Remove once the test utils are typed import { createRenderer, fireEvent, screen } from '@mui/monorepo/test/utils'; +import clsx from 'clsx'; import { expect } from 'chai'; import { spy, stub } from 'sinon'; import Portal from '@mui/material/Portal'; -import { DataGrid, DataGridProps, GridActionsCellItem, GridRowIdGetter } from '@mui/x-data-grid'; +import { + DataGrid, + DataGridProps, + GridActionsCellItem, + GridRowIdGetter, + GridRowClassNameParams, +} from '@mui/x-data-grid'; import { getColumnValues, getRow, getActiveCell, getCell } from 'test/utils/helperFn'; import { getData } from 'storybook/src/data/data-service'; import { COMPACT_DENSITY_FACTOR } from '../hooks/features/density/useGridDensity'; @@ -98,18 +105,44 @@ describe(' - Rows', () => { expect(handleRowClick.callCount).to.equal(1); }); - it('should apply the CSS class returned by getRowClassName', () => { - const getRowId: GridRowIdGetter = (row) => `${row.clientId}`; - const handleRowClassName: DataGridProps['getRowClassName'] = (params) => - params.row.age < 20 ? 'under-age' : ''; - render( -
- -
, - ); - expect(getRow(0)).to.have.class('under-age'); - expect(getRow(1)).to.have.class('under-age'); - expect(getRow(2)).not.to.have.class('under-age'); + describe('prop: getRowClassName', () => { + it('should apply the CSS class returned by getRowClassName', () => { + const getRowId: GridRowIdGetter = (row) => `${row.clientId}`; + const handleRowClassName: DataGridProps['getRowClassName'] = (params) => + params.row.age < 20 ? 'under-age' : ''; + render( +
+ +
, + ); + expect(getRow(0)).to.have.class('under-age'); + expect(getRow(1)).to.have.class('under-age'); + expect(getRow(2)).not.to.have.class('under-age'); + }); + + it('should call with isFirstVisible=true in the first row and isLastVisible=true in the last', () => { + const { rows, columns } = getData(4, 2); + const getRowClassName = (params: GridRowClassNameParams) => + clsx({ first: params.isFirstVisible, last: params.isLastVisible }); + render( +
+ +
, + ); + expect(getRow(0)).to.have.class('first'); + expect(getRow(1)).not.to.have.class('first'); + expect(getRow(1)).not.to.have.class('last'); + expect(getRow(2)).to.have.class('last'); + fireEvent.click(screen.getByRole('button', { name: /next page/i })); + expect(getRow(3)).to.have.class('first'); + expect(getRow(3)).to.have.class('last'); + }); }); describe('columnType: actions', () => { @@ -254,7 +287,7 @@ describe(' - Rows', () => { }); }); - describe('Row height', () => { + describe('prop: getRowHeight', () => { before(function beforeHook() { if (isJSDOM) { // Need layouting @@ -326,4 +359,124 @@ describe(' - Rows', () => { expect(getRow(2).clientHeight).to.equal(30); }); }); + + describe('prop: getRowSpacing', () => { + const { rows, columns } = getData(4, 2); + + const TestCase = (props: Partial) => { + return ( +
+ +
+ ); + }; + + it('should be called with the correct params', () => { + const getRowSpacing = stub().returns({}); + render(); + + expect(getRowSpacing.args[0][0]).to.deep.equal({ + isFirstVisible: true, + isLastVisible: false, + id: 0, + model: rows[0], + }); + expect(getRowSpacing.args[1][0]).to.deep.equal({ + isFirstVisible: false, + isLastVisible: true, + id: 1, + model: rows[1], + }); + + getRowSpacing.resetHistory(); + fireEvent.click(screen.getByRole('button', { name: /next page/i })); + + expect(getRowSpacing.args[0][0]).to.deep.equal({ + isFirstVisible: true, + isLastVisible: false, + id: 2, + model: rows[2], + }); + expect(getRowSpacing.args[1][0]).to.deep.equal({ + isFirstVisible: false, + isLastVisible: true, + id: 3, + model: rows[3], + }); + }); + + it('should consider the spacing when computing the content size', () => { + const spacingTop = 5; + const spacingBottom = 10; + const rowHeight = 50; + render( + ({ top: spacingTop, bottom: spacingBottom })} + disableVirtualization + />, + ); + const virtualScrollerContent = document.querySelector('.MuiDataGrid-virtualScrollerContent'); + const expectedHeight = rows.length * (rowHeight + spacingTop + spacingBottom); + expect(virtualScrollerContent).toHaveInlineStyle({ + width: 'auto', + height: `${expectedHeight}px`, + }); + }); + + it('should update the content size when getRowSpacing is removed', () => { + const spacingTop = 5; + const spacingBottom = 10; + const rowHeight = 50; + const { setProps } = render( + ({ top: spacingTop, bottom: spacingBottom })} + disableVirtualization + />, + ); + const virtualScrollerContent = document.querySelector('.MuiDataGrid-virtualScrollerContent'); + const expectedHeight = rows.length * (rowHeight + spacingTop + spacingBottom); + expect(virtualScrollerContent).toHaveInlineStyle({ + width: 'auto', + height: `${expectedHeight}px`, + }); + setProps({ getRowSpacing: null }); + expect(virtualScrollerContent).toHaveInlineStyle({ + width: 'auto', + height: `${rows.length * rowHeight}px`, + }); + }); + + it('should set the row margin to the value returned by getRowSpacing if rowSpacingType is not defined', () => { + const spacingTop = 5; + const spacingBottom = 10; + render( + ({ top: spacingTop, bottom: spacingBottom })} + disableVirtualization + />, + ); + expect(getRow(0)).toHaveInlineStyle({ + marginTop: `${spacingTop}px`, + marginBottom: `${spacingBottom}px`, + }); + }); + + it('should set the row border to the value returned by getRowSpacing if rowSpacingType=border', () => { + const borderTop = 5; + const borderBottom = 10; + render( + ({ top: borderTop, bottom: borderBottom })} + disableVirtualization + />, + ); + expect(getRow(0)).toHaveInlineStyle({ + borderTopWidth: `${borderTop}px`, + borderBottomWidth: `${borderBottom}px`, + }); + }); + }); }); diff --git a/scripts/x-data-grid-pro.exports.json b/scripts/x-data-grid-pro.exports.json index 3ef19bcd59037..dd32bde9d425b 100644 --- a/scripts/x-data-grid-pro.exports.json +++ b/scripts/x-data-grid-pro.exports.json @@ -321,10 +321,11 @@ { "name": "GridRootProps", "kind": "Interface" }, { "name": "GridRow", "kind": "Function" }, { "name": "GridRowApi", "kind": "Interface" }, + { "name": "GridRowClassNameParams", "kind": "Interface" }, { "name": "GridRowCount", "kind": "Variable" }, { "name": "gridRowCountSelector", "kind": "Variable" }, { "name": "GridRowData", "kind": "TypeAlias" }, - { "name": "GridRowEntry", "kind": "TypeAlias" }, + { "name": "GridRowEntry", "kind": "Interface" }, { "name": "GridRowEventLookup", "kind": "Interface" }, { "name": "GridRowGroupingApi", "kind": "Interface" }, { "name": "GridRowGroupingInitialState", "kind": "Interface" }, @@ -353,6 +354,8 @@ { "name": "GridRowsMetaApi", "kind": "Interface" }, { "name": "gridRowsMetaSelector", "kind": "Variable" }, { "name": "GridRowsMetaState", "kind": "Interface" }, + { "name": "GridRowSpacing", "kind": "Interface" }, + { "name": "GridRowSpacingParams", "kind": "Interface" }, { "name": "GridRowsProp", "kind": "TypeAlias" }, { "name": "GridRowsState", "kind": "Interface" }, { "name": "gridRowsStateSelector", "kind": "Variable" }, diff --git a/scripts/x-data-grid.exports.json b/scripts/x-data-grid.exports.json index 256479057a2df..be962ae1a824d 100644 --- a/scripts/x-data-grid.exports.json +++ b/scripts/x-data-grid.exports.json @@ -295,10 +295,11 @@ { "name": "GridRootProps", "kind": "Interface" }, { "name": "GridRow", "kind": "Function" }, { "name": "GridRowApi", "kind": "Interface" }, + { "name": "GridRowClassNameParams", "kind": "Interface" }, { "name": "GridRowCount", "kind": "Variable" }, { "name": "gridRowCountSelector", "kind": "Variable" }, { "name": "GridRowData", "kind": "TypeAlias" }, - { "name": "GridRowEntry", "kind": "TypeAlias" }, + { "name": "GridRowEntry", "kind": "Interface" }, { "name": "GridRowEventLookup", "kind": "Interface" }, { "name": "gridRowGroupingNameSelector", "kind": "Variable" }, { "name": "GridRowHeightParams", "kind": "Interface" }, @@ -319,6 +320,8 @@ { "name": "GridRowsMetaApi", "kind": "Interface" }, { "name": "gridRowsMetaSelector", "kind": "Variable" }, { "name": "GridRowsMetaState", "kind": "Interface" }, + { "name": "GridRowSpacing", "kind": "Interface" }, + { "name": "GridRowSpacingParams", "kind": "Interface" }, { "name": "GridRowsProp", "kind": "TypeAlias" }, { "name": "GridRowsState", "kind": "Interface" }, { "name": "gridRowsStateSelector", "kind": "Variable" },