Skip to content

Commit 3cb6880

Browse files
kmcfaulthatblindgeye
andauthoredMar 3, 2023
feat(Table): add right sticky column support (#8714)
* feat(Table): add right sticky column support * Updated prop and example verbiage --------- Co-authored-by: Eric Olkowski <thatblindgeye@gmail.com>

File tree

4 files changed

+192
-2
lines changed

4 files changed

+192
-2
lines changed
 

‎packages/react-table/src/components/TableComposable/Td.tsx

+8
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,14 @@ export interface TdProps extends BaseCellProps, Omit<React.HTMLProps<HTMLTableDa
6262
isStickyColumn?: boolean;
6363
/** Adds a border to the right side of the cell */
6464
hasRightBorder?: boolean;
65+
/** Adds a border to the left side of the cell */
66+
hasLeftBorder?: boolean;
6567
/** Minimum width for a sticky column */
6668
stickyMinWidth?: string;
6769
/** Left offset of a sticky column. This will typically be equal to the combined value set by stickyMinWidth of any sticky columns that precede the current sticky column. */
6870
stickyLeftOffset?: string;
71+
/** Right offset of a sticky column. This will typically be equal to the combined value set by stickyMinWidth of any sticky columns that come after the current sticky column. */
72+
stickyRightOffset?: string;
6973
}
7074

