From ce3d9fb6f5babfe1f266b6cb1ff4657fb747a99d Mon Sep 17 00:00:00 2001
From: Alexander Polyankin <alexander.polyankin@metabase.com>
Date: Mon, 20 Jun 2022 22:09:32 +0300
Subject: [PATCH] Cleanup icons and checkboxes in collections (#23413)

---
 .../collections/components/BaseTableItem.jsx  |  2 +-
 .../components/ArchiveItem.styled.tsx         |  7 --
 .../src/metabase/components/ArchivedItem.jsx  | 24 +++--
 .../components/ArchivedItem.styled.tsx        | 13 +++
 .../src/metabase/components/EntityItem.jsx    |  6 +-
 frontend/src/metabase/components/Swapper.jsx  | 91 -------------------
 .../components/Swapper/Swapper.styled.tsx     | 22 +++++
 .../core/components/Swapper/Swapper.tsx       | 47 ++++++++++
 .../metabase/core/components/Swapper/index.ts |  1 +
 9 files changed, 101 insertions(+), 112 deletions(-)
 delete mode 100644 frontend/src/metabase/components/ArchiveItem.styled.tsx
 create mode 100644 frontend/src/metabase/components/ArchivedItem.styled.tsx
 delete mode 100644 frontend/src/metabase/components/Swapper.jsx
 create mode 100644 frontend/src/metabase/core/components/Swapper/Swapper.styled.tsx
 create mode 100644 frontend/src/metabase/core/components/Swapper/Swapper.tsx
 create mode 100644 frontend/src/metabase/core/components/Swapper/index.ts

diff --git a/frontend/src/metabase/collections/components/BaseTableItem.jsx b/frontend/src/metabase/collections/components/BaseTableItem.jsx
index 52fa9b3940f..e4c277ea68a 100644
--- a/frontend/src/metabase/collections/components/BaseTableItem.jsx
+++ b/frontend/src/metabase/collections/components/BaseTableItem.jsx
@@ -81,7 +81,7 @@ export function BaseTableItem({
 
     const icon = { name: item.getIcon().name };
     if (item.model === "card") {
-      icon.color = color("bg-dark");
+      icon.color = color("text-light");
     }
 
     // Table row can be wrapped with ItemDragSource,
diff --git a/frontend/src/metabase/components/ArchiveItem.styled.tsx b/frontend/src/metabase/components/ArchiveItem.styled.tsx
deleted file mode 100644
index 3793d997089..00000000000
--- a/frontend/src/metabase/components/ArchiveItem.styled.tsx
+++ /dev/null
@@ -1,7 +0,0 @@
-import styled from "@emotion/styled";
-import IconWrapper from "metabase/components/IconWrapper";
-
-export const IconContainer = styled(IconWrapper)`
-  padding: 0.5rem;
-  margin-right: 0.5rem;
-`;
diff --git a/frontend/src/metabase/components/ArchivedItem.jsx b/frontend/src/metabase/components/ArchivedItem.jsx
index 773c23bea13..55fbba9ca96 100644
--- a/frontend/src/metabase/components/ArchivedItem.jsx
+++ b/frontend/src/metabase/components/ArchivedItem.jsx
@@ -6,11 +6,11 @@ import { t } from "ttag";
 
 import CheckBox from "metabase/core/components/CheckBox";
 import Icon from "metabase/components/Icon";
-import Swapper from "metabase/components/Swapper";
+import Swapper from "metabase/core/components/Swapper";
 import Tooltip from "metabase/components/Tooltip";
 
 import { color as c } from "metabase/lib/colors";
-import { IconContainer } from "./ArchiveItem.styled";
+import { ItemIcon, ItemIconContainer } from "./ArchivedItem.styled";
 
 const ArchivedItem = ({
   name,
@@ -25,15 +25,19 @@ const ArchivedItem = ({
   showSelect,
 }) => (
   <div className="flex align-center p2 hover-parent hover--visibility border-bottom bg-light-hover">
-    <IconContainer>
-      <Swapper
-        startSwapped={showSelect}
-        defaultElement={<Icon name={icon} color={color} />}
-        swappedElement={
+    <Swapper
+      defaultElement={
+        <ItemIconContainer>
+          <ItemIcon name={icon} color={color} />
+        </ItemIconContainer>
+      }
+      swappedElement={
+        <ItemIconContainer>
           <CheckBox checked={selected} onChange={onToggleSelected} />
-        }
-      />
-    </IconContainer>
+        </ItemIconContainer>
+      }
+      isSwapped={showSelect}
+    />
     {name}
     {isAdmin && (onUnarchive || onDelete) && (
       <span className="ml-auto mr2">
diff --git a/frontend/src/metabase/components/ArchivedItem.styled.tsx b/frontend/src/metabase/components/ArchivedItem.styled.tsx
new file mode 100644
index 00000000000..8cab288bbb7
--- /dev/null
+++ b/frontend/src/metabase/components/ArchivedItem.styled.tsx
@@ -0,0 +1,13 @@
+import styled from "@emotion/styled";
+import IconWrapper from "metabase/components/IconWrapper";
+import Icon from "metabase/components/Icon";
+
+export const ItemIcon = styled(Icon)`
+  display: block;
+`;
+
+export const ItemIconContainer = styled(IconWrapper)`
+  padding: 0.5rem;
+  margin-right: 0.5rem;
+  line-height: 1;
+`;
diff --git a/frontend/src/metabase/components/EntityItem.jsx b/frontend/src/metabase/components/EntityItem.jsx
index b79164ea8b1..7f841443b0e 100644
--- a/frontend/src/metabase/components/EntityItem.jsx
+++ b/frontend/src/metabase/components/EntityItem.jsx
@@ -4,7 +4,7 @@ import { t } from "ttag";
 import cx from "classnames";
 
 import EntityMenu from "metabase/components/EntityMenu";
-import Swapper from "metabase/components/Swapper";
+import Swapper from "metabase/core/components/Swapper";
 import CheckBox from "metabase/core/components/CheckBox";
 import Ellipsified from "metabase/core/components/Ellipsified";
 import Icon from "metabase/components/Icon";
@@ -30,7 +30,7 @@ function EntityIconCheckBox({
   onToggleSelected,
   ...props
 }) {
-  const iconSize = variant === "small" ? 12 : 18;
+  const iconSize = variant === "small" ? 12 : 16;
   const handleClick = e => {
     e.preventDefault();
     onToggleSelected();
@@ -47,7 +47,6 @@ function EntityIconCheckBox({
     >
       {selectable ? (
         <Swapper
-          startSwapped={selected || showCheckbox}
           defaultElement={
             <Icon
               name={icon.name}
@@ -56,6 +55,7 @@ function EntityIconCheckBox({
             />
           }
           swappedElement={<CheckBox checked={selected} size={iconSize} />}
+          isSwapped={selected || showCheckbox}
         />
       ) : (
         <Icon
diff --git a/frontend/src/metabase/components/Swapper.jsx b/frontend/src/metabase/components/Swapper.jsx
deleted file mode 100644
index 698e7493c04..00000000000
--- a/frontend/src/metabase/components/Swapper.jsx
+++ /dev/null
@@ -1,91 +0,0 @@
-/* eslint-disable react/prop-types */
-import React from "react";
-import { Motion, spring } from "react-motion";
-
-import { isReducedMotionPreferred } from "metabase/lib/dom";
-
-class Swapper extends React.Component {
-  state = {
-    hovered: false,
-  };
-
-  _onMouseEnter() {
-    this.setState({ hovered: true });
-  }
-
-  _onMouseLeave() {
-    this.setState({ hovered: false });
-  }
-
-  render() {
-    const { defaultElement, swappedElement, startSwapped } = this.props;
-    const { hovered } = this.state;
-
-    const preferReducedMotion = isReducedMotionPreferred();
-    const springOpts = preferReducedMotion
-      ? { stiffness: 500 }
-      : { stiffness: 170 };
-
-    return (
-      <span
-        onMouseEnter={() => this._onMouseEnter()}
-        onMouseLeave={() => this._onMouseLeave()}
-        className="block relative"
-        style={{ lineHeight: 1 }}
-      >
-        <Motion
-          defaultStyle={{
-            scale: 1,
-          }}
-          style={{
-            scale:
-              hovered || startSwapped
-                ? spring(0, springOpts)
-                : spring(1, springOpts),
-          }}
-        >
-          {({ scale }) => {
-            const snapScale = scale < 0.5 ? 0 : 1;
-            const _scale = preferReducedMotion ? snapScale : scale;
-            return (
-              <span
-                style={{
-                  display: "block",
-                  transform: `scale(${_scale})`,
-                }}
-              >
-                {defaultElement}
-              </span>
-            );
-          }}
-        </Motion>
-        <Motion
-          defaultStyle={{
-            scale: 0,
-          }}
-          style={{
-            scale:
-              hovered || startSwapped
-                ? spring(1, springOpts)
-                : spring(0, springOpts),
-          }}
-        >
-          {({ scale }) => {
-            const snapScale = scale < 0.5 ? 0 : 1;
-            const _scale = preferReducedMotion ? snapScale : scale;
-            return (
-              <span
-                className="absolute top left bottom right"
-                style={{ display: "block", transform: `scale(${_scale})` }}
-              >
-                {swappedElement}
-              </span>
-            );
-          }}
-        </Motion>
-      </span>
-    );
-  }
-}
-
-export default Swapper;
diff --git a/frontend/src/metabase/core/components/Swapper/Swapper.styled.tsx b/frontend/src/metabase/core/components/Swapper/Swapper.styled.tsx
new file mode 100644
index 00000000000..4d43280bca0
--- /dev/null
+++ b/frontend/src/metabase/core/components/Swapper/Swapper.styled.tsx
@@ -0,0 +1,22 @@
+import styled from "@emotion/styled";
+
+export interface SwapperElementProps {
+  isVisible: boolean;
+}
+
+export const SwapperRoot = styled.div`
+  position: relative;
+`;
+
+export const SwapperDefaultElement = styled.div<SwapperElementProps>`
+  transform: scale(${props => (props.isVisible ? 1 : 0)});
+`;
+
+export const SwapperLayeredElement = styled.div<SwapperElementProps>`
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  transform: scale(${props => (props.isVisible ? 1 : 0)});
+`;
diff --git a/frontend/src/metabase/core/components/Swapper/Swapper.tsx b/frontend/src/metabase/core/components/Swapper/Swapper.tsx
new file mode 100644
index 00000000000..f811af4697a
--- /dev/null
+++ b/frontend/src/metabase/core/components/Swapper/Swapper.tsx
@@ -0,0 +1,47 @@
+import React, {
+  forwardRef,
+  HTMLAttributes,
+  ReactNode,
+  Ref,
+  useCallback,
+  useState,
+} from "react";
+import {
+  SwapperDefaultElement,
+  SwapperLayeredElement,
+  SwapperRoot,
+} from "./Swapper.styled";
+
+export interface SwapperProps extends HTMLAttributes<HTMLDivElement> {
+  defaultElement?: ReactNode;
+  swappedElement?: ReactNode;
+  isSwapped?: boolean;
+}
+
+const Swapper = forwardRef(function Swapper(
+  { defaultElement, swappedElement, isSwapped = false, ...props }: SwapperProps,
+  ref: Ref<HTMLDivElement>,
+) {
+  const [isHovered, setIsHovered] = useState(false);
+  const isSelected = isHovered || isSwapped;
+  const handleMouseEnter = useCallback(() => setIsHovered(true), []);
+  const handleMouseLeave = useCallback(() => setIsHovered(false), []);
+
+  return (
+    <SwapperRoot
+      {...props}
+      ref={ref}
+      onMouseEnter={handleMouseEnter}
+      onMouseLeave={handleMouseLeave}
+    >
+      <SwapperDefaultElement isVisible={!isSelected}>
+        {defaultElement}
+      </SwapperDefaultElement>
+      <SwapperLayeredElement isVisible={isSelected}>
+        {swappedElement}
+      </SwapperLayeredElement>
+    </SwapperRoot>
+  );
+});
+
+export default Swapper;
diff --git a/frontend/src/metabase/core/components/Swapper/index.ts b/frontend/src/metabase/core/components/Swapper/index.ts
new file mode 100644
index 00000000000..b3226b152a3
--- /dev/null
+++ b/frontend/src/metabase/core/components/Swapper/index.ts
@@ -0,0 +1 @@
+export { default } from "./Swapper";
-- 
GitLab