diff --git a/CHANGELOG.md b/CHANGELOG.md index 38edf3ad8c9..0c6b0448d66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ ## [`29.5.0`](https://github.com/elastic/eui/tree/v29.5.0) - Added `plus` and `minus` glyphs to `EuiIcon` ([#4111](https://github.com/elastic/eui/pull/4111)) +- Added `cellActions` to `EuiDataGrid` ([#3668](https://github.com/elastic/eui/pull/3668)) **Bug fixes** diff --git a/src-docs/src/views/datagrid/column_cell_actions.js b/src-docs/src/views/datagrid/column_cell_actions.js new file mode 100644 index 00000000000..c544773dc3e --- /dev/null +++ b/src-docs/src/views/datagrid/column_cell_actions.js @@ -0,0 +1,152 @@ +import React, { useState, useCallback } from 'react'; +import { fake } from 'faker'; + +import { EuiDataGrid, EuiAvatar } from '../../../../src/components/'; + +const columns = [ + { + id: 'avatar', + initialWidth: 40, + isResizable: false, + actions: false, + }, + { + id: 'name', + initialWidth: 100, + isSortable: true, + actions: { + showHide: false, + }, + }, + { + id: 'email', + isSortable: true, + cellActions: [ + ({ rowIndex, columnId, Component }) => { + const row = ++rowIndex; + return ( + + alert(`Love sent from row ${row}, column "${columnId}"`) + } + iconType="heart" + aria-label={`Send love to ${row}, column "${columnId}" `}> + Send love + + ); + }, + ], + }, + { + id: 'city', + isSortable: true, + cellActions: [ + ({ rowIndex, columnId, Component, isExpanded }) => { + const row = ++rowIndex; + const message = isExpanded + ? `Cheers sent in Popover to row "${row}" column "${columnId}"` + : `Cheers sent from row ${row}, column "${columnId}"`; + + return ( + alert(message)} + iconType="cheer" + aria-label={message}> + Cheer + + ); + }, + ], + }, + { + id: 'country', + cellActions: [ + ({ rowIndex, columnId, Component }) => { + const row = ++rowIndex; + const label = `Love sent from row ${row}, column "${columnId}"`; + return ( + + alert(`Love sent from row ${row}, column "${columnId}"`) + } + iconType="heart" + aria-label={label}> + Love this city + + ); + }, + ({ rowIndex, columnId, Component }) => { + const row = ++rowIndex; + const label = `Paint country at row ${row}, column "${columnId}"`; + return ( + + alert(`Paint sent from row ${row}, column "${columnId}"`) + } + iconType="brush" + aria-label={label}> + Paint this city + + ); + }, + ], + }, + { + id: 'account', + }, +]; + +const data = []; + +for (let i = 1; i < 5; i++) { + data.push({ + avatar: ( + + ), + name: fake('{{name.lastName}}, {{name.firstName}} {{name.suffix}}'), + email: fake('{{internet.email}}'), + city: fake('{{address.city}}'), + country: fake('{{address.country}}'), + account: fake('{{finance.account}}'), + }); +} + +export default () => { + const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 10 }); + + const [visibleColumns, setVisibleColumns] = useState(() => + columns.map(({ id }) => id) + ); + + const setPageIndex = useCallback( + (pageIndex) => setPagination({ ...pagination, pageIndex }), + [pagination, setPagination] + ); + const setPageSize = useCallback( + (pageSize) => setPagination({ ...pagination, pageSize, pageIndex: 0 }), + [pagination, setPagination] + ); + + return ( + data[rowIndex][columnId]} + pagination={{ + ...pagination, + pageSizeOptions: [5, 10, 25], + onChangeItemsPerPage: setPageSize, + onChangePage: setPageIndex, + }} + /> + ); +}; diff --git a/src-docs/src/views/datagrid/datagrid.js b/src-docs/src/views/datagrid/datagrid.js index 17bb4c3a3d6..7479019ac57 100644 --- a/src-docs/src/views/datagrid/datagrid.js +++ b/src-docs/src/views/datagrid/datagrid.js @@ -4,6 +4,8 @@ import React, { useEffect, useMemo, useState, + createContext, + useContext, } from 'react'; import { fake } from 'faker'; @@ -17,15 +19,84 @@ import { EuiButtonIcon, EuiSpacer, } from '../../../../src/components/'; +const DataContext = createContext(); + +const raw_data = []; + +for (let i = 1; i < 100; i++) { + const email = fake('{{internet.email}}'); + const name = fake('{{name.lastName}}, {{name.firstName}}'); + const suffix = fake('{{name.suffix}}'); + raw_data.push({ + name: { + formatted: `${name} ${suffix}`, + raw: name, + }, + email: { + formatted: {fake('{{internet.email}}')}, + raw: email, + }, + location: ( + + {`${fake('{{address.city}}')}, `} + + {fake('{{address.country}}')} + + + ), + date: fake('{{date.past}}'), + account: fake('{{finance.account}}'), + amount: fake('${{commerce.price}}'), + phone: fake('{{phone.phoneNumber}}'), + version: fake('{{system.semver}}'), + }); +} const columns = [ { id: 'name', displayAsText: 'Name', defaultSortDirection: 'asc', + cellActions: [ + ({ rowIndex, columnId, Component }) => { + const data = useContext(DataContext); + return ( + alert(`Hi ${data[rowIndex][columnId].raw}`)} + iconType="heart" + aria-label={`Say hi to ${data[rowIndex][columnId].raw}!`}> + Say hi + + ); + }, + ({ rowIndex, columnId, Component }) => { + const data = useContext(DataContext); + return ( + alert(`Bye ${data[rowIndex][columnId].raw}`)} + iconType="moon" + aria-label={`Say bye to ${data[rowIndex][columnId].raw}!`}> + Say bye + + ); + }, + ], }, { id: 'email', + cellActions: [ + ({ rowIndex, columnId, Component }) => { + const data = useContext(DataContext); + return ( + alert(data[rowIndex][columnId].raw)} + iconType="email" + aria-label={`Send email to ${data[rowIndex][columnId].raw}`}> + Send email + + ); + }, + ], }, { id: 'location', @@ -46,6 +117,26 @@ const columns = [ }, ], }, + cellActions: [ + ({ rowIndex, columnId, Component, isExpanded }) => { + const data = useContext(DataContext); + const onClick = isExpanded + ? () => + alert(`Sent money to ${data[rowIndex][columnId]} when expanded`) + : () => + alert( + `Sent money to ${data[rowIndex][columnId]} when not expanded` + ); + return ( + + Send money + + ); + }, + ], }, { id: 'date', @@ -67,28 +158,6 @@ const columns = [ }, ]; -const raw_data = []; - -for (let i = 1; i < 100; i++) { - raw_data.push({ - name: fake('{{name.lastName}}, {{name.firstName}} {{name.suffix}}'), - email: {fake('{{internet.email}}')}, - location: ( - - {`${fake('{{address.city}}')}, `} - - {fake('{{address.country}}')} - - - ), - date: fake('{{date.past}}'), - account: fake('{{finance.account}}'), - amount: fake('${{commerce.price}}'), - phone: fake('{{phone.phoneNumber}}'), - version: fake('{{system.semver}}'), - }); -} - const trailingControlColumns = [ { id: 'actions', @@ -186,11 +255,12 @@ export default () => { const renderCellValue = useMemo(() => { return ({ rowIndex, columnId, setCellProps }) => { + const data = useContext(DataContext); useEffect(() => { if (columnId === 'amount') { - if (raw_data.hasOwnProperty(rowIndex)) { + if (data.hasOwnProperty(rowIndex)) { const numeric = parseFloat( - raw_data[rowIndex][columnId].match(/\d+\.\d+/)[0], + data[rowIndex][columnId].match(/\d+\.\d+/)[0], 10 ); setCellProps({ @@ -200,33 +270,41 @@ export default () => { }); } } - }, [rowIndex, columnId, setCellProps]); + }, [rowIndex, columnId, setCellProps, data]); - return raw_data.hasOwnProperty(rowIndex) - ? raw_data[rowIndex][columnId] + function getFormatted() { + return data[rowIndex][columnId].formatted + ? data[rowIndex][columnId].formatted + : data[rowIndex][columnId]; + } + + return data.hasOwnProperty(rowIndex) + ? getFormatted(rowIndex, columnId) : null; }; }, []); return ( - { - console.log(eventData); - }} - /> + + { + console.log(eventData); + }} + /> + ); }; diff --git a/src-docs/src/views/datagrid/datagrid_example.js b/src-docs/src/views/datagrid/datagrid_example.js index 51afb6ee3d5..5910e57f9bb 100644 --- a/src-docs/src/views/datagrid/datagrid_example.js +++ b/src-docs/src/views/datagrid/datagrid_example.js @@ -20,6 +20,7 @@ const dataGridHtml = renderToHtml(DataGrid); import { EuiDataGridColumn, + EuiDataGridColumnCellAction, EuiDataGridPaginationProps, EuiDataGridSorting, EuiDataGridInMemory, @@ -42,12 +43,13 @@ const gridSnippet = ` // Required. There are 200 total records. rowCount={200} // Required. Sets up three columns, the last of which has a custom schema we later define down below. - // The second column B won't allow clicking in to see the content in a popup and doesn't show move actions in column header cell // The first column defines a starting width of 150px, prevents the user from resizing it and no actions are displayed + // The second column B won't allow clicking in to see the content in a popup and doesn't show move actions in column header cell + // The third column provides one additional cell action, that triggers an alert once clicked columns={[ { id: 'A', initialWidth: 150, isResizable: false, actions: false }, { id: 'B', isExpandable: false, actions: { showMoveLeft: false, showMoveRight: false } }, - { id: 'C', schema: 'franchise'} + { id: 'C', schema: 'franchise', cellActions: [{ label: 'test', iconType: 'heart', callback: ()=> alert('test) }]} ]} // Optional. This allows you to initially hide columns. Users can still turn them on. columnVisibility={{ @@ -358,6 +360,7 @@ export const DataGridExample = { props: { EuiDataGrid, EuiDataGridColumn, + EuiDataGridColumnCellAction, EuiDataGridColumnVisibility, EuiDataGridColumnActions, EuiDataGridControlColumn, diff --git a/src-docs/src/views/datagrid/datagrid_styling_example.js b/src-docs/src/views/datagrid/datagrid_styling_example.js index 5c4c0ada88e..f11aad3f04d 100644 --- a/src-docs/src/views/datagrid/datagrid_styling_example.js +++ b/src-docs/src/views/datagrid/datagrid_styling_example.js @@ -24,14 +24,19 @@ const dataGridControlsHtml = renderToHtml(DataGridControls); import DataGridColumnWidths from './column_widths'; import DataGridColumnActions from './column_actions'; +import DataGridColumnCellActions from './column_cell_actions'; const dataGridColumnWidthsSource = require('!!raw-loader!./column_widths'); const dataGridColumnWidthsHtml = renderToHtml(DataGridColumnWidths); const dataGridColumnActionsSource = require('!!raw-loader!./column_actions'); const dataGridColumnActionsHtml = renderToHtml(DataGridColumnActions); +const dataGridColumnCellActionsSource = require('!!raw-loader!./column_cell_actions'); +const dataGridColumnCellActionsHtml = renderToHtml(DataGridColumnActions); import { EuiDataGridColumn, EuiDataGridColumnActions, + EuiDataGridColumnCellAction, + EuiDataGridColumnCellActionProps, EuiDataGridStyle, EuiDataGridToolBarVisibilityOptions, } from '!!prop-loader!../../../../src/components/datagrid/data_grid_types'; @@ -46,11 +51,11 @@ const gridSnippet = ` ), components: { DataGridColumnActions }, - snippet: widthsSnippet, props: { EuiDataGrid, EuiDataGridColumn, @@ -311,5 +315,52 @@ export const DataGridStylingExample = { }, demo: , }, + { + source: [ + { + type: GuideSectionTypes.JS, + code: dataGridColumnCellActionsSource, + }, + { + type: GuideSectionTypes.HTML, + code: dataGridColumnCellActionsHtml, + }, + ], + title: 'Column cell actions', + text: ( + +

