Skip to content
Merged
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
7 changes: 7 additions & 0 deletions src/components/CellWithPopover/CellWithPopover.scss
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
.ydb-cell-with-popover {
display: flex;

max-width: 100%;

&__popover {
display: inline-block;
overflow: hidden;

max-width: 100%;

vertical-align: middle;
white-space: nowrap;
text-overflow: ellipsis;

.yc-popover__handler {
display: inline;
}
}
}
153 changes: 127 additions & 26 deletions src/components/VirtualTable/TableHead.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
import {useState} from 'react';
import {useCallback, useEffect, useMemo, useRef, useState} from 'react';

import type {
HandleTableColumnsResize,
TableColumnsWidthSetup,
} from '../../utils/hooks/useTableResize';

import type {Column, OnSort, SortOrderType, SortParams} from './types';
import {ASCENDING, DEFAULT_SORT_ORDER, DEFAULT_TABLE_ROW_HEIGHT, DESCENDING} from './constants';
import {b} from './shared';

const COLUMN_NAME_HTML_ATTRIBUTE = 'data-columnname';

// Icon similar to original DataTable icons to keep the same tables across diferent pages and tabs
const SortIcon = ({order}: {order?: SortOrderType}) => {
return (
<svg
className={b('icon', {desc: order === DESCENDING})}
className={b('sort-icon', {desc: order === DESCENDING})}
viewBox="0 0 10 6"
width="10"
height="6"
Expand All @@ -27,7 +34,7 @@ interface ColumnSortIconProps {
const ColumnSortIcon = ({sortOrder, sortable, defaultSortOrder}: ColumnSortIconProps) => {
if (sortable) {
return (
<span className={b('sort-icon', {shadow: !sortOrder})}>
<span className={b('sort-icon-container', {shadow: !sortOrder})}>
<SortIcon order={sortOrder || defaultSortOrder} />
</span>
);
Expand All @@ -36,21 +43,129 @@ const ColumnSortIcon = ({sortOrder, sortable, defaultSortOrder}: ColumnSortIconP
}
};

interface TableHeadCellProps<T> {
column: Column<T>;
sortOrder?: SortOrderType;
defaultSortOrder: SortOrderType;
onSort?: (columnName: string) => void;
rowHeight: number;
onCellMount?: (element: Element) => void;
onCellUnMount?: (element: Element) => void;
}

export const TableHeadCell = <T,>({
column,
sortOrder,
defaultSortOrder,
onSort,
rowHeight,
onCellMount,
onCellUnMount,
}: TableHeadCellProps<T>) => {
const cellWrapperRef = useRef<HTMLDivElement>(null);

useEffect(() => {
const cellWrapper = cellWrapperRef.current;
if (cellWrapper) {
onCellMount?.(cellWrapper);
}
return () => {
if (cellWrapper) {
onCellUnMount?.(cellWrapper);
}
};
}, [onCellMount, onCellUnMount]);

const content = column.header ?? column.name;

return (
<th>
<div
ref={cellWrapperRef}
className={b('head-cell-wrapper', {
resizeable: column.resizeable,
})}
style={{
height: `${rowHeight}px`,
width: `${column.width}px`,
}}
{...{
[COLUMN_NAME_HTML_ATTRIBUTE]: column.name,
}}
>
<div
className={b(
'head-cell',
{align: column.align, sortable: column.sortable},
column.className,
)}
onClick={() => {
if (column.sortable) {
onSort?.(column.name);
}
}}
>
<div className={b('head-cell-content')}>{content}</div>
<ColumnSortIcon
sortOrder={sortOrder}
sortable={column.sortable}
defaultSortOrder={defaultSortOrder}
/>
</div>
</div>
</th>
);
};

interface TableHeadProps<T> {
columns: Column<T>[];
onSort?: OnSort;
onColumnsResize?: HandleTableColumnsResize;
defaultSortOrder?: SortOrderType;
rowHeight?: number;
}

export const TableHead = <T,>({
columns,
onSort,
onColumnsResize,
defaultSortOrder = DEFAULT_SORT_ORDER,
rowHeight = DEFAULT_TABLE_ROW_HEIGHT,
}: TableHeadProps<T>) => {
const [sortParams, setSortParams] = useState<SortParams>({});

const isTableResizeable = Boolean(onColumnsResize);

const resizeObserver: ResizeObserver | undefined = useMemo(() => {
if (!isTableResizeable) {
return undefined;
}

return new ResizeObserver((entries) => {
const columnsWidth: TableColumnsWidthSetup = {};
entries.forEach((entry) => {
// @ts-ignore ignore custrom property usage
const id = entry.target.attributes[COLUMN_NAME_HTML_ATTRIBUTE]?.value;
columnsWidth[id] = entry.contentRect.width;
});

onColumnsResize?.(columnsWidth);
});
}, [onColumnsResize, isTableResizeable]);

const handleCellMount = useCallback(
(element: Element) => {
resizeObserver?.observe(element);
},
[resizeObserver],
);
const handleCellUnMount = useCallback(
(element: Element) => {
resizeObserver?.unobserve(element);
},
[resizeObserver],
);

const handleSort = (columnId: string) => {
let newSortParams: SortParams = {};

Expand Down Expand Up @@ -95,34 +210,20 @@ export const TableHead = <T,>({
<thead className={b('head')}>
<tr>
{columns.map((column) => {
const content = column.header ?? column.name;
const sortOrder =
sortParams.columnId === column.name ? sortParams.sortOrder : undefined;

return (
<th
<TableHeadCell
key={column.name}
className={b(
'th',
{align: column.align, sortable: column.sortable},
column.className,
)}
style={{
height: `${rowHeight}px`,
}}
onClick={() => {
handleSort(column.name);
}}
>
<div className={b('head-cell')}>
{content}
<ColumnSortIcon
sortOrder={sortOrder}
sortable={column.sortable}
defaultSortOrder={defaultSortOrder}
/>
</div>
</th>
column={column}
sortOrder={sortOrder}
defaultSortOrder={defaultSortOrder}
onSort={handleSort}
rowHeight={rowHeight}
onCellMount={handleCellMount}
onCellUnMount={handleCellUnMount}
/>
);
})}
</tr>
Expand Down
17 changes: 15 additions & 2 deletions src/components/VirtualTable/TableRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,25 @@ import {b} from './shared';

interface TableCellProps {
height: number;
width: number;
align?: AlignType;
children: ReactNode;
className?: string;
}

const TableRowCell = ({children, className, height, align = DEFAULT_ALIGN}: TableCellProps) => {
const TableRowCell = ({
children,
className,
height,
width,
align = DEFAULT_ALIGN,
}: TableCellProps) => {
// Additional maxWidth to ensure overflow hidden for <td>
return (
<td className={b('td', {align: align}, className)} style={{height: `${height}px`}}>
<td
className={b('row-cell', {align: align}, className)}
style={{height: `${height}px`, width: `${width}px`, maxWidth: `${width}px`}}
>
{children}
</td>
);
Expand All @@ -35,6 +46,7 @@ export const LoadingTableRow = <T,>({index, columns, height}: LoadingTableRowPro
<TableRowCell
key={`${column.name}${index}`}
height={height}
width={column.width}
align={column.align}
className={column.className}
>
Expand Down Expand Up @@ -64,6 +76,7 @@ export const TableRow = <T,>({row, index, columns, getRowClassName, height}: Tab
<TableRowCell
key={`${column.name}${index}`}
height={height}
width={column.width}
align={column.align}
className={column.className}
>
Expand Down
Loading