/** * 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( (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 = { config: TableRowRenderContext; highlighted: boolean; record: T; itemIndex: number; style?: CSSProperties; }; export const TableRow = memo(function TableRow({ record, itemIndex, highlighted, config, }: TableRowProps) { const row = ( { 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( col, record, highlighted, itemIndex, ); return ( {value} ); })} ); if (config.onContextMenu) { return ( {row} ); } else { return row; } }); export function renderColumnValue( col: DataTableColumn, record: T, highlighted: boolean, itemIndex: number, ) { return col.onRender ? col.onRender(record, highlighted, itemIndex) : DataFormatter.format(getValueAtPath(record, col.key), col.formatters); }