Skip to content

Commit

Permalink
feat(Table): column pinning support enabled
Browse files Browse the repository at this point in the history
  • Loading branch information
chelentos committed Jul 12, 2024
1 parent c1c47b6 commit 74ab7fe
Show file tree
Hide file tree
Showing 11 changed files with 231 additions and 2 deletions.
8 changes: 7 additions & 1 deletion src/components/Cell/Cell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import React from 'react';
import type {Cell as CellProperties} from '@tanstack/react-table';
import {flexRender} from '@tanstack/react-table';

import {getCommonPinningClassModes, getCommonPinningStyles} from '../../utils/columnPinning';
import {b} from '../Table/Table.classname';

export interface CellProps<TData> {
Expand All @@ -14,11 +15,16 @@ export interface CellProps<TData> {
export const Cell = <TData,>({cell, className, contentClassName}: CellProps<TData>) => {
return (
<td
className={b('cell', {id: cell.column.id}, className)}
className={b(
'cell',
{id: cell.column.id, ...getCommonPinningClassModes(cell.column)},
className,
)}
style={{
width: cell.column.getSize(),
minWidth: cell.column.columnDef.minSize,
maxWidth: cell.column.columnDef.maxSize,
...getCommonPinningStyles(cell.column),
}}
>
<div className={b('cell-content', contentClassName)}>
Expand Down
5 changes: 4 additions & 1 deletion src/components/HeaderCell/HeaderCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import React from 'react';
import type {Header} from '@tanstack/react-table';
import {flexRender} from '@tanstack/react-table';

import {renderDefaultSortIndicator} from '../../utils';
import {getCommonPinningClassModes, getCommonPinningStyles} from '../../utils/columnPinning';
import {renderDefaultSortIndicator} from '../../utils/renderDefaultSortIndicator';
import {b} from '../Table/Table.classname';

export interface HeaderCellProps<TData, TValue> {
Expand Down Expand Up @@ -53,13 +54,15 @@ export const HeaderCell = <TData, TValue>({
placeholder: header.isPlaceholder,
sortable: header.column.getCanSort(),
wide: header.colSpan > 1,
...getCommonPinningClassModes(header.column),
},
className,
)}
style={{
width: header.getSize(),
minWidth: header.column.columnDef.minSize,
maxWidth: header.column.columnDef.maxSize,
...getCommonPinningStyles(header.column),
}}
colSpan={header.colSpan}
rowSpan={rowSpan}
Expand Down
2 changes: 2 additions & 0 deletions src/components/Table/Table.scss
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ $block: '.#{variables.$ns}table';
height: inherit;

text-align: start;

background: var(--g-color-base-background);
}

&__cell {
Expand Down
3 changes: 3 additions & 0 deletions src/components/__stories__/ColumnPinningDemo.classname.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import {block} from '../../utils';

export const columnPinningDemoBlock = block('column-pinning-demo');
56 changes: 56 additions & 0 deletions src/components/__stories__/ColumnPinningDemo.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
@use '../variables';

$block: '.#{variables.$ns}column-pinning-demo';

#{$block} {
--default-border: 1px solid var(--g-color-line-generic);

width: 600px;
overflow: auto;

border-inline: var(--default-border);
border-block-end: var(--default-border);

&__table {
th,
td {
border: var(--default-border);

padding: var(--g-spacing-1);
}

.gt-table__header-cell_lastPinnedLeft,
.gt-table__cell_lastPinnedLeft {
border-inline-end: 0;

box-shadow: var(--g-color-line-generic-active) -2px 0px 2px -2px inset;
}

.gt-table__header-cell_firstPinnedRight,
.gt-table__cell_firstPinnedRight {
border-inline-start: 0;

box-shadow: var(--g-color-line-generic-active) 2px 0 2px -2px inset;
}

.gt-table__header-row {
.gt-table__header-cell:first-of-type {
border-inline-start: 0;
}

.gt-table__header-cell:last-of-type {
border-inline-end: 0;
}
}

.gt-table__row {
.gt-table__cell:first-of-type {
border-inline-start: 0;
}

.gt-table__cell:last-of-type {
border-inline-end: 0;
}
}
}
}
35 changes: 35 additions & 0 deletions src/components/__stories__/ColumnPinningDemo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React from 'react';

import type {ColumnPinningState} from '@tanstack/react-table';

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

import {columnPinningDemoBlock} from './ColumnPinningDemo.classname';
import {data} from './constants/data';
import {columns} from './constants/pinning';

import './ColumnPinningDemo.scss';

export const ColumnPinningDemo = () => {
const [columnPinning, setColumnPinning] = React.useState<ColumnPinningState>({
left: [],
right: [],
});

const table = useTable({
columns,
data,
enableColumnPinning: true,
onColumnPinningChange: setColumnPinning,
state: {
columnPinning,
},
});

return (
<div className={columnPinningDemoBlock()}>
<Table className={columnPinningDemoBlock('table')} table={table} />
</div>
);
};
4 changes: 4 additions & 0 deletions src/components/__stories__/Table.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 {ColumnPinningDemo} from './ColumnPinningDemo';
import {DefaultDemo} from './DefaultDemo';
import {GroupingDemo} from './GroupingDemo';
import {GroupingDemo2} from './GroupingDemo2';
Expand Down Expand Up @@ -73,3 +74,6 @@ export const ReorderingWithVirtualization: StoryFn = ReorderingWithVirtualizatio

const ResizingTemplate: StoryFn = () => <ResizingDemo />;
export const Resizing: StoryFn = ResizingTemplate.bind({});

const ColumnPinningTemplate: StoryFn = () => <ColumnPinningDemo />;
export const ColumnPinning: StoryFn = ColumnPinningTemplate.bind({});
63 changes: 63 additions & 0 deletions src/components/__stories__/cells/ColumnPinningCell.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import React from 'react';

import type {HeaderContext} from '@tanstack/react-table';

import type {Item} from '../types';

export interface ColumnPinningHeaderCellProps {
value: string;
info: HeaderContext<Item, unknown>;
}

export const ColumnPinningHeaderCell = ({value, info}: ColumnPinningHeaderCellProps) => {
const handlePinLeft = () => {
if (info.column.getCanPin()) {
info.column.pin('left');
}
};

const handlePinRight = () => {
if (info.column.getCanPin()) {
info.column.pin('right');
}
};

const handleUnpin = () => {
info.column.pin(false);
};

return (
<div
style={{
display: 'flex',
flexDirection: 'column',
rowGap: '10px',
}}
>
<div>{value}</div>
<div style={{display: 'flex', columnGap: '10px'}}>
<button onClick={() => handlePinLeft()}>{`<`}</button>
<button onClick={() => handleUnpin()}>{`x`}</button>
<button onClick={() => handlePinRight()}>{`>`}</button>
</div>
</div>
);
};

export interface ColumnPinningCellProps {
value: string;
}

export const ColumnPinningCell = ({value}: ColumnPinningCellProps) => {
return (
<div
style={{
display: 'flex',
flexDirection: 'column',
rowGap: '10px',
}}
>
<div>{value}</div>
</div>
);
};
27 changes: 27 additions & 0 deletions src/components/__stories__/constants/pinning.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React from 'react';

import type {ColumnDef} from '@tanstack/react-table';

import {ColumnPinningCell, ColumnPinningHeaderCell} from '../cells/ColumnPinningCell';
import type {Item} from '../types';

export const columns: ColumnDef<Item>[] = [
{
accessorKey: 'name',
header: (info) => <ColumnPinningHeaderCell value="Name" info={info} />,
cell: (info) => <ColumnPinningCell value={info.getValue<string>()} />,
minSize: 200,
},
{
accessorKey: 'age',
header: (info) => <ColumnPinningHeaderCell value="Age" info={info} />,
cell: (info) => <ColumnPinningCell value={info.getValue<string>()} />,
minSize: 200,
},
{
accessorKey: 'status',
header: (info) => <ColumnPinningHeaderCell value="Status" info={info} />,
cell: (info) => <ColumnPinningCell value={info.getValue<string>()} />,
minSize: 300,
},
];
1 change: 1 addition & 0 deletions src/hooks/useTable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export interface UseTableOptions<TData> extends PassedTableOptions<TData> {
export const useTable = <TData>(options: UseTableOptions<TData>) => {
const tableOptions: TableOptions<TData> = {
...options,
enableColumnPinning: options.enableColumnPinning ?? false,
enableColumnResizing: options.enableColumnResizing ?? false,
enableExpanding: options.enableExpanding ?? false,
enableGrouping: options.enableGrouping ?? false,
Expand Down
29 changes: 29 additions & 0 deletions src/utils/columnPinning.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import type React from 'react';

import type {NoStrictEntityMods} from '@bem-react/classname';
import type {Column} from '@tanstack/react-table';

export function getCommonPinningStyles<TData>(column: Column<TData>): React.CSSProperties {
const isPinned = column.getIsPinned();

return {
left: isPinned === 'left' ? `${column.getStart('left')}px` : undefined,
right: isPinned === 'right' ? `${column.getAfter('right')}px` : undefined,
position: isPinned ? 'sticky' : 'relative',
width: column.getSize(),
zIndex: isPinned ? 1 : 0,
};
}

export function getCommonPinningClassModes<TData>(column: Column<TData>): NoStrictEntityMods {
const isPinned = column.getIsPinned();
const isLastLeftPinnedColumn = isPinned === 'left' && column.getIsLastColumn('left');
const isFirstRightPinnedColumn = isPinned === 'right' && column.getIsFirstColumn('right');

return {
pinnedLeft: isPinned === 'left',
pinnedRight: isPinned === 'right',
lastPinnedLeft: isLastLeftPinnedColumn,
firstPinnedRight: isFirstRightPinnedColumn,
};
}

0 comments on commit 74ab7fe

Please sign in to comment.