@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,175 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Table, TableHeader, TableBody, compoundExpand, TableProps } from '@patternfly/react-table';
|
|
3
|
+
|
|
4
|
+
import CodeBranchIcon from '@patternfly/react-icons/dist/esm/icons/code-branch-icon';
|
|
5
|
+
import CodeIcon from '@patternfly/react-icons/dist/esm/icons/code-icon';
|
|
6
|
+
import CubeIcon from '@patternfly/react-icons/dist/esm/icons/cube-icon';
|
|
7
|
+
|
|
8
|
+
interface Repository {
|
|
9
|
+
name: string;
|
|
10
|
+
branches: number;
|
|
11
|
+
prs: number;
|
|
12
|
+
workspaces: number;
|
|
13
|
+
lastCommit: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const LegacyTableCompoundExpandable: React.FunctionComponent = () => {
|
|
17
|
+
// In real usage, this data would come from some external source like an API via props.
|
|
18
|
+
const repositories: Repository[] = [
|
|
19
|
+
{ name: 'siemur/test-space', branches: 10, prs: 4, workspaces: 4, lastCommit: '20 minutes' },
|
|
20
|
+
{ name: 'siemur/test-space-2', branches: 3, prs: 4, workspaces: 4, lastCommit: '20 minutes' }
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
// In this example, expanded cells are tracked by the repo and property names from each row. This could be any pair of unique identifiers.
|
|
24
|
+
// This is to prevent state from being based on row and column order index in case we later add sorting and rearranging columns.
|
|
25
|
+
// Note that these columnKeys must be in the same order as the `columns` below, because columnIndex will be used to look up the matching key.
|
|
26
|
+
type ColumnKey = 'name' | 'branches' | 'prs' | 'workspaces' | 'lastCommit'; // For typechecking
|
|
27
|
+
const columnKeys: ColumnKey[] = ['name', 'branches', 'prs', 'workspaces', 'lastCommit']; // For indexed order
|
|
28
|
+
const [expandedCells, setExpandedCells] = React.useState<Record<string, ColumnKey>>({
|
|
29
|
+
'siemur/test-space': 'branches' // Default to the first cell of the first row being expanded
|
|
30
|
+
});
|
|
31
|
+
const setCellExpanded = (repo: Repository, columnKey: ColumnKey, isExpanding = true) => {
|
|
32
|
+
const newExpandedCells = { ...expandedCells };
|
|
33
|
+
if (isExpanding) {
|
|
34
|
+
newExpandedCells[repo.name] = columnKey;
|
|
35
|
+
} else {
|
|
36
|
+
delete newExpandedCells[repo.name];
|
|
37
|
+
}
|
|
38
|
+
setExpandedCells(newExpandedCells);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// We want to be able to reference the original data object based on row index. But because parent/child rows are
|
|
42
|
+
// multiple elements in the rows array, repositories[rowIndex] will not be accurate like it would in a normal table.
|
|
43
|
+
// One solution to this is to create an array of data objects indexed by the displayed row index.
|
|
44
|
+
const reposByRowIndex: (Repository | null)[] = [];
|
|
45
|
+
|
|
46
|
+
const columns: TableProps['cells'] = [
|
|
47
|
+
'Repositories',
|
|
48
|
+
{
|
|
49
|
+
title: 'Branches',
|
|
50
|
+
cellTransforms: [compoundExpand]
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
title: 'Pull requests',
|
|
54
|
+
cellTransforms: [compoundExpand]
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
title: 'Workspaces',
|
|
58
|
+
cellTransforms: [compoundExpand]
|
|
59
|
+
},
|
|
60
|
+
'Last Commit',
|
|
61
|
+
{
|
|
62
|
+
title: '',
|
|
63
|
+
dataLabel: 'Action'
|
|
64
|
+
}
|
|
65
|
+
];
|
|
66
|
+
const rows: TableProps['rows'] = [];
|
|
67
|
+
repositories.forEach(repo => {
|
|
68
|
+
const expandedCellKey = expandedCells[repo.name];
|
|
69
|
+
const isRowExpanded = !!expandedCellKey;
|
|
70
|
+
rows.push({
|
|
71
|
+
isOpen: isRowExpanded,
|
|
72
|
+
cells: [
|
|
73
|
+
{ title: <a href="#">{repo.name}</a>, props: { component: 'th' } },
|
|
74
|
+
{
|
|
75
|
+
title: (
|
|
76
|
+
<React.Fragment>
|
|
77
|
+
<CodeBranchIcon key="icon" /> {repo.branches}
|
|
78
|
+
</React.Fragment>
|
|
79
|
+
),
|
|
80
|
+
props: {
|
|
81
|
+
isOpen: expandedCellKey === 'branches',
|
|
82
|
+
ariaControls: `compound-expansion-table-${repo.name}-1`
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
title: (
|
|
87
|
+
<React.Fragment>
|
|
88
|
+
<CodeIcon key="icon" /> {repo.prs}
|
|
89
|
+
</React.Fragment>
|
|
90
|
+
),
|
|
91
|
+
props: {
|
|
92
|
+
isOpen: expandedCellKey === 'prs',
|
|
93
|
+
ariaControls: `compound-expansion-table-${repo.name}-2`
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
title: (
|
|
98
|
+
<React.Fragment>
|
|
99
|
+
<CubeIcon key="icon" /> {repo.branches}
|
|
100
|
+
</React.Fragment>
|
|
101
|
+
),
|
|
102
|
+
props: {
|
|
103
|
+
isOpen: expandedCellKey === 'workspaces',
|
|
104
|
+
ariaControls: `compound-expansion-table-${repo.name}-3`
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
repo.lastCommit,
|
|
108
|
+
{ title: <a href="#">Open in Github</a> }
|
|
109
|
+
]
|
|
110
|
+
});
|
|
111
|
+
reposByRowIndex.push(repo);
|
|
112
|
+
const parentIndex = rows.length - 1;
|
|
113
|
+
rows.push({
|
|
114
|
+
parent: parentIndex,
|
|
115
|
+
compoundParent: 1,
|
|
116
|
+
cells: [
|
|
117
|
+
{
|
|
118
|
+
title: (
|
|
119
|
+
<div className="pf-u-m-md" id={`compound-expansion-table-${repo.name}-1`}>
|
|
120
|
+
Expanded content for {repo.name}: Branches goes here!
|
|
121
|
+
</div>
|
|
122
|
+
),
|
|
123
|
+
props: { colSpan: 6, className: 'pf-m-no-padding' }
|
|
124
|
+
}
|
|
125
|
+
]
|
|
126
|
+
});
|
|
127
|
+
reposByRowIndex.push(null);
|
|
128
|
+
rows.push({
|
|
129
|
+
parent: parentIndex,
|
|
130
|
+
compoundParent: 2,
|
|
131
|
+
cells: [
|
|
132
|
+
{
|
|
133
|
+
title: (
|
|
134
|
+
<div className="pf-u-m-md" id={`compound-expansion-table-${repo.name}-2`}>
|
|
135
|
+
Expanded content for {repo.name}: Pull requests goes here!
|
|
136
|
+
</div>
|
|
137
|
+
),
|
|
138
|
+
props: { colSpan: 6, className: 'pf-m-no-padding' }
|
|
139
|
+
}
|
|
140
|
+
]
|
|
141
|
+
});
|
|
142
|
+
reposByRowIndex.push(null);
|
|
143
|
+
rows.push({
|
|
144
|
+
parent: parentIndex,
|
|
145
|
+
compoundParent: 3,
|
|
146
|
+
cells: [
|
|
147
|
+
{
|
|
148
|
+
title: (
|
|
149
|
+
<div className="pf-u-m-md" id={`compound-expansion-table-${repo.name}-3`}>
|
|
150
|
+
Expanded content for {repo.name}: Workspaces goes here!
|
|
151
|
+
</div>
|
|
152
|
+
),
|
|
153
|
+
props: { colSpan: 6, className: 'pf-m-no-padding' }
|
|
154
|
+
}
|
|
155
|
+
]
|
|
156
|
+
});
|
|
157
|
+
reposByRowIndex.push(null);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
return (
|
|
161
|
+
<Table
|
|
162
|
+
aria-label="Compound expandable table"
|
|
163
|
+
onExpand={(_event, rowIndex, colIndex, isOpen) => {
|
|
164
|
+
if (reposByRowIndex[rowIndex]) {
|
|
165
|
+
setCellExpanded(reposByRowIndex[rowIndex], columnKeys[colIndex], !isOpen);
|
|
166
|
+
}
|
|
167
|
+
}}
|
|
168
|
+
rows={rows}
|
|
169
|
+
cells={columns}
|
|
170
|
+
>
|
|
171
|
+
<TableHeader />
|
|
172
|
+
<TableBody />
|
|
173
|
+
</Table>
|
|
174
|
+
);
|
|
175
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Table,
|
|
4
|
+
TableHeader,
|
|
5
|
+
TableBody,
|
|
6
|
+
cellWidth,
|
|
7
|
+
truncate,
|
|
8
|
+
breakWord,
|
|
9
|
+
wrappable,
|
|
10
|
+
fitContent,
|
|
11
|
+
nowrap,
|
|
12
|
+
TableProps
|
|
13
|
+
} from '@patternfly/react-table';
|
|
14
|
+
|
|
15
|
+
// This example has been simplified to focus on the text modifier props. In real usage,
|
|
16
|
+
// you may want to derive your rows from typed underlying data and minimal state. See other examples.
|
|
17
|
+
|
|
18
|
+
export const LegacyTableControllingText: React.FunctionComponent = () => {
|
|
19
|
+
const columns: TableProps['cells'] = [
|
|
20
|
+
{ title: 'Truncate (width 20%)', transforms: [cellWidth(20)], cellTransforms: [truncate] },
|
|
21
|
+
{ title: 'Break word', cellTransforms: [breakWord] },
|
|
22
|
+
{ title: 'Wrapping table header text. This th text will wrap instead of truncate.', transforms: [wrappable] },
|
|
23
|
+
{ title: 'Fit content', transforms: [fitContent] },
|
|
24
|
+
{ title: '', cellTransforms: [nowrap] }
|
|
25
|
+
];
|
|
26
|
+
const rows: TableProps['rows'] = [
|
|
27
|
+
[
|
|
28
|
+
'This text will truncate instead of wrap.',
|
|
29
|
+
{ title: <a href="#">http://thisisaverylongurlthatneedstobreakusethebreakwordmodifier.org</a> },
|
|
30
|
+
{
|
|
31
|
+
title: (
|
|
32
|
+
<p>
|
|
33
|
+
By default,
|
|
34
|
+
<code>thead</code> cells will truncate and
|
|
35
|
+
<code>tbody</code> cells will wrap. Use
|
|
36
|
+
<code>transforms</code> and/or <code>cellTransforms</code> to change the behavior.
|
|
37
|
+
</p>
|
|
38
|
+
)
|
|
39
|
+
},
|
|
40
|
+
"This cell's content will adjust itself to the parent th width. This modifier only affects table layouts.",
|
|
41
|
+
{ title: <a href="#">No wrap</a> }
|
|
42
|
+
]
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<Table aria-label="Controlling text" cells={columns} rows={rows}>
|
|
47
|
+
<TableHeader />
|
|
48
|
+
<TableBody />
|
|
49
|
+
</Table>
|
|
50
|
+
);
|
|
51
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Table, TableHeader, TableBody, TableProps } from '@patternfly/react-table';
|
|
3
|
+
import {
|
|
4
|
+
Bullseye,
|
|
5
|
+
EmptyState,
|
|
6
|
+
EmptyStateVariant,
|
|
7
|
+
EmptyStateIcon,
|
|
8
|
+
Title,
|
|
9
|
+
EmptyStateBody,
|
|
10
|
+
Button
|
|
11
|
+
} from '@patternfly/react-core';
|
|
12
|
+
import SearchIcon from '@patternfly/react-icons/dist/esm/icons/search-icon';
|
|
13
|
+
|
|
14
|
+
// This example has been simplified to focus on the empty state. In real usage,
|
|
15
|
+
// you may want to derive your rows from typed underlying data and minimal state. See other examples.
|
|
16
|
+
|
|
17
|
+
export const LegacyTableEmptyState: React.FunctionComponent = () => {
|
|
18
|
+
const columns: TableProps['cells'] = ['Repositories', 'Branches', 'Pull requests', 'Workspaces', 'Last commit'];
|
|
19
|
+
const rows: TableProps['rows'] = [
|
|
20
|
+
{
|
|
21
|
+
heightAuto: true,
|
|
22
|
+
cells: [
|
|
23
|
+
{
|
|
24
|
+
props: { colSpan: 8 },
|
|
25
|
+
title: (
|
|
26
|
+
<Bullseye>
|
|
27
|
+
<EmptyState variant={EmptyStateVariant.small}>
|
|
28
|
+
<EmptyStateIcon icon={SearchIcon} />
|
|
29
|
+
<Title headingLevel="h2" size="lg">
|
|
30
|
+
No results found
|
|
31
|
+
</Title>
|
|
32
|
+
<EmptyStateBody>Clear all filters and try again.</EmptyStateBody>
|
|
33
|
+
<Button variant="link">Clear all filters</Button>
|
|
34
|
+
</EmptyState>
|
|
35
|
+
</Bullseye>
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<Table aria-label="Table text with modifiers" cells={columns} rows={rows}>
|
|
44
|
+
<TableHeader />
|
|
45
|
+
<TableBody />
|
|
46
|
+
</Table>
|
|
47
|
+
);
|
|
48
|
+
};
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Table, TableBody, TableHeader, TableVariant, TableProps, expandable, IRowCell } from '@patternfly/react-table';
|
|
3
|
+
import { Checkbox } from '@patternfly/react-core';
|
|
4
|
+
|
|
5
|
+
interface Repository {
|
|
6
|
+
name: string;
|
|
7
|
+
branches: string;
|
|
8
|
+
prs: string;
|
|
9
|
+
workspaces: string;
|
|
10
|
+
details?: {
|
|
11
|
+
detail1?: string;
|
|
12
|
+
detail2?: string;
|
|
13
|
+
detail3?: string;
|
|
14
|
+
detailFormat: 0 | 1 | 2 | 3;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const LegacyTableExpandable: React.FunctionComponent = () => {
|
|
19
|
+
// In real usage, this data would come from some external source like an API via props.
|
|
20
|
+
const repositories: Repository[] = [
|
|
21
|
+
{ name: 'one', branches: 'two', prs: 'a', workspaces: 'four' },
|
|
22
|
+
{
|
|
23
|
+
name: 'parent 1',
|
|
24
|
+
branches: 'two',
|
|
25
|
+
prs: 'k',
|
|
26
|
+
workspaces: 'four',
|
|
27
|
+
// This `details` structure is just for this example. You can drive expanded content from any kind of data.
|
|
28
|
+
details: { detailFormat: 0, detail1: 'single cell' }
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
name: 'parent 2',
|
|
32
|
+
branches: 'two',
|
|
33
|
+
prs: 'b',
|
|
34
|
+
workspaces: 'four',
|
|
35
|
+
details: { detailFormat: 1, detail1: 'single cell - fullWidth' }
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: 'parent 3',
|
|
39
|
+
branches: '2',
|
|
40
|
+
prs: 'b',
|
|
41
|
+
workspaces: 'four',
|
|
42
|
+
details: { detailFormat: 2, detail1: 'single cell - noPadding' }
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: 'parent 4',
|
|
46
|
+
branches: '2',
|
|
47
|
+
prs: 'b',
|
|
48
|
+
workspaces: 'four',
|
|
49
|
+
details: { detailFormat: 3, detail1: 'single cell - fullWidth & noPadding' }
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: 'parent 5',
|
|
53
|
+
branches: '2',
|
|
54
|
+
prs: 'b',
|
|
55
|
+
workspaces: 'four',
|
|
56
|
+
details: {
|
|
57
|
+
detailFormat: 0,
|
|
58
|
+
detail1: "spans 'Repositories and 'Branches'",
|
|
59
|
+
detail2: "spans 'Pull requests' and 'Workspaces', and 'Last commit'"
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: 'parent 6',
|
|
64
|
+
branches: '2',
|
|
65
|
+
prs: 'b',
|
|
66
|
+
workspaces: 'four',
|
|
67
|
+
details: {
|
|
68
|
+
detailFormat: 1,
|
|
69
|
+
detail1: "fullWidth, spans the collapsible column and 'Repositories'",
|
|
70
|
+
detail2: "fullWidth, spans 'Branches' and 'Pull requests'",
|
|
71
|
+
detail3: 'fullWidth, spans the empty column'
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
];
|
|
75
|
+
|
|
76
|
+
// In this example, expanded rows are tracked by the repo names from each row. This could be any unique identifier.
|
|
77
|
+
// This is to prevent state from being based on row order index in case we later add sorting.
|
|
78
|
+
// Note that this behavior is very similar to selection state.
|
|
79
|
+
const initialExpandedRepoNames = repositories.filter(repo => !!repo.details).map(repo => repo.name); // Default to all expanded
|
|
80
|
+
const [expandedRepoNames, setExpandedRepoNames] = React.useState<string[]>(initialExpandedRepoNames);
|
|
81
|
+
const setRepoExpanded = (repo: Repository, isExpanding = true) =>
|
|
82
|
+
setExpandedRepoNames(prevExpanded => {
|
|
83
|
+
const otherExpandedRepoNames = prevExpanded.filter(r => r !== repo.name);
|
|
84
|
+
return isExpanding ? [...otherExpandedRepoNames, repo.name] : otherExpandedRepoNames;
|
|
85
|
+
});
|
|
86
|
+
const isRepoExpanded = (repo: Repository) => expandedRepoNames.includes(repo.name);
|
|
87
|
+
|
|
88
|
+
const [isExampleCompact, setIsExampleCompact] = React.useState(true);
|
|
89
|
+
|
|
90
|
+
// We want to be able to reference the original data object based on row index. But because an expanded
|
|
91
|
+
// row takes up two row indexes, repositories[rowIndex] will not be accurate like it would in a normal table.
|
|
92
|
+
// One solution to this is to create an array of data objects indexed by the displayed row index.
|
|
93
|
+
const reposByRowIndex: (Repository | null)[] = [];
|
|
94
|
+
|
|
95
|
+
const columns: TableProps['cells'] = [
|
|
96
|
+
{
|
|
97
|
+
title: 'Repositories',
|
|
98
|
+
cellFormatters: [expandable]
|
|
99
|
+
},
|
|
100
|
+
'Branches',
|
|
101
|
+
{ title: 'Pull requests' },
|
|
102
|
+
{ title: '' /* deliberately empty */, dataLabel: 'Label for mobile view' }
|
|
103
|
+
];
|
|
104
|
+
const rows: TableProps['rows'] = [];
|
|
105
|
+
repositories.forEach(repo => {
|
|
106
|
+
rows.push({
|
|
107
|
+
...(repo.details ? { isOpen: isRepoExpanded(repo) } : {}),
|
|
108
|
+
cells: [repo.name, repo.branches, repo.prs, repo.workspaces]
|
|
109
|
+
});
|
|
110
|
+
reposByRowIndex.push(repo);
|
|
111
|
+
if (repo.details) {
|
|
112
|
+
// Some arbitrary examples of how you could customize the child row based on your needs
|
|
113
|
+
const { detail1, detail2, detail3, detailFormat } = repo.details;
|
|
114
|
+
const numColumns = 5;
|
|
115
|
+
const childIsFullWidth = [1, 3].includes(detailFormat);
|
|
116
|
+
const childHasNoPadding = [2, 3].includes(detailFormat);
|
|
117
|
+
let detailColSpans = [1, 1, 1];
|
|
118
|
+
if (detail1 && !detail2 && !detail3) {
|
|
119
|
+
detailColSpans = [childIsFullWidth ? numColumns : numColumns + 1]; // Account for toggle column
|
|
120
|
+
} else if (detail1 && detail2 && !detail3) {
|
|
121
|
+
detailColSpans = [2, childIsFullWidth ? 3 : 4];
|
|
122
|
+
} else if (detail1 && detail2 && detail3) {
|
|
123
|
+
detailColSpans = [2, 2, childIsFullWidth ? 1 : 2];
|
|
124
|
+
}
|
|
125
|
+
const cells: IRowCell[] = [];
|
|
126
|
+
[detail1, detail2, detail3].forEach((detail, index) => {
|
|
127
|
+
if (detail) {
|
|
128
|
+
cells.push({
|
|
129
|
+
title: detail1,
|
|
130
|
+
props: {
|
|
131
|
+
colSpan: detailColSpans[index]
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
rows.push({
|
|
137
|
+
parent: rows.length - 1,
|
|
138
|
+
cells,
|
|
139
|
+
fullWidth: childIsFullWidth,
|
|
140
|
+
noPadding: childHasNoPadding
|
|
141
|
+
});
|
|
142
|
+
reposByRowIndex.push(null);
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
return (
|
|
147
|
+
<React.Fragment>
|
|
148
|
+
<Checkbox
|
|
149
|
+
label="Compact"
|
|
150
|
+
isChecked={isExampleCompact}
|
|
151
|
+
onChange={checked => setIsExampleCompact(checked)}
|
|
152
|
+
aria-label="toggle compact variation"
|
|
153
|
+
id="toggle-compact"
|
|
154
|
+
name="toggle-compact"
|
|
155
|
+
/>
|
|
156
|
+
<Table
|
|
157
|
+
aria-label="Expandable table"
|
|
158
|
+
variant={isExampleCompact ? TableVariant.compact : undefined}
|
|
159
|
+
onCollapse={(_event, rowIndex, isOpen) => {
|
|
160
|
+
if (reposByRowIndex[rowIndex]) {
|
|
161
|
+
setRepoExpanded(reposByRowIndex[rowIndex], isOpen);
|
|
162
|
+
}
|
|
163
|
+
}}
|
|
164
|
+
rows={rows}
|
|
165
|
+
cells={columns}
|
|
166
|
+
>
|
|
167
|
+
<TableHeader />
|
|
168
|
+
<TableBody />
|
|
169
|
+
</Table>
|
|
170
|
+
</React.Fragment>
|
|
171
|
+
);
|
|
172
|
+
};
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Table, TableHeader, TableBody, sortable, TableProps } from '@patternfly/react-table';
|
|
3
|
+
import { Checkbox } from '@patternfly/react-core';
|
|
4
|
+
|
|
5
|
+
interface Repository {
|
|
6
|
+
name: string;
|
|
7
|
+
branches: string;
|
|
8
|
+
prs: string;
|
|
9
|
+
workspaces: string;
|
|
10
|
+
lastCommit: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const LegacyTableFavoritable: React.FunctionComponent = () => {
|
|
14
|
+
// In real usage, this data would come from some external source like an API via props.
|
|
15
|
+
const repositories: Repository[] = [
|
|
16
|
+
{ name: 'one', branches: 'two', prs: 'a', workspaces: 'four', lastCommit: 'five' },
|
|
17
|
+
{ name: 'a', branches: 'two', prs: 'k', workspaces: 'four', lastCommit: 'five' },
|
|
18
|
+
{ name: 'p', branches: 'two', prs: 'b', workspaces: 'four', lastCommit: 'five' }
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
// Index of the currently sorted column
|
|
22
|
+
// Note: if you intend to make columns reorderable, you may instead want to use a non-numeric key
|
|
23
|
+
// as the identifier of the sorted column. See the "Compound expandable" example.
|
|
24
|
+
const [activeSortIndex, setActiveSortIndex] = React.useState<number | null>(null);
|
|
25
|
+
|
|
26
|
+
// Sort direction of the currently sorted column
|
|
27
|
+
const [activeSortDirection, setActiveSortDirection] = React.useState<'asc' | 'desc' | null>(null);
|
|
28
|
+
|
|
29
|
+
// Favorite state is similar to selection state. See selectedRepoNames and associated code below.
|
|
30
|
+
const [favoriteRepoNames, setFavoriteRepoNames] = React.useState<string[]>([]);
|
|
31
|
+
const setRepoFavorited = (repo: Repository, isFavoriting = true) =>
|
|
32
|
+
setFavoriteRepoNames(prevFavorites => {
|
|
33
|
+
const otherFavorites = prevFavorites.filter(r => r !== repo.name);
|
|
34
|
+
return isFavoriting ? [...otherFavorites, repo.name] : otherFavorites;
|
|
35
|
+
});
|
|
36
|
+
const isRepoFavorited = (repo: Repository) => favoriteRepoNames.includes(repo.name);
|
|
37
|
+
|
|
38
|
+
// Since OnSort specifies sorted columns by index, we need sortable values for our object by column index.
|
|
39
|
+
// Note: We also memoize the sortable values with useCallback to prevent rows jumping around when you change
|
|
40
|
+
// the favorites while sorting on that column. Only updating the sort state will reorder the rows.
|
|
41
|
+
const getSortableRowValues = React.useCallback(
|
|
42
|
+
(repo: Repository): (boolean | string | number)[] => {
|
|
43
|
+
const { name, branches, prs, workspaces, lastCommit } = repo;
|
|
44
|
+
// Blank value for first column (select checkboxes)
|
|
45
|
+
return ['', isRepoFavorited(repo), name, branches, prs, workspaces, lastCommit];
|
|
46
|
+
},
|
|
47
|
+
[activeSortIndex, activeSortDirection]
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
// Note that we perform the sort as part of the component's render logic and not in onSort.
|
|
51
|
+
// We shouldn't store the list of data in state because we don't want to have to sync that with props.
|
|
52
|
+
let sortedRepositories = repositories;
|
|
53
|
+
if (activeSortIndex !== null) {
|
|
54
|
+
sortedRepositories = repositories.sort((a, b) => {
|
|
55
|
+
const aValue = getSortableRowValues(a)[activeSortIndex];
|
|
56
|
+
const bValue = getSortableRowValues(b)[activeSortIndex];
|
|
57
|
+
if (typeof aValue === 'number' || typeof aValue === 'boolean') {
|
|
58
|
+
// Numeric sort
|
|
59
|
+
if (activeSortDirection === 'asc') {
|
|
60
|
+
return (aValue as number) - (bValue as number);
|
|
61
|
+
}
|
|
62
|
+
return (bValue as number) - (aValue as number);
|
|
63
|
+
} else {
|
|
64
|
+
// String sort
|
|
65
|
+
if (activeSortDirection === 'asc') {
|
|
66
|
+
return (aValue as string).localeCompare(bValue as string);
|
|
67
|
+
}
|
|
68
|
+
return (bValue as string).localeCompare(aValue as string);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const isRepoSelectable = (repo: Repository) => repo.name !== 'a'; // Arbitrary logic for this example
|
|
74
|
+
const selectableRepos = repositories.filter(isRepoSelectable);
|
|
75
|
+
|
|
76
|
+
// In this example, selected rows are tracked by the repo names from each row. This could be any unique identifier.
|
|
77
|
+
// This is to prevent state from being based on row order index in case we later add sorting.
|
|
78
|
+
const [selectedRepoNames, setSelectedRepoNames] = React.useState<string[]>([]);
|
|
79
|
+
const setRepoSelected = (repo: Repository, isSelecting = true) =>
|
|
80
|
+
setSelectedRepoNames(prevSelected => {
|
|
81
|
+
const otherSelectedRepoNames = prevSelected.filter(r => r !== repo.name);
|
|
82
|
+
return isSelecting && isRepoSelectable(repo) ? [...otherSelectedRepoNames, repo.name] : otherSelectedRepoNames;
|
|
83
|
+
});
|
|
84
|
+
const selectAllRepos = (isSelecting = true) =>
|
|
85
|
+
setSelectedRepoNames(isSelecting ? selectableRepos.map(r => r.name) : []);
|
|
86
|
+
const isRepoSelected = (repo: Repository) => selectedRepoNames.includes(repo.name);
|
|
87
|
+
|
|
88
|
+
const columns: TableProps['cells'] = [
|
|
89
|
+
{ title: 'Repositories', transforms: [sortable] },
|
|
90
|
+
'Branches',
|
|
91
|
+
'Pull requests',
|
|
92
|
+
'Workspaces',
|
|
93
|
+
'Last commit'
|
|
94
|
+
];
|
|
95
|
+
const rows: TableProps['rows'] = sortedRepositories.map(repo => {
|
|
96
|
+
const favorited = isRepoFavorited(repo);
|
|
97
|
+
return {
|
|
98
|
+
favorited,
|
|
99
|
+
// The favorites button has defaults that can be overriden
|
|
100
|
+
favoritesProps: {
|
|
101
|
+
'aria-label': favorited ? 'Favorited' : 'Not favorited', // Defaults to 'Starred' / 'Not starred'
|
|
102
|
+
id: `favorites-button-${repo.name}`, // Defaults to `favorites-button-${rowIndex}`
|
|
103
|
+
'aria-labelledby': `favorites-button-${repo.name}` // Defaults to `favorites-button-${rowIndex}`
|
|
104
|
+
},
|
|
105
|
+
selected: isRepoSelected(repo),
|
|
106
|
+
disableSelection: !isRepoSelectable(repo),
|
|
107
|
+
cells: [repo.name, repo.branches, repo.prs, repo.workspaces, repo.lastCommit]
|
|
108
|
+
};
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const [canSortFavorites, setCanSortFavorites] = React.useState(true);
|
|
112
|
+
|
|
113
|
+
return (
|
|
114
|
+
<div>
|
|
115
|
+
<Checkbox
|
|
116
|
+
label="Can sort favorites"
|
|
117
|
+
isChecked={canSortFavorites}
|
|
118
|
+
onChange={setCanSortFavorites}
|
|
119
|
+
aria-label="toggle can sort favorites checkbox"
|
|
120
|
+
id="toggle-can-sort-favorites"
|
|
121
|
+
name="toggle-can-sort-favorites"
|
|
122
|
+
/>
|
|
123
|
+
<Table
|
|
124
|
+
// using this prop enables the favorites column
|
|
125
|
+
onFavorite={(_event, isFavoriting, rowIndex) => {
|
|
126
|
+
const repo = sortedRepositories[rowIndex];
|
|
127
|
+
setRepoFavorited(repo, isFavoriting);
|
|
128
|
+
}}
|
|
129
|
+
// if the onSort prop is detected, favorites can be sorted
|
|
130
|
+
// if you want to exclude favorites from sorting you can use this prop with a value of `false`
|
|
131
|
+
canSortFavorites={canSortFavorites}
|
|
132
|
+
onSelect={(_event, isSelecting, rowIndex) => {
|
|
133
|
+
if (rowIndex === -1) {
|
|
134
|
+
selectAllRepos(isSelecting);
|
|
135
|
+
} else {
|
|
136
|
+
const repo = sortedRepositories[rowIndex];
|
|
137
|
+
setRepoSelected(repo, isSelecting);
|
|
138
|
+
}
|
|
139
|
+
}}
|
|
140
|
+
onSort={(_event, index, direction) => {
|
|
141
|
+
setActiveSortIndex(index);
|
|
142
|
+
setActiveSortDirection(direction);
|
|
143
|
+
}}
|
|
144
|
+
sortBy={{
|
|
145
|
+
index: activeSortIndex,
|
|
146
|
+
direction: activeSortDirection
|
|
147
|
+
}}
|
|
148
|
+
aria-label="Favoritable Table"
|
|
149
|
+
cells={columns}
|
|
150
|
+
rows={rows}
|
|
151
|
+
>
|
|
152
|
+
<TableHeader />
|
|
153
|
+
<TableBody />
|
|
154
|
+
</Table>
|
|
155
|
+
</div>
|
|
156
|
+
);
|
|
157
|
+
};
|