Skip to content

Commit

Permalink
feat: add columns resize
Browse files Browse the repository at this point in the history
  • Loading branch information
artemmufazalov committed Apr 10, 2024
1 parent be75da5 commit 525a8ff
Show file tree
Hide file tree
Showing 12 changed files with 441 additions and 43 deletions.
45 changes: 25 additions & 20 deletions README.md

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion src/.eslintrc
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
{
"extends": ["@gravity-ui/eslint-config", "@gravity-ui/eslint-config/prettier"]
"extends": ["@gravity-ui/eslint-config", "@gravity-ui/eslint-config/prettier"],
"rules": {
"no-implicit-globals": "off"
}
}
18 changes: 18 additions & 0 deletions src/lib/DataTable.scss
Original file line number Diff line number Diff line change
Expand Up @@ -267,4 +267,22 @@ $cell-border: 1px solid var(--data-table-border-color);
--data-table-color-hover-area: #ffeba0;
--data-table-color-footer-area: var(--data-table-color-base);
}

&__resize-handler {
visibility: hidden;
position: absolute;
right: 0;
top: 0;
cursor: col-resize;
width: 6px;
height: 100%;
background-color: var(--g-color-base-generic);

&_resizing {
visibility: visible;
}
}
&__th:hover > &__resize-handler {
visibility: visible;
}
}
82 changes: 67 additions & 15 deletions src/lib/DataTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,20 @@ import * as React from 'react';
import ReactList from 'react-list';

import './DataTable.scss';
import {ResizeHandler} from './ResizeHandler';
import {ASCENDING, CENTER, DESCENDING, FIXED, INDEX_COLUMN, LEFT, MOVING, RIGHT} from './constants';
import {positionStickySupported} from './featureSupport';
import {HeightObserver} from './height-observer';
import {Column, DataTableProps, HeadPosition, OrderType, Settings, SortedDataItem} from './types';
import {
SlimColumn,
cn,
b,
externalToInternalSortOrder,
getSortOrder,
getSortedData,
internalToExternalSortOrder,
} from './util';

const b = cn('data-table');

