From 09be4f19c8192ad19e3eb9aa88a867d3c29b47ea Mon Sep 17 00:00:00 2001
From: chefBingbong <133646395+ChefBingbong@users.noreply.github.com>
Date: Fri, 7 Jul 2023 09:28:04 +0100
Subject: [PATCH] fix: tooltip refactor for buyCrypto entry (#7266)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
### 🤖 Generated by Copilot at f745f90
### Summary
📝📱💰
This pull request enhances the `WalletInfo` component to help users buy
more crypto, refactors the `useTooltip` hook to use `lodash` and improve
performance, and updates the FAQ content for the `BuyCrypto` view. It
also adds new types for the `useTooltip` hook.
> _We're sailing on the crypto sea, me hearties, yo ho ho_
> _We're buying more BNB when our balance is low_
> _We're fixing up the `useTooltip` hook, with `debounce` and
`deviceActions`_
> _We're moving all the FAQ items to a separate file, to avoid
distractions_
### Walkthrough
* Add `InfoFilledIcon` component to `WalletInfo` component and adjust
layout and logic of `buyCryptoTooltip` component
([link](https://github.com/pancakeswap/pancake-frontend/pull/7266/files?diff=unified&w=0#diff-667fa9ffb5f38ba848d89c70e2b060c60ce67fee0185391f612eb425169911beR15),
[link](https://github.com/pancakeswap/pancake-frontend/pull/7266/files?diff=unified&w=0#diff-667fa9ffb5f38ba848d89c70e2b060c60ce67fee0185391f612eb425169911beL71-R86),
[link](https://github.com/pancakeswap/pancake-frontend/pull/7266/files?diff=unified&w=0#diff-667fa9ffb5f38ba848d89c70e2b060c60ce67fee0185391f612eb425169911beL175-R182),
[link](https://github.com/pancakeswap/pancake-frontend/pull/7266/files?diff=unified&w=0#diff-667fa9ffb5f38ba848d89c70e2b060c60ce67fee0185391f612eb425169911beL183-R192))
* Remove FAQ item from `BuyCrypto` view and move FAQ items to separate
file
([link](https://github.com/pancakeswap/pancake-frontend/pull/7266/files?diff=unified&w=0#diff-2999e60a5b9510d1e96d02fe7ecd6283d03a0363972374f5d433eb7ecd52b60fL51-L73))
* Add `debounce` function and `DeviceAction` and `Devices` types to
`useTooltip` hook and simplify event handling logic
([link](https://github.com/pancakeswap/pancake-frontend/pull/7266/files?diff=unified&w=0#diff-184e2bdd1dd0d58dab95d4efa5b52f7abd0c62c7833b5dec7f4347f21500d861R24-R33),
[link](https://github.com/pancakeswap/pancake-frontend/pull/7266/files?diff=unified&w=0#diff-6fe052fda58ce75c3e945093975d8c35309b36402839fb6166f8cfadde2aa35eL7-R12),
[link](https://github.com/pancakeswap/pancake-frontend/pull/7266/files?diff=unified&w=0#diff-6fe052fda58ce75c3e945093975d8c35309b36402839fb6166f8cfadde2aa35eR26-R36),
[link](https://github.com/pancakeswap/pancake-frontend/pull/7266/files?diff=unified&w=0#diff-6fe052fda58ce75c3e945093975d8c35309b36402839fb6166f8cfadde2aa35eL52-L53),
[link](https://github.com/pancakeswap/pancake-frontend/pull/7266/files?diff=unified&w=0#diff-6fe052fda58ce75c3e945093975d8c35309b36402839fb6166f8cfadde2aa35eL59-R91),
[link](https://github.com/pancakeswap/pancake-frontend/pull/7266/files?diff=unified&w=0#diff-6fe052fda58ce75c3e945093975d8c35309b36402839fb6166f8cfadde2aa35eL96-R104),
[link](https://github.com/pancakeswap/pancake-frontend/pull/7266/files?diff=unified&w=0#diff-6fe052fda58ce75c3e945093975d8c35309b36402839fb6166f8cfadde2aa35eL110-R111),
[link](https://github.com/pancakeswap/pancake-frontend/pull/7266/files?diff=unified&w=0#diff-6fe052fda58ce75c3e945093975d8c35309b36402839fb6166f8cfadde2aa35eL125-R142),
[link](https://github.com/pancakeswap/pancake-frontend/pull/7266/files?diff=unified&w=0#diff-6fe052fda58ce75c3e945093975d8c35309b36402839fb6166f8cfadde2aa35eR231))
---
.../components/Menu/UserMenu/WalletInfo.tsx | 26 +++--
.../localization/src/config/translations.json | 1 +
packages/uikit/src/hooks/useTooltip/types.ts | 10 ++
.../uikit/src/hooks/useTooltip/useTooltip.tsx | 110 ++++++++----------
4 files changed, 79 insertions(+), 68 deletions(-)
diff --git a/apps/web/src/components/Menu/UserMenu/WalletInfo.tsx b/apps/web/src/components/Menu/UserMenu/WalletInfo.tsx
index 7749dcf057353..d04effb430424 100644
--- a/apps/web/src/components/Menu/UserMenu/WalletInfo.tsx
+++ b/apps/web/src/components/Menu/UserMenu/WalletInfo.tsx
@@ -11,7 +11,7 @@ import {
FlexGap,
useTooltip,
TooltipText,
- InfoIcon,
+ InfoFilledIcon,
} from '@pancakeswap/uikit'
import { ChainId, WNATIVE } from '@pancakeswap/sdk'
import { FetchStatus } from 'config/constants/types'
@@ -68,15 +68,21 @@ const WalletInfo: React.FC = ({ hasLowNativeBalance, onDismiss
targetRef: buyCryptoTargetRef,
} = useTooltip(
<>
-
-
- {t('%currency% Balance Low. You need %currency% for transaction fees.', {
- currency: native?.symbol,
- })}
-
+
+
+
+ {t('%currency% Balance Low. You need %currency% for transaction fees.', {
+ currency: native?.symbol,
+ })}
+
+ onDismiss?.()}>
+
+
+
>,
{
+ isInPortal: false,
placement: isMobile ? 'top' : 'bottom',
trigger: isMobile ? 'focus' : 'hover',
...(isMobile && { manualVisible: mobileTooltipShow }),
@@ -172,7 +178,7 @@ const WalletInfo: React.FC = ({ hasLowNativeBalance, onDismiss
fontWeight={Number(bnbBalance?.data?.value) === 0 ? 'bold' : 'normal'}
color={Number(bnbBalance?.data?.value) === 0 ? 'warning' : 'normal'}
>
- {bnbBalance?.data?.value && formatBigInt(bnbBalance?.data?.value ?? 0n, 6)}
+ {formatBigInt(bnbBalance?.data?.value ?? 0n, 6)}
= ({ hasLowNativeBalance, onDismiss
display="flex"
style={{ justifyContent: 'center' }}
>
- {Number(bnbBalance?.data?.value) === 0 ? : null}
+ {Number(bnbBalance?.data?.value) === 0 ? (
+
+ ) : null}
{buyCryptoTooltipVisible && (!isMobile || mobileTooltipShow) && buyCryptoTooltip}
diff --git a/packages/localization/src/config/translations.json b/packages/localization/src/config/translations.json
index 22a89980ddf8e..573acd75c3c5b 100644
--- a/packages/localization/src/config/translations.json
+++ b/packages/localization/src/config/translations.json
@@ -2602,6 +2602,7 @@
"PancakeSwap Now Live on Polygon zkEVM!": "PancakeSwap Now Live on Polygon zkEVM!",
"Polygon zkEVM is LIVE!": "Polygon zkEVM is LIVE!",
"Swap and provide liquidity on Polygon zkEVM now": "Swap and provide liquidity on Polygon zkEVM now",
+ "Buy %currency%": "Buy %currency%",
"Caution - METIS Token": "Caution - METIS Token",
"Please exercise due caution when trading / providing liquidity for the METIS token. The protocol was recently affected by the": "Please exercise due caution when trading / providing liquidity for the METIS token. The protocol was recently affected by the",
"PolyNetwork Exploit.": "PolyNetwork Exploit.",
diff --git a/packages/uikit/src/hooks/useTooltip/types.ts b/packages/uikit/src/hooks/useTooltip/types.ts
index cba7d8f5196f8..ed0c1cbb2e847 100644
--- a/packages/uikit/src/hooks/useTooltip/types.ts
+++ b/packages/uikit/src/hooks/useTooltip/types.ts
@@ -21,3 +21,13 @@ export interface TooltipOptions {
}
export type TriggerType = "click" | "hover" | "focus";
+
+export interface DeviceAction {
+ start: string;
+ end: string;
+}
+
+export enum Devices {
+ touchDevice = "touchDevice",
+ nonTouchDevice = "nonTouchDevice",
+}
diff --git a/packages/uikit/src/hooks/useTooltip/useTooltip.tsx b/packages/uikit/src/hooks/useTooltip/useTooltip.tsx
index b20f8e0bdfafc..60c3c9e03ee07 100644
--- a/packages/uikit/src/hooks/useTooltip/useTooltip.tsx
+++ b/packages/uikit/src/hooks/useTooltip/useTooltip.tsx
@@ -1,14 +1,15 @@
import { AnimatePresence, Variants, LazyMotion, domAnimation } from "framer-motion";
-import React, { useCallback, useEffect, useRef, useState } from "react";
+import React, { useCallback, useEffect, useState } from "react";
import { createPortal } from "react-dom";
import { usePopper } from "react-popper";
import { isMobile } from "react-device-detect";
import { DefaultTheme, ThemeProvider, useTheme } from "styled-components";
+import debounce from "lodash/debounce";
import { dark, light } from "../../theme";
import getPortalRoot from "../../util/getPortalRoot";
import isTouchDevice from "../../util/isTouchDevice";
import { Arrow, StyledTooltip } from "./StyledTooltip";
-import { TooltipOptions, TooltipRefs } from "./types";
+import { DeviceAction, Devices, TooltipOptions, TooltipRefs } from "./types";
const animationVariants: Variants = {
initial: { opacity: 0 },
@@ -22,6 +23,17 @@ const animationMap = {
exit: "exit",
};
+const deviceActions: { [device in Devices]: DeviceAction } = {
+ [Devices.touchDevice]: {
+ start: "touchstart",
+ end: "touchend",
+ },
+ [Devices.nonTouchDevice]: {
+ start: "mouseenter",
+ end: "mouseleave",
+ },
+};
+
const invertTheme = (currentTheme: DefaultTheme) => {
if (currentTheme.isDark) {
return light;
@@ -49,65 +61,54 @@ const useTooltip = (content: React.ReactNode, options?: TooltipOptions): Tooltip
const [arrowElement, setArrowElement] = useState(null);
const [visible, setVisible] = useState(manualVisible);
- const isHoveringOverTooltip = useRef(false);
- const hideTimeoutRef = useRef();
useEffect(() => {
setVisible(manualVisible);
}, [manualVisible]);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ const debouncedHide = useCallback(
+ debounce(() => {
+ setVisible(false);
+ }, hideTimeout),
+ [hideTimeout]
+ );
+ // using lodash debounce we can get rid of hideTimeout cleanups
+ // loadash's debounce handles cleanup it its implementation
const hideTooltip = useCallback(
(e: Event) => {
if (manualVisible) return;
- const hide = () => {
- if (!avoidToStopPropagation) {
- e.stopPropagation();
- e.preventDefault();
- }
-
- setVisible(false);
- };
-
+ if (!avoidToStopPropagation) {
+ e.stopPropagation();
+ e.preventDefault();
+ }
if (trigger === "hover") {
- if (hideTimeoutRef.current) {
- window.clearTimeout(hideTimeoutRef.current);
- }
- if (e.target === tooltipElement) {
- isHoveringOverTooltip.current = false;
- }
- if (!isHoveringOverTooltip.current) {
- hideTimeoutRef.current = window.setTimeout(() => {
- if (!isHoveringOverTooltip.current) {
- hide();
- }
- }, hideTimeout);
- }
+ debouncedHide();
} else {
- hide();
+ setVisible(false);
}
},
- [manualVisible, trigger, avoidToStopPropagation, tooltipElement, hideTimeout]
+ [manualVisible, trigger, debouncedHide, avoidToStopPropagation]
);
const showTooltip = useCallback(
(e: Event) => {
setVisible(true);
if (trigger === "hover") {
- if (e.target === targetElement) {
- // If we were about to close the tooltip and got back to it
- // then prevent closing it.
- clearTimeout(hideTimeoutRef.current);
- }
- if (e.target === tooltipElement) {
- isHoveringOverTooltip.current = true;
- }
+ // we dont need to make a inTooltipRef anymore, when we leave
+ // the target, hide tooltip is called for leaving the target, but show tooltip
+ // is called for entering the tooltip. since we enact a delay in hidetooltip,
+ // by the time the dylay is over lodash debounce will be cancelled until we leave the
+ // tooltip calling hidetooltip onece again to close. clever method jackson pointed me
+ // onto. saves a lot of nedless states and refs and listeners
+ debouncedHide.cancel();
}
if (!avoidToStopPropagation) {
e.stopPropagation();
e.preventDefault();
}
},
- [tooltipElement, targetElement, trigger, avoidToStopPropagation]
+ [trigger, avoidToStopPropagation, debouncedHide]
);
const toggleTooltip = useCallback(
@@ -122,32 +123,23 @@ const useTooltip = (content: React.ReactNode, options?: TooltipOptions): Tooltip
useEffect(() => {
if (targetElement === null || trigger !== "hover" || manualVisible) return undefined;
- if (isTouchDevice()) {
- targetElement.addEventListener("touchstart", showTooltip);
- targetElement.addEventListener("touchend", hideTooltip);
- } else {
- targetElement.addEventListener("mouseenter", showTooltip);
- targetElement.addEventListener("mouseleave", hideTooltip);
- }
- return () => {
- targetElement.removeEventListener("touchstart", showTooltip);
- targetElement.removeEventListener("touchend", hideTooltip);
- targetElement.removeEventListener("mouseenter", showTooltip);
- targetElement.removeEventListener("mouseleave", showTooltip);
- };
- }, [trigger, targetElement, hideTooltip, showTooltip, manualVisible]);
+ const eventHandlers = isTouchDevice() ? deviceActions.touchDevice : deviceActions.nonTouchDevice;
- // Keep tooltip open when cursor moves from the targetElement to the tooltip
- useEffect(() => {
- if (tooltipElement === null || trigger !== "hover" || manualVisible) return undefined;
+ [targetElement, tooltipElement].forEach((element) => {
+ element?.addEventListener(eventHandlers.start, showTooltip);
+ element?.addEventListener(eventHandlers.end, hideTooltip);
+ });
- tooltipElement.addEventListener("mouseenter", showTooltip);
- tooltipElement.addEventListener("mouseleave", hideTooltip);
return () => {
- tooltipElement.removeEventListener("mouseenter", showTooltip);
- tooltipElement.removeEventListener("mouseleave", hideTooltip);
+ [targetElement, tooltipElement].forEach((element) => {
+ element?.removeEventListener(eventHandlers.start, showTooltip);
+ element?.removeEventListener(eventHandlers.end, hideTooltip);
+ debouncedHide.cancel();
+ });
};
- }, [trigger, tooltipElement, hideTooltip, showTooltip, manualVisible]);
+ }, [trigger, targetElement, hideTooltip, showTooltip, manualVisible, tooltipElement, debouncedHide]);
+
+ // no longer need the extra useeffect
// Trigger = click
useEffect(() => {