From d2aa71ea01cec53222d6f0d3a7671ae8860f5175 Mon Sep 17 00:00:00 2001
From: Kyle Doherty <kdoh@users.noreply.github.com>
Date: Thu, 31 May 2018 11:43:07 -0700
Subject: [PATCH] Improve Toast workflow (#7755)

* initial version of withToast

* add dark card, use for undo item

* use triggerToast on AutomaticDashboardApp

* betterify

* fix flow
---
 frontend/src/metabase/components/Card.jsx     |  9 ++-
 .../src/metabase/containers/UndoListing.css   | 43 -----------
 .../src/metabase/containers/UndoListing.jsx   | 73 +++++++++----------
 .../containers/AutomaticDashboardApp.jsx      | 37 ++++------
 frontend/src/metabase/hoc/Toast.jsx           | 37 ++++++++++
 .../internal/components/ColorsApp.jsx         | 28 ++-----
 frontend/src/metabase/redux/undo.js           |  2 +-
 7 files changed, 101 insertions(+), 128 deletions(-)
 delete mode 100644 frontend/src/metabase/containers/UndoListing.css
 create mode 100644 frontend/src/metabase/hoc/Toast.jsx

diff --git a/frontend/src/metabase/components/Card.jsx b/frontend/src/metabase/components/Card.jsx
index c3969b2ab0b..d4e66039514 100644
--- a/frontend/src/metabase/components/Card.jsx
+++ b/frontend/src/metabase/components/Card.jsx
@@ -3,14 +3,15 @@ import { space } from "styled-system";
 import { normal } from "metabase/lib/colors";
 
 const Card = styled.div`
-  ${space} background-color: white;
-  border: 1px solid #f5f6f7;
+  ${space} background-color: ${props => (props.dark ? "#2e353b" : "white")};
+  border: 1px solid ${props => (props.dark ? "transparent" : "#f5f6f7")};
+  ${props => props.dark && `color: white`};
   border-radius: 6px;
-  box-shadow: 0 1px 3px ${normal.grey1};
+  box-shadow: 0 1px 3px ${props => (props.dark ? "#65686b" : normal.grey1)};
   ${props =>
     props.hoverable &&
     `&:hover {
-    box-shadow: 0 2px 3px #DCE1E4;
+    box-shadow: 0 2px 3px ${props.dark ? "#2e35b" : "#DCE1E4"};
   }`};
 `;
 
diff --git a/frontend/src/metabase/containers/UndoListing.css b/frontend/src/metabase/containers/UndoListing.css
deleted file mode 100644
index e9c00660e9b..00000000000
--- a/frontend/src/metabase/containers/UndoListing.css
+++ /dev/null
@@ -1,43 +0,0 @@
-:local(.listing) {
-  composes: m2 from "style";
-  composes: fixed left bottom from "style";
-  z-index: 99;
-}
-
-:local(.undo) {
-  composes: mt2 p2 from "style";
-  composes: bordered rounded shadowed from "style";
-  composes: relative from "style";
-  composes: flex align-center from "style";
-  background-color: #2e353b;
-  color: white;
-}
-
-:local(.actions) {
-  composes: flex align-center flex-align-right from "style";
-}
-
-:local(.undoButton) {
-  composes: mx2 from "style";
-  composes: text-uppercase text-bold from "style";
-  color: var(--brand-color);
-}
-:local(.dismissButton) {
-  composes: cursor-pointer from "style";
-  color: var(--grey-1);
-}
-:local(.dismissButton):hover {
-  color: var(--grey-3);
-}
-
-/* enter and exit initial and final state */
-.UndoListing-enter,
-.UndoListing-leave.UndoListing-leave-active {
-  opacity: 0.01;
-  transition: opacity 300ms ease-in;
-}
-
-.UndoListing-leave,
-.UndoListing-enter.UndoListing-enter-active {
-  opacity: 1;
-}
diff --git a/frontend/src/metabase/containers/UndoListing.jsx b/frontend/src/metabase/containers/UndoListing.jsx
index c92cf2451b7..931c1fc7f14 100644
--- a/frontend/src/metabase/containers/UndoListing.jsx
+++ b/frontend/src/metabase/containers/UndoListing.jsx
@@ -2,29 +2,33 @@
 import React, { Component } from "react";
 import PropTypes from "prop-types";
 import { connect } from "react-redux";
+import styled from "styled-components";
+import { space } from "styled-system";
+import { Flex } from "rebass";
+import { t } from "c-3po";
 
-import S from "./UndoListing.css";
-
+import { normal } from "metabase/lib/colors";
 import { dismissUndo, performUndo } from "metabase/redux/undo";
 import { getUndos } from "metabase/selectors/undo";
-import { t } from "c-3po";
 
-import Icon from "metabase/components/Icon";
 import BodyComponent from "metabase/components/BodyComponent";
+import Card from "metabase/components/Card";
+import Icon from "metabase/components/Icon";
+import Link from "metabase/components/Link";
 
-import { CSSTransitionGroup } from "react-transition-group";
-
-const mapStateToProps = (state, props) => {
-  return {
-    undos: getUndos(state, props),
-  };
-};
+const mapStateToProps = (state, props) => ({
+  undos: getUndos(state, props),
+});
 
 const mapDispatchToProps = {
   dismissUndo,
   performUndo,
 };
 
+const UndoList = styled.ul`
+  ${space}, z-index: 99;
+`;
+
 @connect(mapStateToProps, mapDispatchToProps)
 @BodyComponent
 export default class UndoListing extends Component {
@@ -37,37 +41,28 @@ export default class UndoListing extends Component {
   render() {
     const { undos, performUndo, dismissUndo } = this.props;
     return (
-      <ul className={S.listing}>
-        <CSSTransitionGroup
-          transitionName="UndoListing"
-          transitionEnterTimeout={300}
-          transitionLeaveTimeout={300}
-        >
-          {undos.map(undo => (
-            <li key={undo._domId} className={S.undo}>
-              <div className={S.message}>
-                {typeof undo.message === "function"
-                  ? undo.message(undo)
-                  : undo.message}
-              </div>
+      <UndoList m={2} className="fixed left bottom">
+        {undos.map(undo => (
+          <Card key={undo._domId} dark p={2}>
+            <Flex align="center">
+              {typeof undo.message === "function"
+                ? undo.message(undo)
+                : undo.message}
 
               {undo.actions && (
-                <div className={S.actions}>
-                  <a
-                    className={S.undoButton}
-                    onClick={() => performUndo(undo.id)}
-                  >{t`Undo`}</a>
-                  <Icon
-                    className={S.dismissButton}
-                    name="close"
-                    onClick={() => dismissUndo(undo.id)}
-                  />
-                </div>
+                <Link onClick={() => performUndo(undo.id)}>{t`Undo`}</Link>
               )}
-            </li>
-          ))}
-        </CSSTransitionGroup>
-      </ul>
+              <Icon
+                ml={1}
+                color={normal.grey1}
+                hover={{ color: normal.grey2 }}
+                name="close"
+                onClick={() => dismissUndo(undo.id)}
+              />
+            </Flex>
+          </Card>
+        ))}
+      </UndoList>
     );
   }
 }
diff --git a/frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx b/frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx
index 05f7e18cb2d..ff26f9efd1a 100644
--- a/frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx
+++ b/frontend/src/metabase/dashboard/containers/AutomaticDashboardApp.jsx
@@ -6,6 +6,7 @@ import { connect } from "react-redux";
 import { Link } from "react-router";
 
 import title from "metabase/hoc/Title";
+import withToast from "metabase/hoc/Toast";
 import ActionButton from "metabase/components/ActionButton";
 import Button from "metabase/components/Button";
 import Icon from "metabase/components/Icon";
@@ -18,8 +19,6 @@ import { Dashboard } from "metabase/dashboard/containers/Dashboard";
 import DashboardData from "metabase/dashboard/hoc/DashboardData";
 import Parameters from "metabase/parameters/components/Parameters";
 
-import { addUndo, createUndo } from "metabase/redux/undo";
-
 import { getMetadata } from "metabase/selectors/metadata";
 import { getUserIsAdmin } from "metabase/selectors/user";
 
@@ -40,8 +39,9 @@ const mapStateToProps = (state, props) => ({
   dashboardId: getDashboardId(state, props),
 });
 
-@connect(mapStateToProps, { addUndo, createUndo })
+@connect(mapStateToProps)
 @DashboardData
+@withToast
 @title(({ dashboard }) => dashboard && dashboard.name)
 class AutomaticDashboardApp extends React.Component {
   state = {
@@ -56,27 +56,22 @@ class AutomaticDashboardApp extends React.Component {
   }
 
   save = async () => {
-    const { dashboard, addUndo, createUndo } = this.props;
+    const { dashboard, triggerToast } = this.props;
     // remove the transient id before trying to save
     const newDashboard = await DashboardApi.save(dissoc(dashboard, "id"));
-    addUndo(
-      createUndo({
-        type: "metabase/automatic-dashboards/link-to-created-object",
-        message: () => (
-          <div className="flex align-center">
-            <Icon name="dashboard" size={22} className="mr2" color="#93A1AB" />
-            {t`Your dashboard was saved`}
-            <Link
-              className="link text-bold ml1"
-              to={Urls.dashboard(newDashboard.id)}
-            >
-              {t`See it`}
-            </Link>
-          </div>
-        ),
-        action: null,
-      }),
+    triggerToast(
+      <div className="flex align-center">
+        <Icon name="dashboard" size={22} className="mr2" color="#93A1AB" />
+        {t`Your dashboard was saved`}
+        <Link
+          className="link text-bold ml1"
+          to={Urls.dashboard(newDashboard.id)}
+        >
+          {t`See it`}
+        </Link>
+      </div>,
     );
+
     this.setState({ savedDashboardId: newDashboard.id });
     MetabaseAnalytics.trackEvent("AutoDashboard", "Save");
   };
diff --git a/frontend/src/metabase/hoc/Toast.jsx b/frontend/src/metabase/hoc/Toast.jsx
new file mode 100644
index 00000000000..6539da55cd8
--- /dev/null
+++ b/frontend/src/metabase/hoc/Toast.jsx
@@ -0,0 +1,37 @@
+import React from "react";
+import { connect } from "react-redux";
+
+import { createUndo, addUndo } from "metabase/redux/undo";
+
+const mapDispatchToProps = {
+  createUndo,
+  addUndo,
+};
+
+const withToaster = ComposedComponent => {
+  @connect(null, mapDispatchToProps)
+  class ToastedComponent extends React.Component {
+    _triggerToast = toastContent => {
+      const { addUndo, createUndo } = this.props;
+
+      addUndo(
+        createUndo({
+          type: "toast",
+          message: toastContent,
+        }),
+      );
+    };
+    render() {
+      return (
+        <ComposedComponent
+          triggerToast={this._triggerToast}
+          // TODO - omit createUndo, addUndo
+          {...this.props}
+        />
+      );
+    }
+  }
+  return ToastedComponent;
+};
+
+export default withToaster;
diff --git a/frontend/src/metabase/internal/components/ColorsApp.jsx b/frontend/src/metabase/internal/components/ColorsApp.jsx
index 97d506c04f1..0cdca998075 100644
--- a/frontend/src/metabase/internal/components/ColorsApp.jsx
+++ b/frontend/src/metabase/internal/components/ColorsApp.jsx
@@ -1,35 +1,23 @@
-/* @flow */
 import React from "react";
 import cx from "classnames";
 import { Box, Flex, Subhead } from "rebass";
-import { connect } from "react-redux";
 import CopyToClipboard from "react-copy-to-clipboard";
 
-import { addUndo, createUndo } from "metabase/redux/undo";
 import { normal, saturated, harmony } from "metabase/lib/colors";
 
-const SWATCH_SIZE = 150;
+import withToast from "metabase/hoc/Toast";
 
-const mapDispatchToProps = {
-  addUndo,
-  createUndo,
-};
+const SWATCH_SIZE = 150;
 
-@connect(() => ({}), mapDispatchToProps)
+@withToast
 class ColorSwatch extends React.Component {
-  _onCopy(colorValue) {
-    const { addUndo, createUndo } = this.props;
-    addUndo(
-      createUndo({
-        type: "copy-color",
-        message: <div>Copied {colorValue} to clipboard</div>,
-      }),
-    );
-  }
   render() {
-    const { color, name } = this.props;
+    const { color, name, triggerToast } = this.props;
     return (
-      <CopyToClipboard text={color} onCopy={() => this._onCopy(color)}>
+      <CopyToClipboard
+        text={color}
+        onCopy={() => triggerToast(`${color} copied to clipboard`)}
+      >
         <Box
           w={SWATCH_SIZE}
           style={{
diff --git a/frontend/src/metabase/redux/undo.js b/frontend/src/metabase/redux/undo.js
index 0448f6f3efe..b3041ca7a8c 100644
--- a/frontend/src/metabase/redux/undo.js
+++ b/frontend/src/metabase/redux/undo.js
@@ -14,7 +14,7 @@ let nextUndoId = 0;
 // A convenience shorthand for creating single undos
 export function createUndo({ type, message, action }) {
   return {
-    type: type,
+    type,
     count: 1,
     message,
     actions: action ? [action] : null,
-- 
GitLab