From d469e73d5f08281da08dcf6d1b0a3175cfa4112e Mon Sep 17 00:00:00 2001
From: Sean Costello <sean.costello@vimeo.com>
Date: Fri, 24 Feb 2023 17:28:09 -0500
Subject: [PATCH] Replace useAnchor in TourPoint component with
 react-tiny-popover

---
 package.json                                |   3 +
 src/components/TourPoint/TourPoint.tsx      | 196 ++++++++++++--------
 src/components/TourPoint/TourPoint.types.ts |  20 +-
 yarn.lock                                   |   5 +
 4 files changed, 138 insertions(+), 86 deletions(-)

diff --git a/package.json b/package.json
index 04c1d032b..9f9fb6d75 100644
--- a/package.json
+++ b/package.json
@@ -23,6 +23,9 @@
     "main": "index.js",
     "types": "index.d.ts",
     "sideEffects": false,
+    "dependencies": {
+        "react-tiny-popover": "^7.2.3"
+    },
     "devDependencies": {
         "@babel/core": "^7.21.0",
         "@babel/plugin-proposal-class-properties": "^7.18.6",
diff --git a/src/components/TourPoint/TourPoint.tsx b/src/components/TourPoint/TourPoint.tsx
index 6899b26fd..ff9033ca8 100644
--- a/src/components/TourPoint/TourPoint.tsx
+++ b/src/components/TourPoint/TourPoint.tsx
@@ -1,11 +1,6 @@
-import React, {
-  cloneElement,
-  useContext,
-  useRef,
-  MouseEvent,
-} from 'react';
-
-import type { Props } from './TourPoint.types';
+import React, { cloneElement, useContext, MouseEvent } from 'react';
+
+import type { Props, Attach } from './TourPoint.types';
 import { Footer, Steps, TourPointStyled } from './TourPoint.style';
 import { TourContext } from './TourPoint.context';
 import { Motion } from './TourPoint.motion';
@@ -13,31 +8,37 @@ import { Caret, buildClipPaths } from './Caret';
 
 import { Header, Paragraph } from '../../typography';
 import { Button } from '../Button/Button';
+import { capitalize } from '../../utils';
 import {
-  Anchor,
-  useAnchor,
-} from '../../utils/hooks/useAnchor/useAnchor';
-import { usePortal, capitalize } from '../../utils';
+  Popover,
+  PopoverAlign,
+  PopoverPosition,
+} from 'react-tiny-popover';
 
 TourPoint.Motion = Motion;
 
 const lessThan = (a: number, b: number) => a < b && a;
 const greaterThan = (a: number, b: number) => a > b || a;
 
