Summary: DataTable selected rows are a bit hard to spot when changing search, scrolling etc, so give them a bit more prominent background Changelog: DataTable: more pronounced background color for selections Reviewed By: aigoncharov Differential Revision: D36736495 fbshipit-source-id: 2cab9eb99594ffaaccdd9ebe0d922691adb27bdc
165 lines
4.4 KiB
TypeScript
165 lines
4.4 KiB
TypeScript
/**
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @format
|
|
*/
|
|
|
|
import React, {CSSProperties, memo} from 'react';
|
|
import styled from '@emotion/styled';
|
|
import {theme} from '../theme';
|
|
import {DataTableColumn, TableRowRenderContext} from './DataTable';
|
|
import {Width} from '../../utils/widthUtils';
|
|
import {DataFormatter} from '../DataFormatter';
|
|
import {Dropdown} from 'antd';
|
|
import {contextMenuTrigger} from '../data-inspector/DataInspectorNode';
|
|
import {getValueAtPath} from './DataTableManager';
|
|
|
|
// heuristic for row estimation, should match any future styling updates
|
|
export const DEFAULT_ROW_HEIGHT = 24;
|
|
|
|
type TableBodyRowContainerProps = {
|
|
highlighted?: boolean;
|
|
};
|
|
|
|
const backgroundColor = (props: TableBodyRowContainerProps) => {
|
|
if (props.highlighted) {
|
|
return theme.selectionBackgroundColor;
|
|
}
|
|
return undefined;
|
|
};
|
|
|
|
const CircleMargin = 4;
|
|
const RowContextMenuWrapper = styled.div({
|
|
position: 'absolute',
|
|
top: 0,
|
|
right: 0,
|
|
paddingRight: CircleMargin,
|
|
fontSize: DEFAULT_ROW_HEIGHT - CircleMargin * 2,
|
|
color: theme.primaryColor,
|
|
cursor: 'pointer',
|
|
visibility: 'hidden',
|
|
});
|
|
|
|
const TableBodyRowContainer = styled.div<TableBodyRowContainerProps>(
|
|
(props) => ({
|
|
display: 'flex',
|
|
flexDirection: 'row',
|
|
backgroundColor: backgroundColor(props),
|
|
borderLeft: props.highlighted
|
|
? `4px solid ${theme.primaryColor}`
|
|
: `4px solid transparent`,
|
|
borderBottom: `1px solid ${theme.dividerColor}`,
|
|
paddingTop: 1,
|
|
minHeight: DEFAULT_ROW_HEIGHT,
|
|
lineHeight: `${DEFAULT_ROW_HEIGHT - 2}px`,
|
|
'& .anticon': {
|
|
lineHeight: `${DEFAULT_ROW_HEIGHT - 2}px`,
|
|
},
|
|
overflow: 'hidden',
|
|
width: '100%',
|
|
flexShrink: 0,
|
|
[`&:hover ${RowContextMenuWrapper}`]: {
|
|
visibility: 'visible',
|
|
},
|
|
}),
|
|
);
|
|
TableBodyRowContainer.displayName = 'TableRow:TableBodyRowContainer';
|
|
|
|
const TableBodyColumnContainer = styled.div<{
|
|
width: Width;
|
|
multiline?: boolean;
|
|
justifyContent: 'left' | 'right' | 'center';
|
|
}>((props) => ({
|
|
flexShrink: props.width === undefined ? 1 : 0,
|
|
flexGrow: props.width === undefined ? 1 : 0,
|
|
overflow: 'hidden',
|
|
padding: `0 ${theme.space.small}px`,
|
|
verticalAlign: 'top',
|
|
// pre-wrap preserves explicit newlines and whitespace, and wraps as well when needed
|
|
whiteSpace: props.multiline ? 'pre-wrap' : 'nowrap',
|
|
wordWrap: props.multiline ? 'break-word' : 'normal',
|
|
width: props.width,
|
|
minWidth: 25,
|
|
textAlign: props.justifyContent,
|
|
justifyContent: props.justifyContent,
|
|
'&::selection': {
|
|
color: 'inherit',
|
|
backgroundColor: theme.buttonDefaultBackground,
|
|
},
|
|
'& p': {
|
|
margin: 0,
|
|
},
|
|
}));
|
|
TableBodyColumnContainer.displayName = 'TableRow:TableBodyColumnContainer';
|
|
|
|
type TableRowProps<T> = {
|
|
config: TableRowRenderContext<any>;
|
|
highlighted: boolean;
|
|
record: T;
|
|
itemIndex: number;
|
|
style?: CSSProperties;
|
|
};
|
|
|
|
export const TableRow = memo(function TableRow<T>({
|
|
record,
|
|
itemIndex,
|
|
highlighted,
|
|
config,
|
|
}: TableRowProps<T>) {
|
|
const row = (
|
|
<TableBodyRowContainer
|
|
highlighted={highlighted}
|
|
onMouseDown={(e) => {
|
|
config.onMouseDown(e, record, itemIndex);
|
|
}}
|
|
onMouseEnter={(e) => {
|
|
config.onMouseEnter(e, record, itemIndex);
|
|
}}
|
|
style={config.onRowStyle?.(record)}>
|
|
{config.columns
|
|
.filter((col) => col.visible)
|
|
.map((col) => {
|
|
const value = renderColumnValue<T>(
|
|
col,
|
|
record,
|
|
highlighted,
|
|
itemIndex,
|
|
);
|
|
|
|
return (
|
|
<TableBodyColumnContainer
|
|
key={col.key as string}
|
|
multiline={col.wrap}
|
|
justifyContent={col.align ? col.align : 'left'}
|
|
width={col.width}>
|
|
{value}
|
|
</TableBodyColumnContainer>
|
|
);
|
|
})}
|
|
</TableBodyRowContainer>
|
|
);
|
|
if (config.onContextMenu) {
|
|
return (
|
|
<Dropdown overlay={config.onContextMenu} trigger={contextMenuTrigger}>
|
|
{row}
|
|
</Dropdown>
|
|
);
|
|
} else {
|
|
return row;
|
|
}
|
|
});
|
|
|
|
export function renderColumnValue<T>(
|
|
col: DataTableColumn<any>,
|
|
record: T,
|
|
highlighted: boolean,
|
|
itemIndex: number,
|
|
) {
|
|
return col.onRender
|
|
? col.onRender(record, highlighted, itemIndex)
|
|
: DataFormatter.format(getValueAtPath(record, col.key), col.formatters);
|
|
}
|