Skip to content

Commit ad99460

Browse files
committed
feat(DataView): add support for resizable columns
1 parent 25d57cb commit ad99460

File tree

6 files changed

+521
-64
lines changed

6 files changed

+521
-64
lines changed
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import { FunctionComponent } from 'react';
2+
import { DataViewTable, DataViewTr, DataViewTh } from '@patternfly/react-data-view/dist/dynamic/DataViewTable';
3+
import { Button } from '@patternfly/react-core';
4+
import { ActionsColumn } from '@patternfly/react-table';
5+
6+
interface Repository {
7+
id: number;
8+
name: string;
9+
branches: string | null;
10+
prs: string | null;
11+
workspaces: string;
12+
lastCommit: string;
13+
}
14+
15+
const repositories: Repository[] = [
16+
{
17+
id: 1,
18+
name: 'Repository one',
19+
branches: 'Branch one',
20+
prs: 'Pull request one',
21+
workspaces: 'Workspace one',
22+
lastCommit: 'Timestamp one'
23+
},
24+
{
25+
id: 2,
26+
name: 'Repository two',
27+
branches: 'Branch two',
28+
prs: 'Pull request two',
29+
workspaces: 'Workspace two',
30+
lastCommit: 'Timestamp two'
31+
},
32+
{
33+
id: 3,
34+
name: 'Repository three',
35+
branches: 'Branch three',
36+
prs: 'Pull request three',
37+
workspaces: 'Workspace three',
38+
lastCommit: 'Timestamp three'
39+
},
40+
{
41+
id: 4,
42+
name: 'Repository four',
43+
branches: 'Branch four',
44+
prs: 'Pull request four',
45+
workspaces: 'Workspace four',
46+
lastCommit: 'Timestamp four'
47+
},
48+
{
49+
id: 5,
50+
name: 'Repository five',
51+
branches: 'Branch five',
52+
prs: 'Pull request five',
53+
workspaces: 'Workspace five',
54+
lastCommit: 'Timestamp five'
55+
},
56+
{
57+
id: 6,
58+
name: 'Repository six',
59+
branches: 'Branch six',
60+
prs: 'Pull request six',
61+
workspaces: 'Workspace six',
62+
lastCommit: 'Timestamp six'
63+
}
64+
];
65+
66+
const rowActions = [
67+
{
68+
title: 'Some action',
69+
onClick: () => console.log('clicked on Some action') // eslint-disable-line no-console
70+
},
71+
{
72+
title: <div>Another action</div>,
73+
onClick: () => console.log('clicked on Another action') // eslint-disable-line no-console
74+
},
75+
{
76+
isSeparator: true
77+
},
78+
{
79+
title: 'Third action',
80+
onClick: () => console.log('clicked on Third action') // eslint-disable-line no-console
81+
}
82+
];
83+
84+
// you can also pass props to Tr by returning { row: DataViewTd[], props: TrProps } }
85+
const rows: DataViewTr[] = repositories.map(({ id, name, branches, prs, workspaces, lastCommit }) => [
86+
{ id, cell: workspaces, props: { favorites: { isFavorited: true } } },
87+
{
88+
cell: (
89+
<Button href="#" variant="link" isInline>
90+
{name}
91+
</Button>
92+
)
93+
},
94+
branches,
95+
prs,
96+
workspaces,
97+
lastCommit,
98+
{ cell: <ActionsColumn items={rowActions} />, props: { isActionCell: true } }
99+
]);
100+
101+
const columns: DataViewTh[] = [
102+
null,
103+
'Repositories',
104+
{
105+
cell: 'col',
106+
resizableProps: { isResizable: true, onResize: (e, width) => console.log('resized width: ', width) }
107+
},
108+
'Pull requests',
109+
{ cell: 'Workspaces', props: { info: { tooltip: 'More information' } } },
110+
{ cell: 'Last commit', props: { sort: { sortBy: {}, columnIndex: 4 } } }
111+
];
112+
113+
const ouiaId = 'TableExample';
114+
115+
export const ResizableColumnsExample: FunctionComponent = () => (
116+
<DataViewTable isResizable aria-label="Repositories table" ouiaId={ouiaId} columns={columns} rows={rows} />
117+
);

