diff --git a/packages/website/docs/components/tabular_content/data_grid/_category_.yml b/packages/website/docs/components/tabular_content/data_grid/_category_.yml new file mode 100644 index 00000000000..a930daac9ab --- /dev/null +++ b/packages/website/docs/components/tabular_content/data_grid/_category_.yml @@ -0,0 +1,3 @@ +label: 'Data grid' +collapsed: true +position: 2 diff --git a/packages/website/docs/components/tabular_content/data_grid/_prop_snippet_table.tsx b/packages/website/docs/components/tabular_content/data_grid/_prop_snippet_table.tsx new file mode 100644 index 00000000000..423db3477bd --- /dev/null +++ b/packages/website/docs/components/tabular_content/data_grid/_prop_snippet_table.tsx @@ -0,0 +1,91 @@ +import { useMemo, FC, ReactNode } from 'react'; +import { + EuiBasicTable, + EuiBasicTableColumn, + EuiMarkdownFormat, + getDefaultEuiMarkdownPlugins, + EuiCodeBlock, + EuiLink, + EuiSpacer, +} from '@elastic/eui'; +import { ProcessedComponent } from '@elastic/eui-docgen'; + +type DataGridPropSnippetTableProps = { + propSnippetMap: Record; + linksMap?: Record; + docgen: ProcessedComponent; +}; + +const { processingPlugins, parsingPlugins } = getDefaultEuiMarkdownPlugins({ + exclude: ['lineBreaks'], +}); + +const columns: Array> = [ + { + name: 'Prop', + render: ({ propName, propDescription }) => ( + <> + {propName} + {propDescription && ( + <> + + + {propDescription} + + + )} + + ), + textOnly: true, + valign: 'top', + }, + { + field: 'snippet', + name: 'Sample snippet', + render: (snippet: string | ReactNode) => + typeof snippet === 'string' ? ( + + {snippet} + + ) : ( + snippet + ), + valign: 'top', + }, +]; + +export const DataGridPropSnippetTable: FC = ({ + propSnippetMap, + linksMap, + docgen, +}) => { + const items = useMemo( + () => + Object.entries(propSnippetMap).map(([prop, snippet]) => { + const propLink = linksMap?.[prop]; + const propName = propLink ? ( + + {prop} + + ) : ( + {prop} + ); + const propDescription = docgen.props[prop]?.description; + + return { propName, propDescription, snippet }; + }), + [propSnippetMap] + ); + + return ; +}; diff --git a/packages/website/docs/components/tabular_content/data_grid/data_grid.mdx b/packages/website/docs/components/tabular_content/data_grid/data_grid.mdx new file mode 100644 index 00000000000..aeb197642de --- /dev/null +++ b/packages/website/docs/components/tabular_content/data_grid/data_grid.mdx @@ -0,0 +1,457 @@ +--- +slug: /tabular-content/data-grid +id: tabular_content_data_grid +--- + +# Data grid + +**EuiDataGrid** is for displaying large amounts of tabular data. It is a better choice over [EUI tables](../tables/) when there are many columns, the data in those columns is fairly uniform, and when schemas and sorting are important for comparison. Although it is similar to traditional spreedsheet software, EuiDataGrid's current strengths are in rendering rather than creating content. + +## Core concepts + +* The grid allows you to optionally define an [in memory level](./advanced#data-grid-in-memory) to have the grid automatically handle updating your columns. Depending upon the level chosen, you may need to manage the content order separately from the grid. +* [Schemas](./schema-and-columns) allow you to tailor the render and sort methods for each column. The component ships with a few automatic schema detection and types, but you can also pass in custom ones. +* Unlike tables, the data grid **forces truncation**. To display more content your can customize [popovers](./cells-and-popovers#conditionally-customizing-cell-popover-content) to display more content and actions into popovers. +* [Grid styling](./style-display#grid-style) can be controlled by the engineer, but augmented by user preference depending upon the features you enable. +* [Control columns](./schema-and-columns#control-columns) allow you to add repeatable actions and controls like checkboxes or buttons to your grid. +* The [toolbar](./toolbar) offers the user ways to manipulate the display and order of the data. + +```tsx interactive +import React, { + useCallback, + useEffect, + useState, + createContext, + useContext, + useRef, + createRef, +} from 'react'; +import { + EuiButton, + EuiButtonEmpty, + EuiButtonIcon, + EuiCode, + EuiContextMenuItem, + EuiContextMenuPanel, + EuiDataGrid, + EuiFlyout, + EuiFlyoutBody, + EuiFlyoutFooter, + EuiFlyoutHeader, + EuiLink, + EuiModal, + EuiModalBody, + EuiModalFooter, + EuiModalHeader, + EuiModalHeaderTitle, + EuiPopover, + EuiScreenReaderOnly, + EuiText, + EuiTitle, +} from '@elastic/eui'; +import { Link } from 'react-router-dom'; +import { faker } from '@faker-js/faker'; + +const gridRef = createRef(); +const DataContext = createContext(); +const raw_data = []; + +for (let i = 1; i < 100; i++) { + const email = faker.internet.email(); + const name = `${faker.person.lastName()}, ${faker.person.firstName()}`; + const suffix = faker.person.suffix(); + raw_data.push({ + name: { + formatted: `${name} ${suffix}`, + raw: name, + }, + email: { + formatted: {faker.internet.email()}, + raw: email, + }, + location: ( + <> + {`${faker.location.city()}, `} + {faker.location.country()} + + ), + date: `${faker.date.past()}`, + account: faker.finance.accountNumber(), + amount: faker.commerce.price(), + phone: faker.phone.number(), + version: faker.system.semver(), + }); +} + +const RenderCellValue = ({ rowIndex, columnId, setCellProps }) => { + const data = useContext(DataContext); + useEffect(() => { + if (columnId === 'amount') { + if (data.hasOwnProperty(rowIndex)) { + const numeric = parseFloat( + data[rowIndex][columnId].match(/\d+\.\d+/)[0] + ); + setCellProps({ + style: { + backgroundColor: `rgba(0, 255, 0, ${numeric * 0.0002})`, + }, + }); + } + } + }, [rowIndex, columnId, setCellProps, data]); + + function getFormatted() { + return data[rowIndex][columnId].formatted + ? data[rowIndex][columnId].formatted + : data[rowIndex][columnId]; + } + + return data.hasOwnProperty(rowIndex) + ? getFormatted(rowIndex, columnId) + : null; +}; + +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', + displayAsText: 'Email address', + initialWidth: 130, + 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', + displayAsText: 'Location', + }, + { + id: 'account', + displayAsText: 'Account', + actions: { + showHide: { label: 'Custom hide label' }, + showMoveLeft: false, + showMoveRight: false, + additional: [ + { + label: 'Custom action', + onClick: () => {}, + iconType: 'cheer', + size: 'xs', + color: 'text', + }, + ], + }, + 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', + displayAsText: 'Date', + defaultSortDirection: 'desc', + }, + { + id: 'amount', + displayAsText: 'Amount', + }, + { + id: 'phone', + displayAsText: 'Phone', + isSortable: false, + }, + { + id: 'version', + displayAsText: 'Version', + defaultSortDirection: 'desc', + initialWidth: 70, + isResizable: false, + actions: false, + }, +]; + +const trailingControlColumns = [ + { + id: 'actions', + width: 40, + headerCellRender: () => ( + + Controls + + ), + rowCellRender: function RowCellRender({ rowIndex, colIndex }) { + const [isPopoverVisible, setIsPopoverVisible] = useState(false); + const closePopover = () => setIsPopoverVisible(false); + + const [isModalVisible, setIsModalVisible] = useState(false); + const closeModal = () => { + setIsModalVisible(false); + gridRef.current.setFocusedCell({ rowIndex, colIndex }); + }; + const showModal = () => { + closePopover(); + setIsModalVisible(true); + }; + + let modal; + + if (isModalVisible) { + modal = ( + + + A typical modal + + + + +

