Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ module.exports = {
'!src/support/**/*.{js,jsx}',
],
roots: ['<rootDir>/src/'],
transformIgnorePatterns: ['/node_modules/(?!(@faker-js)/)'],
transformIgnorePatterns: [
'/node_modules/(?!(@faker-js|@patternfly/react-tokens|@patternfly/react-data-view)/)',
],

moduleNameMapper: {
'\\.(css|scss|svg)$': 'identity-obj-proxy',
Expand Down
30 changes: 30 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"@patternfly/patternfly": "^6.0.0",
"@patternfly/react-component-groups": "^6.0.0",
"@patternfly/react-core": "^6.0.0",
"@patternfly/react-data-view": "^6.5.0",
"@patternfly/react-table": "^6.0.0",
"@redhat-cloud-services/frontend-components": ">= 6.1.0",
"@redhat-cloud-services/frontend-components-utilities": ">= 6.1.0",
Expand Down
66 changes: 66 additions & 0 deletions src/components/DataViewTable/DataViewTable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import React from 'react';
import propTypes from 'prop-types';
import {
DataView,
DataViewTable as PatternFlyDataViewTable,
DataViewToolbar,
} from '@patternfly/react-data-view';
import { Pagination, PaginationVariant } from '@patternfly/react-core';

import useTableToolsForDataView from './hooks/useTableToolsForDataView';

/**
* Data View presentation variant.
*
* @param {object} props Component props (useTableTools output)
* @param {boolean} [props.loading] Loading state
* @param {object} [props.error] Error state
* @param {object} [props.tableProps] PatternFly table props from useTableTools
* @param {object} [props.toolbarProps] Toolbar props from useTableTools
*
* @group Components
Comment thread
qltysh[bot] marked this conversation as resolved.
Comment thread
qltysh[bot] marked this conversation as resolved.
* @returns {React.ReactElement} DataView table
*/
const DataViewTable = (props) => {
const {
columns: dataViewColumns,
rows: dataViewRows,
activeState,
headStates,
bodyStates,
toolbarProps: { pagination, actions } = {},
} = useTableToolsForDataView(props);

return (
<DataView activeState={activeState}>
<DataViewToolbar
pagination={pagination && <Pagination isCompact {...pagination} />}
actions={actions}
ouiaId="data-view-table-toolbar"
/>
<PatternFlyDataViewTable
aria-label="DataViewTable"
columns={dataViewColumns}
rows={dataViewRows}
headStates={headStates}
bodyStates={bodyStates}
/>
<DataViewToolbar
pagination={
pagination && (
<Pagination variant={PaginationVariant.bottom} {...pagination} />
)
}
/>
</DataView>
);
};

DataViewTable.propTypes = {
loading: propTypes.bool,
error: propTypes.object,
tableProps: propTypes.object,
toolbarProps: propTypes.object,
};

export default DataViewTable;
133 changes: 133 additions & 0 deletions src/components/DataViewTable/DataViewTable.stories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import React from 'react';
import defaultStoryMeta from '~/support/defaultStoryMeta';
import columns from '~/support/factories/columns';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { TableStateProvider, TableToolsTable } from '~/components';
import useExampleDataQuery from '~/support/hooks/useExampleDataQuery';
import paginationSerialiser from '~/components/StaticTableToolsTable/helpers/serialisers/pagination';
import sortSerialiser from '~/components/StaticTableToolsTable/helpers/serialisers/sort';
import { actions } from '~/support/constants';

const meta = {
title: 'DataViewTable',
component: TableToolsTable,
...defaultStoryMeta,
};

export default meta;

const queryClient = new QueryClient();

const defaultOptions = {
serialisers: {
pagination: paginationSerialiser,
sort: sortSerialiser,
},
};

const CommonExample = () => {
const {
loading,
result: { data, meta: { total } = {} } = {},
error,
exporter,
} = useExampleDataQuery({
endpoint: '/api',
useTableState: true,
});

return (
<TableToolsTable
tableToolsTableVariant="dataViewTable"
items={data}
columns={columns}
total={total}
error={error}
loading={loading}
options={{
...defaultOptions,
actions,
exporter,
}}
/>
);
};

export const Common = {
decorators: [
(Story) => (
<QueryClientProvider client={queryClient}>
<TableStateProvider>
<Story />
</TableStateProvider>
</QueryClientProvider>
),
],
render: (args) => <CommonExample {...args} />,
};

const WithErrorPassedExample = () => {
const {
loading,
result: { data, meta: { total } = {} } = {},
error,
} = useExampleDataQuery({ endpoint: '/api/error' });
return (
<TableToolsTable
tableToolsTableVariant="dataViewTable"
items={data}
columns={columns}
total={total}
error={error}
loading={loading}
/>
);
};

export const WithErrorPassed = {
decorators: [
(Story) => (
<QueryClientProvider client={queryClient}>
<TableStateProvider>
<Story />
</TableStateProvider>
</QueryClientProvider>
),
],
render: (args) => <WithErrorPassedExample {...args} />,
};

const EmptyExample = () => {
const {
loading,
result: { data, meta: { total } = {} } = {},
error,
} = useExampleDataQuery({
endpoint: '/api',
params: { total: 0 },
});

return (
<TableToolsTable
tableToolsTableVariant="dataViewTable"
items={data}
columns={columns}
total={total}
error={error}
loading={loading}
/>
);
};

export const Empty = {
decorators: [
(Story) => (
<QueryClientProvider client={queryClient}>
<TableStateProvider>
<Story />
</TableStateProvider>
</QueryClientProvider>
),
],
render: (args) => <EmptyExample {...args} />,
};
36 changes: 36 additions & 0 deletions src/components/DataViewTable/helpers/bodyStates.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React from 'react';
import { ErrorState } from '@patternfly/react-component-groups';
import { EmptyState, EmptyStateBody } from '@patternfly/react-core';
import { CubesIcon } from '@patternfly/react-icons';
import { Tbody, Td, Tr } from '@patternfly/react-table';

export const getErrorBodyState = (columnsCount) => (
<Tbody>
<Tr key="error">
<Td colSpan={columnsCount}>
<ErrorState
titleText="Unable to load data"
bodyText="There was an error retrieving data. Check your connection and reload the page."
/>
</Td>
</Tr>
</Tbody>
);

export const getDefaultEmptyBodyState = (columns) => (
<Tbody>
<Tr>
<Td colSpan={columns.length}>
<EmptyState
headingLevel="h4"
icon={CubesIcon}
titleText="No data found"
>
<EmptyStateBody>
There are no matching data to be displayed.
</EmptyStateBody>
</EmptyState>
</Td>
</Tr>
</Tbody>
);
46 changes: 46 additions & 0 deletions src/components/DataViewTable/helpers/getDataViewStateProps.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React from 'react';
import { DataViewState } from '@patternfly/react-data-view';
import {
SkeletonTableBody,
SkeletonTableHead,
} from '@patternfly/react-component-groups';

import { getDefaultEmptyBodyState, getErrorBodyState } from './bodyStates';

const DEFAULT_SKELETON_ROWS = 10;

export const getDataViewStateProps = ({
loading,
error,
rows,
columns,
emptyState,
perPage,
}) => {
let activeState;

if (loading) {
activeState = DataViewState.loading;
} else if (error) {
activeState = DataViewState.error;
} else if (!rows?.length) {
activeState = DataViewState.empty;
}

return {
activeState,
headStates: {
loading: <SkeletonTableHead columns={columns} />,
},
bodyStates: {
loading: (
<SkeletonTableBody
rowsCount={perPage || DEFAULT_SKELETON_ROWS}
columnsCount={columns.length}
/>
),
error: getErrorBodyState(columns.length),
empty: emptyState || getDefaultEmptyBodyState(columns),
},
};
};
4 changes: 4 additions & 0 deletions src/components/DataViewTable/helpers/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export { toDataViewProps } from './toDataViewProps';
export { getDataViewStateProps } from './getDataViewStateProps';
export { toDataViewActions } from './toDataViewActions';
export { toDataViewExport } from './toDataViewExport';
17 changes: 17 additions & 0 deletions src/components/DataViewTable/helpers/toDataViewActions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import React from 'react';
import { Button } from '@patternfly/react-core';

export const toDataViewActions = (actions) =>
(actions || []).filter(Boolean).map((action, i) => {
if (React.isValidElement(action)) {
return <React.Fragment key={i}>{action}</React.Fragment>;
}
const { label, onClick } = action;
return typeof label === 'string' ? (
<Button key={i} variant="secondary" onClick={onClick}>
{label}
</Button>
) : (
<React.Fragment key={i}>{label}</React.Fragment>
);
});
16 changes: 16 additions & 0 deletions src/components/DataViewTable/helpers/toDataViewExport.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React from 'react';
import DownloadButton from '@redhat-cloud-services/frontend-components/DownloadButton';

/**
* Adapts PrimaryToolbar `exportConfig` into a DataViewToolbar actions slot node.
*
* @param {object} [exportConfig] exportConfig from useExport / toolbarProps
* @returns {React.ReactElement|null} DownloadButton or null when export is off
*/
export const toDataViewExport = (exportConfig) => {
if (!exportConfig?.onSelect && !exportConfig?.extraItems) {
return null;
}

return <DownloadButton key="export" {...exportConfig} />;
};
Loading