@patternfly/react-data-view 6.4.0-prerelease.8 → 6.4.0-prerelease.9
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/dist/cjs/DataViewTable/DataViewTable.d.ts +2 -1
- package/dist/cjs/DataViewTableBasic/DataViewTableBasic.d.ts +11 -0
- package/dist/cjs/DataViewTableBasic/DataViewTableBasic.js +46 -6
- package/dist/cjs/DataViewTableBasic/DataViewTableBasic.test.js +47 -9
- package/dist/dynamic-modules.json +1 -0
- package/dist/esm/DataViewTable/DataViewTable.d.ts +2 -1
- package/dist/esm/DataViewTableBasic/DataViewTableBasic.d.ts +11 -0
- package/dist/esm/DataViewTableBasic/DataViewTableBasic.js +48 -8
- package/dist/esm/DataViewTableBasic/DataViewTableBasic.test.js +45 -10
- package/package.json +1 -1
- package/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableExpandableExample.tsx +108 -0
- package/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableInteractiveExample.tsx +148 -0
- package/patternfly-docs/content/extensions/data-view/examples/Table/DataViewTableStickyExample.tsx +90 -0
- package/patternfly-docs/content/extensions/data-view/examples/Table/Table.md +61 -0
- package/src/DataViewTable/DataViewTable.tsx +3 -1
- package/src/DataViewTable/__snapshots__/DataViewTable.test.tsx.snap +7 -7
- package/src/DataViewTableBasic/DataViewTableBasic.test.tsx +54 -12
- package/src/DataViewTableBasic/DataViewTableBasic.tsx +101 -10
- package/src/DataViewTableBasic/__snapshots__/DataViewTableBasic.test.tsx.snap +10 -10
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { FC, ReactNode } from 'react';
|
|
2
2
|
import { TdProps, ThProps, TrProps } from '@patternfly/react-table';
|
|
3
3
|
import { DataViewTableTreeProps } from '../DataViewTableTree';
|
|
4
|
-
import { DataViewTableBasicProps } from '../DataViewTableBasic';
|
|
4
|
+
import { DataViewTableBasicProps, ExpandableContent } from '../DataViewTableBasic';
|
|
5
5
|
import { DataViewThResizableProps } from '../DataViewTh/DataViewTh';
|
|
6
|
+
export type { ExpandableContent };
|
|
6
7
|
export type DataViewTh = ReactNode | {
|
|
7
8
|
/** Table head cell node */
|
|
8
9
|
cell: ReactNode;
|
|
@@ -2,12 +2,19 @@ import { FC } from 'react';
|
|
|
2
2
|
import { TableProps } from '@patternfly/react-table';
|
|
3
3
|
import { DataViewTh, DataViewTr } from '../DataViewTable';
|
|
4
4
|
import { DataViewState } from '../DataView/DataView';
|
|
5
|
+
export interface ExpandableContent {
|
|
6
|
+
rowId: number;
|
|
7
|
+
columnId: number;
|
|
8
|
+
content: React.ReactNode;
|
|
9
|
+
}
|
|
5
10
|
/** extends TableProps */
|
|
6
11
|
export interface DataViewTableBasicProps extends Omit<TableProps, 'onSelect' | 'rows'> {
|
|
7
12
|
/** Columns definition */
|
|
8
13
|
columns: DataViewTh[];
|
|
9
14
|
/** Current page rows */
|
|
10
15
|
rows: DataViewTr[];
|
|
16
|
+
/** Expanded rows content */
|
|
17
|
+
expandedRows?: ExpandableContent[];
|
|
11
18
|
/** Table head states to be displayed when active */
|
|
12
19
|
headStates?: Partial<Record<DataViewState | string, React.ReactNode>>;
|
|
13
20
|
/** Table body states to be displayed when active */
|
|
@@ -16,6 +23,10 @@ export interface DataViewTableBasicProps extends Omit<TableProps, 'onSelect' | '
|
|
|
16
23
|
ouiaId?: string;
|
|
17
24
|
/** @hide Indicates if the table is resizable */
|
|
18
25
|
hasResizableColumns?: boolean;
|
|
26
|
+
/** Toggles expandable */
|
|
27
|
+
isExpandable?: boolean;
|
|
28
|
+
/** Toggles sticky columns and header */
|
|
29
|
+
isSticky?: boolean;
|
|
19
30
|
}
|
|
20
31
|
export declare const DataViewTableBasic: FC<DataViewTableBasicProps>;
|
|
21
32
|
export default DataViewTableBasic;
|
|
@@ -19,14 +19,26 @@ const InternalContext_1 = require("../InternalContext");
|
|
|
19
19
|
const DataViewTableHead_1 = require("../DataViewTableHead");
|
|
20
20
|
const DataViewTable_1 = require("../DataViewTable");
|
|
21
21
|
const DataViewTableBasic = (_a) => {
|
|
22
|
-
var { columns, rows, ouiaId = 'DataViewTableBasic', headStates, bodyStates, hasResizableColumns } = _a, props = __rest(_a, ["columns", "rows", "ouiaId", "headStates", "bodyStates", "hasResizableColumns"]);
|
|
22
|
+
var { columns, rows, expandedRows, ouiaId = 'DataViewTableBasic', headStates, bodyStates, hasResizableColumns, isExpandable = false, isSticky = false } = _a, props = __rest(_a, ["columns", "rows", "expandedRows", "ouiaId", "headStates", "bodyStates", "hasResizableColumns", "isExpandable", "isSticky"]);
|
|
23
23
|
const { selection, activeState, isSelectable } = (0, InternalContext_1.useInternalContext)();
|
|
24
24
|
const { onSelect, isSelected, isSelectDisabled } = selection !== null && selection !== void 0 ? selection : {};
|
|
25
25
|
const activeHeadState = (0, react_1.useMemo)(() => activeState ? headStates === null || headStates === void 0 ? void 0 : headStates[activeState] : undefined, [activeState, headStates]);
|
|
26
26
|
const activeBodyState = (0, react_1.useMemo)(() => activeState ? bodyStates === null || bodyStates === void 0 ? void 0 : bodyStates[activeState] : undefined, [activeState, bodyStates]);
|
|
27
|
+
const [expandedRowsState, setExpandedRowsState] = (0, react_1.useState)({});
|
|
28
|
+
const [expandedColumnIndex, setExpandedColumnIndex] = (0, react_1.useState)({});
|
|
29
|
+
const tableRef = (0, react_1.useRef)(null);
|
|
30
|
+
const needsSeparateTbody = isExpandable;
|
|
27
31
|
const renderedRows = (0, react_1.useMemo)(() => rows.map((row, rowIndex) => {
|
|
28
32
|
const rowIsObject = (0, DataViewTable_1.isDataViewTrObject)(row);
|
|
29
|
-
|
|
33
|
+
const isRowExpanded = expandedRowsState[rowIndex] || false;
|
|
34
|
+
const expandedColIndex = expandedColumnIndex[rowIndex];
|
|
35
|
+
// Get the first cell to extract the row ID
|
|
36
|
+
const rowData = rowIsObject ? row.row : row;
|
|
37
|
+
const firstCell = rowData[0];
|
|
38
|
+
const rowId = (0, DataViewTable_1.isDataViewTdObject)(firstCell) ? firstCell.id : undefined;
|
|
39
|
+
// Find all expandable contents for this row
|
|
40
|
+
const rowExpandableContents = isExpandable ? expandedRows === null || expandedRows === void 0 ? void 0 : expandedRows.filter((content) => content.rowId === rowId) : [];
|
|
41
|
+
const rowContent = ((0, jsx_runtime_1.jsxs)(react_table_1.Tr, Object.assign({ ouiaId: `${ouiaId}-tr-${rowIndex}` }, (rowIsObject && (row === null || row === void 0 ? void 0 : row.props)), { isContentExpanded: isRowExpanded, isControlRow: true, children: [isSelectable && ((0, jsx_runtime_1.jsx)(react_table_1.Td, { select: {
|
|
30
42
|
rowIndex,
|
|
31
43
|
onSelect: (_event, isSelecting) => {
|
|
32
44
|
onSelect === null || onSelect === void 0 ? void 0 : onSelect(isSelecting, rowIsObject ? row : [row]);
|
|
@@ -36,10 +48,38 @@ const DataViewTableBasic = (_a) => {
|
|
|
36
48
|
} }, `select-${rowIndex}`)), (rowIsObject ? row.row : row).map((cell, colIndex) => {
|
|
37
49
|
var _a;
|
|
38
50
|
const cellIsObject = (0, DataViewTable_1.isDataViewTdObject)(cell);
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
51
|
+
const cellExpandableContent = isExpandable ? expandedRows === null || expandedRows === void 0 ? void 0 : expandedRows.find((content) => content.rowId === rowId && content.columnId === colIndex) : undefined;
|
|
52
|
+
return ((0, jsx_runtime_1.jsx)(react_table_1.Td, Object.assign({}, (cellIsObject && ((_a = cell === null || cell === void 0 ? void 0 : cell.props) !== null && _a !== void 0 ? _a : {})), (cellExpandableContent != null && {
|
|
53
|
+
compoundExpand: {
|
|
54
|
+
isExpanded: isRowExpanded && expandedColIndex === colIndex,
|
|
55
|
+
expandId: `expandable-${rowIndex}`,
|
|
56
|
+
onToggle: () => {
|
|
57
|
+
setExpandedRowsState(prev => {
|
|
58
|
+
const isSameColumn = expandedColIndex === colIndex;
|
|
59
|
+
const wasExpanded = prev[rowIndex];
|
|
60
|
+
return Object.assign(Object.assign({}, prev), { [rowIndex]: isSameColumn ? !wasExpanded : true });
|
|
61
|
+
});
|
|
62
|
+
setExpandedColumnIndex(prev => (Object.assign(Object.assign({}, prev), { [rowIndex]: colIndex })));
|
|
63
|
+
},
|
|
64
|
+
rowIndex,
|
|
65
|
+
columnIndex: colIndex
|
|
66
|
+
}
|
|
67
|
+
}), { "data-ouia-component-id": `${ouiaId}-td-${rowIndex}-${colIndex}`, children: cellIsObject ? cell.cell : cell }), colIndex));
|
|
68
|
+
})] }), needsSeparateTbody ? undefined : rowIndex));
|
|
69
|
+
if (needsSeparateTbody) {
|
|
70
|
+
return ((0, jsx_runtime_1.jsxs)(react_table_1.Tbody, { isExpanded: isRowExpanded, children: [rowContent, rowExpandableContents === null || rowExpandableContents === void 0 ? void 0 : rowExpandableContents.map((expandableContent) => ((0, jsx_runtime_1.jsx)(react_table_1.Tr, { isExpanded: isRowExpanded && expandedColIndex === expandableContent.columnId, children: (0, jsx_runtime_1.jsx)(react_table_1.Td, { colSpan: rowData.length + (isSelectable ? 1 : 0), "data-expanded-column-index": expandableContent.columnId, children: (0, jsx_runtime_1.jsx)(react_table_1.ExpandableRowContent, { children: expandableContent.content }) }) }, `expand-${rowIndex}-${expandableContent.columnId}`)))] }, rowIndex));
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
return rowContent;
|
|
74
|
+
}
|
|
75
|
+
}), [rows, isSelectable, isSelected, isSelectDisabled, onSelect, ouiaId, expandedRowsState, expandedColumnIndex, expandedRows, isExpandable, needsSeparateTbody]);
|
|
76
|
+
const bodyContent = activeBodyState || (needsSeparateTbody ? renderedRows : (0, jsx_runtime_1.jsx)(react_table_1.Tbody, { children: renderedRows }));
|
|
77
|
+
if (isSticky) {
|
|
78
|
+
return ((0, jsx_runtime_1.jsx)(react_table_1.OuterScrollContainer, { children: (0, jsx_runtime_1.jsx)(react_table_1.InnerScrollContainer, { children: (0, jsx_runtime_1.jsxs)(react_table_1.Table, Object.assign({ ref: tableRef, "aria-label": "Data table", ouiaId: ouiaId, isExpandable: isExpandable, hasAnimations: true }, props, { isStickyHeader: true, children: [activeHeadState || (0, jsx_runtime_1.jsx)(DataViewTableHead_1.DataViewTableHead, { columns: columns, ouiaId: ouiaId, hasResizableColumns: hasResizableColumns }), bodyContent] })) }) }));
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
return ((0, jsx_runtime_1.jsxs)(react_table_1.Table, Object.assign({ ref: tableRef, "aria-label": "Data table", ouiaId: ouiaId, isExpandable: isExpandable, hasAnimations: true }, props, { children: [activeHeadState || (0, jsx_runtime_1.jsx)(DataViewTableHead_1.DataViewTableHead, { columns: columns, ouiaId: ouiaId, hasResizableColumns: hasResizableColumns }), bodyContent] })));
|
|
82
|
+
}
|
|
43
83
|
};
|
|
44
84
|
exports.DataViewTableBasic = DataViewTableBasic;
|
|
45
85
|
exports.default = exports.DataViewTableBasic;
|
|
@@ -1,21 +1,41 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
2
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
15
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
4
16
|
const react_1 = require("@testing-library/react");
|
|
17
|
+
const user_event_1 = __importDefault(require("@testing-library/user-event"));
|
|
5
18
|
const DataView_1 = require("../DataView");
|
|
6
19
|
const DataViewTableBasic_1 = require("./DataViewTableBasic");
|
|
7
20
|
const repositories = [
|
|
8
|
-
{ name: 'Repository one', branches: 'Branch one', prs: 'Pull request one', workspaces: 'Workspace one', lastCommit: 'Timestamp one' },
|
|
9
|
-
{ name: 'Repository two', branches: 'Branch two', prs: 'Pull request two', workspaces: 'Workspace two', lastCommit: 'Timestamp two' },
|
|
10
|
-
{ name: 'Repository three', branches: 'Branch three', prs: 'Pull request three', workspaces: 'Workspace three', lastCommit: 'Timestamp three' },
|
|
11
|
-
{ name: 'Repository four', branches: 'Branch four', prs: 'Pull request four', workspaces: 'Workspace four', lastCommit: 'Timestamp four' },
|
|
12
|
-
{ name: 'Repository five', branches: 'Branch five', prs: 'Pull request five', workspaces: 'Workspace five', lastCommit: 'Timestamp five' },
|
|
13
|
-
{ name: 'Repository six', branches: 'Branch six', prs: 'Pull request six', workspaces: 'Workspace six', lastCommit: 'Timestamp six' }
|
|
21
|
+
{ id: 1, name: 'Repository one', branches: 'Branch one', prs: 'Pull request one', workspaces: 'Workspace one', lastCommit: 'Timestamp one' },
|
|
22
|
+
{ id: 2, name: 'Repository two', branches: 'Branch two', prs: 'Pull request two', workspaces: 'Workspace two', lastCommit: 'Timestamp two' },
|
|
23
|
+
{ id: 3, name: 'Repository three', branches: 'Branch three', prs: 'Pull request three', workspaces: 'Workspace three', lastCommit: 'Timestamp three' },
|
|
24
|
+
{ id: 4, name: 'Repository four', branches: 'Branch four', prs: 'Pull request four', workspaces: 'Workspace four', lastCommit: 'Timestamp four' },
|
|
25
|
+
{ id: 5, name: 'Repository five', branches: 'Branch five', prs: 'Pull request five', workspaces: 'Workspace five', lastCommit: 'Timestamp five' },
|
|
26
|
+
{ id: 6, name: 'Repository six', branches: 'Branch six', prs: 'Pull request six', workspaces: 'Workspace six', lastCommit: 'Timestamp six' }
|
|
14
27
|
];
|
|
15
|
-
const rows = repositories.map(
|
|
16
|
-
|
|
17
|
-
|
|
28
|
+
const rows = repositories.map(({ id, name, branches, prs, workspaces, lastCommit }) => [
|
|
29
|
+
{ id, cell: name },
|
|
30
|
+
branches,
|
|
31
|
+
prs,
|
|
32
|
+
workspaces,
|
|
33
|
+
lastCommit
|
|
34
|
+
]);
|
|
18
35
|
const columns = ['Repositories', 'Branches', 'Pull requests', 'Workspaces', 'Last commit'];
|
|
36
|
+
const expandableContents = [
|
|
37
|
+
{ rowId: 1, columnId: 1, content: (0, jsx_runtime_1.jsx)("div", { children: "Branch details for Repository one" }) },
|
|
38
|
+
];
|
|
19
39
|
const ouiaId = 'TableExample';
|
|
20
40
|
describe('DataViewTable component', () => {
|
|
21
41
|
test('should render correctly', () => {
|
|
@@ -34,4 +54,22 @@ describe('DataViewTable component', () => {
|
|
|
34
54
|
const { container } = (0, react_1.render)((0, jsx_runtime_1.jsx)(DataView_1.DataView, { activeState: "loading", children: (0, jsx_runtime_1.jsx)(DataViewTableBasic_1.DataViewTableBasic, { "aria-label": 'Repositories table', ouiaId: ouiaId, columns: columns, bodyStates: { loading: "Data is loading" }, rows: [] }) }));
|
|
35
55
|
expect(container).toMatchSnapshot();
|
|
36
56
|
});
|
|
57
|
+
test('when isExpandable cell should be clickable and expandable', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
58
|
+
var _a, _b, _c;
|
|
59
|
+
const user = user_event_1.default.setup();
|
|
60
|
+
(0, react_1.render)((0, jsx_runtime_1.jsx)(DataViewTableBasic_1.DataViewTableBasic, { "aria-label": 'Repositories table', ouiaId: ouiaId, columns: columns, rows: rows, isExpandable: true, expandedRows: expandableContents }));
|
|
61
|
+
// Initially, expandable content is rendered but should be hidden (not visible)
|
|
62
|
+
const initialBranchContent = react_1.screen.getByText('Branch details for Repository one');
|
|
63
|
+
expect((_a = initialBranchContent.closest('tr')) === null || _a === void 0 ? void 0 : _a.classList.contains('pf-m-expanded')).toBeFalsy();
|
|
64
|
+
// Find the first expandable button by ID
|
|
65
|
+
const branchExpandButton = document.getElementById('expandable-0-0-1');
|
|
66
|
+
expect(branchExpandButton).toBeTruthy();
|
|
67
|
+
// Verify the button is in the cell with "Branch one" text
|
|
68
|
+
expect((_b = branchExpandButton === null || branchExpandButton === void 0 ? void 0 : branchExpandButton.closest('td')) === null || _b === void 0 ? void 0 : _b.textContent).toContain('Branch one');
|
|
69
|
+
// Click the expand button for Branches column
|
|
70
|
+
yield user.click(branchExpandButton);
|
|
71
|
+
// After clicking, the expandable content should be visible
|
|
72
|
+
const branchContent = react_1.screen.getByText('Branch details for Repository one');
|
|
73
|
+
expect((_c = branchContent.closest('tr')) === null || _c === void 0 ? void 0 : _c.classList.contains('pf-m-expanded')).toBeTruthy();
|
|
74
|
+
}));
|
|
37
75
|
});
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
"DataViewTrObject": "dist/dynamic/DataViewTable",
|
|
37
37
|
"DataViewTrTree": "dist/dynamic/DataViewTable",
|
|
38
38
|
"EventTypes": "dist/dynamic/DataViewEventsContext",
|
|
39
|
+
"ExpandableContent": "dist/dynamic/DataViewTableBasic",
|
|
39
40
|
"InternalContext": "dist/dynamic/InternalContext",
|
|
40
41
|
"InternalContextProps": "dist/dynamic/InternalContext",
|
|
41
42
|
"InternalContextProvider": "dist/dynamic/InternalContext",
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { FC, ReactNode } from 'react';
|
|
2
2
|
import { TdProps, ThProps, TrProps } from '@patternfly/react-table';
|
|
3
3
|
import { DataViewTableTreeProps } from '../DataViewTableTree';
|
|
4
|
-
import { DataViewTableBasicProps } from '../DataViewTableBasic';
|
|
4
|
+
import { DataViewTableBasicProps, ExpandableContent } from '../DataViewTableBasic';
|
|
5
5
|
import { DataViewThResizableProps } from '../DataViewTh/DataViewTh';
|
|
6
|
+
export type { ExpandableContent };
|
|
6
7
|
export type DataViewTh = ReactNode | {
|
|
7
8
|
/** Table head cell node */
|
|
8
9
|
cell: ReactNode;
|
|
@@ -2,12 +2,19 @@ import { FC } from 'react';
|
|
|
2
2
|
import { TableProps } from '@patternfly/react-table';
|
|
3
3
|
import { DataViewTh, DataViewTr } from '../DataViewTable';
|
|
4
4
|
import { DataViewState } from '../DataView/DataView';
|
|
5
|
+
export interface ExpandableContent {
|
|
6
|
+
rowId: number;
|
|
7
|
+
columnId: number;
|
|
8
|
+
content: React.ReactNode;
|
|
9
|
+
}
|
|
5
10
|
/** extends TableProps */
|
|
6
11
|
export interface DataViewTableBasicProps extends Omit<TableProps, 'onSelect' | 'rows'> {
|
|
7
12
|
/** Columns definition */
|
|
8
13
|
columns: DataViewTh[];
|
|
9
14
|
/** Current page rows */
|
|
10
15
|
rows: DataViewTr[];
|
|
16
|
+
/** Expanded rows content */
|
|
17
|
+
expandedRows?: ExpandableContent[];
|
|
11
18
|
/** Table head states to be displayed when active */
|
|
12
19
|
headStates?: Partial<Record<DataViewState | string, React.ReactNode>>;
|
|
13
20
|
/** Table body states to be displayed when active */
|
|
@@ -16,6 +23,10 @@ export interface DataViewTableBasicProps extends Omit<TableProps, 'onSelect' | '
|
|
|
16
23
|
ouiaId?: string;
|
|
17
24
|
/** @hide Indicates if the table is resizable */
|
|
18
25
|
hasResizableColumns?: boolean;
|
|
26
|
+
/** Toggles expandable */
|
|
27
|
+
isExpandable?: boolean;
|
|
28
|
+
/** Toggles sticky columns and header */
|
|
29
|
+
isSticky?: boolean;
|
|
19
30
|
}
|
|
20
31
|
export declare const DataViewTableBasic: FC<DataViewTableBasicProps>;
|
|
21
32
|
export default DataViewTableBasic;
|
|
@@ -10,20 +10,32 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
10
10
|
return t;
|
|
11
11
|
};
|
|
12
12
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
13
|
-
import { useMemo } from 'react';
|
|
14
|
-
import { Table, Tbody, Td, Tr, } from '@patternfly/react-table';
|
|
13
|
+
import { useMemo, useState, useRef } from 'react';
|
|
14
|
+
import { ExpandableRowContent, InnerScrollContainer, OuterScrollContainer, Table, Tbody, Td, Tr, } from '@patternfly/react-table';
|
|
15
15
|
import { useInternalContext } from '../InternalContext';
|
|
16
16
|
import { DataViewTableHead } from '../DataViewTableHead';
|
|
17
17
|
import { isDataViewTdObject, isDataViewTrObject } from '../DataViewTable';
|
|
18
18
|
export const DataViewTableBasic = (_a) => {
|
|
19
|
-
var { columns, rows, ouiaId = 'DataViewTableBasic', headStates, bodyStates, hasResizableColumns } = _a, props = __rest(_a, ["columns", "rows", "ouiaId", "headStates", "bodyStates", "hasResizableColumns"]);
|
|
19
|
+
var { columns, rows, expandedRows, ouiaId = 'DataViewTableBasic', headStates, bodyStates, hasResizableColumns, isExpandable = false, isSticky = false } = _a, props = __rest(_a, ["columns", "rows", "expandedRows", "ouiaId", "headStates", "bodyStates", "hasResizableColumns", "isExpandable", "isSticky"]);
|
|
20
20
|
const { selection, activeState, isSelectable } = useInternalContext();
|
|
21
21
|
const { onSelect, isSelected, isSelectDisabled } = selection !== null && selection !== void 0 ? selection : {};
|
|
22
22
|
const activeHeadState = useMemo(() => activeState ? headStates === null || headStates === void 0 ? void 0 : headStates[activeState] : undefined, [activeState, headStates]);
|
|
23
23
|
const activeBodyState = useMemo(() => activeState ? bodyStates === null || bodyStates === void 0 ? void 0 : bodyStates[activeState] : undefined, [activeState, bodyStates]);
|
|
24
|
+
const [expandedRowsState, setExpandedRowsState] = useState({});
|
|
25
|
+
const [expandedColumnIndex, setExpandedColumnIndex] = useState({});
|
|
26
|
+
const tableRef = useRef(null);
|
|
27
|
+
const needsSeparateTbody = isExpandable;
|
|
24
28
|
const renderedRows = useMemo(() => rows.map((row, rowIndex) => {
|
|
25
29
|
const rowIsObject = isDataViewTrObject(row);
|
|
26
|
-
|
|
30
|
+
const isRowExpanded = expandedRowsState[rowIndex] || false;
|
|
31
|
+
const expandedColIndex = expandedColumnIndex[rowIndex];
|
|
32
|
+
// Get the first cell to extract the row ID
|
|
33
|
+
const rowData = rowIsObject ? row.row : row;
|
|
34
|
+
const firstCell = rowData[0];
|
|
35
|
+
const rowId = isDataViewTdObject(firstCell) ? firstCell.id : undefined;
|
|
36
|
+
// Find all expandable contents for this row
|
|
37
|
+
const rowExpandableContents = isExpandable ? expandedRows === null || expandedRows === void 0 ? void 0 : expandedRows.filter((content) => content.rowId === rowId) : [];
|
|
38
|
+
const rowContent = (_jsxs(Tr, Object.assign({ ouiaId: `${ouiaId}-tr-${rowIndex}` }, (rowIsObject && (row === null || row === void 0 ? void 0 : row.props)), { isContentExpanded: isRowExpanded, isControlRow: true, children: [isSelectable && (_jsx(Td, { select: {
|
|
27
39
|
rowIndex,
|
|
28
40
|
onSelect: (_event, isSelecting) => {
|
|
29
41
|
onSelect === null || onSelect === void 0 ? void 0 : onSelect(isSelecting, rowIsObject ? row : [row]);
|
|
@@ -33,9 +45,37 @@ export const DataViewTableBasic = (_a) => {
|
|
|
33
45
|
} }, `select-${rowIndex}`)), (rowIsObject ? row.row : row).map((cell, colIndex) => {
|
|
34
46
|
var _a;
|
|
35
47
|
const cellIsObject = isDataViewTdObject(cell);
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
48
|
+
const cellExpandableContent = isExpandable ? expandedRows === null || expandedRows === void 0 ? void 0 : expandedRows.find((content) => content.rowId === rowId && content.columnId === colIndex) : undefined;
|
|
49
|
+
return (_jsx(Td, Object.assign({}, (cellIsObject && ((_a = cell === null || cell === void 0 ? void 0 : cell.props) !== null && _a !== void 0 ? _a : {})), (cellExpandableContent != null && {
|
|
50
|
+
compoundExpand: {
|
|
51
|
+
isExpanded: isRowExpanded && expandedColIndex === colIndex,
|
|
52
|
+
expandId: `expandable-${rowIndex}`,
|
|
53
|
+
onToggle: () => {
|
|
54
|
+
setExpandedRowsState(prev => {
|
|
55
|
+
const isSameColumn = expandedColIndex === colIndex;
|
|
56
|
+
const wasExpanded = prev[rowIndex];
|
|
57
|
+
return Object.assign(Object.assign({}, prev), { [rowIndex]: isSameColumn ? !wasExpanded : true });
|
|
58
|
+
});
|
|
59
|
+
setExpandedColumnIndex(prev => (Object.assign(Object.assign({}, prev), { [rowIndex]: colIndex })));
|
|
60
|
+
},
|
|
61
|
+
rowIndex,
|
|
62
|
+
columnIndex: colIndex
|
|
63
|
+
}
|
|
64
|
+
}), { "data-ouia-component-id": `${ouiaId}-td-${rowIndex}-${colIndex}`, children: cellIsObject ? cell.cell : cell }), colIndex));
|
|
65
|
+
})] }), needsSeparateTbody ? undefined : rowIndex));
|
|
66
|
+
if (needsSeparateTbody) {
|
|
67
|
+
return (_jsxs(Tbody, { isExpanded: isRowExpanded, children: [rowContent, rowExpandableContents === null || rowExpandableContents === void 0 ? void 0 : rowExpandableContents.map((expandableContent) => (_jsx(Tr, { isExpanded: isRowExpanded && expandedColIndex === expandableContent.columnId, children: _jsx(Td, { colSpan: rowData.length + (isSelectable ? 1 : 0), "data-expanded-column-index": expandableContent.columnId, children: _jsx(ExpandableRowContent, { children: expandableContent.content }) }) }, `expand-${rowIndex}-${expandableContent.columnId}`)))] }, rowIndex));
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
return rowContent;
|
|
71
|
+
}
|
|
72
|
+
}), [rows, isSelectable, isSelected, isSelectDisabled, onSelect, ouiaId, expandedRowsState, expandedColumnIndex, expandedRows, isExpandable, needsSeparateTbody]);
|
|
73
|
+
const bodyContent = activeBodyState || (needsSeparateTbody ? renderedRows : _jsx(Tbody, { children: renderedRows }));
|
|
74
|
+
if (isSticky) {
|
|
75
|
+
return (_jsx(OuterScrollContainer, { children: _jsx(InnerScrollContainer, { children: _jsxs(Table, Object.assign({ ref: tableRef, "aria-label": "Data table", ouiaId: ouiaId, isExpandable: isExpandable, hasAnimations: true }, props, { isStickyHeader: true, children: [activeHeadState || _jsx(DataViewTableHead, { columns: columns, ouiaId: ouiaId, hasResizableColumns: hasResizableColumns }), bodyContent] })) }) }));
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
return (_jsxs(Table, Object.assign({ ref: tableRef, "aria-label": "Data table", ouiaId: ouiaId, isExpandable: isExpandable, hasAnimations: true }, props, { children: [activeHeadState || _jsx(DataViewTableHead, { columns: columns, ouiaId: ouiaId, hasResizableColumns: hasResizableColumns }), bodyContent] })));
|
|
79
|
+
}
|
|
40
80
|
};
|
|
41
81
|
export default DataViewTableBasic;
|
|
@@ -1,19 +1,36 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
1
10
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { render } from '@testing-library/react';
|
|
11
|
+
import { render, screen } from '@testing-library/react';
|
|
12
|
+
import userEvent from '@testing-library/user-event';
|
|
3
13
|
import { DataView } from '../DataView';
|
|
4
14
|
import { DataViewTableBasic } from './DataViewTableBasic';
|
|
5
15
|
const repositories = [
|
|
6
|
-
{ name: 'Repository one', branches: 'Branch one', prs: 'Pull request one', workspaces: 'Workspace one', lastCommit: 'Timestamp one' },
|
|
7
|
-
{ name: 'Repository two', branches: 'Branch two', prs: 'Pull request two', workspaces: 'Workspace two', lastCommit: 'Timestamp two' },
|
|
8
|
-
{ name: 'Repository three', branches: 'Branch three', prs: 'Pull request three', workspaces: 'Workspace three', lastCommit: 'Timestamp three' },
|
|
9
|
-
{ name: 'Repository four', branches: 'Branch four', prs: 'Pull request four', workspaces: 'Workspace four', lastCommit: 'Timestamp four' },
|
|
10
|
-
{ name: 'Repository five', branches: 'Branch five', prs: 'Pull request five', workspaces: 'Workspace five', lastCommit: 'Timestamp five' },
|
|
11
|
-
{ name: 'Repository six', branches: 'Branch six', prs: 'Pull request six', workspaces: 'Workspace six', lastCommit: 'Timestamp six' }
|
|
16
|
+
{ id: 1, name: 'Repository one', branches: 'Branch one', prs: 'Pull request one', workspaces: 'Workspace one', lastCommit: 'Timestamp one' },
|
|
17
|
+
{ id: 2, name: 'Repository two', branches: 'Branch two', prs: 'Pull request two', workspaces: 'Workspace two', lastCommit: 'Timestamp two' },
|
|
18
|
+
{ id: 3, name: 'Repository three', branches: 'Branch three', prs: 'Pull request three', workspaces: 'Workspace three', lastCommit: 'Timestamp three' },
|
|
19
|
+
{ id: 4, name: 'Repository four', branches: 'Branch four', prs: 'Pull request four', workspaces: 'Workspace four', lastCommit: 'Timestamp four' },
|
|
20
|
+
{ id: 5, name: 'Repository five', branches: 'Branch five', prs: 'Pull request five', workspaces: 'Workspace five', lastCommit: 'Timestamp five' },
|
|
21
|
+
{ id: 6, name: 'Repository six', branches: 'Branch six', prs: 'Pull request six', workspaces: 'Workspace six', lastCommit: 'Timestamp six' }
|
|
12
22
|
];
|
|
13
|
-
const rows = repositories.map(
|
|
14
|
-
|
|
15
|
-
|
|
23
|
+
const rows = repositories.map(({ id, name, branches, prs, workspaces, lastCommit }) => [
|
|
24
|
+
{ id, cell: name },
|
|
25
|
+
branches,
|
|
26
|
+
prs,
|
|
27
|
+
workspaces,
|
|
28
|
+
lastCommit
|
|
29
|
+
]);
|
|
16
30
|
const columns = ['Repositories', 'Branches', 'Pull requests', 'Workspaces', 'Last commit'];
|
|
31
|
+
const expandableContents = [
|
|
32
|
+
{ rowId: 1, columnId: 1, content: _jsx("div", { children: "Branch details for Repository one" }) },
|
|
33
|
+
];
|
|
17
34
|
const ouiaId = 'TableExample';
|
|
18
35
|
describe('DataViewTable component', () => {
|
|
19
36
|
test('should render correctly', () => {
|
|
@@ -32,4 +49,22 @@ describe('DataViewTable component', () => {
|
|
|
32
49
|
const { container } = render(_jsx(DataView, { activeState: "loading", children: _jsx(DataViewTableBasic, { "aria-label": 'Repositories table', ouiaId: ouiaId, columns: columns, bodyStates: { loading: "Data is loading" }, rows: [] }) }));
|
|
33
50
|
expect(container).toMatchSnapshot();
|
|
34
51
|
});
|
|
52
|
+
test('when isExpandable cell should be clickable and expandable', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
53
|
+
var _a, _b, _c;
|
|
54
|
+
const user = userEvent.setup();
|
|
55
|
+
render(_jsx(DataViewTableBasic, { "aria-label": 'Repositories table', ouiaId: ouiaId, columns: columns, rows: rows, isExpandable: true, expandedRows: expandableContents }));
|
|
56
|
+
// Initially, expandable content is rendered but should be hidden (not visible)
|
|
57
|
+
const initialBranchContent = screen.getByText('Branch details for Repository one');
|
|
58
|
+
expect((_a = initialBranchContent.closest('tr')) === null || _a === void 0 ? void 0 : _a.classList.contains('pf-m-expanded')).toBeFalsy();
|
|
59
|
+
// Find the first expandable button by ID
|
|
60
|
+
const branchExpandButton = document.getElementById('expandable-0-0-1');
|
|
61
|
+
expect(branchExpandButton).toBeTruthy();
|
|
62
|
+
// Verify the button is in the cell with "Branch one" text
|
|
63
|
+
expect((_b = branchExpandButton === null || branchExpandButton === void 0 ? void 0 : branchExpandButton.closest('td')) === null || _b === void 0 ? void 0 : _b.textContent).toContain('Branch one');
|
|
64
|
+
// Click the expand button for Branches column
|
|
65
|
+
yield user.click(branchExpandButton);
|
|
66
|
+
// After clicking, the expandable content should be visible
|
|
67
|
+
const branchContent = screen.getByText('Branch details for Repository one');
|
|
68
|
+
expect((_c = branchContent.closest('tr')) === null || _c === void 0 ? void 0 : _c.classList.contains('pf-m-expanded')).toBeTruthy();
|
|
69
|
+
}));
|
|
35
70
|
});
|
package/package.json
CHANGED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { FunctionComponent } from 'react';
|
|
2
|
+
import { DataViewTable, DataViewTr, DataViewTh, ExpandableContent } from '@patternfly/react-data-view/dist/dynamic/DataViewTable';
|
|
3
|
+
import { ExclamationCircleIcon } from '@patternfly/react-icons';
|
|
4
|
+
import { Button } from '@patternfly/react-core';
|
|
5
|
+
import { ActionsColumn } from '@patternfly/react-table';
|
|
6
|
+
|
|
7
|
+
interface Repository {
|
|
8
|
+
id: number;
|
|
9
|
+
name: string;
|
|
10
|
+
branches: string | null;
|
|
11
|
+
prs: string | null;
|
|
12
|
+
workspaces: string;
|
|
13
|
+
lastCommit: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const expandableContents: ExpandableContent[] = [
|
|
17
|
+
// Row 1 - Repository one
|
|
18
|
+
{ rowId: 1, columnId: 3, content: <div><strong>PR Details:</strong> 3 open PRs, 45 merged this month, avg review time: 2 days</div> },
|
|
19
|
+
{ rowId: 1, columnId: 5, content: <div><strong>Commit Info:</strong> Author: John Doe, Message: "Fix critical authentication bug", SHA: a1b2c3d</div> },
|
|
20
|
+
|
|
21
|
+
// Row 2 - Repository two
|
|
22
|
+
{ rowId: 2, columnId: 2, content: <div><strong>Branch Details:</strong> 8 active branches, main, staging, feature/api-v2, feature/dashboard</div> },
|
|
23
|
+
{ rowId: 2, columnId: 3, content: <div><strong>PR Details:</strong> 5 open PRs, 120 merged this month, avg review time: 1.5 days</div> },
|
|
24
|
+
{ rowId: 2, columnId: 4, content: <div><strong>Workspace Info:</strong> Development env, 3 active deployments, last updated 30 mins ago</div> },
|
|
25
|
+
{ rowId: 2, columnId: 5, content: <div><strong>Commit Info:</strong> Author: Jane Smith, Message: "Add new API endpoints", SHA: x9y8z7w</div> },
|
|
26
|
+
|
|
27
|
+
// Row 3 - Repository three
|
|
28
|
+
{ rowId: 3, columnId: 2, content: <div><strong>Branch Details:</strong> 12 active branches including main, develop, multiple feature branches</div> },
|
|
29
|
+
{ rowId: 3, columnId: 3, content: <div><strong>PR Details:</strong> 8 open PRs, 200 merged this month, avg review time: 3 days</div> },
|
|
30
|
+
{ rowId: 3, columnId: 4, content: <div><strong>Workspace Info:</strong> Staging env, 10 active deployments, last updated 1 day ago</div> },
|
|
31
|
+
{ rowId: 3, columnId: 5, content: <div><strong>Commit Info:</strong> Author: Bob Johnson, Message: "Refactor core modules", SHA: p0o9i8u</div> },
|
|
32
|
+
|
|
33
|
+
// Row 4 - Repository four
|
|
34
|
+
{ rowId: 4, columnId: 2, content: <div><strong>Branch Details:</strong> 6 active branches, focusing on microservices architecture</div> },
|
|
35
|
+
{ rowId: 4, columnId: 3, content: <div><strong>PR Details:</strong> 2 open PRs, 90 merged this month, avg review time: 2.5 days</div> },
|
|
36
|
+
{ rowId: 4, columnId: 4, content: <div><strong>Workspace Info:</strong> QA env, 7 active deployments, automated testing enabled</div> },
|
|
37
|
+
{ rowId: 4, columnId: 5, content: <div><strong>Commit Info:</strong> Author: Alice Williams, Message: "Update dependencies", SHA: m5n4b3v</div> },
|
|
38
|
+
|
|
39
|
+
// Row 5 - Repository five
|
|
40
|
+
{ rowId: 5, columnId: 2, content: <div><strong>Branch Details:</strong> 4 active branches, clean branch strategy</div> },
|
|
41
|
+
{ rowId: 5, columnId: 3, content: <div><strong>PR Details:</strong> 6 open PRs, 75 merged this month, avg review time: 1 day</div> },
|
|
42
|
+
{ rowId: 5, columnId: 4, content: <div><strong>Workspace Info:</strong> Pre-production env, CI/CD pipeline configured</div> },
|
|
43
|
+
{ rowId: 5, columnId: 5, content: <div><strong>Commit Info:</strong> Author: Charlie Brown, Message: "Implement dark mode", SHA: q2w3e4r</div> },
|
|
44
|
+
|
|
45
|
+
// Row 6 - Repository six
|
|
46
|
+
{ rowId: 6, columnId: 2, content: <div><strong>Branch Details:</strong> 15 active branches, complex branching model</div> },
|
|
47
|
+
{ rowId: 6, columnId: 3, content: <div><strong>PR Details:</strong> 10 open PRs, 250 merged this month, avg review time: 4 days</div> },
|
|
48
|
+
{ rowId: 6, columnId: 4, content: <div><strong>Workspace Info:</strong> Multi-region deployment, high availability setup</div> },
|
|
49
|
+
{ rowId: 6, columnId: 5, content: <div><strong>Commit Info:</strong> Author: David Lee, Message: "Security patches applied", SHA: t6y7u8i</div> },
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
const repositories: Repository[] = [
|
|
53
|
+
{ id: 1, name: 'Repository one', branches: 'Branch one', prs: 'Pull request one', workspaces: 'Workspace one', lastCommit: 'Timestamp one' },
|
|
54
|
+
{ id: 2, name: 'Repository two', branches: 'Branch two', prs: 'Pull request two', workspaces: 'Workspace two', lastCommit: 'Timestamp two' },
|
|
55
|
+
{ id: 3, name: 'Repository three', branches: 'Branch three', prs: 'Pull request three', workspaces: 'Workspace three', lastCommit: 'Timestamp three' },
|
|
56
|
+
{ id: 4, name: 'Repository four', branches: 'Branch four', prs: 'Pull request four', workspaces: 'Workspace four', lastCommit: 'Timestamp four' },
|
|
57
|
+
{ id: 5, name: 'Repository five', branches: 'Branch five', prs: 'Pull request five', workspaces: 'Workspace five', lastCommit: 'Timestamp five' },
|
|
58
|
+
{ id: 6, name: 'Repository six', branches: 'Branch six', prs: 'Pull request six', workspaces: 'Workspace six', lastCommit: 'Timestamp six' }
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
const rowActions = [
|
|
62
|
+
{
|
|
63
|
+
title: 'Some action',
|
|
64
|
+
onClick: () => console.log('clicked on Some action') // eslint-disable-line no-console
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
title: <div>Another action</div>,
|
|
68
|
+
onClick: () => console.log('clicked on Another action') // eslint-disable-line no-console
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
isSeparator: true
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
title: 'Third action',
|
|
75
|
+
onClick: () => console.log('clicked on Third action') // eslint-disable-line no-console
|
|
76
|
+
}
|
|
77
|
+
];
|
|
78
|
+
|
|
79
|
+
const rows: DataViewTr[] = repositories.map(({ id, name, branches, prs, workspaces, lastCommit }) => [
|
|
80
|
+
{
|
|
81
|
+
id,
|
|
82
|
+
cell: workspaces,
|
|
83
|
+
props: {
|
|
84
|
+
favorites: { isFavorited: true }
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
{ cell: <Button href='#' variant='link' isInline>{name}</Button> },
|
|
88
|
+
branches,
|
|
89
|
+
prs,
|
|
90
|
+
workspaces,
|
|
91
|
+
lastCommit,
|
|
92
|
+
{ cell: <ActionsColumn items={rowActions}/>, props: { isActionCell: true } },
|
|
93
|
+
]);
|
|
94
|
+
|
|
95
|
+
const columns: DataViewTh[] = [
|
|
96
|
+
null,
|
|
97
|
+
'Repositories',
|
|
98
|
+
{ cell: <>Branches<ExclamationCircleIcon className='pf-v6-u-ml-sm' color="var(--pf-t--global--color--status--danger--default)"/></> },
|
|
99
|
+
'Pull requests',
|
|
100
|
+
{ cell: 'Workspaces', props: { info: { tooltip: 'More information' }, isStickyColumn: true } },
|
|
101
|
+
{ cell: 'Last commit', props: { sort: { sortBy: {}, columnIndex: 4 } } },
|
|
102
|
+
];
|
|
103
|
+
|
|
104
|
+
const ouiaId = 'TableExample';
|
|
105
|
+
|
|
106
|
+
export const ExpandableExample: FunctionComponent = () => (
|
|
107
|
+
<DataViewTable aria-label='Repositories table' ouiaId={ouiaId} columns={columns} rows={rows} expandedRows={expandableContents} isExpandable={true}/>
|
|
108
|
+
);
|