Skip to content

Commit 14bf7b3

Browse files
committed
feat: enhance toolbar and data view examples to cover all features
1 parent b2776d8 commit 14bf7b3

File tree

5 files changed

+305
-98
lines changed

5 files changed

+305
-98
lines changed

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,16 @@ sourceLink: https://github.com/patternfly/react-data-view/blob/main/packages/mod
88
---
99
import { useState, useEffect, useRef, useMemo } from 'react';
1010
import { Drawer, DrawerContent, DrawerContentBody } from '@patternfly/react-core';
11-
import { useDataViewPagination, useDataViewSelection } from '@patternfly/react-data-view/dist/dynamic/Hooks';
12-
import { BulkSelect, BulkSelectValue } from '@patternfly/react-component-groups/dist/dynamic/BulkSelect';
11+
import { CubesIcon } from '@patternfly/react-icons';
12+
import { useDataViewPagination, useDataViewSelection, useDataViewFilters, useDataViewSort } from '@patternfly/react-data-view/dist/dynamic/Hooks';
13+
import { BulkSelect, BulkSelectValue, ErrorState, ResponsiveAction, ResponsiveActions, SkeletonTableHead, SkeletonTableBody } from '@patternfly/react-component-groups';
1314
import { DataView } from '@patternfly/react-data-view/dist/dynamic/DataView';
1415
import { DataViewToolbar } from '@patternfly/react-data-view/dist/dynamic/DataViewToolbar';
1516
import { DataViewTable } from '@patternfly/react-data-view/dist/dynamic/DataViewTable';
1617
import { useDataViewEventsContext, DataViewEventsContext, DataViewEventsProvider, EventTypes } from '@patternfly/react-data-view/dist/dynamic/DataViewEventsContext';
18+
import { DataViewFilters } from '@patternfly/react-data-view/dist/dynamic/DataViewFilters';
19+
import { DataViewTextFilter } from '@patternfly/react-data-view/dist/dynamic/DataViewTextFilter';
20+
import { DataViewCheckboxFilter } from '@patternfly/react-data-view/dist/dynamic/DataViewCheckboxFilter';
1721

1822
## Core concepts
1923

@@ -45,7 +49,7 @@ For the toolbar, you can make use of the predefined `DataViewToolbar` component,
4549

4650
Data can be presented using the predefined `DataViewTable` component, which is an abstraction above the PatternFly [table](/components/table). For more details, please refer to the [Table](/extensions/data-view/table) docs section. In the near future, we are also planning to introduce a predefined Card view component. If you have more specific needs to display data, you can pass your custom implementation as a `DataView` child.
4751

48-
```js file="./PredefinedLayoutExample.tsx"
52+
```js file="./PredefinedLayoutFullExample.tsx"
4953

