Refactor kebab modal tracking logic in delete/cancel buttons

This commit is contained in:
mabashian
2020-09-10 13:36:05 -04:00
parent c8a07309ee
commit 0fc6affe85
3 changed files with 187 additions and 240 deletions

View File

@@ -1,4 +1,4 @@
import React, { Fragment, useEffect, useState } from 'react'; import React, { useState } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { withI18n } from '@lingui/react'; import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
@@ -51,12 +51,6 @@ function DataListToolbar({
setKebabIsOpen(false); setKebabIsOpen(false);
}; };
useEffect(() => {
if (!kebabModalIsOpen) {
setKebabIsOpen(false);
}
}, [kebabModalIsOpen]);
return ( return (
<Toolbar <Toolbar
id={`${qsConfig.namespace}-list-toolbar`} id={`${qsConfig.namespace}-list-toolbar`}
@@ -98,7 +92,7 @@ function DataListToolbar({
</ToolbarToggleGroup> </ToolbarToggleGroup>
{showExpandCollapse && ( {showExpandCollapse && (
<ToolbarGroup> <ToolbarGroup>
<Fragment> <>
<ToolbarItem> <ToolbarItem>
<ExpandCollapse <ExpandCollapse
isCompact={isCompact} isCompact={isCompact}
@@ -106,7 +100,7 @@ function DataListToolbar({
onExpand={onExpand} onExpand={onExpand}
/> />
</ToolbarItem> </ToolbarItem>
</Fragment> </>
</ToolbarGroup> </ToolbarGroup>
)} )}
{advancedSearchShown && ( {advancedSearchShown && (
@@ -114,7 +108,12 @@ function DataListToolbar({
<KebabifiedProvider <KebabifiedProvider
value={{ value={{
isKebabified: true, isKebabified: true,
onKebabModalChange: isOpen => setKebabModalIsOpen(isOpen), onKebabModalChange: isOpen => {
if (kebabIsOpen && kebabModalIsOpen && !isOpen) {
setKebabIsOpen(false);
}
setKebabModalIsOpen(isOpen);
},
}} }}
> >
<Dropdown <Dropdown
@@ -127,7 +126,7 @@ function DataListToolbar({
}} }}
/> />
} }
isOpen={kebabIsOpen} isOpen={kebabIsOpen || kebabModalIsOpen}
isPlain isPlain
dropdownItems={additionalControls} dropdownItems={additionalControls}
/> />

View File

@@ -1,9 +1,9 @@
import React, { useState } from 'react'; import React, { useContext, useState } from 'react';
import { withI18n } from '@lingui/react'; import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { arrayOf, func } from 'prop-types'; import { arrayOf, func } from 'prop-types';
import { Button, DropdownItem, Tooltip } from '@patternfly/react-core'; import { Button, DropdownItem, Tooltip } from '@patternfly/react-core';
import { Kebabified } from '../../contexts/Kebabified'; import { KebabifiedContext } from '../../contexts/Kebabified';
import AlertModal from '../AlertModal'; import AlertModal from '../AlertModal';
import { Job } from '../../types'; import { Job } from '../../types';
@@ -12,13 +12,21 @@ function cannotCancel(job) {
} }
function JobListCancelButton({ i18n, jobsToCancel, onCancel }) { function JobListCancelButton({ i18n, jobsToCancel, onCancel }) {
const { isKebabified, onKebabModalChange } = useContext(KebabifiedContext);
const [isModalOpen, setIsModalOpen] = useState(false); const [isModalOpen, setIsModalOpen] = useState(false);
const numJobsToCancel = jobsToCancel.length; const numJobsToCancel = jobsToCancel.length;
const zeroOrOneJobSelected = numJobsToCancel < 2; const zeroOrOneJobSelected = numJobsToCancel < 2;
const handleCancel = () => { const handleCancelJob = () => {
onCancel(); onCancel();
setIsModalOpen(false); toggleModal();
};
const toggleModal = () => {
if (isKebabified) {
onKebabModalChange(!isModalOpen);
}
setIsModalOpen(!isModalOpen);
}; };
const renderTooltip = () => { const renderTooltip = () => {
@@ -61,18 +69,13 @@ function JobListCancelButton({ i18n, jobsToCancel, onCancel }) {
); );
return ( return (
<Kebabified>
{({ isKebabified, onKebabModalChange }) => (
<> <>
{isKebabified ? ( {isKebabified ? (
<DropdownItem <DropdownItem
key="cancel-job" key="cancel-job"
isDisabled={isDisabled} isDisabled={isDisabled}
component="button" component="button"
onClick={() => { onClick={toggleModal}
onKebabModalChange(true);
setIsModalOpen(true);
}}
> >
{cancelJobText} {cancelJobText}
</DropdownItem> </DropdownItem>
@@ -82,7 +85,7 @@ function JobListCancelButton({ i18n, jobsToCancel, onCancel }) {
<Button <Button
variant="secondary" variant="secondary"
aria-label={cancelJobText} aria-label={cancelJobText}
onClick={() => setIsModalOpen(true)} onClick={toggleModal}
isDisabled={isDisabled} isDisabled={isDisabled}
> >
{cancelJobText} {cancelJobText}
@@ -95,24 +98,14 @@ function JobListCancelButton({ i18n, jobsToCancel, onCancel }) {
variant="danger" variant="danger"
title={cancelJobText} title={cancelJobText}
isOpen={isModalOpen} isOpen={isModalOpen}
onClose={() => { onClose={toggleModal}
if (isKebabified) {
onKebabModalChange(false);
}
setIsModalOpen(false);
}}
actions={[ actions={[
<Button <Button
id="cancel-job-confirm-button" id="cancel-job-confirm-button"
key="delete" key="delete"
variant="danger" variant="danger"
aria-label={cancelJobText} aria-label={cancelJobText}
onClick={() => { onClick={handleCancelJob}
if (isKebabified) {
onKebabModalChange(false);
}
handleCancel();
}}
> >
{cancelJobText} {cancelJobText}
</Button>, </Button>,
@@ -121,12 +114,7 @@ function JobListCancelButton({ i18n, jobsToCancel, onCancel }) {
key="cancel" key="cancel"
variant="secondary" variant="secondary"
aria-label={i18n._(t`Return`)} aria-label={i18n._(t`Return`)}
onClick={() => { onClick={toggleModal}
if (isKebabified) {
onKebabModalChange(false);
}
setIsModalOpen(false);
}}
> >
{i18n._(t`Return`)} {i18n._(t`Return`)}
</Button>, </Button>,
@@ -149,8 +137,6 @@ function JobListCancelButton({ i18n, jobsToCancel, onCancel }) {
</AlertModal> </AlertModal>
)} )}
</> </>
)}
</Kebabified>
); );
} }

View File

@@ -1,4 +1,4 @@
import React, { Fragment } from 'react'; import React, { useContext, useState } from 'react';
import { import {
func, func,
bool, bool,
@@ -12,7 +12,7 @@ import { Button, DropdownItem, Tooltip } from '@patternfly/react-core';
import { withI18n } from '@lingui/react'; import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import AlertModal from '../AlertModal'; import AlertModal from '../AlertModal';
import { Kebabified } from '../../contexts/Kebabified'; import { KebabifiedContext } from '../../contexts/Kebabified';
const requireNameOrUsername = props => { const requireNameOrUsername = props => {
const { name, username } = props; const { name, username } = props;
@@ -59,53 +59,29 @@ function cannotDelete(item) {
return !item.summary_fields.user_capabilities.delete; return !item.summary_fields.user_capabilities.delete;
} }
class ToolbarDeleteButton extends React.Component { function ToolbarDeleteButton({
static propTypes = {
onDelete: func.isRequired,
itemsToDelete: arrayOf(ItemToDelete).isRequired,
pluralizedItemName: string,
errorMessage: string,
};
static defaultProps = {
pluralizedItemName: 'Items',
errorMessage: '',
};
constructor(props) {
super(props);
this.state = {
isModalOpen: false,
};
this.handleConfirmDelete = this.handleConfirmDelete.bind(this);
this.handleCancelDelete = this.handleCancelDelete.bind(this);
this.handleDelete = this.handleDelete.bind(this);
}
handleConfirmDelete() {
this.setState({ isModalOpen: true });
}
handleCancelDelete() {
this.setState({ isModalOpen: false });
}
handleDelete() {
const { onDelete } = this.props;
onDelete();
this.setState({ isModalOpen: false });
}
renderTooltip() {
const {
itemsToDelete, itemsToDelete,
pluralizedItemName, pluralizedItemName,
errorMessage, errorMessage,
onDelete,
i18n, i18n,
} = this.props; }) {
const { isKebabified, onKebabModalChange } = useContext(KebabifiedContext);
const [isModalOpen, setIsModalOpen] = useState(false);
const handleDelete = () => {
onDelete();
toggleModal();
};
const toggleModal = () => {
if (isKebabified) {
onKebabModalChange(!isModalOpen);
}
setIsModalOpen(!isModalOpen);
};
const renderTooltip = () => {
const itemsUnableToDelete = itemsToDelete const itemsUnableToDelete = itemsToDelete
.filter(cannotDelete) .filter(cannotDelete)
.map(item => item.name) .map(item => item.name)
@@ -125,11 +101,8 @@ class ToolbarDeleteButton extends React.Component {
return i18n._(t`Delete`); return i18n._(t`Delete`);
} }
return i18n._(t`Select a row to delete`); return i18n._(t`Select a row to delete`);
} };
render() {
const { itemsToDelete, pluralizedItemName, i18n } = this.props;
const { isModalOpen } = this.state;
const modalTitle = i18n._(t`Delete ${pluralizedItemName}?`); const modalTitle = i18n._(t`Delete ${pluralizedItemName}?`);
const isDisabled = const isDisabled =
@@ -139,28 +112,23 @@ class ToolbarDeleteButton extends React.Component {
// we can delete the extra <div> around the <DeleteButton> below. // we can delete the extra <div> around the <DeleteButton> below.
// See: https://github.com/patternfly/patternfly-react/issues/1894 // See: https://github.com/patternfly/patternfly-react/issues/1894
return ( return (
<Kebabified> <>
{({ isKebabified, onKebabModalChange }) => (
<Fragment>
{isKebabified ? ( {isKebabified ? (
<DropdownItem <DropdownItem
key="add" key="add"
isDisabled={isDisabled} isDisabled={isDisabled}
component="button" component="button"
onClick={() => { onClick={toggleModal}
onKebabModalChange(true);
this.handleConfirmDelete();
}}
> >
{i18n._(t`Delete`)} {i18n._(t`Delete`)}
</DropdownItem> </DropdownItem>
) : ( ) : (
<Tooltip content={this.renderTooltip()} position="top"> <Tooltip content={renderTooltip()} position="top">
<div> <div>
<Button <Button
variant="secondary" variant="secondary"
aria-label={i18n._(t`Delete`)} aria-label={i18n._(t`Delete`)}
onClick={this.handleConfirmDelete} onClick={toggleModal}
isDisabled={isDisabled} isDisabled={isDisabled}
> >
{i18n._(t`Delete`)} {i18n._(t`Delete`)}
@@ -173,23 +141,13 @@ class ToolbarDeleteButton extends React.Component {
variant="danger" variant="danger"
title={modalTitle} title={modalTitle}
isOpen={isModalOpen} isOpen={isModalOpen}
onClose={() => { onClose={toggleModal}
if (isKebabified) {
onKebabModalChange(false);
}
this.handleCancelDelete();
}}
actions={[ actions={[
<Button <Button
key="delete" key="delete"
variant="danger" variant="danger"
aria-label={i18n._(t`confirm delete`)} aria-label={i18n._(t`confirm delete`)}
onClick={() => { onClick={handleDelete}
if (isKebabified) {
onKebabModalChange(false);
}
this.handleDelete();
}}
> >
{i18n._(t`Delete`)} {i18n._(t`Delete`)}
</Button>, </Button>,
@@ -197,12 +155,7 @@ class ToolbarDeleteButton extends React.Component {
key="cancel" key="cancel"
variant="secondary" variant="secondary"
aria-label={i18n._(t`cancel delete`)} aria-label={i18n._(t`cancel delete`)}
onClick={() => { onClick={toggleModal}
if (isKebabified) {
onKebabModalChange(false);
}
this.handleCancelDelete();
}}
> >
{i18n._(t`Cancel`)} {i18n._(t`Cancel`)}
</Button>, </Button>,
@@ -217,11 +170,20 @@ class ToolbarDeleteButton extends React.Component {
))} ))}
</AlertModal> </AlertModal>
)} )}
</Fragment> </>
)}
</Kebabified>
); );
} }
}
ToolbarDeleteButton.propTypes = {
onDelete: func.isRequired,
itemsToDelete: arrayOf(ItemToDelete).isRequired,
pluralizedItemName: string,
errorMessage: string,
};
ToolbarDeleteButton.defaultProps = {
pluralizedItemName: 'Items',
errorMessage: '',
};
export default withI18n()(ToolbarDeleteButton); export default withI18n()(ToolbarDeleteButton);