+ On top of making a cell expandable, you can add more custom actions + by setting cellActions. This contains functions + called to render additional buttons in the cell and in the popover + when expanded. Behind the scenes those are treated as a React + components allowing hooks, context, and other React concepts to be + used. The functions receives an argument of type + EuiDataGridColumnCellActionProps. The icons of these + actions are displayed on mouse over, and also appear in the popover + when the cell is expanded. Note that once you've defined the{' '} + cellAction property, the cell's + automatically expandable. +

+

+ Below, the email and city columns provide 1{' '} + cellAction each, while the country column + provides 2 cellActions. The city column shows + another action with different alert when it's clicked in the + popover. +

+
+ ), + components: { DataGridColumnCellActions }, + props: { + EuiDataGrid, + EuiDataGridColumn, + EuiDataGridColumnActions, + EuiDataGridColumnCellAction, + EuiDataGridColumnCellActionProps, + EuiListGroupItem, + }, + demo: , + }, ], }; diff --git a/src/components/datagrid/__snapshots__/data_grid.test.tsx.snap b/src/components/datagrid/__snapshots__/data_grid.test.tsx.snap index 15a9b84c196..828d5503ecc 100644 --- a/src/components/datagrid/__snapshots__/data_grid.test.tsx.snap +++ b/src/components/datagrid/__snapshots__/data_grid.test.tsx.snap @@ -1041,7 +1041,7 @@ Array [ >