From 968b4d780801c49edd81bafb5f49666a21cdd83e Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Tue, 14 Apr 2026 12:18:07 +0200 Subject: [PATCH 1/6] sticky columns initial implementation --- components/table/TableBody/TableBody.tsx | 9 +++++++++ components/table/TableCell/TableCell.tsx | 9 ++++++++- components/table/TableCell/style.scss | 6 ++++++ components/table/TableHeaderCell/TableHeaderCell.tsx | 3 +++ components/table/TableHeaderCell/style.scss | 6 ++++++ components/table/types.ts | 11 +++++++++++ 6 files changed, 43 insertions(+), 1 deletion(-) diff --git a/components/table/TableBody/TableBody.tsx b/components/table/TableBody/TableBody.tsx index 0974eb7..8083aa9 100644 --- a/components/table/TableBody/TableBody.tsx +++ b/components/table/TableBody/TableBody.tsx @@ -107,17 +107,26 @@ export const TableBody = ({ const canRowsSelect = table.options.enableRowSelection; // assign static sizing to extra columns at the start of the row let iterIndex = 0; + let cumulativeStickyOffset = 0; if (canRowsExpand) { colSizes['--col-0-size'] = tableActionColumnSize; + cumulativeStickyOffset += tableActionColumnSize; iterIndex += 1; } if (canRowsSelect) { colSizes[`--col-${iterIndex}-size`] = tableActionColumnSize; + cumulativeStickyOffset += tableActionColumnSize; iterIndex += 1; } for (const header of headers) { + const isSticky = header.column.columnDef.meta?.sticky ?? false; colSizes[`--col-${iterIndex}-size`] = header.column.getSize(); colSizes[`--col-${header.column.id}-size`] = header.column.getSize(); + // Calculate sticky offset for this column + if (isSticky) { + colSizes[`--col-${header.column.id}-sticky-left-offset`] = cumulativeStickyOffset; + cumulativeStickyOffset += header.column.getSize(); + } iterIndex += 1; } return colSizes; diff --git a/components/table/TableCell/TableCell.tsx b/components/table/TableCell/TableCell.tsx index a7ad69c..874ed5c 100644 --- a/components/table/TableCell/TableCell.tsx +++ b/components/table/TableCell/TableCell.tsx @@ -21,6 +21,8 @@ export const TableCell = ({ }: TableCellProps) => { const cell = useContext(TableCellContext); + const isSticky = cell?.column.columnDef.meta?.sticky ?? false; + const style = useMemo((): CSSProperties => { const res: CSSProperties = {}; if (outsideStyle?.width) return res; @@ -35,15 +37,20 @@ export const TableCell = ({ } if (hasId) { res.width = `calc(var(--col-${id}-size) * 1px)`; + // Set sticky offset if this is a sticky column + if (isSticky) { + res.left = `calc(var(--col-${id}-sticky-left-offset) * 1px)`; + } } return res; - }, [columnId, empty, flex, outsideStyle?.width, cell]); + }, [columnId, empty, flex, outsideStyle?.width, cell, isSticky]); return (
span { font: var(--t-body-sm-400); color: var(--fg-default); diff --git a/components/table/TableHeaderCell/TableHeaderCell.tsx b/components/table/TableHeaderCell/TableHeaderCell.tsx index 238e554..495b618 100644 --- a/components/table/TableHeaderCell/TableHeaderCell.tsx +++ b/components/table/TableHeaderCell/TableHeaderCell.tsx @@ -42,6 +42,8 @@ export const TableHeaderCell = ({ header }: Props) isPresent(filterOptions) && isPresent(filterMessages); + const isSticky = header.column.columnDef.meta?.sticky ?? false; + const headerSorting = header.column.getIsSorted(); const isEmpty = @@ -109,6 +111,7 @@ export const TableHeaderCell = ({ header }: Props) clickable: isSortable, filterable: isFilterable, resizable, + sticky: isSticky, })} onClick={() => { if (suppressSortOnNextClickRef.current) { diff --git a/components/table/TableHeaderCell/style.scss b/components/table/TableHeaderCell/style.scss index d71cf49..0f627c7 100644 --- a/components/table/TableHeaderCell/style.scss +++ b/components/table/TableHeaderCell/style.scss @@ -12,6 +12,12 @@ } } + &.sticky { + position: sticky; + z-index: 3; + background-color: var(--bg-disabled); + } + .resize-bar { position: absolute; right: -6px; diff --git a/components/table/types.ts b/components/table/types.ts index 12bbade..4bf145f 100644 --- a/components/table/types.ts +++ b/components/table/types.ts @@ -1,6 +1,17 @@ +import type { SelectionOption } from '../../../components/SelectionSection/type'; + export type TableFilterMessages = { searchPlaceholder: string; clearButton: string; applyButton: string; emptyState: string; }; + +// Extend TanStack Table's ColumnMeta with custom fields +declare module '@tanstack/react-table' { + interface ColumnMeta { + flex?: boolean; + filterOptions?: SelectionOption[]; + sticky?: boolean; + } +} From a46d730aa0f693ce392d037115c0104f9b96fb16 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Tue, 14 Apr 2026 12:31:33 +0200 Subject: [PATCH 2/6] sticky checkboxes and headers --- components/table/TableBody/TableBody.tsx | 5 +++++ components/table/TableCell/TableCell.tsx | 4 +++- components/table/TableCell/types.ts | 1 + components/table/TableExpandCell/TableExpandCell.tsx | 6 +++++- components/table/TableSelectionCell/TableSelectionCell.tsx | 2 ++ 5 files changed, 16 insertions(+), 2 deletions(-) diff --git a/components/table/TableBody/TableBody.tsx b/components/table/TableBody/TableBody.tsx index 8083aa9..7418b30 100644 --- a/components/table/TableBody/TableBody.tsx +++ b/components/table/TableBody/TableBody.tsx @@ -108,16 +108,21 @@ export const TableBody = ({ // assign static sizing to extra columns at the start of the row let iterIndex = 0; let cumulativeStickyOffset = 0; + // Expand column (always at position 0 if present) if (canRowsExpand) { + colSizes['--expand-sticky-offset'] = 0; // Always at left edge colSizes['--col-0-size'] = tableActionColumnSize; cumulativeStickyOffset += tableActionColumnSize; iterIndex += 1; } + // Selection column (at position 0 or 1 depending on expand) if (canRowsSelect) { + colSizes['--selection-sticky-offset'] = cumulativeStickyOffset; colSizes[`--col-${iterIndex}-size`] = tableActionColumnSize; cumulativeStickyOffset += tableActionColumnSize; iterIndex += 1; } + // Data columns - sticky ones use the cumulative offset for (const header of headers) { const isSticky = header.column.columnDef.meta?.sticky ?? false; colSizes[`--col-${iterIndex}-size`] = header.column.getSize(); diff --git a/components/table/TableCell/TableCell.tsx b/components/table/TableCell/TableCell.tsx index 874ed5c..3ae8745 100644 --- a/components/table/TableCell/TableCell.tsx +++ b/components/table/TableCell/TableCell.tsx @@ -16,12 +16,14 @@ export const TableCell = ({ alignContent = 'left', flex = false, radius = false, + sticky = false, style: outsideStyle, ...props }: TableCellProps) => { const cell = useContext(TableCellContext); - const isSticky = cell?.column.columnDef.meta?.sticky ?? false; + const isStickyFromMeta = cell?.column.columnDef.meta?.sticky ?? false; + const isSticky = sticky || isStickyFromMeta; const style = useMemo((): CSSProperties => { const res: CSSProperties = {}; diff --git a/components/table/TableCell/types.ts b/components/table/TableCell/types.ts index b750428..bc1e174 100644 --- a/components/table/TableCell/types.ts +++ b/components/table/TableCell/types.ts @@ -9,4 +9,5 @@ export interface TableCellProps extends HTMLProps { columnId?: string; ignoreStyleAssign?: boolean; alignContent?: 'center' | 'left' | 'right'; + sticky?: boolean; } diff --git a/components/table/TableExpandCell/TableExpandCell.tsx b/components/table/TableExpandCell/TableExpandCell.tsx index da8b5e5..51187ea 100644 --- a/components/table/TableExpandCell/TableExpandCell.tsx +++ b/components/table/TableExpandCell/TableExpandCell.tsx @@ -15,10 +15,14 @@ export const TableExpandCell = ({ row }: Props) => { return ( {canExpand && ( { return ( From 0d693adc991d4da28fb5e56fe1d386312322d57e Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Tue, 14 Apr 2026 13:17:58 +0200 Subject: [PATCH 3/6] fix css issues --- components/table/TableBody/TableBody.tsx | 50 ++++++++++++++----- components/table/TableCell/TableCell.tsx | 29 ++++++----- components/table/TableCell/style.scss | 2 +- .../table/TableExpandCell/TableExpandCell.tsx | 9 ++-- components/table/TableHeader/style.scss | 3 +- .../table/TableHeaderCell/TableHeaderCell.tsx | 11 +++- components/table/TableHeaderCell/style.scss | 8 +-- 7 files changed, 75 insertions(+), 37 deletions(-) diff --git a/components/table/TableBody/TableBody.tsx b/components/table/TableBody/TableBody.tsx index 7418b30..46c47f3 100644 --- a/components/table/TableBody/TableBody.tsx +++ b/components/table/TableBody/TableBody.tsx @@ -16,6 +16,7 @@ import { } from 'react'; import { useInView } from 'react-intersection-observer'; import { isPresent } from '../../../utils/isPresent'; +import { Checkbox } from '../../Checkbox/Checkbox'; import { tableActionColumnSize } from '../consts'; import { TableCell } from '../TableCell/TableCell'; import { TableCellContext } from '../TableCell/TableCellContext'; @@ -106,18 +107,17 @@ export const TableBody = ({ const canRowsExpand = table.options.enableExpanding; const canRowsSelect = table.options.enableRowSelection; // assign static sizing to extra columns at the start of the row + // in the same order as they are rendered: selection -> expand -> data columns let iterIndex = 0; let cumulativeStickyOffset = 0; - // Expand column (always at position 0 if present) - if (canRowsExpand) { - colSizes['--expand-sticky-offset'] = 0; // Always at left edge - colSizes['--col-0-size'] = tableActionColumnSize; + if (canRowsSelect) { + colSizes['--selection-sticky-offset'] = cumulativeStickyOffset; + colSizes[`--col-${iterIndex}-size`] = tableActionColumnSize; cumulativeStickyOffset += tableActionColumnSize; iterIndex += 1; } - // Selection column (at position 0 or 1 depending on expand) - if (canRowsSelect) { - colSizes['--selection-sticky-offset'] = cumulativeStickyOffset; + if (canRowsExpand) { + colSizes['--expand-sticky-offset'] = cumulativeStickyOffset; colSizes[`--col-${iterIndex}-size`] = tableActionColumnSize; cumulativeStickyOffset += tableActionColumnSize; iterIndex += 1; @@ -265,14 +265,36 @@ export const TableBody = ({ > {table.options.enableRowSelection && ( - { - table.toggleAllRowsSelected(); + + { + table.toggleAllRowsSelected(); + }} + /> + + )} + {table.options.enableExpanding && ( + )} - {table.options.enableExpanding && } {table.getHeaderGroups()[0].headers.map((header) => { return ; })} @@ -310,7 +332,9 @@ export const TableBody = ({ }} /> )} - {table.options.enableExpanding && } + {table.options.enableExpanding && ( + + )} {row.getAllCells().map((cell) => ( diff --git a/components/table/TableCell/TableCell.tsx b/components/table/TableCell/TableCell.tsx index 3ae8745..d64c886 100644 --- a/components/table/TableCell/TableCell.tsx +++ b/components/table/TableCell/TableCell.tsx @@ -27,23 +27,26 @@ export const TableCell = ({ const style = useMemo((): CSSProperties => { const res: CSSProperties = {}; - if (outsideStyle?.width) return res; const id = columnId ?? cell?.column.id; const hasId = isPresent(id); - if (flex) { - return res; - } - if (empty && !hasId) { - res.width = tableActionColumnSize; - return res; - } - if (hasId) { - res.width = `calc(var(--col-${id}-size) * 1px)`; - // Set sticky offset if this is a sticky column - if (isSticky) { - res.left = `calc(var(--col-${id}-sticky-left-offset) * 1px)`; + + if (!outsideStyle?.width) { + if (flex) { + return res; + } + if (empty && !hasId) { + res.width = tableActionColumnSize; + return res; } + if (hasId) { + res.width = `calc(var(--col-${id}-size) * 1px)`; + } + } + + if (hasId && isSticky) { + res.left = `calc(var(--col-${id}-sticky-left-offset) * 1px)`; } + return res; }, [columnId, empty, flex, outsideStyle?.width, cell, isSticky]); diff --git a/components/table/TableCell/style.scss b/components/table/TableCell/style.scss index 5f58845..d5e4f4a 100644 --- a/components/table/TableCell/style.scss +++ b/components/table/TableCell/style.scss @@ -68,7 +68,7 @@ &.sticky { position: sticky; z-index: 1; - background-color: inherit; + background-color: var(--bg-color); } & > span { diff --git a/components/table/TableExpandCell/TableExpandCell.tsx b/components/table/TableExpandCell/TableExpandCell.tsx index 51187ea..382b8be 100644 --- a/components/table/TableExpandCell/TableExpandCell.tsx +++ b/components/table/TableExpandCell/TableExpandCell.tsx @@ -6,12 +6,15 @@ import { TableCell } from '../TableCell/TableCell'; type Props = { row: Row; + hasSelectionColumn: boolean; }; -export const TableExpandCell = ({ row }: Props) => { +export const TableExpandCell = ({ + row, + hasSelectionColumn, +}: Props) => { const expanded = row.getIsExpanded(); const canExpand = row.getCanExpand(); - const canSelect = row.getCanSelect(); return ( ({ row }: Props) => { noPadding empty={!canExpand} style={{ - width: `calc(var(--col-${canSelect ? 1 : 0}-size) * 1px)`, + width: `calc(var(--col-${hasSelectionColumn ? 1 : 0}-size) * 1px)`, left: 'calc(var(--expand-sticky-offset) * 1px)', }} > diff --git a/components/table/TableHeader/style.scss b/components/table/TableHeader/style.scss index 16fed50..587dcc4 100644 --- a/components/table/TableHeader/style.scss +++ b/components/table/TableHeader/style.scss @@ -1,6 +1,5 @@ .table .table-header { - position: sticky; - inset: 0; + position: relative; z-index: 1; background-color: var(--bg-default); height: 36px; diff --git a/components/table/TableHeaderCell/TableHeaderCell.tsx b/components/table/TableHeaderCell/TableHeaderCell.tsx index 495b618..9be225d 100644 --- a/components/table/TableHeaderCell/TableHeaderCell.tsx +++ b/components/table/TableHeaderCell/TableHeaderCell.tsx @@ -94,8 +94,13 @@ export const TableHeaderCell = ({ header }: Props) if (isEmpty) return ( @@ -104,8 +109,10 @@ export const TableHeaderCell = ({ header }: Props) return ( <> Date: Tue, 14 Apr 2026 13:43:00 +0200 Subject: [PATCH 4/6] fix content overflowing on the left side --- components/table/TableBody/TableBody.tsx | 4 ++-- components/table/TableBody/style.scss | 4 +++- components/table/TableCell/TableCell.tsx | 2 +- components/table/TableCell/style.scss | 2 ++ components/table/TableExpandCell/TableExpandCell.tsx | 2 +- components/table/TableRowContainer/style.scss | 3 +++ components/table/TableSelectionCell/TableSelectionCell.tsx | 2 +- 7 files changed, 13 insertions(+), 6 deletions(-) diff --git a/components/table/TableBody/TableBody.tsx b/components/table/TableBody/TableBody.tsx index 46c47f3..b2e6664 100644 --- a/components/table/TableBody/TableBody.tsx +++ b/components/table/TableBody/TableBody.tsx @@ -272,7 +272,7 @@ export const TableBody = ({ noBorder style={{ width: 'calc(var(--col-0-size) * 1px)', - left: 'calc(var(--selection-sticky-offset) * 1px)', + left: 'calc(var(--selection-sticky-offset) * 1px - var(--table-inline-padding))', }} > ({ empty style={{ width: `calc(var(--col-${canSelect ? 1 : 0}-size) * 1px)`, - left: 'calc(var(--expand-sticky-offset) * 1px)', + left: 'calc(var(--expand-sticky-offset) * 1px - var(--table-inline-padding))', }} /> )} diff --git a/components/table/TableBody/style.scss b/components/table/TableBody/style.scss index 9b2768d..b535442 100644 --- a/components/table/TableBody/style.scss +++ b/components/table/TableBody/style.scss @@ -1,6 +1,8 @@ .table { + --table-inline-padding: var(--spacing-xl); + box-sizing: border-box; - padding: 0 var(--spacing-xl); + padding: 0 var(--table-inline-padding); width: 100%; } diff --git a/components/table/TableCell/TableCell.tsx b/components/table/TableCell/TableCell.tsx index d64c886..4328e3d 100644 --- a/components/table/TableCell/TableCell.tsx +++ b/components/table/TableCell/TableCell.tsx @@ -44,7 +44,7 @@ export const TableCell = ({ } if (hasId && isSticky) { - res.left = `calc(var(--col-${id}-sticky-left-offset) * 1px)`; + res.left = `calc(var(--col-${id}-sticky-left-offset) * 1px - var(--table-inline-padding))`; } return res; diff --git a/components/table/TableCell/style.scss b/components/table/TableCell/style.scss index d5e4f4a..771ddcf 100644 --- a/components/table/TableCell/style.scss +++ b/components/table/TableCell/style.scss @@ -8,6 +8,7 @@ box-sizing: border-box; padding: 0 var(--spacing-md); min-height: 48px; + min-width: 0; background-color: inherit; @include animate(background-color); @@ -72,6 +73,7 @@ } & > span { + min-width: 0; font: var(--t-body-sm-400); color: var(--fg-default); overflow: hidden; diff --git a/components/table/TableExpandCell/TableExpandCell.tsx b/components/table/TableExpandCell/TableExpandCell.tsx index 382b8be..1d6628a 100644 --- a/components/table/TableExpandCell/TableExpandCell.tsx +++ b/components/table/TableExpandCell/TableExpandCell.tsx @@ -24,7 +24,7 @@ export const TableExpandCell = ({ empty={!canExpand} style={{ width: `calc(var(--col-${hasSelectionColumn ? 1 : 0}-size) * 1px)`, - left: 'calc(var(--expand-sticky-offset) * 1px)', + left: 'calc(var(--expand-sticky-offset) * 1px - var(--table-inline-padding))', }} > {canExpand && ( diff --git a/components/table/TableRowContainer/style.scss b/components/table/TableRowContainer/style.scss index a61a777..4d11149 100644 --- a/components/table/TableRowContainer/style.scss +++ b/components/table/TableRowContainer/style.scss @@ -13,6 +13,7 @@ --bg-color: var(--bg-default); .table-cell { + overflow: hidden; border-bottom: var(--border-1) solid var(--border-disabled); } } @@ -23,6 +24,7 @@ user-select: none; .table-cell { + overflow: hidden; min-height: 36px; span { @@ -37,6 +39,7 @@ border-bottom-right-radius: var(--radius-md); .table-cell { + overflow: hidden; border-bottom: var(--border-1) solid var(--border-disabled); &:first-child { diff --git a/components/table/TableSelectionCell/TableSelectionCell.tsx b/components/table/TableSelectionCell/TableSelectionCell.tsx index bc867b6..082d345 100644 --- a/components/table/TableSelectionCell/TableSelectionCell.tsx +++ b/components/table/TableSelectionCell/TableSelectionCell.tsx @@ -14,7 +14,7 @@ export const TableSelectionCell = ({ selected, onClick }: Props) => { noBorder style={{ width: `calc(var(--col-0-size) * 1px)`, - left: 'calc(var(--selection-sticky-offset) * 1px)', + left: 'calc(var(--selection-sticky-offset) * 1px - var(--table-inline-padding))', }} > From de0a676a40479edb2d3a294bf5d2171443b5443c Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Tue, 14 Apr 2026 14:38:35 +0200 Subject: [PATCH 5/6] sticky right column --- components/table/TableBody/TableBody.tsx | 263 ++++++++++-------- components/table/TableCell/TableCell.tsx | 12 +- .../TableCell/TableStickyColumnsContext.tsx | 5 + .../table/TableEditCell/TableEditCell.tsx | 17 +- components/table/TableHeader/style.scss | 5 +- 5 files changed, 182 insertions(+), 120 deletions(-) create mode 100644 components/table/TableCell/TableStickyColumnsContext.tsx diff --git a/components/table/TableBody/TableBody.tsx b/components/table/TableBody/TableBody.tsx index b2e6664..7b22d9e 100644 --- a/components/table/TableBody/TableBody.tsx +++ b/components/table/TableBody/TableBody.tsx @@ -20,6 +20,7 @@ import { Checkbox } from '../../Checkbox/Checkbox'; import { tableActionColumnSize } from '../consts'; import { TableCell } from '../TableCell/TableCell'; import { TableCellContext } from '../TableCell/TableCellContext'; +import { TableStickyColumnsContext } from '../TableCell/TableStickyColumnsContext'; import { TableExpandCell } from '../TableExpandCell/TableExpandCell'; import { TableExpandedRowHeader } from '../TableExpandedRowHeader/TableExpandedRowHeader'; import { TableHeader } from '../TableHeader/TableHeader'; @@ -100,6 +101,28 @@ export const TableBody = ({ .join(' '); }, [table]); + const stickyColumnSides = useMemo(() => { + const headers = table.getFlatHeaders(); + const stickySides: Record = {}; + + let leftIndex = 0; + while ( + leftIndex < headers.length && + headers[leftIndex].column.columnDef.meta?.sticky + ) { + stickySides[headers[leftIndex].column.id] = 'left'; + leftIndex += 1; + } + + let rightIndex = headers.length - 1; + while (rightIndex >= leftIndex && headers[rightIndex].column.columnDef.meta?.sticky) { + stickySides[headers[rightIndex].column.id] = 'right'; + rightIndex -= 1; + } + + return stickySides; + }, [table.getFlatHeaders]); + // biome-ignore lint/correctness/useExhaustiveDependencies: needs to recalculate on sizing changes const columnSizeVars = useMemo(() => { const headers = table.getFlatHeaders(); @@ -124,20 +147,30 @@ export const TableBody = ({ } // Data columns - sticky ones use the cumulative offset for (const header of headers) { - const isSticky = header.column.columnDef.meta?.sticky ?? false; colSizes[`--col-${iterIndex}-size`] = header.column.getSize(); colSizes[`--col-${header.column.id}-size`] = header.column.getSize(); - // Calculate sticky offset for this column - if (isSticky) { + if (stickyColumnSides[header.column.id] === 'left') { colSizes[`--col-${header.column.id}-sticky-left-offset`] = cumulativeStickyOffset; cumulativeStickyOffset += header.column.getSize(); } iterIndex += 1; } + + let cumulativeStickyRightOffset = 0; + for (let index = headers.length - 1; index >= 0; index -= 1) { + const header = headers[index]; + if (stickyColumnSides[header.column.id] === 'right') { + colSizes[`--col-${header.column.id}-sticky-right-offset`] = + cumulativeStickyRightOffset; + cumulativeStickyRightOffset += header.column.getSize(); + } + } + return colSizes; }, [ table.getState().columnSizingInfo, table.getState().columnSizing, + stickyColumnSides, table.getFlatHeaders, ]); @@ -244,129 +277,131 @@ export const TableBody = ({ }, [table]); return ( -
+
- - {table.options.enableRowSelection && ( - - { - table.toggleAllRowsSelected(); +
+ + {table.options.enableRowSelection && ( + + { + table.toggleAllRowsSelected(); + }} + /> + + )} + {table.options.enableExpanding && ( + - - )} - {table.options.enableExpanding && ( - - )} - {table.getHeaderGroups()[0].headers.map((header) => { - return ; - })} - - {rowVirtualizer.getVirtualItems().map((virtualRow) => { - const row = rows[virtualRow.index]; - const isExpanded = row.getIsExpanded() && row.getCanExpand(); - const canSelect = row.getCanSelect(); - const isLast = virtualRow.index === rows.length - 1 && !hasNextPage; - return ( -
rowVirtualizer.measureElement(node)} - data-index={virtualRow.index} - className={clsx('virtual-row', { - expanded: isExpanded && !isLast, - })} - key={row.id} - style={{ - position: 'absolute', - transform: `translateY(${virtualRow.start}px)`, - minWidth: tableWidth, - width: '100%', - }} - > - { + return ; + })} + + {rowVirtualizer.getVirtualItems().map((virtualRow) => { + const row = rows[virtualRow.index]; + const isExpanded = row.getIsExpanded() && row.getCanExpand(); + const canSelect = row.getCanSelect(); + const isLast = virtualRow.index === rows.length - 1 && !hasNextPage; + return ( +
rowVirtualizer.measureElement(node)} + data-index={virtualRow.index} + className={clsx('virtual-row', { + expanded: isExpanded && !isLast, })} + key={row.id} + style={{ + position: 'absolute', + transform: `translateY(${virtualRow.start}px)`, + minWidth: tableWidth, + width: '100%', + }} > - {canSelect && ( - { - row.toggleSelected(); + + {canSelect && ( + { + row.toggleSelected(); + }} + /> + )} + {table.options.enableExpanding && ( + + )} + {row.getAllCells().map((cell) => ( + + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + + ))} + + {isExpanded && isPresent(expandedHeaders) && ( + )} - {table.options.enableExpanding && ( - - )} - {row.getAllCells().map((cell) => ( - - - {flexRender(cell.column.columnDef.cell, cell.getContext())} - - - ))} - - {isExpanded && isPresent(expandedHeaders) && ( - - )} - {isExpanded && - isPresent(renderExpandedRow) && - renderExpandedRow(row, isLast)} + {isExpanded && + isPresent(renderExpandedRow) && + renderExpandedRow(row, isLast)} +
+ ); + })} + {hasNextPage && isPresent(onNextPage) && ( +
+
- ); - })} - {hasNextPage && isPresent(onNextPage) && ( -
- -
- )} + )} +
-
+
); }; diff --git a/components/table/TableCell/TableCell.tsx b/components/table/TableCell/TableCell.tsx index 4328e3d..2f8a4a2 100644 --- a/components/table/TableCell/TableCell.tsx +++ b/components/table/TableCell/TableCell.tsx @@ -4,6 +4,7 @@ import { type CSSProperties, useContext, useMemo } from 'react'; import { isPresent } from '../../../utils/isPresent'; import { tableActionColumnSize } from '../consts'; import { TableCellContext } from './TableCellContext'; +import { TableStickyColumnsContext } from './TableStickyColumnsContext'; import type { TableCellProps } from './types'; export const TableCell = ({ @@ -21,6 +22,7 @@ export const TableCell = ({ ...props }: TableCellProps) => { const cell = useContext(TableCellContext); + const stickyColumns = useContext(TableStickyColumnsContext); const isStickyFromMeta = cell?.column.columnDef.meta?.sticky ?? false; const isSticky = sticky || isStickyFromMeta; @@ -44,11 +46,17 @@ export const TableCell = ({ } if (hasId && isSticky) { - res.left = `calc(var(--col-${id}-sticky-left-offset) * 1px - var(--table-inline-padding))`; + const stickySide = stickyColumns[id]; + if (stickySide === 'left') { + res.left = `calc(var(--col-${id}-sticky-left-offset) * 1px - var(--table-inline-padding))`; + } + if (stickySide === 'right') { + res.right = `calc(var(--col-${id}-sticky-right-offset) * 1px - var(--table-inline-padding))`; + } } return res; - }, [columnId, empty, flex, outsideStyle?.width, cell, isSticky]); + }, [columnId, empty, flex, outsideStyle?.width, cell, isSticky, stickyColumns]); return (
>( + {}, +); diff --git a/components/table/TableEditCell/TableEditCell.tsx b/components/table/TableEditCell/TableEditCell.tsx index 283f024..c851403 100644 --- a/components/table/TableEditCell/TableEditCell.tsx +++ b/components/table/TableEditCell/TableEditCell.tsx @@ -2,15 +2,26 @@ import { IconButtonMenu } from '../../IconButtonMenu/IconButtonMenu'; import type { MenuItemsGroup } from '../../Menu/types'; import { tableEditColumnSize } from '../consts'; import { TableCell } from '../TableCell/TableCell'; +import type { TableCellProps } from '../TableCell/types'; type Props = { menuItems: MenuItemsGroup[]; disabled?: boolean; -}; +} & TableCellProps; -export const TableEditCell = ({ menuItems, disabled }: Props) => { +export const TableEditCell = ({ + menuItems, + disabled, + alignContent = 'right', + style, + ...cellProps +}: Props) => { return ( - + ); diff --git a/components/table/TableHeader/style.scss b/components/table/TableHeader/style.scss index 587dcc4..3c5f107 100644 --- a/components/table/TableHeader/style.scss +++ b/components/table/TableHeader/style.scss @@ -23,8 +23,11 @@ border-top-left-radius: var(--radius-lg); } - &:last-child { + &:last-child:not(.sticky) { flex: 1 1 10px; + } + + &:last-child { border-top-right-radius: var(--radius-lg); } From f6b5b0601ec9b3f5dc121c06bdce706a0d72a804 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Tue, 14 Apr 2026 14:45:22 +0200 Subject: [PATCH 6/6] fix header styling --- components/table/TableHeader/style.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/components/table/TableHeader/style.scss b/components/table/TableHeader/style.scss index 3c5f107..2f61fbc 100644 --- a/components/table/TableHeader/style.scss +++ b/components/table/TableHeader/style.scss @@ -8,7 +8,6 @@ display: flex; min-width: 100%; width: fit-content; - background-color: var(--bg-disabled); border-top-left-radius: var(--radius-lg); border-top-right-radius: var(--radius-lg);