Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
*/

export const CELL_PADDING = 5; // px
export const ROW_MIN_HEIGHT = 38; // px, keeps sparse rows (e.g. bulk-select only) from collapsing
export const DEFAULT_COL_MIN_WIDTH = 150; // px
export const DEFAULT_COL_WIDTH = 1; // fraction, similar to CSS unit fr.
export const MORE_ACTIONS_TITLE = 'More';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ const DefaultColumnRenderers = {
},
},
attributes: {
title: {
textAlign: 'left' as const,
},
name: {
textAlign: 'left' as const,
},
description: {
width: 2,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,16 @@ describe('<EntityDataTable />', () => {
await screen.findByText('Entity description');
});

it('should center cell content, except for title cells', async () => {
render(<EntityDataTable {...defaultProps} />);

const statusCell = await screen.findByRole('cell', { name: 'enabled' });
const titleCell = await screen.findByRole('cell', { name: 'Entity title' });

expect(statusCell).toHaveStyle({ textAlign: 'center' });
expect(titleCell).toHaveStyle({ textAlign: 'left' });
});

it('should render custom cell and header renderer', async () => {
render(
<EntityDataTable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,21 @@
import * as React from 'react';
import styled from 'styled-components';

import { ROW_MIN_HEIGHT } from 'components/common/EntityDataTable/Constants';

const Row = styled.tr`
cursor: default;
height: ${ROW_MIN_HEIGHT}px; /* standardizes row height, acts as a minimum in table layout */
`;

const TD = styled.td`
min-width: 50px;
word-break: break-word;
overflow-wrap: break-word;
padding: 4px 5px 2px;

&& {
vertical-align: middle;
}
`;

const Content = styled.div`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import styled, { css } from 'styled-components';
import { Table as BaseTable } from 'components/bootstrap';
import EntityTableOverrideRow from 'components/common/EntityDataTable/EntityTableOverrideRow';
import ExpandedSections from 'components/common/EntityDataTable/ExpandedSections';
import { ACTIONS_COL_ID } from 'components/common/EntityDataTable/Constants';
import { ACTIONS_COL_ID, ROW_MIN_HEIGHT, UTILITY_COLUMNS } from 'components/common/EntityDataTable/Constants';
import type {
EntityBase,
ExpandedSectionRenderers,
Expand All @@ -44,42 +44,71 @@ const StyledTable = styled(BaseTable)(
({ theme }) => css`
table-layout: fixed;
margin-bottom: 0;
height: 100%; // required to be able to use height: 100% in td
height: 100%; /* required to be able to use height: 100% in td */

tbody > tr.active {
background-color: ${theme.colors.table.row.backgroundStriped} !important;
}
`,
);

const Tr = styled.tr`
height: ${ROW_MIN_HEIGHT}px; /* standardizes row height, acts as a minimum in table layout */
`;

const Td = styled.td<{
$colId: string;
$hidePadding: boolean;
$pinningPosition: ColumnPinningPosition;
$showDivider: boolean;
$textAlign: 'left' | 'center' | 'right' | undefined;
$wrapContent: boolean;
}>(
({ $colId, $hidePadding, $pinningPosition }) => css`
word-break: break-word;
({ $colId, $hidePadding, $pinningPosition, $showDivider, $textAlign, $wrapContent, theme }) => css`
opacity: var(${columnOpacityVar($colId)}, 1);
transform: var(${columnTransformVar($colId)}, none);
transition: var(${columnTransition()}, none);
height: 100%; // required to be able to use height: 100% in child elements
height: 100%; /* required to be able to use height: 100% in child elements */
text-align: ${$textAlign ?? 'center'};

&& {
vertical-align: middle; /* center content vertically (horizontal alignment unchanged) */
}

${$showDivider &&
css`
border-right: 1px solid ${theme.colors.table.row.divider};
`}

${$pinningPosition
? css`
position: sticky;
${$pinningPosition === 'left' ? 'left' : 'right'}: 0;
${ScrollShadow('left')}

&::before {
display: var(${displayScrollRightIndicatorVar}, none);
}
`
: ''}

${$hidePadding &&
css`
&& {
padding: 0;
}
`}
${$hidePadding
? css`
&& {
padding: 0;
}
`
: css`
${$wrapContent
? css`
overflow-wrap: break-word;
`
: css`
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
`}
`}
`,
);

Expand All @@ -106,15 +135,23 @@ const Table = <Entity extends EntityBase>({
{rows.map((row) => {
const visibleCells = row.getVisibleCells();
const visibleCellCount = visibleCells.length;
const renderCell = (cell) => {
const renderCell = (cell, index?: number) => {
const columnMeta = cell.column.columnDef.meta as ColumnMetaContext<Entity>;
const nextCell = index === undefined ? undefined : visibleCells[index + 1];
// Column dividers are only drawn between two attribute columns, so the bulk select
// and the (possibly empty) actions column don't produce stray lines.
const showDivider =
!!nextCell && !UTILITY_COLUMNS.has(cell.column.id) && !UTILITY_COLUMNS.has(nextCell.column.id);

return (
<Td
key={cell.id}
$colId={cell.column.id}
$pinningPosition={cell.column.getIsPinned()}
$hidePadding={columnMeta?.hideCellPadding}>
$hidePadding={columnMeta?.hideCellPadding}
$showDivider={showDivider}
$textAlign={columnMeta?.columnRenderer?.textAlign}
$wrapContent={columnMeta?.columnRenderer?.wrapContent}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</Td>
);
Expand All @@ -133,7 +170,7 @@ const Table = <Entity extends EntityBase>({
/>
) : (
<>
<tr className={isRowExpanded(row.id) ? 'active' : null}>{visibleCells.map(renderCell)}</tr>
<Tr className={isRowExpanded(row.id) ? 'active' : null}>{visibleCells.map(renderCell)}</Tr>
<ExpandedSections
key={`expanded-sections-${row.id}`}
expandedSectionRenderers={expandedSectionRenderers}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ const Actions = styled.div<{ $isEvenRow: boolean }>(
$isEvenRow ? theme.colors.table.row.background : theme.colors.table.row.backgroundStriped,
])};
height: 100%;
align-items: flex-start;
align-items: center;
`,
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,26 @@ import ActiveSliceColContext from 'components/common/EntityDataTable/contexts/Ac

import SortIcon from '../SortIcon';

// Column drag-to-reorder and resize controls are hidden until the header is hovered or focused,
// reducing visual clutter. Uses opacity (not display) so the reserved width stays stable for
// useHeaderSectionObserver and the label doesn't shift when the control appears. Devices
// without hover (touch) keep the controls always visible, since they could not reveal them.
const HandleSlot = styled.div<{ $forceVisible?: boolean }>(
({ $forceVisible }) => css`
opacity: ${$forceVisible ? 1 : 0};
transition: opacity 150ms ease-in-out;

th:hover &,
th:focus-within & {
opacity: 1;
}

@media (hover: none) {
opacity: 1;
}
`,
);

export const ThInner = styled.div`
display: flex;
justify-content: space-between;
Expand Down Expand Up @@ -119,13 +139,15 @@ const AttributeHeader = <Entity extends EntityBase>({
<ThInner ref={setNodeRef}>
<LeftCol ref={leftRef}>
{columnMeta?.enableColumnOrdering && (
<DragHandle
ref={setActivatorNodeRef}
index={ctx.header.index}
dragHandleProps={{ ...attributes, ...listeners }}
isDragging={isDragging}
itemTitle={columnLabel}
/>
<HandleSlot $forceVisible={isDragging}>
<DragHandle
ref={setActivatorNodeRef}
index={ctx.header.index}
dragHandleProps={{ ...attributes, ...listeners }}
isDragging={isDragging}
itemTitle={columnLabel}
/>
</HandleSlot>
)}
<HeaderActionsDropdown
label={columnLabel}
Expand All @@ -142,11 +164,13 @@ const AttributeHeader = <Entity extends EntityBase>({
</LeftCol>
<RightCol ref={rightRef}>
{ctx.header.column.getCanResize() && (
<ResizeHandle
onMouseDown={ctx.header.getResizeHandler()}
onTouchStart={ctx.header.getResizeHandler()}
colTitle={columnLabel}
/>
<HandleSlot $forceVisible={ctx.header.column.getIsResizing()}>
<ResizeHandle
onMouseDown={ctx.header.getResizeHandler()}
onTouchStart={ctx.header.getResizeHandler()}
colTitle={columnLabel}
/>
</HandleSlot>
)}
</RightCol>
</ThInner>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,14 @@ export type ColumnSchema = {
export type ColumnRenderer<Entity extends EntityBase, Meta = unknown> = {
renderCell?: (value: unknown, entity: Entity, meta: Meta, additionalInfo?: unknown) => React.ReactNode;
renderHeader?: (title: string) => React.ReactNode;
textAlign?: string;
// Horizontal cell content alignment. Cells are centered by default; title and link cells should be left-aligned.
textAlign?: 'left' | 'center' | 'right';
minWidth?: number; // px
width?: number; // fraction of unassigned table width, similar to CSS unit fr.
// Uses the rendered title width as the fixed width; or provide a px value, if the title width is too small.
staticWidth?: number | 'matchHeader';
// Opt out of the default single-line truncation for cells whose content needs to wrap (e.g. multi-line renderers).
wrapContent?: boolean;
};

export type ColumnRenderersByAttribute<Entity extends EntityBase, Meta = unknown> = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ const CustomColumnRenderers: ColumnRenderers<Event> = {
...getGeneralEventAttributeRenderers<Event>(),
event_definition_id: {
width: 0.3,
textAlign: 'left',
renderCell: (eventDefinitionId: string, _, meta: EventsAdditionalData) => (
<EventDefinitionRenderer meta={meta} eventDefinitionId={eventDefinitionId} />
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,10 +119,12 @@ const columnRenderers: ColumnRenderers<LookupTableEntity, { adapters: AdaptersMa
},
cache_id: {
width: 0.1,
textAlign: 'left',
renderCell: (cache_id: string, _c, meta) => <CacheCol cacheId={cache_id} caches={meta.caches} />,
},
data_adapter_id: {
width: 0.1,
textAlign: 'left',
renderCell: (data_adapter_id: string, _c, meta) => (
<DataAdapterCol dataAdapterId={data_adapter_id} dataAdapters={meta.adapters} />
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,12 @@ const customColumnRenderers = (
title: {
renderCell: (_title: string, stream) => <TitleCell stream={stream} />,
width: 0.5,
wrapContent: true,
},
index_set_title: {
renderCell: (_index_set_title: string, stream) => <IndexSetCell indexSets={indexSets} stream={stream} />,
width: 0.3,
textAlign: 'left',
},
throughput: {
renderCell: (_throughput: string, stream) => <ThroughputCell stream={stream} />,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,22 +30,41 @@ const DefaultLabel = styled(Label)`
vertical-align: inherit;
`;

const Container = styled.div`
display: flex;
flex-direction: column;
justify-content: center;
min-height: 2lh; /* reserve two lines so rows with and without description have the same height */
`;

const TitleRow = styled.div`
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
`;

const StyledText = styled(Text)(
({ theme }) => css`
color: ${theme.colors.text.secondary};
line-height: inherit; /* match the title line, so two lines fill the container exactly */
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
`,
);

const TitleCell = ({ stream }: Props) => (
<>
<Link to={Routes.stream_search(stream.id)}>{stream.title}</Link>
{stream.is_default && (
<DefaultLabel bsStyle="primary" bsSize="xsmall">
Default
</DefaultLabel>
)}
<StyledText>{stream.description}</StyledText>
</>
<Container>
<TitleRow>
<Link to={Routes.stream_search(stream.id)}>{stream.title}</Link>
{stream.is_default && (
<DefaultLabel bsStyle="primary" bsSize="xsmall">
Default
</DefaultLabel>
)}
</TitleRow>
{stream.description && <StyledText>{stream.description}</StyledText>}
</Container>
);

export default TitleCell;
Loading