Merge pull request #208 from keithjgrant/toolbar-render-prop

use a render prop for PaginatedDataList toolbar
This commit is contained in:
Keith Grant
2019-05-20 06:16:48 -07:00
committed by GitHub
8 changed files with 135 additions and 145 deletions

View File

@@ -176,11 +176,8 @@ exports[`<OrganizationNotifications /> initially renders succesfully 1`] = `
> >
<Route> <Route>
<PaginatedDataList <PaginatedDataList
additionalControls={Array []}
alignToolbarLeft={false}
history={"/history/"} history={"/history/"}
i18n={"/i18n/"} i18n={"/i18n/"}
isAllSelected={false}
itemCount={2} itemCount={2}
itemName="notification" itemName="notification"
itemNamePlural="" itemNamePlural=""
@@ -216,7 +213,6 @@ exports[`<OrganizationNotifications /> initially renders succesfully 1`] = `
"url": "", "url": "",
} }
} }
onSelectAll={null}
qsConfig={ qsConfig={
Object { Object {
"defaultParams": Object { "defaultParams": Object {
@@ -232,8 +228,8 @@ exports[`<OrganizationNotifications /> initially renders succesfully 1`] = `
} }
} }
renderItem={[Function]} renderItem={[Function]}
renderToolbar={[Function]}
showPageSizeOptions={true} showPageSizeOptions={true}
showSelectAll={false}
toolbarColumns={ toolbarColumns={
Array [ Array [
Object { Object {
@@ -257,7 +253,6 @@ exports[`<OrganizationNotifications /> initially renders succesfully 1`] = `
} }
> >
<WithI18n <WithI18n
additionalControls={Array []}
columns={ columns={
Array [ Array [
Object { Object {
@@ -279,12 +274,8 @@ exports[`<OrganizationNotifications /> initially renders succesfully 1`] = `
}, },
] ]
} }
isAllSelected={false}
noLeftMargin={false}
onSearch={[Function]} onSearch={[Function]}
onSelectAll={null}
onSort={[Function]} onSort={[Function]}
showSelectAll={false}
sortOrder="ascending" sortOrder="ascending"
sortedColumnKey="name" sortedColumnKey="name"
> >

View File

@@ -4,6 +4,7 @@ import { withRouter } from 'react-router-dom';
import { withI18n } from '@lingui/react'; import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import PaginatedDataList from '../PaginatedDataList'; import PaginatedDataList from '../PaginatedDataList';
import DataListToolbar from '../DataListToolbar';
import CheckboxListItem from '../ListItem'; import CheckboxListItem from '../ListItem';
import SelectedList from '../SelectedList'; import SelectedList from '../SelectedList';
import { getQSConfig, parseNamespacedQueryString } from '../../util/qs'; import { getQSConfig, parseNamespacedQueryString } from '../../util/qs';
@@ -102,9 +103,7 @@ class SelectResourceStep extends React.Component {
itemCount={count} itemCount={count}
itemName={itemName} itemName={itemName}
qsConfig={this.qsConfig} qsConfig={this.qsConfig}
toolbarColumns={ toolbarColumns={columns}
columns
}
renderItem={item => ( renderItem={item => (
<CheckboxListItem <CheckboxListItem
isSelected={selectedResourceRows.some(i => i.id === item.id)} isSelected={selectedResourceRows.some(i => i.id === item.id)}
@@ -114,7 +113,9 @@ class SelectResourceStep extends React.Component {
onSelect={() => onRowClick(item)} onSelect={() => onRowClick(item)}
/> />
)} )}
alignToolbarLeft renderToolbar={(props) => (
<DataListToolbar {...props} alignToolbarLeft />
)}
showPageSizeOptions={false} showPageSizeOptions={false}
/> />
</Fragment> </Fragment>

View File

@@ -14,6 +14,7 @@ import { t } from '@lingui/macro';
import { withNetwork } from '../../contexts/Network'; import { withNetwork } from '../../contexts/Network';
import PaginatedDataList from '../PaginatedDataList'; import PaginatedDataList from '../PaginatedDataList';
import DataListToolbar from '../DataListToolbar';
import CheckboxListItem from '../ListItem'; import CheckboxListItem from '../ListItem';
import SelectedList from '../SelectedList'; import SelectedList from '../SelectedList';
import { getQSConfig, parseNamespacedQueryString } from '../../util/qs'; import { getQSConfig, parseNamespacedQueryString } from '../../util/qs';
@@ -176,7 +177,9 @@ class Lookup extends React.Component {
onSelect={() => this.toggleSelected(item)} onSelect={() => this.toggleSelected(item)}
/> />
)} )}
alignToolbarLeft renderToolbar={(props) => (
<DataListToolbar {...props} alignToolbarLeft />
)}
showPageSizeOptions={false} showPageSizeOptions={false}
/> />
{lookupSelectedItems.length > 0 && ( {lookupSelectedItems.length > 0 && (

View File

@@ -1,12 +1,7 @@
import React, { Fragment } from 'react'; import React, { Fragment } from 'react';
import PropTypes, { arrayOf, shape, string, bool, node } from 'prop-types'; import PropTypes, { arrayOf, shape, string, bool } from 'prop-types';
import { import {
DataList, DataList,
DataListItem,
DataListItemRow,
DataListItemCells,
DataListCell,
TextContent,
Title, Title,
EmptyState, EmptyState,
EmptyStateIcon, EmptyStateIcon,
@@ -15,11 +10,12 @@ import {
import { CubesIcon } from '@patternfly/react-icons'; import { CubesIcon } from '@patternfly/react-icons';
import { withI18n } from '@lingui/react'; import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { withRouter, Link } from 'react-router-dom'; import { withRouter } from 'react-router-dom';
import styled from 'styled-components'; import styled from 'styled-components';
import Pagination from '../Pagination'; import Pagination from '../Pagination';
import DataListToolbar from '../DataListToolbar'; import DataListToolbar from '../DataListToolbar';
import PaginatedDataListItem from './PaginatedDataListItem';
import { import {
parseNamespacedQueryString, parseNamespacedQueryString,
updateNamespacedQueryString, updateNamespacedQueryString,
@@ -38,13 +34,6 @@ const EmptyStateControlsWrapper = styled.div`
margin-left: 20px; margin-left: 20px;
} }
`; `;
const ListItemGrid = styled(TextContent)`
display: grid;
grid-template-columns: minmax(70px,max-content) repeat(auto-fit, minmax(60px,max-content));
grid-gap: 10px;
`;
class PaginatedDataList extends React.Component { class PaginatedDataList extends React.Component {
constructor (props) { constructor (props) {
super(props); super(props);
@@ -58,12 +47,6 @@ class PaginatedDataList extends React.Component {
this.handleSort = this.handleSort.bind(this); this.handleSort = this.handleSort.bind(this);
} }
getPageCount () {
const { itemCount, qsConfig, location } = this.props;
const queryParams = parseNamespacedQueryString(qsConfig, location.search);
return Math.ceil(itemCount / queryParams.page_size);
}
getSortOrder () { getSortOrder () {
const { qsConfig, location } = this.props; const { qsConfig, location } = this.props;
const queryParams = parseNamespacedQueryString(qsConfig, location.search); const queryParams = parseNamespacedQueryString(qsConfig, location.search);
@@ -95,11 +78,6 @@ class PaginatedDataList extends React.Component {
history.push(`${pathname}?${qs}`); history.push(`${pathname}?${qs}`);
} }
getPluralItemName () {
const { itemName, itemNamePlural } = this.props;
return itemNamePlural || `${itemName}s`;
}
render () { render () {
const { const {
emptyStateControls, emptyStateControls,
@@ -108,16 +86,12 @@ class PaginatedDataList extends React.Component {
qsConfig, qsConfig,
renderItem, renderItem,
toolbarColumns, toolbarColumns,
additionalControls,
itemName, itemName,
itemNamePlural, itemNamePlural,
showSelectAll,
isAllSelected,
onSelectAll,
alignToolbarLeft,
showPageSizeOptions, showPageSizeOptions,
location, location,
i18n i18n,
renderToolbar,
} = this.props; } = this.props;
const { error } = this.state; const { error } = this.state;
const [orderBy, sortOrder] = this.getSortOrder(); const [orderBy, sortOrder] = this.getSortOrder();
@@ -133,78 +107,52 @@ class PaginatedDataList extends React.Component {
)} )}
</Fragment> // TODO: replace with proper error handling </Fragment> // TODO: replace with proper error handling
)} )}
{items.length === 0 {items.length === 0 ? (
? ( <Fragment>
<Fragment> <EmptyStateControlsWrapper>
<EmptyStateControlsWrapper> {emptyStateControls}
{emptyStateControls} </EmptyStateControlsWrapper>
</EmptyStateControlsWrapper> <div css="border-bottom: 1px solid #d2d2d2" />
<div css="border-bottom: 1px solid #d2d2d2" /> <EmptyState>
<EmptyState> <EmptyStateIcon icon={CubesIcon} />
<EmptyStateIcon icon={CubesIcon} /> <Title size="lg">
<Title size="lg"> {i18n._(t`No ${ucFirst(itemNamePlural || pluralize(itemName))} Found `)}
{i18n._(t`No ${ucFirst(itemNamePlural || pluralize(itemName))} Found `)} </Title>
</Title> <EmptyStateBody>
<EmptyStateBody> {i18n._(t`Please add ${ucFirst(itemNamePlural || pluralize(itemName))} to populate this list `)}
{i18n._(t`Please add ${ucFirst(itemNamePlural || pluralize(itemName))} to populate this list `)} </EmptyStateBody>
</EmptyStateBody> </EmptyState>
</EmptyState> </Fragment>
</Fragment> ) : (
) <Fragment>
: ( {renderToolbar({
<Fragment> sortedColumnKey: orderBy,
<DataListToolbar sortOrder,
sortedColumnKey={orderBy} columns,
sortOrder={sortOrder} onSearch: () => { },
columns={columns} onSort: this.handleSort,
onSearch={() => { }} })}
onSort={this.handleSort} <DataList aria-label={i18n._(t`${ucFirst(pluralize(itemName))} List`)}>
showSelectAll={showSelectAll} {items.map(item => (renderItem ? renderItem(item) : (
isAllSelected={isAllSelected} <PaginatedDataListItem key={item.id} item={item} />
onSelectAll={onSelectAll} )))}
additionalControls={additionalControls} </DataList>
noLeftMargin={alignToolbarLeft} <Pagination
variant="bottom"
/> itemCount={itemCount}
<DataList aria-label={i18n._(t`${ucFirst(pluralize(itemName))} List`)}> page={queryParams.page || 1}
{items.map(item => (renderItem ? renderItem(item) : ( perPage={queryParams.page_size}
<DataListItem perPageOptions={showPageSizeOptions ? [
aria-labelledby={`items-list-item-${item.id}`} { title: '5', value: 5 },
key={item.id} { title: '10', value: 10 },
> { title: '20', value: 20 },
<DataListItemRow> { title: '50', value: 50 }
<DataListItemCells dataListCells={[ ] : []}
<DataListCell key="team-name"> onSetPage={this.handleSetPage}
<ListItemGrid> onPerPageSelect={this.handleSetPageSize}
<Link to={{ pathname: item.url }}> />
<b id={`items-list-item-${item.id}`}> </Fragment>
{item.name} )}
</b>
</Link>
</ListItemGrid>
</DataListCell>
]}
/>
</DataListItemRow>
</DataListItem>
)))}
</DataList>
<Pagination
variant="bottom"
itemCount={itemCount}
page={queryParams.page || 1}
perPage={queryParams.page_size}
perPageOptions={showPageSizeOptions ? [
{ title: '5', value: 5 },
{ title: '10', value: 10 },
{ title: '20', value: 20 },
{ title: '50', value: 50 }
] : []}
onSetPage={this.handleSetPage}
onPerPageSelect={this.handleSetPageSize}
/>
</Fragment>
)}
</Fragment> </Fragment>
); );
} }
@@ -228,25 +176,17 @@ PaginatedDataList.propTypes = {
key: string.isRequired, key: string.isRequired,
isSortable: bool, isSortable: bool,
})), })),
additionalControls: arrayOf(node),
showSelectAll: PropTypes.bool,
isAllSelected: PropTypes.bool,
onSelectAll: PropTypes.func,
alignToolbarLeft: PropTypes.bool,
showPageSizeOptions: PropTypes.bool, showPageSizeOptions: PropTypes.bool,
renderToolbar: PropTypes.func,
}; };
PaginatedDataList.defaultProps = { PaginatedDataList.defaultProps = {
renderItem: null, renderItem: null,
toolbarColumns: [], toolbarColumns: [],
additionalControls: [],
itemName: 'item', itemName: 'item',
itemNamePlural: '', itemNamePlural: '',
showSelectAll: false,
isAllSelected: false,
onSelectAll: null,
alignToolbarLeft: false,
showPageSizeOptions: true, showPageSizeOptions: true,
renderToolbar: (props) => (<DataListToolbar {...props} />),
}; };
export { PaginatedDataList as _PaginatedDataList }; export { PaginatedDataList as _PaginatedDataList };