5054
```
5155

packages/module/patternfly-docs/content/extensions/data-view/examples/DataView/PredefinedLayoutExample.tsx

Lines changed: 0 additions & 89 deletions
This file was deleted.
Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
/* eslint-disable no-nested-ternary */
2+
import React, { useEffect, useState, useRef, useMemo } from 'react';
3+
import { Drawer, DrawerActions, DrawerCloseButton, DrawerContent, DrawerContentBody, DrawerHead, DrawerPanelContent, Title, Text, EmptyState, EmptyStateHeader, EmptyStateBody, EmptyStateFooter, EmptyStateActions, Button, EmptyStateIcon } from '@patternfly/react-core';
4+
import { ActionsColumn, Tbody, Td, ThProps, Tr } from '@patternfly/react-table';
5+
import { BulkSelect, BulkSelectValue } from '@patternfly/react-component-groups/dist/dynamic/BulkSelect';
6+
import { Pagination } from '@patternfly/react-core';
7+
import { DataView } from '@patternfly/react-data-view/dist/dynamic/DataView';
8+
import { DataViewToolbar } from '@patternfly/react-data-view/dist/dynamic/DataViewToolbar';
9+
import { DataViewTable, DataViewTh } from '@patternfly/react-data-view/dist/dynamic/DataViewTable';
10+
import { DataViewEventsProvider, EventTypes, useDataViewEventsContext } from '@patternfly/react-data-view/dist/dynamic/DataViewEventsContext';
11+
import { useDataViewPagination, useDataViewSelection, useDataViewFilters, useDataViewSort } from '@patternfly/react-data-view/dist/dynamic/Hooks';
12+
import { ResponsiveAction, ResponsiveActions } from '@patternfly/react-component-groups';
13+
import { DataViewFilterOption, DataViewFilters } from '@patternfly/react-data-view/dist/dynamic/DataViewFilters';
14+
import { DataViewTextFilter } from '@patternfly/react-data-view/dist/dynamic/DataViewTextFilter';
15+
import { DataViewCheckboxFilter } from '@patternfly/react-data-view/dist/dynamic/DataViewCheckboxFilter';
16+
import { CubesIcon } from '@patternfly/react-icons';
17+
18+
const perPageOptions = [
19+
{ title: '5', value: 5 },
20+
{ title: '10', value: 10 }
21+
];
22+
23+
interface Repository {
24+
name: string;
25+
branch: string | null;
26+
prs: string | null;
27+
workspace: string;
28+
lastCommit: string;
29+
};
30+
31+
interface RepositoryFilters {
32+
name: string,
33+
branch: string,
34+
workspace: string[]
35+
};
36+
37+
const repositories: Repository[] = [
38+
{ name: 'Repository one', branch: 'Branch one', prs: 'Pull request one', workspace: 'Workspace one', lastCommit: 'Timestamp one' },
39+
{ name: 'Repository two', branch: 'Branch two', prs: 'Pull request two', workspace: 'Workspace two', lastCommit: 'Timestamp two' },
40+
{ name: 'Repository three', branch: 'Branch three', prs: 'Pull request three', workspace: 'Workspace three', lastCommit: 'Timestamp three' },
41+
{ name: 'Repository four', branch: 'Branch four', prs: 'Pull request four', workspace: 'Workspace four', lastCommit: 'Timestamp four' },
42+
{ name: 'Repository five', branch: 'Branch five', prs: 'Pull request five', workspace: 'Workspace five', lastCommit: 'Timestamp five' },
43+
{ name: 'Repository six', branch: 'Branch six', prs: 'Pull request six', workspace: 'Workspace six', lastCommit: 'Timestamp six' }
44+
];
45+
46+
const filterOptions: DataViewFilterOption[] = [
47+
{ label: 'Workspace one', value: 'workspace-one' },
48+
{ label: 'Workspace two', value: 'workspace-two' },
49+
{ label: 'Workspace three', value: 'workspace-three' }
50+
];
51+
52+
const COLUMNS = [
53+
{ label: 'Repository', key: 'name', index: 0 },
54+
{ label: 'Branch', key: 'branches', index: 1 },
55+
{ label: 'Pull request', key: 'prs', index: 2 },
56+
{ label: 'Workspace', key: 'workspaces', index: 3 },
57+
{ label: 'Last commit', key: 'lastCommit', index: 4 }
58+
];
59+
60+
const ouiaId = 'LayoutExample';
61+
62+
const sortData = (data: Repository[], sortBy: string | undefined, direction: 'asc' | 'desc' | undefined) =>
63+
sortBy && direction
64+
? [ ...data ].sort((a, b) =>
65+
direction === 'asc'
66+
? a[sortBy] < b[sortBy] ? -1 : a[sortBy] > b[sortBy] ? 1 : 0
67+
: a[sortBy] > b[sortBy] ? -1 : a[sortBy] < b[sortBy] ? 1 : 0
68+
)
69+
: data;
70+
71+
const empty = (
72+
<Tbody>
73+
<Tr key="loading" ouiaId={`${ouiaId}-tr-loading`}>
74+
<Td colSpan={COLUMNS.length}>
75+
<EmptyState>
76+
<EmptyStateHeader titleText="No data found" headingLevel="h4" icon={<EmptyStateIcon icon={CubesIcon } />} />
77+
<EmptyStateBody>There are no matching data to be displayed.</EmptyStateBody>
78+
<EmptyStateFooter>
79+
<EmptyStateActions>
80+
<Button variant="primary">Primary action</Button>
81+
</EmptyStateActions>
82+
<EmptyStateActions>
83+
<Button variant="link">Multiple</Button>
84+
<Button variant="link">Action Buttons</Button>
85+
</EmptyStateActions>
86+
</EmptyStateFooter>
87+
</EmptyState>
88+
</Td>
89+
</Tr>
90+
</Tbody>
91+
);
92+
93+
interface RepositoryDetailProps {
94+
selectedRepo?: Repository;
95+
setSelectedRepo: React.Dispatch<React.SetStateAction<Repository | undefined>>;
96+
}
97+
98+
const RepositoryDetail: React.FunctionComponent<RepositoryDetailProps> = ({ selectedRepo, setSelectedRepo }) => {
99+
const context = useDataViewEventsContext();
100+
101+
useEffect(() => {
102+
const unsubscribe = context.subscribe(EventTypes.rowClick, (repo: Repository) => {
103+
setSelectedRepo(repo);
104+
});
105+
106+
return () => unsubscribe();
107+
// eslint-disable-next-line react-hooks/exhaustive-deps
108+
}, []);
109+
110+
return (
111+
<DrawerPanelContent>
112+
<DrawerHead>
113+
<Title className="pf-v5-u-mb-md" headingLevel="h2" ouiaId="detail-drawer-title">
114+
Detail of {selectedRepo?.name}
115+
</Title>
116+
<Text>Branch: {selectedRepo?.branch}</Text>
117+
<Text>Pull requests: {selectedRepo?.prs}</Text>
118+
<Text>Workspace: {selectedRepo?.workspace}</Text>
119+
<Text>Last commit: {selectedRepo?.lastCommit}</Text>
120+
<DrawerActions>
121+
<DrawerCloseButton onClick={() => setSelectedRepo(undefined)} data-ouia-component-id="detail-drawer-close-btn"/>
122+
</DrawerActions>
123+
</DrawerHead>
124+
</DrawerPanelContent>
125+
);
126+
};
127+
128+
interface RepositoriesTableProps {
129+
selectedRepo?: Repository;
130+
}
131+
132+
const rowActions = [
133+
{
134+
title: 'Some action',
135+
onClick: () => console.log('clicked on Some action') // eslint-disable-line no-console
136+
},
137+
{
138+
title: <div>Another action</div>,
139+
onClick: () => console.log('clicked on Another action') // eslint-disable-line no-console
140+
},
141+
{
142+
isSeparator: true
143+
},
144+
{
145+
title: 'Third action',
146+
onClick: () => console.log('clicked on Third action') // eslint-disable-line no-console
147+
}
148+
];
149+
150+
const RepositoriesTable: React.FunctionComponent<RepositoriesTableProps> = ({ selectedRepo = undefined }) => {
151+
const { filters, onSetFilters, clearAllFilters } = useDataViewFilters<RepositoryFilters>({ initialFilters: { name: '', branch: '', workspace: [] } });
152+
153+
const pagination = useDataViewPagination({ perPage: 5 });
154+
const { page, perPage } = pagination;
155+
156+
const selection = useDataViewSelection({ matchOption: (a, b) => a[0] === b[0] });
157+
const { selected, onSelect, isSelected } = selection;
158+
159+
const { trigger } = useDataViewEventsContext();
160+
161+
const { sortBy, direction, onSort } = useDataViewSort();
162+
const sortByIndex = useMemo(() => COLUMNS.findIndex(item => item.key === sortBy), [ sortBy ]);
163+
const getSortParams = (columnIndex: number): ThProps['sort'] => ({
164+
sortBy: {
165+
index: sortByIndex,
166+
direction,
167+
defaultDirection: 'asc'
168+
},
169+
onSort: (_event, index, direction) => onSort(_event, COLUMNS[index].key, direction),
170+
columnIndex
171+
});
172+
173+
const columns: DataViewTh[] = COLUMNS.map((column, index) => ({
174+
cell: column.label,
175+
props: { sort: getSortParams(index) }
176+
}));
177+
178+
const finalData = useMemo(() => sortData(repositories, sortBy, direction).filter(item =>
179+
(!filters.name || item.name?.toLocaleLowerCase().includes(filters.name?.toLocaleLowerCase())) &&
180+
(!filters.branch || item.branch?.toLocaleLowerCase().includes(filters.branch?.toLocaleLowerCase())) &&
181+
(!filters.workspace || filters.workspace.length === 0 || filters.workspace.includes(String(filterOptions.find(option => option.label === item.workspace)?.value)))
182+
), [ filters, sortBy, direction ]);
183+
184+
const pageRows = useMemo(() => {
185+
const handleRowClick = (event, repo: Repository | undefined) => {
186+
// prevents drawer toggle on actions or checkbox click
187+
(event.target.matches('td') || event.target.matches('tr')) && trigger(EventTypes.rowClick, repo);
188+
};
189+
190+
return finalData.map(repo => ({
191+
row: [ ...Object.values(repo), { cell: <ActionsColumn items={rowActions}/>, props: { isActionCell: true } } ],
192+
props: {
193+
isClickable: true,
194+
onRowClick: (event) => handleRowClick(event, selectedRepo?.name === repo.name ? undefined : repo),
195+
isRowSelected: selectedRepo?.name === repo.name
196+
}
197+
})).slice((page - 1) * perPage, ((page - 1) * perPage) + perPage);
198+
}, [ selectedRepo?.name, trigger, page, perPage, finalData ]);
199+
200+
const handleBulkSelect = (value: BulkSelectValue) => {
201+
value === BulkSelectValue.none && onSelect(false);
202+
value === BulkSelectValue.nonePage && onSelect(false, pageRows);
203+
value === BulkSelectValue.page && onSelect(true, pageRows);
204+
};
205+
206+
return (
207+
<DataView selection={selection} activeState={finalData.length > 0 ? undefined : 'empty'}>
208+
<DataViewToolbar
209+
ouiaId='LayoutExampleHeader'
210+
clearAllFilters={clearAllFilters}
211+
bulkSelect={
212+
<BulkSelect
213+
pageCount={pageRows.length}
214+
totalCount={repositories.length}
215+
selectedCount={selected.length}
216+
pageSelected={pageRows.every(item => isSelected(item))}
217+
pagePartiallySelected={pageRows.some(item => isSelected(item)) && !pageRows.every(item => isSelected(item))}
218+
onSelect={handleBulkSelect}
219+
/>
220+
}
221+
filters={
222+
<DataViewFilters onChange={(_e, values) => onSetFilters(values)} values={filters}>
223+
<DataViewTextFilter filterId="name" title='Name' placeholder='Filter by name' />
224+
<DataViewTextFilter filterId="branch" title='Branch' placeholder='Filter by branch' />
225+
<DataViewCheckboxFilter filterId="workspace" title='Workspace' placeholder='Filter by workspace' options={filterOptions} />
226+
</DataViewFilters>
227+
}
228+
actions={
229+
<ResponsiveActions ouiaId="example-actions">
230+
<ResponsiveAction>Add repository</ResponsiveAction>
231+
<ResponsiveAction>Delete repository</ResponsiveAction>
232+
</ResponsiveActions>
233+
}
234+
pagination={
235+
<Pagination
236+
isCompact
237+
perPageOptions={perPageOptions}
238+
itemCount={repositories.length}
239+
{...pagination}
240+
/>
241+
}
242+
/>
243+
<DataViewTable aria-label='Repositories table' ouiaId={ouiaId} columns={columns} rows={pageRows} bodyStates={{ empty }} />
244+
<DataViewToolbar
245+
ouiaId='LayoutExampleFooter'
246+
pagination={
247+
<Pagination
248+
isCompact
249+
perPageOptions={perPageOptions}
250+
itemCount={repositories.length}
251+
{...pagination}
252+
/>
253+
}
254+
/>
255+
</DataView>
256+
);
257+
};
258+
259+
export const BasicExample: React.FunctionComponent = () => {
260+
const [ selectedRepo, setSelectedRepo ] = useState<Repository>();
261+
const drawerRef = useRef<HTMLDivElement>(null);
262+
263+
return (
264+
<DataViewEventsProvider>
265+
<Drawer isExpanded={Boolean(selectedRepo)} onExpand={() => drawerRef.current?.focus()} data-ouia-component-id="detail-drawer" >
266+
<DrawerContent
267+
panelContent={<RepositoryDetail selectedRepo={selectedRepo} setSelectedRepo={setSelectedRepo} />}
268+
>
269+
<DrawerContentBody>
270+
<RepositoriesTable selectedRepo={selectedRepo} />
271+
</DrawerContentBody>
272+
</DrawerContent>
273+
</Drawer>
274+
</DataViewEventsProvider>
275+
);
276+
};

0 commit comments

Comments
 (0)