From 1bfb3a784b987cd435321ce75f422ff80db756e3 Mon Sep 17 00:00:00 2001 From: Chandler Prall Date: Fri, 9 Aug 2019 12:14:18 -0600 Subject: [PATCH] EuiDataGrid column visibility & ordering (#2207) * Show/hide and re-order datagrid columns * Column visability & ordering tests * column styling * column sizing and bars * blergh * tests * feedback * Fix linting --- src-docs/src/views/datagrid/datagrid.js | 13 +- .../__snapshots__/data_grid.test.tsx.snap | 226 +++++++------ src/components/datagrid/_data_grid.scss | 12 + .../datagrid/_data_grid_column_resizer.scss | 15 + .../datagrid/_data_grid_column_selector.scss | 21 ++ .../datagrid/_data_grid_data_row.scss | 1 + src/components/datagrid/_index.scss | 1 + src/components/datagrid/column_selector.tsx | 138 ++++++++ src/components/datagrid/data_grid.test.tsx | 302 +++++++++++++++--- src/components/datagrid/data_grid.tsx | 244 +++++++------- src/components/datagrid/data_grid_cell.tsx | 15 +- .../datagrid/data_grid_column_resizer.tsx | 8 +- .../datagrid/data_grid_data_row.tsx | 11 +- .../datagrid/data_grid_header_row.tsx | 29 +- src/components/datagrid/data_grid_types.ts | 2 +- src/components/form/switch/_switch.scss | 40 +++ src/components/popover/_popover_footer.scss | 2 +- src/test/find_test_subject.ts | 3 +- 18 files changed, 795 insertions(+), 288 deletions(-) create mode 100644 src/components/datagrid/_data_grid_column_selector.scss create mode 100644 src/components/datagrid/column_selector.tsx diff --git a/src-docs/src/views/datagrid/datagrid.js b/src-docs/src/views/datagrid/datagrid.js index 03be6cad200..a6594475771 100644 --- a/src-docs/src/views/datagrid/datagrid.js +++ b/src-docs/src/views/datagrid/datagrid.js @@ -11,16 +11,16 @@ import { const columns = [ { - name: 'name', + id: 'name', }, { - name: 'avatar_url', + id: 'avatar_url', }, { - name: 'url', + id: 'url', }, { - name: 'contributions', + id: 'contributions', }, ]; @@ -323,6 +323,7 @@ export default class DataGrid extends Component { button={button} isOpen={this.state.isPopoverOpen} anchorPosition="rightUp" + zIndex={2} closePopover={this.closePopover.bind(this)}>
@@ -395,9 +396,7 @@ export default class DataGrid extends Component { rowHover: this.state.rowHoverSelected, header: this.state.headerSelected, }} - renderCellValue={({ rowIndex, columnName }) => - data[rowIndex][columnName] - } + renderCellValue={({ rowIndex, columnId }) => data[rowIndex][columnId]} pagination={{ ...pagination, pageSizeOptions: [5, 10, 25], diff --git a/src/components/datagrid/__snapshots__/data_grid.test.tsx.snap b/src/components/datagrid/__snapshots__/data_grid.test.tsx.snap index 86c8a59e62b..a6815af58ad 100644 --- a/src/components/datagrid/__snapshots__/data_grid.test.tsx.snap +++ b/src/components/datagrid/__snapshots__/data_grid.test.tsx.snap @@ -119,129 +119,157 @@ exports[`EuiDataGrid pagination renders 1`] = ` `; exports[`EuiDataGrid rendering renders with common and div attributes 1`] = ` -
+Array [
-
-
- A -
+ + + + Columns + + +
+
+
, +
+
+ class="euiDataGridHeaderCell" + data-test-subj="dataGridHeaderCell-A" + role="columnheader" + style="width:undefinedpx" + > +
+ A +
+
- B +
+ B +
-
-
-
- 0, A -
- 0, B +
+ 0, A +
+
+ 0, B +
-
-
- 1, A +
+ 1, A +
+
+ 1, B +
- 1, B +
+ 2, A +
+
+ 2, B +
-
- 2, A -
-
- 2, B -
-
-
-
-
+ class="euiSpacer euiSpacer--s" + /> +
, +] `; diff --git a/src/components/datagrid/_data_grid.scss b/src/components/datagrid/_data_grid.scss index 62c9e7600ac..983544e2990 100644 --- a/src/components/datagrid/_data_grid.scss +++ b/src/components/datagrid/_data_grid.scss @@ -2,6 +2,18 @@ @include euiScrollBar; font-feature-settings: 'tnum' 1; // Tabular numbers overflow-x: auto; + scroll-padding: 0; max-width: 100%; width: 100%; + background: $euiColorLightestShade; +} + +.euiDataGrid__controls { + @include euiBottomShadowSmall; + + position: relative; + z-index: 2; + border-top: $euiBorderThin; + border-right: $euiBorderThin; + border-left: $euiBorderThin; } \ No newline at end of file diff --git a/src/components/datagrid/_data_grid_column_resizer.scss b/src/components/datagrid/_data_grid_column_resizer.scss index ba500e2900f..f46bce3d618 100644 --- a/src/components/datagrid/_data_grid_column_resizer.scss +++ b/src/components/datagrid/_data_grid_column_resizer.scss @@ -28,6 +28,21 @@ user-select: none; } } +} + +// This is important. Because the resizer sits in the negative space to the right of the column +// it can cause the full grid to be a few pixels longer than it actually is. So for the last one +// we don't use negative positioning and the borders from the cell will match the container. +@include euiDataGridHeaderCell { + &:last-child { + .euiDataGridColumnResizer { + right: 0; + &:after { + left: auto; + right: 0; + } + } + } } diff --git a/src/components/datagrid/_data_grid_column_selector.scss b/src/components/datagrid/_data_grid_column_selector.scss new file mode 100644 index 00000000000..8a213e8c337 --- /dev/null +++ b/src/components/datagrid/_data_grid_column_selector.scss @@ -0,0 +1,21 @@ +// sass-lint:disable-block no-empty-rulesets +.euiDataGridColumnSelector { + // sass-lint:disable-block no-important + // background: $euiColorEmptyShade !important; // No need for droppable coloring +} + +.euiDataGridColumnSelector__item { + background: $euiColorEmptyShade; + padding: $euiSizeXS; + + &-isDragging { + @include euiBottomShadow; + } +} + +// TODO: Find a better solution for how to deal with fix position draggables inside the transformed popover +.euiDataGridColumnSelectorPopover { + // sass-lint:disable-block no-important + transform: none !important; + margin-top: -$euiSizeS; +} diff --git a/src/components/datagrid/_data_grid_data_row.scss b/src/components/datagrid/_data_grid_data_row.scss index 44b0d65ab6a..d600af65a1b 100644 --- a/src/components/datagrid/_data_grid_data_row.scss +++ b/src/components/datagrid/_data_grid_data_row.scss @@ -13,6 +13,7 @@ overflow: hidden; white-space: nowrap; flex: 0 0 auto; + background: $euiColorEmptyShade; &:first-of-type { border-left: $euiBorderThin; diff --git a/src/components/datagrid/_index.scss b/src/components/datagrid/_index.scss index 10d5a8c8837..033b921ce19 100644 --- a/src/components/datagrid/_index.scss +++ b/src/components/datagrid/_index.scss @@ -4,3 +4,4 @@ @import 'data_grid_header_row'; @import 'data_grid_column_resizer'; @import 'data_grid_data_row'; +@import 'data_grid_column_selector'; diff --git a/src/components/datagrid/column_selector.tsx b/src/components/datagrid/column_selector.tsx new file mode 100644 index 00000000000..8cea5046078 --- /dev/null +++ b/src/components/datagrid/column_selector.tsx @@ -0,0 +1,138 @@ +import React, { Fragment, FunctionComponent, useState } from 'react'; +import { EuiDataGridColumn } from './data_grid_types'; +// @ts-ignore-next-line +import { EuiPopover, EuiPopoverFooter } from '../popover'; +// @ts-ignore-next-line +import { EuiButtonEmpty } from '../button'; +import { EuiFlexGroup, EuiFlexItem } from '../flex'; +// @ts-ignore-next-line +import { EuiSwitch } from '../form'; +import { + EuiDragDropContext, + EuiDraggable, + EuiDroppable, +} from '../drag_and_drop'; +import { DropResult } from 'react-beautiful-dnd'; +import { EuiIcon } from '../icon'; + +export const useColumnSelector = ( + availableColumns: EuiDataGridColumn[] +): [FunctionComponent, EuiDataGridColumn[]] => { + const [sortedColumns, setSortedColumns] = useState(availableColumns); + + const [visibleColumns, setVisibleColumns] = useState(availableColumns); + const visibleColumnIds = visibleColumns.reduce((visibleColumnIds, { id }) => { + visibleColumnIds.add(id); + return visibleColumnIds; + }, new Set()); + + const [isOpen, setIsOpen] = useState(false); + + function onDragEnd({ + source: { index: sourceIndex }, + destination, + }: DropResult) { + const destinationIndex = destination!.index; + const nextSortedColumns = [...sortedColumns]; + + nextSortedColumns[sourceIndex] = sortedColumns[destinationIndex]; + nextSortedColumns[destinationIndex] = sortedColumns[sourceIndex]; + + setSortedColumns(nextSortedColumns); + + const nextVisibleColumns = nextSortedColumns.filter(({ id }) => + visibleColumnIds.has(id) + ); + setVisibleColumns(nextVisibleColumns); + } + + const ColumnSelector = () => ( + setIsOpen(false)} + anchorPosition="downLeft" + panelPaddingSize="s" + ownFocus + panelClassName="euiDataGridColumnSelectorPopover" + button={ + + setIsOpen(!isOpen)}> + Columns + + + }> + + + + {sortedColumns.map(({ id }, index) => ( + + {(provided, state) => ( +
+ + + ) => { + const nextVisibleColumns = sortedColumns.filter( + ({ id: columnId }) => + checked + ? visibleColumnIds.has(columnId) || + id === columnId + : visibleColumnIds.has(columnId) && + id !== columnId + ); + setVisibleColumns(nextVisibleColumns); + }} + /> + + +
+ +
+
+
+
+ )} +
+ ))} +
+
+
+ + + + setVisibleColumns(sortedColumns)}> + Show all + + + + setVisibleColumns([])}> + Hide all + + + + +
+ ); + + return [ColumnSelector, visibleColumns]; +}; diff --git a/src/components/datagrid/data_grid.test.tsx b/src/components/datagrid/data_grid.test.tsx index 7277c3dee86..d2af38cf2f9 100644 --- a/src/components/datagrid/data_grid.test.tsx +++ b/src/components/datagrid/data_grid.test.tsx @@ -8,6 +8,7 @@ import { } from '../../test'; import { EuiDataGridColumnResizer } from './data_grid_column_resizer'; import { keyCodes } from '../../services'; +import { act } from 'react-dom/test-utils'; function getFocusableCell(component: ReactWrapper) { return findTestSubject(component, 'dataGridRowCell').find('[tabIndex=0]'); @@ -16,7 +17,7 @@ function getFocusableCell(component: ReactWrapper) { function extractGridData(datagrid: ReactWrapper) { const rows: string[][] = []; - const headerCells = findTestSubject(datagrid, 'dataGridHeaderCell'); + const headerCells = findTestSubject(datagrid, 'dataGridHeaderCell', '|='); const headerRow: string[] = []; headerCells.forEach((cell: any) => headerRow.push(cell.text())); rows.push(headerRow); @@ -33,9 +34,182 @@ function extractGridData(datagrid: ReactWrapper) { } function extractColumnWidths(datagrid: ReactWrapper) { - return (findTestSubject(datagrid, 'dataGridHeaderCell') as ReactWrapper<{ - style: { width: string }; - }>).map(cell => cell.props().style.width); + return (findTestSubject(datagrid, 'dataGridHeaderCell', '|=') as ReactWrapper< + any + >).reduce((widths: { [key: string]: number }, cell) => { + const [, columnId] = cell + .props() + ['data-test-subj'].match(/dataGridHeaderCell-(.*)/); + widths[columnId] = parseFloat(cell.props().style.width); + return widths; + }, {}); +} + +function resizeColumn( + datagrid: ReactWrapper, + columnId: string, + columnWidth: number +) { + const widths = extractColumnWidths(datagrid); + const originalWidth = widths[columnId]; + + const firstResizer = datagrid + .find(`EuiDataGridColumnResizer[columnId="${columnId}"]`) + .instance() as EuiDataGridColumnResizer; + firstResizer.onMouseDown({ pageX: originalWidth }); + firstResizer.onMouseMove({ pageX: columnWidth }); + act(() => firstResizer.onMouseUp()); + + datagrid.update(); +} + +expect.extend({ + toBeEuiPopover(received: ReactWrapper) { + const pass = received.name() === 'EuiPopover'; + if (pass) { + return { + pass: true, + message: () => + `expected component "${received.name}" to not be EuiPopover`, + }; + } else { + return { + pass: false, + message: () => `expected component "${received.name}" to be EuiPopover`, + }; + } + }, + euiPopoverToBeOpen(received) { + expect(received).toBeEuiPopover(); + const { isOpen } = received.props(); + const pass = isOpen === true; + if (pass) { + return { + pass: true, + message: () => 'expected EuiPopover to be closed', + }; + } else { + return { + pass: false, + message: () => 'expected EuiPopover to be open', + }; + } + }, +}); +declare global { + /* eslint-disable @typescript-eslint/no-namespace */ + namespace jest { + interface Matchers { + toBeEuiPopover(): R; + euiPopoverToBeOpen(): R; + } + } +} +function setColumnVisibility( + datagrid: ReactWrapper, + columnId: string, + isVisible: boolean +) { + // open datagrid column options + let popover = datagrid.find( + 'EuiPopover[data-test-subj="dataGridColumnSelectorPopover"]' + ); + expect(popover).not.euiPopoverToBeOpen(); + + let popoverButton = popover + .find('div[className="euiPopover__anchor"]') + .childAt(0); + act(() => popoverButton.props().onClick()); + + datagrid.update(); + + popover = datagrid.find( + 'EuiPopover[data-test-subj="dataGridColumnSelectorPopover"]' + ); + expect(popover).euiPopoverToBeOpen(); + + // toggle column's visibility switch + const portal = popover.find('EuiPortal'); + + const columnSwitch = portal.find(`EuiSwitch[name="${columnId}"]`); + const switchInput = columnSwitch.find('input'); + (switchInput.getDOMNode() as HTMLInputElement).checked = isVisible; + switchInput.simulate('change'); + + // close popover + popover = datagrid.find( + 'EuiPopover[data-test-subj="dataGridColumnSelectorPopover"]' + ); + expect(popover).euiPopoverToBeOpen(); + + popoverButton = popover + .find('div[className="euiPopover__anchor"]') + .childAt(0); + act(() => popoverButton.props().onClick()); + + datagrid.update(); + + popover = datagrid.find( + 'EuiPopover[data-test-subj="dataGridColumnSelectorPopover"]' + ); + expect(popover).not.euiPopoverToBeOpen(); +} + +function moveColumnToIndex( + datagrid: ReactWrapper, + columnId: string, + nextIndex: number +) { + // open datagrid column options + let popover = datagrid.find( + 'EuiPopover[data-test-subj="dataGridColumnSelectorPopover"]' + ); + expect(popover).not.euiPopoverToBeOpen(); + + let popoverButton = popover + .find('div[className="euiPopover__anchor"]') + .childAt(0); + act(() => popoverButton.props().onClick()); + + datagrid.update(); + + popover = datagrid.find( + 'EuiPopover[data-test-subj="dataGridColumnSelectorPopover"]' + ); + expect(popover).euiPopoverToBeOpen(); + + const [initialColumnOrder] = extractGridData(datagrid); + const initialColumnIndex = initialColumnOrder.indexOf(columnId); + + // "drag" column into new location + const portal = popover.find('EuiPortal'); + act(() => + portal.find('EuiDragDropContext').props().onDragEnd!({ + // @ts-ignore-next-line - only `index` is used from `source`, don't need to mock rest of the event + source: { index: initialColumnIndex }, + destination: { index: nextIndex }, + }) + ); + + datagrid.update(); + + // close popover + popover = datagrid.find( + 'EuiPopover[data-test-subj="dataGridColumnSelectorPopover"]' + ); + expect(popover).euiPopoverToBeOpen(); + + popoverButton = popover + .find('div[className="euiPopover__anchor"]') + .childAt(0); + act(() => popoverButton.props().onClick()); + + datagrid.update(); + + popover = datagrid.find( + 'EuiPopover[data-test-subj="dataGridColumnSelectorPopover"]' + ); + expect(popover).not.euiPopoverToBeOpen(); } describe('EuiDataGrid', () => { @@ -44,10 +218,10 @@ describe('EuiDataGrid', () => { const component = render( - `${rowIndex}, ${columnName}` + renderCellValue={({ rowIndex, columnId }) => + `${rowIndex}, ${columnId}` } /> ); @@ -61,10 +235,10 @@ describe('EuiDataGrid', () => { const component = mount( { - const [value] = useState(`Hello, Row ${rowIndex}-${columnName}!`); + renderCellValue={({ rowIndex, columnId }) => { + const [value] = useState(`Hello, Row ${rowIndex}-${columnId}!`); return {value}; }} /> @@ -93,7 +267,7 @@ Array [ const component = mount( rowIndex} pagination={{ @@ -116,7 +290,7 @@ Array [ const component = mount( rowIndex} pagination={{ @@ -173,7 +347,7 @@ Array [ const component = mount( rowIndex} pagination={{ @@ -233,7 +407,7 @@ Array [ const component = mount( rowIndex} pagination={{ @@ -296,29 +470,25 @@ Array [ const component = mount( 'value'} /> ); const originalCellWidths = extractColumnWidths(component); - expect(originalCellWidths).toEqual(['100px', '100px']); - - const firstResizer = component - .find('EuiDataGridColumnResizer') - .first() - .instance() as EuiDataGridColumnResizer; - firstResizer.onMouseDown({ pageX: 100 }); - firstResizer.onMouseMove({ pageX: 113 }); - firstResizer.onMouseMove({ pageX: 136 }); - firstResizer.onMouseMove({ pageX: 150 }); - firstResizer.onMouseUp(); + expect(originalCellWidths).toEqual({ + 'Column 1': 100, + 'Column 2': 100, + }); - component.update(); + resizeColumn(component, 'Column 1', 150); const updatedCellWidths = extractColumnWidths(component); - expect(updatedCellWidths).toEqual(['150px', '100px']); + expect(updatedCellWidths).toEqual({ + 'Column 1': 150, + 'Column 2': 100, + }); }); it('does not trigger value re-renders', () => { @@ -327,7 +497,7 @@ Array [ const component = mount( @@ -336,24 +506,82 @@ Array [ expect(renderCellValue).toHaveBeenCalledTimes(3); renderCellValue.mockClear(); - (component.instance() as EuiDataGrid).setColumnWidth('ColumnA', 200); - - component.update(); + resizeColumn(component, 'ColumnA', 200); - expect(extractColumnWidths(component)).toEqual(['200px']); + expect(extractColumnWidths(component)).toEqual({ ColumnA: 200 }); expect(renderCellValue).toHaveBeenCalledTimes(0); }); }); + describe('column options', () => { + it('column visibility can be toggled', () => { + const component = mount( + + `${rowIndex}-${columnId}` + } + /> + ); + + expect(extractGridData(component)).toEqual([ + ['ColumnA', 'ColumnB'], + ['0-ColumnA', '0-ColumnB'], + ['1-ColumnA', '1-ColumnB'], + ]); + + setColumnVisibility(component, 'ColumnA', false); + expect(extractGridData(component)).toEqual([ + ['ColumnB'], + ['0-ColumnB'], + ['1-ColumnB'], + ]); + + setColumnVisibility(component, 'ColumnA', true); + expect(extractGridData(component)).toEqual([ + ['ColumnA', 'ColumnB'], + ['0-ColumnA', '0-ColumnB'], + ['1-ColumnA', '1-ColumnB'], + ]); + }); + + it('column order can be changed', () => { + const component = mount( + + `${rowIndex}-${columnId}` + } + /> + ); + + expect(extractGridData(component)).toEqual([ + ['ColumnA', 'ColumnB'], + ['0-ColumnA', '0-ColumnB'], + ['1-ColumnA', '1-ColumnB'], + ]); + + moveColumnToIndex(component, 'ColumnB', 0); + + expect(extractGridData(component)).toEqual([ + ['ColumnB', 'ColumnA'], + ['0-ColumnB', '0-ColumnA'], + ['1-ColumnB', '1-ColumnA'], + ]); + }); + }); + describe('keyboard controls', () => { const component = mount( - `${rowIndex}, ${columnName}` - } + renderCellValue={({ rowIndex, columnId }) => `${rowIndex}, ${columnId}`} /> ); diff --git a/src/components/datagrid/data_grid.tsx b/src/components/datagrid/data_grid.tsx index 092cb8fca79..d0acbc557e3 100644 --- a/src/components/datagrid/data_grid.tsx +++ b/src/components/datagrid/data_grid.tsx @@ -1,4 +1,14 @@ -import React, { Component, HTMLAttributes, KeyboardEvent } from 'react'; +import React, { + FunctionComponent, + HTMLAttributes, + KeyboardEvent, + useCallback, + useState, + useRef, + useEffect, + Fragment, +} from 'react'; +import classNames from 'classnames'; import { EuiDataGridHeaderRow } from './data_grid_header_row'; import { CommonProps, Omit } from '../common'; import { @@ -7,13 +17,12 @@ import { EuiDataGridPaginationProps, } from './data_grid_types'; import { EuiDataGridCellProps } from './data_grid_cell'; -import classNames from 'classnames'; import { keyCodes } from '../../services'; - -// @ts-ignore-next-line -import { EuiTablePagination } from '../table/table_pagination'; import { EuiSpacer } from '../spacer'; import { EuiDataGridBody } from './data_grid_body'; +import { useColumnSelector } from './column_selector'; +// @ts-ignore-next-line +import { EuiTablePagination } from '../table/table_pagination'; // Types for styling options, passed down through the `gridStyle` prop type EuiDataGridStyleFontSizes = 's' | 'm' | 'l'; @@ -45,11 +54,6 @@ type CommonGridProps = CommonProps & type EuiDataGridProps = Omit & ({ 'aria-label': string } | { 'aria-labelledby': string }); -interface EuiDataGridState { - columnWidths: EuiDataGridColumnWidths; - focusedCell: [number, number]; -} - // Each gridStyle object above sets a specific CSS select to .euiGrid const fontSizesToClassMap: { [size in EuiDataGridStyleFontSizes]: string } = { s: 'euiDataGrid--fontSizeSmall', @@ -84,158 +88,178 @@ const cellPaddingsToClassMap: { }; const ORIGIN: [number, number] = [0, 0]; -export class EuiDataGrid extends Component { - state = { - columnWidths: {}, - focusedCell: ORIGIN, - }; +function computeVisibleRows(props: EuiDataGridProps) { + const { pagination, rowCount } = props; - computeVisibleRows = () => { - const { pagination, rowCount } = this.props; + const startRow = pagination ? pagination.pageIndex * pagination.pageSize : 0; + let endRow = pagination + ? (pagination.pageIndex + 1) * pagination.pageSize + : rowCount; + endRow = Math.min(endRow, rowCount); - const startRow = pagination - ? pagination.pageIndex * pagination.pageSize - : 0; - let endRow = pagination - ? (pagination.pageIndex + 1) * pagination.pageSize - : rowCount; - endRow = Math.min(endRow, rowCount); + return endRow - startRow; +} - return endRow - startRow; - }; +function renderPagination(props: EuiDataGridProps) { + const { pagination } = props; + + if (pagination == null) { + return null; + } - setColumnWidth = (columnName: string, width: number) => { - this.setState(({ columnWidths }) => ({ - columnWidths: { ...columnWidths, [columnName]: width }, - })); + const { + pageIndex, + pageSize, + pageSizeOptions, + onChangePage, + onChangeItemsPerPage, + } = pagination; + const pageCount = Math.ceil(props.rowCount / pageSize); + + return ( + + ); +} + +export const EuiDataGrid: FunctionComponent = props => { + const [columnWidths, setColumnWidths] = useState({}); + const setColumnWidth = (columnId: string, width: number) => { + setColumnWidths({ ...columnWidths, [columnId]: width }); }; - handleKeyDown = (e: KeyboardEvent) => { - const colCount = this.props.columns.length - 1; - const [x, y] = this.state.focusedCell; - const rowCount = this.computeVisibleRows(); + const gridRef = useRef(null); + + useEffect(() => { + if (gridRef.current != null) { + const gridWidth = Math.max( + gridRef.current!.clientWidth / props.columns.length, + 100 + ); + const columnWidths = props.columns.reduce( + (columnWidths: EuiDataGridColumnWidths, column) => { + columnWidths[column.id] = gridWidth; + return columnWidths; + }, + {} + ); + setColumnWidths(columnWidths); + } + }, []); + + const [focusedCell, setFocusedCell] = useState<[number, number]>(ORIGIN); + const onCellFocus = useCallback( + (x: number, y: number) => { + setFocusedCell([x, y]); + }, + [setFocusedCell] + ); + + const handleKeyDown = (e: KeyboardEvent) => { + const colCount = props.columns.length - 1; + const [x, y] = focusedCell; + const rowCount = computeVisibleRows(props); switch (e.keyCode) { case keyCodes.DOWN: e.preventDefault(); if (y < rowCount) { - this.setState({ focusedCell: [x, y + 1] }); + setFocusedCell([x, y + 1]); } break; case keyCodes.LEFT: e.preventDefault(); if (x > 0) { - this.setState({ focusedCell: [x - 1, y] }); + setFocusedCell([x - 1, y]); } break; case keyCodes.UP: e.preventDefault(); // TODO sort out when a user can arrow up into the column headers if (y > 0) { - this.setState({ focusedCell: [x, y - 1] }); + setFocusedCell([x, y - 1]); } break; case keyCodes.RIGHT: e.preventDefault(); if (x < colCount) { - this.setState({ focusedCell: [x + 1, y] }); + setFocusedCell([x + 1, y]); } break; } }; - onCellFocus = (x: number, y: number) => { - this.setState({ focusedCell: [x, y] }); - }; + const { + columns, + rowCount, + renderCellValue, + className, + gridStyle = {}, + pagination, + ...rest + } = props; - renderPagination() { - const { pagination } = this.props; + const fontSize = gridStyle.fontSize || 'm'; + const border = gridStyle.border || 'all'; + const header = gridStyle.header || 'shade'; + const rowHover = gridStyle.rowHover || 'highlight'; + const stripes = gridStyle.stripes ? true : false; + const cellPadding = gridStyle.cellPadding || 'm'; - if (pagination == null) { - return null; - } + const classes = classNames( + 'euiDataGrid', + fontSizesToClassMap[fontSize], + bordersToClassMap[border], + headerToClassMap[header], + rowHoverToClassMap[rowHover], + cellPaddingsToClassMap[cellPadding], + { + 'euiDataGrid--stripes': stripes, + }, + className + ); - const { - pageIndex, - pageSize, - pageSizeOptions, - onChangePage, - onChangeItemsPerPage, - } = pagination; - const pageCount = Math.ceil(this.props.rowCount / pageSize); - - return ( - - ); - } + const [ColumnSelector, visibleColumns] = useColumnSelector(columns); - render() { - const { columnWidths, focusedCell } = this.state; - const { - columns, - rowCount, - renderCellValue, - className, - gridStyle = {}, - pagination, - ...rest - } = this.props; - - const fontSize = gridStyle.fontSize || 'm'; - const border = gridStyle.border || 'all'; - const header = gridStyle.header || 'shade'; - const rowHover = gridStyle.rowHover || 'highlight'; - const stripes = gridStyle.stripes ? true : false; - const cellPadding = gridStyle.cellPadding || 'm'; - - const classes = classNames( - 'euiDataGrid', - fontSizesToClassMap[fontSize], - bordersToClassMap[border], - headerToClassMap[header], - rowHoverToClassMap[rowHover], - cellPaddingsToClassMap[cellPadding], - { - 'euiDataGrid--stripes': stripes, - }, - className - ); - - return ( - // Unsure why this element causes errors as focus follows spec - // eslint-disable-next-line jsx-a11y/interactive-supports-focus + return ( + +
+ +
+ {/* Unsure why this element causes errors as focus follows spec */} + {/* eslint-disable-next-line jsx-a11y/interactive-supports-focus */}
- {this.renderPagination()} + {renderPagination(props)}
- ); - } -} +
+ ); +}; diff --git a/src/components/datagrid/data_grid_cell.tsx b/src/components/datagrid/data_grid_cell.tsx index 406c8b96c75..ee929840eb4 100644 --- a/src/components/datagrid/data_grid_cell.tsx +++ b/src/components/datagrid/data_grid_cell.tsx @@ -10,14 +10,14 @@ import { Omit } from '../common'; interface CellValueElementProps { rowIndex: number; - columnName: string; + columnId: string; } export interface EuiDataGridCellProps { rowIndex: number; colIndex: number; - columnName: string; - width: number; + columnId: string; + width?: number; isFocusable: boolean; onCellFocus: Function; renderCellValue: @@ -27,7 +27,10 @@ export interface EuiDataGridCellProps { interface EuiDataGridCellState {} -type EuiDataGridCellValueProps = Omit; +type EuiDataGridCellValueProps = Omit< + EuiDataGridCellProps, + 'width' | 'isFocusable' +>; const EuiDataGridCellContent: FunctionComponent< EuiDataGridCellValueProps @@ -59,8 +62,8 @@ export class EuiDataGridCell extends Component< } render() { - const { width, ...rest } = this.props; - const { colIndex, rowIndex, onCellFocus, isFocusable } = rest; + const { width, isFocusable, ...rest } = this.props; + const { colIndex, rowIndex, onCellFocus } = rest; return (
void; + setColumnWidth: (columnId: string, width: number) => void; } interface EuiDataGridColumnResizerState { @@ -34,9 +34,9 @@ export class EuiDataGridColumnResizer extends Component< onMouseUp = () => { const { offset } = this.state; - const { columnName, columnWidth, setColumnWidth } = this.props; + const { columnId, columnWidth, setColumnWidth } = this.props; setColumnWidth( - columnName, + columnId, Math.max(MINIMUM_COLUMN_WIDTH, columnWidth + offset) ); diff --git a/src/components/datagrid/data_grid_data_row.tsx b/src/components/datagrid/data_grid_data_row.tsx index 81b58af7c67..121208dd88a 100644 --- a/src/components/datagrid/data_grid_data_row.tsx +++ b/src/components/datagrid/data_grid_data_row.tsx @@ -3,7 +3,6 @@ import classnames from 'classnames'; import { EuiDataGridColumn, EuiDataGridColumnWidths } from './data_grid_types'; import { CommonProps } from '../common'; -import { DEFAULT_COLUMN_WIDTH } from './data_grid_header_row'; import { EuiDataGridCell, EuiDataGridCellProps } from './data_grid_cell'; export type EuiDataGridDataRowProps = CommonProps & @@ -37,20 +36,18 @@ const EuiDataGridDataRow: FunctionComponent< return (
{columns.map((props, i) => { - const { name } = props; + const { id } = props; - const width = columnWidths.hasOwnProperty(name) - ? columnWidths[name] - : DEFAULT_COLUMN_WIDTH; + const width = columnWidths[id]; const isFocusable = focusedCell[0] === i && focusedCell[1] === rowIndex; return ( & { columns: EuiDataGridColumn[]; columnWidths: EuiDataGridColumnWidths; - setColumnWidth: (columnName: string, width: number) => void; + setColumnWidth: (columnId: string, width: number) => void; }; const EuiDataGridHeaderRow: FunctionComponent< @@ -31,25 +29,26 @@ const EuiDataGridHeaderRow: FunctionComponent< return (
{columns.map(props => { - const { name } = props; + const { id } = props; - const width = columnWidths.hasOwnProperty(name) - ? columnWidths[name] - : DEFAULT_COLUMN_WIDTH; + const width = columnWidths[id]; return (
- -
{name}
+ {width ? ( + + ) : null} + +
{id}
); })} diff --git a/src/components/datagrid/data_grid_types.ts b/src/components/datagrid/data_grid_types.ts index 5e4e4ac4fd4..a332e02ebfd 100644 --- a/src/components/datagrid/data_grid_types.ts +++ b/src/components/datagrid/data_grid_types.ts @@ -1,5 +1,5 @@ export interface EuiDataGridColumn { - name: string; + id: string; } export interface EuiDataGridColumnWidths { diff --git a/src/components/form/switch/_switch.scss b/src/components/form/switch/_switch.scss index cea8270aa94..48522eb090e 100644 --- a/src/components/form/switch/_switch.scss +++ b/src/components/form/switch/_switch.scss @@ -137,4 +137,44 @@ } } } + + &.euiSwitch--compressed { + min-height: $euiSwitchHeight * .5; + + .euiSwitch__label { + line-height: $euiSwitchHeight * .5; + font-size: $euiFontSizeXS; + } + + .euiSwitch__body { + pointer-events: none; + width: $euiSwitchWidth * .5; + height: $euiSwitchHeight * .5; + border-radius: $euiSwitchHeight * .5; + } + + .euiSwitch__thumb { + @include euiCustomControl($type: 'round', $size: ($euiSwitchThumbSize * .5) - 2px); + + left: ($euiSwitchWidth * .5) - (($euiSwitchThumbSize * .5) - 2px) - 1px; + top: 1px; + border-color: $euiColorPrimary; + transition: border-color $euiAnimSpeedNormal $euiAnimSlightBounce, background-color $euiAnimSpeedNormal $euiAnimSlightBounce, left $euiAnimSpeedNormal $euiAnimSlightBounce, transform $euiAnimSpeedNormal $euiAnimSlightBounce; + } + + .euiSwitch__track { + border-radius: $euiSwitchHeight * .5; + } + + .euiSwitch__icon { + display: none; + } + + .euiSwitch__input:not(:checked) ~ .euiSwitch__body { + .euiSwitch__thumb { + left: 1px; + border-color: $euiColorMediumShade; + } + } + } } diff --git a/src/components/popover/_popover_footer.scss b/src/components/popover/_popover_footer.scss index 5d3fa13b62a..11f3cd2e290 100644 --- a/src/components/popover/_popover_footer.scss +++ b/src/components/popover/_popover_footer.scss @@ -9,7 +9,7 @@ @each $modifier, $amount in $euiPanelPaddingModifiers { .euiPopover__panel.euiPanel--#{$modifier} & { - padding: $euiSizeM $amount; + padding: $amount; margin: $amount ($amount * -1) ($amount * -1); } } diff --git a/src/test/find_test_subject.ts b/src/test/find_test_subject.ts index 9c590eeedbb..65baa9b1b75 100644 --- a/src/test/find_test_subject.ts +++ b/src/test/find_test_subject.ts @@ -2,7 +2,8 @@ import { ReactWrapper, ShallowWrapper } from 'enzyme'; type FindTestSubject = ( mountedComponent: T, - testSubjectSelector: string + testSubjectSelector: string, + matcher?: '=' | '~=' | '|=' | '^=' | '$=' | '*=' ) => ReturnType; /**