@patternfly/react-table 4.41.0 → 4.43.2

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 (74) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/dist/esm/components/Table/base/provider.d.ts +5 -5
  3. package/dist/esm/components/Table/base/types.d.ts +5 -5
  4. package/dist/esm/components/TableComposable/InnerScrollContainer.d.ts +9 -0
  5. package/dist/esm/components/TableComposable/InnerScrollContainer.d.ts.map +1 -0
  6. package/dist/esm/components/TableComposable/InnerScrollContainer.js +10 -0
  7. package/dist/esm/components/TableComposable/InnerScrollContainer.js.map +1 -0
  8. package/dist/esm/components/TableComposable/OuterScrollContainer.d.ts +9 -0
  9. package/dist/esm/components/TableComposable/OuterScrollContainer.d.ts.map +1 -0
  10. package/dist/esm/components/TableComposable/OuterScrollContainer.js +10 -0
  11. package/dist/esm/components/TableComposable/OuterScrollContainer.js.map +1 -0
  12. package/dist/esm/components/TableComposable/TableComposable.d.ts +3 -1
  13. package/dist/esm/components/TableComposable/TableComposable.d.ts.map +1 -1
  14. package/dist/esm/components/TableComposable/TableComposable.js +12 -2
  15. package/dist/esm/components/TableComposable/TableComposable.js.map +1 -1
  16. package/dist/esm/components/TableComposable/Th.d.ts +11 -1
  17. package/dist/esm/components/TableComposable/Th.d.ts.map +1 -1
  18. package/dist/esm/components/TableComposable/Th.js +5 -2
  19. package/dist/esm/components/TableComposable/Th.js.map +1 -1
  20. package/dist/esm/components/TableComposable/Thead.d.ts +3 -1
  21. package/dist/esm/components/TableComposable/Thead.d.ts.map +1 -1
  22. package/dist/esm/components/TableComposable/Thead.js +2 -2
  23. package/dist/esm/components/TableComposable/Thead.js.map +1 -1
  24. package/dist/esm/components/TableComposable/Tr.d.ts +3 -1
  25. package/dist/esm/components/TableComposable/Tr.d.ts.map +1 -1
  26. package/dist/esm/components/TableComposable/Tr.js +2 -2
  27. package/dist/esm/components/TableComposable/Tr.js.map +1 -1
  28. package/dist/esm/components/TableComposable/index.d.ts +2 -0
  29. package/dist/esm/components/TableComposable/index.d.ts.map +1 -1
  30. package/dist/esm/components/TableComposable/index.js +2 -0
  31. package/dist/esm/components/TableComposable/index.js.map +1 -1
  32. package/dist/js/components/Table/base/provider.d.ts +5 -5
  33. package/dist/js/components/Table/base/types.d.ts +5 -5
  34. package/dist/js/components/TableComposable/InnerScrollContainer.d.ts +9 -0
  35. package/dist/js/components/TableComposable/InnerScrollContainer.d.ts.map +1 -0
  36. package/dist/js/components/TableComposable/InnerScrollContainer.js +14 -0
  37. package/dist/js/components/TableComposable/InnerScrollContainer.js.map +1 -0
  38. package/dist/js/components/TableComposable/OuterScrollContainer.d.ts +9 -0
  39. package/dist/js/components/TableComposable/OuterScrollContainer.d.ts.map +1 -0
  40. package/dist/js/components/TableComposable/OuterScrollContainer.js +14 -0
  41. package/dist/js/components/TableComposable/OuterScrollContainer.js.map +1 -0
  42. package/dist/js/components/TableComposable/TableComposable.d.ts +3 -1
  43. package/dist/js/components/TableComposable/TableComposable.d.ts.map +1 -1
  44. package/dist/js/components/TableComposable/TableComposable.js +12 -2
  45. package/dist/js/components/TableComposable/TableComposable.js.map +1 -1
  46. package/dist/js/components/TableComposable/Th.d.ts +11 -1
  47. package/dist/js/components/TableComposable/Th.d.ts.map +1 -1
  48. package/dist/js/components/TableComposable/Th.js +5 -2
  49. package/dist/js/components/TableComposable/Th.js.map +1 -1
  50. package/dist/js/components/TableComposable/Thead.d.ts +3 -1
  51. package/dist/js/components/TableComposable/Thead.d.ts.map +1 -1
  52. package/dist/js/components/TableComposable/Thead.js +2 -2
  53. package/dist/js/components/TableComposable/Thead.js.map +1 -1
  54. package/dist/js/components/TableComposable/Tr.d.ts +3 -1
  55. package/dist/js/components/TableComposable/Tr.d.ts.map +1 -1
  56. package/dist/js/components/TableComposable/Tr.js +2 -2
  57. package/dist/js/components/TableComposable/Tr.js.map +1 -1
  58. package/dist/js/components/TableComposable/index.d.ts +2 -0
  59. package/dist/js/components/TableComposable/index.d.ts.map +1 -1
  60. package/dist/js/components/TableComposable/index.js +2 -0
  61. package/dist/js/components/TableComposable/index.js.map +1 -1
  62. package/dist/umd/react-table.css +497 -90
  63. package/dist/umd/react-table.js +247 -208
  64. package/dist/umd/react-table.min.css +497 -90
  65. package/dist/umd/react-table.min.js +3 -3
  66. package/package.json +6 -6
  67. package/src/components/TableComposable/InnerScrollContainer.tsx +22 -0
  68. package/src/components/TableComposable/OuterScrollContainer.tsx +22 -0
  69. package/src/components/TableComposable/TableComposable.tsx +11 -0
  70. package/src/components/TableComposable/Th.tsx +26 -0
  71. package/src/components/TableComposable/Thead.tsx +12 -1
  72. package/src/components/TableComposable/Tr.tsx +5 -1
  73. package/src/components/TableComposable/examples/ComposableTable.md +1106 -0
  74. package/src/components/TableComposable/index.ts +2 -0
