From 90753dabe328d41353814f7c244e3faffb316bcc Mon Sep 17 00:00:00 2001
From: "Mahatthana (Kelvin) Nomsawadi" <mahatthana.n@gmail.com>
Date: Tue, 12 Apr 2022 12:10:18 +0700
Subject: [PATCH] Migrate ExpressionEditorHelpText to TippyPopover (#21572)

---
 .../metabase/components/Popover/Popover.css   |  1 +
 .../components/ExpressionPopover.jsx          |  5 +-
 .../expressions/ExpressionEditorHelpText.tsx  | 89 ++++++++++---------
 .../expressions/ExpressionEditorTextfield.jsx |  7 +-
 .../expressions/ExpressionWidget.jsx          |  5 +-
 5 files changed, 63 insertions(+), 44 deletions(-)

diff --git a/frontend/src/metabase/components/Popover/Popover.css b/frontend/src/metabase/components/Popover/Popover.css
index 1fdc09aae15..63356fd2563 100644
--- a/frontend/src/metabase/components/Popover/Popover.css
+++ b/frontend/src/metabase/components/Popover/Popover.css
@@ -45,6 +45,7 @@
 }
 
 .tippy-box[data-theme~="popover"] {
+  font-size: inherit;
   background-color: var(--color-bg-white);
   color: var(--color-text-default);
   border: 1px solid var(--color-border);
diff --git a/frontend/src/metabase/query_builder/components/ExpressionPopover.jsx b/frontend/src/metabase/query_builder/components/ExpressionPopover.jsx
index 674a27ae519..5af772ed87a 100644
--- a/frontend/src/metabase/query_builder/components/ExpressionPopover.jsx
+++ b/frontend/src/metabase/query_builder/components/ExpressionPopover.jsx
@@ -16,6 +16,8 @@ export default class ExpressionPopover extends React.Component {
     isBlank: true,
   };
 
+  helpTextTarget = React.createRef();
+
   render() {
     const {
       title,
@@ -41,8 +43,9 @@ export default class ExpressionPopover extends React.Component {
             <h3 className="inline-block pl1">{title}</h3>
           </a>
         </div>
-        <div className="p1">
+        <div className="p1" ref={this.helpTextTarget}>
           <ExpressionEditorTextfield
+            helpTextTarget={this.helpTextTarget.current}
             startRule={startRule}
             expression={expression}
             query={query}
diff --git a/frontend/src/metabase/query_builder/components/expressions/ExpressionEditorHelpText.tsx b/frontend/src/metabase/query_builder/components/expressions/ExpressionEditorHelpText.tsx
index 3c8792e889f..c2b6fce4136 100644
--- a/frontend/src/metabase/query_builder/components/expressions/ExpressionEditorHelpText.tsx
+++ b/frontend/src/metabase/query_builder/components/expressions/ExpressionEditorHelpText.tsx
@@ -5,7 +5,7 @@ import MetabaseSettings from "metabase/lib/settings";
 import colors from "metabase/lib/colors";
 import ExternalLink from "metabase/core/components/ExternalLink";
 import Icon from "metabase/components/Icon";
-import Popover from "metabase/components/Popover";
+import TippyPopover from "metabase/components/Popover/TippyPopover";
 
 type Arg = {
   name: string;
@@ -22,48 +22,55 @@ type HelpText = {
 interface HelpTextProps {
   helpText: HelpText;
   width: number;
+  target: Element;
 }
 
-const HelpText = ({ helpText, width }: HelpTextProps) =>
-  helpText ? (
-    <Popover
-      tetherOptions={{
-        attachment: "top left",
-        targetAttachment: "bottom left",
-      }}
-      style={{ width }}
-      isOpen
-    >
-      {/* Prevent stealing focus from input box causing the help text to be closed (metabase#17548) */}
-      <div onMouseDown={e => e.preventDefault()}>
-        <p
-          className="p2 m0 text-monospace text-bold"
-          style={{ background: colors["bg-yellow"] }}
-        >
-          {helpText.structure}
-        </p>
-        <div className="p2 border-top">
-          <p className="mt0 text-bold">{helpText.description}</p>
-          <p className="text-code m0 text-body">{helpText.example}</p>
-        </div>
-        <div className="p2 border-top">
-          {helpText.args.map(({ name, description }, index) => (
-            <div key={index}>
-              <h4 className="text-medium">{name}</h4>
-              <p className="mt1 text-bold">{description}</p>
+const HelpText = ({ helpText, width, target }: HelpTextProps) => {
+  if (!helpText) {
+    return null;
+  }
+
+  return (
+    <TippyPopover
+      maxWidth={width}
+      reference={target}
+      placement="bottom-start"
+      visible
+      content={
+        <>
+          {/* Prevent stealing focus from input box causing the help text to be closed (metabase#17548) */}
+          <div onMouseDown={e => e.preventDefault()}>
+            <p
+              className="p2 m0 text-monospace text-bold"
+              style={{ background: colors["bg-yellow"] }}
+            >
+              {helpText.structure}
+            </p>
+            <div className="p2 border-top">
+              <p className="mt0 text-bold">{helpText.description}</p>
+              <p className="text-code m0 text-body">{helpText.example}</p>
+            </div>
+            <div className="p2 border-top">
+              {helpText.args.map(({ name, description }, index) => (
+                <div key={index}>
+                  <h4 className="text-medium">{name}</h4>
+                  <p className="mt1 text-bold">{description}</p>
+                </div>
+              ))}
+              <ExternalLink
+                className="link text-bold block my1"
+                target="_blank"
+                href={MetabaseSettings.docsUrl("users-guide/expressions")}
+              >
+                <Icon name="reference" size={12} className="mr1" />
+                {t`Learn more`}
+              </ExternalLink>
             </div>
-          ))}
-          <ExternalLink
-            className="link text-bold block my1"
-            target="_blank"
-            href={MetabaseSettings.docsUrl("users-guide/expressions")}
-          >
-            <Icon name="reference" size={12} className="mr1" />
-            {t`Learn more`}
-          </ExternalLink>
-        </div>
-      </div>
-    </Popover>
-  ) : null;
+          </div>
+        </>
+      }
+    ></TippyPopover>
+  );
+};
 
 export default HelpText;
diff --git a/frontend/src/metabase/query_builder/components/expressions/ExpressionEditorTextfield.jsx b/frontend/src/metabase/query_builder/components/expressions/ExpressionEditorTextfield.jsx
index df8a2570355..0c2e1eab613 100644
--- a/frontend/src/metabase/query_builder/components/expressions/ExpressionEditorTextfield.jsx
+++ b/frontend/src/metabase/query_builder/components/expressions/ExpressionEditorTextfield.jsx
@@ -60,6 +60,7 @@ export default class ExpressionEditorTextfield extends React.Component {
     onError: PropTypes.func.isRequired,
     startRule: PropTypes.string.isRequired,
     onBlankChange: PropTypes.func,
+    helpTextTarget: PropTypes.instanceOf(Element).isRequired,
   };
 
   static defaultProps = {
@@ -445,7 +446,11 @@ export default class ExpressionEditorTextfield extends React.Component {
           />
         </EditorContainer>
         <ErrorMessage error={errorMessage} />
-        <HelpText helpText={this.state.helpText} width={this.props.width} />
+        <HelpText
+          target={this.props.helpTextTarget}
+          helpText={this.state.helpText}
+          width={this.props.width}
+        />
       </React.Fragment>
     );
   }
diff --git a/frontend/src/metabase/query_builder/components/expressions/ExpressionWidget.jsx b/frontend/src/metabase/query_builder/components/expressions/ExpressionWidget.jsx
index bdc3a14b394..fbb152dac74 100644
--- a/frontend/src/metabase/query_builder/components/expressions/ExpressionWidget.jsx
+++ b/frontend/src/metabase/query_builder/components/expressions/ExpressionWidget.jsx
@@ -30,6 +30,8 @@ export default class ExpressionWidget extends Component {
     name: "",
   };
 
+  helpTextTarget = React.createRef();
+
   UNSAFE_componentWillMount() {
     this.UNSAFE_componentWillReceiveProps(this.props);
   }
@@ -61,8 +63,9 @@ export default class ExpressionWidget extends Component {
       <div style={{ maxWidth: "600px" }}>
         <div className="p2">
           <div className="h5 text-uppercase text-medium text-bold">{t`Field formula`}</div>
-          <div>
+          <div ref={this.helpTextTarget}>
             <ExpressionEditorTextfield
+              helpTextTarget={this.helpTextTarget.current}
               expression={expression}
               query={query}
               onChange={parsedExpression =>
-- 
GitLab