Skip to content

Commit

Permalink
feat: update delete modal for dataset (#10258)
Browse files Browse the repository at this point in the history
* update delete modal for dataset

* update datasetList to use hooks

* fix typo on dataset delete modal
  • Loading branch information
lilykuang committed Jul 10, 2020
1 parent 4d17962 commit 80b06f6
Show file tree
Hide file tree
Showing 7 changed files with 446 additions and 355 deletions.
Expand Up @@ -18,8 +18,10 @@
*/
import React from 'react';
import { mount } from 'enzyme';
import { Modal, Button } from 'react-bootstrap';
import { Button } from 'react-bootstrap';
import { supersetTheme, ThemeProvider } from '@superset-ui/style';
import ConfirmStatusChange from 'src/components/ConfirmStatusChange';
import Modal from 'src/components/Modal';

describe('ConfirmStatusChange', () => {
const mockedProps = {
Expand All @@ -35,6 +37,10 @@ describe('ConfirmStatusChange', () => {
</>
)}
</ConfirmStatusChange>,
{
wrappingComponent: ThemeProvider,
wrappingComponentProps: { theme: supersetTheme },
},
);

it('opens a confirm modal', () => {
Expand Down
78 changes: 35 additions & 43 deletions superset-frontend/src/components/ConfirmStatusChange.tsx
Expand Up @@ -16,63 +16,55 @@
* specific language governing permissions and limitations
* under the License.
*/
import { t } from '@superset-ui/translation';
import * as React from 'react';
// @ts-ignore
import { Button, Modal } from 'react-bootstrap';
import React, { useState } from 'react';
import DeleteModal from 'src/components/DeleteModal';

type Callback = (...args: any[]) => void;
interface Props {
title: string | React.ReactNode;
description: string | React.ReactNode;
interface ConfirmStatusChangeProps {
title: React.ReactNode;
description: React.ReactNode;
onConfirm: Callback;
children: (showConfirm: Callback) => React.ReactNode;
}

interface State {
callbackArgs: any[];
open: boolean;
}
export default class ConfirmStatusChange extends React.Component<Props, State> {
public state = {
callbackArgs: [],
open: false,
};
export default function ConfirmStatusChange({
title,
description,
onConfirm,
children,
}: ConfirmStatusChangeProps) {
const [open, setOpen] = useState(false);
const [currentCallbackArgs, setCurrentCallbackArgs] = useState<any[]>([]);

public showConfirm = (...callbackArgs: any[]) => {
const showConfirm = (...callbackArgs: any[]) => {
// check if any args are DOM events, if so, call persist
callbackArgs.forEach(
arg => arg && typeof arg.persist === 'function' && arg.persist(),
);

this.setState({
callbackArgs,
open: true,
});
setOpen(true);
setCurrentCallbackArgs(callbackArgs);
};

public hide = () => this.setState({ open: false, callbackArgs: [] });
const hide = () => {
setOpen(false);
setCurrentCallbackArgs([]);
};

public confirm = () => {
this.props.onConfirm(...this.state.callbackArgs);
this.hide();
const confirm = () => {
onConfirm(...currentCallbackArgs);
hide();
};

public render() {
return (
<>
{this.props.children && this.props.children(this.showConfirm)}
<Modal show={this.state.open} onHide={this.hide}>
<Modal.Header closeButton>{this.props.title}</Modal.Header>
<Modal.Body>{this.props.description}</Modal.Body>
<Modal.Footer>
<Button onClick={this.hide}>{t('Cancel')}</Button>
<Button bsStyle="danger" onClick={this.confirm}>
{t('OK')}
</Button>
</Modal.Footer>
</Modal>
</>
);
}
return (
<>
{children && children(showConfirm)}
<DeleteModal
description={description}
onConfirm={confirm}
onHide={hide}
open={open}
title={title}
/>
</>
);
}
80 changes: 80 additions & 0 deletions superset-frontend/src/components/DeleteModal.tsx
@@ -0,0 +1,80 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { t } from '@superset-ui/translation';
import React, { useState } from 'react';
import styled from '@superset-ui/style';
import { FormGroup, FormControl } from 'react-bootstrap';
import Modal from 'src/components/Modal';

const StyleFormGroup = styled(FormGroup)`
padding-top: 8px;
width: 50%;
label {
color: ${({ theme }) => theme.colors.grayscale.light1};
text-transform: uppercase;
}
`;

const DescriptionContainer = styled.div`
line-height: 40px;
padding-top: 16px;
`;

interface DeleteModalProps {
description: React.ReactNode;
onConfirm: () => void;
onHide: () => void;
open: boolean;
title: React.ReactNode;
}

export default function DeleteModal({
description,
onConfirm,
onHide,
open,
title,
}: DeleteModalProps) {
const [disableChange, setDisableChange] = useState(true);

return (
<Modal
disablePrimaryButton={disableChange}
onHide={onHide}
onHandledPrimaryAction={onConfirm}
primaryButtonName={t('delete')}
primaryButtonType="danger"
show={open}
title={title}
>
<DescriptionContainer>{description}</DescriptionContainer>
<StyleFormGroup>
<label htmlFor="delete">{t('type delete to confirm')}</label>
<FormControl
id="delete"
type="text"
// @ts-ignore
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
setDisableChange(event.target.value !== 'DELETE')
}
/>
</StyleFormGroup>
</Modal>
);
}
Expand Up @@ -20,13 +20,15 @@ import React from 'react';
import styled from '@superset-ui/style';
import { Modal as BaseModal } from 'react-bootstrap';
import { t } from '@superset-ui/translation';
import Button from './Button';
import Button from '../views/datasetList/Button';

interface ModalProps {
children: React.ReactNode;
disableSave: boolean;
disablePrimaryButton?: boolean;
onHide: () => void;
onSave: () => void;
onHandledPrimaryAction: () => void;
primaryButtonName: string;
primaryButtonType?: 'primary' | 'danger';
show: boolean;
title: React.ReactNode;
}
Expand All @@ -45,8 +47,7 @@ const StyledModal = styled(BaseModal)`
}
.modal-body {
padding: 18px 0 340px 18px;
width: 65%;
padding: 18px;
}
.modal-footer {
Expand All @@ -66,9 +67,11 @@ const Title = styled.div`

export default function Modal({
children,
disableSave,
disablePrimaryButton = false,
onHandledPrimaryAction,
onHide,
onSave,
primaryButtonName,
primaryButtonType = 'primary',
show,
title,
}: ModalProps) {
Expand All @@ -83,8 +86,12 @@ export default function Modal({
<BaseModal.Footer>
<span className="float-right">
<Button onClick={onHide}>{t('Cancel')}</Button>
<Button bsStyle="primary" disabled={disableSave} onClick={onSave}>
{t('Add')}
<Button
bsStyle={primaryButtonType}
disabled={disablePrimaryButton}
onClick={onHandledPrimaryAction}
>
{primaryButtonName}
</Button>
</span>
</BaseModal.Footer>
Expand Down
7 changes: 6 additions & 1 deletion superset-frontend/src/views/datasetList/Button.tsx
Expand Up @@ -25,7 +25,7 @@ interface ModalProps {
disabled?: boolean;
onClick: () => void;
padding?: number;
bsStyle?: 'default' | 'primary';
bsStyle?: 'default' | 'primary' | 'danger';
width?: number;
}

Expand All @@ -51,6 +51,11 @@ const StyledButton = styled(BaseButton)`
background-color: ${({ theme }) => theme.colors.primary.base};
color: ${({ theme }) => theme.colors.grayscale.light5};
}
&.btn-danger,
&.btn-danger:hover {
background-color: ${({ theme }) => theme.colors.error.base};
color: ${({ theme }) => theme.colors.grayscale.light5};
}
`;

export default function Modal({
Expand Down

0 comments on commit 80b06f6

Please sign in to comment.