7175
const TdBase: React.FunctionComponent<TdProps> = ({
@@ -91,8 +95,10 @@ const TdBase: React.FunctionComponent<TdProps> = ({
9195
onMouseEnter: onMouseEnterProp = () => {},
9296
isStickyColumn = false,
9397
hasRightBorder = false,
98+
hasLeftBorder = false,
9499
stickyMinWidth = '120px',
95100
stickyLeftOffset,
101+
stickyRightOffset,
96102
...props
97103
}: TdProps) => {
98104
const [showTooltip, setShowTooltip] = React.useState(false);
@@ -255,6 +261,7 @@ const TdBase: React.FunctionComponent<TdProps> = ({
255261
noPadding && styles.modifiers.noPadding,
256262
isStickyColumn && scrollStyles.tableStickyColumn,
257263
hasRightBorder && scrollStyles.modifiers.borderRight,
264+
hasLeftBorder && scrollStyles.modifiers.borderLeft,
258265
styles.modifiers[modifier as 'breakWord' | 'fitContent' | 'nowrap' | 'truncate' | 'wrap' | undefined],
259266
draggableParams && styles.tableDraggable,
260267
mergedClassName
@@ -266,6 +273,7 @@ const TdBase: React.FunctionComponent<TdProps> = ({
266273
style: {
267274
'--pf-c-table__sticky-column--MinWidth': stickyMinWidth ? stickyMinWidth : undefined,
268275
'--pf-c-table__sticky-column--Left': stickyLeftOffset ? stickyLeftOffset : undefined,
276+
right: stickyRightOffset ? stickyRightOffset : 0,
269277
...props.style
270278
} as React.CSSProperties
271279
})}

‎packages/react-table/src/components/TableComposable/Th.tsx

+8
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,14 @@ export interface ThProps
4646
isStickyColumn?: boolean;
4747
/** Adds a border to the right side of the cell */
4848
hasRightBorder?: boolean;
49+
/** Adds a border to the left side of the cell */
50+
hasLeftBorder?: boolean;
4951
/** Minimum width for a sticky column */
5052
stickyMinWidth?: string;
5153
/** Left offset of a sticky column. This will typically be equal to the combined value set by stickyMinWidth of any sticky columns that precede the current sticky column. */
5254
stickyLeftOffset?: string;
55+
/** Right offset of a sticky column. This will typically be equal to the combined value set by stickyMinWidth of any sticky columns that come after the current sticky column. */
56+
stickyRightOffset?: string;
5357
/** Indicates the <th> is part of a subheader of a nested header */
5458
isSubheader?: boolean;
5559
}
@@ -73,8 +77,10 @@ const ThBase: React.FunctionComponent<ThProps> = ({
7377
info: infoProps,
7478
isStickyColumn = false,
7579
hasRightBorder = false,
80+
hasLeftBorder = false,
7681
stickyMinWidth = '120px',
7782
stickyLeftOffset,
83+
stickyRightOffset,
7884
isSubheader = false,
7985
...props
8086
}: ThProps) => {
@@ -171,6 +177,7 @@ const ThBase: React.FunctionComponent<ThProps> = ({
171177
isSubheader && styles.tableSubhead,
172178
isStickyColumn && scrollStyles.tableStickyColumn,
173179
hasRightBorder && scrollStyles.modifiers.borderRight,
180+
hasLeftBorder && scrollStyles.modifiers.borderLeft,
174181
modifier && styles.modifiers[modifier as 'breakWord' | 'fitContent' | 'nowrap' | 'truncate' | 'wrap'],
175182
mergedClassName
176183
)}
@@ -180,6 +187,7 @@ const ThBase: React.FunctionComponent<ThProps> = ({
180187
style: {
181188
'--pf-c-table__sticky-column--MinWidth': stickyMinWidth ? stickyMinWidth : undefined,
182189
'--pf-c-table__sticky-column--Left': stickyLeftOffset ? stickyLeftOffset : undefined,
190+
right: stickyRightOffset ? stickyRightOffset : 0,
183191
...props.style
184192
} as React.CSSProperties
185193
})}

‎packages/react-table/src/components/TableComposable/examples/ComposableTable.md

+23-2
Original file line numberDiff line numberDiff line change
@@ -333,13 +333,34 @@ To make a column sticky, wrap `TableComposable` with `InnerScrollContainer` and
333333
```ts file="ComposableTableStickyColumn.tsx"
334334
```
335335

336-
### Composable: Multiple sticky columns
336+
### Composable: Multiple left-aligned sticky columns
337337

338-
To make multiple columns sticky, wrap `TableComposable` with `InnerScrollContainer` and add `isStickyColumn` to all columns that should be sticky. The rightmost column should also have the `hasRightBorder` property, and each sticky column after the first must define a `stickyLeftOffset` property that equals the combined width of the previous sticky columns - set by `stickyMinWidth`. To prevent the default text wrapping behavior and allow horizontal scrolling, all `Th` or `Td` cells should also have the `modifier="nowrap"` property.
338+
To make multiple left-aligned columns sticky:
339+
340+
- wrap `TableComposable` with `InnerScrollContainer`
341+
- add `isStickyColumn` to all columns that should be sticky
342+
- add `hasRightBorder` to the rightmost sticky column
343+
- add `stickyLeftOffset` to each sticky column after the first, with a value that equals the combined width - set by `stickyMindWidth` - of the previous sticky columns
344+
345+
To prevent the default text wrapping behavior and allow horizontal scrolling, all `Th` or `Td` cells should also have the `modifier="nowrap"` property.
339346

340347
```ts file="ComposableTableMultipleStickyColumns.tsx"
341348
```
342349

350+
### Composable: Multiple right-aligned sticky columns
351+
352+
To make multiple right-aligned columns sticky:
353+
354+
- wrap `TableComposable` with `InnerScrollContainer`
355+
- add `isStickyColumn` to all columns that should be sticky
356+
- add `hasLeftBorder` to the leftmost sticky column
357+
- add `stickyRightOffset` to each sticky column preceding the last, with a value that equals the combined width - set by `stickyMindWidth` - of the next sticky columns
358+
359+
To prevent the default text wrapping behavior and allow horizontal scrolling, all `Th` or `Td` cells should also have the `modifier="nowrap"` property.
360+
361+
```ts file="ComposableTableRightStickyColumn.tsx"
362+
```
363+
343364
### Composable: Sticky columns and header
344365

345366
To maintain proper sticky behavior across sticky columns and header, `TableComposable` must be wrapped with `OuterScrollContainer` and `InnerScrollContainer`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
import React from 'react';
2+
import { TableComposable, Thead, Tr, Th, Tbody, Td, InnerScrollContainer, ThProps } from '@patternfly/react-table';
3+
4+
interface Fact {
5+
name: string;
6+
state: string;
7+
detail1: string;
8+
detail2: string;
9+
detail3: string;
10+
detail4: string;
11+
detail5: string;
12+
detail6: string;
13+
detail7: string;
14+
}
15+
16+
export const ComposableTableRightStickyColumn: React.FunctionComponent = () => {
17+
// In real usage, this data would come from some external source like an API via props.
18+
const facts: Fact[] = Array.from({ length: 9 }, (_, index) => ({
19+
name: `Fact ${index + 1}`,
20+
state: `State ${index + 1}`,
21+
detail1: `Test cell ${index + 1}-3`,
22+
detail2: `Test cell ${index + 1}-4`,
23+
detail3: `Test cell ${index + 1}-5`,
24+
detail4: `Test cell ${index + 1}-6`,
25+
detail5: `Test cell ${index + 1}-7`,
26+
detail6: `Test cell ${index + 1}-8`,
27+
detail7: `Test cell ${index + 1}-9`
28+
}));
29+
30+
const columnNames = {
31+
name: 'Fact',
32+
state: 'State',
33+
header3: 'Header 3',
34+
header4: 'Header 4',
35+
header5: 'Header 5',
36+
header6: 'Header 6',
37+
header7: 'Header 7',
38+
header8: 'Header 8',
39+
header9: 'Header 9'
40+
};
41+
42+
// Index of the currently sorted column
43+
// Note: if you intend to make columns reorderable, you may instead want to use a non-numeric key
44+
// as the identifier of the sorted column. See the "Compound expandable" example.
45+
const [activeSortIndex, setActiveSortIndex] = React.useState<number | null>(null);
46+
47+
// Sort direction of the currently sorted column
48+
const [activeSortDirection, setActiveSortDirection] = React.useState<'asc' | 'desc' | null>(null);
49+
50+
// Since OnSort specifies sorted columns by index, we need sortable values for our object by column index.
51+
// This example is trivial since our data objects just contain strings, but if the data was more complex
52+
// this would be a place to return simplified string or number versions of each column to sort by.
53+
const getSortableRowValues = (fact: Fact): (string | number)[] => {
54+
const { name, state, detail1, detail2, detail3, detail4, detail5, detail6, detail7 } = fact;
55+
return [name, state, detail1, detail2, detail3, detail4, detail5, detail6, detail7];
56+
};
57+
58+
// Note that we perform the sort as part of the component's render logic and not in onSort.
59+
// We shouldn't store the list of data in state because we don't want to have to sync that with props.
60+
let sortedFacts = facts;
61+
if (activeSortIndex !== null) {
62+
sortedFacts = facts.sort((a, b) => {
63+
const aValue = getSortableRowValues(a)[activeSortIndex];
64+
const bValue = getSortableRowValues(b)[activeSortIndex];
65+
if (aValue === bValue) {
66+
return 0;
67+
}
68+
if (activeSortDirection === 'asc') {
69+
return aValue > bValue ? 1 : -1;
70+
} else {
71+
return bValue > aValue ? 1 : -1;
72+
}
73+
});
74+
}
75+
76+
const getSortParams = (columnIndex: number): ThProps['sort'] => ({
77+
sortBy: {
78+
index: activeSortIndex,
79+
direction: activeSortDirection
80+
},
81+
onSort: (_event, index, direction) => {
82+
setActiveSortIndex(index);
83+
setActiveSortDirection(direction);
84+
},
85+
columnIndex
86+
});
87+
88+
return (
89+
<InnerScrollContainer>
90+
<TableComposable aria-label="Sticky column table" gridBreakPoint="">
91+
<Thead>
92+
<Tr>
93+
<Th modifier="truncate" sort={getSortParams(0)}>
94+
{columnNames.name}
95+
</Th>
96+
<Th modifier="truncate" sort={getSortParams(1)}>
97+
{columnNames.state}
98+
</Th>
99+
<Th modifier="truncate">{columnNames.header3}</Th>
100+
<Th modifier="truncate">{columnNames.header4}</Th>
101+
<Th modifier="truncate">{columnNames.header5}</Th>
102+
<Th modifier="truncate">{columnNames.header6}</Th>
103+
<Th modifier="truncate">{columnNames.header7}</Th>
104+
<Th isStickyColumn hasLeftBorder stickyMinWidth="130px" stickyRightOffset="130px" modifier="truncate">
105+
{columnNames.header8}
106+
</Th>
107+
<Th isStickyColumn stickyMinWidth="130px" modifier="truncate">
108+
{columnNames.header9}
109+
</Th>
110+
</Tr>
111+
</Thead>
112+
<Tbody>
113+
{sortedFacts.map(fact => (
114+
<Tr key={fact.name}>
115+
<Th modifier="nowrap">{fact.name}</Th>
116+
<Td modifier="nowrap" dataLabel={columnNames.state}>
117+
{fact.state}
118+
</Td>
119+
<Td modifier="nowrap" dataLabel={columnNames.header3}>
120+
{fact.detail1}
121+
</Td>
122+
<Td modifier="nowrap" dataLabel={columnNames.header4}>
123+
{fact.detail2}
124+
</Td>
125+
<Td modifier="nowrap" dataLabel={columnNames.header5}>
126+
{fact.detail3}
127+
</Td>
128+
<Td modifier="nowrap" dataLabel={columnNames.header6}>
129+
{fact.detail4}
130+
</Td>
131+
<Td modifier="nowrap" dataLabel={columnNames.header7}>
132+
{fact.detail5}
133+
</Td>
134+
<Td
135+
isStickyColumn
136+
hasLeftBorder
137+
stickyMinWidth="130px"
138+
stickyRightOffset="130px"
139+
modifier="nowrap"
140+
dataLabel={columnNames.header8}
141+
>
142+
{fact.detail6}
143+
</Td>
144+
<Td isStickyColumn stickyMinWidth="130px" modifier="nowrap" dataLabel={columnNames.header9}>
145+
{fact.detail7}
146+
</Td>
147+
</Tr>
148+
))}
149+
</Tbody>
150+
</TableComposable>
151+
</InnerScrollContainer>
152+
);
153+
};

0 commit comments

Comments
 (0)
Please sign in to comment.