@patternfly/react-table 4.44.12 → 4.44.16

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 (70) hide show
  1. package/CHANGELOG.md +35 -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/dist/umd/react-table.css +250 -250
  23. package/dist/umd/react-table.js +9 -1
  24. package/dist/umd/react-table.min.js +3 -3
  25. package/package.json +6 -6
  26. package/src/components/Table/Table.tsx +2 -1
  27. package/src/components/Table/TableTypes.tsx +2 -1
  28. package/src/components/Table/examples/LegacyTableActions.tsx +218 -0
  29. package/src/components/Table/examples/LegacyTableBasic.tsx +90 -0
  30. package/src/components/Table/examples/LegacyTableCellWidth.tsx +66 -0
  31. package/src/components/Table/examples/LegacyTableCompoundExpandable.tsx +175 -0
  32. package/src/components/Table/examples/LegacyTableControllingText.tsx +51 -0
  33. package/src/components/Table/examples/LegacyTableEmptyState.tsx +48 -0
  34. package/src/components/Table/examples/LegacyTableExpandable.tsx +172 -0
  35. package/src/components/Table/examples/LegacyTableFavoritable.tsx +157 -0
  36. package/src/components/Table/examples/LegacyTableHoverable.tsx +125 -0
  37. package/src/components/Table/examples/LegacyTableMisc.tsx +56 -0
  38. package/src/components/Table/examples/LegacyTableSelectable.tsx +124 -0
  39. package/src/components/Table/examples/LegacyTableSelectableRadio.tsx +54 -0
  40. package/src/components/Table/examples/LegacyTableSortable.tsx +120 -0
  41. package/src/components/Table/examples/LegacyTableSortableCustom.tsx +189 -0
  42. package/src/components/Table/examples/LegacyTableTextModifiers.tsx +31 -0
  43. package/src/components/Table/examples/LegacyTableTree.tsx +213 -0
  44. package/src/components/Table/examples/Table.md +83 -1848
  45. package/src/components/TableComposable/examples/ComposableTable.md +31 -2805
  46. package/src/components/TableComposable/examples/ComposableTableActions.tsx +163 -0
  47. package/src/components/TableComposable/examples/ComposableTableBasic.tsx +89 -0
  48. package/src/components/TableComposable/examples/ComposableTableCellWidth.tsx +56 -0
  49. package/src/components/TableComposable/examples/ComposableTableCompoundExpandable.tsx +103 -0
  50. package/src/components/TableComposable/examples/ComposableTableControllingText.tsx +51 -0
  51. package/src/components/TableComposable/examples/ComposableTableDraggable.tsx +170 -0
  52. package/src/components/TableComposable/examples/ComposableTableEmptyState.tsx +45 -0
  53. package/src/components/TableComposable/examples/ComposableTableExpandable.tsx +195 -0
  54. package/src/components/TableComposable/examples/ComposableTableFavoritable.tsx +118 -0
  55. package/src/components/TableComposable/examples/ComposableTableHoverable.tsx +67 -0
  56. package/src/components/TableComposable/examples/ComposableTableMisc.tsx +92 -0
  57. package/src/components/TableComposable/examples/ComposableTableMultipleStickyColumns.tsx +153 -0
  58. package/src/components/TableComposable/examples/ComposableTableNestedExpandable.tsx +127 -0
  59. package/src/components/TableComposable/examples/ComposableTableNestedHeaders.tsx +202 -0
  60. package/src/components/TableComposable/examples/ComposableTableSelectable.tsx +125 -0
  61. package/src/components/TableComposable/examples/ComposableTableSelectableRadio.tsx +68 -0
  62. package/src/components/TableComposable/examples/ComposableTableSortable.tsx +107 -0
  63. package/src/components/TableComposable/examples/ComposableTableSortableCustom.tsx +175 -0
  64. package/src/components/TableComposable/examples/ComposableTableStickyColumn.tsx +144 -0
  65. package/src/components/TableComposable/examples/ComposableTableStickyColumnsAndHeader.tsx +167 -0
  66. package/src/components/TableComposable/examples/ComposableTableTextModifiers.tsx +33 -0
  67. package/src/components/TableComposable/examples/ComposableTableTree.tsx +201 -0
  68. package/src/demos/Table.md +7 -6
  69. package/src/components/Table/examples/DemoSortableTable.js +0 -65
  70. package/src/components/TableComposable/examples/DemoSortableTable.js +0 -65
