From 4d77b60ee07cbb23a080a873c018cce398350894 Mon Sep 17 00:00:00 2001
From: Dalton <daltojohnso@users.noreply.github.com>
Date: Thu, 5 May 2022 13:42:01 -0700
Subject: [PATCH] Add minHeight option to sizeToFit modifier (#22438)

* Add minHeight option to sizeToFit modifier

* Rmv unused things
---
 .../components/Popover/SizeToFitModifier.ts   | 65 +++++++++++++++++++
 .../components/Popover/TippyPopover.info.js   |  2 +-
 .../components/Popover/TippyPopover.tsx       | 33 ++--------
 3 files changed, 73 insertions(+), 27 deletions(-)
 create mode 100644 frontend/src/metabase/components/Popover/SizeToFitModifier.ts

diff --git a/frontend/src/metabase/components/Popover/SizeToFitModifier.ts b/frontend/src/metabase/components/Popover/SizeToFitModifier.ts
new file mode 100644
index 00000000000..5c4c31674ff
--- /dev/null
+++ b/frontend/src/metabase/components/Popover/SizeToFitModifier.ts
@@ -0,0 +1,65 @@
+import * as popper from "@popperjs/core";
+
+const PAGE_PADDING = 10;
+const SIZE_TO_FIT_MIN_HEIGHT = 200;
+
+export type SizeToFitOptions = {
+  minHeight: number;
+};
+
+export function sizeToFitModifierFn({
+  state,
+  options,
+}: popper.ModifierArguments<SizeToFitOptions>) {
+  const {
+    placement,
+    rects: {
+      popper: { height },
+      reference: { height: targetHeight },
+    },
+  } = state;
+  const { minHeight = SIZE_TO_FIT_MIN_HEIGHT } = options;
+
+  const topPlacement = placement.startsWith("top");
+  const bottomPlacement = placement.startsWith("bottom");
+
+  if (topPlacement || bottomPlacement) {
+    const overflow = popper.detectOverflow(state);
+    const distanceFromEdge = placement.startsWith("top")
+      ? overflow.top
+      : overflow.bottom;
+
+    const maxHeight = height - distanceFromEdge - PAGE_PADDING;
+    const minnedMaxHeight = Math.max(maxHeight, minHeight);
+
+    const oppositeSpace = window.innerHeight - targetHeight - maxHeight;
+
+    if (
+      maxHeight < minHeight &&
+      oppositeSpace > minHeight &&
+      !state.modifiersData.sizeToFit.flipped
+    ) {
+      state.placement = getAltPlacement(placement);
+      state.modifiersData.sizeToFit.flipped = true;
+      state.reset = true;
+    } else {
+      state.styles.popper.maxHeight = `${minnedMaxHeight}px`;
+    }
+  }
+}
+
+export function getAltPlacement(
+  placementStr: popper.Placement,
+): popper.Placement {
+  const [targetPlacement, horizontalPlacement] = placementStr.split("-") as
+    | [string]
+    | [string, string];
+  const newPlacement = [
+    targetPlacement === "top" ? "bottom" : "top",
+    horizontalPlacement,
+  ]
+    .filter((str): str is string => Boolean(str))
+    .join("-");
+
+  return newPlacement as popper.Placement;
+}
diff --git a/frontend/src/metabase/components/Popover/TippyPopover.info.js b/frontend/src/metabase/components/Popover/TippyPopover.info.js
index d7d5a454332..6d6a780d1d8 100644
--- a/frontend/src/metabase/components/Popover/TippyPopover.info.js
+++ b/frontend/src/metabase/components/Popover/TippyPopover.info.js
@@ -119,5 +119,5 @@ export const examples = {
       {target}
     </TippyPopover>
   ),
-  extra_space: <div style={{ height: 250 }} />,
+  extra_space: <div style={{ height: 400 }} />,
 };
diff --git a/frontend/src/metabase/components/Popover/TippyPopover.tsx b/frontend/src/metabase/components/Popover/TippyPopover.tsx
index 7ffcbcae739..3ae1d500d91 100644
--- a/frontend/src/metabase/components/Popover/TippyPopover.tsx
+++ b/frontend/src/metabase/components/Popover/TippyPopover.tsx
@@ -2,7 +2,6 @@ import React, { useState, useMemo, useCallback, useRef } from "react";
 import PropTypes from "prop-types";
 import * as TippyReact from "@tippyjs/react";
 import * as tippy from "tippy.js";
-import * as popper from "@popperjs/core";
 import cx from "classnames";
 import { merge } from "icepick";
 
@@ -12,6 +11,7 @@ import { isCypressActive } from "metabase/env";
 import useSequencedContentCloseHandler from "metabase/hooks/use-sequenced-content-close-handler";
 
 import { DEFAULT_Z_INDEX } from "./constants";
+import { sizeToFitModifierFn, SizeToFitOptions } from "./SizeToFitModifier";
 
 const TippyComponent = TippyReact.default;
 type TippyProps = TippyReact.TippyProps;
@@ -21,11 +21,10 @@ export interface ITippyPopoverProps extends TippyProps {
   disableContentSandbox?: boolean;
   lazy?: boolean;
   flip?: boolean;
-  sizeToFit?: boolean;
+  sizeToFit?: boolean | SizeToFitOptions;
   onClose?: () => void;
 }
 
-const PAGE_PADDING = 10;
 const OFFSET: [number, number] = [0, 5];
 
 const propTypes = {
@@ -48,33 +47,15 @@ function getPopperOptions({
       modifiers: [
         {
           name: "flip",
-          enabled: flip,
+          enabled: flip && !sizeToFit,
         },
         {
           name: "sizeToFit",
           phase: "beforeWrite",
-          enabled: sizeToFit,
-          requiresIfExists: ["offset", "flip"],
-          fn: ({
-            state,
-            options,
-          }: popper.ModifierArguments<Record<string, unknown>>) => {
-            const {
-              placement,
-              rects: {
-                popper: { height },
-              },
-            } = state;
-            if (placement.startsWith("top") || placement.startsWith("bottom")) {
-              const overflow = popper.detectOverflow(state, options);
-              const distanceFromEdge = placement.startsWith("top")
-                ? overflow.top
-                : overflow.bottom;
-
-              const maxHeight = height - distanceFromEdge - PAGE_PADDING;
-              state.styles.popper.maxHeight = `${maxHeight}px`;
-            }
-          },
+          enabled: sizeToFit !== false,
+          requiresIfExists: ["offset"],
+          fn: sizeToFitModifierFn,
+          options: typeof sizeToFit === "object" ? sizeToFit : undefined,
         },
       ],
     },
-- 
GitLab