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"