+type compareFn = (nextIndex, totalSteps) => number | boolean;
+
 export function TourPoint({
-  active = true,
-  alt = null,
-  attach = 'left',
+  active,
+  content,
   children,
+  // legacy
+  attach,
+  positions,
+  align,
+  style,
+  src,
+  alt = '',
+  title,
+  onClose,
   confirmation = 'Got it',
   dismission = 'Dismiss',
-  content,
   getStepsTranslation,
-  onClose,
-  src,
-  step,
-  style,
-  title,
+  step = 0,
   ...props
 }: Props) {
   const {
@@ -47,23 +48,32 @@ export function TourPoint({
     steps,
   } = useContext(TourContext);
 
-  const ref = useRef(null);
-  const refAnchor = useRef(null);
-  const childrenClone = cloneElement(children, { ref });
-  const propsAnchor = useAnchor(ref, attach, active);
+  let pos = positions;
+  let alignment = align;
+  let clipPaths = {};
+  let margin = {};
+  const marginSize = '1rem';
+  if (attach && !positions) {
+    [pos, alignment] = convertAttachToPositionAlign(attach);
+    clipPaths = buildClipPaths(attach);
+    const [side] = attach.split('-');
+    const marginSide = 'margin' + capitalize(side);
+    margin = { [marginSide]: marginSize };
+  }
 
   const Image = src && <img src={src} alt={alt} />;
   const Title = title && <Header size="3">{title}</Header>;
-
   const Content = slotProgressive(content, <Paragraph size="1" />);
 
-  function stepFn(direction, increment = 0, compare = null) {
-    const next = compare ? compare(index + increment, steps) : null;
+  function stepFn(direction, increment = 0, compare?: compareFn) {
+    const next = compare
+      ? compare((index || 0) + increment, steps)
+      : null;
 
     return (event: MouseEvent) => {
       if (automated) {
         onClose?.(event, { direction });
-        indexSet(next);
+        indexSet?.(next);
       }
     };
   }
@@ -82,58 +92,90 @@ export function TourPoint({
     <Button variant="minimalTransparent" onClick={dismiss} />
   );
 
-  const clipPath = buildClipPaths(attach);
-
-  const side = attach.split('-')[0] || attach;
-  const marginSide = 'margin' + capitalize(side);
-  const margin = '1rem';
-
-  const zIndex = style?.zIndex || 6000;
-
-  const childrenPortal = usePortal(
-    <Anchor zIndex={zIndex} {...propsAnchor}>
-      <Motion attach={attach}>
-        <TourPointStyled
-          style={{
-            ...style,
-            ...clipPath,
-            [marginSide]: margin,
-          }}
-          ref={refAnchor}
-          {...props}
-        >
-          {Image}
-          {Title}
-          {Content}
-
-          <Footer>
-            {steps && (
-              <Steps onClick={stepBack}>
-                {getStepsTranslation
-                  ? getStepsTranslation({
-                      currentStep: step,
-                      totalSteps: steps,
-                    })
-                  : `Step ${step} of ${steps}`}
-              </Steps>
-            )}
-            {Dismiss}
-            {Confirm}
-          </Footer>
-          <Caret attach={attach} />
-        </TourPointStyled>
-      </Motion>
-    </Anchor>
-  );
-
   return (
-    <>
-      {childrenClone}
-      {childrenPortal}
-    </>
+    <Motion attach={attach}>
+      <Popover
+        isOpen={!!active}
+        positions={pos}
+        align={alignment}
+        containerStyle={{
+          // Added so this is rendered on the same plane as useAnchor currently is.
+          // This should be removed once useAnchor is totally deprecated.
+          zIndex: '5000',
+        }}
+        content={
+          <TourPointStyled
+            style={{
+              ...style,
+              ...clipPaths,
+              ...margin,
+            }}
+            {...props}
+          >
+            {Image}
+            {Title}
+            {Content}
+            <Footer>
+              {steps && (
+                <Steps onClick={stepBack}>
+                  {getStepsTranslation
+                    ? getStepsTranslation({
+                        currentStep: step,
+                        totalSteps: steps,
+                      })
+                    : `Step ${step} of ${steps}`}
+                </Steps>
+              )}
+              {Dismiss}
+              {Confirm}
+            </Footer>
+            <Caret attach={attach} />
+          </TourPointStyled>
+        }
+      >
+        {children}
+      </Popover>
+    </Motion>
   );
 }
 
+function convertAttachToPositionAlign(
+  attach: Attach
+): [PopoverPosition[], PopoverAlign] {
+  const [side, placement] = attach.split('-');
+
+  let position: PopoverPosition = 'right';
+  switch (side) {
+    case 'bottom':
+      position = 'top';
+      break;
+    case 'right':
+      position = 'left';
+      break;
+    case 'top':
+      position = 'bottom';
+      break;
+    default:
+      break;
+  }
+
+  let align: PopoverAlign = 'center';
+  switch (placement) {
+    case 'top':
+    case 'left':
+      align = 'start';
+      break;
+    case 'bottom':
+    case 'right':
+      align = 'end';
+      break;
+    default:
+      break;
+  }
+
+  return [[position], align];
+}
+
 function slotProgressive(children, Wrapper) {
   const ProgressiveElement =
     typeof children === 'string' || typeof children === 'number'
diff --git a/src/components/TourPoint/TourPoint.types.ts b/src/components/TourPoint/TourPoint.types.ts
index 7f97c7df7..27dd55772 100644
--- a/src/components/TourPoint/TourPoint.types.ts
+++ b/src/components/TourPoint/TourPoint.types.ts
@@ -1,9 +1,5 @@
-import {
-  CSSProperties,
-  ReactElement,
-  ReactNode,
-  MouseEvent,
-} from 'react';
+import { CSSProperties, ReactNode, MouseEvent } from 'react';
+import { PopoverAlign, PopoverPosition } from 'react-tiny-popover';
 export interface Props {
   id?: string;
   active?: boolean;
@@ -18,7 +14,7 @@ export interface Props {
    * [default = 'left']
    */
   attach?: Attach;
-  children?: ReactElement;
+  children?: JSX.Element;
   confirmation?: ReactNode;
   content?: ReactNode;
   dismission?: ReactNode;
@@ -28,7 +24,7 @@ export interface Props {
    * The address or URL of the a media resource that is to be considered.
    */
   src?: HTMLImageElement['src'];
-  step?: number | null;
+  step: number;
   style?: CSSProperties;
   title?: HTMLElement['title'];
   getStepsTranslation?: ({
@@ -38,11 +34,17 @@ export interface Props {
     currentStep: number;
     totalSteps: number;
   }) => string;
+
+  /**
+   * react-tiny-popover props not yet implemented
+   */
+  positions?: PopoverPosition[];
+  align?: PopoverAlign;
 }
 
 type StepEvent = { direction: 'back' | 'next' | 'dismiss' };
 
-type Attach =
+export type Attach =
   | 'right'
   | 'right-top'
   | 'right-bottom'
diff --git a/yarn.lock b/yarn.lock
index 7b596e68e..2791777f8 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -13490,6 +13490,11 @@ react-syntax-highlighter@^15.4.5:
     prismjs "^1.27.0"
     refractor "^3.6.0"
 
+react-tiny-popover@^7.2.3:
+  version "7.2.3"
+  resolved "https://registry.yarnpkg.com/react-tiny-popover/-/react-tiny-popover-7.2.3.tgz#5130b2162a117c646c8448218306ac90c56d6cc8"
+  integrity sha512-Gcufxg7kZMVMWAEveMDZEt1cwhP+x/2uVmYAV52Cp2ZBxXabUkAlzWrRlwKEiqjBVkFtIgBmycb1fGNX38/5hA==
+
 react@*:
   version "17.0.1"
   resolved "https://registry.yarnpkg.com/react/-/react-17.0.1.tgz#6e0600416bd57574e3f86d92edba3d9008726127"