@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.
Files changed (67) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/dist/esm/components/Table/Table.d.ts +2 -1
  3. package/dist/esm/components/Table/Table.d.ts.map +1 -1
  4. package/dist/esm/components/Table/Table.js.map +1 -1
  5. package/dist/esm/components/Table/TableTypes.d.ts +2 -1
  6. package/dist/esm/components/Table/TableTypes.d.ts.map +1 -1
  7. package/dist/esm/components/Table/TableTypes.js.map +1 -1
  8. package/dist/esm/components/Table/base/provider.d.ts +2 -2
  9. package/dist/esm/components/Table/base/types.d.ts +2 -2
  10. package/dist/esm/components/TableComposable/TableComposable.d.ts +1 -1
  11. package/dist/esm/components/TableComposable/Td.d.ts +1 -1
  12. package/dist/js/components/Table/Table.d.ts +2 -1
  13. package/dist/js/components/Table/Table.d.ts.map +1 -1
  14. package/dist/js/components/Table/Table.js.map +1 -1
  15. package/dist/js/components/Table/TableTypes.d.ts +2 -1
  16. package/dist/js/components/Table/TableTypes.d.ts.map +1 -1
  17. package/dist/js/components/Table/TableTypes.js.map +1 -1
  18. package/dist/js/components/Table/base/provider.d.ts +2 -2
  19. package/dist/js/components/Table/base/types.d.ts +2 -2
  20. package/dist/js/components/TableComposable/TableComposable.d.ts +1 -1
  21. package/dist/js/components/TableComposable/Td.d.ts +1 -1
  22. package/package.json +6 -6
  23. package/src/components/Table/Table.tsx +2 -1
  24. package/src/components/Table/TableTypes.tsx +2 -1
  25. package/src/components/Table/examples/LegacyTableActions.tsx +218 -0
  26. package/src/components/Table/examples/LegacyTableBasic.tsx +90 -0
  27. package/src/components/Table/examples/LegacyTableCellWidth.tsx +66 -0
  28. package/src/components/Table/examples/LegacyTableCompoundExpandable.tsx +175 -0
  29. package/src/components/Table/examples/LegacyTableControllingText.tsx +51 -0
  30. package/src/components/Table/examples/LegacyTableEmptyState.tsx +48 -0
  31. package/src/components/Table/examples/LegacyTableExpandable.tsx +172 -0
  32. package/src/components/Table/examples/LegacyTableFavoritable.tsx +157 -0
  33. package/src/components/Table/examples/LegacyTableHoverable.tsx +125 -0
  34. package/src/components/Table/examples/LegacyTableMisc.tsx +56 -0
  35. package/src/components/Table/examples/LegacyTableSelectable.tsx +124 -0
  36. package/src/components/Table/examples/LegacyTableSelectableRadio.tsx +54 -0
  37. package/src/components/Table/examples/LegacyTableSortable.tsx +120 -0
  38. package/src/components/Table/examples/LegacyTableSortableCustom.tsx +189 -0
  39. package/src/components/Table/examples/LegacyTableTextModifiers.tsx +31 -0
  40. package/src/components/Table/examples/LegacyTableTree.tsx +213 -0
  41. package/src/components/Table/examples/Table.md +83 -1848
  42. package/src/components/TableComposable/examples/ComposableTable.md +31 -2805
  43. package/src/components/TableComposable/examples/ComposableTableActions.tsx +163 -0
  44. package/src/components/TableComposable/examples/ComposableTableBasic.tsx +89 -0
  45. package/src/components/TableComposable/examples/ComposableTableCellWidth.tsx +56 -0
  46. package/src/components/TableComposable/examples/ComposableTableCompoundExpandable.tsx +103 -0
  47. package/src/components/TableComposable/examples/ComposableTableControllingText.tsx +51 -0
  48. package/src/components/TableComposable/examples/ComposableTableDraggable.tsx +170 -0
  49. package/src/components/TableComposable/examples/ComposableTableEmptyState.tsx +45 -0
  50. package/src/components/TableComposable/examples/ComposableTableExpandable.tsx +195 -0
  51. package/src/components/TableComposable/examples/ComposableTableFavoritable.tsx +118 -0
  52. package/src/components/TableComposable/examples/ComposableTableHoverable.tsx +67 -0
  53. package/src/components/TableComposable/examples/ComposableTableMisc.tsx +92 -0
  54. package/src/components/TableComposable/examples/ComposableTableMultipleStickyColumns.tsx +153 -0
  55. package/src/components/TableComposable/examples/ComposableTableNestedExpandable.tsx +127 -0
  56. package/src/components/TableComposable/examples/ComposableTableNestedHeaders.tsx +202 -0
  57. package/src/components/TableComposable/examples/ComposableTableSelectable.tsx +125 -0
  58. package/src/components/TableComposable/examples/ComposableTableSelectableRadio.tsx +68 -0
  59. package/src/components/TableComposable/examples/ComposableTableSortable.tsx +107 -0
  60. package/src/components/TableComposable/examples/ComposableTableSortableCustom.tsx +175 -0
  61. package/src/components/TableComposable/examples/ComposableTableStickyColumn.tsx +144 -0
  62. package/src/components/TableComposable/examples/ComposableTableStickyColumnsAndHeader.tsx +167 -0
  63. package/src/components/TableComposable/examples/ComposableTableTextModifiers.tsx +33 -0
  64. package/src/components/TableComposable/examples/ComposableTableTree.tsx +201 -0
  65. package/src/demos/Table.md +7 -6
  66. package/src/components/Table/examples/DemoSortableTable.js +0 -65
  67. 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 html table. Generally, updates and new feature requests are implemented in the `ComposableTable`.
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
- ```js
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
- ```js
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
- ```js
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
- ```js
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
- ```js
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
- ```js
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
- ```js
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, pass a `TdActionsType` object via the `actions` prop on a rows's last `Td`.
789
-
790
- ```js
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
- ```js
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
- ```js
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
- ```js
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
- ```js
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
- ```js
1403
- import React from 'react';
1404
- import { TableComposable, Thead, Tbody, Tr, Th, Td, TableText } from '@patternfly/react-table';
1405
-
1406
- ComposableTableText = () => {
1407
- const columns = ['Truncating text', 'Wrapping table header text. This th text will wrap instead of truncate.'];
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
- ```js
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
- ```js isBeta
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
- ```js isBeta
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
- ```js
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
- ```js
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
- ```js
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
- ```js
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
- ```js
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
  ```