Skip to content

Commit

Permalink
[EuiDataGrid] Replace popoverContents API with renderCellPopover (#…
Browse files Browse the repository at this point in the history
…5640)

* [types] Set up interface for new `renderCellPopover` API

- which replaces `popoverContents` and shares some common props with `renderCellValue`

* [types] Replace component popoverContents prop with renderCellPopover

+ improve prop documentation for renderCellValue, renderFooterCellValue

* [parents] Remove passed popoverContents for renderCellPopover
- in grid parent components

* [cell] Remove popoverContent for renderCellPopover

* Convert DefaultColumnFormatter to DefaultCellPopover

+ move to data_grid_cell_popover instead of popover_utils

* Pass cellActions to popover renderers as JSX

* Pass schema info to both renderCellValue and renderCellPopover

- the popover needs this to conditionally change formatting based on schema, and renderCellValue seems like it could use this info as well

* Convert default JSON popover formatter

* Delete popover_utils

* Add defaultPopoverRender prop to renderCellPopover

- which will allow custom renderers to pass back the default renderer if they only want custom rendering for certain popovers

* [misc] fix Typescript complaints in data_grid.spec file

- likely created by recent Cypress reference/ts PRs

* Add Cypress E2E tests for cell popover customization/rendering

* [docs] Add new cell popover documentation page and examples

* [docs] Remove cell popover example/logic from schema page

+ remove props unrelated to schema

+ schema.js misc cleanup:
- Remove unnecessary pagination state/props - there's only 5 items
- Remove unnecessary conditional JSON
- Make Star Wars vs Star Trek alternating, so sorting does more
- Fix aria-label
- (lint) fragment, export default

* [docs] Update main datagrid documentation page

- remove popoverContents, add renderCellPopover
- improve documentation on rendercellValue

* [docs] Misc fixes/improvements to main datagrid snippet

- Fix several incorrect instances of `optional` vs `required` notation
- move inMemory down to sorting/pagination instead of being at the top, since it's an optional prop
- misc indentation/lint fixes

* Add changelog entry

* [PR feedback] Change `defaultPopoverRender` to `DefaultCellPopover`

- the component usage is cleaner than a render fn

* [PR feedback] Documentation copy

* [PR feedback] Emphasize that leaving out the cell actions is not recommended
  • Loading branch information
Constance authored Feb 16, 2022
1 parent 327adcf commit 93b70f9
Show file tree
Hide file tree
Showing 24 changed files with 1,011 additions and 419 deletions.
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
## [`main`](https://github.com/elastic/eui/tree/main)