+ + EuiModal + {' '} + components have a higher z-index than{' '} + EuiDataGrid components, even in fullscreen + mode. This ensures that modals will never appear behind the + data grid. +

+
+
+ + + + Close + + +
+ ); + } + + const [isFlyoutVisible, setIsFlyoutVisible] = useState(false); + const closeFlyout = () => { + setIsFlyoutVisible(false); + gridRef.current.setFocusedCell({ rowIndex, colIndex }); + }; + const showFlyout = () => { + closePopover(); + setIsFlyoutVisible(true); + }; + + let flyout; + + if (isFlyoutVisible) { + flyout = ( + + + +

A typical flyout

+
+
+ + + +

+ + EuiFlyout + {' '} + components have a higher z-index than{' '} + EuiDataGrid components, even in fullscreen + mode. This ensures that flyouts will never appear behind the + data grid. +

+ +

+ Flyouts are also styled with a vertical offset that accounts + for the presence of fixed headers. However, when the data grid + is in fullscreen mode, these offset styles are ignored to + allow the flyout to correctly appear at the top of the + viewport. +

+
+
+ + + + Close + + +
+ ); + } + + const actions = [ + + Modal example + , + + Flyout example + , + ]; + + return ( + <> + setIsPopoverVisible(!isPopoverVisible)} + /> + } + closePopover={closePopover} + > + + + + {modal} + + {flyout} + + ); + }, + }, +]; + +export default () => { + // Pagination + const [pagination, setPagination] = useState({ pageIndex: 0 }); + const onChangeItemsPerPage = useCallback( + (pageSize) => + setPagination((pagination) => ({ + ...pagination, + pageSize, + pageIndex: 0, + })), + [setPagination] + ); + const onChangePage = useCallback( + (pageIndex) => + setPagination((pagination) => ({ ...pagination, pageIndex })), + [setPagination] + ); + + // Sorting + const [sortingColumns, setSortingColumns] = useState([]); + const onSort = useCallback( + (sortingColumns) => { + setSortingColumns(sortingColumns); + }, + [setSortingColumns] + ); + + // Column visibility + const [visibleColumns, setVisibleColumns] = useState( + columns.map(({ id }) => id) // initialize to the full set of columns + ); + + const onColumnResize = useRef((eventData) => { + console.log(eventData); + }); + + return ( + + + + ); +}; +``` + +## Top level props + +The below table contains a list of all top level **EuiDataGrid** props and sample snippets used to configure or customize them. + +For a full list of all data grid types, including lower level configurations, please see the [/datagrid/data\_grid\_types.ts](https://github.com/elastic/eui/tree/main/packages/eui/src/components/datagrid/data_grid_types.ts) definition file. + +import TopLevelProps from './data_grid_props'; + + diff --git a/packages/website/docs/components/tabular_content/data_grid_advanced.mdx b/packages/website/docs/components/tabular_content/data_grid/data_grid_advanced.mdx similarity index 98% rename from packages/website/docs/components/tabular_content/data_grid_advanced.mdx rename to packages/website/docs/components/tabular_content/data_grid/data_grid_advanced.mdx index 54237e9fc8b..8b478bcefab 100644 --- a/packages/website/docs/components/tabular_content/data_grid_advanced.mdx +++ b/packages/website/docs/components/tabular_content/data_grid/data_grid_advanced.mdx @@ -1,9 +1,10 @@ --- slug: /tabular-content/data-grid/advanced id: tabular_content_data_grid_advanced +sidebar_position: 6 --- -# Data grid advanced +# Advanced use cases ## Ref methods @@ -324,7 +325,7 @@ The grid has levels of **in-memory** settings that can be set. It is in the cons When enabled, **in-memory** renders cell data off-screen and uses those values to detect schemas and perform sorting. This detaches the user experience from the raw data; the data grid never has access to the backing data, only what is returned by `renderCellValue`. -## When in-memory is not used +### When in-memory is not used When `inMemory` is not in use the grid will not autodetect schemas. In the below example only the `amount` column has a schema because it is manually set. Sorting and pagination data management is the responsibility of the consuming application. Column sorting in particular is going to be imprecise because there is no backend service to call, and data grid instead defaults to naively applying JavaScript's default array sort which doesn't work well with numeric data and doesn't sort React elements such as the links. This is a good example of what happens when you **don't** utilize schemas for complex data. @@ -469,7 +470,7 @@ export default () => { ``` -## Enhancements only in-memory +### Enhancements only in-memory With `inMemory={{ level: 'enhancements' }}` the grid will now autodetect schemas based on the content it has available on the currently viewed page. Notice that the field list under Sort fields has detected the type of data each column contains. @@ -617,7 +618,7 @@ export default () => { ``` -## Pagination only in-memory +### Pagination only in-memory With `inMemory={{ level: 'pagination' }}` the grid will now take care of managing the data cleanup for pagination. Like before it will autodetect schemas when possible. @@ -747,7 +748,7 @@ export default () => { ``` -## Sorting and pagination in-memory +### Sorting and pagination in-memory With `inMemory={{ level: 'sorting' }}` the grid will now take care of managing the data cleanup for sorting as well as pagination. Like before it will autodetect schemas when possible. @@ -867,7 +868,9 @@ export default () => { 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](/docs/tabular-content/data-grid#virtualization) 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](https://www.w3.org/WAI/ARIA/apg/patterns/grid), and that keyboard focus and navigation still work in an accessible manner. +:::warning +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](https://www.w3.org/WAI/ARIA/apg/patterns/grid), and that keyboard focus and navigation still work in an accessible manner. +::: ```tsx interactive import React, { useEffect, useCallback, useState, useRef } from 'react'; diff --git a/packages/website/docs/components/tabular_content/data_grid_cells_and_popovers.mdx b/packages/website/docs/components/tabular_content/data_grid/data_grid_cells_and_popovers.mdx similarity index 99% rename from packages/website/docs/components/tabular_content/data_grid_cells_and_popovers.mdx rename to packages/website/docs/components/tabular_content/data_grid/data_grid_cells_and_popovers.mdx index 35a1340ad3f..12fe56065b2 100644 --- a/packages/website/docs/components/tabular_content/data_grid_cells_and_popovers.mdx +++ b/packages/website/docs/components/tabular_content/data_grid/data_grid_cells_and_popovers.mdx @@ -1,9 +1,10 @@ --- slug: /tabular-content/data-grid/cells-and-popovers id: tabular_content_data_grid_cells_popovers +sidebar_position: 2 --- -# Data grid cells & popovers +# Cells & popovers Data grid cells are rendered using the `renderCellValue` prop. This prop accepts a function which is treated as a React component behind the scenes. This means the data grid passes cell-specific props (e.g. row index, column/schema info, etc.) to your render function, which can ouput anything from a string to any JSX element. @@ -180,7 +181,6 @@ export default () => { /> ); }; - ``` ## Visible cell actions and cell popovers @@ -291,7 +291,6 @@ export default () => { /> ); }; - ``` ## Conditionally customizing cell popover content @@ -395,7 +394,6 @@ export default () => { /> ); }; - ``` ## Completely customizing cell popover rendering @@ -598,7 +596,6 @@ export default () => { /> ); }; - ``` ## Disabling cell expansion popovers @@ -697,7 +694,6 @@ export default () => { /> ); }; - ``` ## Focus @@ -1042,5 +1038,4 @@ export default () => { ); }; - ``` diff --git a/packages/website/docs/components/tabular_content/data_grid.mdx b/packages/website/docs/components/tabular_content/data_grid/data_grid_container_constraints.mdx similarity index 52% rename from packages/website/docs/components/tabular_content/data_grid.mdx rename to packages/website/docs/components/tabular_content/data_grid/data_grid_container_constraints.mdx index 1a80ddb2b24..888e942ff75 100644 --- a/packages/website/docs/components/tabular_content/data_grid.mdx +++ b/packages/website/docs/components/tabular_content/data_grid/data_grid_container_constraints.mdx @@ -1,456 +1,10 @@ --- -slug: /tabular-content/data-grid -id: tabular_content_data_grid +slug: /tabular-content/data-grid/container-constraints +id: tabular_content_data_grid_container_constraints +sidebar_position: 5 --- -# Data grid - -**EuiDataGrid** is for displaying large amounts of tabular data. It is a better choice over [EUI tables](/docs/tabular-content/tables/) when there are many columns, the data in those columns is fairly uniform, and when schemas and sorting are important for comparison. Although it is similar to traditional spreedsheet software, EuiDataGrid's current strengths are in rendering rather than creating content. - -## Core concepts - -* The grid allows you to optionally define an [in memory level](/docs/tabular-content/data-grid-advanced#data-grid-in-memory) to have the grid automatically handle updating your columns. Depending upon the level chosen, you may need to manage the content order separately from the grid. -* [Schemas](/docs/tabular-content/data-grid-schema-columns) allow you to tailor the render and sort methods for each column. The component ships with a few automatic schema detection and types, but you can also pass in custom ones. -* Unlike tables, the data grid **forces truncation**. To display more content your can customize [popovers](/docs/tabular-content/data-grid/cells-and-popovers#conditionally-customizing-cell-popover-content) to display more content and actions into popovers. -* [Grid styling](/docs/tabular-content/data-grid-style-display#grid-style) can be controlled by the engineer, but augmented by user preference depending upon the features you enable. -* [Control columns](/docs/tabular-content/data-grid-schema-columns#control-columns) allow you to add repeatable actions and controls like checkboxes or buttons to your grid. -* The [toolbar](/docs/tabular-content/data-grid-toolbar) offers the user ways to manipulate the display and order of the data. - -```tsx interactive -import React, { - Fragment, - useCallback, - useEffect, - useState, - createContext, - useContext, - useRef, - createRef, -} from 'react'; -import { - EuiButton, - EuiButtonEmpty, - EuiButtonIcon, - EuiCode, - EuiContextMenuItem, - EuiContextMenuPanel, - EuiDataGrid, - EuiFlyout, - EuiFlyoutBody, - EuiFlyoutFooter, - EuiFlyoutHeader, - EuiLink, - EuiModal, - EuiModalBody, - EuiModalFooter, - EuiModalHeader, - EuiModalHeaderTitle, - EuiPopover, - EuiScreenReaderOnly, - EuiText, - EuiTitle, -} from '@elastic/eui'; -import { Link } from 'react-router-dom'; -import { faker } from '@faker-js/faker'; - -const gridRef = createRef(); -const DataContext = createContext(); -const raw_data = []; - -for (let i = 1; i < 100; i++) { - const email = faker.internet.email(); - const name = `${faker.person.lastName()}, ${faker.person.firstName()}`; - const suffix = faker.person.suffix(); - raw_data.push({ - name: { - formatted: `${name} ${suffix}`, - raw: name, - }, - email: { - formatted: {faker.internet.email()}, - raw: email, - }, - location: ( - - {`${faker.location.city()}, `} - {faker.location.country()} - - ), - date: `${faker.date.past()}`, - account: faker.finance.accountNumber(), - amount: faker.commerce.price(), - phone: faker.phone.number(), - version: faker.system.semver(), - }); -} - -const RenderCellValue = ({ rowIndex, columnId, setCellProps }) => { - const data = useContext(DataContext); - useEffect(() => { - if (columnId === 'amount') { - if (data.hasOwnProperty(rowIndex)) { - const numeric = parseFloat( - data[rowIndex][columnId].match(/\d+\.\d+/)[0] - ); - setCellProps({ - style: { - backgroundColor: `rgba(0, 255, 0, ${numeric * 0.0002})`, - }, - }); - } - } - }, [rowIndex, columnId, setCellProps, data]); - - function getFormatted() { - return data[rowIndex][columnId].formatted - ? data[rowIndex][columnId].formatted - : data[rowIndex][columnId]; - } - - return data.hasOwnProperty(rowIndex) - ? getFormatted(rowIndex, columnId) - : null; -}; - -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', - displayAsText: 'Email address', - initialWidth: 130, - 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', - displayAsText: 'Location', - }, - { - id: 'account', - displayAsText: 'Account', - actions: { - showHide: { label: 'Custom hide label' }, - showMoveLeft: false, - showMoveRight: false, - additional: [ - { - label: 'Custom action', - onClick: () => {}, - iconType: 'cheer', - size: 'xs', - color: 'text', - }, - ], - }, - 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', - displayAsText: 'Date', - defaultSortDirection: 'desc', - }, - { - id: 'amount', - displayAsText: 'Amount', - }, - { - id: 'phone', - displayAsText: 'Phone', - isSortable: false, - }, - { - id: 'version', - displayAsText: 'Version', - defaultSortDirection: 'desc', - initialWidth: 70, - isResizable: false, - actions: false, - }, -]; - -const trailingControlColumns = [ - { - id: 'actions', - width: 40, - headerCellRender: () => ( - - Controls - - ), - rowCellRender: function RowCellRender({ rowIndex, colIndex }) { - const [isPopoverVisible, setIsPopoverVisible] = useState(false); - const closePopover = () => setIsPopoverVisible(false); - - const [isModalVisible, setIsModalVisible] = useState(false); - const closeModal = () => { - setIsModalVisible(false); - gridRef.current.setFocusedCell({ rowIndex, colIndex }); - }; - const showModal = () => { - closePopover(); - setIsModalVisible(true); - }; - - let modal; - - if (isModalVisible) { - modal = ( - - - A typical modal - - - - -

- - EuiModal - {' '} - components have a higher z-index than{' '} - EuiDataGrid components, even in fullscreen - mode. This ensures that modals will never appear behind the - data grid. -

-
-
- - - - Close - - -
- ); - } - - const [isFlyoutVisible, setIsFlyoutVisible] = useState(false); - const closeFlyout = () => { - setIsFlyoutVisible(false); - gridRef.current.setFocusedCell({ rowIndex, colIndex }); - }; - const showFlyout = () => { - closePopover(); - setIsFlyoutVisible(true); - }; - - let flyout; - - if (isFlyoutVisible) { - flyout = ( - - - -

A typical flyout

-
-
- - - -

- - EuiFlyout - {' '} - components have a higher z-index than{' '} - EuiDataGrid components, even in fullscreen - mode. This ensures that flyouts will never appear behind the - data grid. -

- -

- Flyouts are also styled with a vertical offset that accounts - for the presence of fixed headers. However, when the data grid - is in fullscreen mode, these offset styles are ignored to - allow the flyout to correctly appear at the top of the - viewport. -

-
-
- - - - Close - - -
- ); - } - - const actions = [ - - Modal example - , - - Flyout example - , - ]; - - return ( - <> - setIsPopoverVisible(!isPopoverVisible)} - /> - } - closePopover={closePopover} - > - - - - {modal} - - {flyout} - - ); - }, - }, -]; - -export default () => { - // Pagination - const [pagination, setPagination] = useState({ pageIndex: 0 }); - const onChangeItemsPerPage = useCallback( - (pageSize) => - setPagination((pagination) => ({ - ...pagination, - pageSize, - pageIndex: 0, - })), - [setPagination] - ); - const onChangePage = useCallback( - (pageIndex) => - setPagination((pagination) => ({ ...pagination, pageIndex })), - [setPagination] - ); - - // Sorting - const [sortingColumns, setSortingColumns] = useState([]); - const onSort = useCallback( - (sortingColumns) => { - setSortingColumns(sortingColumns); - }, - [setSortingColumns] - ); - - // Column visibility - const [visibleColumns, setVisibleColumns] = useState( - columns.map(({ id }) => id) // initialize to the full set of columns - ); - - const onColumnResize = useRef((eventData) => { - console.log(eventData); - }); - - return ( - - - - ); -}; - -``` - -## Top level props - -Please check the [props section](#props) below for more explanation on the lower level object types. The majority of the types are defined in the [/datagrid/data\_grid\_types.ts](https://github.com/elastic/eui/tree/main/packages/eui/src/components/datagrid/data_grid_types.ts) file. +# Container constraints ## Data grid adapts to its container @@ -539,7 +93,6 @@ export default () => { ); }; - ``` When placed within an [**EuiFlexGroup** and **EuiFlexItem**](/docs/layout/flex), the data grid will have trouble shrinking to fit. To fix this, you will need to manually add a style of `min-width: 0` to the **EuiFlexItem**. @@ -644,7 +197,6 @@ export default () => { ); }; - ``` ## Virtualization @@ -659,7 +211,6 @@ Similar to React's rule of not switching between a controlled and uncontrolled i ```tsx interactive import React, { - Fragment, useCallback, useState, createContext, @@ -732,12 +283,12 @@ function RenderCellValue({ rowIndex, columnId }) { name: `${name} ${suffix}`, email: {email}, location: ( - + <> {`${faker.location.city()}, `} {faker.location.country()} - + ), date: `${faker.date.past()}`, account: faker.finance.accountNumber(), @@ -861,14 +412,12 @@ export default () => { ); }; - ``` ### Constrained by DOM ```tsx interactive import React, { - Fragment, useCallback, useState, createContext, @@ -939,12 +488,12 @@ function RenderCellValue({ rowIndex, columnId }) { name: `${name} ${suffix}`, email: {email}, location: ( - + <> {`${faker.location.city()}, `} {faker.location.country()} - + ), date: `${faker.date.past()}`, account: faker.finance.accountNumber(), @@ -1039,11 +588,14 @@ export default () => { ); }; - ``` -## Props + diff --git a/packages/website/docs/components/tabular_content/data_grid/data_grid_props.tsx b/packages/website/docs/components/tabular_content/data_grid/data_grid_props.tsx new file mode 100644 index 00000000000..6bb255855af --- /dev/null +++ b/packages/website/docs/components/tabular_content/data_grid/data_grid_props.tsx @@ -0,0 +1,176 @@ +import { EuiDataGrid } from '@elastic/eui-docgen/dist/components/datagrid/data_grid.json'; + +import { DataGridPropSnippetTable } from './_prop_snippet_table'; + +export const topLevelPropSnippets = { + rowCount: 'rowCount={200}', + columns: `columns={[ + { + id: 'A', // required + display: <>Column A , // optional column header rendering + displayAsText: 'Column A', // column header as plain text + displayHeaderCellProps: { className: 'eui-textCenter' }, // optional column header cell props + initialWidth: 150, // starting width of 150px + isResizable: false, // prevents the user from resizing width + isExpandable: false, // doesn't allow clicking in to see the content in a popup + isSortable: false, // prevents the user from sorting the data grid by this column + defaultSortDirection: 'asc', // sets the default sort direction + schema: 'franchise', // custom schema later defined under schemaDetectors + actions: false, // no column header actions are displayed + actions: { showMoveLeft: false, showMoveRight: false }, // doesn't show move actions in column header + cellActions: [ // provides one additional cell action that triggers an alert once clicked + ({ Component }) => alert('test')}>Custom action, + ], + visibleCellActions: 2, // configures the number of cell action buttons immediately visible on a cell + }, +]}`, + leadingControlColumns: `leadingControlColumns={[ + { + id: 'selection', + width: 31, + headerCellRender: () => Select a row, + headerCellProps: { className: 'eui-textCenter' }, + rowCellRender: () =>
, + footerCellRender: () => Select a row, + footerCellProps: { className: 'eui-textCenter' }, + }, +]}`, + trailingControlColumns: `trailingControlColumns={[ + { + id: 'actions', + width: 40, + headerCellRender: () => 'Actions', + headerCellProps: { className: 'euiScreenReaderOnly' }, + rowCellRender: MyGridActionsComponent, + footerCellRender: () => null, + footerCellProps: { data-test-subj: 'emptyFooterCell' }, + }, +]}`, + columnVisibility: `columnVisibility={{ + visibleColumns: ['A'], + setVisibleColumns: () => {}, + canDragAndDropColumns: true, +}}`, + onColumnResize: 'onColumnResize={({columnId, width}) => {}}', + schemaDetectors: ( + + See Schemas & columns for full details. + + ), + renderCellValue: 'renderCellValue={({ rowIndex, columnId }) => {}}', + cellContext: `cellContext={{ + // Will be passed to your \`renderCellValue\` function/component as a prop + yourData, +}} +renderCellValue={({ rowIndex, columnId, yourData }) => {}}`, + renderCellPopover: `renderCellPopover={({ children, cellActions }) => ( + <> + I'm a custom popover! + {children} + {cellActions} + +)}`, + renderFooterCellValue: + 'renderFooterCellValue={({ rowIndex, columnId }) => {}}', + renderCustomToolbar: + 'renderCustomToolbar={({ displayControl }) =>
Custom toolbar content {displayControl}
}', + renderCustomGridBody: `// Optional; advanced usage only. This render function is an escape hatch for consumers who need to opt out of virtualization or otherwise need total custom control over how data grid cells are rendered. + +renderCustomGridBody={({ visibleColumns, visibleRowData, Cell }) => ( + +)}`, + pagination: `pagination={{ + pageIndex: 1, + pageSize: 100, // If not specified, defaults to EuiTablePagination.itemsPerPage + pageSizeOptions: [50, 100, 200], // If not specified, defaults to EuiTablePagination.itemsPerPageOptions + onChangePage: () => {}, + onChangeItemsPerPage: () => {}, +}}`, + sorting: `sorting={{ + columns: [{ id: 'A', direction: 'asc' }], + onSort: () => {}, +}}`, + inMemory: `// Will try to autodectect schemas and do sorting and pagination in memory. +inMemory={{ level: 'sorting' }}`, + toolbarVisibility: `toolbarVisibility={{ + showColumnSelector: false, + showDisplaySelector: false, + showSortSelector: false, + showKeyboardShortcuts: false, + showFullScreenSelector: false, + additionalControls: { + left: , + right: , + }, +}}`, + gridStyle: `gridStyle={{ + border: 'none', + stripes: true, + rowHover: 'highlight', + header: 'underline', + // If showDisplaySelector.allowDensity={true} from toolbarVisibility, fontSize and cellPadding will be superceded by what the user decides. + cellPadding: 'm', + fontSize: 'm', + footer: 'overline' +}}`, + rowHeightsOptions: `rowHeightsOptions={{ + defaultHeight: { + lineCount: 3 // default every row to 3 lines of text + }, + lineHeight: '2em', // default every cell line-height to 2em + rowHeights: { + 1: { + lineCount: 5, // row at index 1 will show 5 lines + }, + 4: 200, // row at index 4 will adjust the height to 200px + 6: 'auto', // row at index 6 will automatically adjust the height + }, + scrollAnchorRow: 'start', // compensate for layout shift when auto-sized rows are scrolled into view +}}`, + ref: `// Optional. For advanced control of internal data grid state, passes back an object of imperative API methods +ref={dataGridRef}`, + virtualizationOptions: `// Optional. For advanced control of the underlying react-window virtualization grid. +virtualizationOptions={{ + className: 'virtualizedGridClass', + style: {}, + direction: 'ltr', + estimatedRowHeight: 50, + overscanColumnCount: 1, + overscanRowCount: 1, + initialScrollLeft: 0, + initialScrollTop: 0, + onScroll: () => {}, + onItemsRendered: () => {}, + itemKey: () => {}, + outerElementType: 'div', +}} +// Properties not listed above are used by EuiDataGrid internals and cannot be overridden. +`, +}; + +const propLinks = { + schemaDetectors: '../data-grid/schema-and-columns#schemas', + onColumnResize: '../data-grid/schema-and-columns/#column-widths', + leadingControlColumns: '../data-grid/schema-and-columns#control-columns', + trailingControlColumns: '../data-grid/schema-and-columns#control-columns', + renderFooterCellValue: '../data-grid/schema-and-columns#footer-row', + renderCellPopover: + '../data-grid/cells-and-popovers#completely-customizing-cell-popover-rendering', + cellContext: '../data-grid/cells-and-popovers#cell-context', + rowHeightsOptions: '../data-grid/style-and-display#row-heights-options', + gridStyle: '../data-grid/style-and-display#grid-style', + inMemory: '../data-grid/advanced#data-grid-in-memory', + ref: '../data-grid/advanced#ref-methods', + renderCustomGridBody: '../data-grid/advanced#custom-body-renderer', + renderCustomToolbar: + '../data-grid/toolbar#completely-custom-toolbar-rendering', + toolbarVisibility: '../data-grid/toolbar#toolbar-visibility', +}; + +export default () => ( + +); diff --git a/packages/website/docs/components/tabular_content/data_grid_schema_and_columns.mdx b/packages/website/docs/components/tabular_content/data_grid/data_grid_schema_and_columns.mdx similarity index 90% rename from packages/website/docs/components/tabular_content/data_grid_schema_and_columns.mdx rename to packages/website/docs/components/tabular_content/data_grid/data_grid_schema_and_columns.mdx index e3ad27a9aaf..13da04bb85e 100644 --- a/packages/website/docs/components/tabular_content/data_grid_schema_and_columns.mdx +++ b/packages/website/docs/components/tabular_content/data_grid/data_grid_schema_and_columns.mdx @@ -1,9 +1,10 @@ --- slug: /tabular-content/data-grid/schema-and-columns id: tabular_content_data_grid_schema_columns +sidebar_position: 1 --- -# Data grid schema & columns +# Schema & columns ## Schemas @@ -11,7 +12,7 @@ Schemas are data types you pass to grid columns to affect how the columns and ex ### Defining custom schemas -Custom schemas are passed as an array to `schemaDetectors` and are constructed against the **EuiDataGridSchemaDetector** interface. You can see an example of a simple custom schema used on the last column below. In addition to schemas being useful to map against for cell and expansion rendering, any schema will also add a`className="euiDataGridRowCell--schemaName"` to each matching cell. +Custom schemas are passed as an array to `schemaDetectors` and are constructed against the **EuiDataGridSchemaDetector** interface. You can see an example of a simple custom schema used on the last column below. In addition to schemas being useful to map against for cell and expansion rendering, any schema will also add a `className="euiDataGridRowCell--schemaName"` to each matching cell. ```tsx interactive import React, { useState } from 'react'; @@ -146,7 +147,6 @@ export default () => { /> ); }; - ``` ## Column widths @@ -242,7 +242,6 @@ export default () => { /> ); }; - ``` ## Column actions @@ -361,7 +360,100 @@ export default () => { /> ); }; +``` + +## Draggable columns + +To reorder columns directly instead of via the actions menu popover, you can enable draggable **EuiDataGrid** header columns via the `columnVisibility.canDragAndDropColumns` prop. This will allow you to reorder the column by dragging them. + +```tsx interactive +import React, { useState, useCallback } from 'react'; +import { EuiDataGrid, EuiAvatar } from '@elastic/eui'; +import { faker } from '@faker-js/faker'; + +const columns = [ + { + id: 'avatar', + initialWidth: 40, + isResizable: false, + }, + { + id: 'name', + initialWidth: 100, + }, + { + id: 'email', + }, + { + id: 'city', + }, + { + id: 'country', + }, + { + id: 'account', + }, +]; + +const data = []; + +for (let i = 1; i < 5; i++) { + data.push({ + avatar: ( + + ), + name: `${faker.person.lastName()}, ${faker.person.firstName()} ${faker.person.suffix()}`, + email: faker.internet.email(), + city: faker.location.city(), + country: faker.location.country(), + account: faker.finance.accountNumber(), + }); +} + +export default () => { + const [pagination, setPagination] = useState({ pageIndex: 0 }); + + const [visibleColumns, setVisibleColumns] = useState( + columns.map(({ id }) => id) + ); + + const setPageIndex = useCallback( + (pageIndex) => + setPagination((pagination) => ({ ...pagination, pageIndex })), + [] + ); + const setPageSize = useCallback( + (pageSize) => + setPagination((pagination) => ({ + ...pagination, + pageSize, + pageIndex: 0, + })), + [] + ); + return ( + data[rowIndex][columnId]} + pagination={{ + ...pagination, + onChangeItemsPerPage: setPageSize, + onChangePage: setPageIndex, + }} + /> + ); +}; ``` ## Control columns @@ -379,7 +471,6 @@ import React, { useCallback, useReducer, useState, - Fragment, } from 'react'; import { EuiDataGrid, @@ -564,10 +655,10 @@ const FlyoutRowCell = (rowIndex) => { const details = Object.entries(rowData).map(([key, value]) => { return ( - + <> {key} {value} - + ); }); @@ -588,7 +679,7 @@ const FlyoutRowCell = (rowIndex) => { } return ( - + <> { onClick={() => setIsFlyoutOpen(!isFlyoutOpen)} /> {flyout} - + ); }; @@ -751,7 +842,6 @@ export default function DataGrid() { ); } - ``` ## Footer row @@ -958,5 +1048,15 @@ export default () => { ); }; - ``` + + diff --git a/packages/website/docs/components/tabular_content/data_grid_style_and_display.mdx b/packages/website/docs/components/tabular_content/data_grid/data_grid_style_and_display.mdx similarity index 99% rename from packages/website/docs/components/tabular_content/data_grid_style_and_display.mdx rename to packages/website/docs/components/tabular_content/data_grid/data_grid_style_and_display.mdx index a8c95f72651..1a902af537b 100644 --- a/packages/website/docs/components/tabular_content/data_grid_style_and_display.mdx +++ b/packages/website/docs/components/tabular_content/data_grid/data_grid_style_and_display.mdx @@ -1,9 +1,10 @@ --- slug: /tabular-content/data-grid/style-and-display id: tabular_content_data_grid_style_display +sidebar_position: 4 --- -# Data grid style & display +# Style & display ## Grid style @@ -129,7 +130,6 @@ const DataGridStyle = ({ }; export default DataGridStyle; - ``` ## Grid row classes @@ -304,7 +304,6 @@ export default () => { ); }; - ``` ## Row heights options @@ -568,7 +567,6 @@ export default () => { ); }; - ``` ## Overriding specific row heights @@ -814,7 +812,6 @@ export default () => { ); }; - ``` ## Auto heights for rows @@ -1049,7 +1046,6 @@ export default () => { ); }; - ``` ## Adjusting your grid to user/toolbar changes[](/docs/tabular-content/data-grid-style-display#adjusting-your-grid-to-usertoolbar-changes) @@ -1153,5 +1149,4 @@ export default () => { /> ); }; - ``` diff --git a/packages/website/docs/components/tabular_content/data_grid_toolbar.mdx b/packages/website/docs/components/tabular_content/data_grid/data_grid_toolbar.mdx similarity index 89% rename from packages/website/docs/components/tabular_content/data_grid_toolbar.mdx rename to packages/website/docs/components/tabular_content/data_grid/data_grid_toolbar.mdx index 4ce83dabb05..f0289983697 100644 --- a/packages/website/docs/components/tabular_content/data_grid_toolbar.mdx +++ b/packages/website/docs/components/tabular_content/data_grid/data_grid_toolbar.mdx @@ -1,9 +1,10 @@ --- slug: /tabular-content/data-grid/toolbar id: tabular_content_data_grid_toolbar +sidebar_position: 3 --- -# Data grid toolbar +# Toolbar ## Toolbar visibility @@ -185,7 +186,6 @@ const DataGridStyle = ({ }; export default DataGridStyle; - ``` ## Additional controls in the toolbar @@ -202,7 +202,7 @@ Passing a single node to `additionalControls` will default to being placed in th Although any node is allowed, the recommendation is to use `` for the left-side of the toolbar and `` for the right-side of the toolbar. ```tsx interactive -import React, { useState, useCallback, Fragment } from 'react'; +import React, { useState, useCallback } from 'react'; import { EuiDataGrid, EuiDataGridToolbarControl, @@ -389,7 +389,7 @@ export default () => { ), }, right: ( - + <> { onClick={() => setIsFlyoutVisible(true)} /> - + ), }, }} @@ -418,7 +418,6 @@ export default () => { ); }; - ``` ## Completely custom toolbar rendering @@ -577,105 +576,10 @@ export default () => { /> ); }; - ``` ## Toolbar props -### `EuiDataGridToolBarVisibilityOptions` - -This table contains 6 rows. -| -Prop - - | - -Sample snippet - - | -| --- | --- | -| - -**showSortSelector** - -Allows the ability for the user to sort rows based upon column values - - | - -``` -showSortSelector: false -``` - - | -| - -**additionalControls** - -If passed a `ReactNode`, appends the passed custom control into the left side of the toolbar, after the column & sort controls. Or use **EuiDataGridToolBarAdditionalControlsOptions** to customize the location of your control. - - | - -``` -additionalControls: { - left: , - right: , -} -``` - - | -| - -**showColumnSelector** - -Allows the ability for the user to hide fields and sort columns, boolean or a **EuiDataGridToolBarVisibilityColumnSelectorOptions** - - | - -``` -showColumnSelector: { - allowHide: false; - allowReorder: false; -} -``` - - | -| - -**showDisplaySelector** - -Allows the ability for the user to customize display settings such as grid density and row heights. User changes will override what is provided in **EuiDataGridStyle** and **EuiDataGridRowHeightsOptions** - - | - -``` -showDisplaySelector: { - allowDensity: false; - allowRowHeight: false; - allowResetButton: false; - additionalDisplaySettings: ; -} -``` - - | -| - -**showFullScreenSelector** - -Allows user to be able to fullscreen the data grid. If set to `false` make sure your grid fits within a large enough panel to still show the other controls. - - | - -``` -showFullScreenSelector: false -``` - - | -| - -**showKeyboardShortcuts** - -Displays a popover listing all keyboard controls and shortcuts for the data grid. If set to `false`, the toggle will be visually hidden, but still focusable by keyboard and screen reader users. - - | +import ToolbarProps from './data_grid_toolbar_props'; - | + diff --git a/packages/website/docs/components/tabular_content/data_grid/data_grid_toolbar_props.tsx b/packages/website/docs/components/tabular_content/data_grid/data_grid_toolbar_props.tsx new file mode 100644 index 00000000000..11ff4b39b11 --- /dev/null +++ b/packages/website/docs/components/tabular_content/data_grid/data_grid_toolbar_props.tsx @@ -0,0 +1,39 @@ +import { EuiCode } from '@elastic/eui'; +import { EuiDataGridToolbarPropsComponent } from '@elastic/eui-docgen/dist/components/datagrid/data_grid.stories.utils.json'; + +import { DataGridPropSnippetTable } from './_prop_snippet_table'; + +const toolbarVisibilitySnippets = { + showColumnSelector: `showColumnSelector: { + allowHide: false, + allowReorder: false, + }`, + showSortSelector: 'showSortSelector: false', + showKeyboardShortcuts: 'showKeyboardShortcuts: false', + showDisplaySelector: `showDisplaySelector: { + allowDensity: false, + allowRowHeight: false, + allowResetButton: false, + additionalDisplaySettings: , + customRender: ({ densityControl, rowHeightControl, resetButton, additionalDisplaySettings }) => ( + <>Completely custom display settings + ), + }`, + showFullScreenSelector: 'showFullScreenSelector: false', + additionalControls: `additionalControls: { + left: , + right: , + }`, +}; + +export default () => ( + <> +

+ EuiDataGridToolBarVisibilityOptions +

+ + +);