From 0b421004ea3a41aa73fba414010ecbe5fa687f20 Mon Sep 17 00:00:00 2001 From: Patrick O'Carroll Date: Fri, 20 Jul 2018 09:59:04 +0200 Subject: [PATCH 1/3] built a component for delete button in tables, instead of using a modal to confirm it now does it in the row of the table, created a sass file for the component, the component uses css transitions for animation --- public/app/containers/Teams/TeamList.tsx | 19 +---- .../components/DeleteButton/DeleteButton.tsx | 78 +++++++++++++++++++ public/sass/_grafana.scss | 1 + public/sass/components/_delete_button.scss | 49 ++++++++++++ 4 files changed, 131 insertions(+), 16 deletions(-) create mode 100644 public/app/core/components/DeleteButton/DeleteButton.tsx create mode 100644 public/sass/components/_delete_button.scss diff --git a/public/app/containers/Teams/TeamList.tsx b/public/app/containers/Teams/TeamList.tsx index 4429764b1cc..475f8762c69 100644 --- a/public/app/containers/Teams/TeamList.tsx +++ b/public/app/containers/Teams/TeamList.tsx @@ -6,6 +6,7 @@ import { NavStore } from 'app/stores/NavStore/NavStore'; import { TeamsStore, ITeam } from 'app/stores/TeamsStore/TeamsStore'; import { BackendSrv } from 'app/core/services/backend_srv'; import appEvents from 'app/core/app_events'; +import DeleteButton from 'app/core/components/DeleteButton/DeleteButton'; interface Props { nav: typeof NavStore.Type; @@ -28,18 +29,6 @@ export class TeamList extends React.Component { } deleteTeam(team: ITeam) { - appEvents.emit('confirm-modal', { - title: 'Delete', - text: 'Are you sure you want to delete Team ' + team.name + '?', - yesText: 'Delete', - icon: 'fa-warning', - onConfirm: () => { - this.deleteTeamConfirmed(team); - }, - }); - } - - deleteTeamConfirmed(team) { this.props.backendSrv.delete('/api/teams/' + team.id).then(this.fetchTeams.bind(this)); } @@ -67,9 +56,7 @@ export class TeamList extends React.Component { {team.memberCount} - this.deleteTeam(team)} className="btn btn-danger btn-small"> - - + this.deleteTeam(team)} /> ); @@ -102,7 +89,7 @@ export class TeamList extends React.Component { -
+
diff --git a/public/app/core/components/DeleteButton/DeleteButton.tsx b/public/app/core/components/DeleteButton/DeleteButton.tsx new file mode 100644 index 00000000000..61a322b591e --- /dev/null +++ b/public/app/core/components/DeleteButton/DeleteButton.tsx @@ -0,0 +1,78 @@ +import React, { Component } from 'react'; + +export default class DeleteButton extends Component { + state = { + deleteButton: 'delete-button--show', + confirmSpan: 'confirm-delete--removed', + }; + + handleDelete = event => { + if (event) { + event.preventDefault(); + } + + this.setState({ + deleteButton: 'delete-button--hide', + }); + + setTimeout(() => { + this.setState({ + deleteButton: 'delete-button--removed', + }); + }, 100); + + setTimeout(() => { + this.setState({ + confirmSpan: 'confirm-delete--hide', + }); + }, 100); + + setTimeout(() => { + this.setState({ + confirmSpan: 'confirm-delete--show', + }); + }, 150); + }; + + cancelDelete = event => { + event.preventDefault(); + + this.setState({ + confirmSpan: 'confirm-delete--hide', + }); + + setTimeout(() => { + this.setState({ + confirmSpan: 'confirm-delete--removed', + deleteButton: 'delete-button--hide', + }); + }, 140); + + setTimeout(() => { + this.setState({ + deleteButton: 'delete-button--show', + }); + }, 190); + }; + + render() { + const { confirmDelete } = this.props; + return ( + + + + + + + + Cancel + + + Confirm Delete + + + + + ); + } +} diff --git a/public/sass/_grafana.scss b/public/sass/_grafana.scss index 9e3bec267ed..3a72bd45a1a 100644 --- a/public/sass/_grafana.scss +++ b/public/sass/_grafana.scss @@ -93,6 +93,7 @@ @import 'components/form_select_box'; @import 'components/user-picker'; @import 'components/description-picker'; +@import 'components/delete_button'; // PAGES @import 'pages/login'; diff --git a/public/sass/components/_delete_button.scss b/public/sass/components/_delete_button.scss new file mode 100644 index 00000000000..19f32189d81 --- /dev/null +++ b/public/sass/components/_delete_button.scss @@ -0,0 +1,49 @@ +.delete-button-container { + max-width: 24px; + width: 24px; + direction: rtl; + max-height: 38px; + display: block; +} + +.confirm-delete-container { + overflow: hidden; + width: 145px; + display: block; +} + +.delete-button { + &--show { + display: inline-block; + opacity: 1; + transition: opacity 0.1s ease; + } + + &--hide { + display: inline-block; + opacity: 0; + transition: opacity 0.1s ease; + } + &--removed { + display: none; + } +} + +.confirm-delete { + &--show { + display: inline-block; + opacity: 1; + transition: opacity 0.08s ease-out, transform 0.1s ease-out; + transform: translateX(0); + } + + &--hide { + display: inline-block; + opacity: 0; + transition: opacity 0.12s ease-in, transform 0.14s ease-in; + transform: translateX(100px); + } + &--removed { + display: none; + } +} From b8a4b7771ae72660fd16022920265750ce42e073 Mon Sep 17 00:00:00 2001 From: Patrick O'Carroll Date: Fri, 20 Jul 2018 11:09:24 +0200 Subject: [PATCH 2/3] removed import appEvents --- public/app/containers/Teams/TeamList.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/public/app/containers/Teams/TeamList.tsx b/public/app/containers/Teams/TeamList.tsx index 475f8762c69..87d24f8ddd4 100644 --- a/public/app/containers/Teams/TeamList.tsx +++ b/public/app/containers/Teams/TeamList.tsx @@ -5,7 +5,6 @@ import PageHeader from 'app/core/components/PageHeader/PageHeader'; import { NavStore } from 'app/stores/NavStore/NavStore'; import { TeamsStore, ITeam } from 'app/stores/TeamsStore/TeamsStore'; import { BackendSrv } from 'app/core/services/backend_srv'; -import appEvents from 'app/core/app_events'; import DeleteButton from 'app/core/components/DeleteButton/DeleteButton'; interface Props { From f4ab432542383c726d517f7a70000460d69ac4b3 Mon Sep 17 00:00:00 2001 From: Patrick O'Carroll Date: Wed, 25 Jul 2018 10:29:55 +0200 Subject: [PATCH 3/3] added position absolute and some flexbox so I could remov changes in display and setTimeout, added tests and types, did some renaming --- public/app/containers/Teams/TeamList.tsx | 2 +- .../DeleteButton/DeleteButton.jest.tsx | 44 ++++++++++ .../components/DeleteButton/DeleteButton.tsx | 82 ++++++++----------- public/sass/components/_delete_button.scss | 37 +++++---- 4 files changed, 99 insertions(+), 66 deletions(-) create mode 100644 public/app/core/components/DeleteButton/DeleteButton.jest.tsx diff --git a/public/app/containers/Teams/TeamList.tsx b/public/app/containers/Teams/TeamList.tsx index 87d24f8ddd4..b86763d8799 100644 --- a/public/app/containers/Teams/TeamList.tsx +++ b/public/app/containers/Teams/TeamList.tsx @@ -55,7 +55,7 @@ export class TeamList extends React.Component { {team.memberCount} ); diff --git a/public/app/core/components/DeleteButton/DeleteButton.jest.tsx b/public/app/core/components/DeleteButton/DeleteButton.jest.tsx new file mode 100644 index 00000000000..12acadee18a --- /dev/null +++ b/public/app/core/components/DeleteButton/DeleteButton.jest.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import DeleteButton from './DeleteButton'; +import { shallow } from 'enzyme'; + +describe('DeleteButton', () => { + let wrapper; + let deleted; + + beforeAll(() => { + deleted = false; + + function deleteItem() { + deleted = true; + } + wrapper = shallow( deleteItem()} />); + }); + + it('should show confirm delete when clicked', () => { + expect(wrapper.state().showConfirm).toBe(false); + wrapper.find('.delete-button').simulate('click'); + expect(wrapper.state().showConfirm).toBe(true); + }); + + it('should hide confirm delete when clicked', () => { + wrapper.find('.delete-button').simulate('click'); + expect(wrapper.state().showConfirm).toBe(true); + wrapper + .find('.confirm-delete') + .find('.btn') + .at(0) + .simulate('click'); + expect(wrapper.state().showConfirm).toBe(false); + }); + + it('should show confirm delete when clicked', () => { + expect(deleted).toBe(false); + wrapper + .find('.confirm-delete') + .find('.btn') + .at(1) + .simulate('click'); + expect(deleted).toBe(true); + }); +}); diff --git a/public/app/core/components/DeleteButton/DeleteButton.tsx b/public/app/core/components/DeleteButton/DeleteButton.tsx index 61a322b591e..a83ce6097ad 100644 --- a/public/app/core/components/DeleteButton/DeleteButton.tsx +++ b/public/app/core/components/DeleteButton/DeleteButton.tsx @@ -1,73 +1,61 @@ -import React, { Component } from 'react'; +import React, { PureComponent } from 'react'; -export default class DeleteButton extends Component { - state = { - deleteButton: 'delete-button--show', - confirmSpan: 'confirm-delete--removed', +export interface DeleteButtonProps { + onConfirmDelete(); +} + +export interface DeleteButtonStates { + showConfirm: boolean; +} + +export default class DeleteButton extends PureComponent { + state: DeleteButtonStates = { + showConfirm: false, }; - handleDelete = event => { + onClickDelete = event => { if (event) { event.preventDefault(); } this.setState({ - deleteButton: 'delete-button--hide', + showConfirm: true, }); - - setTimeout(() => { - this.setState({ - deleteButton: 'delete-button--removed', - }); - }, 100); - - setTimeout(() => { - this.setState({ - confirmSpan: 'confirm-delete--hide', - }); - }, 100); - - setTimeout(() => { - this.setState({ - confirmSpan: 'confirm-delete--show', - }); - }, 150); }; - cancelDelete = event => { - event.preventDefault(); - + onClickCancel = event => { + if (event) { + event.preventDefault(); + } this.setState({ - confirmSpan: 'confirm-delete--hide', + showConfirm: false, }); - - setTimeout(() => { - this.setState({ - confirmSpan: 'confirm-delete--removed', - deleteButton: 'delete-button--hide', - }); - }, 140); - - setTimeout(() => { - this.setState({ - deleteButton: 'delete-button--show', - }); - }, 190); }; render() { - const { confirmDelete } = this.props; + const onClickConfirm = this.props.onConfirmDelete; + let showConfirm; + let showDeleteButton; + + if (this.state.showConfirm) { + showConfirm = 'show'; + showDeleteButton = 'hide'; + } else { + showConfirm = 'hide'; + showDeleteButton = 'show'; + } + return ( - + - - + + Cancel - + Confirm Delete diff --git a/public/sass/components/_delete_button.scss b/public/sass/components/_delete_button.scss index 19f32189d81..e56a1181a09 100644 --- a/public/sass/components/_delete_button.scss +++ b/public/sass/components/_delete_button.scss @@ -1,49 +1,50 @@ +// sets a fixed width so that the rest of the table +// isn't affected by the animation .delete-button-container { - max-width: 24px; width: 24px; direction: rtl; - max-height: 38px; - display: block; + display: flex; + align-items: center; } +//this container is used to make sure confirm-delete isn't +//shown outside of table .confirm-delete-container { overflow: hidden; width: 145px; - display: block; + position: absolute; + z-index: 1; } .delete-button { - &--show { - display: inline-block; + position: absolute; + + &.show { opacity: 1; transition: opacity 0.1s ease; + z-index: 2; } - &--hide { - display: inline-block; + &.hide { opacity: 0; transition: opacity 0.1s ease; - } - &--removed { - display: none; + z-index: 0; } } .confirm-delete { - &--show { - display: inline-block; + display: flex; + align-items: flex-start; + + &.show { opacity: 1; transition: opacity 0.08s ease-out, transform 0.1s ease-out; transform: translateX(0); } - &--hide { - display: inline-block; + &.hide { opacity: 0; transition: opacity 0.12s ease-in, transform 0.14s ease-in; transform: translateX(100px); } - &--removed { - display: none; - } }
- this.deleteTeam(team)} /> + this.deleteTeam(team)} />