View File

@@ -0,0 +1,42 @@
import React from 'react';
import { Link } from 'react-router-dom';
import {
DataListItem,
DataListItemRow,
DataListItemCells,
DataListCell,
TextContent,
} from '@patternfly/react-core';
import styled from 'styled-components';
const DetailWrapper = styled(TextContent)`
display: grid;
grid-template-columns:
minmax(70px, max-content)
repeat(auto-fit, minmax(60px, max-content));
grid-gap: 10px;
`;
export default function PaginatedDataListItem ({ item }) {
return (
<DataListItem
aria-labelledby={`items-list-item-${item.id}`}
key={item.id}
>
<DataListItemRow>
<DataListItemCells dataListCells={[
<DataListCell key="team-name">
<DetailWrapper>
<Link to={{ pathname: item.url }}>
<b id={`items-list-item-${item.id}`}>
{item.name}
</b>
</Link>
</DetailWrapper>
</DataListCell>
]}
/>
</DataListItemRow>
</DataListItem>
);
}

View File

@@ -1,5 +1,6 @@
import PaginatedDataList from './PaginatedDataList'; import PaginatedDataList from './PaginatedDataList';
export default PaginatedDataList; export default PaginatedDataList;
export { default as PaginatedDataListItem } from './PaginatedDataListItem';
export { default as ToolbarDeleteButton } from './ToolbarDeleteButton'; export { default as ToolbarDeleteButton } from './ToolbarDeleteButton';
export { default as ToolbarAddButton } from './ToolbarAddButton'; export { default as ToolbarAddButton } from './ToolbarAddButton';