No public interface changes since `48.1.1`.
- Added new `renderCellPopover` prop to `EuiDataGrid` ([#5640](https://github.com/elastic/eui/pull/5640))
- Added cell `schema` info to `EuiDataGrid`'s `renderCellValue` props ([#5640](https://github.com/elastic/eui/pull/5640))

**Breaking changes**

- Removed `popoverContents` props from `EuiDataGrid` (use new `renderCellPopover` instead) ([#5640](https://github.com/elastic/eui/pull/5640))

## [`48.1.1`](https://github.com/elastic/eui/tree/v48.1.1)

Expand Down
2 changes: 2 additions & 0 deletions src-docs/src/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ import { CopyExample } from './views/copy/copy_example';
import { DataGridExample } from './views/datagrid/datagrid_example';
import { DataGridMemoryExample } from './views/datagrid/datagrid_memory_example';
import { DataGridSchemaExample } from './views/datagrid/datagrid_schema_example';
import { DataGridCellPopoverExample } from './views/datagrid/datagrid_cell_popover_example';
import { DataGridFocusExample } from './views/datagrid/datagrid_focus_example';
import { DataGridStylingExample } from './views/datagrid/datagrid_styling_example';
import { DataGridControlColumnsExample } from './views/datagrid/datagrid_controlcolumns_example';
Expand Down Expand Up @@ -486,6 +487,7 @@ const navigation = [
DataGridExample,
DataGridMemoryExample,
DataGridSchemaExample,
DataGridCellPopoverExample,
DataGridFocusExample,
DataGridStylingExample,
DataGridControlColumnsExample,
Expand Down
96 changes: 96 additions & 0 deletions src-docs/src/views/datagrid/cell_popover_is_details.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import React, { useState, ReactNode } from 'react';
// @ts-ignore - faker does not have type declarations
import { fake } from 'faker';

import {
EuiDataGrid,
EuiDataGridCellValueElementProps,
EuiDataGridColumnCellAction,
EuiDataGridColumn,
EuiTitle,
} from '../../../../src/components';

const cellActions: EuiDataGridColumnCellAction[] = [
({ Component }) => (
<Component iconType="plusInCircle" aria-label="Filter in">
Filter in
</Component>
),
({ Component }) => (
<Component iconType="minusInCircle" aria-label="Filter out">
Filter out
</Component>
),
];

const columns: EuiDataGridColumn[] = [
{
id: 'default',
cellActions,
},
{
id: 'datetime',
cellActions,
},
{
id: 'json',
schema: 'json',
cellActions,
},
{
id: 'custom',
schema: 'favoriteFranchise',
cellActions,
},
];

const data: Array<{ [key: string]: ReactNode }> = [];
for (let i = 1; i < 5; i++) {
data.push({
default: fake('{{name.lastName}}, {{name.firstName}} {{name.suffix}}'),
datetime: fake('{{date.past}}'),
json: JSON.stringify([
{
numeric: fake('{{finance.account}}'),
currency: fake('${{finance.amount}}'),
date: fake('{{date.past}}'),
},
]),
custom: i % 2 === 0 ? 'Star Wars' : 'Star Trek',
});
}

const RenderCellValue = ({
rowIndex,
columnId,
schema,
isDetails,
}: EuiDataGridCellValueElementProps) => {
let value = data[rowIndex][columnId];

if (schema === 'favoriteFranchise' && isDetails) {
value = (
<EuiTitle size="xs">
<h3>{value} is the best!</h3>
</EuiTitle>
);
}

return value;
};

export default () => {
const [visibleColumns, setVisibleColumns] = useState(
columns.map(({ id }) => id)
);

return (
<EuiDataGrid
aria-label="Data grid example of renderCellValue.isDetails"
columns={columns}
columnVisibility={{ visibleColumns, setVisibleColumns }}
rowCount={data.length}
renderCellValue={RenderCellValue}
/>
);
};
68 changes: 68 additions & 0 deletions src-docs/src/views/datagrid/cell_popover_is_expandable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import React, { useState, ReactNode } from 'react';
// @ts-ignore - faker does not have type declarations
import { fake } from 'faker';

import {
EuiDataGrid,
EuiDataGridColumnCellAction,
EuiDataGridColumn,
} from '../../../../src/components';

const cellActions: EuiDataGridColumnCellAction[] = [
({ Component }) => (
<Component iconType="plusInCircle" aria-label="Filter in">
Filter in
</Component>
),
({ Component }) => (
<Component iconType="minusInCircle" aria-label="Filter out">
Filter out
</Component>
),
];

const columns: EuiDataGridColumn[] = [
{
id: 'firstName',
cellActions,
},
{
id: 'lastName',
isExpandable: false,
cellActions,
},
{
id: 'suffix',
isExpandable: false,
},
{
id: 'boolean',
isExpandable: false,
},
];

const data: Array<{ [key: string]: ReactNode }> = [];
for (let i = 1; i < 5; i++) {
data.push({
firstName: fake('{{name.firstName}}'),
lastName: fake('{{name.lastName}}'),
suffix: fake('{{name.suffix}}'),
boolean: fake('{{random.boolean}}'),
});
}

export default () => {
const [visibleColumns, setVisibleColumns] = useState(
columns.map(({ id }) => id)
);

return (
<EuiDataGrid
aria-label="Data grid example of columns.isExpandable"
columns={columns}
columnVisibility={{ visibleColumns, setVisibleColumns }}
rowCount={data.length}
renderCellValue={({ rowIndex, columnId }) => data[rowIndex][columnId]}
/>
);
};
176 changes: 176 additions & 0 deletions src-docs/src/views/datagrid/cell_popover_rendercellpopover.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
import React, { useState, ReactNode } from 'react';
// @ts-ignore - faker does not have type declarations
import { fake } from 'faker';

import {
EuiDataGrid,
EuiDataGridCellPopoverElementProps,
EuiDataGridColumnCellAction,
EuiDataGridColumn,
EuiPopoverTitle,
EuiPopoverFooter,
EuiFlexGroup,
EuiFlexItem,
EuiButtonEmpty,
EuiCopy,
EuiText,
EuiImage,
} from '../../../../src/components';

const cellActions: EuiDataGridColumnCellAction[] = [
({ Component }) => (
<Component iconType="plusInCircle" aria-label="Filter in">
Filter in
</Component>
),
({ Component }) => (
<Component iconType="minusInCircle" aria-label="Filter out">
Filter out
</Component>
),
];

const columns: EuiDataGridColumn[] = [
{
id: 'default',
cellActions,
},
{
id: 'datetime',
cellActions,
},
{
id: 'json',
cellActions,
},
{
id: 'custom',
schema: 'favoriteFranchise',
cellActions,
},
];

const data: Array<{ [key: string]: ReactNode }> = [];
for (let i = 1; i < 5; i++) {
data.push({
default: fake('{{name.lastName}}, {{name.firstName}} {{name.suffix}}'),
datetime: fake('{{date.past}}'),
json: JSON.stringify([
{
numeric: fake('{{finance.account}}'),
currency: fake('${{finance.amount}}'),
date: fake('{{date.past}}'),
},
]),
custom: i % 2 === 0 ? 'Star Wars' : 'Star Trek',
});
}

const RenderCellPopover = (props: EuiDataGridCellPopoverElementProps) => {
const {
columnId,
schema,
children,
cellActions,
cellContentsElement,
DefaultCellPopover,
} = props;

let title: ReactNode = 'Custom popover';
let content: ReactNode = <EuiText size="s">{children}</EuiText>;
let footer: ReactNode = cellActions;

// An example of custom popover content
if (schema === 'favoriteFranchise') {
title = 'Custom popover with custom content';
const franchise = cellContentsElement.innerText;
const caption = `${franchise} is the best!`;
content = (
<>
{franchise === 'Star Wars' ? (
<EuiImage
allowFullScreen
size="m"
hasShadow
caption={caption}
alt="Random Star Wars image"
url="https://source.unsplash.com/600x600/?starwars"
/>
) : (
<EuiImage
allowFullScreen
size="m"
hasShadow
caption={caption}
alt="Random Star Trek image"
url="https://source.unsplash.com/600x600/?startrek"
/>
)}
</>
);
}

// An example of a custom cell actions footer, and of using
// `cellContentsElement` to directly access a cell's raw text
if (columnId === 'datetime') {
title = 'Custom popover with custom actions';
footer = (
<EuiPopoverFooter>
<EuiFlexGroup
justifyContent="spaceBetween"
gutterSize="none"
responsive={false}
>
<EuiFlexItem className="eui-displayBlock">
{/* When not using the default cellActions, be sure to replace them
with your own action buttons to ensure a consistent user experience */}
<EuiButtonEmpty size="xs">Filter in</EuiButtonEmpty>
<EuiButtonEmpty size="xs">Filter out</EuiButtonEmpty>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiCopy textToCopy={cellContentsElement.innerText}>
{(copy) => (
<EuiButtonEmpty size="xs" onClick={copy} color="success">
Click to copy
</EuiButtonEmpty>
)}
</EuiCopy>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPopoverFooter>
);
}

// An example of conditionally falling back back to the default cell popover render.
// Note that JSON schemas have automatic EuiCodeBlock and isCopyable formatting
// which can be nice to keep intact. For cells that have non-JSON content but
// JSON popovers, you can also manually pass a `json` schema to force this formatting.
if (columnId === 'json') {
return <DefaultCellPopover {...props} schema="json" />;
}

return (
<>
<EuiPopoverTitle>{title}</EuiPopoverTitle>
{content}
{footer}
</>
);
};

export default () => {
const [visibleColumns, setVisibleColumns] = useState(
columns.map(({ id }) => id)
);

return (
<EuiDataGrid
aria-label="Data grid renderCellPopover example"
columns={columns}
columnVisibility={{ visibleColumns, setVisibleColumns }}
rowCount={data.length}
renderCellValue={({ rowIndex, columnId }) => data[rowIndex][columnId]}
renderCellPopover={RenderCellPopover}
/>
);
};
Loading

0 comments on commit 93b70f9

Please sign in to comment.