Skip to content

Commit

Permalink
fix: tooltip refactor for buyCrypto entry (#7266)
Browse files Browse the repository at this point in the history
<!--
Before opening a pull request, please read the [contributing
guidelines](https://github.com/pancakeswap/pancake-frontend/blob/develop/CONTRIBUTING.md)
first
-->

<!--
copilot:all
-->
### <samp>🤖 Generated by Copilot at f745f90</samp>

### Summary
📝📱💰

<!--
1. 📝 - This emoji represents the change that moves the FAQ items to a
separate file and updates the content and links. It conveys the idea of
editing or writing documentation or text.
2. 📱 - This emoji represents the change that adds two new types to the
`useTooltip` hook: `DeviceAction` and `Devices`. It conveys the idea of
adding support or compatibility for different devices or platforms, such
as mobile phones or tablets.
3. 💰 - This emoji represents the change that improves the user
experience of the `WalletInfo` component by adding a button and an icon
to help users buy more crypto. It conveys the idea of money, finance, or
transactions.
-->
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))
  • Loading branch information
ChefBingbong authored Jul 7, 2023
1 parent 31e874d commit 09be4f1
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 68 deletions.
26 changes: 17 additions & 9 deletions apps/web/src/components/Menu/UserMenu/WalletInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -68,15 +68,21 @@ const WalletInfo: React.FC<WalletInfoProps> = ({ hasLowNativeBalance, onDismiss
targetRef: buyCryptoTargetRef,
} = useTooltip(
<>
<Box maxWidth="150px">
<Text as="p">
{t('%currency% Balance Low. You need %currency% for transaction fees.', {
currency: native?.symbol,
})}
</Text>
<Box maxWidth="140px">
<FlexGap gap="8px" flexDirection="column" justifyContent="space-between">
<Text as="p">
{t('%currency% Balance Low. You need %currency% for transaction fees.', {
currency: native?.symbol,
})}
</Text>
<InternalLink href="/buy-crypto" onClick={() => onDismiss?.()}>
<Button height="30px">{t('Buy %currency%', { currency: native?.symbol })}</Button>
</InternalLink>
</FlexGap>
</Box>
</>,
{
isInPortal: false,
placement: isMobile ? 'top' : 'bottom',
trigger: isMobile ? 'focus' : 'hover',
...(isMobile && { manualVisible: mobileTooltipShow }),
Expand Down Expand Up @@ -172,15 +178,17 @@ const WalletInfo: React.FC<WalletInfoProps> = ({ 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)}
</Text>
<TooltipText
ref={buyCryptoTargetRef}
onClick={() => setMobileTooltipShow(false)}
display="flex"
style={{ justifyContent: 'center' }}
>
{Number(bnbBalance?.data?.value) === 0 ? <InfoIcon pl="4px" fill="#000" color="#D67E0A" /> : null}
{Number(bnbBalance?.data?.value) === 0 ? (
<InfoFilledIcon pl="2px" fill="#000" color="#D67E0A" width="22px" />
) : null}
</TooltipText>
{buyCryptoTooltipVisible && (!isMobile || mobileTooltipShow) && buyCryptoTooltip}
</Flex>
Expand Down
1 change: 1 addition & 0 deletions packages/localization/src/config/translations.json
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
Expand Down
10 changes: 10 additions & 0 deletions packages/uikit/src/hooks/useTooltip/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
}
110 changes: 51 additions & 59 deletions packages/uikit/src/hooks/useTooltip/useTooltip.tsx
Original file line number Diff line number Diff line change
@@ -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 },
Expand All @@ -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;
Expand Down Expand Up @@ -49,65 +61,54 @@ const useTooltip = (content: React.ReactNode, options?: TooltipOptions): Tooltip
const [arrowElement, setArrowElement] = useState<HTMLElement | null>(null);

const [visible, setVisible] = useState(manualVisible);
const isHoveringOverTooltip = useRef(false);
const hideTimeoutRef = useRef<number>();

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(
Expand All @@ -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(() => {
Expand Down

1 comment on commit 09be4f1

@vercel
Copy link

@vercel vercel bot commented on 09be4f1 Jul 7, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

uikit – ./packages/uikit

uikit.pancake.run
uikit-git-develop.pancake.run

Please sign in to comment.