packages/module/patternfly-docs/content/extensions/data-view/examples/Table/Table.md

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,18 @@ source: react
1212
# If you use typescript, the name of the interface to display props for
1313
# These are found through the sourceProps function provided in patternfly-docs.source.js
1414
sortValue: 3
15-
propComponents: ['DataViewTableBasic', 'DataViewTableTree', 'DataViewTrTree', 'DataViewTrObject']
15+
propComponents:
16+
[
17+
'DataViewTableBasic',
18+
'DataViewTableTree',
19+
'DataViewTrTree',
20+
'DataViewTrObject',
21+
'DataViewTh',
22+
'DataViewThResizableProps'
23+
]
1624
sourceLink: https://github.com/patternfly/react-data-view/blob/main/packages/module/patternfly-docs/content/extensions/data-view/examples/Table/Table.md
1725
---
26+
1827
import { FunctionComponent, useMemo } from 'react';
1928
import { BrowserRouter, useSearchParams } from 'react-router-dom';
2029
import { Button, EmptyState, EmptyStateActions, EmptyStateBody, EmptyStateFooter } from '@patternfly/react-core';
@@ -28,7 +37,9 @@ import { DataView, DataViewState } from '@patternfly/react-data-view/dist/dynami
2837
The **data view table** component renders your data into columns and rows within a [PatternFly table](/components/table) component. You can easily customize and configure the table with these additional [data view components and props](/extensions/data-view/table#props).
2938

3039
## Configuring rows and columns
40+
3141
To define rows and columns for your table, use these props:
42+
3243
- `columns`: Defines the column heads of the table. Each item in the array can be a `ReactNode` for simple heads, or an object with the following properties:
3344
- `cell`: Content to display in the column head.
3445
- `props` (optional): (`ThProps`) to pass to the `<Th>` component, such as `width`, `sort`, and other table head cell properties.
@@ -42,20 +53,35 @@ It is also possible to disable row selection using the `isSelectDisabled` functi
4253
If you want to have all expandable nodes open on initial load pass the `expandAll` prop to the DataViewTable component
4354

4455
### Table example
56+
4557
```js file="./DataViewTableExample.tsx"
4658

4759
```
4860

61+
### Resizable columns
62+
63+
To allow a column to resize, add `isResizable` to the `DataViewTable` element, and pass `resizableProps` to each applicable header cell. The `resizableProps` object consists of the following fields:
64+
65+
- `isResizable` - indicates that the column is resizable
66+
- `onResize` - a callback that will return the source event and the new width of the column
67+
- `width` - a default width value for a column
68+
- `minWidth` - the minimum width a column may shrink to
69+
- `increment` - how many pixels the column will move left or right for keyboard navigation
70+
71+
```js file="./DataViewTableResizableColumnsExample.tsx"
72+
73+
```
74+
4975
## Tree table
5076

51-
A tree table includes expandable rows and custom icons for leaf and parent nodes.
77+
A tree table includes expandable rows and custom icons for leaf and parent nodes.
5278
To enable a tree table, pass the `isTreeTable` flag to the `<DataViewTable>` component.
5379

54-
5580
Tree table rows have to be defined with following keys:
56-
- `row`: Defines the content for each cell in the row.
57-
- `id`: Unique identifier for the row that's used for matching selected items.
58-
- `children` (optional): Defines the children rows.
81+
82+
- `row`: Defines the content for each cell in the row.
83+
- `id`: Unique identifier for the row that's used for matching selected items.
84+
- `children` (optional): Defines the children rows.
5985

6086
To update a row's icon to reflect its expansion state, pass `collapsedIcon`, `expandedIcon`, and `leafIcon` to `<DataViewTable>`.
6187

@@ -68,17 +94,21 @@ To disable row selection, pass the `isSelectDisabled` function to `selection` pr
6894
```
6995

7096
## Sorting
97+
7198
The following example demonstrates how to enable sorting functionality within a data view. This implementation supports dynamic sorting by column and persists the sort state in the page's URL via [React Router](https://reactrouter.com/).
7299

73100
### Sorting example
101+
74102
```js file="./SortingExample.tsx"
75103

76104
```
105+
77106
### Sorting state
78107

79108
The `useDataViewSort` hook manages the sorting state of a data view and provides an easy way to handle sorting logic, such as synchronization with URL parameters and the definition of default sorting behavior.
80109

81110
**Initial values:**
111+
82112
- `initialSort` object to set default `sortBy` and `direction` values:
83113
- `sortBy`: Key of the initial column to sort.
84114
- `direction`: Default sorting direction (`asc` or `desc`).
@@ -88,44 +118,47 @@ The `useDataViewSort` hook manages the sorting state of a data view and provides
88118
- Customizable parameter names for the URL:
89119
- `sortByParam`: Name of the URL parameter for the column key.
90120
- `directionParam`: Name of the URL parameter for the sorting direction.
91-
The `useDataViewSort` hook integrates seamlessly with [React Router](https://reactrouter.com/) to manage the sort state via URL parameters. Alternatively, you can use `URLSearchParams` and `window.history.pushState` APIs, or other routing libraries. If URL synchronization is not configured, the sort state is managed internally within the component.
121+
The `useDataViewSort` hook integrates seamlessly with [React Router](https://reactrouter.com/) to manage the sort state via URL parameters. Alternatively, you can use `URLSearchParams` and `window.history.pushState` APIs, or other routing libraries. If URL synchronization is not configured, the sort state is managed internally within the component.
92122

93123
**Return values:**
124+
94125
- `sortBy`: Key of the column currently being sorted.
95126
- `direction`: Current sorting direction (`asc` or `desc`).
96127
- `onSort`: Function to handle sorting changes programmatically or via user interaction.
97128

98129
## States
99130

100-
The data view table allows you to react to the `activeState` of the data view (such as `empty`, `error`, `loading`). You can use the `headStates` and `bodyStates` props to define the table head and body for a given state.
131+
The data view table allows you to react to the `activeState` of the data view (such as `empty`, `error`, `loading`). You can use the `headStates` and `bodyStates` props to define the table head and body for a given state.
101132

102133
### Empty
103-
When there is no data to render in the data view, you can instead display an empty state.
104134

105-
You can create your empty state by passing a [PatternFly empty state](/components/empty-state) to the `empty` key of `headStates` or `bodyStates`.
135+
When there is no data to render in the data view, you can instead display an empty state.
136+
137+
You can create your empty state by passing a [PatternFly empty state](/components/empty-state) to the `empty` key of `headStates` or `bodyStates`.
106138

107139
```js file="./DataViewTableEmptyExample.tsx"
108140

109141
```
110142

111143
### Error
144+
112145
When there is a data connection or retrieval error, you can display an error state.
113146

114147
The error state will be displayed when the data view `activeState` value is `error`.
115148

116-
You can create your error state by passing either the [component groups extension's error state](/component-groups/error-state) or a [PatternFly empty state](/components/empty-state) to the `error` key of `headStates` or `bodyStates`.
149+
You can create your error state by passing either the [component groups extension's error state](/component-groups/error-state) or a [PatternFly empty state](/components/empty-state) to the `error` key of `headStates` or `bodyStates`.
117150

118151
```js file="./DataViewTableErrorExample.tsx"
119152

120153
```
121154

122155
### Loading
156+
123157
To indicate that data is loading, you can display a loading state.
124158

125159
The loading state will be displayed when the data view `activeState` value is `loading`.
126160

127-
You can create your loading state by passing either the [component groups extension's skeleton table](/component-groups/skeleton-table) or a customized [PatternFly empty state](/components/empty-state) to the `loading` key of `headStates` or `bodyStates`.
128-
161+
You can create your loading state by passing either the [component groups extension's skeleton table](/component-groups/skeleton-table) or a customized [PatternFly empty state](/components/empty-state) to the `loading` key of `headStates` or `bodyStates`.
129162

130163
```js file="./DataViewTableLoadingExample.tsx"
131164

Lines changed: 65 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,94 @@
1-
import { FC, ReactNode } from 'react';
2-
import {
3-
TdProps,
4-
ThProps,
5-
TrProps
6-
} from '@patternfly/react-table';
1+
import { FC, ReactNode, MouseEvent as ReactMouseEvent, KeyboardEvent as ReactKeyboardEvent } from 'react';
2+
import { TdProps, ThProps, TrProps, InnerScrollContainer } from '@patternfly/react-table';
73
import { DataViewTableTree, DataViewTableTreeProps } from '../DataViewTableTree';
84
import { DataViewTableBasic, DataViewTableBasicProps } from '../DataViewTableBasic';
95

6+
// Table resizable typings
7+
export interface DataViewThResizableProps {
8+
/** Whether the column is resizable */
9+
isResizable?: boolean;
10+
/** Callback after the column is resized */
11+
onResize?: (
12+
event: ReactMouseEvent | MouseEvent | ReactKeyboardEvent | KeyboardEvent | TouchEvent,
13+
width: number
14+
) => void;
15+
/** Width of the column */
16+
width?: number;
17+
/** Minimum width of the column */
18+
minWidth?: number;
19+
/** Increment for keyboard navigation */
20+
increment?: number;
21+
}
22+
1023
// Table head typings
11-
export type DataViewTh = ReactNode | {
12-
/** Table head cell node */
13-
cell: ReactNode;
14-
/** Props passed to Th */
15-
props?: ThProps
16-
};
17-
export const isDataViewThObject = (value: DataViewTh): value is { cell: ReactNode; props?: ThProps } => value != null && typeof value === 'object' && 'cell' in value;
24+
export type DataViewTh =
25+
| ReactNode
26+
| {
27+
/** Table head cell node */
28+
cell: ReactNode;
29+
/** Whether the column is resizable */
30+
resizableProps?: DataViewThResizableProps;
31+
/** Props passed to Th */
32+
props?: ThProps;
33+
};
34+
export const isDataViewThObject = (value: DataViewTh): value is { cell: ReactNode; props?: ThProps } =>
35+
value != null && typeof value === 'object' && 'cell' in value;
1836

1937
// Basic table typings
2038
export interface DataViewTrObject {
2139
/** Array of rows */
22-
row: DataViewTd[],
40+
row: DataViewTd[];
2341
/** Unique identifier of a row */
24-
id?: string,
42+
id?: string;
2543
/** Props passed to Tr */
26-
props?: TrProps
44+
props?: TrProps;
2745
}
2846

29-
export type DataViewTd = ReactNode | {
30-
/** Table body cell node */
31-
cell: ReactNode;
32-
/** Props passed to Td */
33-
props?: TdProps
34-
};
47+
export type DataViewTd =
48+
| ReactNode
49+
| {
50+
/** Table body cell node */
51+
cell: ReactNode;
52+
/** Props passed to Td */
53+
props?: TdProps;
54+
};
3555

3656
export type DataViewTr = DataViewTd[] | DataViewTrObject;
3757

38-
export const isDataViewTdObject = (value: DataViewTd): value is { cell: ReactNode; props?: TdProps } => value != null && typeof value === 'object' && 'cell' in value;
39-
export const isDataViewTrObject = (value: DataViewTr): value is { row: DataViewTd[], id?: string } => value != null && typeof value === 'object' && 'row' in value;
58+
export const isDataViewTdObject = (value: DataViewTd): value is { cell: ReactNode; props?: TdProps } =>
59+
value != null && typeof value === 'object' && 'cell' in value;
60+
export const isDataViewTrObject = (value: DataViewTr): value is { row: DataViewTd[]; id?: string } =>
61+
value != null && typeof value === 'object' && 'row' in value;
4062

4163
// Tree table typings
4264
/** extends DataViewTrObject */
43-
export interface DataViewTrTree extends DataViewTrObject { id: string, children?: DataViewTrTree[] }
65+
export interface DataViewTrTree extends DataViewTrObject {
66+
id: string;
67+
children?: DataViewTrTree[];
68+
}
4469

4570
export type DataViewTableProps =
4671
| ({
4772
isTreeTable: true;
4873
} & DataViewTableTreeProps)
4974
| ({
5075
isTreeTable?: false;
76+
isResizable?: boolean;
5177
} & DataViewTableBasicProps);
5278

53-
export const DataViewTable: FC<DataViewTableProps> = (props) => (
54-
props.isTreeTable ? <DataViewTableTree {...props} /> : <DataViewTableBasic {...props} />
55-
);
79+
export const DataViewTable: FC<DataViewTableProps> = (props) => {
80+
if (props.isTreeTable) {
81+
return <DataViewTableTree {...props} />;
82+
} else {
83+
const { isResizable, ...rest } = props;
84+
return isResizable ? (
85+
<InnerScrollContainer>
86+
<DataViewTableBasic {...rest} />
87+
</InnerScrollContainer>
88+
) : (
89+
<DataViewTableBasic {...rest} />
90+
);
91+
}
92+
};
5693

5794
export default DataViewTable;

0 commit comments

Comments
 (0)