View File

@@ -3,6 +3,7 @@ import { withRouter } from 'react-router-dom';
import { withI18n } from '@lingui/react'; import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import PaginatedDataList, { ToolbarAddButton } from '../../../../components/PaginatedDataList'; import PaginatedDataList, { ToolbarAddButton } from '../../../../components/PaginatedDataList';
import DataListToolbar from '../../../../components/DataListToolbar';
import OrganizationAccessItem from '../../components/OrganizationAccessItem'; import OrganizationAccessItem from '../../components/OrganizationAccessItem';
import DeleteRoleConfirmationModal from '../../components/DeleteRoleConfirmationModal'; import DeleteRoleConfirmationModal from '../../components/DeleteRoleConfirmationModal';
import AddResourceRole from '../../../../components/AddRole/AddResourceRole'; import AddResourceRole from '../../../../components/AddRole/AddResourceRole';
@@ -172,9 +173,14 @@ class OrganizationAccess extends React.Component {
{ name: i18n._(t`Username`), key: 'username', isSortable: true }, { name: i18n._(t`Username`), key: 'username', isSortable: true },
{ name: i18n._(t`Last Name`), key: 'last_name', isSortable: true }, { name: i18n._(t`Last Name`), key: 'last_name', isSortable: true },
]} ]}
additionalControls={canEdit ? [ renderToolbar={(props) => (
<ToolbarAddButton key="add" onClick={this.toggleAddModal} /> <DataListToolbar
] : null} {...props}
additionalControls={canEdit ? [
<ToolbarAddButton key="add" onClick={this.toggleAddModal} />
] : null}
/>
)}
renderItem={accessRecord => ( renderItem={accessRecord => (
<OrganizationAccessItem <OrganizationAccessItem
key={accessRecord.id} key={accessRecord.id}

View File

@@ -13,6 +13,7 @@ import PaginatedDataList, {
ToolbarDeleteButton, ToolbarDeleteButton,
ToolbarAddButton ToolbarAddButton
} from '../../../components/PaginatedDataList'; } from '../../../components/PaginatedDataList';
import DataListToolbar from '../../../components/DataListToolbar';
import OrganizationListItem from '../components/OrganizationListItem'; import OrganizationListItem from '../components/OrganizationListItem';
import { getQSConfig, parseNamespacedQueryString } from '../../../util/qs'; import { getQSConfig, parseNamespacedQueryString } from '../../../util/qs';
@@ -163,20 +164,25 @@ class OrganizationsList extends Component {
{ name: i18n._(t`Modified`), key: 'modified', isSortable: true, isNumeric: true }, { name: i18n._(t`Modified`), key: 'modified', isSortable: true, isNumeric: true },
{ name: i18n._(t`Created`), key: 'created', isSortable: true, isNumeric: true }, { name: i18n._(t`Created`), key: 'created', isSortable: true, isNumeric: true },
]} ]}
showSelectAll renderToolbar={(props) => (
isAllSelected={isAllSelected} <DataListToolbar
onSelectAll={this.handleSelectAll} {...props}
additionalControls={[ showSelectAll
<ToolbarDeleteButton isAllSelected={isAllSelected}
key="delete" onSelectAll={this.handleSelectAll}
onDelete={this.handleOrgDelete} additionalControls={[
itemsToDelete={selected} <ToolbarDeleteButton
itemName="Organization" key="delete"
/>, onDelete={this.handleOrgDelete}
canAdd itemsToDelete={selected}
? <ToolbarAddButton key="add" linkTo={`${match.url}/add`} /> itemName="Organization"
: null, />,
]} canAdd
? <ToolbarAddButton key="add" linkTo={`${match.url}/add`} />
: null,
]}
/>
)}
renderItem={(o) => ( renderItem={(o) => (
<OrganizationListItem <OrganizationListItem
key={o.id} key={o.id}