(null);
- useEffect(() => {
- setCustomGridBodyProps({
- ref: bodyRef,
- onScroll: () =>
- console.debug('scrollTop:', bodyRef.current?.scrollTop),
- });
- }, [setCustomGridBodyProps]);
-
- return (
- <>
- {visibleRows.map((row, rowIndex) => (
-
-
- {visibleColumns.map((column, colIndex) => {
- // Skip the row details cell - we'll render it manually outside of the flex wrapper
- if (column.id !== 'row-details') {
- return (
- |
- );
- }
- })}
-
- {showRowDetails && (
-
- |
-
- )}
-
- ))}
- >
- );
- },
- [showRowDetails, euiTheme]
- );
-
- return (
- <>
-
- setAutoHeight(!autoHeight)}
- />
- setShowRowDetails(!showRowDetails)}
- />
-
-
-
- >
- );
-};
diff --git a/packages/eui/src-docs/src/views/datagrid/advanced/datagrid_advanced_example.js b/packages/eui/src-docs/src/views/datagrid/advanced/datagrid_advanced_example.js
index ce49e6dda5a5..41c6868c5850 100644
--- a/packages/eui/src-docs/src/views/datagrid/advanced/datagrid_advanced_example.js
+++ b/packages/eui/src-docs/src/views/datagrid/advanced/datagrid_advanced_example.js
@@ -1,5 +1,4 @@
import React from 'react';
-import { Link } from 'react-router-dom';
import { GuideSectionTypes } from '../../../components';
import {
@@ -10,12 +9,10 @@ import {
EuiLink,
} from '../../../../../src/components';
-import {
- EuiDataGridRefProps,
- EuiDataGridCustomBodyProps,
-} from '!!prop-loader!../../../../../src/components/datagrid/data_grid_types';
+import { EuiDataGridRefProps } from '!!prop-loader!../../../../../src/components/datagrid/data_grid_types';
import { DataGridMemoryExample } from './datagrid_memory_example';
+import { DataGridCustomBodyExample } from './render_custom_grid_body/example';
import DataGridRef from './ref';
const dataGridRefSource = require('!!raw-loader!./ref');
@@ -35,28 +32,6 @@ dataGridRef.current.openCellPopover({ rowIndex, colIndex });
dataGridRef.current.closeCellPopover();
`;
-import CustomRenderer from './custom_renderer';
-const customRendererSource = require('!!raw-loader!./custom_renderer');
-const customRendererSnippet = `const CustomGridBody = ({ visibleColumns, visibleRowData, Cell }) => {
- const visibleRows = raw_data.slice(
- visibleRowData.startRow,
- visibleRowData.endRow
- );
- return (
- <>
- {visibleRows.map((row, rowIndex) => (
-
- {visibleColumns.map((column, colIndex) => (
- |
- ))}
-
- ))}
- >
- );
-};
-
- `;
-
export const DataGridAdvancedExample = {
title: 'Data grid advanced',
sections: [
@@ -211,47 +186,6 @@ export const DataGridAdvancedExample = {
props: { EuiDataGridRefProps },
},
...DataGridMemoryExample.sections,
- {
- title: 'Custom body renderer',
- source: [
- {
- type: GuideSectionTypes.TSX,
- code: customRendererSource,
- },
- ],
- text: (
- <>
-
- For extremely advanced use cases, the{' '}
- renderCustomGridBody prop may be used to take
- complete control over rendering the grid body. This may be useful
- for scenarios where the default{' '}
-
- virtualized
- {' '}
- rendering is not desired, or where custom row layouts (e.g., the
- conditional row details cell below) are required.
-
-
- Please note that this prop is meant to be an{' '}
- escape hatch , and should only be used if you know
- exactly what you are doing. Once a custom renderer is used, you are
- in charge of ensuring the grid has all the correct semantic and aria
- labels required by the{' '}
-
- data grid spec
-
- , and that keyboard focus and navigation still work in an accessible
- manner.
-
- >
- ),
- demo: ,
- snippet: customRendererSnippet,
- props: { EuiDataGridCustomBodyProps },
- },
+ DataGridCustomBodyExample,
],
};
diff --git a/packages/eui/src-docs/src/views/datagrid/advanced/render_custom_grid_body/data_columns_cells.tsx b/packages/eui/src-docs/src/views/datagrid/advanced/render_custom_grid_body/data_columns_cells.tsx
new file mode 100644
index 000000000000..f0086d3116f7
--- /dev/null
+++ b/packages/eui/src-docs/src/views/datagrid/advanced/render_custom_grid_body/data_columns_cells.tsx
@@ -0,0 +1,185 @@
+import React, { useEffect, memo } from 'react';
+import { faker } from '@faker-js/faker';
+
+import {
+ EuiDataGridProps,
+ EuiDataGridColumnCellActionProps,
+ EuiScreenReaderOnly,
+ EuiCheckbox,
+ EuiCallOut,
+ EuiButton,
+ EuiButtonIcon,
+} from '../../../../../../src';
+
+/**
+ * Mock data
+ */
+export const raw_data: Array<{ [key: string]: string }> = [];
+for (let i = 1; i < 100; i++) {
+ raw_data.push({
+ name: `${faker.person.lastName()}, ${faker.person.firstName()}`,
+ email: faker.internet.email(),
+ location: `${faker.location.city()}, ${faker.location.country()}`,
+ date: `${faker.date.past()}`,
+ amount: faker.commerce.price({ min: 1, max: 1000, dec: 2, symbol: '$' }),
+ feesOwed: faker.datatype.boolean() ? 'true' : '',
+ });
+}
+const footer_data: { [key: string]: string } = {
+ amount: `Total: ${raw_data
+ .reduce((acc, { amount }) => acc + Number(amount.split('$')[1]), 0)
+ .toLocaleString('en-US', { style: 'currency', currency: 'USD' })}`,
+};
+
+/**
+ * Columns
+ */
+export const columns = [
+ {
+ id: 'name',
+ displayAsText: 'Name',
+ cellActions: [
+ ({ Component }: EuiDataGridColumnCellActionProps) => (
+ alert('action')}
+ iconType="faceHappy"
+ aria-label="Some action"
+ >
+ Some action
+
+ ),
+ ],
+ },
+ {
+ id: 'email',
+ displayAsText: 'Email address',
+ initialWidth: 130,
+ },
+ {
+ id: 'location',
+ displayAsText: 'Location',
+ },
+ {
+ id: 'date',
+ displayAsText: 'Date',
+ },
+ {
+ id: 'amount',
+ displayAsText: 'Amount',
+ },
+];
+
+/**
+ * Cell component
+ */
+export const RenderCellValue: EuiDataGridProps['renderCellValue'] = ({
+ rowIndex,
+ columnId,
+}) => raw_data[rowIndex][columnId];
+
+/**
+ * Row details component
+ */
+// eslint-disable-next-line local/forward-ref
+const RenderRowDetails: EuiDataGridProps['renderCellValue'] = memo(
+ ({ setCellProps, rowIndex }) => {
+ setCellProps({ style: { width: '100%', height: 'auto' } });
+
+ // Mock data
+ const firstName = raw_data[rowIndex].name.split(', ')[1];
+ const hasFees = !!raw_data[rowIndex].feesOwed;
+
+ return (
+
+ {hasFees && (
+
+ Send an email reminder
+
+ )}
+
+ );
+ }
+);
+
+/**
+ * Control columns
+ */
+export const leadingControlColumns: EuiDataGridProps['leadingControlColumns'] =
+ [
+ {
+ id: 'selection',
+ width: 32,
+ headerCellRender: () => (
+ {}}
+ />
+ ),
+ rowCellRender: ({ rowIndex }) => (
+ {}}
+ />
+ ),
+ },
+ ];
+export const trailingControlColumns: EuiDataGridProps['trailingControlColumns'] =
+ [
+ {
+ id: 'actions',
+ width: 40,
+ headerCellRender: () => (
+
+ Actions
+
+ ),
+ rowCellRender: () => (
+
+ ),
+ },
+ // The custom row details is actually a trailing control column cell with
+ // a hidden header. This is important for accessibility and markup reasons
+ // @see https://fuschia-stretch.glitch.me/ for more
+ {
+ id: 'row-details',
+
+ // The header cell should be visually hidden, but available to screen readers
+ width: 0,
+ headerCellRender: () => <>Row details>,
+ headerCellProps: { className: 'euiScreenReaderOnly' },
+
+ // The footer cell can be hidden to both visual & SR users, as it does not contain meaningful information
+ footerCellProps: { style: { display: 'none' } },
+
+ // When rendering this custom cell, we'll want to override
+ // the automatic width/heights calculated by EuiDataGrid
+ rowCellRender: RenderRowDetails,
+ },
+ ];
+
+/**
+ * Footer cell component
+ */
+export const RenderFooterCellValue: EuiDataGridProps['renderFooterCellValue'] =
+ ({ columnId, setCellProps }) => {
+ const value = footer_data[columnId];
+
+ useEffect(() => {
+ // Turn off the cell expansion button if the footer cell is empty
+ if (!value) setCellProps({ isExpandable: false });
+ }, [value, setCellProps, columnId]);
+
+ return value || null;
+ };
diff --git a/packages/eui/src-docs/src/views/datagrid/advanced/render_custom_grid_body/data_grid.tsx b/packages/eui/src-docs/src/views/datagrid/advanced/render_custom_grid_body/data_grid.tsx
new file mode 100644
index 000000000000..008d1abd4ab3
--- /dev/null
+++ b/packages/eui/src-docs/src/views/datagrid/advanced/render_custom_grid_body/data_grid.tsx
@@ -0,0 +1,69 @@
+import React, { useCallback, useState } from 'react';
+
+import {
+ EuiDataGrid,
+ EuiDataGridProps,
+ EuiDataGridPaginationProps,
+ EuiDataGridSorting,
+ EuiDataGridColumnSortingConfig,
+} from '../../../../../../src';
+
+import {
+ raw_data,
+ columns,
+ leadingControlColumns,
+ trailingControlColumns,
+ RenderCellValue,
+ RenderFooterCellValue,
+} from './data_columns_cells';
+
+export default (props: Partial>) => {
+ // Column visibility
+ const [visibleColumns, setVisibleColumns] = useState(() =>
+ columns.map(({ id }) => id)
+ );
+
+ // Pagination
+ const [pagination, setPagination] = useState({ pageIndex: 0 });
+ const onChangePage = useCallback(
+ (pageIndex) => {
+ setPagination((pagination) => ({ ...pagination, pageIndex }));
+ },
+ []
+ );
+ const onChangePageSize = useCallback<
+ EuiDataGridPaginationProps['onChangeItemsPerPage']
+ >((pageSize) => {
+ setPagination((pagination) => ({ ...pagination, pageSize }));
+ }, []);
+
+ // Sorting
+ const [sortingColumns, setSortingColumns] = useState<
+ EuiDataGridColumnSortingConfig[]
+ >([]);
+ const onSort = useCallback((sortingColumns) => {
+ setSortingColumns(sortingColumns);
+ }, []);
+
+ return (
+
+ );
+};
diff --git a/packages/eui/src-docs/src/views/datagrid/advanced/render_custom_grid_body/example.js b/packages/eui/src-docs/src/views/datagrid/advanced/render_custom_grid_body/example.js
new file mode 100644
index 000000000000..3edb747ebec6
--- /dev/null
+++ b/packages/eui/src-docs/src/views/datagrid/advanced/render_custom_grid_body/example.js
@@ -0,0 +1,123 @@
+import React, { useState } from 'react';
+import { Link } from 'react-router-dom';
+import {
+ EuiSplitPanel,
+ EuiFlexGroup,
+ EuiCode,
+ EuiSwitch,
+ EuiSpacer,
+ EuiLink,
+} from '../../../../../../src/components';
+
+import { GuideSectionTypes } from '../../../../components';
+import { GuideSectionCodeTypesMap } from '../../../../components/guide_section/guide_section';
+import { GuideSectionExampleTabs } from '../../../../components/guide_section/guide_section_parts/guide_section_tabs';
+
+import { EuiDataGridCustomBodyProps } from '!!prop-loader!../../../../../../src/components/datagrid/data_grid_types';
+
+import VirtualizedBody from './virtualized_body';
+const virtualizedSource = require('!!raw-loader!./virtualized_body');
+
+import UnvirtualizedBody from './unvirtualized_body';
+const unvirtualizedSource = require('!!raw-loader!./unvirtualized_body');
+
+const dataGridSource = require('!!raw-loader!./data_grid');
+const dataSource = require('!!raw-loader!./data_columns_cells');
+
+export const ConditionalDemo = () => {
+ const [virtualized, setVirtualized] = useState(false);
+ const [autoHeight, setAutoHeight] = useState(true);
+
+ return (
+
+
+
+ setVirtualized(!virtualized)}
+ />
+ setAutoHeight(!autoHeight)}
+ />
+
+
+
+ {virtualized ? (
+
+ ) : (
+
+ )}
+
+
+
+
+
+ );
+};
+
+export const DataGridCustomBodyExample = {
+ title: 'Custom body renderer',
+ text: (
+ <>
+
+ For extremely advanced use cases, the{' '}
+ renderCustomGridBody prop may be used to take
+ complete control over rendering the grid body. This may be useful for
+ scenarios where the default{' '}
+ virtualized{' '}
+ rendering is not desired, or where custom row layouts (e.g., the
+ full-width row details cell below) are required.
+
+
+ Please note that this prop is meant to be an{' '}
+ escape hatch , and should only be used if you know
+ exactly what you are doing. Once a custom renderer is used, you are in
+ charge of ensuring the grid has all the correct semantic and aria labels
+ required by the{' '}
+
+ data grid spec
+
+ , and that keyboard focus and navigation still work in an accessible
+ manner.
+
+ >
+ ),
+ children: (
+ <>
+
+
+ >
+ ),
+};
diff --git a/packages/eui/src-docs/src/views/datagrid/advanced/render_custom_grid_body/unvirtualized_body.tsx b/packages/eui/src-docs/src/views/datagrid/advanced/render_custom_grid_body/unvirtualized_body.tsx
new file mode 100644
index 000000000000..a84a2fb85a3e
--- /dev/null
+++ b/packages/eui/src-docs/src/views/datagrid/advanced/render_custom_grid_body/unvirtualized_body.tsx
@@ -0,0 +1,96 @@
+import React, { useEffect, useRef, memo } from 'react';
+import { css } from '@emotion/react';
+
+import {
+ EuiDataGridCustomBodyProps,
+ useEuiTheme,
+ logicalCSS,
+} from '../../../../../../src';
+
+import { raw_data } from './data_columns_cells';
+import CustomEuiDataGrid from './data_grid';
+
+export const CustomUnvirtualizedGridBody = memo(
+ ({
+ Cell,
+ visibleColumns,
+ visibleRowData,
+ setCustomGridBodyProps,
+ headerRow,
+ footerRow,
+ }: EuiDataGridCustomBodyProps) => {
+ // Ensure we're displaying correctly-paginated rows
+ const visibleRows = raw_data.slice(
+ visibleRowData.startRow,
+ visibleRowData.endRow
+ );
+
+ // Add styling needed for custom grid body rows
+ const { euiTheme } = useEuiTheme();
+ const styles = {
+ row: css`
+ ${logicalCSS('width', 'fit-content')};
+ ${logicalCSS('border-bottom', euiTheme.border.thin)};
+ background-color: ${euiTheme.colors.emptyShade};
+ `,
+ rowCellsWrapper: css`
+ display: flex;
+ `,
+ rowDetailsWrapper: css`
+ /* Extra specificity needed to override EuiDataGrid's default styles */
+ && .euiDataGridRowCell__content {
+ display: block;
+ padding: 0;
+ }
+ `,
+ };
+
+ // Set custom props onto the grid body wrapper
+ const bodyRef = useRef(null);
+ useEffect(() => {
+ setCustomGridBodyProps({
+ ref: bodyRef,
+ onScroll: () => console.debug('scrollTop:', bodyRef.current?.scrollTop),
+ });
+ }, [setCustomGridBodyProps]);
+
+ return (
+ <>
+ {headerRow}
+ {visibleRows.map((row, rowIndex) => (
+
+
+ {visibleColumns.map((column, colIndex) => {
+ // Skip the row details cell - we'll render it manually outside of the flex wrapper
+ if (column.id !== 'row-details') {
+ return (
+ |
+ );
+ }
+ })}
+
+
+ |
+
+
+ ))}
+ {footerRow}
+ >
+ );
+ }
+);
+CustomUnvirtualizedGridBody.displayName = 'CustomUnvirtualizedGridBody';
+
+export default ({ autoHeight }: { autoHeight: boolean }) => (
+
+);
diff --git a/packages/eui/src-docs/src/views/datagrid/advanced/render_custom_grid_body/virtualized_body.tsx b/packages/eui/src-docs/src/views/datagrid/advanced/render_custom_grid_body/virtualized_body.tsx
new file mode 100644
index 000000000000..f53ad032ccda
--- /dev/null
+++ b/packages/eui/src-docs/src/views/datagrid/advanced/render_custom_grid_body/virtualized_body.tsx
@@ -0,0 +1,216 @@
+import React, {
+ useEffect,
+ useCallback,
+ useRef,
+ useMemo,
+ memo,
+ forwardRef,
+ PropsWithChildren,
+ CSSProperties,
+} from 'react';
+import { VariableSizeList } from 'react-window';
+import { css } from '@emotion/react';
+
+import {
+ EuiDataGridCustomBodyProps,
+ EuiAutoSizer,
+ useEuiTheme,
+ logicalCSS,
+} from '../../../../../../src';
+
+import { raw_data } from './data_columns_cells';
+import CustomEuiDataGrid from './data_grid';
+
+type CustomTimelineDataGridSingleRowProps = {
+ rowIndex: number;
+ setRowHeight: (index: number, height: number) => void;
+ maxWidth: number | undefined;
+} & Pick;
+
+const Row = memo(
+ ({
+ rowIndex,
+ visibleColumns,
+ setRowHeight,
+ Cell,
+ }: CustomTimelineDataGridSingleRowProps) => {
+ const { euiTheme } = useEuiTheme();
+ const styles = {
+ row: css`
+ ${logicalCSS('width', 'fit-content')};
+ ${logicalCSS('border-bottom', euiTheme.border.thin)};
+ background-color: ${euiTheme.colors.emptyShade};
+ `,
+ rowCellsWrapper: css`
+ display: flex;
+ `,
+ rowDetailsWrapper: css`
+ /* Extra specificity needed to override EuiDataGrid's default styles */
+ && .euiDataGridRowCell__content {
+ display: block;
+ padding: 0;
+ }
+ `,
+ };
+ const rowRef = useRef(null);
+
+ useEffect(() => {
+ if (rowRef.current) {
+ setRowHeight(rowIndex, rowRef.current.offsetHeight);
+ }
+ }, [Cell, rowIndex, setRowHeight]);
+
+ return (
+
+
+ {visibleColumns.map((column, colIndex) => {
+ // Skip the row details cell - we'll render it manually outside of the flex wrapper
+ if (column.id !== 'row-details') {
+ return (
+ |
+ );
+ }
+ })}
+
+
+ |
+
+
+ );
+ }
+);
+Row.displayName = 'Row';
+
+export const CustomVirtualizedGridBody = memo(
+ ({
+ Cell,
+ visibleColumns,
+ visibleRowData,
+ setCustomGridBodyProps,
+ headerRow,
+ footerRow,
+ }: EuiDataGridCustomBodyProps) => {
+ // Ensure we're displaying correctly-paginated rows
+ const visibleRows = raw_data.slice(
+ visibleRowData.startRow,
+ visibleRowData.endRow
+ );
+
+ // Set custom props onto the grid body wrapper
+ const bodyRef = useRef(null);
+ useEffect(() => {
+ setCustomGridBodyProps({
+ ref: bodyRef,
+ onScroll: () => console.debug('scrollTop:', bodyRef.current?.scrollTop),
+ });
+ }, [setCustomGridBodyProps]);
+
+ const listRef = useRef>(null);
+ const rowHeights = useRef([]);
+
+ const setRowHeight = useCallback((index: number, height: number) => {
+ if (rowHeights.current[index] === height) return;
+ listRef.current?.resetAfterIndex(index);
+
+ rowHeights.current[index] = height;
+ }, []);
+
+ const getRowHeight = useCallback((index: number) => {
+ return rowHeights.current[index] ?? 100;
+ }, []);
+
+ const outer = useMemo(
+ () =>
+ forwardRef>(
+ ({ children, ...rest }, ref) => {
+ return (
+
+ {headerRow}
+ {children}
+ {footerRow}
+
+ );
+ }
+ ),
+ [headerRow, footerRow]
+ );
+
+ const inner = useMemo(
+ () =>
+ forwardRef>(
+ ({ children, style, ...rest }, ref) => {
+ return (
+
+ {children}
+
+ );
+ }
+ ),
+ []
+ );
+
+ return (
+
+ {({ height }: { height: number }) => {
+ return (
+ `${height}-${visibleRows.length}-${index}`}
+ outerElementType={outer}
+ innerElementType={inner}
+ overscanCount={0}
+ layout="vertical"
+ >
+ {({ index: rowIndex, style }) => {
+ return (
+
+
+
+ );
+ }}
+
+ );
+ }}
+
+ );
+ }
+);
+CustomVirtualizedGridBody.displayName = 'CustomVirtualizedGridBody';
+
+export default () => (
+
+);
diff --git a/packages/eui/src/components/datagrid/body/data_grid_body_custom.spec.tsx b/packages/eui/src/components/datagrid/body/data_grid_body_custom.spec.tsx
index bce75af98579..6bfcf859f0b2 100644
--- a/packages/eui/src/components/datagrid/body/data_grid_body_custom.spec.tsx
+++ b/packages/eui/src/components/datagrid/body/data_grid_body_custom.spec.tsx
@@ -22,6 +22,8 @@ describe('EuiDataGridBodyCustomRender', () => {
visibleColumns,
visibleRowData,
Cell,
+ headerRow,
+ footerRow,
}) => {
const visibleRows = raw_data.slice(
visibleRowData.startRow,
@@ -29,6 +31,7 @@ describe('EuiDataGridBodyCustomRender', () => {
);
return (
<>
+ {headerRow}
{visibleRows.map((row, rowIndex) => (
{visibleColumns.map((column, colIndex) => (
@@ -41,6 +44,7 @@ describe('EuiDataGridBodyCustomRender', () => {
))}
))}
+ {footerRow}
>
);
};
diff --git a/packages/eui/src/components/datagrid/body/data_grid_body_custom.test.tsx b/packages/eui/src/components/datagrid/body/data_grid_body_custom.test.tsx
index 43012117206a..1620054e0274 100644
--- a/packages/eui/src/components/datagrid/body/data_grid_body_custom.test.tsx
+++ b/packages/eui/src/components/datagrid/body/data_grid_body_custom.test.tsx
@@ -37,6 +37,8 @@ describe('EuiDataGridBodyCustomRender', () => {
const CustomGridBody: EuiDataGridProps['renderCustomGridBody'] = ({
visibleColumns,
visibleRowData,
+ headerRow,
+ footerRow,
Cell,
}) => {
const visibleRows = raw_data.slice(
@@ -45,6 +47,7 @@ describe('EuiDataGridBodyCustomRender', () => {
);
return (
<>
+ {headerRow}
{visibleRows.map((row, rowIndex) => (
{visibleColumns.map((column, colIndex) => (
@@ -56,6 +59,7 @@ describe('EuiDataGridBodyCustomRender', () => {
))}
))}
+ {footerRow}
>
);
};
diff --git a/packages/eui/src/components/datagrid/body/data_grid_body_custom.tsx b/packages/eui/src/components/datagrid/body/data_grid_body_custom.tsx
index 2cd174c007b9..513623263f56 100644
--- a/packages/eui/src/components/datagrid/body/data_grid_body_custom.tsx
+++ b/packages/eui/src/components/datagrid/body/data_grid_body_custom.tsx
@@ -8,9 +8,10 @@
import React, {
FunctionComponent,
+ JSXElementConstructor,
useState,
useMemo,
- useCallback,
+ memo,
} from 'react';
import classNames from 'classnames';
@@ -26,186 +27,251 @@ import { useDataGridHeader } from './header';
import { useDataGridFooter } from './footer';
import { CellWrapper } from './cell';
-export const EuiDataGridBodyCustomRender: FunctionComponent<
- EuiDataGridBodyProps
-> = ({
- renderCustomGridBody,
- renderCellValue,
- cellContext,
- renderCellPopover,
- renderFooterCellValue,
- interactiveCellId,
- visibleRows,
- visibleColCount,
- leadingControlColumns,
- trailingControlColumns,
- columns,
- setVisibleColumns,
- switchColumnPos,
- onColumnResize,
- schema,
- schemaDetectors,
- sorting,
- pagination,
- rowHeightsOptions,
- gridWidth,
- gridStyles,
- className,
-}) => {
- /**
- * Columns & widths
- */
- const visibleColumns = useMemo(() => {
- return [...leadingControlColumns, ...columns, ...trailingControlColumns];
- }, [columns, leadingControlColumns, trailingControlColumns]);
-
- // compute the default column width from the container's width and count of visible columns
- const defaultColumnWidth = useDefaultColumnWidth(
- gridWidth,
- leadingControlColumns,
- trailingControlColumns,
- columns
- );
-
- const { columnWidths, setColumnWidth } = useColumnWidths({
- columns,
- leadingControlColumns,
- trailingControlColumns,
- defaultColumnWidth,
- onColumnResize,
- });
-
- /**
- * Row heights
- */
- const rowHeightUtils = useRowHeightUtils({
- rowHeightsOptions,
- gridStyles,
- columns,
- });
-
- const { setRowHeight, getRowHeight } = useDefaultRowHeight({
- rowHeightsOptions,
- rowHeightUtils,
- });
-
- /**
- * Header & footer
- */
- const { headerRow } = useDataGridHeader({
- leadingControlColumns,
- trailingControlColumns,
- columns,
- columnWidths,
- defaultColumnWidth,
- setColumnWidth,
- visibleColCount,
- setVisibleColumns,
- switchColumnPos,
- sorting,
- schema,
- schemaDetectors,
- gridStyles,
- });
-
- const { footerRow } = useDataGridFooter({
- renderFooterCellValue,
- renderCellPopover,
- rowIndex: visibleRows.visibleRowCount,
- visibleRowIndex: visibleRows.visibleRowCount,
- visibleColCount,
- interactiveCellId,
- leadingControlColumns,
- trailingControlColumns,
- columns,
- columnWidths,
- defaultColumnWidth,
- schema,
- gridStyles,
- });
-
- /**
- * Cell render fn
- */
- const cellProps = useMemo(() => {
- return {
- schema,
- schemaDetectors,
- pagination,
- columns,
- leadingControlColumns,
- trailingControlColumns,
- visibleColCount,
- columnWidths,
- defaultColumnWidth,
+export const EuiDataGridBodyCustomRender: FunctionComponent =
+ memo(
+ ({
+ renderCustomGridBody,
renderCellValue,
cellContext,
renderCellPopover,
+ renderFooterCellValue,
interactiveCellId,
- setRowHeight,
+ visibleRows,
+ visibleColCount,
+ leadingControlColumns,
+ trailingControlColumns,
+ columns,
+ setVisibleColumns,
+ switchColumnPos,
+ onColumnResize,
+ schema,
+ schemaDetectors,
+ sorting,
+ pagination,
rowHeightsOptions,
- rowHeightUtils,
+ gridWidth,
gridStyles,
- };
- }, [
- schema,
- schemaDetectors,
- pagination,
- columns,
- leadingControlColumns,
- trailingControlColumns,
- visibleColCount,
- columnWidths,
- defaultColumnWidth,
- renderCellValue,
- cellContext,
- renderCellPopover,
- interactiveCellId,
- setRowHeight,
- rowHeightsOptions,
- rowHeightUtils,
- gridStyles,
- ]);
-
- const Cell = useCallback(
- ({ colIndex, visibleRowIndex, ...rest }) => {
- const style = {
- height: rowHeightUtils.isAutoHeight(visibleRowIndex, rowHeightsOptions)
- ? 'auto'
- : getRowHeight(visibleRowIndex),
- };
- const props = {
- colIndex,
- visibleRowIndex,
- style,
- ...cellProps,
- };
- return ;
- },
- [cellProps, getRowHeight, rowHeightUtils, rowHeightsOptions]
- );
+ className,
+ }) => {
+ /**
+ * Columns & widths
+ */
+ const visibleColumns = useMemo(() => {
+ return [
+ ...leadingControlColumns,
+ ...columns,
+ ...trailingControlColumns,
+ ];
+ }, [columns, leadingControlColumns, trailingControlColumns]);
+
+ // compute the default column width from the container's width and count of visible columns
+ const defaultColumnWidth = useDefaultColumnWidth(
+ gridWidth,
+ leadingControlColumns,
+ trailingControlColumns,
+ columns
+ );
+
+ const { columnWidths, setColumnWidth } = useColumnWidths({
+ columns,
+ leadingControlColumns,
+ trailingControlColumns,
+ defaultColumnWidth,
+ onColumnResize,
+ });
+
+ /**
+ * Row heights
+ */
+ const rowHeightUtils = useRowHeightUtils({
+ rowHeightsOptions,
+ gridStyles,
+ columns,
+ });
+
+ const { setRowHeight, getRowHeight } = useDefaultRowHeight({
+ rowHeightsOptions,
+ rowHeightUtils,
+ });
+
+ const headerRowProps = useMemo(() => {
+ return {
+ leadingControlColumns,
+ trailingControlColumns,
+ columns,
+ columnWidths,
+ defaultColumnWidth,
+ setColumnWidth,
+ setVisibleColumns,
+ visibleColCount,
+ switchColumnPos,
+ sorting,
+ schema,
+ schemaDetectors,
+ gridStyles,
+ };
+ }, [
+ leadingControlColumns,
+ trailingControlColumns,
+ columns,
+ columnWidths,
+ defaultColumnWidth,
+ setColumnWidth,
+ visibleColCount,
+ setVisibleColumns,
+ switchColumnPos,
+ sorting,
+ schema,
+ schemaDetectors,
+ gridStyles,
+ ]);
+
+ /**
+ * Header & footer
+ */
+ const { headerRow } = useDataGridHeader(headerRowProps);
+
+ const footerRowProps = useMemo(
+ () => ({
+ renderFooterCellValue,
+ renderCellPopover,
+ rowIndex: visibleRows.visibleRowCount,
+ visibleRowIndex: visibleRows.visibleRowCount,
+ visibleColCount,
+ interactiveCellId,
+ leadingControlColumns,
+ trailingControlColumns,
+ columns,
+ columnWidths,
+ defaultColumnWidth,
+ schema,
+ gridStyles,
+ }),
+ [
+ renderFooterCellValue,
+ renderCellPopover,
+ visibleRows.visibleRowCount,
+ visibleColCount,
+ interactiveCellId,
+ leadingControlColumns,
+ trailingControlColumns,
+ columns,
+ columnWidths,
+ defaultColumnWidth,
+ schema,
+ gridStyles,
+ ]
+ );
- // Allow consumers to pass custom props/attributes/listeners etc. to the wrapping div
- const [customGridBodyProps, setCustomGridBodyProps] =
- useState({});
-
- return (
-
- {headerRow}
- {renderCustomGridBody!({
- visibleColumns,
- visibleRowData: visibleRows,
- Cell,
- setCustomGridBodyProps,
- })}
- {footerRow}
-
+ const { footerRow } = useDataGridFooter(footerRowProps);
+
+ /**
+ * Cell render fn
+ */
+ const cellProps = useMemo(() => {
+ return {
+ schema,
+ schemaDetectors,
+ pagination,
+ columns,
+ leadingControlColumns,
+ trailingControlColumns,
+ visibleColCount,
+ columnWidths,
+ defaultColumnWidth,
+ renderCellValue,
+ cellContext,
+ renderCellPopover,
+ interactiveCellId,
+ setRowHeight,
+ rowHeightsOptions,
+ rowHeightUtils,
+ gridStyles,
+ };
+ }, [
+ schema,
+ schemaDetectors,
+ pagination,
+ columns,
+ leadingControlColumns,
+ trailingControlColumns,
+ visibleColCount,
+ columnWidths,
+ defaultColumnWidth,
+ renderCellValue,
+ cellContext,
+ renderCellPopover,
+ interactiveCellId,
+ setRowHeight,
+ rowHeightsOptions,
+ rowHeightUtils,
+ gridStyles,
+ ]);
+
+ const Cell: EuiDataGridCustomBodyProps['Cell'] = useMemo(
+ () =>
+ ({ colIndex, visibleRowIndex, ...rest }) => {
+ const style = {
+ height: rowHeightUtils.isAutoHeight(
+ visibleRowIndex,
+ rest.rowHeightsOptions ?? rowHeightsOptions
+ )
+ ? 'auto'
+ : getRowHeight(visibleRowIndex),
+ };
+
+ const props = {
+ colIndex,
+ visibleRowIndex,
+ style,
+ ...cellProps,
+ };
+ return ;
+ },
+ [cellProps, getRowHeight, rowHeightUtils, rowHeightsOptions]
+ );
+
+ // Allow consumers to pass custom props/attributes/listeners etc. to the wrapping div
+ const [customGridBodyProps, setCustomGridBodyProps] =
+ useState({});
+
+ const customDataGridBodyProps: EuiDataGridCustomBodyProps = useMemo(
+ () => ({
+ gridWidth,
+ visibleColumns,
+ visibleRowData: visibleRows,
+ Cell,
+ setCustomGridBodyProps,
+ headerRow,
+ footerRow,
+ }),
+ [
+ gridWidth,
+ visibleColumns,
+ visibleRows,
+ Cell,
+ setCustomGridBodyProps,
+ headerRow,
+ footerRow,
+ ]
+ );
+
+ const BodyElement =
+ renderCustomGridBody as JSXElementConstructor;
+
+ return (
+
+
+
+ );
+ }
);
-};
+
+EuiDataGridBodyCustomRender.displayName = 'EuiDataGridBodyCustomRender';
diff --git a/packages/eui/src/components/datagrid/data_grid_types.ts b/packages/eui/src/components/datagrid/data_grid_types.ts
index d4983b13af83..a2f459f6bbf5 100644
--- a/packages/eui/src/components/datagrid/data_grid_types.ts
+++ b/packages/eui/src/components/datagrid/data_grid_types.ts
@@ -516,6 +516,19 @@ export interface EuiDataGridCustomBodyProps {
* It's best to wrap calls to `setCustomGridBodyProps` in a `useEffect` hook
*/
setCustomGridBodyProps: (props: EuiDataGridSetCustomGridBodyProps) => void;
+
+ /**
+ * The width of the grid, can be used by consumer as a layout utility
+ */
+ gridWidth: number;
+ /**
+ * Header row component to render by custom renderer
+ * */
+ headerRow: React.JSX.Element;
+ /**
+ * Footer row component to render by custom renderer
+ * */
+ footerRow: React.JSX.Element | null;
}
export type EuiDataGridSetCustomGridBodyProps = CommonProps &