Skip to content

Commit cf594e3

Browse files
author
Amrit Kashyap Borah
committed
fix: sticky columns in table
1 parent b5820c6 commit cf594e3

3 files changed

Lines changed: 151 additions & 77 deletions

File tree

src/Shared/Components/Table/InternalTable.tsx

Lines changed: 116 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import BulkSelectionActionWidget from './BulkSelectionActionWidget'
1818
import { BULK_ACTION_GUTTER_LABEL, EVENT_TARGET, NO_ROWS_OR_GET_ROWS_ERROR, SHIMMER_DUMMY_ARRAY } from './constants'
1919
import { BulkActionStateType, FiltersTypeEnum, InternalTableProps, PaginationEnum, SignalsType } from './types'
2020
import useTableWithKeyboardShortcuts from './useTableWithKeyboardShortcuts'
21-
import { getFilteringPromise, searchAndSortRows } from './utils'
21+
import { getFilteringPromise, getStickyColumnConfig, searchAndSortRows } from './utils'
2222

2323
const InternalTable = ({
2424
filtersVariant,
@@ -238,7 +238,7 @@ const InternalTable = ({
238238
key={row.id}
239239
ref={isRowActive ? activeRowRef : null}
240240
onClick={handleChangeActiveRowIndex}
241-
className={`dc__grid px-20 checkbox__parent-container ${
241+
className={`dc__grid px-20 dc__min-width-fit-content checkbox__parent-container ${
242242
showSeparatorBetweenRows ? 'border__secondary--bottom' : ''
243243
} fs-13 fw-4 lh-20 cn-9 generic-table__row dc__gap-16 ${
244244
isRowActive ? 'generic-table__row--active checkbox__parent-container--active' : ''
@@ -252,37 +252,48 @@ const InternalTable = ({
252252
// NOTE: by giving it a negative tabIndex we can programmatically focus it through .focus()
253253
tabIndex={-1}
254254
>
255-
{visibleColumns.map(({ field, CellComponent }) => {
256-
if (field === BULK_ACTION_GUTTER_LABEL) {
255+
{visibleColumns.map(({ field, horizontallySticky: isStickyColumn, CellComponent }, index) => {
256+
const isBulkActionGutter = field === BULK_ACTION_GUTTER_LABEL
257+
const horizontallySticky = isStickyColumn || isBulkActionGutter
258+
const { className: stickyClassName = '', left: stickyLeftValue = '' } = horizontallySticky
259+
? getStickyColumnConfig(gridTemplateColumns, index)
260+
: {}
261+
262+
if (isBulkActionGutter) {
257263
return (
258-
<Checkbox
264+
<div
265+
className={`flex ${stickyClassName}`}
266+
style={{ left: stickyLeftValue }}
259267
key={field}
260-
isChecked={isRowBulkSelected}
261-
onChange={handleToggleBulkSelectionForRow}
262-
rootClassName="mb-0"
263-
value={CHECKBOX_VALUE.CHECKED}
264-
/>
265-
)
266-
}
267-
268-
if (CellComponent) {
269-
return (
270-
<CellComponent
271-
field={field}
272-
value={row.data[field]}
273-
signals={EVENT_TARGET as SignalsType}
274-
row={row}
275-
filterData={filterData as any}
276-
isRowActive={isRowActive}
277-
{...additionalProps}
278-
/>
268+
>
269+
<Checkbox
270+
isChecked={isRowBulkSelected}
271+
onChange={handleToggleBulkSelectionForRow}
272+
rootClassName="mb-0"
273+
value={CHECKBOX_VALUE.CHECKED}
274+
/>
275+
</div>
279276
)
280277
}
281278

282279
return (
283-
<span key={field} className="py-12">
284-
{row.data[field]}
285-
</span>
280+
<div className={`${stickyClassName}`} style={{ left: stickyLeftValue }} key={field}>
281+
{CellComponent ? (
282+
<CellComponent
283+
field={field}
284+
value={row.data[field]}
285+
signals={EVENT_TARGET as SignalsType}
286+
row={row}
287+
filterData={filterData as any}
288+
isRowActive={isRowActive}
289+
{...additionalProps}
290+
/>
291+
) : (
292+
<span key={field} className="py-12">
293+
{row.data[field]}
294+
</span>
295+
)}
296+
</div>
286297
)
287298
})}
288299

@@ -311,58 +322,87 @@ const InternalTable = ({
311322

312323
return (
313324
<div tabIndex={0} role="grid" className="generic-table flexbox-col dc__overflow-hidden flex-grow-1">
314-
<div className="flexbox-col flex-grow-1 w-100 dc__overflow-auto" ref={parentRef}>
315-
<div className="bg__primary dc__min-width-fit-content px-20 border__secondary--bottom">
316-
{loading ? (
317-
<div className="flexbox py-12 dc__gap-16">
318-
{SHIMMER_DUMMY_ARRAY.map((label) => (
319-
<div key={label} className="shimmer w-180" />
320-
))}
321-
</div>
322-
) : (
323-
<div
324-
className="dc__grid fw-6 cn-7 fs-12 lh-20 py-8 dc__gap-16"
325-
style={{
326-
gridTemplateColumns,
327-
}}
328-
>
329-
{visibleColumns.map(({ label, field, isSortable, size, showTippyOnTruncate }) => {
330-
const isResizable = !!size?.range
331-
332-
if (field === BULK_ACTION_GUTTER_LABEL) {
333-
return (
334-
<BulkSelection
335-
ref={bulkSelectionButtonRef}
336-
key={field}
337-
showPagination={showPagination}
338-
/>
339-
)
340-
}
341-
342-
return (
343-
<SortableTableHeaderCell
344-
key={field}
345-
title={label}
346-
isSortable={!!isSortable}
347-
sortOrder={sortOrder}
348-
isSorted={sortBy === field}
349-
triggerSorting={getTriggerSortingHandler(field)}
350-
showTippyOnTruncate={showTippyOnTruncate}
351-
disabled={areFilteredRowsLoading}
352-
{...(isResizable
353-
? { isResizable, handleResize, id: label }
354-
: { isResizable: false })}
355-
/>
356-
)
357-
})}
358-
</div>
359-
)}
360-
</div>
361-
325+
<div className="flexbox-col flex-grow-1 w-100 dc__overflow-hidden" ref={parentRef}>
362326
<div
363327
ref={rowsContainerRef}
364-
className="flex-grow-1 flexbox-col dc__min-width-fit-content dc__overflow-auto"
328+
className={`flex-grow-1 flexbox-col dc__overflow-auto ${rowsContainerRef.current?.scrollLeft > 0}`}
365329
>
330+
<div className="bg__primary dc__min-width-fit-content px-20 border__secondary--bottom dc__position-sticky dc__zi-2 dc__top-0">
331+
{loading ? (
332+
<div className="flexbox py-12 dc__gap-16">
333+
{SHIMMER_DUMMY_ARRAY.map((label) => (
334+
<div key={label} className="shimmer w-180" />
335+
))}
336+
</div>
337+
) : (
338+
<div
339+
className="dc__grid fw-6 cn-7 fs-12 lh-20 py-8 dc__gap-16"
340+
style={{
341+
gridTemplateColumns,
342+
}}
343+
>
344+
{visibleColumns.map(
345+
(
346+
{
347+
label,
348+
field,
349+
isSortable,
350+
size,
351+
showTippyOnTruncate,
352+
horizontallySticky: isStickyColumn,
353+
},
354+
index,
355+
) => {
356+
const isResizable = !!size?.range
357+
const isBulkActionGutter = field === BULK_ACTION_GUTTER_LABEL
358+
const horizontallySticky = isStickyColumn || isBulkActionGutter
359+
const { className: stickyClassName = '', left: stickyLeftValue = '' } =
360+
horizontallySticky
361+
? getStickyColumnConfig(gridTemplateColumns, index)
362+
: {}
363+
364+
if (field === BULK_ACTION_GUTTER_LABEL) {
365+
return (
366+
<div
367+
className={`flex ${stickyClassName}`}
368+
style={{ left: stickyLeftValue }}
369+
key={field}
370+
>
371+
<BulkSelection
372+
ref={bulkSelectionButtonRef}
373+
key={field}
374+
showPagination={showPagination}
375+
/>
376+
</div>
377+
)
378+
}
379+
380+
return (
381+
<div
382+
className={`${stickyClassName}`}
383+
style={{ left: stickyLeftValue }}
384+
key={field}
385+
>
386+
<SortableTableHeaderCell
387+
key={field}
388+
title={label}
389+
isSortable={!!isSortable}
390+
sortOrder={sortOrder}
391+
isSorted={sortBy === field}
392+
triggerSorting={getTriggerSortingHandler(field)}
393+
showTippyOnTruncate={showTippyOnTruncate}
394+
disabled={areFilteredRowsLoading}
395+
{...(isResizable
396+
? { isResizable, handleResize, id: label }
397+
: { isResizable: false })}
398+
/>
399+
</div>
400+
)
401+
},
402+
)}
403+
</div>
404+
)}
405+
</div>
366406
{renderRows()}
367407
</div>
368408

src/Shared/Components/Table/styles.scss

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,22 @@
11
.generic-table {
2+
&__cell--sticky {
3+
background-color: var(--bg-primary);
4+
5+
&::before {
6+
position: absolute;
7+
content: '';
8+
left: -16px;
9+
width: 16px;
10+
height: 100%;
11+
background-color: inherit;
12+
}
13+
14+
&:first-of-type::before {
15+
left: -20px;
16+
width: 20px;
17+
}
18+
}
19+
220
&__row {
321
&:focus {
422
outline: none;
@@ -23,4 +41,4 @@
2341
background-color: var(--B100);
2442
}
2543
}
26-
}
44+
}

src/Shared/Components/Table/utils.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,3 +118,19 @@ export const getFilteringPromise = ({ searchSortTimeoutRef, callback }: GetFilte
118118
searchSortTimeoutRef.current = -1
119119
}, SEARCH_SORT_CHANGE_DEBOUNCE_TIME)
120120
})
121+
122+
export const getStickyColumnConfig = (gridTemplateColumns: string, columnIndex: number) => ({
123+
className: 'dc__position-sticky dc__zi-1 generic-table__cell--sticky',
124+
// NOTE: container has a padding left of 20px and the gap between columns is 16px
125+
// so we want each sticky column to stick to the left of the previous column
126+
left: `${
127+
Number(
128+
gridTemplateColumns
129+
.split(' ')
130+
.slice(0, columnIndex)
131+
.reduce((acc, num) => acc + Number(num.split('px')[0]), 0) ?? 1,
132+
) +
133+
20 +
134+
16 * columnIndex
135+
}px`,
136+
})

0 commit comments

Comments
 (0)