From 0826d22b8b817f6286214fb6644b9f99990361ae Mon Sep 17 00:00:00 2001
From: Anton Kulyk <kuliks.anton@gmail.com>
Date: Fri, 30 Jun 2023 14:58:24 +0100
Subject: [PATCH] Clean up `NotebookCell` component, forward refs (#32035)

* Convert `NotebookCell` to TS, add `forwardRef`

* Move styled components to a styled file
---
 .../components/notebook/NotebookCell.jsx      | 139 ------------------
 .../NotebookCell/NotebookCell.styled.tsx      |  81 ++++++++++
 .../notebook/NotebookCell/NotebookCell.tsx    |  93 ++++++++++++
 .../components/notebook/NotebookCell/index.ts |   1 +
 4 files changed, 175 insertions(+), 139 deletions(-)
 delete mode 100644 frontend/src/metabase/query_builder/components/notebook/NotebookCell.jsx
 create mode 100644 frontend/src/metabase/query_builder/components/notebook/NotebookCell/NotebookCell.styled.tsx
 create mode 100644 frontend/src/metabase/query_builder/components/notebook/NotebookCell/NotebookCell.tsx
 create mode 100644 frontend/src/metabase/query_builder/components/notebook/NotebookCell/index.ts

diff --git a/frontend/src/metabase/query_builder/components/notebook/NotebookCell.jsx b/frontend/src/metabase/query_builder/components/notebook/NotebookCell.jsx
deleted file mode 100644
index 682e655559b..00000000000
--- a/frontend/src/metabase/query_builder/components/notebook/NotebookCell.jsx
+++ /dev/null
@@ -1,139 +0,0 @@
-/* eslint-disable react/prop-types */
-import { isValidElement } from "react";
-
-import styled from "@emotion/styled";
-import { css } from "@emotion/react";
-
-import { Icon } from "metabase/core/components/Icon";
-
-import { alpha } from "metabase/lib/colors";
-
-const CONTAINER_PADDING = "10px";
-
-const _NotebookCell = styled.div`
-  display: flex;
-  align-items: center;
-  flex-wrap: wrap;
-  border-radius: 8px;
-  background-color: ${props => alpha(props.color, 0.1)};
-  padding: ${props => props.padding || "14px"};
-  color: ${props => props.color};
-`;
-
-export const NotebookCell = Object.assign(_NotebookCell, {
-  displayName: "NotebookCell",
-  CONTAINER_PADDING,
-});
-
-const NotebookCellItemContainer = styled.div`
-  display: flex;
-  align-items: center;
-  font-weight: bold;
-  color: ${props => (props.inactive ? props.color : "white")};
-  border-radius: 6px;
-  margin-right: 4px;
-
-  border: 2px solid transparent;
-  border-color: ${props =>
-    props.inactive ? alpha(props.color, 0.25) : "transparent"};
-
-  &:hover {
-    border-color: ${props => props.inactive && alpha(props.color, 0.8)};
-  }
-
-  transition: border 300ms linear;
-
-  .Icon-close {
-    opacity: 0.6;
-  }
-`;
-
-const NotebookCellItemContentContainer = styled.div`
-  display: flex;
-  align-items: center;
-  padding: ${CONTAINER_PADDING};
-  background-color: ${props => (props.inactive ? "transparent" : props.color)};
-
-  &:hover {
-    background-color: ${props => !props.inactive && alpha(props.color, 0.8)};
-  }
-
-  ${props =>
-    !!props.border &&
-    css`
-    border-${props.border}: 1px solid ${alpha("white", 0.25)};
-  `}
-
-  ${props =>
-    props.roundedCorners.includes("left") &&
-    css`
-      border-top-left-radius: 6px;
-      border-bottom-left-radius: 6px;
-    `}
-
-  ${props =>
-    props.roundedCorners.includes("right") &&
-    css`
-      border-top-right-radius: 6px;
-      border-bottom-right-radius: 6px;
-    `}
-
-  transition: background 300ms linear;
-`;
-
-export function NotebookCellItem(props) {
-  const {
-    inactive,
-    color,
-    containerStyle,
-    right,
-    rightContainerStyle,
-    children,
-    readOnly,
-    ...restProps
-  } = props;
-
-  const hasRightSide = isValidElement(right) && !readOnly;
-  const mainContentRoundedCorners = ["left"];
-  if (!hasRightSide) {
-    mainContentRoundedCorners.push("right");
-  }
-  return (
-    <NotebookCellItemContainer
-      inactive={inactive}
-      color={color}
-      {...restProps}
-      data-testid={props["data-testid"] ?? "notebook-cell-item"}
-    >
-      <NotebookCellItemContentContainer
-        inactive={inactive}
-        color={color}
-        roundedCorners={mainContentRoundedCorners}
-        style={containerStyle}
-      >
-        {children}
-      </NotebookCellItemContentContainer>
-      {hasRightSide && (
-        <NotebookCellItemContentContainer
-          inactive={inactive}
-          color={color}
-          border="left"
-          roundedCorners={["right"]}
-          style={rightContainerStyle}
-        >
-          {right}
-        </NotebookCellItemContentContainer>
-      )}
-    </NotebookCellItemContainer>
-  );
-}
-
-NotebookCellItem.displayName = "NotebookCellItem";
-
-export const NotebookCellAdd = ({ initialAddText, ...props }) => (
-  <NotebookCellItem {...props} inactive={!!initialAddText}>
-    {initialAddText || <Icon name="add" className="text-white" />}
-  </NotebookCellItem>
-);
-
-NotebookCellAdd.displayName = "NotebookCellAdd";
diff --git a/frontend/src/metabase/query_builder/components/notebook/NotebookCell/NotebookCell.styled.tsx b/frontend/src/metabase/query_builder/components/notebook/NotebookCell/NotebookCell.styled.tsx
new file mode 100644
index 00000000000..5321c9c0239
--- /dev/null
+++ b/frontend/src/metabase/query_builder/components/notebook/NotebookCell/NotebookCell.styled.tsx
@@ -0,0 +1,81 @@
+import styled from "@emotion/styled";
+import { css } from "@emotion/react";
+import { alpha } from "metabase/lib/colors";
+
+export type BorderSide = "top" | "right" | "bottom" | "left";
+
+export const CONTAINER_PADDING = "10px";
+
+export const NotebookCell = styled.div<{ color: string; padding?: string }>`
+  display: flex;
+  align-items: center;
+  flex-wrap: wrap;
+  border-radius: 8px;
+  background-color: ${props => alpha(props.color, 0.1)};
+  padding: ${props => props.padding || "14px"};
+  color: ${props => props.color};
+`;
+
+export const NotebookCellItemContainer = styled.div<{
+  color: string;
+  inactive?: boolean;
+}>`
+  display: flex;
+  align-items: center;
+  font-weight: bold;
+  color: ${props => (props.inactive ? props.color : "white")};
+  border-radius: 6px;
+  margin-right: 4px;
+
+  border: 2px solid transparent;
+  border-color: ${props =>
+    props.inactive ? alpha(props.color, 0.25) : "transparent"};
+
+  &:hover {
+    border-color: ${props => props.inactive && alpha(props.color, 0.8)};
+  }
+
+  transition: border 300ms linear;
+
+  .Icon-close {
+    opacity: 0.6;
+  }
+`;
+
+export const NotebookCellItemContentContainer = styled.div<{
+  color: string;
+  inactive?: boolean;
+  border?: BorderSide;
+  roundedCorners: BorderSide[];
+}>`
+  display: flex;
+  align-items: center;
+  padding: ${CONTAINER_PADDING};
+  background-color: ${props => (props.inactive ? "transparent" : props.color)};
+
+  &:hover {
+    background-color: ${props => !props.inactive && alpha(props.color, 0.8)};
+  }
+
+  ${props =>
+    !!props.border &&
+    css`
+    border-${props.border}: 1px solid ${alpha("white", 0.25)};
+  `}
+
+  ${props =>
+    props.roundedCorners.includes("left") &&
+    css`
+      border-top-left-radius: 6px;
+      border-bottom-left-radius: 6px;
+    `}
+
+  ${props =>
+    props.roundedCorners.includes("right") &&
+    css`
+      border-top-right-radius: 6px;
+      border-bottom-right-radius: 6px;
+    `}
+
+  transition: background 300ms linear;
+`;
diff --git a/frontend/src/metabase/query_builder/components/notebook/NotebookCell/NotebookCell.tsx b/frontend/src/metabase/query_builder/components/notebook/NotebookCell/NotebookCell.tsx
new file mode 100644
index 00000000000..aba6f5ee660
--- /dev/null
+++ b/frontend/src/metabase/query_builder/components/notebook/NotebookCell/NotebookCell.tsx
@@ -0,0 +1,93 @@
+import { forwardRef, isValidElement } from "react";
+import { Icon } from "metabase/core/components/Icon";
+import {
+  NotebookCell as _NotebookCell,
+  NotebookCellItemContainer,
+  NotebookCellItemContentContainer,
+  CONTAINER_PADDING,
+  BorderSide,
+} from "./NotebookCell.styled";
+
+export const NotebookCell = Object.assign(_NotebookCell, {
+  displayName: "NotebookCell",
+  CONTAINER_PADDING,
+});
+
+interface NotebookCellItemProps {
+  color: string;
+  inactive?: boolean;
+  readOnly?: boolean;
+  right?: React.ReactNode;
+  containerStyle?: React.CSSProperties;
+  rightContainerStyle?: React.CSSProperties;
+  children?: React.ReactNode;
+  onClick?: React.MouseEventHandler;
+  "data-testid"?: string;
+  ref?: React.Ref<HTMLDivElement>;
+}
+
+export const NotebookCellItem = forwardRef<
+  HTMLDivElement,
+  NotebookCellItemProps
+>(function NotebookCellItem(
+  {
+    inactive,
+    color,
+    containerStyle,
+    right,
+    rightContainerStyle,
+    children,
+    readOnly,
+    ...restProps
+  },
+  ref,
+) {
+  const hasRightSide = isValidElement(right) && !readOnly;
+  const mainContentRoundedCorners: BorderSide[] = ["left"];
+  if (!hasRightSide) {
+    mainContentRoundedCorners.push("right");
+  }
+  return (
+    <NotebookCellItemContainer
+      inactive={inactive}
+      color={color}
+      {...restProps}
+      data-testid={restProps["data-testid"] ?? "notebook-cell-item"}
+      ref={ref}
+    >
+      <NotebookCellItemContentContainer
+        inactive={inactive}
+        color={color}
+        roundedCorners={mainContentRoundedCorners}
+        style={containerStyle}
+      >
+        {children}
+      </NotebookCellItemContentContainer>
+      {hasRightSide && (
+        <NotebookCellItemContentContainer
+          inactive={inactive}
+          color={color}
+          border="left"
+          roundedCorners={["right"]}
+          style={rightContainerStyle}
+        >
+          {right}
+        </NotebookCellItemContentContainer>
+      )}
+    </NotebookCellItemContainer>
+  );
+});
+
+interface NotebookCellAddProps extends NotebookCellItemProps {
+  initialAddText?: React.ReactNode;
+}
+
+export const NotebookCellAdd = forwardRef<HTMLDivElement, NotebookCellAddProps>(
+  function NotebookCellAdd({ initialAddText, ...props }, ref) {
+    return (
+      <NotebookCellItem {...props} inactive={!!initialAddText} ref={ref}>
+        {initialAddText || <Icon name="add" className="text-white" />}
+      </NotebookCellItem>
+    );
+  },
+);
diff --git a/frontend/src/metabase/query_builder/components/notebook/NotebookCell/index.ts b/frontend/src/metabase/query_builder/components/notebook/NotebookCell/index.ts
new file mode 100644
index 00000000000..9a7c9f790bd
--- /dev/null
+++ b/frontend/src/metabase/query_builder/components/notebook/NotebookCell/index.ts
@@ -0,0 +1 @@
+export * from "./NotebookCell";
-- 
GitLab