Skip to content

Commit

Permalink
feat(HeaderCell): add header groupping possibility
Browse files Browse the repository at this point in the history
  • Loading branch information
kseniyakuzina committed Jul 1, 2024
1 parent a668e31 commit 5f6d837
Show file tree
Hide file tree
Showing 7 changed files with 122 additions and 29 deletions.
74 changes: 46 additions & 28 deletions src/components/HeaderCell/HeaderCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export interface HeaderCellProps<TData, TValue> {
className?: string;
contentClassName?: string;
header: Header<TData, TValue>;
parentHeader?: Header<TData, unknown>;
renderSortIndicator?: (header: Header<TData, TValue>, className?: string) => React.ReactNode;
selection?: boolean;
sortIndicatorClassName?: string;
Expand All @@ -19,6 +20,7 @@ export const HeaderCell = <TData, TValue>({
className,
contentClassName,
header,
parentHeader,
renderSortIndicator = renderDefaultSortIndicator,
selection,
sortIndicatorClassName,
Expand All @@ -27,6 +29,23 @@ export const HeaderCell = <TData, TValue>({
const {columnSizingInfo} = table.getState();
const {columnResizeDirection, columnResizeMode, enableColumnResizing} = table.options;

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

if (!header.isPlaceholder && header.id === header.column.id && columnRelativeDepth > 1) {
return null;
}

let rowSpan = 1;

if (header.isPlaceholder) {
if (parentHeader?.isPlaceholder && parentHeader.placeholderId === header.placeholderId) {
return null;
}

const leafs = header.getLeafHeaders();
rowSpan = leafs.length ?? 1;
}

return (
<th
className={b(
Expand All @@ -35,6 +54,7 @@ export const HeaderCell = <TData, TValue>({
id: header.column.id,
placeholder: header.isPlaceholder,
sortable: header.column.getCanSort(),
wide: header.colSpan > 1,
},
className,
)}
Expand All @@ -44,36 +64,34 @@ export const HeaderCell = <TData, TValue>({
maxWidth: header.column.columnDef.maxSize,
}}
colSpan={header.colSpan}
rowSpan={rowSpan}
onClick={header.column.getToggleSortingHandler()}
>
{header.isPlaceholder ? null : (
<div className={b('header-cell-content', {selection}, contentClassName)}>
{flexRender(header.column.columnDef.header, header.getContext())}{' '}
{header.column.getCanSort() &&
renderSortIndicator(header, sortIndicatorClassName)}
{enableColumnResizing && (
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
<div
className={b('resizer', {
direction: columnResizeDirection,
resizing: header.column.getIsResizing(),
})}
onDoubleClick={() => header.column.resetSize()}
onMouseDown={header.getResizeHandler()}
onTouchStart={header.getResizeHandler()}
style={{
transform:
columnResizeMode === 'onEnd' && header.column.getIsResizing()
? `translateX(${
(columnResizeDirection === 'rtl' ? -1 : 1) *
(columnSizingInfo.deltaOffset ?? 0)
}px)`
: '',
}}
/>
)}
</div>
)}
<div className={b('header-cell-content', {selection}, contentClassName)}>
{flexRender(header.column.columnDef.header, header.getContext())}{' '}
{header.column.getCanSort() && renderSortIndicator(header, sortIndicatorClassName)}
{enableColumnResizing && (
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
<div
className={b('resizer', {
direction: columnResizeDirection,
resizing: header.column.getIsResizing(),
})}
onDoubleClick={() => header.column.resetSize()}
onMouseDown={header.getResizeHandler()}
onTouchStart={header.getResizeHandler()}
style={{
transform:
columnResizeMode === 'onEnd' && header.column.getIsResizing()
? `translateX(${
(columnResizeDirection === 'rtl' ? -1 : 1) *
(columnSizingInfo.deltaOffset ?? 0)
}px)`
: '',
}}
/>
)}
</div>
</th>
);
};
5 changes: 5 additions & 0 deletions src/components/HeaderRow/HeaderRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export interface HeaderRowProps<TData, TValue> {
cellContentClassName: HeaderCellProps<TData, TValue>['contentClassName'];
className?: string;
headerGroup: HeaderGroup<TData>;
parentHeaderGroup?: HeaderGroup<TData>;
renderSortIndicator: HeaderCellProps<TData, TValue>['renderSortIndicator'];
selectionColumnId?: string;
sortIndicatorClassName: HeaderCellProps<TData, TValue>['sortIndicatorClassName'];
Expand All @@ -21,6 +22,7 @@ export const HeaderRow = <TData, TValue>({
cellContentClassName,
className,
headerGroup,
parentHeaderGroup,
renderSortIndicator,
selectionColumnId,
sortIndicatorClassName,
Expand All @@ -33,6 +35,9 @@ export const HeaderRow = <TData, TValue>({
className={cellClassName}
contentClassName={cellContentClassName}
header={header as Header<TData, TValue>}
parentHeader={parentHeaderGroup?.headers.find(
(item) => header.column.id === item.column.id,
)}
renderSortIndicator={renderSortIndicator}
selection={header.column.id === selectionColumnId}
sortIndicatorClassName={sortIndicatorClassName}
Expand Down
6 changes: 6 additions & 0 deletions src/components/Table/Table.scss
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@ $block: '.#{variables.$ns}table';
opacity: 1;
}
}

&_wide {
#{$block}__header-cell-content {
justify-content: center;
}
}
}

&__header-cell-content,
Expand Down
5 changes: 4 additions & 1 deletion src/components/Table/Table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,8 @@ export const Table = React.memo(

const {rows} = table.getRowModel();

const headerGroups = table.getHeaderGroups();

return (
<TableContextProvider
getRowByIndex={getRowByIndex}
Expand All @@ -267,10 +269,11 @@ export const Table = React.memo(
<table className={b(null, className)}>
{withHeader && (
<thead className={b('header', headerClassName)}>
{table.getHeaderGroups().map((headerGroup) => (
{headerGroups.map((headerGroup, index) => (
<HeaderRow
key={headerGroup.id}
headerGroup={headerGroup}
parentHeaderGroup={headerGroups[index - 1]}
className={headerRowClassName}
cellClassName={headerCellClassName}
cellContentClassName={headerCellContentClassName}
Expand Down
48 changes: 48 additions & 0 deletions src/components/__stories__/Grid2.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type {Meta, StoryFn} from '@storybook/react';

import {Table} from '../Table';

import {cnGridDemo} from './GridDemo.classname';
import {GroupingDemo} from './GroupingDemo';
import {GroupingDemo2} from './GroupingDemo2';
import {GroupingWithSelectionDemo} from './GroupingWithSelectionDemo';
Expand All @@ -20,6 +21,8 @@ import {data} from './constants/data';
import type {Item} from './types';
import {generateData} from './utils';

import './GridDemo.scss';

export default {
title: 'Table',
component: Table,
Expand Down Expand Up @@ -48,6 +51,51 @@ WithoutHeader.args = {
onRowClick: undefined,
};

export const HeaderGroups: StoryFn<typeof Table<Item>> = Template.bind({});

HeaderGroups.args = {
data,
columns: [
{
id: 'id',
header: 'ID',
accessorKey: 'id',
},
{
id: 'columns-parent',
header: 'Columns header',
columns: [
{
id: 'columns',
header: 'Columns',
columns,
},
],
},
{
id: 'actions',
header: 'Actions',
accessorKey: 'id',
columns: [
{
id: 'edit',
header: 'Edit',
accessorKey: 'id',
},
{
id: 'delete',
header: 'Delete',
accessorKey: 'id',
},
],
},
],
getRowId: (item: Item) => item.id,
onSelectedChange: undefined,
onRowClick: undefined,
className: cnGridDemo('header-groups-grid'),
};

const WithSelectionTemplate: StoryFn<typeof Table<Item>> = (args) => (
<WithSelectionDemo {...args} />
);
Expand Down
3 changes: 3 additions & 0 deletions src/components/__stories__/GridDemo.classname.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import {cn} from '@bem-react/classname';

export const cnGridDemo = cn('grid-demo');
10 changes: 10 additions & 0 deletions src/components/__stories__/GridDemo.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.grid-demo {
&-header-groups-grid {
border-collapse: collapse;

th,
td {
border: 1px solid var(--g-color-line-generic);
}
}
}

0 comments on commit 5f6d837

Please sign in to comment.