diff --git a/locales/en-US/server.ftl b/locales/en-US/server.ftl
index 1d850e7907..9259b9803a 100644
--- a/locales/en-US/server.ftl
+++ b/locales/en-US/server.ftl
@@ -261,6 +261,15 @@ shotIndexPageNextPage =
shotIndexNoExpirationSymbol = ∞
.title = This shot does not expire
+
+## Delete Confirmation Dialog
+
+shotDeleteConfirmationMessage = Are you sure you want to delete this shot?
+shotDeleteCancel = Cancel
+ .title = Cancel
+shotDeleteConfirm = Delete
+ .title = Delete
+
## Metrics page
## All metrics strings are optional for translation
diff --git a/server/src/delete-shot-button.js b/server/src/delete-shot-button.js
new file mode 100644
index 0000000000..a26f32ab77
--- /dev/null
+++ b/server/src/delete-shot-button.js
@@ -0,0 +1,96 @@
+const React = require("react");
+const PropTypes = require("prop-types");
+const { Localized } = require("fluent-react/compat");
+
+exports.DeleteShotButton = class DeleteShotButton extends React.Component {
+ constructor(props) {
+ super(props);
+ this.elRef = React.createRef();
+ this.trashButtonRef = React.createRef();
+ this.state = {confirmationPanelOpen: false}
+ this.maybeCloseDeleteConfirmation = this.maybeCloseDeleteConfirmation.bind(this);
+ }
+
+ componentDidUpdate() {
+ if (this.state.confirmationPanelOpen) {
+ document.addEventListener("mousedown", this.maybeCloseDeleteConfirmation);
+ } else {
+ document.removeEventListener("mousedown", this.maybeCloseDeleteConfirmation);
+ }
+ }
+
+ componentWillUnmount() {
+ document.removeEventListener("mousedown", this.maybeCloseDeleteConfirmation);
+ }
+
+ onClickDelete(e) {
+ e.stopPropagation();
+ e.preventDefault();
+ this.props.clickDeleteHandler && this.props.clickDeleteHandler();
+ this.setState({confirmationPanelOpen: true});
+ }
+
+ onConfirmDelete(e) {
+ e.stopPropagation();
+ this.props.confirmDeleteHandler && this.props.confirmDeleteHandler();
+ this.setState({confirmationPanelOpen: false});
+ }
+
+ onCancelDelete(e) {
+ e.stopPropagation();
+ this.props.cancelDeleteHandler && this.props.cancelDeleteHandler();
+ this.setState({confirmationPanelOpen: false});
+ }
+
+ maybeCloseDeleteConfirmation(e) {
+ if (this.elRef.current === e.target || this.elRef.current.contains(e.target)) {
+ return;
+ }
+
+ this.onCancelDelete(e);
+ }
+
+ render() {
+ let rightAlign = "";
+ if (this.trashButtonRef.current && (this.trashButtonRef.current.getBoundingClientRect().left + 320) > document.body.scrollWidth) {
+ rightAlign = "right-align";
+ }
+
+ let confirmationPanel = null, deletePanelOpenClass = null;
+ if (this.state.confirmationPanelOpen) {
+ confirmationPanel =
+
@@ -426,20 +437,20 @@ class Card extends React.Component {
return title;
}
- onClickDelete(shot, event) {
- event.stopPropagation();
- event.preventDefault();
+ clickDeleteHandler(event) {
sendEvent("start-delete", "my-shots", {useBeacon: true});
- const confirmMessage = document.getElementById("shotIndexPageConfirmShotDelete").textContent;
- if (window.confirm(confirmMessage)) {
- sendEvent("delete", "my-shots-popup-confirm", {useBeacon: true});
- this.setState({deleted: true});
- controller.deleteShot(shot);
- } else {
- sendEvent("cancel-delete", "my-shots-popup-confirm");
- }
- this.trashButton.blur();
- return false;
+ this.setState({ deletePanelOpen: true });
+ }
+
+ confirmDeleteHandler(shot, event) {
+ sendEvent("delete", "my-shots-popup-confirm", { useBeacon: true });
+ controller.deleteShot(shot);
+ this.setState({ deleted: true, deletePanelOpen: false });
+ }
+
+ cancelDeleteHandler(event) {
+ this.setState({ deletePanelOpen: false });
+ sendEvent("cancel-delete", "my-shots-popup-confirm");
}
onClickDownload() {
diff --git a/static/css/frame.scss b/static/css/frame.scss
index 508d4f83f9..d7cc5b447f 100644
--- a/static/css/frame.scss
+++ b/static/css/frame.scss
@@ -1,4 +1,5 @@
@import "partials/partials";
+@import "partials/delete-confirmation";
.frame-header {
@include respond-to("medium") {
@@ -77,6 +78,13 @@
}
}
+.delete-confirmation-dialog {
+ &.right-align {
+ top: 48px;
+ }
+}
+
+
.back-to-index {
@include respond-to("small") {
background-position: left -5px center;
diff --git a/static/css/partials/_buttons.scss b/static/css/partials/_buttons.scss
index bec99599f1..a3333bd8ef 100644
--- a/static/css/partials/_buttons.scss
+++ b/static/css/partials/_buttons.scss
@@ -22,6 +22,10 @@
padding: 0 8px;
}
+ &.active {
+ background-color: $light-active;
+ }
+
&.tiny {
font-size: 14px;
height: 26px;
diff --git a/static/css/partials/_delete-confirmation.scss b/static/css/partials/_delete-confirmation.scss
new file mode 100644
index 0000000000..df12472d6d
--- /dev/null
+++ b/static/css/partials/_delete-confirmation.scss
@@ -0,0 +1,66 @@
+.delete-shot-button {
+ position: relative;
+}
+
+.delete-confirmation-dialog {
+ background-color: #fff;
+ border: 1px solid #c7c7c7 ;
+ border-radius: 3px;
+ box-shadow: 0 5px 11px 0 rgba(0, 0, 0, 0.25), 0 0 0 0 rgba(0, 0, 0, 0.29), inset 0 0 0 0 #fff;
+ min-height: 114px;
+ min-width: 320px;
+ padding: 18px;
+ position: absolute;
+ left: 3px;
+ top: 47px;
+ z-index: 3;
+
+ .triangle {
+ position: absolute;
+ width: 0;
+ height: 0;
+ border-left: 9px solid transparent;
+ border-right: 9px solid transparent;
+ border-bottom: 15px solid $light-border;
+ top: -15px;
+ left: 8px;
+
+ .triangle-inner {
+ position: absolute;
+ width: 0;
+ height: 0;
+ border-left: 8px solid transparent;
+ border-right: 8px solid transparent;
+ border-bottom: 13px solid #fff;
+ left: -8px;
+ top: 2px;
+ }
+ }
+ .delete-confirmation-message {
+ font-size: 15px;
+ }
+ .delete-confirmation-buttons {
+ display: flex;
+ justify-content: space-between;
+ margin-top: 18px;
+
+ button {
+ &.primary {
+ width: 135px;
+ height: 40px;
+ }
+ &.secondary {
+ border: 1px solid #c7c7c7;
+ width: 133px;
+ height: 38px;
+ }
+ }
+ }
+
+ &.right-align {
+ left: -281px;
+ .triangle {
+ left: 292px;
+ }
+ }
+}
diff --git a/static/css/shot-index.scss b/static/css/shot-index.scss
index 987a771998..0b6432e8d1 100644
--- a/static/css/shot-index.scss
+++ b/static/css/shot-index.scss
@@ -1,4 +1,5 @@
@import "partials/partials";
+@import "partials/delete-confirmation";
//Shot index page styles