diff --git a/CHANGELOG.md b/CHANGELOG.md
index 57e68d3acda..78c8e666a2a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,11 +2,16 @@
- Updated the organization of `EuiDataGrid`'s toolbar/grid controls ([#5334](https://github.com/elastic/eui/pull/5334))
- Added `left.append` and `left.prepend` to `EuiDataGrid`'s `toolbarVisibility.additionalControls` prop [#5394](https://github.com/elastic/eui/pull/5394))
+- Added a row height control to `EuiDataGrid`'s toolbar ([#5372](https://github.com/elastic/eui/pull/5372))
**Bug fixes**
- Fixed persistent `EuiDataGrid` full screen `
` class ([#5354](https://github.com/elastic/eui/pull/5354))
+**Breaking changes**
+
+- Removed `toolbarVisibility`'s `showStyleSelector` prop of `EuiDataGrid` in favor of `showDisplaySelector`, which allows configuration of both grid density and row height ([#5372](https://github.com/elastic/eui/pull/5372))
+
## END FEATURE BRANCH
**Bug fixes**
diff --git a/cypress/support/index.js b/cypress/support/index.js
index 9bc34e3acd7..6ba42a3fcf9 100644
--- a/cypress/support/index.js
+++ b/cypress/support/index.js
@@ -15,3 +15,10 @@
import '@cypress/code-coverage/support';
require(THEME_IMPORT); // defined by DefinePlugin in the cypress webpack config
+
+// @see https://github.com/quasarframework/quasar/issues/2233#issuecomment-492975745
+Cypress.on('uncaught:exception', (err) => {
+ if (err.message.includes('> ResizeObserver loop limit exceeded')) {
+ return false;
+ }
+});
diff --git a/src-docs/src/views/datagrid/datagrid_example.js b/src-docs/src/views/datagrid/datagrid_example.js
index 1d8d6938431..5aba7eaecf3 100644
--- a/src-docs/src/views/datagrid/datagrid_example.js
+++ b/src-docs/src/views/datagrid/datagrid_example.js
@@ -97,7 +97,7 @@ const gridSnippet = `
// The prop also accepts a boolean if you want to toggle the entire toolbar on/off.
toolbarVisibility={{
showColumnSelector: false,
- showStyleSelector: false,
+ showDisplaySelector: false,
showSortSelector: false,
showFullScreenSelector: false,
additionalControls: {
@@ -248,6 +248,13 @@ const gridConcepts = [
Data grid row heights options
{' '}
for more details and examples.
+
- With the default settings, the showStyleSelector{' '}
- setting in toolbarVisibility means the user has
- the ability to override the padding and font size passed into{' '}
+ With the default settings, the{' '}
+ showDisplaySelector.allowDensity setting in{' '}
+ toolbarVisibility means the user has the ability
+ to override the padding and font size passed into{' '}
gridStyle by the engineer. The font size
overriding only works with text or elements that can inherit the
parent font size or elements that use units relative to the parent
diff --git a/src-docs/src/views/datagrid/row_height_fixed.tsx b/src-docs/src/views/datagrid/row_height_fixed.tsx
index 91df500a1ca..ff6230e1e6a 100644
--- a/src-docs/src/views/datagrid/row_height_fixed.tsx
+++ b/src-docs/src/views/datagrid/row_height_fixed.tsx
@@ -215,6 +215,9 @@ export default () => {
inMemory={{ level: 'sorting' }}
sorting={{ columns: sortingColumns, onSort }}
rowHeightsOptions={rowHeightsOptions}
+ toolbarVisibility={{
+ showDisplaySelector: { allowRowHeight: false },
+ }}
virtualizationOptions={{
// rough average of the cell heights in the example
// accurately setting this smooths out the scrolling experience
diff --git a/src-docs/src/views/datagrid/styling.js b/src-docs/src/views/datagrid/styling.js
index 0750486c839..addddfae77d 100644
--- a/src-docs/src/views/datagrid/styling.js
+++ b/src-docs/src/views/datagrid/styling.js
@@ -1,4 +1,4 @@
-import React, { useState, Fragment, useCallback } from 'react';
+import React, { useState, Fragment, useCallback, useMemo } from 'react';
import { fake } from 'faker';
import {
@@ -155,7 +155,7 @@ const DataGrid = () => {
},
];
- const showSortSelectorOptions = [
+ const showColumnSelectorOptions = [
{
id: 'true',
label: 'True',
@@ -165,8 +165,17 @@ const DataGrid = () => {
label: 'False',
},
];
-
- const showStyleSelectorOptions = [
+ const allowHideColumnsOptions = [
+ {
+ id: 'true',
+ label: 'True',
+ },
+ {
+ id: 'false',
+ label: 'False',
+ },
+ ];
+ const allowOrderingColumnsOptions = [
{
id: 'true',
label: 'True',
@@ -177,7 +186,7 @@ const DataGrid = () => {
},
];
- const showColumnSelectorOptions = [
+ const showSortSelectorOptions = [
{
id: 'true',
label: 'True',
@@ -188,7 +197,7 @@ const DataGrid = () => {
},
];
- const allowHideColumnsOptions = [
+ const showDisplaySelectorOptions = [
{
id: 'true',
label: 'True',
@@ -199,7 +208,17 @@ const DataGrid = () => {
},
];
- const allowOrderingColumnsOptions = [
+ const allowDensityOptions = [
+ {
+ id: 'true',
+ label: 'True',
+ },
+ {
+ id: 'false',
+ label: 'False',
+ },
+ ];
+ const allowRowHeightOptions = [
{
id: 'true',
label: 'True',
@@ -252,7 +271,9 @@ const DataGrid = () => {
const [headerSelected, setHeaderSelected] = useState('underline');
const [footerSelected, setFooterSelected] = useState('overline');
const [showSortSelector, setShowSortSelector] = useState(true);
- const [showStyleSelector, setShowStyleSelector] = useState(true);
+ const [showDisplaySelector, setShowDisplaySelector] = useState(true);
+ const [allowDensity, setAllowDensity] = useState(true);
+ const [allowRowHeight, setAllowRowHeight] = useState(true);
const [showColumnSelector, setShowColumnSelector] = useState(true);
const [allowHideColumns, setAllowHideColumns] = useState(true);
const [allowOrderingColumns, setAllowOrderingColumns] = useState(true);
@@ -297,26 +318,30 @@ const DataGrid = () => {
setFooterSelected(optionId);
};
- const onShowSortSelectorChange = (optionId) => {
- setShowSortSelector(optionId === 'true');
- };
-
- const onShowStyleSelectorChange = (optionId) => {
- setShowStyleSelector(optionId === 'true');
- };
-
const onShowColumnSelectorChange = (optionId) => {
setShowColumnSelector(optionId === 'true');
};
-
const onAllowHideColumnsChange = (optionId) => {
setAllowHideColumns(optionId === 'true');
};
-
const onAllowOrderingColumnsChange = (optionId) => {
setAllowOrderingColumns(optionId === 'true');
};
+ const onShowSortSelectorChange = (optionId) => {
+ setShowSortSelector(optionId === 'true');
+ };
+
+ const onShowDisplaySelectorChange = (optionId) => {
+ setShowDisplaySelector(optionId === 'true');
+ };
+ const onAllowDensityChange = (optionId) => {
+ setAllowDensity(optionId === 'true');
+ };
+ const onAllowRowHeightChange = (optionId) => {
+ setAllowRowHeight(optionId === 'true');
+ };
+
const onShowFullScreenSelectorChange = (optionId) => {
setShowFullScreenSelector(optionId === 'true');
};
@@ -389,20 +414,35 @@ const DataGrid = () => {
toolbarVisibility options
);
- let displayColumnSelector = showColumnSelector;
- if (
- displayColumnSelector === true &&
- (allowHideColumns === false || allowOrderingColumns === false)
- ) {
- displayColumnSelector = {
- allowHide: allowHideColumns,
- allowReorder: allowOrderingColumns,
- };
- }
+
+ const toggleColumnSelector = useMemo(() => {
+ if (
+ showColumnSelector === true &&
+ (allowHideColumns === false || allowOrderingColumns === false)
+ ) {
+ return {
+ allowHide: allowHideColumns,
+ allowReorder: allowOrderingColumns,
+ };
+ } else {
+ return showColumnSelector;
+ }
+ }, [showColumnSelector, allowHideColumns, allowOrderingColumns]);
+
+ const toggleDisplaySelector = useMemo(() => {
+ if (
+ showDisplaySelector === true &&
+ (allowDensity === false || allowRowHeight === false)
+ ) {
+ return { allowDensity, allowRowHeight };
+ } else {
+ return showDisplaySelector;
+ }
+ }, [showDisplaySelector, allowDensity, allowRowHeight]);
const toolbarVisibilityOptions = {
- showColumnSelector: displayColumnSelector,
- showStyleSelector: showStyleSelector,
+ showColumnSelector: toggleColumnSelector,
+ showDisplaySelector: toggleDisplaySelector,
showSortSelector: showSortSelector,
showFullScreenSelector: showFullScreenSelector,
};
@@ -453,7 +493,7 @@ const DataGrid = () => {
{
{
+ {toggleColumnSelector && (
+ <>
+
+
+
+
+
+
+ >
+ )}
{
{
-
-
-
-
- {displayColumnSelector && (
+ {toggleDisplaySelector && (
<>
>
)}
+
+
+
+
) : (
* {
max-width: 100%;
width: 100%;
+ height: 100%;
}
&.euiDataGridRowCell--firstColumn {
@@ -139,8 +140,12 @@
.euiDataGridRowCell__expandFlex {
position: relative; // for positioning expand button
display: flex;
- align-items: center;
+ align-items: baseline;
height: 100%;
+
+ .euiDataGridRowCell--controlColumn & {
+ align-items: center;
+ }
}
.euiDataGridRowCell__expandContent {
@@ -152,21 +157,21 @@
height: 100%;
}
-.euiDataGridRowCell__alignBaseLine {
- align-items: baseline;
-}
-
+// Cell actions
+// Could probably be more precisely named than '__expandButton', since there can be multiple actions/buttons
+// TODO: Consider renaming this when working on https://github.com/elastic/eui/issues/5132
.euiDataGridRowCell__expandButton {
display: flex;
+}
+@include euiDataGridRowCellActions($definedHeight: false) {
flex-grow: 0;
-
- .euiDataGridRowCell__contentByHeight + & {
- background-color: $euiColorEmptyShade;
- position: absolute;
- right: 0;
- top: 0;
- padding: $euiDataGridCellPaddingM 0;
- }
+}
+@include euiDataGridRowCellActions($definedHeight: true) {
+ background-color: $euiColorEmptyShade;
+ position: absolute;
+ right: 0;
+ top: 0;
+ padding: $euiDataGridCellPaddingM 0;
}
.euiDataGridRowCell__expandButtonIcon {
@@ -202,6 +207,11 @@
// Needed to overtake striping
background-color: $euiColorHighlight !important;
}
+ @include euiDataGridRowCellActions($definedHeight: true) {
+ // sass-lint:disable-block no-important
+ // Needed to overtake striping
+ background-color: $euiColorHighlight !important;
+ }
}
}
@@ -209,6 +219,9 @@
@include euiDataGridStyles(stripes) {
@include euiDataGridRowCell {
&.euiDataGridRowCell--stripe {
+ @include euiDataGridRowCellActions($definedHeight: true) {
+ background-color: $euiColorLightestShade;
+ }
background: $euiColorLightestShade;
}
}
@@ -255,6 +268,16 @@
}
}
+// Compressed density grids - height tweaks
+@include euiDataGridStyles(fontSizeSmall, paddingSmall) {
+ @include euiDataGridRowCellActions($definedHeight: true) {
+ padding: ($euiDataGridCellPaddingS / 2) 0;
+ }
+ @include euiDataGridRowCellActions($definedHeight: false) {
+ transform: translateY(1px);
+ }
+}
+
@keyframes euiDataGridCellButtonSlideIn {
from {
margin-left: 0;
diff --git a/src/components/datagrid/_mixins.scss b/src/components/datagrid/_mixins.scss
index c50a74f26b3..87f53255081 100644
--- a/src/components/datagrid/_mixins.scss
+++ b/src/components/datagrid/_mixins.scss
@@ -82,3 +82,18 @@ $euiDataGridStyles: (
@content;
}
}
+
+@mixin euiDataGridRowCellActions($definedHeight: false) {
+ @if $definedHeight {
+ // Defined heights are cells with row heights of auto, lineCount, or a static height
+ // that set the __contentByHeight class
+ .euiDataGridRowCell__contentByHeight + .euiDataGridRowCell__expandButton {
+ @content;
+ }
+ } @else {
+ // Otherwise, an undefined height (single flex row) will set __expandContent
+ .euiDataGridRowCell__expandContent + .euiDataGridRowCell__expandButton {
+ @content;
+ }
+ }
+}
diff --git a/src/components/datagrid/body/__snapshots__/data_grid_cell.test.tsx.snap b/src/components/datagrid/body/__snapshots__/data_grid_cell.test.tsx.snap
index a2fce7944c6..0f574bea5be 100644
--- a/src/components/datagrid/body/__snapshots__/data_grid_cell.test.tsx.snap
+++ b/src/components/datagrid/body/__snapshots__/data_grid_cell.test.tsx.snap
@@ -67,6 +67,7 @@ exports[`EuiDataGridCell renders 1`] = `
{
});
});
- describe('recalculateLineCountHeight', () => {
+ describe('recalculateLineHeight', () => {
const setRowHeight = jest.fn();
const callMethod = (component: ReactWrapper) =>
- (component.instance() as any).recalculateLineCountHeight();
+ (component.instance() as any).recalculateLineHeight();
describe('default height', () => {
it('observes the first cell for size changes and calls this.props.setRowHeight on change', () => {
@@ -245,7 +245,32 @@ describe('EuiDataGridCell', () => {
});
});
- it('does nothing if cell height is not set to lineCount', () => {
+ it('recalculates when rowHeightsOptions.defaultHeight.lineCount changes', () => {
+ const component = mountEuiDataGridCellWithContext({
+ rowHeightsOptions: { defaultHeight: { lineCount: 7 } },
+ setRowHeight,
+ });
+
+ component.setProps({
+ rowHeightsOptions: { defaultHeight: { lineCount: 6 } },
+ });
+ expect(setRowHeight).toHaveBeenCalled();
+ });
+
+ it('calculates undefined heights as single rows with a lineCount of 1', () => {
+ const component = mountEuiDataGridCellWithContext({
+ rowHeightsOptions: { defaultHeight: undefined },
+ setRowHeight,
+ });
+
+ callMethod(component);
+ expect(
+ mockRowHeightUtils.calculateHeightForLineCount
+ ).toHaveBeenCalledWith(expect.any(HTMLElement), 1, false);
+ expect(setRowHeight).toHaveBeenCalled();
+ });
+
+ it('does nothing if cell height is not lineCount or undefined', () => {
const component = mountEuiDataGridCellWithContext({
rowHeightsOptions: { defaultHeight: 34 },
setRowHeight,
diff --git a/src/components/datagrid/body/data_grid_cell.tsx b/src/components/datagrid/body/data_grid_cell.tsx
index a3d20d06672..9b253a403d4 100644
--- a/src/components/datagrid/body/data_grid_cell.tsx
+++ b/src/components/datagrid/body/data_grid_cell.tsx
@@ -39,8 +39,9 @@ import { IS_JEST_ENVIRONMENT } from '../../../test';
const EuiDataGridCellContent: FunctionComponent<
EuiDataGridCellValueProps & {
setCellProps: EuiDataGridCellValueElementProps['setCellProps'];
- isExpanded: boolean;
setCellContentsRef: EuiDataGridCell['setCellContentsRef'];
+ isExpanded: boolean;
+ isDefinedHeight: boolean;
}
> = memo(
({
@@ -51,6 +52,7 @@ const EuiDataGridCellContent: FunctionComponent<
rowIndex,
colIndex,
rowHeightUtils,
+ isDefinedHeight,
...rest
}) => {
// React is more permissible than the TS types indicate
@@ -64,11 +66,6 @@ const EuiDataGridCellContent: FunctionComponent<
{ row: rowIndex + 1, col: colIndex + 1 }
);
- const isDefinedHeight = !!rowHeightUtils?.getRowHeightOption(
- rowIndex,
- rowHeightsOptions
- );
-
return (
<>
{
+ recalculateLineHeight = () => {
if (!this.props.setRowHeight) return; // setRowHeight is only passed by data_grid_body into one cell per row
if (!this.cellContentsRef) return;
@@ -196,7 +193,10 @@ export class EuiDataGridCell extends Component<
rowIndex,
rowHeightsOptions
);
- const lineCount = rowHeightUtils?.getLineCount(rowHeightOption);
+ const isSingleLine = rowHeightOption == null; // Undefined rowHeightsOptions default to a single line
+ const lineCount = isSingleLine
+ ? 1
+ : rowHeightUtils?.getLineCount(rowHeightOption);
if (lineCount) {
const shouldUseHeightsCache = rowHeightUtils?.isRowHeightOverride(
@@ -249,6 +249,13 @@ export class EuiDataGridCell extends Component<
componentDidUpdate(prevProps: EuiDataGridCellProps) {
this.recalculateAutoHeight();
+ if (
+ this.props.rowHeightsOptions?.defaultHeight !==
+ prevProps.rowHeightsOptions?.defaultHeight
+ ) {
+ this.recalculateLineHeight();
+ }
+
if (this.props.columnId !== prevProps.columnId) {
this.setCellProps({});
}
@@ -303,7 +310,7 @@ export class EuiDataGridCell extends Component<
if (ref && hasResizeObserver) {
this.contentObserver = new (window as any).ResizeObserver(() => {
this.recalculateAutoHeight();
- this.recalculateLineCountHeight();
+ this.recalculateLineHeight();
});
this.contentObserver.observe(ref);
} else if (this.contentObserver) {
@@ -380,6 +387,7 @@ export class EuiDataGridCell extends Component<
className,
column,
style,
+ rowHeightUtils,
rowHeightsOptions,
rowManager,
...rest
@@ -474,6 +482,11 @@ export class EuiDataGridCell extends Component<
}
};
+ const isDefinedHeight = !!rowHeightUtils?.getRowHeightOption(
+ rowIndex,
+ rowHeightsOptions
+ );
+
const cellContentProps = {
...rest,
setCellProps: this.setCellProps,
@@ -483,14 +496,13 @@ export class EuiDataGridCell extends Component<
isExpanded: this.state.popoverIsOpen,
isDetails: false,
setCellContentsRef: this.setCellContentsRef,
- rowHeightsOptions: this.props.rowHeightsOptions,
- rowHeightUtils: this.props.rowHeightUtils,
+ rowHeightsOptions,
+ rowHeightUtils,
+ isDefinedHeight,
};
- const anchorClass = classNames('euiDataGridRowCell__expandFlex', {
- euiDataGridRowCell__alignBaseLine: this.props.rowHeightsOptions,
- });
- const expandClass = this.props.rowHeightsOptions
+ const anchorClass = 'euiDataGridRowCell__expandFlex';
+ const expandClass = isDefinedHeight
? 'euiDataGridRowCell__contentByHeight'
: 'euiDataGridRowCell__expandContent';
@@ -501,7 +513,7 @@ export class EuiDataGridCell extends Component<
onDeactivation={() => {
this.setState({ isEntered: false }, this.preventTabbing);
}}
- style={this.props.rowHeightsOptions ? { height: '100%' } : {}}
+ style={isDefinedHeight ? { height: '100%' } : {}}
clickOutsideDisables={true}
>
@@ -549,7 +561,7 @@ export class EuiDataGridCell extends Component<
innerContent = (
}
closePopover={[Function]}
- data-test-subj="dataGridStyleSelectorPopover"
+ data-test-subj="dataGridDisplaySelectorPopover"
display="inlineBlock"
hasArrow={true}
isOpen={false}
@@ -42,10 +42,32 @@ exports[`useDataGridStyleSelector styleSelector renders a toolbar button/popover
}
tokens={
Array [
- "euiStyleSelector.densityLabel",
- "euiStyleSelector.labelCompact",
- "euiStyleSelector.labelNormal",
- "euiStyleSelector.labelExpanded",
+ "euiDisplaySelector.densityLabel",
+ "euiDisplaySelector.labelCompact",
+ "euiDisplaySelector.labelNormal",
+ "euiDisplaySelector.labelExpanded",
+ ]
+ }
+ >
+
+
+
diff --git a/src/components/datagrid/controls/column_selector.test.tsx b/src/components/datagrid/controls/column_selector.test.tsx
index 31f4957cdfb..7fdb2ae8cd8 100644
--- a/src/components/datagrid/controls/column_selector.test.tsx
+++ b/src/components/datagrid/controls/column_selector.test.tsx
@@ -83,36 +83,16 @@ describe('useDataGridColumnSelector', () => {
closePopover(component);
});
- describe('getShowColumnSelectorValue', () => {
- it('renders both hiding and reordering functionality when showColumnSelector is true', () => {
- const component = mount();
- openPopover(component);
-
- expect(component.find('EuiSwitch')).toHaveLength(2);
- expect(component.find('EuiIcon[type="grab"]')).toHaveLength(2);
- });
-
- it('defaults to enabling all functionality when showColumnSelector is not defined', () => {
- const component = mount(
- // @ts-ignore - normally this would be undefined and not null, but we have = fallbacks up above for testing QOL
-
- );
- openPopover(component);
-
- expect(component.find('EuiSwitch')).toHaveLength(2);
- expect(component.find('EuiIcon[type="grab"]')).toHaveLength(2);
- });
-
- it('does not render either hiding or reordering when showColumnSelector is false', () => {
- const component = mount();
- openPopover(component);
-
- expect(component.find('EuiSwitch')).toHaveLength(0);
- expect(component.find('EuiIcon[type="grab"]')).toHaveLength(0);
- expect(component.find('EuiDroppable').prop('isDropDisabled')).toEqual(
- true
- );
- });
+ it('does not render if all valid sub-options are disabled', () => {
+ const component = shallow(
+
+ );
+ expect(component.text()).toEqual('');
});
describe('column filtering', () => {
diff --git a/src/components/datagrid/controls/column_selector.tsx b/src/components/datagrid/controls/column_selector.tsx
index a701bab82e6..52d22129abd 100644
--- a/src/components/datagrid/controls/column_selector.tsx
+++ b/src/components/datagrid/controls/column_selector.tsx
@@ -11,16 +11,10 @@ import React, {
useState,
useMemo,
useCallback,
- ReactElement,
+ ReactNode,
ChangeEvent,
} from 'react';
import classNames from 'classnames';
-import {
- EuiDataGridColumn,
- EuiDataGridColumnVisibility,
- EuiDataGridToolBarVisibilityColumnSelectorOptions,
- EuiDataGridToolBarVisibilityOptions,
-} from '../data_grid_types';
import { EuiPopover, EuiPopoverFooter, EuiPopoverTitle } from '../../popover';
import { EuiI18n } from '../../i18n';
import { EuiButtonEmpty } from '../../button';
@@ -36,15 +30,12 @@ import { DropResult } from 'react-beautiful-dnd';
import { EuiIcon } from '../../icon';
import { useDependentState } from '../../../services';
-const getShowColumnSelectorValue = (
- showColumnSelector: EuiDataGridToolBarVisibilityOptions['showColumnSelector'],
- valueName: keyof EuiDataGridToolBarVisibilityColumnSelectorOptions
-) => {
- if (showColumnSelector === false) return false;
- if (showColumnSelector == null) return true;
- if (showColumnSelector === true) return true;
- return showColumnSelector[valueName] !== false;
-};
+import {
+ EuiDataGridColumn,
+ EuiDataGridColumnVisibility,
+ EuiDataGridToolBarVisibilityOptions,
+} from '../data_grid_types';
+import { getNestedObjectOptions } from './data_grid_toolbar';
export const useDataGridColumnSelector = (
availableColumns: EuiDataGridColumn[],
@@ -52,16 +43,16 @@ export const useDataGridColumnSelector = (
showColumnSelector: EuiDataGridToolBarVisibilityOptions['showColumnSelector'],
displayValues: { [key: string]: string }
): [
- ReactElement,
+ ReactNode,
EuiDataGridColumn[],
(columns: string[]) => void,
(colFrom: string, colTo: string) => void
] => {
- const allowColumnHiding = getShowColumnSelectorValue(
+ const allowColumnHiding = getNestedObjectOptions(
showColumnSelector,
'allowHide'
);
- const allowColumnReorder = getShowColumnSelectorValue(
+ const allowColumnReorder = getNestedObjectOptions(
showColumnSelector,
'allowReorder'
);
@@ -148,159 +139,163 @@ export const useDataGridColumnSelector = (
);
}
- const columnSelector = (
- setIsOpen(false)}
- anchorPosition="downLeft"
- panelPaddingSize="s"
- panelClassName="euiDataGrid__controlPopoverWithDragDrop"
- button={
- setIsOpen(!isOpen)}
- >
- {buttonText}
-
- }
- >
-
- {allowColumnHiding && (
-
-
- {([search, searchcolumns]: string[]) => (
- ) =>
- setColumnSearchText(e.currentTarget.value)
- }
- data-test-subj="dataGridColumnSelectorSearch"
- />
- )}
-
-
- )}
-
-
-
-
- {filteredColumns.map((id, index) => (
-
- {(provided, state) => (
-
-
-
- {allowColumnHiding ? (
- {
- const {
- target: { checked },
- } = event;
- const nextVisibleColumns = sortedColumns.filter(
- (columnId) =>
- checked
- ? visibleColumnIds.has(columnId) ||
- id === columnId
- : visibleColumnIds.has(columnId) &&
- id !== columnId
- );
- setVisibleColumns(nextVisibleColumns);
- }}
- data-test-subj={`dataGridColumnSelectorToggleColumnVisibility-${id}`}
- />
- ) : (
-
- {id}
-
- )}
-
- {isDragEnabled && (
-
-
-
- )}
-
-
- )}
-
- ))}
-
-
-
-
-
- {allowColumnHiding && (
-
- setIsOpen(false)}
+ anchorPosition="downLeft"
+ panelPaddingSize="s"
+ panelClassName="euiDataGrid__controlPopoverWithDragDrop"
+ button={
+ setIsOpen(!isOpen)}
>
-
- setVisibleColumns(sortedColumns)}
- data-test-subj="dataGridColumnSelectorShowAllButton"
+ {buttonText}
+
+ }
+ >
+
+ {allowColumnHiding && (
+
+
-
-
-
-
- setVisibleColumns([])}
- data-test-subj="dataGridColumnSelectorHideAllButton"
+ {([search, searchcolumns]: string[]) => (
+ ) =>
+ setColumnSearchText(e.currentTarget.value)
+ }
+ data-test-subj="dataGridColumnSelectorSearch"
+ />
+ )}
+
+
+ )}
+
+
+
-
-
-
-
-
- )}
-
- );
+
+ {filteredColumns.map((id, index) => (
+
+ {(provided, state) => (
+
+
+
+ {allowColumnHiding ? (
+ {
+ const {
+ target: { checked },
+ } = event;
+ const nextVisibleColumns = sortedColumns.filter(
+ (columnId) =>
+ checked
+ ? visibleColumnIds.has(columnId) ||
+ id === columnId
+ : visibleColumnIds.has(columnId) &&
+ id !== columnId
+ );
+ setVisibleColumns(nextVisibleColumns);
+ }}
+ data-test-subj={`dataGridColumnSelectorToggleColumnVisibility-${id}`}
+ />
+ ) : (
+
+ {id}
+
+ )}
+
+ {isDragEnabled && (
+
+
+
+ )}
+
+
+ )}
+
+ ))}
+
+
+
+
+
+ {allowColumnHiding && (
+
+
+
+ setVisibleColumns(sortedColumns)}
+ data-test-subj="dataGridColumnSelectorShowAllButton"
+ >
+
+
+
+
+ setVisibleColumns([])}
+ data-test-subj="dataGridColumnSelectorHideAllButton"
+ >
+
+
+
+
+
+ )}
+
+ ) : null;
const orderedVisibleColumns = useMemo(
() =>
diff --git a/src/components/datagrid/controls/data_grid_toolbar.test.tsx b/src/components/datagrid/controls/data_grid_toolbar.test.tsx
index a1c513df46b..411fa9b891d 100644
--- a/src/components/datagrid/controls/data_grid_toolbar.test.tsx
+++ b/src/components/datagrid/controls/data_grid_toolbar.test.tsx
@@ -13,6 +13,7 @@ import {
EuiDataGridToolbar,
checkOrDefaultToolBarDisplayOptions,
renderAdditionalControls,
+ getNestedObjectOptions,
} from './data_grid_toolbar';
describe('EuiDataGridToolbar', () => {
@@ -20,9 +21,9 @@ describe('EuiDataGridToolbar', () => {
gridWidth: 500,
toolbarVisibility: true,
isFullScreen: false,
- styleSelector: React.createElement('div', null, 'mock style selector'),
+ displaySelector: mock style selector
,
controlBtnClasses: '',
- columnSelector: React.createElement('div', null, 'mock column selector'),
+ columnSelector: mock column selector
,
columnSorting: mock column sorting
,
setRef: jest.fn(),
setIsFullScreen: jest.fn(),
@@ -99,7 +100,7 @@ describe('EuiDataGridToolbar', () => {
{...requiredProps}
toolbarVisibility={{
showColumnSelector: false,
- showStyleSelector: false,
+ showDisplaySelector: false,
showSortSelector: false,
showFullScreenSelector: false,
additionalControls: {
@@ -159,7 +160,7 @@ describe('EuiDataGridToolbar', () => {
});
describe('checkOrDefaultToolBarDisplayOptions', () => {
- const key = 'showStyleSelector';
+ const key = 'showDisplaySelector';
it('returns boolean `toolbarVisibility`s as-is', () => {
expect(checkOrDefaultToolBarDisplayOptions(true, key)).toEqual(true);
@@ -326,3 +327,41 @@ describe('renderAdditionalControls', () => {
});
});
});
+
+describe('getNestedObjectOptions', () => {
+ interface MockOptions {
+ someKey?: boolean;
+ }
+
+ describe('non-object configuration', () => {
+ it('returns passed booleans', () => {
+ expect(getNestedObjectOptions(true, 'someKey')).toEqual(
+ true
+ );
+ expect(getNestedObjectOptions(false, 'someKey')).toEqual(
+ false
+ );
+ });
+
+ it('returns true if the option is undefined', () => {
+ expect(getNestedObjectOptions(undefined, 'someKey')).toEqual(
+ true
+ );
+ });
+ });
+
+ describe('object configuration', () => {
+ it('returns nested object booleans', () => {
+ expect(
+ getNestedObjectOptions({ someKey: true }, 'someKey')
+ ).toEqual(true);
+ expect(
+ getNestedObjectOptions({ someKey: false }, 'someKey')
+ ).toEqual(false);
+ });
+
+ it('returns true if the nested object key is undefined', () => {
+ expect(getNestedObjectOptions({}, 'someKey')).toEqual(true);
+ });
+ });
+});
diff --git a/src/components/datagrid/controls/data_grid_toolbar.tsx b/src/components/datagrid/controls/data_grid_toolbar.tsx
index fe3e5344794..81b610aadb0 100644
--- a/src/components/datagrid/controls/data_grid_toolbar.tsx
+++ b/src/components/datagrid/controls/data_grid_toolbar.tsx
@@ -33,7 +33,7 @@ export const EuiDataGridToolbar = ({
toolbarVisibility,
isFullScreen,
controlBtnClasses,
- styleSelector,
+ displaySelector,
columnSelector,
columnSorting,
setRef,
@@ -114,9 +114,9 @@ export const EuiDataGridToolbar = ({
{renderAdditionalControls(toolbarVisibility, 'right')}
{checkOrDefaultToolBarDisplayOptions(
toolbarVisibility,
- 'showStyleSelector'
+ 'showDisplaySelector'
)
- ? styleSelector
+ ? displaySelector
: null}
{checkOrDefaultToolBarDisplayOptions(
toolbarVisibility,
@@ -200,3 +200,20 @@ export function renderAdditionalControls(
return null;
}
+
+/**
+ * Utility helper for selectors/controls that allow nested options
+ * (e.g. column selector, display selector)
+ */
+
+export function getNestedObjectOptions(
+ controlOption: undefined | boolean | T,
+ objectKey: keyof T
+): boolean {
+ // If the config is a boolean, nested options follow that boolean
+ if (controlOption === false || controlOption === true) return controlOption;
+ // If config is not defined, default to enabled
+ if (controlOption == null) return true;
+ // Otherwise, type should be an object of boolean values - dive into it and return the value
+ return !!(controlOption[objectKey] ?? true);
+}
diff --git a/src/components/datagrid/controls/display_selector.test.tsx b/src/components/datagrid/controls/display_selector.test.tsx
new file mode 100644
index 00000000000..88b699a8d01
--- /dev/null
+++ b/src/components/datagrid/controls/display_selector.test.tsx
@@ -0,0 +1,391 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import React from 'react';
+import { act } from 'react-dom/test-utils';
+import { shallow, mount, ShallowWrapper, ReactWrapper } from 'enzyme';
+
+import {
+ EuiDataGridToolBarVisibilityOptions,
+ EuiDataGridRowHeightsOptions,
+} from '../data_grid_types';
+
+import { useDataGridDisplaySelector, startingStyles } from './display_selector';
+
+describe('useDataGridDisplaySelector', () => {
+ describe('displaySelector', () => {
+ // Hooks can only be called inside function components
+ const MockComponent = ({
+ showDisplaySelector = true as EuiDataGridToolBarVisibilityOptions['showDisplaySelector'],
+ gridStyles = {},
+ rowHeightsOptions = undefined as EuiDataGridRowHeightsOptions | undefined,
+ }) => {
+ const [displaySelector] = useDataGridDisplaySelector(
+ showDisplaySelector,
+ gridStyles,
+ rowHeightsOptions
+ );
+ return <>{displaySelector}>;
+ };
+ const openPopover = (component: ReactWrapper) => {
+ component
+ .find('[data-test-subj="dataGridDisplaySelectorButton"]')
+ .last()
+ .simulate('click');
+ };
+ const closePopover = (component: ReactWrapper) => {
+ act(() => {
+ (component
+ .find('[data-test-subj="dataGridDisplaySelectorPopover"]')
+ .first()
+ .prop('closePopover') as Function)();
+ });
+ };
+
+ it('renders a toolbar button/popover allowing users to customize display settings', () => {
+ const component = shallow();
+ expect(component).toMatchSnapshot();
+ });
+
+ it('does not render if all valid sub-options are disabled', () => {
+ const component = shallow(
+
+ );
+ expect(component.text()).toEqual('');
+ });
+
+ describe('density', () => {
+ const getSelection = (component: ReactWrapper) =>
+ component
+ .find('EuiButtonGroup[data-test-subj="densityButtonGroup"]')
+ .prop('idSelected');
+
+ it('renders display density buttons that change grid density on click', () => {
+ const component = mount();
+ openPopover(component);
+
+ // Click density 'buttons' (actually hidden radios)
+ component.find('[data-test-subj="expanded"]').simulate('change');
+ expect(getSelection(component)).toEqual('expanded');
+ component.find('[data-test-subj="normal"]').simulate('change');
+ expect(getSelection(component)).toEqual('normal');
+ component.find('[data-test-subj="compact"]').simulate('change');
+ expect(getSelection(component)).toEqual('compact');
+
+ // Should have changed the main toolbar icon accordingly
+ closePopover(component);
+ expect(
+ component
+ .find('[data-test-subj="dataGridDisplaySelectorButton"]')
+ .first()
+ .prop('iconType')
+ ).toEqual('tableDensityCompact');
+ });
+
+ it('hides the density buttongroup if allowDensity is set to false', () => {
+ const component = mount(
+
+ );
+ openPopover(component);
+
+ expect(
+ component.find('[data-test-subj="densityButtonGroup"]')
+ ).toHaveLength(0);
+ });
+
+ describe('convertGridStylesToSelection (loading initial state from passed gridStyles', () => {
+ it('should set compact state if both fontSize and cellPadding are s', () => {
+ const component = mount(
+
+ );
+ openPopover(component);
+ expect(getSelection(component)).toEqual('compact');
+ });
+
+ it('should set normal state if both fontSize and cellPadding are m', () => {
+ const component = mount(
+
+ );
+ openPopover(component);
+ expect(getSelection(component)).toEqual('normal');
+ });
+
+ it('should set compact state if both fontSize and cellPadding are l', () => {
+ const component = mount(
+
+ );
+ openPopover(component);
+ expect(getSelection(component)).toEqual('expanded');
+ });
+
+ it('should not select any buttons if fontSize and cellPadding do not match a set density state', () => {
+ const component = mount(
+
+ );
+ openPopover(component);
+ expect(getSelection(component)).toEqual('');
+ });
+ });
+ });
+
+ describe('row height', () => {
+ const getSelection = (component: ReactWrapper) =>
+ component
+ .find('EuiButtonGroup[data-test-subj="rowHeightButtonGroup"]')
+ .prop('idSelected');
+
+ it('renders row height buttons that toggle betwen undefined, auto, and lineCount', () => {
+ const component = mount();
+ openPopover(component);
+ expect(getSelection(component)).toEqual('undefined');
+
+ component.find('[data-test-subj="auto"]').simulate('change');
+ expect(getSelection(component)).toEqual('auto');
+ });
+
+ it('hides the row height buttongroup if allowRowHeight is set to false', () => {
+ const component = mount(
+
+ );
+ openPopover(component);
+
+ expect(
+ component.find('[data-test-subj="rowHeightButtonGroup"]')
+ ).toHaveLength(0);
+ });
+
+ describe('convertRowHeightsOptionsToSelection (loading initial state from passed rowHeightsOptions)', () => {
+ test('auto', () => {
+ const component = mount(
+
+ );
+ openPopover(component);
+ expect(getSelection(component)).toEqual('auto');
+ });
+
+ test('lineCount', () => {
+ const component = mount(
+
+ );
+ openPopover(component);
+ expect(getSelection(component)).toEqual('lineCount');
+ });
+
+ test('undefined', () => {
+ const component = mount(
+
+ );
+ openPopover(component);
+ expect(getSelection(component)).toEqual('undefined');
+ });
+
+ test('height should not select any buttons', () => {
+ const component1 = mount(
+
+ );
+ openPopover(component1);
+ expect(getSelection(component1)).toEqual('');
+
+ const component2 = mount(
+
+ );
+ openPopover(component2);
+ expect(getSelection(component2)).toEqual('');
+ });
+ });
+
+ describe('lineCount', () => {
+ const getLineCountNumber = (component: ReactWrapper) =>
+ component
+ .find('EuiRange[data-test-subj="lineCountNumber"]')
+ .prop('value');
+ const setLineCountNumber = (component: ReactWrapper, number: number) =>
+ component
+ .find('input[type="range"][data-test-subj="lineCountNumber"]')
+ .simulate('change', { target: { value: number } });
+
+ it('conditionally displays a line count number input when the lineCount button is selected', () => {
+ const component = mount();
+ openPopover(component);
+ expect(
+ component.find('[data-test-subj="lineCountNumber"]').exists()
+ ).toBe(false);
+
+ component.find('[data-test-subj="lineCount"]').simulate('change');
+ expect(getSelection(component)).toEqual('lineCount');
+
+ expect(
+ component.find('[data-test-subj="lineCountNumber"]').exists()
+ ).toBe(true);
+ });
+
+ it('displays the defaultHeight.lineCount passed in by the developer', () => {
+ const component = mount(
+
+ );
+ openPopover(component);
+
+ expect(getLineCountNumber(component)).toEqual(5);
+ });
+
+ it('defaults to a lineCount of 2 when no developer settings have been passed', () => {
+ const component = mount();
+ openPopover(component);
+ component.find('[data-test-subj="lineCount"]').simulate('change');
+
+ expect(getLineCountNumber(component)).toEqual(2);
+ });
+
+ it('increments the rowHeightOptions line count number', () => {
+ const component = mount(
+
+ );
+ openPopover(component);
+
+ setLineCountNumber(component, 3);
+ expect(getLineCountNumber(component)).toEqual(3);
+ });
+
+ it('does not allow zero or negative line count values', () => {
+ const component = mount(
+
+ );
+ openPopover(component);
+
+ setLineCountNumber(component, 0);
+ expect(getLineCountNumber(component)).toEqual(2);
+
+ setLineCountNumber(component, -50);
+ expect(getLineCountNumber(component)).toEqual(2);
+ });
+ });
+ });
+ });
+
+ describe('gridStyles', () => {
+ it('returns an object of grid styles with user overrides', () => {
+ const initialStyles = { ...startingStyles, stripes: true };
+ const MockComponent = () => {
+ const [, gridStyles] = useDataGridDisplaySelector(
+ true,
+ initialStyles,
+ {}
+ );
+ return ;
+ };
+ const component = shallow();
+
+ expect(component).toMatchInlineSnapshot(`
+
+ `);
+ });
+ });
+
+ describe('rowHeightsOptions', () => {
+ // Test helpers
+ const MockComponent = ({
+ initialRowHeightsOptions = undefined as
+ | EuiDataGridRowHeightsOptions
+ | undefined,
+ }) => {
+ const [displaySelector, , rowHeightsOptions] = useDataGridDisplaySelector(
+ true,
+ {},
+ initialRowHeightsOptions
+ );
+ return (
+ <>
+ {displaySelector}
+ {JSON.stringify(rowHeightsOptions)}
+ >
+ );
+ };
+ const diveIntoEuiI18n = (component: ShallowWrapper) => {
+ return (component
+ .find('EuiI18n')
+ .last()
+ .renderProp('children') as Function)(['', '', '', '']);
+ };
+ const setRowHeight = (component: ShallowWrapper, selection = '') => {
+ diveIntoEuiI18n(component)
+ .find('[data-test-subj="rowHeightButtonGroup"]')
+ .simulate('change', selection);
+ };
+ const setLineCount = (component: ShallowWrapper, lineCount = 1) => {
+ diveIntoEuiI18n(component)
+ .find('[data-test-subj="lineCountNumber"]')
+ .simulate('change', { target: { value: lineCount } });
+ };
+ const getOutput = (component: ShallowWrapper) => {
+ return JSON.parse(component.find('[data-test-subj="output"]').text());
+ };
+
+ it('returns an object of rowHeightsOptions with user overrides', () => {
+ const component = shallow(
+
+ );
+
+ setRowHeight(component, 'lineCount');
+ setLineCount(component, 5);
+
+ expect(getOutput(component)).toEqual({
+ lineHeight: '2em',
+ defaultHeight: { lineCount: 5 },
+ });
+ });
+
+ it('handles undefined rowHeightsObjects (from the developer)', () => {
+ const component = shallow(
+
+ );
+ expect(getOutput(component)).toEqual({});
+
+ setRowHeight(component, 'auto');
+
+ expect(getOutput(component)).toEqual({
+ defaultHeight: 'auto',
+ });
+ });
+
+ it('handles undefined rowHeightsOptions (from the user)', () => {
+ const component = shallow(
+
+ );
+
+ setRowHeight(component, 'undefined');
+
+ expect(getOutput(component)).toEqual({
+ lineHeight: '2em',
+ });
+ });
+ });
+});
diff --git a/src/components/datagrid/controls/display_selector.tsx b/src/components/datagrid/controls/display_selector.tsx
new file mode 100644
index 00000000000..bafc4591c46
--- /dev/null
+++ b/src/components/datagrid/controls/display_selector.tsx
@@ -0,0 +1,313 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import React, { ReactNode, useState, useMemo, useCallback } from 'react';
+
+import { EuiI18n, useEuiI18n } from '../../i18n';
+import { EuiPopover } from '../../popover';
+import { EuiButtonIcon, EuiButtonGroup } from '../../button';
+import { EuiFormRow, EuiRange } from '../../form';
+import { EuiToolTip } from '../../tool_tip';
+
+import {
+ EuiDataGridToolBarVisibilityOptions,
+ EuiDataGridStyle,
+ EuiDataGridRowHeightsOptions,
+} from '../data_grid_types';
+import { getNestedObjectOptions } from './data_grid_toolbar';
+
+export const startingStyles: EuiDataGridStyle = {
+ cellPadding: 'm',
+ fontSize: 'm',
+ border: 'all',
+ stripes: false,
+ rowHover: 'highlight',
+ header: 'shade',
+ footer: 'overline',
+ stickyFooter: true,
+};
+
+// These are the available options. They power the gridDensity hook and also the options in the render
+const densityOptions: string[] = ['compact', 'normal', 'expanded'];
+const densityStyles: { [key: string]: Partial } = {
+ expanded: {
+ fontSize: 'l',
+ cellPadding: 'l',
+ },
+ normal: {
+ fontSize: 'm',
+ cellPadding: 'm',
+ },
+ compact: {
+ fontSize: 's',
+ cellPadding: 's',
+ },
+};
+const convertGridStylesToSelection = (gridStyles: EuiDataGridStyle) => {
+ if (gridStyles?.fontSize === 's' && gridStyles?.cellPadding === 's')
+ return 'compact';
+ if (gridStyles?.fontSize === 'm' && gridStyles?.cellPadding === 'm')
+ return 'normal';
+ if (gridStyles?.fontSize === 'l' && gridStyles?.cellPadding === 'l')
+ return 'expanded';
+ return '';
+};
+
+// Used to correctly format the icon name for the grid density icon
+const capitalizeDensityString = (s: string) => s[0].toUpperCase() + s.slice(1);
+
+// Row height options and utilities
+const rowHeightButtonOptions: string[] = ['undefined', 'auto', 'lineCount'];
+const convertRowHeightsOptionsToSelection = (
+ rowHeightsOptions?: EuiDataGridRowHeightsOptions
+) => {
+ if (rowHeightsOptions) {
+ const { defaultHeight } = rowHeightsOptions;
+
+ if (defaultHeight === 'auto') {
+ return rowHeightButtonOptions[1];
+ }
+ if (typeof defaultHeight === 'object' && defaultHeight?.lineCount) {
+ return rowHeightButtonOptions[2];
+ }
+ if (
+ typeof defaultHeight === 'number' ||
+ (typeof defaultHeight === 'object' && defaultHeight.height)
+ ) {
+ return '';
+ }
+ }
+ return rowHeightButtonOptions[0];
+};
+
+export const useDataGridDisplaySelector = (
+ showDisplaySelector: EuiDataGridToolBarVisibilityOptions['showDisplaySelector'],
+ initialStyles: EuiDataGridStyle,
+ initialRowHeightsOptions?: EuiDataGridRowHeightsOptions
+): [ReactNode, EuiDataGridStyle, EuiDataGridRowHeightsOptions] => {
+ const [isOpen, setIsOpen] = useState(false);
+
+ const showDensityControls = getNestedObjectOptions(
+ showDisplaySelector,
+ 'allowDensity'
+ );
+
+ const showRowHeightControls = getNestedObjectOptions(
+ showDisplaySelector,
+ 'allowRowHeight'
+ );
+
+ // track styles specified by the user at run time
+ const [userGridStyles, setUserGridStyles] = useState({});
+ const [userRowHeightsOptions, setUserRowHeightsOptions] = useState({});
+
+ // Normal is the default density
+ const [gridDensity, _setGridDensity] = useState(
+ convertGridStylesToSelection(initialStyles)
+ );
+ const setGridDensity = (density: string) => {
+ _setGridDensity(density);
+ setUserGridStyles(densityStyles[density]);
+ };
+
+ // Row height state
+ const [lineCount, setLineCount] = useState(
+ // @ts-ignore - optional chaining operator handles types & cases that aren't lineCount
+ initialRowHeightsOptions?.defaultHeight?.lineCount || 2
+ );
+ const [rowHeightSelection, setRowHeightSelection] = useState(
+ convertRowHeightsOptionsToSelection(initialRowHeightsOptions)
+ );
+ const setRowHeight = useCallback(
+ (option: string) => {
+ let rowHeightsOptions: EuiDataGridRowHeightsOptions | undefined;
+
+ if (option === 'auto') {
+ rowHeightsOptions = { defaultHeight: 'auto' };
+ } else if (option === 'lineCount') {
+ rowHeightsOptions = { defaultHeight: { lineCount } };
+ } else {
+ rowHeightsOptions = { defaultHeight: undefined };
+ }
+
+ setRowHeightSelection(option);
+ setUserRowHeightsOptions(rowHeightsOptions);
+ },
+ [lineCount]
+ );
+ const setLineCountHeight = useCallback((event) => {
+ const newLineCount = Number(event.target.value);
+ if (newLineCount < 1) return; // Don't let users set a 0 or negative line count
+
+ setLineCount(newLineCount);
+ setUserRowHeightsOptions({ defaultHeight: { lineCount: newLineCount } });
+ }, []);
+
+ // merge the developer-specified styles with any user overrides
+ const gridStyles = useMemo(() => {
+ return {
+ ...initialStyles,
+ ...userGridStyles,
+ };
+ }, [initialStyles, userGridStyles]);
+
+ const rowHeightsOptions = useMemo(() => {
+ return {
+ ...initialRowHeightsOptions,
+ ...userRowHeightsOptions,
+ };
+ }, [initialRowHeightsOptions, userRowHeightsOptions]);
+
+ const buttonLabel = useEuiI18n(
+ 'euiDisplaySelector.buttonText',
+ 'Display options'
+ );
+
+ const displaySelector =
+ showDensityControls || showRowHeightControls ? (
+ setIsOpen(false)}
+ anchorPosition="downRight"
+ panelPaddingSize="s"
+ panelClassName="euiDataGrid__displayPopoverPanel"
+ button={
+
+ setIsOpen(!isOpen)}
+ aria-label={buttonLabel}
+ />
+
+ }
+ >
+ {showDensityControls && (
+
+ {([
+ densityLabel,
+ labelCompact,
+ labelNormal,
+ labelExpanded,
+ ]: string[]) => (
+
+
+
+ )}
+
+ )}
+ {showRowHeightControls && (
+
+ {([
+ rowHeightLabel,
+ labelSingle,
+ labelAuto,
+ labelCustom,
+ lineCountLabel,
+ ]: string[]) => (
+ <>
+
+
+
+ {rowHeightSelection === rowHeightButtonOptions[2] && (
+
+
+
+ )}
+ >
+ )}
+
+ )}
+
+ ) : null;
+
+ return [displaySelector, gridStyles, rowHeightsOptions];
+};
diff --git a/src/components/datagrid/controls/index.ts b/src/components/datagrid/controls/index.ts
index c3c5cce82f7..0c0ca128208 100644
--- a/src/components/datagrid/controls/index.ts
+++ b/src/components/datagrid/controls/index.ts
@@ -8,7 +8,7 @@
export { useDataGridColumnSelector } from './column_selector';
export { useDataGridColumnSorting } from './column_sorting';
-export { useDataGridStyleSelector, startingStyles } from './style_selector';
+export { useDataGridDisplaySelector, startingStyles } from './display_selector';
export {
checkOrDefaultToolBarDisplayOptions,
EuiDataGridToolbar,
diff --git a/src/components/datagrid/controls/style_selector.test.tsx b/src/components/datagrid/controls/style_selector.test.tsx
deleted file mode 100644
index 509667f00ff..00000000000
--- a/src/components/datagrid/controls/style_selector.test.tsx
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0 and the Server Side Public License, v 1; you may not use this file except
- * in compliance with, at your election, the Elastic License 2.0 or the Server
- * Side Public License, v 1.
- */
-
-import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { shallow, mount } from 'enzyme';
-
-import { useDataGridStyleSelector, startingStyles } from './style_selector';
-
-describe('useDataGridStyleSelector', () => {
- describe('styleSelector', () => {
- // Hooks can only be called inside function components
- const MockComponent = () => {
- const [styleSelector] = useDataGridStyleSelector({});
- return <>{styleSelector}>;
- };
-
- it('renders a toolbar button/popover allowing users to customize styles', () => {
- const component = shallow();
- expect(component).toMatchSnapshot();
- });
-
- it('renders display density buttons that change grid density on click', () => {
- const component = mount();
-
- // Open popover
- component
- .find('[data-test-subj="dataGridStyleSelectorButton"]')
- .last()
- .simulate('click');
-
- // Click density 'buttons' (actually hidden radios)
- component.find('[data-test-subj="expanded"]').simulate('change');
- component.find('[data-test-subj="normal"]').simulate('change');
- component.find('[data-test-subj="compact"]').simulate('change');
- expect(component.find('EuiButtonGroup').prop('idSelected')).toEqual(
- 'compact'
- );
-
- // Close popover
- act(() => {
- (component
- .find('[data-test-subj="dataGridStyleSelectorPopover"]')
- .first()
- .prop('closePopover') as Function)();
- });
- });
- });
-
- describe('gridStyles', () => {
- it('returns an object of grid styles with user overrides', () => {
- const initialStyles = { ...startingStyles, stripes: true };
- const MockComponent = () => {
- const [, gridStyles] = useDataGridStyleSelector(initialStyles);
- return ;
- };
- const component = shallow();
-
- expect(component).toMatchInlineSnapshot(`
-
- `);
- });
- });
-});
diff --git a/src/components/datagrid/controls/style_selector.tsx b/src/components/datagrid/controls/style_selector.tsx
deleted file mode 100644
index 7d46e2a2eb4..00000000000
--- a/src/components/datagrid/controls/style_selector.tsx
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0 and the Server Side Public License, v 1; you may not use this file except
- * in compliance with, at your election, the Elastic License 2.0 or the Server
- * Side Public License, v 1.
- */
-
-import React, { ReactElement, useState } from 'react';
-import { EuiDataGridStyle } from '../data_grid_types';
-import { EuiI18n, useEuiI18n } from '../../i18n';
-import { EuiPopover } from '../../popover';
-import { EuiButtonIcon, EuiButtonGroup } from '../../button';
-import { EuiFormRow } from '../../form';
-import { EuiToolTip } from '../../tool_tip';
-
-export const startingStyles: EuiDataGridStyle = {
- cellPadding: 'm',
- fontSize: 'm',
- border: 'all',
- stripes: false,
- rowHover: 'highlight',
- header: 'shade',
- footer: 'overline',
- stickyFooter: true,
-};
-
-// These are the available options. They power the gridDensity hook and also the options in the render
-const densityOptions: string[] = ['compact', 'normal', 'expanded'];
-const densityStyles: { [key: string]: Partial } = {
- expanded: {
- fontSize: 'l',
- cellPadding: 'l',
- },
- normal: {
- fontSize: 'm',
- cellPadding: 'm',
- },
- compact: {
- fontSize: 's',
- cellPadding: 's',
- },
-};
-// Used to correctly format the icon name for the grid density icon
-const capitalizeDensityString = (s: string) => s[0].toUpperCase() + s.slice(1);
-
-export const useDataGridStyleSelector = (
- initialStyles: EuiDataGridStyle
-): [ReactElement, EuiDataGridStyle] => {
- // track styles specified by the user at run time
- const [userGridStyles, setUserGridStyles] = useState({});
-
- const [isOpen, setIsOpen] = useState(false);
-
- // Normal is the default density
- const [gridDensity, _setGridDensity] = useState(densityOptions[1]);
- const setGridDensity = (density: string) => {
- _setGridDensity(density);
- setUserGridStyles(densityStyles[density]);
- };
-
- // merge the developer-specified styles with any user overrides
- const gridStyles = {
- ...initialStyles,
- ...userGridStyles,
- };
-
- const buttonLabel = useEuiI18n(
- 'euiStyleSelector.buttonText',
- 'Display options'
- );
-
- const styleSelector = (
- setIsOpen(false)}
- anchorPosition="downRight"
- panelPaddingSize="s"
- panelClassName="euiDataGrid__displayPopoverPanel"
- button={
-
- setIsOpen(!isOpen)}
- aria-label={buttonLabel}
- />
-
- }
- >
-
- {([
- densityLabel,
- labelCompact,
- labelNormal,
- labelExpanded,
- ]: string[]) => (
-
-
-
- )}
-
-
- );
-
- return [styleSelector, gridStyles];
-};
diff --git a/src/components/datagrid/data_grid.test.tsx b/src/components/datagrid/data_grid.test.tsx
index a05f1df80be..bd58ee30e25 100644
--- a/src/components/datagrid/data_grid.test.tsx
+++ b/src/components/datagrid/data_grid.test.tsx
@@ -752,7 +752,7 @@ describe('EuiDataGrid', () => {
toolbarVisibility: {
showFullScreenSelector: false,
showSortSelector: false,
- showStyleSelector: true,
+ showDisplaySelector: true,
},
});
@@ -769,7 +769,7 @@ describe('EuiDataGrid', () => {
// style selector
component.debug();
expect(
- findTestSubject(component, 'dataGridStyleSelectorButton').length
+ findTestSubject(component, 'dataGridDisplaySelectorButton').length
).toBe(1);
// column selector
diff --git a/src/components/datagrid/data_grid.tsx b/src/components/datagrid/data_grid.tsx
index e9908d8b22c..af29264c086 100644
--- a/src/components/datagrid/data_grid.tsx
+++ b/src/components/datagrid/data_grid.tsx
@@ -27,7 +27,7 @@ import { EuiDataGridBody, VIRTUALIZED_CONTAINER_CLASS } from './body';
import {
useDataGridColumnSelector,
useDataGridColumnSorting,
- useDataGridStyleSelector,
+ useDataGridDisplaySelector,
startingStyles,
checkOrDefaultToolBarDisplayOptions,
EuiDataGridToolbar,
@@ -488,7 +488,7 @@ export const EuiDataGrid: FunctionComponent = (props) => {
minSizeForControls,
height,
width,
- rowHeightsOptions,
+ rowHeightsOptions: _rowHeightsOptions,
virtualizationOptions,
...rest
} = props;
@@ -648,8 +648,17 @@ export const EuiDataGrid: FunctionComponent = (props) => {
allSchemaDetectors,
displayValues
);
- const [styleSelector, gridStyles] = useDataGridStyleSelector(
- gridStyleWithDefaults
+ const [
+ displaySelector,
+ gridStyles,
+ rowHeightsOptions,
+ ] = useDataGridDisplaySelector(
+ checkOrDefaultToolBarDisplayOptions(
+ toolbarVisibility,
+ 'showDisplaySelector'
+ ),
+ gridStyleWithDefaults,
+ _rowHeightsOptions
);
// compute the default column width from the container's clientWidth and count of visible columns
@@ -808,7 +817,7 @@ export const EuiDataGrid: FunctionComponent = (props) => {
gridWidth={gridWidth}
minSizeForControls={minSizeForControls}
toolbarVisibility={toolbarVisibility}
- styleSelector={styleSelector}
+ displaySelector={displaySelector}
isFullScreen={isFullScreen}
setIsFullScreen={setIsFullScreen}
controlBtnClasses={controlBtnClasses}
diff --git a/src/components/datagrid/data_grid_types.ts b/src/components/datagrid/data_grid_types.ts
index c3a5eeff132..9b1b15ab9e8 100644
--- a/src/components/datagrid/data_grid_types.ts
+++ b/src/components/datagrid/data_grid_types.ts
@@ -30,10 +30,10 @@ export interface EuiDataGridToolbarProps {
gridWidth: number;
minSizeForControls?: number;
toolbarVisibility: boolean | EuiDataGridToolBarVisibilityOptions;
- styleSelector: ReactElement;
+ displaySelector: ReactNode;
isFullScreen: boolean;
controlBtnClasses: string;
- columnSelector: ReactElement;
+ columnSelector: ReactNode;
columnSorting: ReactNode;
setRef: RefCallback;
setIsFullScreen: Dispatch>;
@@ -589,6 +589,17 @@ export interface EuiDataGridToolBarVisibilityColumnSelectorOptions {
allowReorder?: boolean;
}
+export interface EuiDataGridToolBarVisibilityDisplaySelectorOptions {
+ /**
+ * When `false`, removes the ability to change density display through the UI
+ */
+ allowDensity?: boolean;
+ /**
+ * When `false`, removes the ability to change row height display through the UI
+ */
+ allowRowHeight?: boolean;
+}
+
export interface EuiDataGridToolBarVisibilityOptions {
/**
* Allows the ability for the user to hide fields and sort columns, boolean or a #EuiDataGridToolBarVisibilityColumnSelectorOptions
@@ -597,9 +608,12 @@ export interface EuiDataGridToolBarVisibilityOptions {
| boolean
| EuiDataGridToolBarVisibilityColumnSelectorOptions;
/**
- * Allows the ability for the user to set the grid density. If on, this merges against what is provided in #EuiDataGridStyle
+ * 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
*/
- showStyleSelector?: boolean;
+ showDisplaySelector?:
+ | boolean
+ | EuiDataGridToolBarVisibilityDisplaySelectorOptions;
/**
* Allows the ability for the user to sort rows based upon column values
*/
@@ -744,6 +758,9 @@ export interface EuiDataGridRowHeightsOptions {
defaultHeight?: EuiDataGridRowHeightOption;
/**
* Defines the height for a specific row. It can be line count or just height.
+ *
+ * When using row height overrides, we strongly setting the `showDisplaySelector: allowRowHeight`
+ * toolbar control to `false` in #EuiDataGridToolBarVisibilityOptions
*/
rowHeights?: Record;
/**
diff --git a/src/components/datagrid/index.ts b/src/components/datagrid/index.ts
index 8dad0388361..7d32fd2d869 100644
--- a/src/components/datagrid/index.ts
+++ b/src/components/datagrid/index.ts
@@ -10,7 +10,7 @@ export { EuiDataGrid } from './data_grid';
export {
useDataGridColumnSelector,
useDataGridColumnSorting,
- useDataGridStyleSelector,
+ useDataGridDisplaySelector,
} from './controls';
export * from './data_grid_types';