const ICON_ASC = (
<svg className={b('icon')} viewBox="0 0 10 6" width="10" height="6">
<path fill="currentColor" d="M0 5h10l-5 -5z" />
Expand Down Expand Up @@ -122,19 +121,29 @@ class TableRow<T> extends React.PureComponent<TableRowProps<T>> {
}

const value = column._getValue(row);

let style = column.customStyle({
row,
index,
name: column.name,
header: false,
footer,
headerData,
});

// Fixed cell width for resizeable columns for proper content wrap
if (column.resizeable) {
style = style
? {...style, width: column.width, maxWidth: column.width}
: {width: column.width, maxWidth: column.width};
}

return (
<td
key={columnIndex}
className={column._className}
title={column._getTitle(row)}
style={column.customStyle({
row,
index,
name: column.name,
header: false,
footer,
headerData,
})}
style={style}
colSpan={colSpans ? colSpans[column.name] : undefined}
rowSpan={rowSpan}
onClick={column._getOnClick({row, index, footer, headerData})}
Expand All @@ -160,6 +169,7 @@ interface TableHeadProps<T> {
displayIndices?: boolean;

onSort?: TableProps<T>['onSort'];
onResize?: TableProps<T>['onResize'];
onColumnsUpdated?: (widths: number[]) => void;
renderedDataRows?: React.ReactNode;

Expand All @@ -169,7 +179,7 @@ interface TableHeadProps<T> {
class TableHead<T> extends React.Component<TableHeadProps<T>> {
_dataRowsRef: HTMLTableSectionElement | null = null;
dataRowsHeightObserver?: HeightObserver;
renderedColumns: HTMLTableHeaderCellElement[] = [];
renderedColumns: HTMLTableCellElement[] = [];

componentDidMount() {
this._calculateColumnsWidth();
Expand Down Expand Up @@ -250,6 +260,15 @@ class TableHead<T> extends React.Component<TableHeadProps<T>> {

const {headerTitle = (typeof header === 'string' && header) || undefined} = column;

let style = column.customStyle?.({header: true, name: column.name});

// Fixed cell width for resizeable columns for proper content wrap
if (column.resizeable) {
style = style
? {...style, width: column.width, maxWidth: column.width}
: {width: column.width, maxWidth: column.width};
}

return (
<th
ref={column.dataColumn ? this._getColumnRef(columnIndex!) : null}

Check warning on line 274 in src/lib/DataTable.tsx

View workflow job for this annotation

GitHub Actions / Verify Files

Forbidden non-null assertion
Expand All @@ -259,16 +278,34 @@ class TableHead<T> extends React.Component<TableHeadProps<T>> {
data-index={index}
colSpan={colSpan}
rowSpan={rowSpan}
style={column.customStyle && column.customStyle({header: true, name: column.name})}
style={style}
onClick={this._getOnSortClick(column)}
>
<div className={b('head-cell')}>
{header}
{<ColumnSortIcon {...column} />}
</div>
{this.renderCellResizeHandler(column)}
</th>
);
};
renderCellResizeHandler(column: HeadCellColumn<T>) {
const {onResize} = this.props;

if (!column.resizeable || !onResize || typeof column.width !== 'number') {
return null;
}

return (
<ResizeHandler
onResize={onResize}
columnId={column.name}
initialWidth={column.width}
minWidth={column.resizeMinWidth}
maxWidth={column.resizeMaxWidth}
/>
);
}
renderHeadLevel = (row: HeadColumnsItem<T>, rowIndex: number) => {
return (
<tr key={rowIndex} className={b('head-row')}>
Expand Down Expand Up @@ -298,6 +335,7 @@ interface StickyHeadProps<T> {
mode: HeadPositionInner;
displayIndices?: Settings['displayIndices'];
onSort?: TableProps<T>['onSort'];
onResize?: TableProps<T>['onResize'];
top: number;

renderedDataRows: React.ReactNode;
Expand Down Expand Up @@ -534,6 +572,7 @@ interface TableProps<T> {
rowKey: (row: T, index: number) => string | number;
startIndex: DataTableProps<T>['startIndex'];
onSort: DataTableView<T>['onSort'];
onResize: DataTableProps<T>['onResize'];
renderEmptyRow: unknown;
nullBeforeNumbers?: boolean;
getColSpansOfRow?: (
Expand Down Expand Up @@ -673,7 +712,7 @@ class Table<T> extends React.PureComponent<TableProps<T>, TableState> {
}

renderHead() {
const {columns, onSort} = this.props;
const {columns, onSort, onResize} = this.props;
const {displayIndices} = this.props.settings;
const rows = this.renderHeaderRows();
return (
Expand All @@ -682,13 +721,14 @@ class Table<T> extends React.PureComponent<TableProps<T>, TableState> {
{...columns}
displayIndices={Boolean(displayIndices)}
onSort={onSort}
onResize={onResize}
onColumnsUpdated={this._onColumnsUpdated}
renderedDataRows={rows}
/>
);
}
renderStickyHead() {
const {columns, onSort} = this.props;
const {columns, onSort, onResize} = this.props;
const {displayIndices, stickyTop, stickyHead} = this.props.settings;
const top =
stickyTop === 'auto' && this._body && this._body.parentNode
Expand All @@ -703,6 +743,7 @@ class Table<T> extends React.PureComponent<TableProps<T>, TableState> {
{...columns}
displayIndices={displayIndices}
onSort={onSort}
onResize={onResize}
renderedDataRows={rows}
onDataRowsHeightChange={this.onMovingHeaderDataRowsHeightChange}
/>
Expand Down Expand Up @@ -916,6 +957,7 @@ class DataTableView<T> extends React.Component<DataTableProps<T>, DataTableViewS
sortable: true,
externalSort: false,
defaultOrder: ASCENDING as OrderType,
defaultResizeable: false,
},
rowKey: (row: any, index: number) =>

Check warning on line 962 in src/lib/DataTable.tsx

View workflow job for this annotation

GitHub Actions / Verify Files

Unexpected any. Specify a different type
Object.prototype.hasOwnProperty.call(row, 'id') ? row.id : index,
Expand Down Expand Up @@ -971,6 +1013,7 @@ class DataTableView<T> extends React.Component<DataTableProps<T>, DataTableViewS
return startIndex + index;
},
sortable: false,
resizeable: false,
width: 20 + Math.ceil(Math.log10(lastIndex)) * 10,
};
}
Expand Down Expand Up @@ -1006,6 +1049,7 @@ class DataTableView<T> extends React.Component<DataTableProps<T>, DataTableViewS
rowClassName,
rowKey,
onRowClick,
onResize,
theme,
renderEmptyRow,
nullBeforeNumbers,
Expand Down Expand Up @@ -1055,6 +1099,7 @@ class DataTableView<T> extends React.Component<DataTableProps<T>, DataTableViewS
)}
footerData={footerData}
onSort={this.onSort}
onResize={onResize}
/>
);
}
Expand All @@ -1079,6 +1124,7 @@ class DataTableView<T> extends React.Component<DataTableProps<T>, DataTableViewS
};

getColumn = (column: Column<T>, columnIndex: number) => {
const {onResize} = this.props;
const {settings} = this.state;
const {defaultOrder} = settings;
const {sortOrder = {}, sortColumns, indexColumn} = this.state;
Expand All @@ -1098,6 +1144,11 @@ class DataTableView<T> extends React.Component<DataTableProps<T>, DataTableViewS
const {sortAccessor, onClick} = column;
const _className = b('td', {align}, column.className);

const resizeable =
(column.resizeable ?? settings.defaultResizeable) &&
typeof column.width === 'number' &&
Boolean(onResize);

const _getValue =
typeof accessor === 'function'
? (row: T) => accessor(row)
Expand Down Expand Up @@ -1137,6 +1188,7 @@ class DataTableView<T> extends React.Component<DataTableProps<T>, DataTableViewS
dataColumn: true,
defaultOrder,
...column,
resizeable,
sortable: sortable && isSortEnabled,
_className,
_getValue,
Expand Down
91 changes: 91 additions & 0 deletions src/lib/ResizeHandler.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import React from 'react';

import {b, calculateColumnWidth, rafThrottle} from './util';

interface ResizeHandlerProps {
columnId: string;
initialWidth: number;
maxWidth?: number;
minWidth?: number;
onResize: (columnId: string, width: number) => void;
}

export function ResizeHandler({
columnId,
initialWidth,
minWidth,
maxWidth,
onResize,
}: ResizeHandlerProps) {
const elementRef = React.useRef<HTMLElement>(null);

const [resizing, setResizing] = React.useState(false);

React.useEffect(() => {
const element = elementRef.current;

if (!element) {
return undefined;
}

let mouseXPosition: number | null = null;
let initialColumnWidth = initialWidth;
let currentColumnWidth = initialWidth;

const onMouseMove = rafThrottle((e: MouseEvent) => {
if (typeof mouseXPosition !== 'number') {
return;
}

const xDiff = e.clientX - mouseXPosition;

const newWidth = calculateColumnWidth(initialColumnWidth + xDiff, minWidth, maxWidth);

if (newWidth === currentColumnWidth) {
return;
}

currentColumnWidth = newWidth;
onResize(columnId, currentColumnWidth);
});

const onMouseUp = (e: MouseEvent) => {
// Prevent sort trigger on resize
e.stopPropagation();

onResize(columnId, currentColumnWidth);
initialColumnWidth = currentColumnWidth;

setResizing(false);

document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', onMouseUp);
};

const onMouseDown = (e: MouseEvent) => {
mouseXPosition = e.clientX;

setResizing(true);

document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', onMouseUp);
};

element.addEventListener('mousedown', onMouseDown);

return () => {
element.removeEventListener('mousedown', onMouseDown);
document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', onMouseUp);
};
}, [columnId, onResize, minWidth, maxWidth]);

return (
<span
ref={elementRef}
className={b('resize-handler', {resizing})}
// Prevent sort trigger on resize
onClick={(e) => e.stopPropagation()}
/>
);
}
1 change: 1 addition & 0 deletions src/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export {INDEX_COLUMN} from './constants';

export * from './types';
export * from './useTableResize';

import DataTable from './DataTable';
export default DataTable;
Loading

0 comments on commit 525a8ff

Please sign in to comment.