@patternfly/react-table 4.44.11 → 4.44.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +38 -0
- package/dist/esm/components/Table/Table.d.ts +2 -1
- package/dist/esm/components/Table/Table.d.ts.map +1 -1
- package/dist/esm/components/Table/Table.js.map +1 -1
- package/dist/esm/components/Table/TableTypes.d.ts +2 -1
- package/dist/esm/components/Table/TableTypes.d.ts.map +1 -1
- package/dist/esm/components/Table/TableTypes.js.map +1 -1
- package/dist/esm/components/Table/base/provider.d.ts +2 -2
- package/dist/esm/components/Table/base/types.d.ts +2 -2
- package/dist/esm/components/TableComposable/TableComposable.d.ts +1 -1
- package/dist/esm/components/TableComposable/Td.d.ts +1 -1
- package/dist/js/components/Table/Table.d.ts +2 -1
- package/dist/js/components/Table/Table.d.ts.map +1 -1
- package/dist/js/components/Table/Table.js.map +1 -1
- package/dist/js/components/Table/TableTypes.d.ts +2 -1
- package/dist/js/components/Table/TableTypes.d.ts.map +1 -1
- package/dist/js/components/Table/TableTypes.js.map +1 -1
- package/dist/js/components/Table/base/provider.d.ts +2 -2
- package/dist/js/components/Table/base/types.d.ts +2 -2
- package/dist/js/components/TableComposable/TableComposable.d.ts +1 -1
- package/dist/js/components/TableComposable/Td.d.ts +1 -1
- package/package.json +6 -6
- package/src/components/Table/Table.tsx +2 -1
- package/src/components/Table/TableTypes.tsx +2 -1
- package/src/components/Table/examples/LegacyTableActions.tsx +218 -0
- package/src/components/Table/examples/LegacyTableBasic.tsx +90 -0
- package/src/components/Table/examples/LegacyTableCellWidth.tsx +66 -0
- package/src/components/Table/examples/LegacyTableCompoundExpandable.tsx +175 -0
- package/src/components/Table/examples/LegacyTableControllingText.tsx +51 -0
- package/src/components/Table/examples/LegacyTableEmptyState.tsx +48 -0
- package/src/components/Table/examples/LegacyTableExpandable.tsx +172 -0
- package/src/components/Table/examples/LegacyTableFavoritable.tsx +157 -0
- package/src/components/Table/examples/LegacyTableHoverable.tsx +125 -0
- package/src/components/Table/examples/LegacyTableMisc.tsx +56 -0
- package/src/components/Table/examples/LegacyTableSelectable.tsx +124 -0
- package/src/components/Table/examples/LegacyTableSelectableRadio.tsx +54 -0
- package/src/components/Table/examples/LegacyTableSortable.tsx +120 -0
- package/src/components/Table/examples/LegacyTableSortableCustom.tsx +189 -0
- package/src/components/Table/examples/LegacyTableTextModifiers.tsx +31 -0
- package/src/components/Table/examples/LegacyTableTree.tsx +213 -0
- package/src/components/Table/examples/Table.md +83 -1848
- package/src/components/TableComposable/examples/ComposableTable.md +31 -2805
- package/src/components/TableComposable/examples/ComposableTableActions.tsx +163 -0
- package/src/components/TableComposable/examples/ComposableTableBasic.tsx +89 -0
- package/src/components/TableComposable/examples/ComposableTableCellWidth.tsx +56 -0
- package/src/components/TableComposable/examples/ComposableTableCompoundExpandable.tsx +103 -0
- package/src/components/TableComposable/examples/ComposableTableControllingText.tsx +51 -0
- package/src/components/TableComposable/examples/ComposableTableDraggable.tsx +170 -0
- package/src/components/TableComposable/examples/ComposableTableEmptyState.tsx +45 -0
- package/src/components/TableComposable/examples/ComposableTableExpandable.tsx +195 -0
- package/src/components/TableComposable/examples/ComposableTableFavoritable.tsx +118 -0
- package/src/components/TableComposable/examples/ComposableTableHoverable.tsx +67 -0
- package/src/components/TableComposable/examples/ComposableTableMisc.tsx +92 -0
- package/src/components/TableComposable/examples/ComposableTableMultipleStickyColumns.tsx +153 -0
- package/src/components/TableComposable/examples/ComposableTableNestedExpandable.tsx +127 -0
- package/src/components/TableComposable/examples/ComposableTableNestedHeaders.tsx +202 -0
- package/src/components/TableComposable/examples/ComposableTableSelectable.tsx +125 -0
- package/src/components/TableComposable/examples/ComposableTableSelectableRadio.tsx +68 -0
- package/src/components/TableComposable/examples/ComposableTableSortable.tsx +107 -0
- package/src/components/TableComposable/examples/ComposableTableSortableCustom.tsx +175 -0
- package/src/components/TableComposable/examples/ComposableTableStickyColumn.tsx +144 -0
- package/src/components/TableComposable/examples/ComposableTableStickyColumnsAndHeader.tsx +167 -0
- package/src/components/TableComposable/examples/ComposableTableTextModifiers.tsx +33 -0
- package/src/components/TableComposable/examples/ComposableTableTree.tsx +201 -0
- package/src/demos/Table.md +7 -6
- package/src/components/Table/examples/DemoSortableTable.js +0 -65
- package/src/components/TableComposable/examples/DemoSortableTable.js +0 -65
|
@@ -40,7 +40,7 @@ Note: Table lives in its own package at [@patternfly/react-table](https://www.np
|
|
|
40
40
|
|
|
41
41
|
PatternFly has two implementations of a React table.
|
|
42
42
|
|
|
43
|
-
The first is the newer `TableComposable` component. It takes a more explicit and declarative approach, and its implementation more closely mirrors that of an
|
|
43
|
+
The first is the newer `TableComposable` component. It takes a more explicit and declarative approach, and its implementation more closely mirrors that of an HTML table. Generally, updates and new feature requests are implemented in the `TableComposable`.
|
|
44
44
|
|
|
45
45
|
The second is the original `Table` component. It is configuration based and takes a less declarative and more implicit approach about laying out the table structure, such as the rows and cells within it. Its documentation can be found <a href="/components/table/react-legacy">here</a>.
|
|
46
46
|
|
|
@@ -59,8 +59,6 @@ import BlueprintIcon from '@patternfly/react-icons/dist/esm/icons/blueprint-icon
|
|
|
59
59
|
import { css } from '@patternfly/react-styles';
|
|
60
60
|
import styles from '@patternfly/react-styles/css/components/Table/table';
|
|
61
61
|
|
|
62
|
-
import DemoSortableTable from './DemoSortableTable';
|
|
63
|
-
|
|
64
62
|
## TableComposable examples
|
|
65
63
|
|
|
66
64
|
The `TableComposable` component differs from the regular `Table` component, in that it allows you to compose the table by nesting the relevant `Thead`, `Tbody`, `Tr`, `Th` and `Td` components within it. For a less declarative and more implicit approach, use the `Table` component instead.
|
|
@@ -73,73 +71,7 @@ Some general notes:
|
|
|
73
71
|
|
|
74
72
|
### Composable: Basic
|
|
75
73
|
|
|
76
|
-
```
|
|
77
|
-
import React from 'react';
|
|
78
|
-
import { TableComposable, Thead, Tbody, Tr, Th, Td, Caption } from '@patternfly/react-table';
|
|
79
|
-
import { ToggleGroup, ToggleGroupItem } from '@patternfly/react-core';
|
|
80
|
-
|
|
81
|
-
ComposableTableBasic = () => {
|
|
82
|
-
const columns = ['Repositories', 'Branches', 'Pull requests', 'Workspaces', 'Last commit'];
|
|
83
|
-
const rows = [
|
|
84
|
-
['one', 'two', 'three', 'four', 'five'],
|
|
85
|
-
['one - 2', null, null, 'four - 2', 'five - 2'],
|
|
86
|
-
['one - 3', 'two - 3', 'three - 3', 'four - 3', 'five - 3']
|
|
87
|
-
];
|
|
88
|
-
const [choice, setChoice] = React.useState('default');
|
|
89
|
-
const handleItemClick = (isSelected, event) => {
|
|
90
|
-
const id = event.currentTarget.id;
|
|
91
|
-
setChoice(id);
|
|
92
|
-
};
|
|
93
|
-
return (
|
|
94
|
-
<React.Fragment>
|
|
95
|
-
<ToggleGroup aria-label="Default with single selectable">
|
|
96
|
-
<ToggleGroupItem
|
|
97
|
-
text="Default"
|
|
98
|
-
buttonId="default"
|
|
99
|
-
isSelected={choice === 'default'}
|
|
100
|
-
onChange={handleItemClick}
|
|
101
|
-
/>
|
|
102
|
-
<ToggleGroupItem
|
|
103
|
-
text="Compact"
|
|
104
|
-
buttonId="compact"
|
|
105
|
-
isSelected={choice === 'compact'}
|
|
106
|
-
onChange={handleItemClick}
|
|
107
|
-
/>
|
|
108
|
-
<ToggleGroupItem
|
|
109
|
-
text="Compact borderless"
|
|
110
|
-
buttonId="compactBorderless"
|
|
111
|
-
isSelected={choice === 'compactBorderless'}
|
|
112
|
-
onChange={handleItemClick}
|
|
113
|
-
/>
|
|
114
|
-
</ToggleGroup>
|
|
115
|
-
<TableComposable
|
|
116
|
-
aria-label="Simple table"
|
|
117
|
-
variant={choice !== 'default' ? 'compact' : null}
|
|
118
|
-
borders={choice !== 'compactBorderless'}
|
|
119
|
-
>
|
|
120
|
-
<Caption>Simple table using composable components</Caption>
|
|
121
|
-
<Thead>
|
|
122
|
-
<Tr>
|
|
123
|
-
{columns.map((column, columnIndex) => (
|
|
124
|
-
<Th key={columnIndex}>{column}</Th>
|
|
125
|
-
))}
|
|
126
|
-
</Tr>
|
|
127
|
-
</Thead>
|
|
128
|
-
<Tbody>
|
|
129
|
-
{rows.map((row, rowIndex) => (
|
|
130
|
-
<Tr key={rowIndex}>
|
|
131
|
-
{row.map((cell, cellIndex) => (
|
|
132
|
-
<Td key={`${rowIndex}_${cellIndex}`} dataLabel={columns[cellIndex]}>
|
|
133
|
-
{cell}
|
|
134
|
-
</Td>
|
|
135
|
-
))}
|
|
136
|
-
</Tr>
|
|
137
|
-
))}
|
|
138
|
-
</Tbody>
|
|
139
|
-
</TableComposable>
|
|
140
|
-
</React.Fragment>
|
|
141
|
-
);
|
|
142
|
-
};
|
|
74
|
+
```ts file="ComposableTableBasic.tsx"
|
|
143
75
|
```
|
|
144
76
|
|
|
145
77
|
### Composable: Custom row wrapper, header tooltips & popovers
|
|
@@ -150,97 +82,7 @@ ComposableTableBasic = () => {
|
|
|
150
82
|
|
|
151
83
|
To add a header tooltip or popover to `Th`, pass a `ThInfoType` object via the `info` prop.
|
|
152
84
|
|
|
153
|
-
```
|
|
154
|
-
import React from 'react';
|
|
155
|
-
import { TableComposable, Thead, Tbody, Tr, Th, Td } from '@patternfly/react-table';
|
|
156
|
-
|
|
157
|
-
ComposableTableMisc = () => {
|
|
158
|
-
const columns = ['Repositories', 'Branches', 'Pull requests', 'Workspaces', 'Last commit'];
|
|
159
|
-
const rows = [
|
|
160
|
-
['one', 'two', 'three', 'four', 'five'],
|
|
161
|
-
[{ title: 'one - 2', colSpan: 3 }, null, null, 'four - 2', 'five - 2'],
|
|
162
|
-
['one - 3', 'two - 3', 'three - 3', 'four - 3', { title: 'five - 3 (not centered)', textCenter: false }]
|
|
163
|
-
];
|
|
164
|
-
|
|
165
|
-
return (
|
|
166
|
-
<TableComposable aria-label="Misc table">
|
|
167
|
-
<Thead noWrap>
|
|
168
|
-
<Tr>
|
|
169
|
-
<Th
|
|
170
|
-
info={{
|
|
171
|
-
tooltip: 'More information about repositories',
|
|
172
|
-
className: 'repositories-info-tip',
|
|
173
|
-
tooltipProps: {
|
|
174
|
-
isContentLeftAligned: true
|
|
175
|
-
}
|
|
176
|
-
}}
|
|
177
|
-
>
|
|
178
|
-
{columns[0]}
|
|
179
|
-
</Th>
|
|
180
|
-
<Th>{columns[1]}</Th>
|
|
181
|
-
<Th
|
|
182
|
-
info={{
|
|
183
|
-
popover: (
|
|
184
|
-
<div>
|
|
185
|
-
More <strong>information</strong> on pull requests
|
|
186
|
-
</div>
|
|
187
|
-
),
|
|
188
|
-
ariaLabel: 'More information on pull requests',
|
|
189
|
-
popoverProps: {
|
|
190
|
-
headerContent: 'Pull requests',
|
|
191
|
-
footerContent: <a href="">Click here for even more info</a>
|
|
192
|
-
}
|
|
193
|
-
}}
|
|
194
|
-
>
|
|
195
|
-
{columns[2]}
|
|
196
|
-
</Th>
|
|
197
|
-
<Th>{columns[3]}</Th>
|
|
198
|
-
<Th textCenter>{columns[4]}</Th>
|
|
199
|
-
</Tr>
|
|
200
|
-
</Thead>
|
|
201
|
-
<Tbody>
|
|
202
|
-
{rows.map((row, rowIndex) => {
|
|
203
|
-
const isOddRow = (rowIndex + 1) % 2;
|
|
204
|
-
const customStyle = {
|
|
205
|
-
borderLeft: '3px solid var(--pf-global--primary-color--100)'
|
|
206
|
-
};
|
|
207
|
-
return (
|
|
208
|
-
<Tr
|
|
209
|
-
key={rowIndex}
|
|
210
|
-
className={isOddRow ? 'odd-row-class' : 'even-row-class'}
|
|
211
|
-
style={isOddRow ? customStyle : {}}
|
|
212
|
-
>
|
|
213
|
-
{row.map((cell, cellIndex) => {
|
|
214
|
-
if (!cell) {
|
|
215
|
-
return null;
|
|
216
|
-
}
|
|
217
|
-
let cellObject;
|
|
218
|
-
if (typeof cell !== 'object') {
|
|
219
|
-
cellObject = {
|
|
220
|
-
title: cell
|
|
221
|
-
};
|
|
222
|
-
} else {
|
|
223
|
-
cellObject = cell;
|
|
224
|
-
}
|
|
225
|
-
const { title, ...rest } = cellObject;
|
|
226
|
-
return (
|
|
227
|
-
<Td
|
|
228
|
-
key={`${rowIndex}_${cellIndex}`}
|
|
229
|
-
dataLabel={columns[cellIndex]}
|
|
230
|
-
textCenter={cellIndex === row.length - 1}
|
|
231
|
-
{...rest}
|
|
232
|
-
>
|
|
233
|
-
{title}
|
|
234
|
-
</Td>
|
|
235
|
-
);
|
|
236
|
-
})}
|
|
237
|
-
</Tr>
|
|
238
|
-
);
|
|
239
|
-
})}
|
|
240
|
-
</Tbody>
|
|
241
|
-
</TableComposable>
|
|
242
|
-
);
|
|
243
|
-
};
|
|
85
|
+
```ts file="ComposableTableMisc.tsx"
|
|
244
86
|
```
|
|
245
87
|
|
|
246
88
|
### Composable: Sortable & wrapping headers
|
|
@@ -261,256 +103,14 @@ type OnSort = (
|
|
|
261
103
|
|
|
262
104
|
The built in display for sorting is not fully responsive, as the column headers will be displayed per row when the screen size is small. To see a full page demo of a responsive sortable table, utilizing a toolbar item to control sorting for small screens, view the `Sortable - responsive` demo in the `React demos` tab.
|
|
263
105
|
|
|
264
|
-
```
|
|
265
|
-
import React from 'react';
|
|
266
|
-
import { TableComposable, Thead, Tbody, Tr, Th, Td } from '@patternfly/react-table';
|
|
267
|
-
|
|
268
|
-
ComposableTableSortable = () => {
|
|
269
|
-
const columns = [
|
|
270
|
-
'This is a really long table header that goes on for a long time 1.',
|
|
271
|
-
'This is a really long table header that goes on for a long time 2.',
|
|
272
|
-
'This is a really long table header that goes on for a long time 3.',
|
|
273
|
-
'This is a really long table header that goes on for a long time 4.',
|
|
274
|
-
'This is a really long table header that goes on for a long time 5.'
|
|
275
|
-
];
|
|
276
|
-
const [rows, setRows] = React.useState([
|
|
277
|
-
['one', 'two', 'a', 'four', 'five'],
|
|
278
|
-
['a', 'two', 'k', 'four', 'five'],
|
|
279
|
-
['p', 'two', 'b', 'four', 'five']
|
|
280
|
-
]);
|
|
281
|
-
// index of the currently active column
|
|
282
|
-
const [activeSortIndex, setActiveSortIndex] = React.useState(-1);
|
|
283
|
-
// sort direction of the currently active column
|
|
284
|
-
const [activeSortDirection, setActiveSortDirection] = React.useState('none');
|
|
285
|
-
const onSort = (event, index, direction) => {
|
|
286
|
-
setActiveSortIndex(index);
|
|
287
|
-
setActiveSortDirection(direction);
|
|
288
|
-
// sorts the rows
|
|
289
|
-
const updatedRows = rows.sort((a, b) => {
|
|
290
|
-
if (typeof a[index] === 'number') {
|
|
291
|
-
// numeric sort
|
|
292
|
-
if (direction === 'asc') {
|
|
293
|
-
return a[index] - b[index];
|
|
294
|
-
}
|
|
295
|
-
return b[index] - a[index];
|
|
296
|
-
} else {
|
|
297
|
-
// string sort
|
|
298
|
-
if (direction === 'asc') {
|
|
299
|
-
return a[index].localeCompare(b[index]);
|
|
300
|
-
}
|
|
301
|
-
return b[index].localeCompare(a[index]);
|
|
302
|
-
}
|
|
303
|
-
});
|
|
304
|
-
setRows(updatedRows);
|
|
305
|
-
};
|
|
306
|
-
return (
|
|
307
|
-
<TableComposable aria-label="Sortable Table">
|
|
308
|
-
<Thead>
|
|
309
|
-
<Tr>
|
|
310
|
-
{columns.map((column, columnIndex) => {
|
|
311
|
-
// In this example, wrap all but the first column just to demonstrate
|
|
312
|
-
const modifier = columnIndex !== 0 ? 'wrap' : null;
|
|
313
|
-
// In this example, make the 1st and 3rd columns sortable
|
|
314
|
-
const sortParams =
|
|
315
|
-
columnIndex === 0 || columnIndex === 2
|
|
316
|
-
? {
|
|
317
|
-
sort: {
|
|
318
|
-
sortBy: {
|
|
319
|
-
index: activeSortIndex,
|
|
320
|
-
direction: activeSortDirection,
|
|
321
|
-
defaultDirection: 'asc' // starting sort direction when first sorting a column. Defaults to 'asc'
|
|
322
|
-
},
|
|
323
|
-
onSort,
|
|
324
|
-
columnIndex
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
: {};
|
|
328
|
-
const infoParams =
|
|
329
|
-
columnIndex === 2
|
|
330
|
-
? {
|
|
331
|
-
info: {
|
|
332
|
-
tooltip: 'More information'
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
: {};
|
|
336
|
-
return (
|
|
337
|
-
<Th key={columnIndex} modifier={modifier} {...sortParams} {...infoParams}>
|
|
338
|
-
{column}
|
|
339
|
-
</Th>
|
|
340
|
-
);
|
|
341
|
-
})}
|
|
342
|
-
</Tr>
|
|
343
|
-
</Thead>
|
|
344
|
-
<Tbody>
|
|
345
|
-
{rows.map((row, rowIndex) => (
|
|
346
|
-
<Tr key={rowIndex}>
|
|
347
|
-
{row.map((cell, cellIndex) => (
|
|
348
|
-
<Td key={`${rowIndex}_${cellIndex}`} dataLabel={columns[cellIndex]}>
|
|
349
|
-
{cell}
|
|
350
|
-
</Td>
|
|
351
|
-
))}
|
|
352
|
-
</Tr>
|
|
353
|
-
))}
|
|
354
|
-
</Tbody>
|
|
355
|
-
</TableComposable>
|
|
356
|
-
);
|
|
357
|
-
};
|
|
106
|
+
```ts file="ComposableTableSortable.tsx"
|
|
358
107
|
```
|
|
359
108
|
|
|
360
109
|
### Composable: Sortable - custom control
|
|
361
110
|
|
|
362
111
|
Sorting a table may also be controlled manually with a toolbar control. To see a full page demo of a responsive table, view the `Sortable - responsive` demo in the `React demos` tab.
|
|
363
112
|
|
|
364
|
-
```
|
|
365
|
-
import React from 'react';
|
|
366
|
-
import { TableComposable, Thead, Tbody, Tr, Th, Td } from '@patternfly/react-table';
|
|
367
|
-
import {
|
|
368
|
-
Toolbar,
|
|
369
|
-
ToolbarItem,
|
|
370
|
-
ToolbarContent,
|
|
371
|
-
OptionsMenu,
|
|
372
|
-
OptionsMenuItemGroup,
|
|
373
|
-
OptionsMenuItem,
|
|
374
|
-
OptionsMenuSeparator,
|
|
375
|
-
OptionsMenuToggle
|
|
376
|
-
} from '@patternfly/react-core';
|
|
377
|
-
import SortAmountDownIcon from '@patternfly/react-icons/dist/esm/icons/sort-amount-down-icon';
|
|
378
|
-
|
|
379
|
-
ComposableTableSortable = () => {
|
|
380
|
-
const columns = ['Column 1', 'Column 2', 'Column 3', 'Column 4', 'Column 5'];
|
|
381
|
-
const [rows, setRows] = React.useState([
|
|
382
|
-
['one', 'two', 'a', 'four', 'five'],
|
|
383
|
-
['a', 'two', 'k', 'four', 'five'],
|
|
384
|
-
['p', 'two', 'b', 'four', 'five']
|
|
385
|
-
]);
|
|
386
|
-
// index of the currently active column
|
|
387
|
-
const [activeSortIndex, setActiveSortIndex] = React.useState(-1);
|
|
388
|
-
// sort direction of the currently active column
|
|
389
|
-
const [activeSortDirection, setActiveSortDirection] = React.useState('none');
|
|
390
|
-
// sort dropdown expansion
|
|
391
|
-
const [isSortDropdownOpen, setIsSortDropdownOpen] = React.useState(false);
|
|
392
|
-
const onSort = (event, index, direction) => {
|
|
393
|
-
setActiveSortIndex(index);
|
|
394
|
-
setActiveSortDirection(direction);
|
|
395
|
-
console.log('i; ', index, direction);
|
|
396
|
-
// sorts the rows
|
|
397
|
-
const updatedRows = rows.sort((a, b) => {
|
|
398
|
-
if (typeof a[index] === 'number') {
|
|
399
|
-
// numeric sort
|
|
400
|
-
if (direction === 'asc') {
|
|
401
|
-
return a[index] - b[index];
|
|
402
|
-
}
|
|
403
|
-
return b[index] - a[index];
|
|
404
|
-
} else {
|
|
405
|
-
// string sort
|
|
406
|
-
if (direction === 'asc') {
|
|
407
|
-
return a[index].localeCompare(b[index]);
|
|
408
|
-
}
|
|
409
|
-
return b[index].localeCompare(a[index]);
|
|
410
|
-
}
|
|
411
|
-
});
|
|
412
|
-
setRows(updatedRows);
|
|
413
|
-
};
|
|
414
|
-
return (
|
|
415
|
-
<React.Fragment>
|
|
416
|
-
<Toolbar id="toolbar">
|
|
417
|
-
<ToolbarContent>
|
|
418
|
-
<ToolbarItem>
|
|
419
|
-
<OptionsMenu
|
|
420
|
-
id="options-menu-multiple-options-example"
|
|
421
|
-
menuItems={[
|
|
422
|
-
<OptionsMenuItemGroup key="first group" aria-label="Sort column">
|
|
423
|
-
{columns.map((column, columnIndex) => (
|
|
424
|
-
<OptionsMenuItem
|
|
425
|
-
key={column}
|
|
426
|
-
isSelected={activeSortIndex === columnIndex}
|
|
427
|
-
onSelect={evt =>
|
|
428
|
-
onSort(evt, columnIndex, activeSortDirection !== 'none' ? activeSortDirection : 'asc')
|
|
429
|
-
}
|
|
430
|
-
>
|
|
431
|
-
{column}
|
|
432
|
-
</OptionsMenuItem>
|
|
433
|
-
))}
|
|
434
|
-
</OptionsMenuItemGroup>,
|
|
435
|
-
<OptionsMenuSeparator key="separator" />,
|
|
436
|
-
<OptionsMenuItemGroup key="second group" aria-label="Sort direction">
|
|
437
|
-
<OptionsMenuItem
|
|
438
|
-
onSelect={evt => onSort(evt, activeSortIndex, 'asc')}
|
|
439
|
-
isSelected={activeSortDirection === 'asc'}
|
|
440
|
-
id="ascending"
|
|
441
|
-
key="ascending"
|
|
442
|
-
>
|
|
443
|
-
Ascending
|
|
444
|
-
</OptionsMenuItem>
|
|
445
|
-
<OptionsMenuItem
|
|
446
|
-
onSelect={evt => onSort(evt, activeSortIndex, 'desc')}
|
|
447
|
-
isSelected={activeSortDirection === 'desc'}
|
|
448
|
-
id="descending"
|
|
449
|
-
key="descending"
|
|
450
|
-
>
|
|
451
|
-
Descending
|
|
452
|
-
</OptionsMenuItem>
|
|
453
|
-
</OptionsMenuItemGroup>
|
|
454
|
-
]}
|
|
455
|
-
isOpen={isSortDropdownOpen}
|
|
456
|
-
toggle={
|
|
457
|
-
<OptionsMenuToggle
|
|
458
|
-
hideCaret
|
|
459
|
-
onToggle={() => setIsSortDropdownOpen(!isSortDropdownOpen)}
|
|
460
|
-
toggleTemplate={<SortAmountDownIcon />}
|
|
461
|
-
/>
|
|
462
|
-
}
|
|
463
|
-
isPlain
|
|
464
|
-
isGrouped
|
|
465
|
-
/>
|
|
466
|
-
</ToolbarItem>
|
|
467
|
-
</ToolbarContent>
|
|
468
|
-
</Toolbar>
|
|
469
|
-
<TableComposable aria-label="Sortable Table">
|
|
470
|
-
<Thead>
|
|
471
|
-
<Tr>
|
|
472
|
-
{columns.map((column, columnIndex) => {
|
|
473
|
-
const sortParams = {
|
|
474
|
-
sort: {
|
|
475
|
-
sortBy: {
|
|
476
|
-
index: activeSortIndex,
|
|
477
|
-
direction: activeSortDirection
|
|
478
|
-
},
|
|
479
|
-
onSort,
|
|
480
|
-
columnIndex
|
|
481
|
-
}
|
|
482
|
-
};
|
|
483
|
-
const infoParams =
|
|
484
|
-
columnIndex === 2
|
|
485
|
-
? {
|
|
486
|
-
info: {
|
|
487
|
-
tooltip: 'More information'
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
: {};
|
|
491
|
-
return (
|
|
492
|
-
<Th key={columnIndex} {...sortParams} {...infoParams}>
|
|
493
|
-
{column}
|
|
494
|
-
</Th>
|
|
495
|
-
);
|
|
496
|
-
})}
|
|
497
|
-
</Tr>
|
|
498
|
-
</Thead>
|
|
499
|
-
<Tbody>
|
|
500
|
-
{rows.map((row, rowIndex) => (
|
|
501
|
-
<Tr key={rowIndex}>
|
|
502
|
-
{row.map((cell, cellIndex) => (
|
|
503
|
-
<Td key={`${rowIndex}_${cellIndex}`} dataLabel={columns[cellIndex]}>
|
|
504
|
-
{cell}
|
|
505
|
-
</Td>
|
|
506
|
-
))}
|
|
507
|
-
</Tr>
|
|
508
|
-
))}
|
|
509
|
-
</Tbody>
|
|
510
|
-
</TableComposable>
|
|
511
|
-
</React.Fragment>
|
|
512
|
-
);
|
|
513
|
-
};
|
|
113
|
+
```ts file="ComposableTableSortableCustom.tsx"
|
|
514
114
|
```
|
|
515
115
|
|
|
516
116
|
### Composable: Selectable with checkbox
|
|
@@ -540,369 +140,30 @@ type OnSelect = (
|
|
|
540
140
|
**Note:** This example has a `shift + select` feature where holding shift while
|
|
541
141
|
checking checkboxes will check intermediate rows' checkboxes.
|
|
542
142
|
|
|
543
|
-
```
|
|
544
|
-
import React from 'react';
|
|
545
|
-
import { TableComposable, Thead, Tbody, Tr, Th, Td } from '@patternfly/react-table';
|
|
546
|
-
|
|
547
|
-
ComposableTableSelectable = () => {
|
|
548
|
-
const columns = ['Repositories', 'Branches', 'Pull requests', 'Workspaces', 'Last commit'];
|
|
549
|
-
const rows = [
|
|
550
|
-
['one', 'two', 'a', 'four', 'five'],
|
|
551
|
-
['a', 'two', 'k', 'four', 'five'],
|
|
552
|
-
['a', 'two', 'k', 'four', 'five'],
|
|
553
|
-
['a', 'two', 'k', 'four', 'five'],
|
|
554
|
-
['a', 'two', 'k', 'four', 'five'],
|
|
555
|
-
['p', 'two', 'b', 'four', 'five']
|
|
556
|
-
];
|
|
557
|
-
const [allRowsSelected, setAllRowsSelected] = React.useState(false);
|
|
558
|
-
const [selected, setSelected] = React.useState(rows.map(row => false));
|
|
559
|
-
const [recentSelection, setRecentSelection] = React.useState(null);
|
|
560
|
-
const [shifting, setShifting] = React.useState(false);
|
|
561
|
-
const onSelect = (event, isSelected, rowId) => {
|
|
562
|
-
let newSelected = selected.map((sel, index) => (index === rowId ? isSelected : sel));
|
|
563
|
-
|
|
564
|
-
// if the user is shift + selecting the checkboxes, then all intermediate checkboxes should be selected
|
|
565
|
-
if (shifting && recentSelection !== null && isSelected) {
|
|
566
|
-
const numberSelected = rowId - recentSelection;
|
|
567
|
-
newSelected = newSelected.map((sel, index) => {
|
|
568
|
-
// select all between recentSelection and current rowId;
|
|
569
|
-
const intermediateIndexes =
|
|
570
|
-
numberSelected > 0
|
|
571
|
-
? Array.from(new Array(numberSelected + 1), (x, i) => i + recentSelection)
|
|
572
|
-
: Array.from(new Array(Math.abs(numberSelected) + 1), (x, i) => i + rowId);
|
|
573
|
-
return intermediateIndexes.includes(index) ? true : sel;
|
|
574
|
-
});
|
|
575
|
-
}
|
|
576
|
-
setSelected(newSelected);
|
|
577
|
-
setRecentSelection(rowId);
|
|
578
|
-
|
|
579
|
-
if (!isSelected && allRowsSelected) {
|
|
580
|
-
setAllRowsSelected(false);
|
|
581
|
-
} else if (isSelected && !allRowsSelected) {
|
|
582
|
-
let allSelected = true;
|
|
583
|
-
for (let i = 0; i < selected.length; i++) {
|
|
584
|
-
if (i !== rowId) {
|
|
585
|
-
if (!selected[i]) {
|
|
586
|
-
allSelected = false;
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
if (allSelected) {
|
|
591
|
-
setAllRowsSelected(true);
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
};
|
|
595
|
-
|
|
596
|
-
onKeyDown = e => {
|
|
597
|
-
if (e.key === 'Shift') {
|
|
598
|
-
setShifting(true);
|
|
599
|
-
}
|
|
600
|
-
};
|
|
601
|
-
onKeyUp = e => {
|
|
602
|
-
if (e.key === 'Shift') {
|
|
603
|
-
setShifting(false);
|
|
604
|
-
}
|
|
605
|
-
};
|
|
606
|
-
|
|
607
|
-
React.useEffect(() => {
|
|
608
|
-
document.addEventListener('keydown', onKeyDown);
|
|
609
|
-
document.addEventListener('keyup', onKeyUp);
|
|
610
|
-
|
|
611
|
-
return () => {
|
|
612
|
-
document.removeEventListener('keydown', onKeyDown);
|
|
613
|
-
document.removeEventListener('keyup', onKeyUp);
|
|
614
|
-
};
|
|
615
|
-
}, []);
|
|
616
|
-
|
|
617
|
-
const onSelectAll = (event, isSelected) => {
|
|
618
|
-
setAllRowsSelected(isSelected);
|
|
619
|
-
setSelected(selected.map(sel => isSelected));
|
|
620
|
-
};
|
|
621
|
-
|
|
622
|
-
return (
|
|
623
|
-
<TableComposable aria-label="Selectable Table">
|
|
624
|
-
<Thead>
|
|
625
|
-
<Tr>
|
|
626
|
-
<Th
|
|
627
|
-
select={{
|
|
628
|
-
onSelect: onSelectAll,
|
|
629
|
-
isSelected: allRowsSelected
|
|
630
|
-
}}
|
|
631
|
-
/>
|
|
632
|
-
<Th>{columns[0]}</Th>
|
|
633
|
-
<Th>{columns[1]}</Th>
|
|
634
|
-
<Th>{columns[2]}</Th>
|
|
635
|
-
<Th>{columns[3]}</Th>
|
|
636
|
-
<Th>{columns[4]}</Th>
|
|
637
|
-
</Tr>
|
|
638
|
-
</Thead>
|
|
639
|
-
<Tbody>
|
|
640
|
-
{rows.map((row, rowIndex) => (
|
|
641
|
-
<Tr key={rowIndex}>
|
|
642
|
-
<Td
|
|
643
|
-
key={`${rowIndex}_0`}
|
|
644
|
-
select={{
|
|
645
|
-
rowIndex,
|
|
646
|
-
onSelect,
|
|
647
|
-
isSelected: selected[rowIndex] && rowIndex !== 1,
|
|
648
|
-
disable: rowIndex === 1
|
|
649
|
-
}}
|
|
650
|
-
/>
|
|
651
|
-
{row.map((cell, cellIndex) => {
|
|
652
|
-
const shiftedIndex = cellIndex + 1;
|
|
653
|
-
return (
|
|
654
|
-
<Td key={`${rowIndex}_${shiftedIndex}`} dataLabel={columns[cellIndex]}>
|
|
655
|
-
{cell}
|
|
656
|
-
</Td>
|
|
657
|
-
);
|
|
658
|
-
})}
|
|
659
|
-
</Tr>
|
|
660
|
-
))}
|
|
661
|
-
</Tbody>
|
|
662
|
-
</TableComposable>
|
|
663
|
-
);
|
|
664
|
-
};
|
|
143
|
+
```ts file="ComposableTableSelectable.tsx"
|
|
665
144
|
```
|
|
666
145
|
|
|
667
146
|
### Composable: Selectable radio input
|
|
668
147
|
|
|
669
148
|
Similarly to the selectable example above, the radio buttons use the first column. The first header cell is empty, and each body row's first cell has radio button props.
|
|
670
149
|
|
|
671
|
-
```
|
|
672
|
-
import React from 'react';
|
|
673
|
-
import { TableComposable, Thead, Tbody, Tr, Th, Td } from '@patternfly/react-table';
|
|
674
|
-
|
|
675
|
-
ComposableTableSelectableRadio = () => {
|
|
676
|
-
const columns = ['Repositories', 'Branches', 'Pull requests', 'Workspaces', 'Last commit'];
|
|
677
|
-
const rows = [
|
|
678
|
-
['one', 'two', 'a', 'four', 'five'],
|
|
679
|
-
['a', 'two', 'k', 'four', 'five'],
|
|
680
|
-
['p', 'two', 'b', 'four', 'five']
|
|
681
|
-
];
|
|
682
|
-
const [selected, setSelected] = React.useState(-1);
|
|
683
|
-
const onSelect = (event, isSelected, rowId) => {
|
|
684
|
-
setSelected(rowId);
|
|
685
|
-
};
|
|
686
|
-
return (
|
|
687
|
-
<TableComposable aria-label="Radio selectable table">
|
|
688
|
-
<Thead>
|
|
689
|
-
<Tr>
|
|
690
|
-
<Th />
|
|
691
|
-
<Th>{columns[0]}</Th>
|
|
692
|
-
<Th>{columns[1]}</Th>
|
|
693
|
-
<Th>{columns[2]}</Th>
|
|
694
|
-
<Th>{columns[3]}</Th>
|
|
695
|
-
<Th>{columns[4]}</Th>
|
|
696
|
-
</Tr>
|
|
697
|
-
</Thead>
|
|
698
|
-
<Tbody>
|
|
699
|
-
{rows.map((row, rowIndex) => (
|
|
700
|
-
<Tr key={rowIndex}>
|
|
701
|
-
<Td
|
|
702
|
-
key={`${rowIndex}_0`}
|
|
703
|
-
select={{
|
|
704
|
-
rowIndex,
|
|
705
|
-
onSelect,
|
|
706
|
-
isSelected: selected === rowIndex,
|
|
707
|
-
disable: rowIndex === 1,
|
|
708
|
-
variant: 'radio'
|
|
709
|
-
}}
|
|
710
|
-
/>
|
|
711
|
-
{row.map((cell, cellIndex) => {
|
|
712
|
-
const shiftedIndex = cellIndex + 1;
|
|
713
|
-
return (
|
|
714
|
-
<Td key={`${rowIndex}_${shiftedIndex}`} dataLabel={columns[cellIndex]}>
|
|
715
|
-
{cell}
|
|
716
|
-
</Td>
|
|
717
|
-
);
|
|
718
|
-
})}
|
|
719
|
-
</Tr>
|
|
720
|
-
))}
|
|
721
|
-
</Tbody>
|
|
722
|
-
</TableComposable>
|
|
723
|
-
);
|
|
724
|
-
};
|
|
150
|
+
```ts file="ComposableTableSelectableRadio.tsx"
|
|
725
151
|
```
|
|
726
152
|
|
|
727
153
|
### Composable: Row click handler, hoverable & selected rows
|
|
728
154
|
|
|
729
155
|
This selectable rows feature is intended for use when a table is used to present a list of objects in a Primary-detail view.
|
|
730
156
|
|
|
731
|
-
```
|
|
732
|
-
import React from 'react';
|
|
733
|
-
import { TableComposable, Thead, Tbody, Tr, Th, Td } from '@patternfly/react-table';
|
|
734
|
-
|
|
735
|
-
ComposableTableHoverable = () => {
|
|
736
|
-
const columns = ['Repositories', 'Branches', 'Pull requests', 'Workspaces', 'Last commit'];
|
|
737
|
-
const [rows, setRows] = React.useState([
|
|
738
|
-
{ cells: ['one', 'two', 'a', 'four', 'five'], isRowSelected: false },
|
|
739
|
-
{ cells: ['a', 'two', 'k', 'four', 'five'], isRowSelected: false },
|
|
740
|
-
{ cells: ['p', 'two', 'b', 'four', 'five'], isRowSelected: false }
|
|
741
|
-
]);
|
|
742
|
-
const onRowClick = (event, rowIndex, row) => {
|
|
743
|
-
const updatedRows = [...rows];
|
|
744
|
-
updatedRows[rowIndex].isRowSelected = !rows[rowIndex].isRowSelected;
|
|
745
|
-
setRows(updatedRows);
|
|
746
|
-
};
|
|
747
|
-
|
|
748
|
-
return (
|
|
749
|
-
<TableComposable aria-label="Misc table">
|
|
750
|
-
<Thead noWrap>
|
|
751
|
-
<Tr>
|
|
752
|
-
<Th>{columns[0]}</Th>
|
|
753
|
-
<Th>{columns[1]}</Th>
|
|
754
|
-
<Th>{columns[2]}</Th>
|
|
755
|
-
<Th>{columns[3]}</Th>
|
|
756
|
-
<Th>{columns[4]}</Th>
|
|
757
|
-
</Tr>
|
|
758
|
-
</Thead>
|
|
759
|
-
<Tbody>
|
|
760
|
-
{rows.map((row, rowIndex) => {
|
|
761
|
-
return (
|
|
762
|
-
<Tr
|
|
763
|
-
key={rowIndex}
|
|
764
|
-
onRowClick={event => onRowClick(event, rowIndex, row.cells)}
|
|
765
|
-
isHoverable
|
|
766
|
-
isRowSelected={row.isRowSelected}
|
|
767
|
-
>
|
|
768
|
-
{row.cells.map((cell, cellIndex) => {
|
|
769
|
-
return (
|
|
770
|
-
<Td key={`${rowIndex}_${cellIndex}`} dataLabel={columns[cellIndex]}>
|
|
771
|
-
{cell}
|
|
772
|
-
</Td>
|
|
773
|
-
);
|
|
774
|
-
})}
|
|
775
|
-
</Tr>
|
|
776
|
-
);
|
|
777
|
-
})}
|
|
778
|
-
</Tbody>
|
|
779
|
-
</TableComposable>
|
|
780
|
-
);
|
|
781
|
-
};
|
|
157
|
+
```ts file="ComposableTableHoverable.tsx"
|
|
782
158
|
```
|
|
783
159
|
|
|
784
160
|
### Composable: Actions
|
|
785
161
|
|
|
786
162
|
This example demonstrates adding actions as the last column. The header's last cell is an empty cell, and each body row's last cell is an action cell.
|
|
787
163
|
|
|
788
|
-
To make a cell an action cell,
|
|
789
|
-
|
|
790
|
-
```
|
|
791
|
-
import React from 'react';
|
|
792
|
-
import { TableComposable, Thead, Tbody, Tr, Th, Td } from '@patternfly/react-table';
|
|
793
|
-
import { ButtonVariant, DropdownToggle, ToggleGroup, ToggleGroupItem } from '@patternfly/react-core';
|
|
794
|
-
|
|
795
|
-
ComposableTableActions = () => {
|
|
796
|
-
const defaultActions = [
|
|
797
|
-
{
|
|
798
|
-
title: 'Some action',
|
|
799
|
-
onClick: (event, rowId, rowData, extra) => console.log('clicked on Some action, on row: ', rowId)
|
|
800
|
-
},
|
|
801
|
-
{
|
|
802
|
-
title: <a href="https://www.patternfly.org">Link action</a>
|
|
803
|
-
},
|
|
804
|
-
{
|
|
805
|
-
isSeparator: true
|
|
806
|
-
},
|
|
807
|
-
{
|
|
808
|
-
title: 'Third action',
|
|
809
|
-
onClick: (event, rowId, rowData, extra) => console.log('clicked on Third action, on row: ', rowId)
|
|
810
|
-
},
|
|
811
|
-
{
|
|
812
|
-
title: 'Start',
|
|
813
|
-
variant: ButtonVariant.secondary,
|
|
814
|
-
onClick: (event, rowId, rowData, extra) => console.log('clicked on extra action, on row: ', rowId),
|
|
815
|
-
isOutsideDropdown: true
|
|
816
|
-
}
|
|
817
|
-
];
|
|
818
|
-
const lastRowActions = [
|
|
819
|
-
{
|
|
820
|
-
title: 'Some action',
|
|
821
|
-
onClick: (event, rowId, rowData, extra) => console.log(`clicked on Some action, on row ${rowId}`)
|
|
822
|
-
},
|
|
823
|
-
{
|
|
824
|
-
title: <div>Another action</div>,
|
|
825
|
-
onClick: (event, rowId, rowData, extra) => console.log(`clicked on Another action, on row ${rowId}`)
|
|
826
|
-
},
|
|
827
|
-
{
|
|
828
|
-
isSeparator: true
|
|
829
|
-
},
|
|
830
|
-
{
|
|
831
|
-
title: 'Third action',
|
|
832
|
-
onClick: (event, rowId, rowData, extra) => console.log(`clicked on Third action, on row ${rowId}`)
|
|
833
|
-
}
|
|
834
|
-
];
|
|
835
|
-
const columns = ['Repositories', 'Branches', 'Pull requests', 'Workspaces', 'Last commit'];
|
|
836
|
-
const rows = [
|
|
837
|
-
['one', 'two', 'a', 'four', 'five'],
|
|
838
|
-
['a', 'two', 'k', 'four', 'five'],
|
|
839
|
-
['p', 'two', 'b', 'four', 'five'],
|
|
840
|
-
['4', '2', 'b', 'four', 'five'],
|
|
841
|
-
['5', '2', 'b', 'four', 'five']
|
|
842
|
-
];
|
|
843
|
-
|
|
844
|
-
const [toggle, setToggle] = React.useState('defaultToggle');
|
|
845
|
-
|
|
846
|
-
const customActionsToggle = props => (
|
|
847
|
-
<DropdownToggle onToggle={props.onToggle} isDisabled={props.isDisabled}>
|
|
848
|
-
Actions
|
|
849
|
-
</DropdownToggle>
|
|
850
|
-
);
|
|
851
|
-
|
|
852
|
-
const toggleCustomToggle = (checked, event) => {
|
|
853
|
-
setToggle(event.currentTarget.id);
|
|
854
|
-
};
|
|
855
|
-
|
|
856
|
-
return (
|
|
857
|
-
<React.Fragment>
|
|
858
|
-
<ToggleGroup aria-label="Default uses kebab toggle">
|
|
859
|
-
<ToggleGroupItem
|
|
860
|
-
text="Default actions toggle"
|
|
861
|
-
buttonId="defaultToggle"
|
|
862
|
-
isSelected={toggle === 'defaultToggle'}
|
|
863
|
-
onChange={toggleCustomToggle}
|
|
864
|
-
/>
|
|
865
|
-
<ToggleGroupItem
|
|
866
|
-
text="Custom actions toggle"
|
|
867
|
-
buttonId="customToggle"
|
|
868
|
-
isSelected={toggle === 'customToggle'}
|
|
869
|
-
onChange={toggleCustomToggle}
|
|
870
|
-
/>
|
|
871
|
-
</ToggleGroup>
|
|
872
|
-
<TableComposable aria-label="Actions table">
|
|
873
|
-
<Thead>
|
|
874
|
-
<Tr>
|
|
875
|
-
<Th>{columns[0]}</Th>
|
|
876
|
-
<Th>{columns[1]}</Th>
|
|
877
|
-
<Th>{columns[2]}</Th>
|
|
878
|
-
<Th>{columns[3]}</Th>
|
|
879
|
-
<Th>{columns[4]}</Th>
|
|
880
|
-
<Th />
|
|
881
|
-
</Tr>
|
|
882
|
-
</Thead>
|
|
883
|
-
<Tbody>
|
|
884
|
-
{rows.map((row, rowIndex) => (
|
|
885
|
-
<Tr key={rowIndex}>
|
|
886
|
-
{row.map((cell, cellIndex) => (
|
|
887
|
-
<Td key={`${rowIndex}_${cellIndex}`} dataLabel={columns[cellIndex]}>
|
|
888
|
-
{cell}
|
|
889
|
-
</Td>
|
|
890
|
-
))}
|
|
891
|
-
<Td
|
|
892
|
-
key={`${rowIndex}_5`}
|
|
893
|
-
actions={{
|
|
894
|
-
items: rowIndex === 1 ? null : rowIndex === 4 ? lastRowActions : defaultActions,
|
|
895
|
-
disable: rowIndex === 3,
|
|
896
|
-
actionsToggle: toggle === 'customToggle' ? customActionsToggle : undefined
|
|
897
|
-
}}
|
|
898
|
-
/>
|
|
899
|
-
</Tr>
|
|
900
|
-
))}
|
|
901
|
-
</Tbody>
|
|
902
|
-
</TableComposable>
|
|
903
|
-
</React.Fragment>
|
|
904
|
-
);
|
|
905
|
-
};
|
|
164
|
+
To make a cell an action cell, render an `ActionsColumn` component inside a row's last `Td` and pass an array of `IAction` objects via the `items` prop of `ActionsColumn`.
|
|
165
|
+
|
|
166
|
+
```ts file="ComposableTableActions.tsx"
|
|
906
167
|
```
|
|
907
168
|
|
|
908
169
|
### Composable: Expandable
|
|
@@ -925,165 +186,7 @@ type OnCollapse = (
|
|
|
925
186
|
) => void;
|
|
926
187
|
```
|
|
927
188
|
|
|
928
|
-
```
|
|
929
|
-
import React from 'react';
|
|
930
|
-
import { TableComposable, Thead, Tbody, Tr, Th, Td, ExpandableRowContent } from '@patternfly/react-table';
|
|
931
|
-
import { Checkbox } from '@patternfly/react-core';
|
|
932
|
-
|
|
933
|
-
ComposableTableExpandable = () => {
|
|
934
|
-
const columns = ['Repositories', 'Branches', 'Pull requests', 'Workspaces', 'Last commit'];
|
|
935
|
-
const rowPairs = [
|
|
936
|
-
{ parent: ['one', 'two', 'a', 'four', 'five'], child: null },
|
|
937
|
-
{
|
|
938
|
-
parent: ['parent 1', 'two', 'k', 'four', 'five'],
|
|
939
|
-
child: ['single cell']
|
|
940
|
-
},
|
|
941
|
-
{
|
|
942
|
-
parent: ['parent 2', 'two', 'b', 'four', 'five'],
|
|
943
|
-
child: ['single cell - fullWidth'],
|
|
944
|
-
fullWidth: true
|
|
945
|
-
},
|
|
946
|
-
{
|
|
947
|
-
parent: ['parent 3', '2', 'b', 'four', 'five'],
|
|
948
|
-
child: ['single cell - noPadding'],
|
|
949
|
-
noPadding: true
|
|
950
|
-
},
|
|
951
|
-
{
|
|
952
|
-
parent: ['parent 4', '2', 'b', 'four', 'five'],
|
|
953
|
-
child: ['single cell - fullWidth & noPadding'],
|
|
954
|
-
fullWidth: true,
|
|
955
|
-
noPadding: true
|
|
956
|
-
},
|
|
957
|
-
{
|
|
958
|
-
parent: ['parent 5', '2', 'b', 'four', 'five'],
|
|
959
|
-
child: ["spans 'Repositories' and 'Branches'", "spans 'Pull requests' and 'Workspaces', and 'Last commit'"]
|
|
960
|
-
},
|
|
961
|
-
{
|
|
962
|
-
parent: ['parent 6', '2', 'b', 'four', 'five'],
|
|
963
|
-
child: [
|
|
964
|
-
"fullWidth, spans the collapsible column and 'Repositories'",
|
|
965
|
-
"fullWidth, spans 'Branches' and 'Pull requests'",
|
|
966
|
-
"fullWidth, spans 'Workspaces' and 'Last commit'"
|
|
967
|
-
],
|
|
968
|
-
fullWidth: true
|
|
969
|
-
}
|
|
970
|
-
];
|
|
971
|
-
const numColumns = columns.length;
|
|
972
|
-
// Init all to true
|
|
973
|
-
const [expanded, setExpanded] = React.useState(
|
|
974
|
-
Object.fromEntries(Object.entries(rowPairs).map(([k, v]) => [k, Boolean(v.child)]))
|
|
975
|
-
);
|
|
976
|
-
const [compact, setCompact] = React.useState(true);
|
|
977
|
-
const toggleCompact = checked => {
|
|
978
|
-
setCompact(checked);
|
|
979
|
-
};
|
|
980
|
-
const handleExpansionToggle = (event, pairIndex) => {
|
|
981
|
-
setExpanded({
|
|
982
|
-
...expanded,
|
|
983
|
-
[pairIndex]: !expanded[pairIndex]
|
|
984
|
-
});
|
|
985
|
-
};
|
|
986
|
-
let rowIndex = -1;
|
|
987
|
-
return (
|
|
988
|
-
<React.Fragment>
|
|
989
|
-
<Checkbox
|
|
990
|
-
label="Compact"
|
|
991
|
-
isChecked={compact}
|
|
992
|
-
onChange={toggleCompact}
|
|
993
|
-
aria-label="toggle compact variation"
|
|
994
|
-
id="toggle-compact"
|
|
995
|
-
name="toggle-compact"
|
|
996
|
-
/>
|
|
997
|
-
<TableComposable aria-label="Expandable Table" variant={compact ? 'compact' : null}>
|
|
998
|
-
<Thead>
|
|
999
|
-
<Tr>
|
|
1000
|
-
<Th />
|
|
1001
|
-
<Th>{columns[0]}</Th>
|
|
1002
|
-
<Th>{columns[1]}</Th>
|
|
1003
|
-
<Th>{columns[2]}</Th>
|
|
1004
|
-
<Th>{columns[3]}</Th>
|
|
1005
|
-
<Th>{columns[4]}</Th>
|
|
1006
|
-
</Tr>
|
|
1007
|
-
</Thead>
|
|
1008
|
-
{rowPairs.map((pair, pairIndex) => {
|
|
1009
|
-
rowIndex += 1;
|
|
1010
|
-
const parentRow = (
|
|
1011
|
-
<Tr key={rowIndex}>
|
|
1012
|
-
<Td
|
|
1013
|
-
key={`${rowIndex}_0`}
|
|
1014
|
-
expand={
|
|
1015
|
-
pair.child
|
|
1016
|
-
? {
|
|
1017
|
-
rowIndex: pairIndex,
|
|
1018
|
-
isExpanded: expanded[pairIndex],
|
|
1019
|
-
onToggle: handleExpansionToggle
|
|
1020
|
-
}
|
|
1021
|
-
: null
|
|
1022
|
-
}
|
|
1023
|
-
/>
|
|
1024
|
-
{pair.parent.map((cell, cellIndex) => (
|
|
1025
|
-
<Td key={`${rowIndex}_${cellIndex}`} dataLabel={columns[cellIndex]}>
|
|
1026
|
-
{cell}
|
|
1027
|
-
</Td>
|
|
1028
|
-
))}
|
|
1029
|
-
</Tr>
|
|
1030
|
-
);
|
|
1031
|
-
if (pair.child) {
|
|
1032
|
-
rowIndex += 1;
|
|
1033
|
-
}
|
|
1034
|
-
const childRow = pair.child ? (
|
|
1035
|
-
<Tr key={rowIndex} isExpanded={expanded[pairIndex] === true}>
|
|
1036
|
-
{!rowPairs[pairIndex].fullWidth && <Td key={`${rowIndex}_0`} />}
|
|
1037
|
-
{rowPairs[pairIndex].child.map((cell, cellIndex) => {
|
|
1038
|
-
const numChildCells = rowPairs[pairIndex].child.length;
|
|
1039
|
-
const shift = rowPairs[pairIndex].fullWidth ? 1 : 0;
|
|
1040
|
-
const shiftedCellIndex = cellIndex + shift;
|
|
1041
|
-
// some examples of how you could customize colSpan based on your needs
|
|
1042
|
-
const getColSpan = () => {
|
|
1043
|
-
// we have 6 columns (1 expandable column + 5 regular columns)
|
|
1044
|
-
// for the rowPairs where we've specificed `fullWidth`, add +1 to account for the expandable column
|
|
1045
|
-
let colSpan = 1;
|
|
1046
|
-
if (numChildCells === 1) {
|
|
1047
|
-
// single child cell: take up full width
|
|
1048
|
-
colSpan = numColumns + shift;
|
|
1049
|
-
} else if (numChildCells === 2) {
|
|
1050
|
-
// 2 children
|
|
1051
|
-
// child 1: 2 colspan
|
|
1052
|
-
// child 2: 3 or 4 colspan depending on fullWidth
|
|
1053
|
-
colSpan = cellIndex === 0 ? 2 : 3 + shift;
|
|
1054
|
-
} else if (numChildCells === 3) {
|
|
1055
|
-
// 3 children
|
|
1056
|
-
// child 1: 2 colspam
|
|
1057
|
-
// child 2: 2 colspan
|
|
1058
|
-
// child 3: 1 or 2 colspan depending on fullWidth
|
|
1059
|
-
colSpan = cellIndex === 2 ? 1 + shift : 2;
|
|
1060
|
-
}
|
|
1061
|
-
return colSpan;
|
|
1062
|
-
};
|
|
1063
|
-
return (
|
|
1064
|
-
<Td
|
|
1065
|
-
key={`${rowIndex}_${shiftedCellIndex}`}
|
|
1066
|
-
dataLabel={columns[cellIndex]}
|
|
1067
|
-
noPadding={rowPairs[pairIndex].noPadding}
|
|
1068
|
-
colSpan={getColSpan()}
|
|
1069
|
-
>
|
|
1070
|
-
<ExpandableRowContent>{cell.title || cell}</ExpandableRowContent>
|
|
1071
|
-
</Td>
|
|
1072
|
-
);
|
|
1073
|
-
})}
|
|
1074
|
-
</Tr>
|
|
1075
|
-
) : null;
|
|
1076
|
-
return (
|
|
1077
|
-
<Tbody key={pairIndex} isExpanded={expanded[pairIndex] === true}>
|
|
1078
|
-
{parentRow}
|
|
1079
|
-
{childRow}
|
|
1080
|
-
</Tbody>
|
|
1081
|
-
);
|
|
1082
|
-
})}
|
|
1083
|
-
</TableComposable>
|
|
1084
|
-
</React.Fragment>
|
|
1085
|
-
);
|
|
1086
|
-
};
|
|
189
|
+
```ts file="ComposableTableExpandable.tsx"
|
|
1087
190
|
```
|
|
1088
191
|
|
|
1089
192
|
### Composable: Compound expandable
|
|
@@ -1107,334 +210,29 @@ export type OnExpand = (
|
|
|
1107
210
|
) => void;
|
|
1108
211
|
```
|
|
1109
212
|
|
|
1110
|
-
```
|
|
1111
|
-
import React from 'react';
|
|
1112
|
-
import { TableComposable, Thead, Tbody, Tr, Th, Td, ExpandableRowContent } from '@patternfly/react-table';
|
|
1113
|
-
|
|
1114
|
-
import CodeBranchIcon from '@patternfly/react-icons/dist/esm/icons/code-branch-icon';
|
|
1115
|
-
import CodeIcon from '@patternfly/react-icons/dist/esm/icons/code-icon';
|
|
1116
|
-
import CubeIcon from '@patternfly/react-icons/dist/esm/icons/cube-icon';
|
|
1117
|
-
|
|
1118
|
-
// https://github.com/patternfly/patternfly-react/blob/main/packages/react-table/src/components/Table/composable-table-examples/DemoSortableTable.js
|
|
1119
|
-
import DemoSortableTable from './DemoSortableTable';
|
|
1120
|
-
|
|
1121
|
-
ComposableCompoundExpandableTable = () => {
|
|
1122
|
-
const columns = ['Repositories', 'Branches', 'Pull requests', 'Workspaces', 'Last commit', ''];
|
|
1123
|
-
const rows = [
|
|
1124
|
-
['siemur/test-space', 10, 4, 4, '20 minutes', 'Open in Github'],
|
|
1125
|
-
['siemur/test-space', 3, 4, 2, '10 minutes', 'Open in Github']
|
|
1126
|
-
];
|
|
1127
|
-
// index corresponds to row index, and value corresponds to column index of the expanded, null means no cell is expanded
|
|
1128
|
-
const [activeChild, setActiveChild] = React.useState([1, null]);
|
|
1129
|
-
// key = row_col of the parent it corresponds to
|
|
1130
|
-
const childData = {
|
|
1131
|
-
'0_1': {
|
|
1132
|
-
component: (
|
|
1133
|
-
<DemoSortableTable
|
|
1134
|
-
firstColumnRows={['parent-0', 'compound-1', 'three', 'four', 'five']}
|
|
1135
|
-
id="compound-expansion-table-0_1"
|
|
1136
|
-
key="0_1"
|
|
1137
|
-
/>
|
|
1138
|
-
)
|
|
1139
|
-
},
|
|
1140
|
-
'0_2': {
|
|
1141
|
-
component: (
|
|
1142
|
-
<DemoSortableTable
|
|
1143
|
-
firstColumnRows={['parent-0', 'compound-2', 'three', 'four', 'five']}
|
|
1144
|
-
id="compound-expansion-table-0_2"
|
|
1145
|
-
key="0_2"
|
|
1146
|
-
/>
|
|
1147
|
-
)
|
|
1148
|
-
},
|
|
1149
|
-
'0_3': {
|
|
1150
|
-
component: (
|
|
1151
|
-
<DemoSortableTable
|
|
1152
|
-
firstColumnRows={['parent-0', 'compound-3', 'three', 'four', 'five']}
|
|
1153
|
-
id="compound-expansion-table-0_3"
|
|
1154
|
-
key="0_3"
|
|
1155
|
-
/>
|
|
1156
|
-
)
|
|
1157
|
-
},
|
|
1158
|
-
'1_1': {
|
|
1159
|
-
component: (
|
|
1160
|
-
<DemoSortableTable
|
|
1161
|
-
firstColumnRows={['parent-1', 'compound-1', 'three', 'four', 'five']}
|
|
1162
|
-
id="compound-expansion-table-1_1"
|
|
1163
|
-
key="1_1"
|
|
1164
|
-
/>
|
|
1165
|
-
)
|
|
1166
|
-
},
|
|
1167
|
-
'1_2': {
|
|
1168
|
-
component: (
|
|
1169
|
-
<DemoSortableTable
|
|
1170
|
-
firstColumnRows={['parent-1', 'compound-2', 'three', 'four', 'five']}
|
|
1171
|
-
id="compound-expansion-table-1_2"
|
|
1172
|
-
key="1_2"
|
|
1173
|
-
/>
|
|
1174
|
-
)
|
|
1175
|
-
},
|
|
1176
|
-
'1_3': {
|
|
1177
|
-
component: (
|
|
1178
|
-
<DemoSortableTable
|
|
1179
|
-
firstColumnRows={['parent-1', 'compound-3', 'three', 'four', 'five']}
|
|
1180
|
-
id="compound-expansion-table-1_3"
|
|
1181
|
-
key="1_3"
|
|
1182
|
-
/>
|
|
1183
|
-
)
|
|
1184
|
-
}
|
|
1185
|
-
};
|
|
1186
|
-
const customRender = (cell, index) => {
|
|
1187
|
-
if (index === 0) {
|
|
1188
|
-
return <a href="#">{cell}</a>;
|
|
1189
|
-
} else if (index === 1) {
|
|
1190
|
-
return (
|
|
1191
|
-
<React.Fragment>
|
|
1192
|
-
<CodeBranchIcon key="icon" /> {cell}
|
|
1193
|
-
</React.Fragment>
|
|
1194
|
-
);
|
|
1195
|
-
} else if (index === 2) {
|
|
1196
|
-
return (
|
|
1197
|
-
<React.Fragment>
|
|
1198
|
-
<CodeIcon key="icon" /> {cell}
|
|
1199
|
-
</React.Fragment>
|
|
1200
|
-
);
|
|
1201
|
-
} else if (index === 3) {
|
|
1202
|
-
return (
|
|
1203
|
-
<React.Fragment>
|
|
1204
|
-
<CubeIcon key="icon" /> {cell}
|
|
1205
|
-
</React.Fragment>
|
|
1206
|
-
);
|
|
1207
|
-
} else if (index === 5) {
|
|
1208
|
-
return <a href="#">{cell}</a>;
|
|
1209
|
-
}
|
|
1210
|
-
return cell;
|
|
1211
|
-
};
|
|
1212
|
-
const isCompoundExpanded = (rowIndex, cellIndex) => {
|
|
1213
|
-
// only columns 1 - 3 are compound expansion toggles in this example
|
|
1214
|
-
if (1 <= cellIndex && cellIndex <= 3) {
|
|
1215
|
-
return activeChild[rowIndex] === cellIndex;
|
|
1216
|
-
}
|
|
1217
|
-
return null;
|
|
1218
|
-
};
|
|
1219
|
-
return (
|
|
1220
|
-
<TableComposable aria-label="Compound expandable table">
|
|
1221
|
-
<Thead>
|
|
1222
|
-
<Tr>
|
|
1223
|
-
{columns.map((column, columnIndex) => (
|
|
1224
|
-
<Th key={columnIndex}>{column}</Th>
|
|
1225
|
-
))}
|
|
1226
|
-
</Tr>
|
|
1227
|
-
</Thead>
|
|
1228
|
-
{rows.map((row, rowIndex) => {
|
|
1229
|
-
const isRowExpanded = activeChild[rowIndex] !== null;
|
|
1230
|
-
return (
|
|
1231
|
-
<Tbody key={rowIndex} isExpanded={isRowExpanded}>
|
|
1232
|
-
<React.Fragment>
|
|
1233
|
-
<Tr>
|
|
1234
|
-
{row.map((cell, cellIndex) => {
|
|
1235
|
-
// for this example, only columns 1 - 3 are clickable
|
|
1236
|
-
const compoundExpandParams =
|
|
1237
|
-
1 <= cellIndex && cellIndex <= 3
|
|
1238
|
-
? {
|
|
1239
|
-
compoundExpand: {
|
|
1240
|
-
isExpanded: isCompoundExpanded(rowIndex, cellIndex),
|
|
1241
|
-
onToggle: () => {
|
|
1242
|
-
if (activeChild[rowIndex] === cellIndex) {
|
|
1243
|
-
// closing the expansion on the current toggle
|
|
1244
|
-
// set the corresponding item to null
|
|
1245
|
-
const updatedActiveChild = activeChild.map((item, index) =>
|
|
1246
|
-
index === rowIndex ? null : item
|
|
1247
|
-
);
|
|
1248
|
-
setActiveChild(updatedActiveChild);
|
|
1249
|
-
} else {
|
|
1250
|
-
// expanding
|
|
1251
|
-
// set the corresponding cell index
|
|
1252
|
-
const updatedActiveChild = activeChild.map((item, index) =>
|
|
1253
|
-
index === rowIndex ? cellIndex : item
|
|
1254
|
-
);
|
|
1255
|
-
setActiveChild(updatedActiveChild);
|
|
1256
|
-
}
|
|
1257
|
-
}
|
|
1258
|
-
}
|
|
1259
|
-
}
|
|
1260
|
-
: {};
|
|
1261
|
-
return (
|
|
1262
|
-
<Td
|
|
1263
|
-
key={`${rowIndex}_${cellIndex}`}
|
|
1264
|
-
dataLabel={columns[cellIndex]}
|
|
1265
|
-
component={cellIndex === 0 ? 'th' : 'td'}
|
|
1266
|
-
{...compoundExpandParams}
|
|
1267
|
-
>
|
|
1268
|
-
{customRender(cell, cellIndex)}
|
|
1269
|
-
</Td>
|
|
1270
|
-
);
|
|
1271
|
-
})}
|
|
1272
|
-
</Tr>
|
|
1273
|
-
{isRowExpanded && (
|
|
1274
|
-
<Tr key={`${rowIndex}-child`} isExpanded={isRowExpanded}>
|
|
1275
|
-
<Td dataLabel={columns[0]} noPadding colSpan={6}>
|
|
1276
|
-
<ExpandableRowContent>
|
|
1277
|
-
{childData[`${rowIndex}_${activeChild[rowIndex]}`].component}
|
|
1278
|
-
</ExpandableRowContent>
|
|
1279
|
-
</Td>
|
|
1280
|
-
</Tr>
|
|
1281
|
-
)}
|
|
1282
|
-
</React.Fragment>
|
|
1283
|
-
</Tbody>
|
|
1284
|
-
);
|
|
1285
|
-
})}
|
|
1286
|
-
</TableComposable>
|
|
1287
|
-
);
|
|
1288
|
-
};
|
|
213
|
+
```ts file="ComposableTableCompoundExpandable.tsx"
|
|
1289
214
|
```
|
|
1290
215
|
|
|
1291
216
|
### Composable: Cell width, breakpoint modifiers
|
|
1292
217
|
|
|
1293
|
-
```
|
|
1294
|
-
import React from 'react';
|
|
1295
|
-
import { TableComposable, Thead, Tbody, Tr, Th, Td } from '@patternfly/react-table';
|
|
1296
|
-
|
|
1297
|
-
ComposableTableCellWidth = () => {
|
|
1298
|
-
const columns = ['Repositories', 'Branches', 'Pull requests', 'Workspaces', 'Last commit'];
|
|
1299
|
-
const rows = [
|
|
1300
|
-
['one', 'two', 'three', 'four', 'five'],
|
|
1301
|
-
['one - 2', null, null, 'four - 2', 'five - 2'],
|
|
1302
|
-
['one - 3', 'two - 3', 'three - 3', 'four - 3', 'five - 3']
|
|
1303
|
-
];
|
|
1304
|
-
return (
|
|
1305
|
-
<TableComposable aria-label="Cell widths">
|
|
1306
|
-
<Thead>
|
|
1307
|
-
<Tr>
|
|
1308
|
-
{columns.map((column, columnIndex) => (
|
|
1309
|
-
<Th
|
|
1310
|
-
key={columnIndex}
|
|
1311
|
-
width={columnIndex === 2 ? 40 : 15}
|
|
1312
|
-
visibility={columnIndex === 2 ? ['hiddenOnMd', 'visibleOnLg'] : null}
|
|
1313
|
-
>
|
|
1314
|
-
{column}
|
|
1315
|
-
</Th>
|
|
1316
|
-
))}
|
|
1317
|
-
</Tr>
|
|
1318
|
-
</Thead>
|
|
1319
|
-
<Tbody>
|
|
1320
|
-
{rows.map((row, rowIndex) => (
|
|
1321
|
-
<Tr key={rowIndex}>
|
|
1322
|
-
{row.map((cell, cellIndex) => (
|
|
1323
|
-
<Td
|
|
1324
|
-
key={`${rowIndex}_${cellIndex}`}
|
|
1325
|
-
dataLabel={columns[cellIndex]}
|
|
1326
|
-
visibility={cellIndex === 2 ? ['hiddenOnMd', 'visibleOnLg'] : null}
|
|
1327
|
-
>
|
|
1328
|
-
{cell}
|
|
1329
|
-
</Td>
|
|
1330
|
-
))}
|
|
1331
|
-
</Tr>
|
|
1332
|
-
))}
|
|
1333
|
-
</Tbody>
|
|
1334
|
-
</TableComposable>
|
|
1335
|
-
);
|
|
1336
|
-
};
|
|
218
|
+
```ts file="ComposableTableCellWidth.tsx"
|
|
1337
219
|
```
|
|
1338
220
|
|
|
1339
221
|
### Composable: Controlling text
|
|
1340
222
|
|
|
1341
|
-
```
|
|
1342
|
-
import React from 'react';
|
|
1343
|
-
import { TableComposable, Thead, Tbody, Tr, Th, Td } from '@patternfly/react-table';
|
|
1344
|
-
|
|
1345
|
-
ComposableControllingText = () => {
|
|
1346
|
-
const columns = [
|
|
1347
|
-
'Truncate (width 20%)',
|
|
1348
|
-
'Break word',
|
|
1349
|
-
'Wrapping table header text. This th text will wrap instead of truncate.',
|
|
1350
|
-
'Fit content',
|
|
1351
|
-
'No wrap'
|
|
1352
|
-
];
|
|
1353
|
-
const rows = [
|
|
1354
|
-
[
|
|
1355
|
-
'This text will truncate instead of wrap.',
|
|
1356
|
-
<a href="#">http://thisisaverylongurlthatneedstobreakusethebreakwordmodifier.org</a>,
|
|
1357
|
-
<p>
|
|
1358
|
-
By default,
|
|
1359
|
-
<code>thead</code> cells will truncate and
|
|
1360
|
-
<code>tbody</code> cells will wrap. Use
|
|
1361
|
-
<code>.pf-m-wrap</code> on a<code>th</code> to change its behavior.
|
|
1362
|
-
</p>,
|
|
1363
|
-
"This cell's content will adjust itself to the parent th width. This modifier only affects table layouts.",
|
|
1364
|
-
<a href="#">No wrap</a>
|
|
1365
|
-
]
|
|
1366
|
-
];
|
|
1367
|
-
return (
|
|
1368
|
-
<TableComposable aria-label="Controlling text">
|
|
1369
|
-
<Thead>
|
|
1370
|
-
<Tr>
|
|
1371
|
-
<Th width={20}>{columns[0]}</Th>
|
|
1372
|
-
<Th>{columns[1]}</Th>
|
|
1373
|
-
<Th modifier="wrap">{columns[2]}</Th>
|
|
1374
|
-
<Th modifier="fitContent">{columns[3]}</Th>
|
|
1375
|
-
<Th>{columns[4]}</Th>
|
|
1376
|
-
</Tr>
|
|
1377
|
-
</Thead>
|
|
1378
|
-
<Tbody>
|
|
1379
|
-
{rows.map((row, rowIndex) => (
|
|
1380
|
-
<Tr key={rowIndex}>
|
|
1381
|
-
<Td dataLabel={columns[0]} modifier="truncate">
|
|
1382
|
-
{row[0]}
|
|
1383
|
-
</Td>
|
|
1384
|
-
<Td dataLabel={columns[1]} modifier="breakWord">
|
|
1385
|
-
{row[1]}
|
|
1386
|
-
</Td>
|
|
1387
|
-
<Td dataLabel={columns[2]}>{row[2]}</Td>
|
|
1388
|
-
<Td dataLabel={columns[3]}>{row[3]}</Td>
|
|
1389
|
-
<Td dataLabel={columns[4]} modifier="nowrap">
|
|
1390
|
-
{row[4]}
|
|
1391
|
-
</Td>
|
|
1392
|
-
</Tr>
|
|
1393
|
-
))}
|
|
1394
|
-
</Tbody>
|
|
1395
|
-
</TableComposable>
|
|
1396
|
-
);
|
|
1397
|
-
};
|
|
223
|
+
```ts file="ComposableTableControllingText.tsx"
|
|
1398
224
|
```
|
|
1399
225
|
|
|
226
|
+
#### TODO: distinguish between the use cases for modifier on Td vs on TableText?
|
|
227
|
+
|
|
1400
228
|
### Composable: Modifiers with table text
|
|
1401
229
|
|
|
1402
|
-
```
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
const rows = [
|
|
1409
|
-
[
|
|
1410
|
-
<TableText wrapModifier="truncate">This text will truncate instead of wrap.</TableText>,
|
|
1411
|
-
<TableText wrapModifier="nowrap">
|
|
1412
|
-
<a href="#">This is a link that needs to be on one line and fully readable.</a>
|
|
1413
|
-
</TableText>
|
|
1414
|
-
]
|
|
1415
|
-
];
|
|
1416
|
-
return (
|
|
1417
|
-
<TableComposable aria-label="Table text">
|
|
1418
|
-
<Thead>
|
|
1419
|
-
<Tr>
|
|
1420
|
-
<Th width={30}>{columns[0]}</Th>
|
|
1421
|
-
<Th>{columns[1]}</Th>
|
|
1422
|
-
</Tr>
|
|
1423
|
-
</Thead>
|
|
1424
|
-
<Tbody>
|
|
1425
|
-
{rows.map((row, rowIndex) => (
|
|
1426
|
-
<Tr key={rowIndex}>
|
|
1427
|
-
{row.map((cell, cellIndex) => (
|
|
1428
|
-
<Td key={`${rowIndex}_${cellIndex}`} dataLabel={columns[cellIndex]}>
|
|
1429
|
-
{cell}
|
|
1430
|
-
</Td>
|
|
1431
|
-
))}
|
|
1432
|
-
</Tr>
|
|
1433
|
-
))}
|
|
1434
|
-
</Tbody>
|
|
1435
|
-
</TableComposable>
|
|
1436
|
-
);
|
|
1437
|
-
};
|
|
230
|
+
```ts file="ComposableTableTextModifiers.tsx"
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Composable: Empty state
|
|
234
|
+
|
|
235
|
+
```ts file="ComposableTableEmptyState.tsx"
|
|
1438
236
|
```
|
|
1439
237
|
|
|
1440
238
|
### Composable: Favoritable (implemented with sortable)
|
|
@@ -1456,85 +254,7 @@ type OnFavorite = (
|
|
|
1456
254
|
|
|
1457
255
|
To make a favoritable column sortable, pass a `ThSortType` object to the favoritable column's `Th` with `isFavorites` set to true.
|
|
1458
256
|
|
|
1459
|
-
```
|
|
1460
|
-
import React from 'react';
|
|
1461
|
-
import { TableComposable, Thead, Tbody, Tr, Th, Td } from '@patternfly/react-table';
|
|
1462
|
-
|
|
1463
|
-
ComposableTableFavoritable = () => {
|
|
1464
|
-
const columns = ['Repositories', 'Branches', 'Pull requests', 'Workspaces', 'Last commit'];
|
|
1465
|
-
const [rows, setRows] = React.useState([
|
|
1466
|
-
{ favorited: true, cells: ['one', 'two', 'three', 'four', 'five'] },
|
|
1467
|
-
{ favorited: false, cells: ['one - 2', null, null, 'four - 2', 'five - 2'] },
|
|
1468
|
-
{ favorited: false, cells: ['one - 3', 'two - 3', 'three - 3', 'four - 3', 'five - 3'] }
|
|
1469
|
-
]);
|
|
1470
|
-
// index of the currently active column
|
|
1471
|
-
const [activeSortIndex, setActiveSortIndex] = React.useState(-1);
|
|
1472
|
-
// sort direction of the currently active column
|
|
1473
|
-
const [activeSortDirection, setActiveSortDirection] = React.useState('none');
|
|
1474
|
-
const onSort = (event, index, direction) => {
|
|
1475
|
-
setActiveSortIndex(index);
|
|
1476
|
-
setActiveSortDirection(direction);
|
|
1477
|
-
// sorts the rows
|
|
1478
|
-
const updatedRows = rows.sort((a, b) => {
|
|
1479
|
-
if (a.favorited && !b.favorited) {
|
|
1480
|
-
return 1;
|
|
1481
|
-
} else if (!a.favorited && b.favorited) {
|
|
1482
|
-
return -1;
|
|
1483
|
-
}
|
|
1484
|
-
return 0;
|
|
1485
|
-
});
|
|
1486
|
-
setRows(direction === 'asc' ? updatedRows : updatedRows.reverse());
|
|
1487
|
-
};
|
|
1488
|
-
const sortParams = {
|
|
1489
|
-
sort: {
|
|
1490
|
-
isFavorites: true,
|
|
1491
|
-
sortBy: {
|
|
1492
|
-
index: activeSortIndex,
|
|
1493
|
-
direction: activeSortDirection
|
|
1494
|
-
},
|
|
1495
|
-
onSort,
|
|
1496
|
-
columnIndex: 0
|
|
1497
|
-
}
|
|
1498
|
-
};
|
|
1499
|
-
return (
|
|
1500
|
-
<TableComposable aria-label="Favoritable table" variant={'compact'}>
|
|
1501
|
-
<Thead>
|
|
1502
|
-
<Tr>
|
|
1503
|
-
<Th {...sortParams} />
|
|
1504
|
-
{columns.map((column, columnIndex) => (
|
|
1505
|
-
<Th key={columnIndex}>{column}</Th>
|
|
1506
|
-
))}
|
|
1507
|
-
</Tr>
|
|
1508
|
-
</Thead>
|
|
1509
|
-
<Tbody>
|
|
1510
|
-
{rows.map((row, rowIndex) => (
|
|
1511
|
-
<Tr key={rowIndex}>
|
|
1512
|
-
<Td
|
|
1513
|
-
favorites={{
|
|
1514
|
-
isFavorited: row.favorited,
|
|
1515
|
-
onFavorite: (event, isFavorited) =>
|
|
1516
|
-
setRows(
|
|
1517
|
-
rows.map((row, index) => {
|
|
1518
|
-
if (index === rowIndex) {
|
|
1519
|
-
row.favorited = isFavorited;
|
|
1520
|
-
}
|
|
1521
|
-
return row;
|
|
1522
|
-
})
|
|
1523
|
-
),
|
|
1524
|
-
rowIndex
|
|
1525
|
-
}}
|
|
1526
|
-
/>
|
|
1527
|
-
{row.cells.map((cell, cellIndex) => (
|
|
1528
|
-
<Td key={`${rowIndex}_${cellIndex}`} dataLabel={columns[cellIndex]}>
|
|
1529
|
-
{cell}
|
|
1530
|
-
</Td>
|
|
1531
|
-
))}
|
|
1532
|
-
</Tr>
|
|
1533
|
-
))}
|
|
1534
|
-
</Tbody>
|
|
1535
|
-
</TableComposable>
|
|
1536
|
-
);
|
|
1537
|
-
};
|
|
257
|
+
```ts file="ComposableTableFavoritable.tsx"
|
|
1538
258
|
```
|
|
1539
259
|
|
|
1540
260
|
### Composable: Tree table
|
|
@@ -1567,243 +287,7 @@ Note: If this table is going to be tested using axe-core, the tests will flag th
|
|
|
1567
287
|
aria-posinset, and aria-setsize as violations. This is an intentional choice at this time so that
|
|
1568
288
|
the voice over technologies will recognize the flat table structure as a tree.
|
|
1569
289
|
|
|
1570
|
-
```
|
|
1571
|
-
import React from 'react';
|
|
1572
|
-
import { TableComposable, Thead, Tbody, Tr, Th, Td, Caption, TreeRowWrapper } from '@patternfly/react-table';
|
|
1573
|
-
import LeafIcon from '@patternfly/react-icons/dist/js/icons/leaf-icon';
|
|
1574
|
-
import FolderIcon from '@patternfly/react-icons/dist/js/icons/folder-icon';
|
|
1575
|
-
import FolderOpenIcon from '@patternfly/react-icons/dist/js/icons/folder-open-icon';
|
|
1576
|
-
|
|
1577
|
-
class TreeTable extends React.Component {
|
|
1578
|
-
constructor(props) {
|
|
1579
|
-
super(props);
|
|
1580
|
-
this.state = {
|
|
1581
|
-
data: [
|
|
1582
|
-
{
|
|
1583
|
-
repositories: 'Repositories one',
|
|
1584
|
-
branches: 'Branch one',
|
|
1585
|
-
pullRequests: 'Pull request one',
|
|
1586
|
-
workspaces: 'Workplace one',
|
|
1587
|
-
children: [
|
|
1588
|
-
{
|
|
1589
|
-
repositories: 'Repositories two',
|
|
1590
|
-
branches: 'Branch two',
|
|
1591
|
-
pullRequests: 'Pull request two',
|
|
1592
|
-
workspaces: 'Workplace two',
|
|
1593
|
-
children: [
|
|
1594
|
-
{
|
|
1595
|
-
repositories: 'Repositories three',
|
|
1596
|
-
branches: 'Branch three',
|
|
1597
|
-
pullRequests: 'Pull request three',
|
|
1598
|
-
workspaces: 'Workplace three'
|
|
1599
|
-
},
|
|
1600
|
-
{
|
|
1601
|
-
repositories: 'Repositories four',
|
|
1602
|
-
branches: 'Branch four',
|
|
1603
|
-
pullRequests: 'Pull request four',
|
|
1604
|
-
workspaces: 'Workplace four'
|
|
1605
|
-
}
|
|
1606
|
-
]
|
|
1607
|
-
},
|
|
1608
|
-
{
|
|
1609
|
-
repositories: 'Repositories five',
|
|
1610
|
-
branches: 'Branch five',
|
|
1611
|
-
pullRequests: 'Pull request five',
|
|
1612
|
-
workspaces: 'Workplace five'
|
|
1613
|
-
},
|
|
1614
|
-
{
|
|
1615
|
-
repositories: 'Repositories six',
|
|
1616
|
-
branches: 'Branch six',
|
|
1617
|
-
pullRequests: 'Pull request six',
|
|
1618
|
-
workspaces: 'Workplace six'
|
|
1619
|
-
}
|
|
1620
|
-
]
|
|
1621
|
-
},
|
|
1622
|
-
{
|
|
1623
|
-
repositories: 'Repositories seven',
|
|
1624
|
-
branches: 'Branch seven',
|
|
1625
|
-
pullRequests: 'Pull request seven',
|
|
1626
|
-
workspaces: 'Workplace seven',
|
|
1627
|
-
children: [
|
|
1628
|
-
{
|
|
1629
|
-
repositories: 'Repositories eight',
|
|
1630
|
-
branches: 'Branch eight',
|
|
1631
|
-
pullRequests: 'Pull request eight',
|
|
1632
|
-
workspaces: 'Workplace eight'
|
|
1633
|
-
}
|
|
1634
|
-
]
|
|
1635
|
-
},
|
|
1636
|
-
{
|
|
1637
|
-
repositories: 'Repositories nine',
|
|
1638
|
-
branches: 'Branch nine',
|
|
1639
|
-
pullRequests: 'Pull request nine',
|
|
1640
|
-
workspaces: 'Workplace nine'
|
|
1641
|
-
}
|
|
1642
|
-
],
|
|
1643
|
-
expandedRows: ['Repositories one', 'Repositories six'],
|
|
1644
|
-
expandedDetailsRows: [],
|
|
1645
|
-
checkedRows: []
|
|
1646
|
-
};
|
|
1647
|
-
|
|
1648
|
-
/**
|
|
1649
|
-
Recursive function which flattens the data into an array of flattened IRow objects
|
|
1650
|
-
to be later iterated over and each passed to the `row` prop of the TreeRowWrapper
|
|
1651
|
-
params:
|
|
1652
|
-
- rowData - array of data
|
|
1653
|
-
- level - number representing how deeply nested the current row is
|
|
1654
|
-
- posinset - position of the row relative to this row's siblings
|
|
1655
|
-
- isHidden - defaults to false, true if this row's parent is expanded
|
|
1656
|
-
*/
|
|
1657
|
-
this.buildRows = ([x, ...xs], level, posinset, isHidden = false) => {
|
|
1658
|
-
// take the first datum from the array (if any)
|
|
1659
|
-
if (x) {
|
|
1660
|
-
const isExpanded = this.state.expandedRows.includes(x.repositories);
|
|
1661
|
-
const isDetailsExpanded = this.state.expandedDetailsRows.includes(x.repositories);
|
|
1662
|
-
const isChecked = this.mapChecked(x);
|
|
1663
|
-
let icon = <LeafIcon />;
|
|
1664
|
-
if (x.children) {
|
|
1665
|
-
icon = isExpanded ? <FolderOpenIcon aria-hidden /> : <FolderIcon aria-hidden />;
|
|
1666
|
-
}
|
|
1667
|
-
return [
|
|
1668
|
-
{
|
|
1669
|
-
cells: [x.repositories, x.branches, x.pullRequests, x.workspaces],
|
|
1670
|
-
props: {
|
|
1671
|
-
isExpanded,
|
|
1672
|
-
isDetailsExpanded,
|
|
1673
|
-
isHidden,
|
|
1674
|
-
'aria-level': level,
|
|
1675
|
-
'aria-posinset': posinset,
|
|
1676
|
-
'aria-setsize': x.children ? x.children.length : 0,
|
|
1677
|
-
isChecked,
|
|
1678
|
-
icon
|
|
1679
|
-
}
|
|
1680
|
-
},
|
|
1681
|
-
...(x.children && x.children.length ? this.buildRows(x.children, level + 1, 1, !isExpanded || isHidden) : []),
|
|
1682
|
-
...this.buildRows(xs, level, posinset + 1, isHidden)
|
|
1683
|
-
];
|
|
1684
|
-
}
|
|
1685
|
-
return [];
|
|
1686
|
-
};
|
|
1687
|
-
|
|
1688
|
-
this.onCollapse = (event, rowIndex, title) => {
|
|
1689
|
-
this.setState(prevState => {
|
|
1690
|
-
const { expandedRows } = prevState;
|
|
1691
|
-
const openedIndex = expandedRows.indexOf(title);
|
|
1692
|
-
const newExpandedRows = openedIndex === -1 ? [...expandedRows, title] : expandedRows.filter(o => o !== title);
|
|
1693
|
-
return {
|
|
1694
|
-
expandedRows: newExpandedRows
|
|
1695
|
-
};
|
|
1696
|
-
});
|
|
1697
|
-
};
|
|
1698
|
-
|
|
1699
|
-
this.onToggleRowDetails = (event, rowIndex, title) => {
|
|
1700
|
-
this.setState(prevState => {
|
|
1701
|
-
const { expandedDetailsRows } = prevState;
|
|
1702
|
-
const openedIndex = expandedDetailsRows.indexOf(title);
|
|
1703
|
-
const newExpandedDetailsRows =
|
|
1704
|
-
openedIndex === -1 ? [...expandedDetailsRows, title] : expandedDetailsRows.filter(o => o !== title);
|
|
1705
|
-
return {
|
|
1706
|
-
expandedDetailsRows: newExpandedDetailsRows
|
|
1707
|
-
};
|
|
1708
|
-
});
|
|
1709
|
-
};
|
|
1710
|
-
|
|
1711
|
-
this.onCheck = (event, checked, rowIndex, checkedTitle) => {
|
|
1712
|
-
this.setState(prevState => {
|
|
1713
|
-
let flattenedData = this.getFlattenedTitles(prevState.data, checkedTitle);
|
|
1714
|
-
const prevChecked = prevState.checkedRows;
|
|
1715
|
-
return {
|
|
1716
|
-
checkedRows: checked
|
|
1717
|
-
? prevChecked.concat(flattenedData.filter(title => !prevChecked.includes(title)))
|
|
1718
|
-
: prevChecked.filter(title => !flattenedData.includes(title))
|
|
1719
|
-
};
|
|
1720
|
-
});
|
|
1721
|
-
};
|
|
1722
|
-
|
|
1723
|
-
// // helper methods for determining the checkbox's checked state for each row ///
|
|
1724
|
-
|
|
1725
|
-
/** Recursive function which flattens the data into an array of row titles
|
|
1726
|
-
* whose checkboxes' checked states are being updated.
|
|
1727
|
-
* Titles are only added to the array if they are the root checked title node,
|
|
1728
|
-
* or if they are a descendent of the root checked title node */
|
|
1729
|
-
this.getFlattenedTitles = (data, checkedTitle, parentChecked) => {
|
|
1730
|
-
let flattenedData = [];
|
|
1731
|
-
data.forEach(item => {
|
|
1732
|
-
if (item.repositories === checkedTitle || parentChecked) {
|
|
1733
|
-
flattenedData.push(item.repositories);
|
|
1734
|
-
}
|
|
1735
|
-
if (item.children) {
|
|
1736
|
-
flattenedData = flattenedData.concat(
|
|
1737
|
-
this.getFlattenedTitles(item.children, checkedTitle, item.repositories === checkedTitle || parentChecked)
|
|
1738
|
-
);
|
|
1739
|
-
}
|
|
1740
|
-
return flattenedData;
|
|
1741
|
-
});
|
|
1742
|
-
return flattenedData;
|
|
1743
|
-
};
|
|
1744
|
-
|
|
1745
|
-
this.areAllDescendantsChecked = row =>
|
|
1746
|
-
row.children
|
|
1747
|
-
? row.children.every(child => this.areAllDescendantsChecked(child))
|
|
1748
|
-
: this.state.checkedRows.indexOf(row.repositories) !== -1;
|
|
1749
|
-
|
|
1750
|
-
this.areSomeDescendantsChecked = row =>
|
|
1751
|
-
row.children
|
|
1752
|
-
? row.children.some(child => this.areSomeDescendantsChecked(child))
|
|
1753
|
-
: this.state.checkedRows.indexOf(row.repositories) !== -1;
|
|
1754
|
-
|
|
1755
|
-
this.mapChecked = row => {
|
|
1756
|
-
if (this.areAllDescendantsChecked(row)) {
|
|
1757
|
-
return true;
|
|
1758
|
-
}
|
|
1759
|
-
if (this.areSomeDescendantsChecked(row)) {
|
|
1760
|
-
return null;
|
|
1761
|
-
}
|
|
1762
|
-
return false;
|
|
1763
|
-
};
|
|
1764
|
-
}
|
|
1765
|
-
|
|
1766
|
-
render() {
|
|
1767
|
-
const columns = ['Repositories', 'Branches', 'Pull Requests', 'Workspaces'];
|
|
1768
|
-
return (
|
|
1769
|
-
<TableComposable isTreeTable aria-label="Tree Table">
|
|
1770
|
-
<Thead>
|
|
1771
|
-
<Tr>
|
|
1772
|
-
{columns.map((column, columnIndex) => (
|
|
1773
|
-
<Th key={columnIndex}>{column}</Th>
|
|
1774
|
-
))}
|
|
1775
|
-
</Tr>
|
|
1776
|
-
</Thead>
|
|
1777
|
-
<Tbody>
|
|
1778
|
-
{this.buildRows(this.state.data, 1, 1).map((row, rowIndex) => (
|
|
1779
|
-
<TreeRowWrapper row={row} key={rowIndex}>
|
|
1780
|
-
{row.cells.map((cell, cellIndex) =>
|
|
1781
|
-
cellIndex === 0 ? (
|
|
1782
|
-
<Td
|
|
1783
|
-
key={`${rowIndex}_0`}
|
|
1784
|
-
treeRow={{
|
|
1785
|
-
onCollapse: this.onCollapse,
|
|
1786
|
-
onCheckChange: this.onCheck,
|
|
1787
|
-
onToggleRowDetails: this.onToggleRowDetails,
|
|
1788
|
-
props: row.props,
|
|
1789
|
-
rowIndex: rowIndex
|
|
1790
|
-
}}
|
|
1791
|
-
>
|
|
1792
|
-
{cell}
|
|
1793
|
-
</Td>
|
|
1794
|
-
) : (
|
|
1795
|
-
<Td key={`${rowIndex}_${cellIndex}`} data-label={columns[cellIndex]}>
|
|
1796
|
-
{cell}
|
|
1797
|
-
</Td>
|
|
1798
|
-
)
|
|
1799
|
-
)}
|
|
1800
|
-
</TreeRowWrapper>
|
|
1801
|
-
))}
|
|
1802
|
-
</Tbody>
|
|
1803
|
-
</TableComposable>
|
|
1804
|
-
);
|
|
1805
|
-
}
|
|
1806
|
-
}
|
|
290
|
+
```ts isBeta file="ComposableTableTree.tsx"
|
|
1807
291
|
```
|
|
1808
292
|
|
|
1809
293
|
### Composable: Draggable row table
|
|
@@ -1816,910 +300,28 @@ To make a row draggable:
|
|
|
1816
300
|
4. While the user is dragging a row, the `` class needs to be applied to `TableComposable`.
|
|
1817
301
|
5. The draggable `Td` in each row needs a `TdDraggableType` object passed to its `draggable` prop.
|
|
1818
302
|
|
|
1819
|
-
```
|
|
1820
|
-
import React from 'react';
|
|
1821
|
-
import styles from '@patternfly/react-styles/css/components/Table/table';
|
|
1822
|
-
import { TableComposable, Thead, Tbody, Tr, Th, Td } from '@patternfly/react-table';
|
|
1823
|
-
|
|
1824
|
-
class DraggableTable extends React.Component {
|
|
1825
|
-
constructor(props) {
|
|
1826
|
-
super(props);
|
|
1827
|
-
this.state = {
|
|
1828
|
-
draggedItemId: null,
|
|
1829
|
-
draggingToItemIndex: null,
|
|
1830
|
-
dragging: false,
|
|
1831
|
-
itemOrder: ['row1', 'row2', 'row3'],
|
|
1832
|
-
tempItemOrder: []
|
|
1833
|
-
};
|
|
1834
|
-
this.bodyRef = React.createRef();
|
|
1835
|
-
|
|
1836
|
-
this.getIndex = id => Array.from(this.bodyRef.current.children).findIndex(item => item.id === id);
|
|
1837
|
-
|
|
1838
|
-
this.onDragStart = evt => {
|
|
1839
|
-
evt.dataTransfer.effectAllowed = 'move';
|
|
1840
|
-
evt.dataTransfer.setData('text/plain', evt.currentTarget.id);
|
|
1841
|
-
const draggedItemId = evt.currentTarget.id;
|
|
1842
|
-
|
|
1843
|
-
evt.currentTarget.classList.add(styles.modifiers.ghostRow);
|
|
1844
|
-
evt.currentTarget.setAttribute('aria-pressed', 'true');
|
|
1845
|
-
this.setState({
|
|
1846
|
-
draggedItemId,
|
|
1847
|
-
dragging: true
|
|
1848
|
-
});
|
|
1849
|
-
};
|
|
1850
|
-
|
|
1851
|
-
this.moveItem = (arr, i1, toIndex) => {
|
|
1852
|
-
const fromIndex = arr.indexOf(i1);
|
|
1853
|
-
if (fromIndex === toIndex) {
|
|
1854
|
-
return arr;
|
|
1855
|
-
}
|
|
1856
|
-
const temp = arr.splice(fromIndex, 1);
|
|
1857
|
-
arr.splice(toIndex, 0, temp[0]);
|
|
1858
|
-
|
|
1859
|
-
return arr;
|
|
1860
|
-
};
|
|
1861
|
-
|
|
1862
|
-
this.move = itemOrder => {
|
|
1863
|
-
const ulNode = this.bodyRef.current;
|
|
1864
|
-
const nodes = Array.from(ulNode.children);
|
|
1865
|
-
if (nodes.map(node => node.id).every((id, i) => id === itemOrder[i])) {
|
|
1866
|
-
return;
|
|
1867
|
-
}
|
|
1868
|
-
while (ulNode.firstChild) {
|
|
1869
|
-
ulNode.removeChild(ulNode.lastChild);
|
|
1870
|
-
}
|
|
1871
|
-
|
|
1872
|
-
itemOrder.forEach(id => {
|
|
1873
|
-
ulNode.appendChild(nodes.find(n => n.id === id));
|
|
1874
|
-
});
|
|
1875
|
-
};
|
|
1876
|
-
|
|
1877
|
-
this.onDragCancel = () => {
|
|
1878
|
-
// this.move(this.state.itemOrder);
|
|
1879
|
-
Array.from(this.bodyRef.current.children).forEach(el => {
|
|
1880
|
-
el.classList.remove(styles.modifiers.ghostRow);
|
|
1881
|
-
el.setAttribute('aria-pressed', 'false');
|
|
1882
|
-
});
|
|
1883
|
-
this.setState({
|
|
1884
|
-
draggedItemId: null,
|
|
1885
|
-
draggingToItemIndex: null,
|
|
1886
|
-
dragging: false
|
|
1887
|
-
});
|
|
1888
|
-
};
|
|
1889
|
-
|
|
1890
|
-
this.onDragLeave = evt => {
|
|
1891
|
-
if (!this.isValidDrop(evt)) {
|
|
1892
|
-
this.move(this.state.itemOrder);
|
|
1893
|
-
this.setState({
|
|
1894
|
-
draggingToItemIndex: null
|
|
1895
|
-
});
|
|
1896
|
-
}
|
|
1897
|
-
};
|
|
1898
|
-
|
|
1899
|
-
this.isValidDrop = evt => {
|
|
1900
|
-
const ulRect = this.bodyRef.current.getBoundingClientRect();
|
|
1901
|
-
return (
|
|
1902
|
-
evt.clientX > ulRect.x &&
|
|
1903
|
-
evt.clientX < ulRect.x + ulRect.width &&
|
|
1904
|
-
evt.clientY > ulRect.y &&
|
|
1905
|
-
evt.clientY < ulRect.y + ulRect.height
|
|
1906
|
-
);
|
|
1907
|
-
};
|
|
1908
|
-
|
|
1909
|
-
this.onDrop = evt => {
|
|
1910
|
-
if (this.isValidDrop(evt)) {
|
|
1911
|
-
this.setState({
|
|
1912
|
-
itemOrder: this.state.tempItemOrder
|
|
1913
|
-
});
|
|
1914
|
-
} else {
|
|
1915
|
-
this.onDragCancel();
|
|
1916
|
-
}
|
|
1917
|
-
};
|
|
1918
|
-
|
|
1919
|
-
this.onDragOver = evt => {
|
|
1920
|
-
evt.preventDefault();
|
|
1921
|
-
|
|
1922
|
-
const curListItem = evt.target.closest('tr');
|
|
1923
|
-
if (!curListItem || !this.bodyRef.current.contains(curListItem) || curListItem.id === this.state.draggedItemId) {
|
|
1924
|
-
return null;
|
|
1925
|
-
} else {
|
|
1926
|
-
const dragId = curListItem.id;
|
|
1927
|
-
const draggingToItemIndex = Array.from(this.bodyRef.current.children).findIndex(item => item.id === dragId);
|
|
1928
|
-
if (draggingToItemIndex !== this.state.draggingToItemIndex) {
|
|
1929
|
-
const tempItemOrder = this.moveItem([...this.state.itemOrder], this.state.draggedItemId, draggingToItemIndex);
|
|
1930
|
-
this.move(tempItemOrder);
|
|
1931
|
-
|
|
1932
|
-
this.setState({
|
|
1933
|
-
draggingToItemIndex,
|
|
1934
|
-
tempItemOrder
|
|
1935
|
-
});
|
|
1936
|
-
}
|
|
1937
|
-
}
|
|
1938
|
-
};
|
|
1939
|
-
|
|
1940
|
-
this.onDragEnd = evt => {
|
|
1941
|
-
evt.target.classList.remove(styles.modifiers.ghostRow);
|
|
1942
|
-
evt.target.setAttribute('aria-pressed', 'false');
|
|
1943
|
-
this.setState({
|
|
1944
|
-
draggedItemId: null,
|
|
1945
|
-
draggingToItemIndex: null,
|
|
1946
|
-
dragging: false
|
|
1947
|
-
});
|
|
1948
|
-
};
|
|
1949
|
-
}
|
|
1950
|
-
|
|
1951
|
-
render() {
|
|
1952
|
-
const columns = ['Repositories', 'Branches', 'Pull requests', 'Workspaces', 'Last commit'];
|
|
1953
|
-
const rows = [
|
|
1954
|
-
{
|
|
1955
|
-
id: 'row1',
|
|
1956
|
-
repository: 'one',
|
|
1957
|
-
branch: 'two',
|
|
1958
|
-
pullRequest: 'three',
|
|
1959
|
-
workspace: 'four',
|
|
1960
|
-
lastCommit: 'five'
|
|
1961
|
-
},
|
|
1962
|
-
{
|
|
1963
|
-
id: 'row2',
|
|
1964
|
-
repository: 'one -2',
|
|
1965
|
-
branch: null,
|
|
1966
|
-
pullRequest: null,
|
|
1967
|
-
workspace: 'four -2',
|
|
1968
|
-
lastCommit: 'five -2'
|
|
1969
|
-
},
|
|
1970
|
-
{
|
|
1971
|
-
id: 'row3',
|
|
1972
|
-
repository: 'one - 3',
|
|
1973
|
-
branch: 'two - 3',
|
|
1974
|
-
pullRequest: 'three - 3',
|
|
1975
|
-
workspace: 'four - 3',
|
|
1976
|
-
lastCommit: 'five - 3'
|
|
1977
|
-
}
|
|
1978
|
-
];
|
|
1979
|
-
|
|
1980
|
-
return (
|
|
1981
|
-
<TableComposable aria-label="Draggable table" className={this.state.dragging && styles.modifiers.dragOver}>
|
|
1982
|
-
<Thead>
|
|
1983
|
-
<Tr>
|
|
1984
|
-
<Th />
|
|
1985
|
-
{columns.map((column, columnIndex) => (
|
|
1986
|
-
<Th key={columnIndex}>{column}</Th>
|
|
1987
|
-
))}
|
|
1988
|
-
</Tr>
|
|
1989
|
-
</Thead>
|
|
1990
|
-
<Tbody ref={this.bodyRef} onDragOver={this.onDragOver} onDrop={this.onDragOver} onDragLeave={this.onDragLeave}>
|
|
1991
|
-
{rows.map((row, rowIndex) => (
|
|
1992
|
-
<Tr
|
|
1993
|
-
key={rowIndex}
|
|
1994
|
-
id={row.id}
|
|
1995
|
-
draggable
|
|
1996
|
-
onDrop={this.onDrop}
|
|
1997
|
-
onDragEnd={this.onDragEnd}
|
|
1998
|
-
onDragStart={this.onDragStart}
|
|
1999
|
-
>
|
|
2000
|
-
<Td
|
|
2001
|
-
draggableRow={{
|
|
2002
|
-
id: `draggable-row-${row.id}`
|
|
2003
|
-
}}
|
|
2004
|
-
/>
|
|
2005
|
-
{Object.keys(row).map((key, keyIndex) => (
|
|
2006
|
-
<Td key={`${rowIndex}_${keyIndex}`} dataLabel={columns[keyIndex]}>
|
|
2007
|
-
{row[key]}
|
|
2008
|
-
</Td>
|
|
2009
|
-
))}
|
|
2010
|
-
</Tr>
|
|
2011
|
-
))}
|
|
2012
|
-
</Tbody>
|
|
2013
|
-
</TableComposable>
|
|
2014
|
-
);
|
|
2015
|
-
}
|
|
2016
|
-
}
|
|
303
|
+
```ts isBeta file="ComposableTableDraggable.tsx"
|
|
2017
304
|
```
|
|
2018
305
|
|
|
2019
306
|
### Composable: Sticky column
|
|
2020
307
|
|
|
2021
308
|
To make a column sticky, wrap `TableComposable` with `InnerScrollContainer` and add the following properties to the `Th` that should be sticky: `isStickyColumn` and `hasRightBorder`. To prevent the default text wrapping behavior and allow horizontal scrolling, all `Th` should also have the `modifier="nowrap"` property. To set the minimum width of the sticky column, use the `stickyMinWidth` property.
|
|
2022
309
|
|
|
2023
|
-
```
|
|
2024
|
-
import React from 'react';
|
|
2025
|
-
import {
|
|
2026
|
-
TableComposable,
|
|
2027
|
-
Thead,
|
|
2028
|
-
Tbody,
|
|
2029
|
-
Tr,
|
|
2030
|
-
Th,
|
|
2031
|
-
Td,
|
|
2032
|
-
Caption,
|
|
2033
|
-
TableText,
|
|
2034
|
-
InnerScrollContainer
|
|
2035
|
-
} from '@patternfly/react-table';
|
|
2036
|
-
import { Flex, FlexItem } from '@patternfly/react-core';
|
|
2037
|
-
import BlueprintIcon from '@patternfly/react-icons/dist/esm/icons/blueprint-icon';
|
|
2038
|
-
|
|
2039
|
-
ComposableTableStickyColumn = () => {
|
|
2040
|
-
const columns = ['Fact', 'State', 'Header 3', 'Header 4', 'Header 5', 'Header 6', 'Header 7', 'Header 8', 'Header 9'];
|
|
2041
|
-
const [rows, setRows] = React.useState([
|
|
2042
|
-
[
|
|
2043
|
-
'Fact 1',
|
|
2044
|
-
'State 1',
|
|
2045
|
-
'Test cell 1-3',
|
|
2046
|
-
'Test cell 1-4',
|
|
2047
|
-
'Test cell 1-5',
|
|
2048
|
-
'Test cell 1-6',
|
|
2049
|
-
'Test cell 1-7',
|
|
2050
|
-
'Test cell 1-8',
|
|
2051
|
-
'Test cell 1-9'
|
|
2052
|
-
],
|
|
2053
|
-
[
|
|
2054
|
-
'Fact 2',
|
|
2055
|
-
'State 2',
|
|
2056
|
-
'Test cell 2-3',
|
|
2057
|
-
'Test cell 2-4',
|
|
2058
|
-
'Test cell 2-5',
|
|
2059
|
-
'Test cell 2-6',
|
|
2060
|
-
'Test cell 2-7',
|
|
2061
|
-
'Test cell 2-8',
|
|
2062
|
-
'Test cell 2-9'
|
|
2063
|
-
],
|
|
2064
|
-
[
|
|
2065
|
-
'Fact 3',
|
|
2066
|
-
'State 3',
|
|
2067
|
-
'Test cell 3-3',
|
|
2068
|
-
'Test cell 3-4',
|
|
2069
|
-
'Test cell 3-5',
|
|
2070
|
-
'Test cell 3-6',
|
|
2071
|
-
'Test cell 3-7',
|
|
2072
|
-
'Test cell 3-8',
|
|
2073
|
-
'Test cell 3-9'
|
|
2074
|
-
],
|
|
2075
|
-
[
|
|
2076
|
-
'Fact 4',
|
|
2077
|
-
'State 4',
|
|
2078
|
-
'Test cell 4-3',
|
|
2079
|
-
'Test cell 4-4',
|
|
2080
|
-
'Test cell 4-5',
|
|
2081
|
-
'Test cell 4-6',
|
|
2082
|
-
'Test cell 4-7',
|
|
2083
|
-
'Test cell 4-8',
|
|
2084
|
-
'Test cell 4-9'
|
|
2085
|
-
],
|
|
2086
|
-
[
|
|
2087
|
-
'Fact 5',
|
|
2088
|
-
'State 5',
|
|
2089
|
-
'Test cell 5-3',
|
|
2090
|
-
'Test cell 5-4',
|
|
2091
|
-
'Test cell 5-5',
|
|
2092
|
-
'Test cell 5-6',
|
|
2093
|
-
'Test cell 5-7',
|
|
2094
|
-
'Test cell 5-8',
|
|
2095
|
-
'Test cell 5-9'
|
|
2096
|
-
],
|
|
2097
|
-
[
|
|
2098
|
-
'Fact 6',
|
|
2099
|
-
'State 6',
|
|
2100
|
-
'Test cell 6-3',
|
|
2101
|
-
'Test cell 6-4',
|
|
2102
|
-
'Test cell 6-5',
|
|
2103
|
-
'Test cell 6-6',
|
|
2104
|
-
'Test cell 6-7',
|
|
2105
|
-
'Test cell 6-8',
|
|
2106
|
-
'Test cell 6-9'
|
|
2107
|
-
],
|
|
2108
|
-
[
|
|
2109
|
-
'Fact 7',
|
|
2110
|
-
'State 7',
|
|
2111
|
-
'Test cell 7-3',
|
|
2112
|
-
'Test cell 7-4',
|
|
2113
|
-
'Test cell 7-5',
|
|
2114
|
-
'Test cell 7-6',
|
|
2115
|
-
'Test cell 7-7',
|
|
2116
|
-
'Test cell 7-8',
|
|
2117
|
-
'Test cell 7-9'
|
|
2118
|
-
],
|
|
2119
|
-
[
|
|
2120
|
-
'Fact 8',
|
|
2121
|
-
'State 8',
|
|
2122
|
-
'Test cell 8-3',
|
|
2123
|
-
'Test cell 8-4',
|
|
2124
|
-
'Test cell 8-5',
|
|
2125
|
-
'Test cell 8-6',
|
|
2126
|
-
'Test cell 8-7',
|
|
2127
|
-
'Test cell 8-8',
|
|
2128
|
-
'Test cell 8-9'
|
|
2129
|
-
],
|
|
2130
|
-
[
|
|
2131
|
-
'Fact 9',
|
|
2132
|
-
'State 9',
|
|
2133
|
-
'Test cell 9-3',
|
|
2134
|
-
'Test cell 9-4',
|
|
2135
|
-
'Test cell 9-5',
|
|
2136
|
-
'Test cell 9-6',
|
|
2137
|
-
'Test cell 9-7',
|
|
2138
|
-
'Test cell 9-8',
|
|
2139
|
-
'Test cell 9-9'
|
|
2140
|
-
]
|
|
2141
|
-
]);
|
|
2142
|
-
// index of the currently active column
|
|
2143
|
-
const [activeSortIndex, setActiveSortIndex] = React.useState(-1);
|
|
2144
|
-
// sort direction of the currently active column
|
|
2145
|
-
const [activeSortDirection, setActiveSortDirection] = React.useState('none');
|
|
2146
|
-
const onSort = (event, index, direction) => {
|
|
2147
|
-
setActiveSortIndex(index);
|
|
2148
|
-
setActiveSortDirection(direction);
|
|
2149
|
-
// sorts the rows
|
|
2150
|
-
const updatedRows = rows.sort((a, b) => {
|
|
2151
|
-
if (typeof a[index] === 'number') {
|
|
2152
|
-
// numeric sort
|
|
2153
|
-
if (direction === 'asc') {
|
|
2154
|
-
return a[index] - b[index];
|
|
2155
|
-
}
|
|
2156
|
-
return b[index] - a[index];
|
|
2157
|
-
} else {
|
|
2158
|
-
// string sort
|
|
2159
|
-
if (direction === 'asc') {
|
|
2160
|
-
return a[index].localeCompare(b[index]);
|
|
2161
|
-
}
|
|
2162
|
-
return b[index].localeCompare(a[index]);
|
|
2163
|
-
}
|
|
2164
|
-
});
|
|
2165
|
-
setRows(updatedRows);
|
|
2166
|
-
};
|
|
2167
|
-
return (
|
|
2168
|
-
<React.Fragment>
|
|
2169
|
-
<InnerScrollContainer>
|
|
2170
|
-
<TableComposable aria-label="Sticky column table" gridBreakPoint="">
|
|
2171
|
-
<Thead>
|
|
2172
|
-
<Tr>
|
|
2173
|
-
{columns.map((column, columnIndex) => {
|
|
2174
|
-
const sortParams =
|
|
2175
|
-
columnIndex === 0 || columnIndex === 1
|
|
2176
|
-
? {
|
|
2177
|
-
sort: {
|
|
2178
|
-
sortBy: {
|
|
2179
|
-
index: activeSortIndex,
|
|
2180
|
-
direction: activeSortDirection
|
|
2181
|
-
},
|
|
2182
|
-
onSort,
|
|
2183
|
-
columnIndex
|
|
2184
|
-
}
|
|
2185
|
-
}
|
|
2186
|
-
: {};
|
|
2187
|
-
|
|
2188
|
-
if (columnIndex === 0) {
|
|
2189
|
-
return (
|
|
2190
|
-
<Th key={columnIndex} isStickyColumn hasRightBorder modifier="truncate" {...sortParams}>
|
|
2191
|
-
{column}
|
|
2192
|
-
</Th>
|
|
2193
|
-
);
|
|
2194
|
-
} else if (columnIndex === 1) {
|
|
2195
|
-
return (
|
|
2196
|
-
<Th key={columnIndex} modifier="truncate" {...sortParams}>
|
|
2197
|
-
{column}
|
|
2198
|
-
</Th>
|
|
2199
|
-
);
|
|
2200
|
-
} else {
|
|
2201
|
-
return (
|
|
2202
|
-
<Th key={columnIndex} modifier="truncate">
|
|
2203
|
-
{column}
|
|
2204
|
-
</Th>
|
|
2205
|
-
);
|
|
2206
|
-
}
|
|
2207
|
-
})}
|
|
2208
|
-
</Tr>
|
|
2209
|
-
</Thead>
|
|
2210
|
-
<Tbody>
|
|
2211
|
-
{rows.map((row, rowIndex) => (
|
|
2212
|
-
<Tr key={rowIndex}>
|
|
2213
|
-
{row.map((cell, cellIndex) => {
|
|
2214
|
-
if (cellIndex === 0) {
|
|
2215
|
-
return (
|
|
2216
|
-
<Th key={cellIndex} isStickyColumn hasRightBorder modifier="truncate">
|
|
2217
|
-
{cell}
|
|
2218
|
-
</Th>
|
|
2219
|
-
);
|
|
2220
|
-
} else {
|
|
2221
|
-
return (
|
|
2222
|
-
<Td key={`${rowIndex}_${cellIndex}`} modifier="nowrap" dataLabel={columns[cellIndex]}>
|
|
2223
|
-
{cell}
|
|
2224
|
-
</Td>
|
|
2225
|
-
);
|
|
2226
|
-
}
|
|
2227
|
-
})}
|
|
2228
|
-
</Tr>
|
|
2229
|
-
))}
|
|
2230
|
-
</Tbody>
|
|
2231
|
-
</TableComposable>
|
|
2232
|
-
</InnerScrollContainer>
|
|
2233
|
-
</React.Fragment>
|
|
2234
|
-
);
|
|
2235
|
-
};
|
|
310
|
+
```ts file="ComposableTableStickyColumn.tsx"
|
|
2236
311
|
```
|
|
2237
312
|
|
|
2238
313
|
### Composable: Multiple sticky columns
|
|
2239
314
|
|
|
2240
315
|
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` should also have the `modifier="nowrap"` property.
|
|
2241
316
|
|
|
2242
|
-
```
|
|
2243
|
-
import React from 'react';
|
|
2244
|
-
import {
|
|
2245
|
-
TableComposable,
|
|
2246
|
-
Thead,
|
|
2247
|
-
Tbody,
|
|
2248
|
-
Tr,
|
|
2249
|
-
Th,
|
|
2250
|
-
Td,
|
|
2251
|
-
Caption,
|
|
2252
|
-
TableText,
|
|
2253
|
-
InnerScrollContainer
|
|
2254
|
-
} from '@patternfly/react-table';
|
|
2255
|
-
import { Flex, FlexItem } from '@patternfly/react-core';
|
|
2256
|
-
import BlueprintIcon from '@patternfly/react-icons/dist/esm/icons/blueprint-icon';
|
|
2257
|
-
|
|
2258
|
-
ComposableTableMultipleStickyColumn = () => {
|
|
2259
|
-
const columns = ['Fact', 'State', 'Header 3', 'Header 4', 'Header 5', 'Header 6', 'Header 7', 'Header 8', 'Header 9'];
|
|
2260
|
-
const [rows, setRows] = React.useState([
|
|
2261
|
-
[
|
|
2262
|
-
'Fact 1',
|
|
2263
|
-
'State 1',
|
|
2264
|
-
'Test cell 1-3',
|
|
2265
|
-
'Test cell 1-4',
|
|
2266
|
-
'Test cell 1-5',
|
|
2267
|
-
'Test cell 1-6',
|
|
2268
|
-
'Test cell 1-7',
|
|
2269
|
-
'Test cell 1-8',
|
|
2270
|
-
'Test cell 1-9'
|
|
2271
|
-
],
|
|
2272
|
-
[
|
|
2273
|
-
'Fact 2',
|
|
2274
|
-
'State 2',
|
|
2275
|
-
'Test cell 2-3',
|
|
2276
|
-
'Test cell 2-4',
|
|
2277
|
-
'Test cell 2-5',
|
|
2278
|
-
'Test cell 2-6',
|
|
2279
|
-
'Test cell 2-7',
|
|
2280
|
-
'Test cell 2-8',
|
|
2281
|
-
'Test cell 2-9'
|
|
2282
|
-
],
|
|
2283
|
-
[
|
|
2284
|
-
'Fact 3',
|
|
2285
|
-
'State 3',
|
|
2286
|
-
'Test cell 3-3',
|
|
2287
|
-
'Test cell 3-4',
|
|
2288
|
-
'Test cell 3-5',
|
|
2289
|
-
'Test cell 3-6',
|
|
2290
|
-
'Test cell 3-7',
|
|
2291
|
-
'Test cell 3-8',
|
|
2292
|
-
'Test cell 3-9'
|
|
2293
|
-
],
|
|
2294
|
-
[
|
|
2295
|
-
'Fact 4',
|
|
2296
|
-
'State 4',
|
|
2297
|
-
'Test cell 4-3',
|
|
2298
|
-
'Test cell 4-4',
|
|
2299
|
-
'Test cell 4-5',
|
|
2300
|
-
'Test cell 4-6',
|
|
2301
|
-
'Test cell 4-7',
|
|
2302
|
-
'Test cell 4-8',
|
|
2303
|
-
'Test cell 4-9'
|
|
2304
|
-
],
|
|
2305
|
-
[
|
|
2306
|
-
'Fact 5',
|
|
2307
|
-
'State 5',
|
|
2308
|
-
'Test cell 5-3',
|
|
2309
|
-
'Test cell 5-4',
|
|
2310
|
-
'Test cell 5-5',
|
|
2311
|
-
'Test cell 5-6',
|
|
2312
|
-
'Test cell 5-7',
|
|
2313
|
-
'Test cell 5-8',
|
|
2314
|
-
'Test cell 5-9'
|
|
2315
|
-
],
|
|
2316
|
-
[
|
|
2317
|
-
'Fact 6',
|
|
2318
|
-
'State 6',
|
|
2319
|
-
'Test cell 6-3',
|
|
2320
|
-
'Test cell 6-4',
|
|
2321
|
-
'Test cell 6-5',
|
|
2322
|
-
'Test cell 6-6',
|
|
2323
|
-
'Test cell 6-7',
|
|
2324
|
-
'Test cell 6-8',
|
|
2325
|
-
'Test cell 6-9'
|
|
2326
|
-
],
|
|
2327
|
-
[
|
|
2328
|
-
'Fact 7',
|
|
2329
|
-
'State 7',
|
|
2330
|
-
'Test cell 7-3',
|
|
2331
|
-
'Test cell 7-4',
|
|
2332
|
-
'Test cell 7-5',
|
|
2333
|
-
'Test cell 7-6',
|
|
2334
|
-
'Test cell 7-7',
|
|
2335
|
-
'Test cell 7-8',
|
|
2336
|
-
'Test cell 7-9'
|
|
2337
|
-
],
|
|
2338
|
-
[
|
|
2339
|
-
'Fact 8',
|
|
2340
|
-
'State 8',
|
|
2341
|
-
'Test cell 8-3',
|
|
2342
|
-
'Test cell 8-4',
|
|
2343
|
-
'Test cell 8-5',
|
|
2344
|
-
'Test cell 8-6',
|
|
2345
|
-
'Test cell 8-7',
|
|
2346
|
-
'Test cell 8-8',
|
|
2347
|
-
'Test cell 8-9'
|
|
2348
|
-
],
|
|
2349
|
-
[
|
|
2350
|
-
'Fact 9',
|
|
2351
|
-
'State 9',
|
|
2352
|
-
'Test cell 9-3',
|
|
2353
|
-
'Test cell 9-4',
|
|
2354
|
-
'Test cell 9-5',
|
|
2355
|
-
'Test cell 9-6',
|
|
2356
|
-
'Test cell 9-7',
|
|
2357
|
-
'Test cell 9-8',
|
|
2358
|
-
'Test cell 9-9'
|
|
2359
|
-
]
|
|
2360
|
-
]);
|
|
2361
|
-
// index of the currently active column
|
|
2362
|
-
const [activeSortIndex, setActiveSortIndex] = React.useState(-1);
|
|
2363
|
-
// sort direction of the currently active column
|
|
2364
|
-
const [activeSortDirection, setActiveSortDirection] = React.useState('none');
|
|
2365
|
-
const onSort = (event, index, direction) => {
|
|
2366
|
-
setActiveSortIndex(index);
|
|
2367
|
-
setActiveSortDirection(direction);
|
|
2368
|
-
// sorts the rows
|
|
2369
|
-
const updatedRows = rows.sort((a, b) => {
|
|
2370
|
-
if (typeof a[index] === 'number') {
|
|
2371
|
-
// numeric sort
|
|
2372
|
-
if (direction === 'asc') {
|
|
2373
|
-
return a[index] - b[index];
|
|
2374
|
-
}
|
|
2375
|
-
return b[index] - a[index];
|
|
2376
|
-
} else {
|
|
2377
|
-
// string sort
|
|
2378
|
-
if (direction === 'asc') {
|
|
2379
|
-
return a[index].localeCompare(b[index]);
|
|
2380
|
-
}
|
|
2381
|
-
return b[index].localeCompare(a[index]);
|
|
2382
|
-
}
|
|
2383
|
-
});
|
|
2384
|
-
setRows(updatedRows);
|
|
2385
|
-
};
|
|
2386
|
-
return (
|
|
2387
|
-
<React.Fragment>
|
|
2388
|
-
<InnerScrollContainer>
|
|
2389
|
-
<TableComposable aria-label="Sticky column table" gridBreakPoint="">
|
|
2390
|
-
<Thead>
|
|
2391
|
-
<Tr>
|
|
2392
|
-
{columns.map((column, columnIndex) => {
|
|
2393
|
-
const sortParams =
|
|
2394
|
-
columnIndex === 0 || columnIndex === 1
|
|
2395
|
-
? {
|
|
2396
|
-
sort: {
|
|
2397
|
-
sortBy: {
|
|
2398
|
-
index: activeSortIndex,
|
|
2399
|
-
direction: activeSortDirection
|
|
2400
|
-
},
|
|
2401
|
-
onSort,
|
|
2402
|
-
columnIndex
|
|
2403
|
-
}
|
|
2404
|
-
}
|
|
2405
|
-
: {};
|
|
2406
|
-
|
|
2407
|
-
if (columnIndex === 0) {
|
|
2408
|
-
return (
|
|
2409
|
-
<Th key={columnIndex} isStickyColumn modifier="truncate" {...sortParams}>
|
|
2410
|
-
{column}
|
|
2411
|
-
</Th>
|
|
2412
|
-
);
|
|
2413
|
-
} else if (columnIndex === 1) {
|
|
2414
|
-
return (
|
|
2415
|
-
<Th
|
|
2416
|
-
key={columnIndex}
|
|
2417
|
-
isStickyColumn
|
|
2418
|
-
stickyMinWidth="120px"
|
|
2419
|
-
stickyLeftOffset="120px"
|
|
2420
|
-
hasRightBorder
|
|
2421
|
-
modifier="truncate"
|
|
2422
|
-
{...sortParams}
|
|
2423
|
-
>
|
|
2424
|
-
{column}
|
|
2425
|
-
</Th>
|
|
2426
|
-
);
|
|
2427
|
-
} else {
|
|
2428
|
-
return (
|
|
2429
|
-
<Th key={columnIndex} modifier="truncate">
|
|
2430
|
-
{column}
|
|
2431
|
-
</Th>
|
|
2432
|
-
);
|
|
2433
|
-
}
|
|
2434
|
-
})}
|
|
2435
|
-
</Tr>
|
|
2436
|
-
</Thead>
|
|
2437
|
-
<Tbody>
|
|
2438
|
-
{rows.map((row, rowIndex) => (
|
|
2439
|
-
<Tr key={rowIndex}>
|
|
2440
|
-
{row.map((cell, cellIndex) => {
|
|
2441
|
-
if (cellIndex === 0) {
|
|
2442
|
-
return (
|
|
2443
|
-
<Th key={cellIndex} isStickyColumn modifier="truncate">
|
|
2444
|
-
{cell}
|
|
2445
|
-
</Th>
|
|
2446
|
-
);
|
|
2447
|
-
} else if (cellIndex === 1) {
|
|
2448
|
-
return (
|
|
2449
|
-
<Th
|
|
2450
|
-
key={cellIndex}
|
|
2451
|
-
isStickyColumn
|
|
2452
|
-
stickyMinWidth="120px"
|
|
2453
|
-
stickyLeftOffset="120px"
|
|
2454
|
-
modifier="truncate"
|
|
2455
|
-
hasRightBorder
|
|
2456
|
-
>
|
|
2457
|
-
<BlueprintIcon />
|
|
2458
|
-
{` ${cell}`}
|
|
2459
|
-
</Th>
|
|
2460
|
-
);
|
|
2461
|
-
} else {
|
|
2462
|
-
return (
|
|
2463
|
-
<Td key={`${rowIndex}_${cellIndex}`} modifier="nowrap" dataLabel={columns[cellIndex]}>
|
|
2464
|
-
{cell}
|
|
2465
|
-
</Td>
|
|
2466
|
-
);
|
|
2467
|
-
}
|
|
2468
|
-
})}
|
|
2469
|
-
</Tr>
|
|
2470
|
-
))}
|
|
2471
|
-
</Tbody>
|
|
2472
|
-
</TableComposable>
|
|
2473
|
-
</InnerScrollContainer>
|
|
2474
|
-
</React.Fragment>
|
|
2475
|
-
);
|
|
2476
|
-
};
|
|
317
|
+
```ts file="ComposableTableMultipleStickyColumns.tsx"
|
|
2477
318
|
```
|
|
2478
319
|
|
|
2479
320
|
### Composable: Sticky columns and header
|
|
2480
321
|
|
|
2481
322
|
To maintain proper sticky behavior across sticky columns and header, `TableComposable` must be wrapped with `OuterScrollContainer` and `InnerScrollContainer` as shown in the example below.
|
|
2482
323
|
|
|
2483
|
-
```
|
|
2484
|
-
import React from 'react';
|
|
2485
|
-
import {
|
|
2486
|
-
TableComposable,
|
|
2487
|
-
Thead,
|
|
2488
|
-
Tbody,
|
|
2489
|
-
Tr,
|
|
2490
|
-
Th,
|
|
2491
|
-
Td,
|
|
2492
|
-
Caption,
|
|
2493
|
-
TableText,
|
|
2494
|
-
InnerScrollContainer,
|
|
2495
|
-
OuterScrollContainer
|
|
2496
|
-
} from '@patternfly/react-table';
|
|
2497
|
-
import { Flex, FlexItem } from '@patternfly/react-core';
|
|
2498
|
-
import BlueprintIcon from '@patternfly/react-icons/dist/esm/icons/blueprint-icon';
|
|
2499
|
-
|
|
2500
|
-
ComposableTableStickyColumnAndHeader = () => {
|
|
2501
|
-
const columns = ['Fact', 'State', 'Header 3', 'Header 4', 'Header 5', 'Header 6', 'Header 7', 'Header 8', 'Header 9'];
|
|
2502
|
-
const [rows, setRows] = React.useState([
|
|
2503
|
-
[
|
|
2504
|
-
'Fact 1',
|
|
2505
|
-
'State 1',
|
|
2506
|
-
'Test cell 1-3',
|
|
2507
|
-
'Test cell 1-4',
|
|
2508
|
-
'Test cell 1-5',
|
|
2509
|
-
'Test cell 1-6',
|
|
2510
|
-
'Test cell 1-7',
|
|
2511
|
-
'Test cell 1-8',
|
|
2512
|
-
'Test cell 1-9'
|
|
2513
|
-
],
|
|
2514
|
-
[
|
|
2515
|
-
'Fact 2',
|
|
2516
|
-
'State 2',
|
|
2517
|
-
'Test cell 2-3',
|
|
2518
|
-
'Test cell 2-4',
|
|
2519
|
-
'Test cell 2-5',
|
|
2520
|
-
'Test cell 2-6',
|
|
2521
|
-
'Test cell 2-7',
|
|
2522
|
-
'Test cell 2-8',
|
|
2523
|
-
'Test cell 2-9'
|
|
2524
|
-
],
|
|
2525
|
-
[
|
|
2526
|
-
'Fact 3',
|
|
2527
|
-
'State 3',
|
|
2528
|
-
'Test cell 3-3',
|
|
2529
|
-
'Test cell 3-4',
|
|
2530
|
-
'Test cell 3-5',
|
|
2531
|
-
'Test cell 3-6',
|
|
2532
|
-
'Test cell 3-7',
|
|
2533
|
-
'Test cell 3-8',
|
|
2534
|
-
'Test cell 3-9'
|
|
2535
|
-
],
|
|
2536
|
-
[
|
|
2537
|
-
'Fact 4',
|
|
2538
|
-
'State 4',
|
|
2539
|
-
'Test cell 4-3',
|
|
2540
|
-
'Test cell 4-4',
|
|
2541
|
-
'Test cell 4-5',
|
|
2542
|
-
'Test cell 4-6',
|
|
2543
|
-
'Test cell 4-7',
|
|
2544
|
-
'Test cell 4-8',
|
|
2545
|
-
'Test cell 4-9'
|
|
2546
|
-
],
|
|
2547
|
-
[
|
|
2548
|
-
'Fact 5',
|
|
2549
|
-
'State 5',
|
|
2550
|
-
'Test cell 5-3',
|
|
2551
|
-
'Test cell 5-4',
|
|
2552
|
-
'Test cell 5-5',
|
|
2553
|
-
'Test cell 5-6',
|
|
2554
|
-
'Test cell 5-7',
|
|
2555
|
-
'Test cell 5-8',
|
|
2556
|
-
'Test cell 5-9'
|
|
2557
|
-
],
|
|
2558
|
-
[
|
|
2559
|
-
'Fact 6',
|
|
2560
|
-
'State 6',
|
|
2561
|
-
'Test cell 6-3',
|
|
2562
|
-
'Test cell 6-4',
|
|
2563
|
-
'Test cell 6-5',
|
|
2564
|
-
'Test cell 6-6',
|
|
2565
|
-
'Test cell 6-7',
|
|
2566
|
-
'Test cell 6-8',
|
|
2567
|
-
'Test cell 6-9'
|
|
2568
|
-
],
|
|
2569
|
-
[
|
|
2570
|
-
'Fact 7',
|
|
2571
|
-
'State 7',
|
|
2572
|
-
'Test cell 7-3',
|
|
2573
|
-
'Test cell 7-4',
|
|
2574
|
-
'Test cell 7-5',
|
|
2575
|
-
'Test cell 7-6',
|
|
2576
|
-
'Test cell 7-7',
|
|
2577
|
-
'Test cell 7-8',
|
|
2578
|
-
'Test cell 7-9'
|
|
2579
|
-
],
|
|
2580
|
-
[
|
|
2581
|
-
'Fact 8',
|
|
2582
|
-
'State 8',
|
|
2583
|
-
'Test cell 8-3',
|
|
2584
|
-
'Test cell 8-4',
|
|
2585
|
-
'Test cell 8-5',
|
|
2586
|
-
'Test cell 8-6',
|
|
2587
|
-
'Test cell 8-7',
|
|
2588
|
-
'Test cell 8-8',
|
|
2589
|
-
'Test cell 8-9'
|
|
2590
|
-
],
|
|
2591
|
-
[
|
|
2592
|
-
'Fact 9',
|
|
2593
|
-
'State 9',
|
|
2594
|
-
'Test cell 9-3',
|
|
2595
|
-
'Test cell 9-4',
|
|
2596
|
-
'Test cell 9-5',
|
|
2597
|
-
'Test cell 9-6',
|
|
2598
|
-
'Test cell 9-7',
|
|
2599
|
-
'Test cell 9-8',
|
|
2600
|
-
'Test cell 9-9'
|
|
2601
|
-
]
|
|
2602
|
-
]);
|
|
2603
|
-
// index of the currently active column
|
|
2604
|
-
const [activeSortIndex, setActiveSortIndex] = React.useState(-1);
|
|
2605
|
-
// sort direction of the currently active column
|
|
2606
|
-
const [activeSortDirection, setActiveSortDirection] = React.useState('none');
|
|
2607
|
-
const onSort = (event, index, direction) => {
|
|
2608
|
-
setActiveSortIndex(index);
|
|
2609
|
-
setActiveSortDirection(direction);
|
|
2610
|
-
// sorts the rows
|
|
2611
|
-
const updatedRows = rows.sort((a, b) => {
|
|
2612
|
-
if (typeof a[index] === 'number') {
|
|
2613
|
-
// numeric sort
|
|
2614
|
-
if (direction === 'asc') {
|
|
2615
|
-
return a[index] - b[index];
|
|
2616
|
-
}
|
|
2617
|
-
return b[index] - a[index];
|
|
2618
|
-
} else {
|
|
2619
|
-
// string sort
|
|
2620
|
-
if (direction === 'asc') {
|
|
2621
|
-
return a[index].localeCompare(b[index]);
|
|
2622
|
-
}
|
|
2623
|
-
return b[index].localeCompare(a[index]);
|
|
2624
|
-
}
|
|
2625
|
-
});
|
|
2626
|
-
setRows(updatedRows);
|
|
2627
|
-
};
|
|
2628
|
-
return (
|
|
2629
|
-
<React.Fragment>
|
|
2630
|
-
<div style={{ height: '600px' }}>
|
|
2631
|
-
<OuterScrollContainer>
|
|
2632
|
-
<InnerScrollContainer>
|
|
2633
|
-
<TableComposable aria-label="Sticky column table" gridBreakPoint="" isStickyHeader>
|
|
2634
|
-
<Thead>
|
|
2635
|
-
<Tr>
|
|
2636
|
-
{columns.map((column, columnIndex) => {
|
|
2637
|
-
const sortParams =
|
|
2638
|
-
columnIndex === 0 || columnIndex === 1
|
|
2639
|
-
? {
|
|
2640
|
-
sort: {
|
|
2641
|
-
sortBy: {
|
|
2642
|
-
index: activeSortIndex,
|
|
2643
|
-
direction: activeSortDirection
|
|
2644
|
-
},
|
|
2645
|
-
onSort,
|
|
2646
|
-
columnIndex
|
|
2647
|
-
}
|
|
2648
|
-
}
|
|
2649
|
-
: {};
|
|
2650
|
-
|
|
2651
|
-
if (columnIndex === 0) {
|
|
2652
|
-
return (
|
|
2653
|
-
<Th key={columnIndex} isStickyColumn modifier="truncate" {...sortParams}>
|
|
2654
|
-
{column}
|
|
2655
|
-
</Th>
|
|
2656
|
-
);
|
|
2657
|
-
} else if (columnIndex === 1) {
|
|
2658
|
-
return (
|
|
2659
|
-
<Th
|
|
2660
|
-
key={columnIndex}
|
|
2661
|
-
isStickyColumn
|
|
2662
|
-
stickyMinWidth="120px"
|
|
2663
|
-
stickyLeftOffset="120px"
|
|
2664
|
-
hasRightBorder
|
|
2665
|
-
modifier="truncate"
|
|
2666
|
-
{...sortParams}
|
|
2667
|
-
>
|
|
2668
|
-
{column}
|
|
2669
|
-
</Th>
|
|
2670
|
-
);
|
|
2671
|
-
} else {
|
|
2672
|
-
return (
|
|
2673
|
-
<Th key={columnIndex} modifier="truncate">
|
|
2674
|
-
{column}
|
|
2675
|
-
</Th>
|
|
2676
|
-
);
|
|
2677
|
-
}
|
|
2678
|
-
})}
|
|
2679
|
-
</Tr>
|
|
2680
|
-
</Thead>
|
|
2681
|
-
<Tbody>
|
|
2682
|
-
{rows.map((row, rowIndex) => (
|
|
2683
|
-
<Tr key={rowIndex}>
|
|
2684
|
-
{row.map((cell, cellIndex) => {
|
|
2685
|
-
if (cellIndex === 0) {
|
|
2686
|
-
return (
|
|
2687
|
-
<Th key={cellIndex} isStickyColumn modifier="truncate">
|
|
2688
|
-
{cell}
|
|
2689
|
-
</Th>
|
|
2690
|
-
);
|
|
2691
|
-
} else if (cellIndex === 1) {
|
|
2692
|
-
return (
|
|
2693
|
-
<Th
|
|
2694
|
-
key={cellIndex}
|
|
2695
|
-
isStickyColumn
|
|
2696
|
-
stickyMinWidth="120px"
|
|
2697
|
-
stickyLeftOffset="120px"
|
|
2698
|
-
modifier="truncate"
|
|
2699
|
-
hasRightBorder
|
|
2700
|
-
>
|
|
2701
|
-
<BlueprintIcon />
|
|
2702
|
-
{` ${cell}`}
|
|
2703
|
-
</Th>
|
|
2704
|
-
);
|
|
2705
|
-
} else {
|
|
2706
|
-
return (
|
|
2707
|
-
<Td key={`${rowIndex}_${cellIndex}`} modifier="nowrap" dataLabel={columns[cellIndex]}>
|
|
2708
|
-
{cell}
|
|
2709
|
-
</Td>
|
|
2710
|
-
);
|
|
2711
|
-
}
|
|
2712
|
-
})}
|
|
2713
|
-
</Tr>
|
|
2714
|
-
))}
|
|
2715
|
-
</Tbody>
|
|
2716
|
-
</TableComposable>
|
|
2717
|
-
</InnerScrollContainer>
|
|
2718
|
-
</OuterScrollContainer>
|
|
2719
|
-
</div>
|
|
2720
|
-
</React.Fragment>
|
|
2721
|
-
);
|
|
2722
|
-
};
|
|
324
|
+
```ts file="ComposableTableStickyColumnsAndHeader.tsx"
|
|
2723
325
|
```
|
|
2724
326
|
|
|
2725
327
|
### Composable: Nested column headers
|
|
@@ -2735,386 +337,10 @@ The first `Tr` represents the top level of columns, and each must pass either `r
|
|
|
2735
337
|
|
|
2736
338
|
The second `Tr` represents the second level of sub columns. The `Th` in this row each should pass `isSubHeader`, and the last sub column of a column should also pass `hasRightBorder`.
|
|
2737
339
|
|
|
2738
|
-
```
|
|
2739
|
-
import React from 'react';
|
|
2740
|
-
import {
|
|
2741
|
-
TableComposable,
|
|
2742
|
-
Thead,
|
|
2743
|
-
Tbody,
|
|
2744
|
-
Tr,
|
|
2745
|
-
Th,
|
|
2746
|
-
Td,
|
|
2747
|
-
Caption,
|
|
2748
|
-
TableText,
|
|
2749
|
-
InnerScrollContainer,
|
|
2750
|
-
OuterScrollContainer
|
|
2751
|
-
} from '@patternfly/react-table';
|
|
2752
|
-
import { Flex, FlexItem, Stack, StackItem } from '@patternfly/react-core';
|
|
2753
|
-
import BlueprintIcon from '@patternfly/react-icons/dist/esm/icons/blueprint-icon';
|
|
2754
|
-
|
|
2755
|
-
ComposableTableStickyColumnAndHeader = () => {
|
|
2756
|
-
const columnData = [
|
|
2757
|
-
{
|
|
2758
|
-
name: 'Pods',
|
|
2759
|
-
subColumns: [
|
|
2760
|
-
{ name: 'Source', index: 0, sort: true },
|
|
2761
|
-
{ name: 'Destination', index: 1, sort: true },
|
|
2762
|
-
{ name: 'Date & Time', index: 2, sort: true }
|
|
2763
|
-
]
|
|
2764
|
-
},
|
|
2765
|
-
{
|
|
2766
|
-
name: 'Ports',
|
|
2767
|
-
subColumns: [
|
|
2768
|
-
{ name: 'Source', index: 3, sort: true },
|
|
2769
|
-
{ name: 'Desination', index: 4, sort: true }
|
|
2770
|
-
]
|
|
2771
|
-
},
|
|
2772
|
-
{ name: 'Protocol', index: 5, sort: true },
|
|
2773
|
-
{ name: 'Flow rate', index: 6, sort: true },
|
|
2774
|
-
{ name: 'Traffic', index: 7, sort: true },
|
|
2775
|
-
{ name: 'Packets', index: 8, sort: true }
|
|
2776
|
-
];
|
|
2777
|
-
const colSpans = columnData.map(column => {
|
|
2778
|
-
return column.subColumns ? column.subColumns.length : 1;
|
|
2779
|
-
});
|
|
2780
|
-
const [rows, setRows] = React.useState([
|
|
2781
|
-
[
|
|
2782
|
-
'api-pod-source-name',
|
|
2783
|
-
'api-pod-destination-name',
|
|
2784
|
-
<div>
|
|
2785
|
-
<span>June 22, 2021</span>
|
|
2786
|
-
<span className="pf-u-color-200">3:58:24 PM</span>
|
|
2787
|
-
</div>,
|
|
2788
|
-
<Stack>
|
|
2789
|
-
<StackItem>
|
|
2790
|
-
<span>443</span>
|
|
2791
|
-
</StackItem>
|
|
2792
|
-
<StackItem>
|
|
2793
|
-
<span className="pf-u-color-200">(HTTPS)</span>
|
|
2794
|
-
</StackItem>
|
|
2795
|
-
</Stack>,
|
|
2796
|
-
<Stack>
|
|
2797
|
-
<StackItem>
|
|
2798
|
-
<span>24</span>
|
|
2799
|
-
</StackItem>
|
|
2800
|
-
<StackItem>
|
|
2801
|
-
<span className="pf-u-color-200">(smtp)</span>
|
|
2802
|
-
</StackItem>
|
|
2803
|
-
</Stack>,
|
|
2804
|
-
'TCP',
|
|
2805
|
-
'1.9 Kbps',
|
|
2806
|
-
'2.1 KB',
|
|
2807
|
-
'3'
|
|
2808
|
-
]
|
|
2809
|
-
]);
|
|
2810
|
-
// index of the currently active column
|
|
2811
|
-
const [activeSortIndex, setActiveSortIndex] = React.useState(-1);
|
|
2812
|
-
// sort direction of the currently active column
|
|
2813
|
-
const [activeSortDirection, setActiveSortDirection] = React.useState('none');
|
|
2814
|
-
const onSort = (event, index, direction) => {
|
|
2815
|
-
setActiveSortIndex(index);
|
|
2816
|
-
setActiveSortDirection(direction);
|
|
2817
|
-
// sorts the rows
|
|
2818
|
-
const updatedRows = rows.sort((a, b) => {
|
|
2819
|
-
if (typeof a[index] === 'number') {
|
|
2820
|
-
// numeric sort
|
|
2821
|
-
if (direction === 'asc') {
|
|
2822
|
-
return a[index] - b[index];
|
|
2823
|
-
}
|
|
2824
|
-
return b[index] - a[index];
|
|
2825
|
-
} else {
|
|
2826
|
-
// string sort
|
|
2827
|
-
if (direction === 'asc') {
|
|
2828
|
-
return a[index].localeCompare(b[index]);
|
|
2829
|
-
}
|
|
2830
|
-
return b[index].localeCompare(a[index]);
|
|
2831
|
-
}
|
|
2832
|
-
});
|
|
2833
|
-
setRows(updatedRows);
|
|
2834
|
-
};
|
|
2835
|
-
|
|
2836
|
-
return (
|
|
2837
|
-
<React.Fragment>
|
|
2838
|
-
<InnerScrollContainer>
|
|
2839
|
-
<TableComposable aria-label="Sticky column table" gridBreakPoint="" nestedHeaderColumnSpans={colSpans}>
|
|
2840
|
-
<Thead hasNestedHeader>
|
|
2841
|
-
<Tr>
|
|
2842
|
-
{columnData.map((column, columnIndex) => {
|
|
2843
|
-
const sortParams = column.sort
|
|
2844
|
-
? {
|
|
2845
|
-
sort: {
|
|
2846
|
-
sortBy: {
|
|
2847
|
-
index: activeSortIndex,
|
|
2848
|
-
direction: activeSortDirection
|
|
2849
|
-
},
|
|
2850
|
-
onSort,
|
|
2851
|
-
columnIndex: column.index
|
|
2852
|
-
}
|
|
2853
|
-
}
|
|
2854
|
-
: {};
|
|
2855
|
-
const colParams = column.subColumns ? { colSpan: column.subColumns.length } : { rowSpan: 2 };
|
|
2856
|
-
return (
|
|
2857
|
-
<Th
|
|
2858
|
-
key={`${columnIndex}-${column.name}`}
|
|
2859
|
-
{...(columnIndex !== 0 && columnIndex !== 1 && { modifier: 'fitContent' })}
|
|
2860
|
-
{...(columnIndex !== columnData.length - 1 && { hasRightBorder: true })}
|
|
2861
|
-
{...colParams}
|
|
2862
|
-
{...sortParams}
|
|
2863
|
-
>
|
|
2864
|
-
{column.name}
|
|
2865
|
-
</Th>
|
|
2866
|
-
);
|
|
2867
|
-
})}
|
|
2868
|
-
</Tr>
|
|
2869
|
-
<Tr>
|
|
2870
|
-
{columnData.map((column, columnIndex) => {
|
|
2871
|
-
if (column.subColumns) {
|
|
2872
|
-
return column.subColumns.map((subColumn, subColumnIndex) => {
|
|
2873
|
-
const sortParams = subColumn.sort
|
|
2874
|
-
? {
|
|
2875
|
-
sort: {
|
|
2876
|
-
sortBy: {
|
|
2877
|
-
index: activeSortIndex,
|
|
2878
|
-
direction: activeSortDirection
|
|
2879
|
-
},
|
|
2880
|
-
onSort,
|
|
2881
|
-
columnIndex: subColumn.index
|
|
2882
|
-
}
|
|
2883
|
-
}
|
|
2884
|
-
: {};
|
|
2885
|
-
|
|
2886
|
-
return (
|
|
2887
|
-
<Th
|
|
2888
|
-
key={`${subColumnIndex}-${subColumn.name}`}
|
|
2889
|
-
isSubheader
|
|
2890
|
-
{...(columnIndex !== 0 && { modifier: 'fitContent' })}
|
|
2891
|
-
{...(columnIndex === 0 &&
|
|
2892
|
-
subColumnIndex !== 0 &&
|
|
2893
|
-
subColumnIndex !== 1 && { modifier: 'fitContent' })}
|
|
2894
|
-
{...(subColumnIndex === column.subColumns.length - 1 && { hasRightBorder: true })}
|
|
2895
|
-
{...sortParams}
|
|
2896
|
-
>
|
|
2897
|
-
{subColumn.name}
|
|
2898
|
-
</Th>
|
|
2899
|
-
);
|
|
2900
|
-
});
|
|
2901
|
-
}
|
|
2902
|
-
})}
|
|
2903
|
-
</Tr>
|
|
2904
|
-
</Thead>
|
|
2905
|
-
<Tbody>
|
|
2906
|
-
{rows.map((row, rowIndex) => (
|
|
2907
|
-
<Tr key={rowIndex}>
|
|
2908
|
-
{row.map((cell, cellIndex) => {
|
|
2909
|
-
return (
|
|
2910
|
-
<Td key={`${rowIndex}_${cellIndex}`} dataLabel={''}>
|
|
2911
|
-
{cell}
|
|
2912
|
-
</Td>
|
|
2913
|
-
);
|
|
2914
|
-
})}
|
|
2915
|
-
</Tr>
|
|
2916
|
-
))}
|
|
2917
|
-
</Tbody>
|
|
2918
|
-
</TableComposable>
|
|
2919
|
-
</InnerScrollContainer>
|
|
2920
|
-
</React.Fragment>
|
|
2921
|
-
);
|
|
2922
|
-
};
|
|
340
|
+
```ts file="ComposableTableNestedHeaders.tsx"
|
|
2923
341
|
```
|
|
2924
342
|
|
|
2925
343
|
### Composable: Nested column headers and expandable rows
|
|
2926
344
|
|
|
2927
|
-
```
|
|
2928
|
-
import React from 'react';
|
|
2929
|
-
import {
|
|
2930
|
-
TableComposable,
|
|
2931
|
-
Thead,
|
|
2932
|
-
Tbody,
|
|
2933
|
-
Tr,
|
|
2934
|
-
Th,
|
|
2935
|
-
Td,
|
|
2936
|
-
ExpandableRowContent,
|
|
2937
|
-
InnerScrollContainer
|
|
2938
|
-
} from '@patternfly/react-table';
|
|
2939
|
-
import { Button } from '@patternfly/react-core';
|
|
2940
|
-
|
|
2941
|
-
ComposableTableNestedExpandable = () => {
|
|
2942
|
-
const columns = [
|
|
2943
|
-
{
|
|
2944
|
-
name: 'Team',
|
|
2945
|
-
index: 0
|
|
2946
|
-
},
|
|
2947
|
-
{
|
|
2948
|
-
name: 'Members',
|
|
2949
|
-
subColumns: [
|
|
2950
|
-
{ name: 'Design Lead', index: 1 },
|
|
2951
|
-
{ name: 'Interaction Designer', index: 2 },
|
|
2952
|
-
{ name: 'Visual Designer', index: 3 }
|
|
2953
|
-
]
|
|
2954
|
-
},
|
|
2955
|
-
{ name: 'Contact', index: 4 }
|
|
2956
|
-
];
|
|
2957
|
-
const rowPairs = [
|
|
2958
|
-
{
|
|
2959
|
-
parent: [
|
|
2960
|
-
'Developer program',
|
|
2961
|
-
'Person 1',
|
|
2962
|
-
'Person 2',
|
|
2963
|
-
'Person 3',
|
|
2964
|
-
<Button variant="link" isInline>
|
|
2965
|
-
Message us!
|
|
2966
|
-
</Button>
|
|
2967
|
-
],
|
|
2968
|
-
child: [
|
|
2969
|
-
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'
|
|
2970
|
-
],
|
|
2971
|
-
fullWidth: true
|
|
2972
|
-
},
|
|
2973
|
-
{
|
|
2974
|
-
parent: [
|
|
2975
|
-
'Developer program',
|
|
2976
|
-
'Person A',
|
|
2977
|
-
'Person B',
|
|
2978
|
-
'Person C',
|
|
2979
|
-
<Button variant="link" isInline>
|
|
2980
|
-
Message us!
|
|
2981
|
-
</Button>
|
|
2982
|
-
],
|
|
2983
|
-
child: [
|
|
2984
|
-
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'
|
|
2985
|
-
],
|
|
2986
|
-
fullWidth: true
|
|
2987
|
-
},
|
|
2988
|
-
{
|
|
2989
|
-
parent: [
|
|
2990
|
-
'Developer program',
|
|
2991
|
-
'Person X',
|
|
2992
|
-
'Person Y',
|
|
2993
|
-
'Person Z',
|
|
2994
|
-
<Button variant="link" isInline>
|
|
2995
|
-
Message us!
|
|
2996
|
-
</Button>
|
|
2997
|
-
],
|
|
2998
|
-
child: [
|
|
2999
|
-
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'
|
|
3000
|
-
],
|
|
3001
|
-
fullWidth: true
|
|
3002
|
-
}
|
|
3003
|
-
];
|
|
3004
|
-
const numColumns = 5;
|
|
3005
|
-
const colSpans = columns.map(column => {
|
|
3006
|
-
return column.subColumns ? column.subColumns.length : 1;
|
|
3007
|
-
});
|
|
3008
|
-
// Init all to false
|
|
3009
|
-
const [expanded, setExpanded] = React.useState(
|
|
3010
|
-
Object.fromEntries(Object.entries(rowPairs).map(([k, v]) => [k, !Boolean(v.child)]))
|
|
3011
|
-
);
|
|
3012
|
-
const handleExpansionToggle = (event, pairIndex) => {
|
|
3013
|
-
setExpanded({
|
|
3014
|
-
...expanded,
|
|
3015
|
-
[pairIndex]: !expanded[pairIndex]
|
|
3016
|
-
});
|
|
3017
|
-
};
|
|
3018
|
-
let rowIndex = -1;
|
|
3019
|
-
return (
|
|
3020
|
-
<InnerScrollContainer>
|
|
3021
|
-
<TableComposable aria-label="Expandable Table" nestedHeaderColumnSpans={colSpans}>
|
|
3022
|
-
<Thead hasNestedHeader>
|
|
3023
|
-
<Tr>
|
|
3024
|
-
<Th rowSpan={2} />
|
|
3025
|
-
<Th rowSpan={2} hasRightBorder>
|
|
3026
|
-
{columns[0].name}
|
|
3027
|
-
</Th>
|
|
3028
|
-
<Th colSpan={3} hasRightBorder>
|
|
3029
|
-
{columns[1].name}
|
|
3030
|
-
</Th>
|
|
3031
|
-
<Th rowSpan={2}>{columns[2].name}</Th>
|
|
3032
|
-
</Tr>
|
|
3033
|
-
<Tr resetOffset>
|
|
3034
|
-
<Th isSubheader>{columns[1].subColumns[0].name}</Th>
|
|
3035
|
-
<Th isSubheader>{columns[1].subColumns[1].name}</Th>
|
|
3036
|
-
<Th isSubheader hasRightBorder>
|
|
3037
|
-
{columns[1].subColumns[2].name}
|
|
3038
|
-
</Th>
|
|
3039
|
-
</Tr>
|
|
3040
|
-
</Thead>
|
|
3041
|
-
{rowPairs.map((pair, pairIndex) => {
|
|
3042
|
-
rowIndex += 1;
|
|
3043
|
-
const parentRow = (
|
|
3044
|
-
<Tr key={rowIndex}>
|
|
3045
|
-
<Td
|
|
3046
|
-
key={`${rowIndex}_0`}
|
|
3047
|
-
expand={
|
|
3048
|
-
pair.child
|
|
3049
|
-
? {
|
|
3050
|
-
rowIndex: pairIndex,
|
|
3051
|
-
isExpanded: expanded[pairIndex],
|
|
3052
|
-
onToggle: handleExpansionToggle
|
|
3053
|
-
}
|
|
3054
|
-
: null
|
|
3055
|
-
}
|
|
3056
|
-
/>
|
|
3057
|
-
{pair.parent.map((cell, cellIndex) => (
|
|
3058
|
-
<Td key={`${rowIndex}_${cellIndex}`} dataLabel={columns[cellIndex]}>
|
|
3059
|
-
{cell}
|
|
3060
|
-
</Td>
|
|
3061
|
-
))}
|
|
3062
|
-
</Tr>
|
|
3063
|
-
);
|
|
3064
|
-
if (pair.child) {
|
|
3065
|
-
rowIndex += 1;
|
|
3066
|
-
}
|
|
3067
|
-
const childRow = pair.child ? (
|
|
3068
|
-
<Tr key={rowIndex} isExpanded={expanded[pairIndex] === true}>
|
|
3069
|
-
{!rowPairs[pairIndex].fullWidth && <Td key={`${rowIndex}_0`} />}
|
|
3070
|
-
{rowPairs[pairIndex].child.map((cell, cellIndex) => {
|
|
3071
|
-
const numChildCells = rowPairs[pairIndex].child.length;
|
|
3072
|
-
const shift = rowPairs[pairIndex].fullWidth ? 1 : 0;
|
|
3073
|
-
const shiftedCellIndex = cellIndex + shift;
|
|
3074
|
-
// some examples of how you could customize colSpan based on your needs
|
|
3075
|
-
const getColSpan = () => {
|
|
3076
|
-
// we have 6 columns (1 expandable column + 5 regular columns)
|
|
3077
|
-
// for the rowPairs where we've specificed `fullWidth`, add +1 to account for the expandable column
|
|
3078
|
-
let colSpan = 1;
|
|
3079
|
-
if (numChildCells === 1) {
|
|
3080
|
-
// single child cell: take up full width
|
|
3081
|
-
colSpan = numColumns + shift;
|
|
3082
|
-
} else if (numChildCells === 2) {
|
|
3083
|
-
// 2 children
|
|
3084
|
-
// child 1: 2 colspan
|
|
3085
|
-
// child 2: 3 or 4 colspan depending on fullWidth
|
|
3086
|
-
colSpan = cellIndex === 0 ? 2 : 3 + shift;
|
|
3087
|
-
} else if (numChildCells === 3) {
|
|
3088
|
-
// 3 children
|
|
3089
|
-
// child 1: 2 colspam
|
|
3090
|
-
// child 2: 2 colspan
|
|
3091
|
-
// child 3: 1 or 2 colspan depending on fullWidth
|
|
3092
|
-
colSpan = cellIndex === 2 ? 1 + shift : 2;
|
|
3093
|
-
}
|
|
3094
|
-
return colSpan;
|
|
3095
|
-
};
|
|
3096
|
-
return (
|
|
3097
|
-
<Td
|
|
3098
|
-
key={`${rowIndex}_${shiftedCellIndex}`}
|
|
3099
|
-
dataLabel={columns[cellIndex]}
|
|
3100
|
-
noPadding={rowPairs[pairIndex].noPadding}
|
|
3101
|
-
colSpan={getColSpan()}
|
|
3102
|
-
>
|
|
3103
|
-
<ExpandableRowContent>{cell.title || cell}</ExpandableRowContent>
|
|
3104
|
-
</Td>
|
|
3105
|
-
);
|
|
3106
|
-
})}
|
|
3107
|
-
</Tr>
|
|
3108
|
-
) : null;
|
|
3109
|
-
return (
|
|
3110
|
-
<Tbody key={pairIndex} isExpanded={expanded[pairIndex] === true}>
|
|
3111
|
-
{parentRow}
|
|
3112
|
-
{childRow}
|
|
3113
|
-
</Tbody>
|
|
3114
|
-
);
|
|
3115
|
-
})}
|
|
3116
|
-
</TableComposable>
|
|
3117
|
-
</InnerScrollContainer>
|
|
3118
|
-
);
|
|
3119
|
-
};
|
|
345
|
+
```ts file="ComposableTableNestedExpandable.tsx"
|
|
3120
346
|
```
|