Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(BaseTable): additional properties for classNames and attributes customization #36

Merged
merged 2 commits into from
Sep 10, 2024
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
15 changes: 6 additions & 9 deletions src/components/BaseCell/BaseCell.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import React from 'react';

import type {Cell as CellType} from '@tanstack/react-table';
import type {Cell} from '@tanstack/react-table';
import {flexRender} from '@tanstack/react-table';

import {getCellClassModes, getCellStyles} from '../../utils';
import {b} from '../BaseTable/BaseTable.classname';

export interface BaseCellProps<TData>
extends Omit<React.TdHTMLAttributes<HTMLTableCellElement>, 'className'> {
cell?: CellType<TData, unknown>;
className?: string | ((cell?: CellType<TData, unknown>) => string);
cell?: Cell<TData, unknown>;
className?: string | ((cell?: Cell<TData, unknown>) => string);
attributes?:
| React.TdHTMLAttributes<HTMLTableCellElement>
| ((cell?: CellType<TData, unknown>) => React.TdHTMLAttributes<HTMLTableCellElement>);
| ((cell?: Cell<TData, unknown>) => React.TdHTMLAttributes<HTMLTableCellElement>);
}

export const BaseCell = <TData,>({
Expand All @@ -23,18 +23,15 @@ export const BaseCell = <TData,>({
attributes: attributesProp,
...restProps
}: BaseCellProps<TData>) => {
const className = React.useMemo(() => {
return typeof classNameProp === 'function' ? classNameProp(cell) : classNameProp;
}, [cell, classNameProp]);

const attributes = typeof attributesProp === 'function' ? attributesProp(cell) : attributesProp;
const className = typeof classNameProp === 'function' ? classNameProp(cell) : classNameProp;

return (
<td
className={b('cell', getCellClassModes(cell), className)}
style={getCellStyles(cell, style)}
{...restProps}
{...attributes}
style={getCellStyles(cell, {...style, ...attributes?.style})}
>
{cell ? flexRender(cell.column.columnDef.cell, cell.getContext()) : children}
</td>
Expand Down
11 changes: 6 additions & 5 deletions src/components/BaseDragHandle/BaseDragHandle.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,29 @@
import React from 'react';

import {useSortable} from '@dnd-kit/sortable';
import type {Row} from '@tanstack/react-table';
import type {Row, Table} from '@tanstack/react-table';

import {useDraggableRowDepth} from '../../hooks';
import {BaseDraggableRowMarker} from '../BaseDraggableRowMarker';
import {TableContext} from '../TableContext';
import {SortableListContext} from '../SortableListContext';

import {b} from './BaseDragHandle.classname';

import './BaseDragHandle.scss';

export interface BaseDragHandleProps<TData> {
row: Row<TData>;
table: Table<TData>;
}

export const BaseDragHandle = <TData,>({row}: BaseDragHandleProps<TData>) => {
export const BaseDragHandle = <TData,>({row, table}: BaseDragHandleProps<TData>) => {
const {attributes, listeners, isDragging} = useSortable({
id: row.id,
});

const {enableNesting} = React.useContext(TableContext);
const {enableNesting} = React.useContext(SortableListContext) || {};

const {depth} = useDraggableRowDepth({row, isDragging});
const {depth} = useDraggableRowDepth({row, table, isDragging});

return (
<React.Fragment>
Expand Down
17 changes: 9 additions & 8 deletions src/components/BaseDraggableRow/BaseDraggableRow.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import React from 'react';

import {useForkRef} from '@gravity-ui/uikit';
import type {Row as RowType} from '@tanstack/react-table';
import type {Row} from '@tanstack/react-table';

import {useDraggableRowDepth, useDraggableRowStyle} from '../../hooks';
import type {BaseRowProps} from '../BaseRow';
import {BaseRow} from '../BaseRow';
import {SortableListContext} from '../SortableListContext';
import {TableContext} from '../TableContext';

export interface BaseDraggableRowProps<
TData,
Expand All @@ -20,6 +19,7 @@ export const BaseDraggableRow = React.forwardRef(
attributes: attributesProp,
row,
style,
table,
...restProps
}: BaseDraggableRowProps<TData, TScrollElement>,
ref: React.Ref<HTMLTableRowElement>,
Expand All @@ -28,6 +28,7 @@ export const BaseDraggableRow = React.forwardRef(
isChildMode,
activeItemKey,
targetItemIndex = -1,
enableNesting,
// The `useSortable` hook is provided by `@dnd-kit/sortable` library and imported via `SortableListContext`.
// This is a temporary solution to prevent importing the entire `@dnd-kit` library
// when the user doesn't use the reordering feature.
Expand All @@ -43,15 +44,14 @@ export const BaseDraggableRow = React.forwardRef(
id: row.id,
}) || {};

const {enableNesting} = React.useContext(TableContext);

const isDragActive = Boolean(activeItemKey);
const isParent = isChildMode && targetItemIndex === row.index;

const handleRowRef = useForkRef(setNodeRef, ref);

const {isFirstChild, depth} = useDraggableRowDepth<TData>({
row,
table,
isDragging,
});

Expand All @@ -62,11 +62,11 @@ export const BaseDraggableRow = React.forwardRef(
isDragging,
isDragActive,
isFirstChild,
nestingEnabled: enableNesting,
enableNesting,
});

const getDraggableRowDataAttributes = React.useCallback(
(draggableRow: RowType<TData>) => {
const getDraggableRowAttributes = React.useCallback(
(draggableRow: Row<TData>) => {
const attributes =
typeof attributesProp === 'function'
? attributesProp(draggableRow)
Expand All @@ -88,9 +88,10 @@ export const BaseDraggableRow = React.forwardRef(
return (
<BaseRow
ref={handleRowRef}
attributes={getDraggableRowDataAttributes}
attributes={getDraggableRowAttributes}
row={row}
style={draggableStyle}
table={table}
{...restProps}
/>
);
Expand Down
18 changes: 12 additions & 6 deletions src/components/BaseFooterCell/BaseFooterCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,22 @@ import {getCellStyles, getHeaderCellClassModes} from '../../utils';
import {b} from '../BaseTable/BaseTable.classname';

export interface BaseFooterCellProps<TData, TValue> {
className?: string;
header: Header<TData, TValue>;
attributes?:
| React.ThHTMLAttributes<HTMLTableCellElement>
| ((header: Header<TData, TValue>) => React.ThHTMLAttributes<HTMLTableCellElement>);
className?: string | ((header: Header<TData, TValue>) => string);
}

export const BaseFooterCell = <TData, TValue>({
className,
header,
attributes: attributesProp,
className: classNameProp,
}: BaseFooterCellProps<TData, TValue>) => {
if (header.isPlaceholder) {
return null;
}
const attributes =
typeof attributesProp === 'function' ? attributesProp(header) : attributesProp;

const className = typeof classNameProp === 'function' ? classNameProp(header) : classNameProp;

const rowSpan = header.depth - header.column.depth;

Expand All @@ -26,7 +31,8 @@ export const BaseFooterCell = <TData, TValue>({
className={b('footer-cell', getHeaderCellClassModes(header), className)}
colSpan={header.colSpan > 1 ? header.colSpan : undefined}
rowSpan={rowSpan > 1 ? rowSpan : undefined}
style={getCellStyles(header)}
{...attributes}
style={getCellStyles(header, attributes?.style)}
>
{flexRender(header.column.columnDef.footer, header.getContext())}
</th>
Expand Down
46 changes: 30 additions & 16 deletions src/components/BaseFooterRow/BaseFooterRow.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,49 @@
import React from 'react';

import type {HeaderGroup} from '@tanstack/react-table';
import type {Header, HeaderGroup} from '@tanstack/react-table';

import {shouldRenderFooterCell} from '../../utils';
import type {BaseFooterCellProps} from '../BaseFooterCell';
import {BaseFooterCell} from '../BaseFooterCell';
import {b} from '../BaseTable/BaseTable.classname';

export interface BaseFooterRowProps<TData, TValue>
extends React.TdHTMLAttributes<HTMLTableRowElement> {
cellClassName: BaseFooterCellProps<TData, TValue>['className'];
className?: string;
export interface BaseFooterRowProps<TData, TValue = unknown>
extends Omit<React.HTMLAttributes<HTMLTableRowElement>, 'className'> {
footerGroup: HeaderGroup<TData>;
attributes?:
| React.HTMLAttributes<HTMLTableRowElement>
| ((footerGroup: HeaderGroup<TData>) => React.HTMLAttributes<HTMLTableRowElement>);
cellAttributes?: BaseFooterCellProps<TData, TValue>['attributes'];
cellClassName?: BaseFooterCellProps<TData, TValue>['className'];
className?: string | ((footerGroup: HeaderGroup<TData>) => string);
}

export const BaseFooterRow = <TData, TValue>({
cellClassName,
className,
export const BaseFooterRow = <TData, TValue = unknown>({
footerGroup,
attributes: attributesProp,
cellAttributes,
cellClassName,
className: classNameProp,
...restProps
}: BaseFooterRowProps<TData, TValue>) => {
const isEmptyRow = footerGroup.headers.every((header) => !header.column.columnDef.footer);
const attributes =
typeof attributesProp === 'function' ? attributesProp(footerGroup) : attributesProp;

if (isEmptyRow) {
return null;
}
const className =
typeof classNameProp === 'function' ? classNameProp(footerGroup) : classNameProp;

return (
<tr className={b('footer-row', className)} {...restProps}>
{footerGroup.headers.map((header) => (
<BaseFooterCell key={header.column.id} className={cellClassName} header={header} />
))}
<tr className={b('footer-row', className)} {...restProps} {...attributes}>
{footerGroup.headers.map((header) =>
shouldRenderFooterCell(header) ? (
<BaseFooterCell
key={header.column.id}
header={header as Header<TData, TValue>}
attributes={cellAttributes}
className={cellClassName}
/>
) : null,
)}
</tr>
);
};
4 changes: 2 additions & 2 deletions src/components/BaseGroupHeader/BaseGroupHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ import {b} from './BaseGroupHeader.classname';
import './BaseGroupHeader.scss';

export interface BaseGroupHeaderProps<TData> {
row: Row<TData>;
className?: string;
getGroupTitle?: (row: Row<TData>) => React.ReactNode;
row: Row<TData>;
}

export const BaseGroupHeader = <TData,>({
row,
className,
getGroupTitle,
row,
}: BaseGroupHeaderProps<TData>) => {
return (
<h2 className={b(null, className)}>
Expand Down
33 changes: 8 additions & 25 deletions src/components/BaseHeaderCell/BaseHeaderCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@ export interface BaseHeaderCellProps<TData, TValue> {
resizeHandleClassName?: string;
sortIndicatorClassName?: string;
attributes?:
| React.TdHTMLAttributes<HTMLTableCellElement>
| React.ThHTMLAttributes<HTMLTableCellElement>
| ((
header: Header<TData, TValue>,
parentHeader?: Header<TData, unknown>,
) => React.TdHTMLAttributes<HTMLTableCellElement>);
) => React.ThHTMLAttributes<HTMLTableCellElement>);
}

export const BaseHeaderCell = <TData, TValue>({
Expand All @@ -43,43 +43,26 @@ export const BaseHeaderCell = <TData, TValue>({
sortIndicatorClassName,
attributes: attributesProp,
}: BaseHeaderCellProps<TData, TValue>) => {
const className = React.useMemo(() => {
return typeof classNameProp === 'function'
? classNameProp(header, parentHeader)
: classNameProp;
}, [classNameProp, header, parentHeader]);

const isPlaceholderRowSpannedCell =
header.isPlaceholder &&
parentHeader?.isPlaceholder &&
parentHeader.placeholderId === header.placeholderId;

const isLeafRowSpannedCell =
!header.isPlaceholder &&
header.id === header.column.id &&
header.depth - header.column.depth > 1;

if (isPlaceholderRowSpannedCell || isLeafRowSpannedCell) {
return null;
}

const rowSpan = header.isPlaceholder ? header.getLeafHeaders().length : 1;

const attributes =
typeof attributesProp === 'function'
? attributesProp(header, parentHeader)
: attributesProp;

const className =
typeof classNameProp === 'function' ? classNameProp(header, parentHeader) : classNameProp;

const rowSpan = header.isPlaceholder ? header.getLeafHeaders().length : 1;

return (
<th
className={b('header-cell', getHeaderCellClassModes(header), className)}
colSpan={header.colSpan > 1 ? header.colSpan : undefined}
rowSpan={rowSpan > 1 ? rowSpan : undefined}
onClick={header.column.getToggleSortingHandler()}
style={getCellStyles(header)}
aria-sort={getAriaSort(header.column.getIsSorted())}
aria-colindex={getHeaderCellAriaColIndex(header)}
{...attributes}
style={getCellStyles(header, attributes?.style)}
>
{flexRender(header.column.columnDef.header, header.getContext())}{' '}
{header.column.getCanSort() &&
Expand Down
Loading
Loading