@patternfly/react-table 4.44.12 → 4.44.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/dist/esm/components/Table/Table.d.ts +2 -1
- package/dist/esm/components/Table/Table.d.ts.map +1 -1
- package/dist/esm/components/Table/Table.js.map +1 -1
- package/dist/esm/components/Table/TableTypes.d.ts +2 -1
- package/dist/esm/components/Table/TableTypes.d.ts.map +1 -1
- package/dist/esm/components/Table/TableTypes.js.map +1 -1
- package/dist/esm/components/Table/base/provider.d.ts +2 -2
- package/dist/esm/components/Table/base/types.d.ts +2 -2
- package/dist/esm/components/TableComposable/TableComposable.d.ts +1 -1
- package/dist/esm/components/TableComposable/Td.d.ts +1 -1
- package/dist/js/components/Table/Table.d.ts +2 -1
- package/dist/js/components/Table/Table.d.ts.map +1 -1
- package/dist/js/components/Table/Table.js.map +1 -1
- package/dist/js/components/Table/TableTypes.d.ts +2 -1
- package/dist/js/components/Table/TableTypes.d.ts.map +1 -1
- package/dist/js/components/Table/TableTypes.js.map +1 -1
- package/dist/js/components/Table/base/provider.d.ts +2 -2
- package/dist/js/components/Table/base/types.d.ts +2 -2
- package/dist/js/components/TableComposable/TableComposable.d.ts +1 -1
- package/dist/js/components/TableComposable/Td.d.ts +1 -1
- package/package.json +6 -6
- package/src/components/Table/Table.tsx +2 -1
- package/src/components/Table/TableTypes.tsx +2 -1
- package/src/components/Table/examples/LegacyTableActions.tsx +218 -0
- package/src/components/Table/examples/LegacyTableBasic.tsx +90 -0
- package/src/components/Table/examples/LegacyTableCellWidth.tsx +66 -0
- package/src/components/Table/examples/LegacyTableCompoundExpandable.tsx +175 -0
- package/src/components/Table/examples/LegacyTableControllingText.tsx +51 -0
- package/src/components/Table/examples/LegacyTableEmptyState.tsx +48 -0
- package/src/components/Table/examples/LegacyTableExpandable.tsx +172 -0
- package/src/components/Table/examples/LegacyTableFavoritable.tsx +157 -0
- package/src/components/Table/examples/LegacyTableHoverable.tsx +125 -0
- package/src/components/Table/examples/LegacyTableMisc.tsx +56 -0
- package/src/components/Table/examples/LegacyTableSelectable.tsx +124 -0
- package/src/components/Table/examples/LegacyTableSelectableRadio.tsx +54 -0
- package/src/components/Table/examples/LegacyTableSortable.tsx +120 -0
- package/src/components/Table/examples/LegacyTableSortableCustom.tsx +189 -0
- package/src/components/Table/examples/LegacyTableTextModifiers.tsx +31 -0
- package/src/components/Table/examples/LegacyTableTree.tsx +213 -0
- package/src/components/Table/examples/Table.md +83 -1848
- package/src/components/TableComposable/examples/ComposableTable.md +31 -2805
- package/src/components/TableComposable/examples/ComposableTableActions.tsx +163 -0
- package/src/components/TableComposable/examples/ComposableTableBasic.tsx +89 -0
- package/src/components/TableComposable/examples/ComposableTableCellWidth.tsx +56 -0
- package/src/components/TableComposable/examples/ComposableTableCompoundExpandable.tsx +103 -0
- package/src/components/TableComposable/examples/ComposableTableControllingText.tsx +51 -0
- package/src/components/TableComposable/examples/ComposableTableDraggable.tsx +170 -0
- package/src/components/TableComposable/examples/ComposableTableEmptyState.tsx +45 -0
- package/src/components/TableComposable/examples/ComposableTableExpandable.tsx +195 -0
- package/src/components/TableComposable/examples/ComposableTableFavoritable.tsx +118 -0
- package/src/components/TableComposable/examples/ComposableTableHoverable.tsx +67 -0
- package/src/components/TableComposable/examples/ComposableTableMisc.tsx +92 -0
- package/src/components/TableComposable/examples/ComposableTableMultipleStickyColumns.tsx +153 -0
- package/src/components/TableComposable/examples/ComposableTableNestedExpandable.tsx +127 -0
- package/src/components/TableComposable/examples/ComposableTableNestedHeaders.tsx +202 -0
- package/src/components/TableComposable/examples/ComposableTableSelectable.tsx +125 -0
- package/src/components/TableComposable/examples/ComposableTableSelectableRadio.tsx +68 -0
- package/src/components/TableComposable/examples/ComposableTableSortable.tsx +107 -0
- package/src/components/TableComposable/examples/ComposableTableSortableCustom.tsx +175 -0
- package/src/components/TableComposable/examples/ComposableTableStickyColumn.tsx +144 -0
- package/src/components/TableComposable/examples/ComposableTableStickyColumnsAndHeader.tsx +167 -0
- package/src/components/TableComposable/examples/ComposableTableTextModifiers.tsx +33 -0
- package/src/components/TableComposable/examples/ComposableTableTree.tsx +201 -0
- package/src/demos/Table.md +7 -6
- package/src/components/Table/examples/DemoSortableTable.js +0 -65
- package/src/components/TableComposable/examples/DemoSortableTable.js +0 -65
|
@@ -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
|
+
};
|