diff --git a/src/data-table/__tests__/data-table-row-height.scenario.js b/src/data-table/__tests__/data-table-row-height.scenario.js index 033521f901..b2a0e1ce22 100644 --- a/src/data-table/__tests__/data-table-row-height.scenario.js +++ b/src/data-table/__tests__/data-table-row-height.scenario.js @@ -23,9 +23,15 @@ const loremIpsum = `"We went upstairs together, the colonel first with the lamp, const columns = [ StringColumn({ title: 'Name', - minWidth: 300, + minWidth: 250, mapDataToValue: (data: RowDataT) => data.Name, }), + StringColumn({ + title: 'Vertically Center', + minWidth: 250, + mapDataToValue: (data: RowDataT) => data.Name, + cellBlockAlign: 'center', + }), StringColumn({ title: 'Long Text', maxWidth: 300, diff --git a/src/data-table/cell-shell.js b/src/data-table/cell-shell.js deleted file mode 100644 index ac8b746e4b..0000000000 --- a/src/data-table/cell-shell.js +++ /dev/null @@ -1,61 +0,0 @@ -/* -Copyright (c) 2018-2020 Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -// @flow - -import * as React from 'react'; - -import {Checkbox} from '../checkbox/index.js'; -import {useStyletron} from '../styles/index.js'; - -type PropsT = {| - children: React.Node, - isMeasured?: boolean, - isSelected?: boolean, - onSelect?: () => void, -|}; - -const CellShell = React.forwardRef((props, ref) => { - const [css, theme] = useStyletron(); - - return ( -
-
- {Boolean(props.onSelect) && ( - - )} - {props.children} -
-
- ); -}); -CellShell.displayName = 'CellShell'; - -export default CellShell; diff --git a/src/data-table/column-anchor.js b/src/data-table/column-anchor.js index 99786b0d27..de350672ea 100644 --- a/src/data-table/column-anchor.js +++ b/src/data-table/column-anchor.js @@ -11,9 +11,9 @@ import * as React from 'react'; import {StyledLink} from '../link/index.js'; import {useStyletron} from '../styles/index.js'; -import CellShell from './cell-shell.js'; +import Column from './column.js'; import {COLUMNS} from './constants.js'; -import type {ColumnT} from './types.js'; +import type {ColumnT, SharedColumnOptionsT} from './types.js'; type ValueT = {content: string, href: string}; @@ -23,13 +23,8 @@ type ReplacementElementAs = React.AbstractComponent<{| |}>; type OptionsT = {| + ...SharedColumnOptionsT, elementAs?: ReplacementElementAs | string, - // eslint-disable-next-line flowtype/no-weak-types - mapDataToValue: (data: any) => ValueT, - maxWidth?: number, - minWidth?: number, - sortable?: boolean, - title: string, |}; type FilterParametersT = {}; @@ -40,54 +35,47 @@ function AnchorFilter(props) { return
not implemented for anchor column
; } -const AnchorCell = React.forwardRef<_, HTMLDivElement>((props, ref) => { +function AnchorCell(props) { const [css] = useStyletron(); return ( - -
- - {props.value.content} - -
-
+ + {props.value.content} + + ); -}); -AnchorCell.displayName = 'AnchorCell'; +} function AnchorColumn(options: OptionsT): AnchorColumnT { - return { + return Column({ kind: COLUMNS.ANCHOR, buildFilter: function(params) { return function(data) { return true; }; }, + cellBlockAlign: options.cellBlockAlign, filterable: false, mapDataToValue: options.mapDataToValue, maxWidth: options.maxWidth, minWidth: options.minWidth, - renderCell: React.forwardRef((props, ref) => { - return ; - }), + renderCell: function RenderAnchorCell(props) { + return ; + }, renderFilter: AnchorFilter, sortable: options.sortable === undefined ? true : options.sortable, sortFn: function(a, b) { return a.content.localeCompare(b.content); }, title: options.title, - }; + }); } export default AnchorColumn; diff --git a/src/data-table/column-boolean.js b/src/data-table/column-boolean.js index 3e7ae1080b..bd8b7916fd 100644 --- a/src/data-table/column-boolean.js +++ b/src/data-table/column-boolean.js @@ -10,20 +10,15 @@ import * as React from 'react'; import {useStyletron} from '../styles/index.js'; -import CellShell from './cell-shell.js'; import {CategoricalFilter} from './column-categorical.js'; +import Column from './column.js'; import {COLUMNS} from './constants.js'; -import type {ColumnT} from './types.js'; +import type {ColumnT, SharedColumnOptionsT} from './types.js'; import {LocaleContext} from '../locale/index.js'; type OptionsT = {| + ...SharedColumnOptionsT, filterable?: boolean, - // eslint-disable-next-line flowtype/no-weak-types - mapDataToValue: (data: any) => boolean, - maxWidth?: number, - minWidth?: number, - sortable?: boolean, - title: string, |}; type FilterParametersT = {| @@ -82,34 +77,26 @@ function BooleanFilter(props) { ); } -const BooleanCell = React.forwardRef<_, HTMLDivElement>((props, ref) => { +function BooleanCell(props) { const [css, theme] = useStyletron(); const locale = React.useContext(LocaleContext); return ( - -
- {props.value - ? locale.datatable.booleanColumnTrueShort - : locale.datatable.booleanColumnFalseShort} -
-
+ {props.value + ? locale.datatable.booleanColumnTrueShort + : locale.datatable.booleanColumnFalseShort} + ); -}); -BooleanCell.displayName = 'BooleanCell'; +} function BooleanColumn(options: OptionsT): BooleanColumnT { - return { + return Column({ kind: COLUMNS.BOOLEAN, buildFilter: function(params) { return function(data) { @@ -117,6 +104,7 @@ function BooleanColumn(options: OptionsT): BooleanColumnT { return params.exclude ? !included : included; }; }, + cellBlockAlign: options.cellBlockAlign, filterable: options.filterable === undefined ? true : options.filterable, mapDataToValue: options.mapDataToValue, maxWidth: options.maxWidth, @@ -129,7 +117,7 @@ function BooleanColumn(options: OptionsT): BooleanColumnT { return a ? -1 : 1; }, title: options.title, - }; + }); } export default BooleanColumn; diff --git a/src/data-table/column-categorical.js b/src/data-table/column-categorical.js index 60e57cc6f6..ed3f267f15 100644 --- a/src/data-table/column-categorical.js +++ b/src/data-table/column-categorical.js @@ -16,21 +16,16 @@ import {Input, SIZE as INPUT_SIZE} from '../input/index.js'; import {useStyletron, withStyle} from '../styles/index.js'; import {Label3} from '../typography/index.js'; -import CellShell from './cell-shell.js'; +import Column from './column.js'; import {COLUMNS} from './constants.js'; -import type {ColumnT} from './types.js'; +import type {ColumnT, SharedColumnOptionsT} from './types.js'; import {LocaleContext} from '../locale/index.js'; import FilterShell from './filter-shell.js'; import {matchesQuery, splitByQuery, HighlightCellText} from './text-search.js'; type OptionsT = {| + ...SharedColumnOptionsT, filterable?: boolean, - // eslint-disable-next-line flowtype/no-weak-types - mapDataToValue: (data: any) => string, - maxWidth?: number, - minWidth?: number, - sortable?: boolean, - title: string, |}; type FilterParametersT = {| @@ -219,36 +214,28 @@ export function CategoricalFilter(props: CategoricalFilterProps) { ); } -const CategoricalCell = React.forwardRef<_, HTMLDivElement>((props, ref) => { +function CategoricalCell(props) { const [css] = useStyletron(); return ( - -
- {props.textQuery ? ( - - ) : ( - props.value - )} -
-
+ {props.textQuery ? ( + + ) : ( + props.value + )} + ); -}); -CategoricalCell.displayName = 'CategoricalCell'; +} function CategoricalColumn(options: OptionsT): CategoricalColumnT { - return { + return Column({ kind: COLUMNS.CATEGORICAL, buildFilter: function(params) { return function(data) { @@ -256,6 +243,7 @@ function CategoricalColumn(options: OptionsT): CategoricalColumnT { return params.exclude ? !included : included; }; }, + cellBlockAlign: options.cellBlockAlign, textQueryFilter: function(textQuery, data) { return data.toLowerCase().includes(textQuery.toLowerCase()); }, @@ -270,7 +258,7 @@ function CategoricalColumn(options: OptionsT): CategoricalColumnT { return a.localeCompare(b); }, title: options.title, - }; + }); } export default CategoricalColumn; diff --git a/src/data-table/column-custom.js b/src/data-table/column-custom.js index 88f0d53ffd..728635bbeb 100644 --- a/src/data-table/column-custom.js +++ b/src/data-table/column-custom.js @@ -8,18 +8,15 @@ LICENSE file in the root directory of this source tree. import * as React from 'react'; -import CellShell from './cell-shell.js'; +import Column from './column.js'; import {COLUMNS} from './constants.js'; -import type {ColumnT} from './types.js'; +import type {ColumnT, SharedColumnOptionsT} from './types.js'; // I could not re-use the ColumnT type to build this.. tried to spread the ColumnT // and define renderFilter, etc. to optional, but required status was maintained. type OptionsT = {| + ...SharedColumnOptionsT, filterable?: boolean, - // eslint-disable-next-line flowtype/no-weak-types - mapDataToValue: (data: any) => ValueT, - maxWidth?: number, - minWidth?: number, renderCell: React.ComponentType<{value: ValueT, isMeasured?: boolean}>, renderFilter?: React.ComponentType<{| close: () => void, @@ -29,43 +26,13 @@ type OptionsT = {| |}>, buildFilter?: FilterParamsT => ValueT => boolean, textQueryFilter?: (string, ValueT) => boolean, - sortable?: boolean, sortFn?: (ValueT, ValueT) => number, - title: string, |}; function CustomColumn( options: OptionsT, ): ColumnT { - return { - kind: COLUMNS.CUSTOM, - buildFilter: options.buildFilter || (() => () => true), - textQueryFilter: options.textQueryFilter, - filterable: - Boolean(options.filterable) && - Boolean(options.renderFilter) && - Boolean(options.buildFilter), - mapDataToValue: options.mapDataToValue, - maxWidth: options.maxWidth, - minWidth: options.minWidth, - renderCell: React.forwardRef((props, ref) => { - const ProvidedCell = options.renderCell; - return ( - - - - ); - }), - renderFilter: options.renderFilter || (() => null), - sortable: Boolean(options.sortable) && Boolean(options.sortFn), - sortFn: options.sortFn || (() => 0), - title: options.title, - }; + return Column({kind: COLUMNS.CUSTOM, ...options}); } export default CustomColumn; diff --git a/src/data-table/column-datetime.js b/src/data-table/column-datetime.js index 903deba709..43a1d0118a 100644 --- a/src/data-table/column-datetime.js +++ b/src/data-table/column-datetime.js @@ -34,22 +34,17 @@ import {TimePicker} from '../timepicker/index.js'; import {useStyletron} from '../styles/index.js'; import {Select, type ValueT} from '../select/index.js'; -import CellShell from './cell-shell.js'; +import Column from './column.js'; import {COLUMNS, DATETIME_OPERATIONS} from './constants.js'; import FilterShell from './filter-shell.js'; -import type {ColumnT} from './types.js'; +import type {ColumnT, SharedColumnOptionsT} from './types.js'; import {LocaleContext} from '../locale/index.js'; type OptionsT = {| + ...SharedColumnOptionsT, filterable?: boolean, formatString?: string, // eslint-disable-next-line flowtype/no-weak-types - mapDataToValue: (data: any) => Date, - maxWidth?: number, - minWidth?: number, - sortable?: boolean, - title: string, - // eslint-disable-next-line flowtype/no-weak-types locale?: any, |}; @@ -583,31 +578,22 @@ function DatetimeFilter(props) { ); } -const DatetimeCell = React.forwardRef<_, HTMLDivElement>((props, ref) => { +function DatetimeCell(props) { const [css, theme] = useStyletron(); - return ( - -
- {format(props.value, props.formatString)} -
-
+ {format(props.value, props.formatString)} + ); -}); -DatetimeCell.displayName = 'DatetimeCell'; +} const defaultOptions = { title: '', @@ -622,7 +608,7 @@ function DatetimeColumn(options: OptionsT): DatetimeColumnT { ...options, }; - return { + return Column({ kind: COLUMNS.DATETIME, buildFilter: function(params) { return function(data) { @@ -670,22 +656,19 @@ function DatetimeColumn(options: OptionsT): DatetimeColumnT { return params.exclude ? !included : included; }; }, + cellBlockAlign: options.cellBlockAlign, filterable: normalizedOptions.filterable, mapDataToValue: options.mapDataToValue, maxWidth: options.maxWidth, minWidth: options.minWidth, - renderCell: React.forwardRef((props, ref) => { + renderCell: function RenderDatetimeCell(props) { return ( ); - }), + }, renderFilter: function RenderDatetimeFilter(props) { return ; }, @@ -693,7 +676,7 @@ function DatetimeColumn(options: OptionsT): DatetimeColumnT { sortFn: sortDates, title: options.title, - }; + }); } export default DatetimeColumn; diff --git a/src/data-table/column-numerical.js b/src/data-table/column-numerical.js index 6f9474f7fa..0d3161e14c 100644 --- a/src/data-table/column-numerical.js +++ b/src/data-table/column-numerical.js @@ -14,10 +14,10 @@ import {Input, SIZE as INPUT_SIZE} from '../input/index.js'; import {useStyletron} from '../styles/index.js'; import {Paragraph4} from '../typography/index.js'; -import CellShell from './cell-shell.js'; +import Column from './column.js'; import {COLUMNS, NUMERICAL_FORMATS, NUMERICAL_OPERATIONS} from './constants.js'; import FilterShell from './filter-shell.js'; -import type {ColumnT} from './types.js'; +import type {ColumnT, SharedColumnOptionsT} from './types.js'; import {LocaleContext} from '../locale/index.js'; type NumericalFormats = @@ -33,16 +33,11 @@ type NumericalOperations = | typeof NUMERICAL_OPERATIONS.LTE; type OptionsT = {| + ...SharedColumnOptionsT, filterable?: boolean, format?: NumericalFormats | ((value: number) => string), highlight?: number => boolean, - // eslint-disable-next-line flowtype/no-weak-types - mapDataToValue: (data: any) => number, - maxWidth?: number, - minWidth?: number, precision?: number, - sortable?: boolean, - title: string, |}; type FilterParametersT = {| @@ -431,35 +426,27 @@ function NumericalFilter(props) { ); } -const NumericalCell = React.forwardRef<_, HTMLDivElement>((props, ref) => { +function NumericalCell(props) { const [css, theme] = useStyletron(); return ( - -
- {format(props.value, { - format: props.format, - precision: props.precision, - })} -
-
+ {format(props.value, { + format: props.format, + precision: props.precision, + })} + ); -}); -NumericalCell.displayName = 'NumericalCell'; +} const defaultOptions = { title: '', @@ -490,7 +477,7 @@ function NumericalColumn(options: OptionsT): NumericalColumnT { normalizedOptions.highlight = (n: number) => (n < 0: boolean); } - return { + return Column({ kind: COLUMNS.NUMERICAL, buildFilter: function(params) { return function(data) { @@ -515,24 +502,21 @@ function NumericalColumn(options: OptionsT): NumericalColumnT { return params.exclude ? !included : included; }; }, + cellBlockAlign: options.cellBlockAlign, filterable: normalizedOptions.filterable, mapDataToValue: options.mapDataToValue, maxWidth: options.maxWidth, minWidth: options.minWidth, - renderCell: React.forwardRef((props, ref) => { + renderCell: function RenderNumericalCell(props) { return ( ); - }), + }, renderFilter: function RenderNumericalFilter(props) { return ; }, @@ -541,7 +525,7 @@ function NumericalColumn(options: OptionsT): NumericalColumnT { return a - b; }, title: normalizedOptions.title, - }; + }); } export default NumericalColumn; diff --git a/src/data-table/column-string.js b/src/data-table/column-string.js index 9720bed613..2b65fc4e64 100644 --- a/src/data-table/column-string.js +++ b/src/data-table/column-string.js @@ -10,19 +10,14 @@ import * as React from 'react'; import {useStyletron} from '../styles/index.js'; -import CellShell from './cell-shell.js'; +import Column from './column.js'; import {COLUMNS} from './constants.js'; import {HighlightCellText} from './text-search.js'; -import type {ColumnT} from './types.js'; +import type {ColumnT, SharedColumnOptionsT} from './types.js'; type OptionsT = {| + ...SharedColumnOptionsT, lineClamp?: number, - // eslint-disable-next-line flowtype/no-weak-types - mapDataToValue: (data: any) => string, - maxWidth?: number, - minWidth?: number, - sortable?: boolean, - title: string, |}; type FilterParametersT = {| @@ -36,37 +31,30 @@ function StringFilter(props) { return
not implemented for string column
; } -const StringCell = React.forwardRef<_, HTMLDivElement>((props, ref) => { +function StringCell(props) { const [css] = useStyletron(); return ( - -
- {props.textQuery ? ( - - ) : ( - props.value - )} -
-
+ {props.textQuery ? ( + + ) : ( + props.value + )} + ); -}); -StringCell.displayName = 'StringCell'; +} function StringColumn(options: OptionsT): StringColumnT { - return { + return Column({ kind: COLUMNS.STRING, + cellBlockAlign: options.cellBlockAlign, buildFilter: function(params) { return function(data) { return true; @@ -79,16 +67,16 @@ function StringColumn(options: OptionsT): StringColumnT { mapDataToValue: options.mapDataToValue, maxWidth: options.maxWidth, minWidth: options.minWidth, - renderCell: React.forwardRef((props, ref) => { - return ; - }), + renderCell: function RenderStringCell(props) { + return ; + }, renderFilter: StringFilter, sortable: options.sortable === undefined ? true : options.sortable, sortFn: function(a, b) { return a.localeCompare(b); }, title: options.title, - }; + }); } export default StringColumn; diff --git a/src/data-table/column.js b/src/data-table/column.js new file mode 100644 index 0000000000..3108ab1e80 --- /dev/null +++ b/src/data-table/column.js @@ -0,0 +1,85 @@ +/* +Copyright (c) 2018-2020 Uber Technologies, Inc. + +This source code is licensed under the MIT license found in the +LICENSE file in the root directory of this source tree. +*/ +// @flow + +import * as React from 'react'; + +import {Checkbox} from '../checkbox/index.js'; +import {useStyletron} from '../styles/index.js'; + +import type {ColumnT} from './types.js'; + +function Column( + options: ColumnT, +): ColumnT { + return { + kind: options.kind, + buildFilter: options.buildFilter || (() => () => true), + textQueryFilter: options.textQueryFilter, + filterable: + Boolean(options.filterable) && + Boolean(options.renderFilter) && + Boolean(options.buildFilter), + mapDataToValue: options.mapDataToValue, + maxWidth: options.maxWidth, + minWidth: options.minWidth, + renderCell: React.forwardRef((props, ref) => { + const [css, theme] = useStyletron(); + const ProvidedCell = options.renderCell; + + let cellBlockAlign = 'flex-start'; + if (options.cellBlockAlign === 'center') { + cellBlockAlign = 'center'; + } else if (options.cellBlockAlign === 'end') { + cellBlockAlign = 'flex-end'; + } + + return ( +
+
+ {Boolean(props.onSelect) && ( + + )} + +
+
+ ); + }), + renderFilter: options.renderFilter || (() => null), + sortable: Boolean(options.sortable) && Boolean(options.sortFn), + sortFn: options.sortFn || (() => 0), + title: options.title, + }; +} + +export default Column; diff --git a/src/data-table/types.js b/src/data-table/types.js index 6cdaba0947..fcd964fec2 100644 --- a/src/data-table/types.js +++ b/src/data-table/types.js @@ -24,14 +24,24 @@ export type ColumnsT = | typeof COLUMNS.NUMERICAL | typeof COLUMNS.STRING; +// These options are available on all column kinds. Most have additional +// unique options depending on the data visualization requirements. +export type SharedColumnOptionsT = {| + cellBlockAlign?: 'start' | 'center' | 'end', + // eslint-disable-next-line flowtype/no-weak-types + mapDataToValue: (data: any) => ValueT, + maxWidth?: number, + minWidth?: number, + sortable?: boolean, + title: string, +|}; + // eslint-disable-next-line flowtype/no-weak-types export type ColumnT = {| + ...SharedColumnOptionsT, kind: ColumnsT, - title: string, sortable: boolean, filterable: boolean, - // eslint-disable-next-line flowtype/no-weak-types - mapDataToValue: (data: any) => ValueT, renderCell: React.AbstractComponent<{ value: ValueT, isMeasured?: boolean, @@ -48,8 +58,6 @@ export type ColumnT = {| buildFilter: FilterParamsT => ValueT => boolean, textQueryFilter?: (string, ValueT) => boolean, sortFn: (ValueT, ValueT) => number, - maxWidth?: number, - minWidth?: number, |}; export type RowT = { diff --git a/vrt/__image_snapshots__/data-table-row-height__dark-snap.png b/vrt/__image_snapshots__/data-table-row-height__dark-snap.png index 3b5cacb0c7..ecc1c97960 100644 Binary files a/vrt/__image_snapshots__/data-table-row-height__dark-snap.png and b/vrt/__image_snapshots__/data-table-row-height__dark-snap.png differ diff --git a/vrt/__image_snapshots__/data-table-row-height__desktop-snap.png b/vrt/__image_snapshots__/data-table-row-height__desktop-snap.png index 5069763d0a..200aebc0ce 100644 Binary files a/vrt/__image_snapshots__/data-table-row-height__desktop-snap.png and b/vrt/__image_snapshots__/data-table-row-height__desktop-snap.png differ diff --git a/vrt/__image_snapshots__/data-table-row-height__mobile-snap.png b/vrt/__image_snapshots__/data-table-row-height__mobile-snap.png index 5ef282febc..616adeb9c9 100644 Binary files a/vrt/__image_snapshots__/data-table-row-height__mobile-snap.png and b/vrt/__image_snapshots__/data-table-row-height__mobile-snap.png differ