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 { withI18n } from '@lingui/react';
import { t } from '@lingui/macro';
@ -51,12 +51,6 @@ function DataListToolbar({
setKebabIsOpen(false);
};
useEffect(() => {
if (!kebabModalIsOpen) {
setKebabIsOpen(false);
}
}, [kebabModalIsOpen]);
return (
<Toolbar
id={`${qsConfig.namespace}-list-toolbar`}
@ -98,7 +92,7 @@ function DataListToolbar({
</ToolbarToggleGroup>
{showExpandCollapse && (
<ToolbarGroup>
<Fragment>
<>
<ToolbarItem>
<ExpandCollapse
isCompact={isCompact}
@ -106,7 +100,7 @@ function DataListToolbar({
onExpand={onExpand}
/>
</ToolbarItem>
</Fragment>
</>
</ToolbarGroup>
)}
{advancedSearchShown && (
@ -114,7 +108,12 @@ function DataListToolbar({
<KebabifiedProvider
value={{
isKebabified: true,
onKebabModalChange: isOpen => setKebabModalIsOpen(isOpen),
onKebabModalChange: isOpen => {
if (kebabIsOpen && kebabModalIsOpen && !isOpen) {
setKebabIsOpen(false);
}
setKebabModalIsOpen(isOpen);
},
}}
>
<Dropdown
@ -127,7 +126,7 @@ function DataListToolbar({
}}
/>
}
isOpen={kebabIsOpen}
isOpen={kebabIsOpen || kebabModalIsOpen}
isPlain
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 { t } from '@lingui/macro';
import { arrayOf, func } from 'prop-types';
import { Button, DropdownItem, Tooltip } from '@patternfly/react-core';
import { Kebabified } from '../../contexts/Kebabified';
import { KebabifiedContext } from '../../contexts/Kebabified';
import AlertModal from '../AlertModal';
import { Job } from '../../types';
@ -12,13 +12,21 @@ function cannotCancel(job) {
}
function JobListCancelButton({ i18n, jobsToCancel, onCancel }) {
const { isKebabified, onKebabModalChange } = useContext(KebabifiedContext);
const [isModalOpen, setIsModalOpen] = useState(false);
const numJobsToCancel = jobsToCancel.length;
const zeroOrOneJobSelected = numJobsToCancel < 2;
const handleCancel = () => {
const handleCancelJob = () => {
onCancel();
setIsModalOpen(false);
toggleModal();
};
const toggleModal = () => {
if (isKebabified) {
onKebabModalChange(!isModalOpen);
}
setIsModalOpen(!isModalOpen);
};
const renderTooltip = () => {
@ -61,96 +69,74 @@ function JobListCancelButton({ i18n, jobsToCancel, onCancel }) {
);
return (
<Kebabified>
{({ isKebabified, onKebabModalChange }) => (
<>
{isKebabified ? (
<DropdownItem
key="cancel-job"
<>
{isKebabified ? (
<DropdownItem
key="cancel-job"
isDisabled={isDisabled}
component="button"
onClick={toggleModal}
>
{cancelJobText}
</DropdownItem>
) : (
<Tooltip content={renderTooltip()} position="top">
<div>
<Button
variant="secondary"
aria-label={cancelJobText}
onClick={toggleModal}
isDisabled={isDisabled}
component="button"
onClick={() => {
onKebabModalChange(true);
setIsModalOpen(true);
}}
>
{cancelJobText}
</DropdownItem>
) : (
<Tooltip content={renderTooltip()} position="top">
<div>
<Button
variant="secondary"
aria-label={cancelJobText}
onClick={() => setIsModalOpen(true)}
isDisabled={isDisabled}
>
{cancelJobText}
</Button>
</div>
</Tooltip>
)}
{isModalOpen && (
<AlertModal
variant="danger"
title={cancelJobText}
isOpen={isModalOpen}
onClose={() => {
if (isKebabified) {
onKebabModalChange(false);
}
setIsModalOpen(false);
}}
actions={[
<Button
id="cancel-job-confirm-button"
key="delete"
variant="danger"
aria-label={cancelJobText}
onClick={() => {
if (isKebabified) {
onKebabModalChange(false);
}
handleCancel();
}}
>
{cancelJobText}
</Button>,
<Button
id="cancel-job-return-button"
key="cancel"
variant="secondary"
aria-label={i18n._(t`Return`)}
onClick={() => {
if (isKebabified) {
onKebabModalChange(false);
}
setIsModalOpen(false);
}}
>
{i18n._(t`Return`)}
</Button>,
]}
>
<div>
{i18n._(
'{numJobsToCancel, plural, one {This action will cancel the following job:} other {This action will cancel the following jobs:}}',
{
numJobsToCancel,
}
)}
</div>
{jobsToCancel.map(job => (
<span key={job.id}>
<strong>{job.name}</strong>
<br />
</span>
))}
</AlertModal>
)}
</>
</Button>
</div>
</Tooltip>
)}
</Kebabified>
{isModalOpen && (
<AlertModal
variant="danger"
title={cancelJobText}
isOpen={isModalOpen}
onClose={toggleModal}
actions={[
<Button
id="cancel-job-confirm-button"
key="delete"
variant="danger"
aria-label={cancelJobText}
onClick={handleCancelJob}
>
{cancelJobText}
</Button>,
<Button
id="cancel-job-return-button"
key="cancel"
variant="secondary"
aria-label={i18n._(t`Return`)}
onClick={toggleModal}
>
{i18n._(t`Return`)}
</Button>,
]}
>
<div>
{i18n._(
'{numJobsToCancel, plural, one {This action will cancel the following job:} other {This action will cancel the following jobs:}}',
{
numJobsToCancel,
}
)}
</div>
{jobsToCancel.map(job => (
<span key={job.id}>
<strong>{job.name}</strong>
<br />
</span>
))}
</AlertModal>
)}
</>
);
}

View File

@ -1,4 +1,4 @@
import React, { Fragment } from 'react';
import React, { useContext, useState } from 'react';
import {
func,
bool,
@ -12,7 +12,7 @@ import { Button, DropdownItem, Tooltip } from '@patternfly/react-core';
import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro';
import AlertModal from '../AlertModal';
import { Kebabified } from '../../contexts/Kebabified';
import { KebabifiedContext } from '../../contexts/Kebabified';
const requireNameOrUsername = props => {
const { name, username } = props;
@ -59,53 +59,29 @@ function cannotDelete(item) {
return !item.summary_fields.user_capabilities.delete;
}
class ToolbarDeleteButton extends React.Component {
static propTypes = {
onDelete: func.isRequired,
itemsToDelete: arrayOf(ItemToDelete).isRequired,
pluralizedItemName: string,
errorMessage: string,
};
function ToolbarDeleteButton({
itemsToDelete,
pluralizedItemName,
errorMessage,
onDelete,
i18n,
}) {
const { isKebabified, onKebabModalChange } = useContext(KebabifiedContext);
const [isModalOpen, setIsModalOpen] = useState(false);
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;
const handleDelete = () => {
onDelete();
this.setState({ isModalOpen: false });
}
toggleModal();
};
renderTooltip() {
const {
itemsToDelete,
pluralizedItemName,
errorMessage,
i18n,
} = this.props;
const toggleModal = () => {
if (isKebabified) {
onKebabModalChange(!isModalOpen);
}
setIsModalOpen(!isModalOpen);
};
const renderTooltip = () => {
const itemsUnableToDelete = itemsToDelete
.filter(cannotDelete)
.map(item => item.name)
@ -125,103 +101,89 @@ class ToolbarDeleteButton extends React.Component {
return i18n._(t`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 =
itemsToDelete.length === 0 || itemsToDelete.some(cannotDelete);
const isDisabled =
itemsToDelete.length === 0 || itemsToDelete.some(cannotDelete);
// NOTE: Once PF supports tooltips on disabled elements,
// we can delete the extra <div> around the <DeleteButton> below.
// See: https://github.com/patternfly/patternfly-react/issues/1894
return (
<Kebabified>
{({ isKebabified, onKebabModalChange }) => (
<Fragment>
{isKebabified ? (
<DropdownItem
key="add"
isDisabled={isDisabled}
component="button"
onClick={() => {
onKebabModalChange(true);
this.handleConfirmDelete();
}}
>
{i18n._(t`Delete`)}
</DropdownItem>
) : (
<Tooltip content={this.renderTooltip()} position="top">
<div>
<Button
variant="secondary"
aria-label={i18n._(t`Delete`)}
onClick={this.handleConfirmDelete}
isDisabled={isDisabled}
>
{i18n._(t`Delete`)}
</Button>
</div>
</Tooltip>
)}
{isModalOpen && (
<AlertModal
variant="danger"
title={modalTitle}
isOpen={isModalOpen}
onClose={() => {
if (isKebabified) {
onKebabModalChange(false);
}
this.handleCancelDelete();
}}
actions={[
<Button
key="delete"
variant="danger"
aria-label={i18n._(t`confirm delete`)}
onClick={() => {
if (isKebabified) {
onKebabModalChange(false);
}
this.handleDelete();
}}
>
{i18n._(t`Delete`)}
</Button>,
<Button
key="cancel"
variant="secondary"
aria-label={i18n._(t`cancel delete`)}
onClick={() => {
if (isKebabified) {
onKebabModalChange(false);
}
this.handleCancelDelete();
}}
>
{i18n._(t`Cancel`)}
</Button>,
]}
>
<div>{i18n._(t`This action will delete the following:`)}</div>
{itemsToDelete.map(item => (
<span key={item.id}>
<strong>{item.name || item.username}</strong>
<br />
</span>
))}
</AlertModal>
)}
</Fragment>
)}
</Kebabified>
);
}
// NOTE: Once PF supports tooltips on disabled elements,
// we can delete the extra <div> around the <DeleteButton> below.
// See: https://github.com/patternfly/patternfly-react/issues/1894
return (
<>
{isKebabified ? (
<DropdownItem
key="add"
isDisabled={isDisabled}
component="button"
onClick={toggleModal}
>
{i18n._(t`Delete`)}
</DropdownItem>
) : (
<Tooltip content={renderTooltip()} position="top">
<div>
<Button
variant="secondary"
aria-label={i18n._(t`Delete`)}
onClick={toggleModal}
isDisabled={isDisabled}
>
{i18n._(t`Delete`)}
</Button>
</div>
</Tooltip>
)}
{isModalOpen && (
<AlertModal
variant="danger"
title={modalTitle}
isOpen={isModalOpen}
onClose={toggleModal}
actions={[
<Button
key="delete"
variant="danger"
aria-label={i18n._(t`confirm delete`)}
onClick={handleDelete}
>
{i18n._(t`Delete`)}
</Button>,
<Button
key="cancel"
variant="secondary"
aria-label={i18n._(t`cancel delete`)}
onClick={toggleModal}
>
{i18n._(t`Cancel`)}
</Button>,
]}
>
<div>{i18n._(t`This action will delete the following:`)}</div>
{itemsToDelete.map(item => (
<span key={item.id}>
<strong>{item.name || item.username}</strong>
<br />
</span>
))}
</AlertModal>
)}
</>
);
}
ToolbarDeleteButton.propTypes = {
onDelete: func.isRequired,
itemsToDelete: arrayOf(ItemToDelete).isRequired,
pluralizedItemName: string,
errorMessage: string,
};
ToolbarDeleteButton.defaultProps = {
pluralizedItemName: 'Items',
errorMessage: '',
};
export default withI18n()(ToolbarDeleteButton);