Skip to content

Commit 009c486

Browse files
authored
Merge pull request #512 from kmcfaul/resize-columns
feat(DataView): add support for resizable columns
2 parents 25d57cb + 6f2e993 commit 009c486

File tree

19 files changed

+942
-271
lines changed

19 files changed

+942
-271
lines changed

.eslintrc.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
"@typescript-eslint/unified-signatures": "error",
5353
"@typescript-eslint/no-var-requires": "off",
5454
"arrow-body-style": "error",
55-
"array-bracket-spacing": ["error", "always"],
55+
"array-bracket-spacing": "off",
5656
"camelcase": [
5757
"error",
5858
{

config/setupTests.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,15 @@ global.MutationObserver = class {
1111
observe(element, initObject) {}
1212
};
1313

14+
global.IntersectionObserver = class {
15+
constructor(callback, options) {}
16+
disconnect() {}
17+
observe(element) {}
18+
unobserve(element) {}
19+
};
20+
1421
jest.mock('react', () => ({
1522
...jest.requireActual('react'),
16-
useLayoutEffect: jest.requireActual('react').useEffect,
23+
useLayoutEffect: jest.requireActual('react').useEffect
1724
}));
18-
Element.prototype.scrollTo = () => {};
25+
Element.prototype.scrollTo = () => {};

package-lock.json

Lines changed: 232 additions & 85 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/module/package.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,9 @@
3232
},
3333
"dependencies": {
3434
"@patternfly/react-component-groups": "^6.1.0",
35-
"@patternfly/react-core": "^6.0.0",
36-
"@patternfly/react-icons": "^6.0.0",
37-
"@patternfly/react-table": "^6.0.0",
35+
"@patternfly/react-core": "6.4.0-prerelease.1",
36+
"@patternfly/react-icons": "6.4.0-prerelease.1",
37+
"@patternfly/react-table": "6.4.0-prerelease.2",
3838
"clsx": "^2.1.1",
3939
"react-jss": "^10.10.0"
4040
},
@@ -44,8 +44,8 @@
4444
},
4545
"devDependencies": {
4646
"@patternfly/documentation-framework": "^6.5.20",
47-
"@patternfly/patternfly": "^6.0.0",
48-
"@patternfly/react-code-editor": "^6.0.0",
47+
"@patternfly/patternfly": "6.4.0-prerelease.1",
48+
"@patternfly/react-code-editor": "6.4.0-prerelease.1",
4949
"@patternfly/patternfly-a11y": "^5.1.0",
5050
"@types/react": "^18.3.23",
5151
"@types/react-dom": "^18.3.7",
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
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 ouiaId = 'TableExample';
102+
103+
export const ResizableColumnsExample: FunctionComponent = () => {
104+
const onResize = (
105+
_e: React.MouseEvent | MouseEvent | React.KeyboardEvent | KeyboardEvent | TouchEvent,
106+
id: string | number | undefined,
107+
width: number
108+
) => {
109+
// eslint-disable-next-line no-console
110+
console.log(`resized column id: ${id} width to: ${width.toFixed(0)}px`);
111+
};
112+
113+
const columns: DataViewTh[] = [
114+
null,
115+
'Repositories',
116+
{
117+
cell: 'Branches',
118+
resizableProps: {
119+
isResizable: true,
120+
onResize,
121+
resizeButtonAriaLabel: 'Resize repositories column'
122+
},
123+
props: { id: 'repositories' }
124+
},
125+
{
126+
cell: 'Pull requests',
127+
resizableProps: {
128+
isResizable: true,
129+
onResize,
130+
resizeButtonAriaLabel: 'Resize pull requests column'
131+
},
132+
props: { info: { tooltip: 'More information' }, id: 'pull-requests' }
133+
},
134+
{
135+
cell: 'This is a really long title',
136+
resizableProps: {
137+
isResizable: true,
138+
onResize,
139+
resizeButtonAriaLabel: 'Resize this is a really long title column'
140+
},
141+
props: { info: { tooltip: 'More information' }, id: 'this-is-a-really-long-title' }
142+
},
143+
{
144+
cell: 'Last commit',
145+
resizableProps: {
146+
isResizable: true,
147+
onResize,
148+
resizeButtonAriaLabel: 'Resize last commit column'
149+
},
150+
props: { sort: { sortBy: {}, columnIndex: 4 }, id: 'last-commit' }
151+
}
152+
];
153+
154+
return <DataViewTable isResizable aria-label="Repositories table" ouiaId={ouiaId} columns={columns} rows={rows} />;
155+
};

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

Lines changed: 50 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,19 @@ 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
---
18-
import { FunctionComponent, useMemo } from 'react';
26+
27+
import { FunctionComponent, useMemo, useState } from 'react';
1928
import { BrowserRouter, useSearchParams } from 'react-router-dom';
2029
import { Button, EmptyState, EmptyStateActions, EmptyStateBody, EmptyStateFooter } from '@patternfly/react-core';
2130
import { CubesIcon, FolderIcon, FolderOpenIcon, LeafIcon, ExclamationCircleIcon } from '@patternfly/react-icons';
@@ -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,38 @@ 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+
- `resizeButtonAriaLabel` - an accessible name for the resizable column's resize button. This must be passed in if the column is resizable.
67+
- `onResize` - a callback that will return the source event and the new width of the column
68+
- `width` - a default width value for a column
69+
- `minWidth` - the minimum width a column may shrink to
70+
- `increment` - how many pixels the column will move left or right for keyboard navigation
71+
- `shiftIncrement` - how many pixels the column will move left or right while shift is held for keyboard navigation
72+
- `screenReaderText` - text that will be announced when a column is resized
73+
74+
```js file="./DataViewTableResizableColumnsExample.tsx"
75+
76+
```
77+
4978
## Tree table
5079

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

54-
5583
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.
84+
85+
- `row`: Defines the content for each cell in the row.
86+
- `id`: Unique identifier for the row that's used for matching selected items.
87+
- `children` (optional): Defines the children rows.
5988

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

@@ -68,17 +97,21 @@ To disable row selection, pass the `isSelectDisabled` function to `selection` pr
6897
```
6998

7099
## Sorting
100+
71101
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/).
72102

73103
### Sorting example
104+
74105
```js file="./SortingExample.tsx"
75106

76107
```
108+
77109
### Sorting state
78110

79111
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.
80112

81113
**Initial values:**
114+
82115
- `initialSort` object to set default `sortBy` and `direction` values:
83116
- `sortBy`: Key of the initial column to sort.
84117
- `direction`: Default sorting direction (`asc` or `desc`).
@@ -88,44 +121,47 @@ The `useDataViewSort` hook manages the sorting state of a data view and provides
88121
- Customizable parameter names for the URL:
89122
- `sortByParam`: Name of the URL parameter for the column key.
90123
- `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.
124+
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.
92125

93126
**Return values:**
127+
94128
- `sortBy`: Key of the column currently being sorted.
95129
- `direction`: Current sorting direction (`asc` or `desc`).
96130
- `onSort`: Function to handle sorting changes programmatically or via user interaction.
97131

98132
## States
99133

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.
134+
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.
101135

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

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

107142
```js file="./DataViewTableEmptyExample.tsx"
108143

109144
```
110145

111146
### Error
147+
112148
When there is a data connection or retrieval error, you can display an error state.
113149

114150
The error state will be displayed when the data view `activeState` value is `error`.
115151

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`.
152+
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`.
117153

118154
```js file="./DataViewTableErrorExample.tsx"
119155

120156
```
121157

122158
### Loading
159+
123160
To indicate that data is loading, you can display a loading state.
124161

125162
The loading state will be displayed when the data view `activeState` value is `loading`.
126163

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-
164+
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`.
129165

130166
```js file="./DataViewTableLoadingExample.tsx"
131167

packages/module/patternfly-docs/generated/index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ module.exports = {
1414
'/extensions/data-view/table/react': {
1515
id: "Table",
1616
title: "Data view table",
17-
toc: [{"text":"Configuring rows and columns"},[{"text":"Table example"}],{"text":"Tree table"},[{"text":"Tree table example"}],{"text":"Sorting"},[{"text":"Sorting example"},{"text":"Sorting state"}],{"text":"States"},[{"text":"Empty"},{"text":"Error"},{"text":"Loading"}]],
18-
examples: ["Table example","Tree table example","Sorting example","Empty","Error","Loading"],
17+
toc: [{"text":"Configuring rows and columns"},[{"text":"Table example"},{"text":"Resizable columns"}],{"text":"Tree table"},[{"text":"Tree table example"}],{"text":"Sorting"},[{"text":"Sorting example"},{"text":"Sorting state"}],{"text":"States"},[{"text":"Empty"},{"text":"Error"},{"text":"Loading"}]],
18+
examples: ["Table example","Resizable columns","Tree table example","Sorting example","Empty","Error","Loading"],
1919
section: "extensions",
2020
subsection: "Data view",
2121
source: "react",

packages/module/src/DataViewCheckboxFilter/__snapshots__/DataViewCheckboxFilter.test.tsx.snap

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,6 @@ exports[`DataViewCheckboxFilter component should render correctly 1`] = `
135135
class="pf-v6-c-label__actions"
136136
>
137137
<button
138-
aria-disabled="false"
139138
aria-label="Close Workspace one"
140139
class="pf-v6-c-button pf-m-plain pf-m-no-padding"
141140
data-ouia-component-id="OUIA-Generated-Button-plain-1"
@@ -176,7 +175,6 @@ exports[`DataViewCheckboxFilter component should render correctly 1`] = `
176175
class="pf-v6-c-toolbar__item"
177176
>
178177
<button
179-
aria-disabled="false"
180178
class="pf-v6-c-button pf-m-link pf-m-inline"
181179
data-ouia-component-id="DataViewToolbar-clear-all-filters"
182180
data-ouia-component-type="PF6/Button"

0 commit comments

Comments
 (0)