@@ -28,6 +28,8 @@ propComponents:
28
28
  'EditableTextCellProps',
29
29
  'ThSortType',
30
30
  'ISortBy',
31
+ 'InnerScrollContainer',
32
+ 'OuterScrollContainer',
31
33
  ]
32
34
  ouia: true
33
35
  ---
@@ -52,6 +54,7 @@ import LeafIcon from '@patternfly/react-icons/dist/esm/icons/leaf-icon';
52
54
  import FolderIcon from '@patternfly/react-icons/dist/esm/icons/folder-icon';
53
55
  import FolderOpenIcon from '@patternfly/react-icons/dist/esm/icons/folder-open-icon';
54
56
  import SortAmountDownIcon from '@patternfly/react-icons/dist/esm/icons/sort-amount-down-icon';
57
+ import BlueprintIcon from '@patternfly/react-icons/dist/esm/icons/blueprint-icon';
55
58
 
56
59
  import { css } from '@patternfly/react-styles';
57
60
  import styles from '@patternfly/react-styles/css/components/Table/table';
@@ -2012,3 +2015,1106 @@ class DraggableTable extends React.Component {
2012
2015
  }
2013
2016
  }
2014
2017
  ```
2018
+
2019
+ ### Composable: Sticky column
2020
+
2021
+ 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
+
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
+ };
2236
+ ```
2237
+
2238
+ ### Composable: Multiple sticky columns
2239
+
2240
+ 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
+
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
+ };
2477
+ ```
2478
+
2479
+ ### Composable: Sticky columns and header
2480
+
2481
+ 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
+
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
+ };
2723
+ ```
2724
+
2725
+ ### Composable: Nested column headers
2726
+
2727
+ To make a nested column header:
2728
+
2729
+ 1. Wrap `TableComposable` with `InnerScrollContainer`.
2730
+ 2. Pass `nestedHeaderColumnSpans` to `TableComposable`. `nestedHeaderColumnSpans` is an array of numbers representing the column spans of the top level columns to `TableComposable`, where each number is equal to the number of sub columns for a column, or `1` if a column contains no sub columns.
2731
+ 3. Pass `hasNestedHeader` to `Thead`.
2732
+ 4. Pass two `Tr` as children of `Thead`.
2733
+
2734
+ The first `Tr` represents the top level of columns, and each must pass either `rowSpan` if the column does not contain sub columns or `colSpan` if the column contains sub columns. The value of `rowSpan` is equal to the number of rows the nested header will span, typically `2`, and the value of `colSpan` is equal to the number of sub columns in a column. Each `Th` except the last should also pass `hasRightBorder`.
2735
+
2736
+ 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
+
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
+ };
2923
+ ```
2924
+
2925
+ ### Composable: Nested column headers and expandable rows
2926
+
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
+ };
3120
+ ```