@@ -0,0 +1,127 @@
1
+ import React from 'react';
2
+ import {
3
+ TableComposable,
4
+ Thead,
5
+ Tr,
6
+ Th,
7
+ Tbody,
8
+ Td,
9
+ InnerScrollContainer,
10
+ ExpandableRowContent
11
+ } from '@patternfly/react-table';
12
+ import { Button } from '@patternfly/react-core';
13
+
14
+ interface Team {
15
+ name: string;
16
+ members: {
17
+ lead: string;
18
+ interaction: string;
19
+ visual: string;
20
+ };
21
+ email: string;
22
+ description: string;
23
+ }
24
+
25
+ export const ComposableTableNestedExpandable: React.FunctionComponent = () => {
26
+ // In real usage, this data would come from some external source like an API via props.
27
+ const teams: Team[] = [
28
+ {
29
+ name: 'Developer program 1',
30
+ members: { lead: 'Person 1', interaction: 'Person 2', visual: 'Person 3' },
31
+ email: 'devteam1@example.com',
32
+ description:
33
+ '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.'
34
+ },
35
+ {
36
+ name: 'Developer program 2',
37
+ members: { lead: 'Person A', interaction: 'Person B', visual: 'Person C' },
38
+ email: 'devteam2@example.com',
39
+ description:
40
+ '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.'
41
+ },
42
+ {
43
+ name: 'Developer program 3',
44
+ members: { lead: 'Person X', interaction: 'Person Y', visual: 'Person Z' },
45
+ email: 'devteam3@example.com',
46
+ description:
47
+ '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.'
48
+ }
49
+ ];
50
+
51
+ const columnNames = {
52
+ team: 'Team',
53
+ members: 'Members',
54
+ lead: 'Design Lead',
55
+ interaction: 'Interaction Designer',
56
+ visual: 'Visual Designer',
57
+ contact: 'Contact'
58
+ };
59
+
60
+ // In this example, expanded rows are tracked by the team names from each row. This could be any unique identifier.
61
+ // This is to prevent state from being based on row order index in case we later add sorting.
62
+ // Note that this behavior is very similar to selection state.
63
+ const [expandedTeamNames, setExpandedTeamNames] = React.useState<string[]>([]);
64
+ const setTeamExpanded = (team: Team, isExpanding = true) =>
65
+ setExpandedTeamNames(prevExpanded => {
66
+ const otherExpandedTeamNames = prevExpanded.filter(t => t !== team.name);
67
+ return isExpanding ? [...otherExpandedTeamNames, team.name] : otherExpandedTeamNames;
68
+ });
69
+ const isTeamExpanded = (team: Team) => expandedTeamNames.includes(team.name);
70
+
71
+ return (
72
+ <InnerScrollContainer>
73
+ <TableComposable
74
+ aria-label="Nested column headers with expandable rows table"
75
+ gridBreakPoint=""
76
+ nestedHeaderColumnSpans={[1, 3, 1]}
77
+ >
78
+ <Thead hasNestedHeader>
79
+ <Tr>
80
+ <Th rowSpan={2} />
81
+ <Th rowSpan={2} hasRightBorder>
82
+ {columnNames.team}
83
+ </Th>
84
+ <Th colSpan={3} hasRightBorder>
85
+ {columnNames.members}
86
+ </Th>
87
+ <Th rowSpan={2}>{columnNames.contact}</Th>
88
+ </Tr>
89
+ <Tr resetOffset>
90
+ <Th isSubheader>{columnNames.lead}</Th>
91
+ <Th isSubheader>{columnNames.interaction}</Th>
92
+ <Th isSubheader hasRightBorder>
93
+ {columnNames.visual}
94
+ </Th>
95
+ </Tr>
96
+ </Thead>
97
+ {teams.map((team, rowIndex) => (
98
+ <Tbody key={team.name} isExpanded={isTeamExpanded(team)}>
99
+ <Tr>
100
+ <Td
101
+ expand={{
102
+ rowIndex,
103
+ isExpanded: isTeamExpanded(team),
104
+ onToggle: () => setTeamExpanded(team, !isTeamExpanded(team))
105
+ }}
106
+ />
107
+ <Td dataLabel={columnNames.team}>{team.name}</Td>
108
+ <Td dataLabel={columnNames.lead}>{team.members.lead}</Td>
109
+ <Td dataLabel={columnNames.interaction}>{team.members.interaction}</Td>
110
+ <Td dataLabel={columnNames.visual}>{team.members.visual}</Td>
111
+ <Td dataLabel={columnNames.contact}>
112
+ <Button variant="link" component="a" href={`mailto:${team.email}`} isInline>
113
+ Message us!
114
+ </Button>
115
+ </Td>
116
+ </Tr>
117
+ <Tr isExpanded={isTeamExpanded(team)}>
118
+ <Td dataLabel={`Team ${team.name} description`} colSpan={6}>
119
+ <ExpandableRowContent>{team.description}</ExpandableRowContent>
120
+ </Td>
121
+ </Tr>
122
+ </Tbody>
123
+ ))}
124
+ </TableComposable>
125
+ </InnerScrollContainer>
126
+ );
127
+ };
@@ -0,0 +1,202 @@
1
+ import React from 'react';
2
+ import { TableComposable, Thead, Tr, Th, Tbody, Td, InnerScrollContainer, ThProps } from '@patternfly/react-table';
3
+ import { Stack, StackItem } from '@patternfly/react-core';
4
+
5
+ interface PodConnection {
6
+ source: {
7
+ podName: string;
8
+ port: { num: number; protocol: string };
9
+ };
10
+ destination: {
11
+ podName: string;
12
+ port: { num: number; protocol: string };
13
+ };
14
+ timestamp: string;
15
+ protocol: string;
16
+ flowRate: string;
17
+ traffic: string;
18
+ packets: number;
19
+ }
20
+
21
+ export const ComposableTableNestedHeaders: React.FunctionComponent = () => {
22
+ // In real usage, this data would come from some external source like an API via props.
23
+ const connections: PodConnection[] = [
24
+ {
25
+ source: { podName: 'api-pod-source-name', port: { num: 443, protocol: 'HTTPS' } },
26
+ destination: { podName: 'api-pod-destination-name', port: { num: 24, protocol: 'SMTP' } },
27
+ timestamp: '2021-06-22T19:58:24.000Z',
28
+ protocol: 'TCP',
29
+ flowRate: '1.9 Kbps',
30
+ traffic: '2.1 KB',
31
+ packets: 3
32
+ },
33
+ {
34
+ source: { podName: 'api-pod-source2-name', port: { num: 80, protocol: 'HTTP' } },
35
+ destination: { podName: 'api-pod-destination2-name', port: { num: 24, protocol: 'SMTP' } },
36
+ timestamp: '2021-06-22T21:42:01.000Z',
37
+ protocol: 'UDP',
38
+ flowRate: '3.4 Kbps',
39
+ traffic: '6.1 KB',
40
+ packets: 7
41
+ }
42
+ ];
43
+
44
+ const columnNames = {
45
+ pods: 'Pods',
46
+ source: 'Source',
47
+ destination: 'Destination',
48
+ datetime: 'Date & Time',
49
+ ports: 'Ports',
50
+ protocol: 'Protocol',
51
+ flowRate: 'Flow rate',
52
+ traffic: 'Traffic',
53
+ packets: 'Packets'
54
+ };
55
+
56
+ // Index of the currently sorted column
57
+ // Note: if you intend to make columns reorderable, you may instead want to use a non-numeric key
58
+ // as the identifier of the sorted column. See the "Compound expandable" example.
59
+ const [activeSortIndex, setActiveSortIndex] = React.useState<number | null>(null);
60
+
61
+ // Sort direction of the currently sorted column
62
+ const [activeSortDirection, setActiveSortDirection] = React.useState<'asc' | 'desc' | null>(null);
63
+
64
+ // Since OnSort specifies sorted columns by index, we need sortable values for our object by column index.
65
+ const getSortableRowValues = (connection: PodConnection): (string | number)[] => {
66
+ const { source, destination, timestamp, protocol, flowRate, traffic, packets } = connection;
67
+ return [
68
+ source.podName,
69
+ destination.podName,
70
+ timestamp,
71
+ source.port.num,
72
+ destination.port.num,
73
+ protocol,
74
+ flowRate,
75
+ traffic,
76
+ packets
77
+ ];
78
+ };
79
+
80
+ // Note that we perform the sort as part of the component's render logic and not in onSort.
81
+ // We shouldn't store the list of data in state because we don't want to have to sync that with props.
82
+ let sortedConnections = connections;
83
+ if (activeSortIndex !== null) {
84
+ sortedConnections = connections.sort((a, b) => {
85
+ const aValue = getSortableRowValues(a)[activeSortIndex];
86
+ const bValue = getSortableRowValues(b)[activeSortIndex];
87
+ if (typeof aValue === 'number') {
88
+ // Numeric sort
89
+ if (activeSortDirection === 'asc') {
90
+ return (aValue as number) - (bValue as number);
91
+ }
92
+ return (bValue as number) - (aValue as number);
93
+ } else {
94
+ // String sort
95
+ if (activeSortDirection === 'asc') {
96
+ return (aValue as string).localeCompare(bValue as string);
97
+ }
98
+ return (bValue as string).localeCompare(aValue as string);
99
+ }
100
+ });
101
+ }
102
+
103
+ const getSortParams = (columnIndex: number): ThProps['sort'] => ({
104
+ sortBy: {
105
+ index: activeSortIndex,
106
+ direction: activeSortDirection
107
+ },
108
+ onSort: (_event, index, direction) => {
109
+ setActiveSortIndex(index);
110
+ setActiveSortDirection(direction);
111
+ },
112
+ columnIndex
113
+ });
114
+
115
+ return (
116
+ <InnerScrollContainer>
117
+ <TableComposable
118
+ aria-label="Nested column headers table"
119
+ gridBreakPoint=""
120
+ nestedHeaderColumnSpans={[3, 2, 1, 1, 1, 1]}
121
+ >
122
+ <Thead hasNestedHeader>
123
+ <Tr>
124
+ <Th hasRightBorder colSpan={3}>
125
+ {columnNames.pods}
126
+ </Th>
127
+ <Th hasRightBorder colSpan={2}>
128
+ {columnNames.ports}
129
+ </Th>
130
+ <Th modifier="fitContent" hasRightBorder rowSpan={2} sort={getSortParams(5)}>
131
+ {columnNames.protocol}
132
+ </Th>
133
+ <Th modifier="fitContent" hasRightBorder rowSpan={2} sort={getSortParams(6)}>
134
+ {columnNames.flowRate}
135
+ </Th>
136
+ <Th modifier="fitContent" hasRightBorder rowSpan={2} sort={getSortParams(7)}>
137
+ {columnNames.traffic}
138
+ </Th>
139
+ <Th modifier="fitContent" rowSpan={2} sort={getSortParams(8)}>
140
+ {columnNames.packets}
141
+ </Th>
142
+ </Tr>
143
+ <Tr>
144
+ <Th isSubheader sort={getSortParams(0)}>
145
+ {columnNames.source}
146
+ </Th>
147
+ <Th isSubheader sort={getSortParams(1)}>
148
+ {columnNames.destination}
149
+ </Th>
150
+ <Th isSubheader modifier="fitContent" hasRightBorder sort={getSortParams(2)}>
151
+ {columnNames.datetime}
152
+ </Th>
153
+ <Th isSubheader modifier="fitContent" sort={getSortParams(3)}>
154
+ {columnNames.source}
155
+ </Th>
156
+ <Th isSubheader modifier="fitContent" hasRightBorder sort={getSortParams(4)}>
157
+ {columnNames.destination}
158
+ </Th>
159
+ </Tr>
160
+ </Thead>
161
+ <Tbody>
162
+ {sortedConnections.map(connection => (
163
+ <Tr key={connection.source.podName}>
164
+ <Td dataLabel={columnNames.source}>{connection.source.podName}</Td>
165
+ <Td dataLabel={columnNames.destination}>{connection.destination.podName}</Td>\
166
+ <Td dataLabel={columnNames.datetime}>
167
+ <div>
168
+ <span>{new Date(connection.timestamp).toDateString()}</span>{' '}
169
+ <span className="pf-u-color-200">{new Date(connection.timestamp).toLocaleTimeString()}</span>
170
+ </div>
171
+ </Td>
172
+ <Td dataLabel={columnNames.source}>
173
+ <Stack>
174
+ <StackItem>
175
+ <span>{connection.source.port.num}</span>
176
+ </StackItem>
177
+ <StackItem>
178
+ <span className="pf-u-color-200">({connection.source.port.protocol})</span>
179
+ </StackItem>
180
+ </Stack>
181
+ </Td>
182
+ <Td dataLabel={columnNames.destination}>
183
+ <Stack>
184
+ <StackItem>
185
+ <span>{connection.destination.port.num}</span>
186
+ </StackItem>
187
+ <StackItem>
188
+ <span className="pf-u-color-200">({connection.destination.port.protocol})</span>
189
+ </StackItem>
190
+ </Stack>
191
+ </Td>
192
+ <Td dataLabel={columnNames.protocol}>{connection.protocol}</Td>
193
+ <Td dataLabel={columnNames.flowRate}>{connection.flowRate}</Td>
194
+ <Td dataLabel={columnNames.traffic}>{connection.traffic}</Td>
195
+ <Td dataLabel={columnNames.packets}>{connection.packets}</Td>
196
+ </Tr>
197
+ ))}
198
+ </Tbody>
199
+ </TableComposable>
200
+ </InnerScrollContainer>
201
+ );
202
+ };
@@ -0,0 +1,125 @@
1
+ import React from 'react';
2
+ import { TableComposable, Thead, Tr, Th, Tbody, Td } from '@patternfly/react-table';
3
+
4
+ interface Repository {
5
+ name: string;
6
+ branches: string;
7
+ prs: string;
8
+ workspaces: string;
9
+ lastCommit: string;
10
+ }
11
+
12
+ export const ComposableTableSelectable: React.FunctionComponent = () => {
13
+ // In real usage, this data would come from some external source like an API via props.
14
+ const repositories: Repository[] = [
15
+ { name: 'one', branches: 'two', prs: 'a', workspaces: 'four', lastCommit: 'five' },
16
+ { name: 'a', branches: 'two', prs: 'k', workspaces: 'four', lastCommit: 'five' },
17
+ { name: 'b', branches: 'two', prs: 'k', workspaces: 'four', lastCommit: 'five' },
18
+ { name: 'c', branches: 'two', prs: 'k', workspaces: 'four', lastCommit: 'five' },
19
+ { name: 'd', branches: 'two', prs: 'k', workspaces: 'four', lastCommit: 'five' },
20
+ { name: 'e', branches: 'two', prs: 'b', workspaces: 'four', lastCommit: 'five' }
21
+ ];
22
+
23
+ const columnNames = {
24
+ name: 'Repositories',
25
+ branches: 'Branches',
26
+ prs: 'Pull requests',
27
+ workspaces: 'Workspaces',
28
+ lastCommit: 'Last commit'
29
+ };
30
+
31
+ const isRepoSelectable = (repo: Repository) => repo.name !== 'a'; // Arbitrary logic for this example
32
+ const selectableRepos = repositories.filter(isRepoSelectable);
33
+
34
+ // In this example, selected rows are tracked by the repo names from each row. This could be any unique identifier.
35
+ // This is to prevent state from being based on row order index in case we later add sorting.
36
+ const [selectedRepoNames, setSelectedRepoNames] = React.useState<string[]>([]);
37
+ const setRepoSelected = (repo: Repository, isSelecting = true) =>
38
+ setSelectedRepoNames(prevSelected => {
39
+ const otherSelectedRepoNames = prevSelected.filter(r => r !== repo.name);
40
+ return isSelecting && isRepoSelectable(repo) ? [...otherSelectedRepoNames, repo.name] : otherSelectedRepoNames;
41
+ });
42
+ const selectAllRepos = (isSelecting = true) =>
43
+ setSelectedRepoNames(isSelecting ? selectableRepos.map(r => r.name) : []);
44
+ const areAllReposSelected = selectedRepoNames.length === selectableRepos.length;
45
+ const isRepoSelected = (repo: Repository) => selectedRepoNames.includes(repo.name);
46
+
47
+ // To allow shift+click to select/deselect multiple rows
48
+ const [recentSelectedRowIndex, setRecentSelectedRowIndex] = React.useState<number | null>(null);
49
+ const [shifting, setShifting] = React.useState(false);
50
+
51
+ const onSelectRepo = (repo: Repository, rowIndex: number, isSelecting: boolean) => {
52
+ // If the user is shift + selecting the checkboxes, then all intermediate checkboxes should be selected
53
+ if (shifting && recentSelectedRowIndex !== null) {
54
+ const numberSelected = rowIndex - recentSelectedRowIndex;
55
+ const intermediateIndexes =
56
+ numberSelected > 0
57
+ ? Array.from(new Array(numberSelected + 1), (_x, i) => i + recentSelectedRowIndex)
58
+ : Array.from(new Array(Math.abs(numberSelected) + 1), (_x, i) => i + rowIndex);
59
+ intermediateIndexes.forEach(index => setRepoSelected(repositories[index], isSelecting));
60
+ } else {
61
+ setRepoSelected(repo, isSelecting);
62
+ }
63
+ setRecentSelectedRowIndex(rowIndex);
64
+ };
65
+
66
+ React.useEffect(() => {
67
+ const onKeyDown = (e: KeyboardEvent) => {
68
+ if (e.key === 'Shift') {
69
+ setShifting(true);
70
+ }
71
+ };
72
+ const onKeyUp = (e: KeyboardEvent) => {
73
+ if (e.key === 'Shift') {
74
+ setShifting(false);
75
+ }
76
+ };
77
+
78
+ document.addEventListener('keydown', onKeyDown);
79
+ document.addEventListener('keyup', onKeyUp);
80
+
81
+ return () => {
82
+ document.removeEventListener('keydown', onKeyDown);
83
+ document.removeEventListener('keyup', onKeyUp);
84
+ };
85
+ }, []);
86
+
87
+ return (
88
+ <TableComposable aria-label="Selectable table">
89
+ <Thead>
90
+ <Tr>
91
+ <Th
92
+ select={{
93
+ onSelect: (_event, isSelecting) => selectAllRepos(isSelecting),
94
+ isSelected: areAllReposSelected
95
+ }}
96
+ />
97
+ <Th>{columnNames.name}</Th>
98
+ <Th>{columnNames.branches}</Th>
99
+ <Th>{columnNames.prs}</Th>
100
+ <Th>{columnNames.workspaces}</Th>
101
+ <Th>{columnNames.lastCommit}</Th>
102
+ </Tr>
103
+ </Thead>
104
+ <Tbody>
105
+ {repositories.map((repo, rowIndex) => (
106
+ <Tr key={repo.name}>
107
+ <Td
108
+ select={{
109
+ rowIndex,
110
+ onSelect: (_event, isSelecting) => onSelectRepo(repo, rowIndex, isSelecting),
111
+ isSelected: isRepoSelected(repo),
112
+ disable: !isRepoSelectable(repo)
113
+ }}
114
+ />
115
+ <Td dataLabel={columnNames.name}>{repo.name}</Td>
116
+ <Td dataLabel={columnNames.branches}>{repo.branches}</Td>
117
+ <Td dataLabel={columnNames.prs}>{repo.prs}</Td>
118
+ <Td dataLabel={columnNames.workspaces}>{repo.workspaces}</Td>
119
+ <Td dataLabel={columnNames.lastCommit}>{repo.lastCommit}</Td>
120
+ </Tr>
121
+ ))}
122
+ </Tbody>
123
+ </TableComposable>
124
+ );
125
+ };
@@ -0,0 +1,68 @@
1
+ import React from 'react';
2
+ import { TableComposable, Thead, Tr, Th, Tbody, Td } from '@patternfly/react-table';
3
+
4
+ interface Repository {
5
+ name: string;
6
+ branches: string;
7
+ prs: string;
8
+ workspaces: string;
9
+ lastCommit: string;
10
+ }
11
+
12
+ export const ComposableTableSelectableRadio: React.FunctionComponent = () => {
13
+ // In real usage, this data would come from some external source like an API via props.
14
+ const repositories: Repository[] = [
15
+ { name: 'one', branches: 'two', prs: 'a', workspaces: 'four', lastCommit: 'five' },
16
+ { name: 'a', branches: 'two', prs: 'k', workspaces: 'four', lastCommit: 'five' },
17
+ { name: 'p', branches: 'two', prs: 'b', workspaces: 'four', lastCommit: 'five' }
18
+ ];
19
+
20
+ const columnNames = {
21
+ name: 'Repositories',
22
+ branches: 'Branches',
23
+ prs: 'Pull requests',
24
+ workspaces: 'Workspaces',
25
+ lastCommit: 'Last commit'
26
+ };
27
+
28
+ const isRepoSelectable = (repo: Repository) => repo.name !== 'a'; // Arbitrary logic for this example
29
+
30
+ // In this example, selected rows are tracked by the repo names from each row. This could be any unique identifier.
31
+ // This is to prevent state from being based on row order index in case we later add sorting.
32
+ const [selectedRepoName, setSelectedRepoName] = React.useState<string | null>(null);
33
+
34
+ return (
35
+ <TableComposable aria-label="Selectable table">
36
+ <Thead>
37
+ <Tr>
38
+ <Th />
39
+ <Th>{columnNames.name}</Th>
40
+ <Th>{columnNames.branches}</Th>
41
+ <Th>{columnNames.prs}</Th>
42
+ <Th>{columnNames.workspaces}</Th>
43
+ <Th>{columnNames.lastCommit}</Th>
44
+ </Tr>
45
+ </Thead>
46
+ <Tbody>
47
+ {repositories.map((repo, rowIndex) => (
48
+ <Tr key={repo.name}>
49
+ <Td
50
+ select={{
51
+ rowIndex,
52
+ onSelect: () => setSelectedRepoName(repo.name),
53
+ isSelected: selectedRepoName === repo.name,
54
+ disable: !isRepoSelectable(repo),
55
+ variant: 'radio'
56
+ }}
57
+ />
58
+ <Td dataLabel={columnNames.name}>{repo.name}</Td>
59
+ <Td dataLabel={columnNames.branches}>{repo.branches}</Td>
60
+ <Td dataLabel={columnNames.prs}>{repo.prs}</Td>
61
+ <Td dataLabel={columnNames.workspaces}>{repo.workspaces}</Td>
62
+ <Td dataLabel={columnNames.lastCommit}>{repo.lastCommit}</Td>
63
+ </Tr>
64
+ ))}
65
+ </Tbody>
66
+ </TableComposable>
67
+ );
68
+ };
@@ -0,0 +1,107 @@
1
+ import React from 'react';
2
+ import { TableComposable, Thead, Tr, Th, Tbody, Td, ThProps } from '@patternfly/react-table';
3
+
4
+ interface Repository {
5
+ name: string;
6
+ branches: string;
7
+ prs: string;
8
+ workspaces: string;
9
+ lastCommit: string;
10
+ }
11
+
12
+ export const ComposableTableSortable: React.FunctionComponent = () => {
13
+ // In real usage, this data would come from some external source like an API via props.
14
+ const repositories: Repository[] = [
15
+ { name: 'one', branches: 'two', prs: 'a', workspaces: 'four', lastCommit: 'five' },
16
+ { name: 'a', branches: 'two', prs: 'k', workspaces: 'four', lastCommit: 'five' },
17
+ { name: 'p', branches: 'two', prs: 'b', workspaces: 'four', lastCommit: 'five' }
18
+ ];
19
+
20
+ const columnNames = {
21
+ name: 'Repositories table header that goes on for a long time.',
22
+ branches: 'Branches table header that goes on for a long time.',
23
+ prs: 'Pull requests table header that goes on for a long time.',
24
+ workspaces: 'Workspaces table header that goes on for a long time.',
25
+ lastCommit: 'Last commit table header that goes on for a long time.'
26
+ };
27
+
28
+ // Index of the currently sorted column
29
+ // Note: if you intend to make columns reorderable, you may instead want to use a non-numeric key
30
+ // as the identifier of the sorted column. See the "Compound expandable" example.
31
+ const [activeSortIndex, setActiveSortIndex] = React.useState<number | null>(null);
32
+
33
+ // Sort direction of the currently sorted column
34
+ const [activeSortDirection, setActiveSortDirection] = React.useState<'asc' | 'desc' | null>(null);
35
+
36
+ // Since OnSort specifies sorted columns by index, we need sortable values for our object by column index.
37
+ // This example is trivial since our data objects just contain strings, but if the data was more complex
38
+ // this would be a place to return simplified string or number versions of each column to sort by.
39
+ const getSortableRowValues = (repo: Repository): (string | number)[] => {
40
+ const { name, branches, prs, workspaces, lastCommit } = repo;
41
+ return [name, branches, prs, workspaces, lastCommit];
42
+ };
43
+
44
+ // Note that we perform the sort as part of the component's render logic and not in onSort.
45
+ // We shouldn't store the list of data in state because we don't want to have to sync that with props.
46
+ let sortedRepositories = repositories;
47
+ if (activeSortIndex !== null) {
48
+ sortedRepositories = repositories.sort((a, b) => {
49
+ const aValue = getSortableRowValues(a)[activeSortIndex];
50
+ const bValue = getSortableRowValues(b)[activeSortIndex];
51
+ if (typeof aValue === 'number') {
52
+ // Numeric sort
53
+ if (activeSortDirection === 'asc') {
54
+ return (aValue as number) - (bValue as number);
55
+ }
56
+ return (bValue as number) - (aValue as number);
57
+ } else {
58
+ // String sort
59
+ if (activeSortDirection === 'asc') {
60
+ return (aValue as string).localeCompare(bValue as string);
61
+ }
62
+ return (bValue as string).localeCompare(aValue as string);
63
+ }
64
+ });
65
+ }
66
+
67
+ const getSortParams = (columnIndex: number): ThProps['sort'] => ({
68
+ sortBy: {
69
+ index: activeSortIndex,
70
+ direction: activeSortDirection,
71
+ defaultDirection: 'asc' // starting sort direction when first sorting a column. Defaults to 'asc'
72
+ },
73
+ onSort: (_event, index, direction) => {
74
+ setActiveSortIndex(index);
75
+ setActiveSortDirection(direction);
76
+ },
77
+ columnIndex
78
+ });
79
+
80
+ // In this example, we wrap all but the 1st column and make the 1st and 3rd columns sortable just to demonstrate.
81
+ return (
82
+ <TableComposable aria-label="Sortable table">
83
+ <Thead>
84
+ <Tr>
85
+ <Th sort={getSortParams(0)}>{columnNames.name}</Th>
86
+ <Th modifier="wrap">{columnNames.branches}</Th>
87
+ <Th modifier="wrap" sort={getSortParams(2)} info={{ tooltip: 'More information ' }}>
88
+ {columnNames.prs}
89
+ </Th>
90
+ <Th modifier="wrap">{columnNames.workspaces}</Th>
91
+ <Th modifier="wrap">{columnNames.lastCommit}</Th>
92
+ </Tr>
93
+ </Thead>
94
+ <Tbody>
95
+ {sortedRepositories.map((repo, rowIndex) => (
96
+ <Tr key={rowIndex}>
97
+ <Td dataLabel={columnNames.name}>{repo.name}</Td>
98
+ <Td dataLabel={columnNames.branches}>{repo.branches}</Td>
99
+ <Td dataLabel={columnNames.prs}>{repo.prs}</Td>
100
+ <Td dataLabel={columnNames.workspaces}>{repo.workspaces}</Td>
101
+ <Td dataLabel={columnNames.lastCommit}>{repo.lastCommit}</Td>
102
+ </Tr>
103
+ ))}
104
+ </Tbody>
105
+ </TableComposable>
106
+ );
107
+ };