Skip to content

Commit

Permalink
fix tooltip on overflow hidden container
Browse files Browse the repository at this point in the history
  • Loading branch information
nikitayutanov committed Dec 20, 2024
1 parent 59bd3d6 commit 4be9c1d
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 33 deletions.
13 changes: 8 additions & 5 deletions frontend/src/components/formatted-balance/fortmatted-balance.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { formatBalance } from '@polkadot/util';
import { ComponentProps } from 'react';
import { formatUnits } from 'viem';

import { cx } from '@/utils';
Expand All @@ -11,18 +12,20 @@ type Props = {
value: bigint;
decimals: number;
symbol: string;
tooltipPosition?: ComponentProps<typeof Tooltip>['position'];
className?: string;
};

function FormattedBalance({ value, decimals, symbol, className }: Props) {
function FormattedBalance({ value, decimals, symbol, tooltipPosition, className }: Props) {
const formattedValue = formatUnits(value, decimals);
const compactBalance = formatBalance(value, { decimals, withUnit: symbol, withZero: false });

return (
<span className={cx(styles.balance, className)}>
{compactBalance}
<Tooltip text={`${formattedValue} ${symbol}`} />
</span>
<Tooltip value={`${formattedValue} ${symbol}`} position={tooltipPosition}>
<span className={cx(styles.balance, className)}>
{compactBalance === '0' ? `${compactBalance} ${symbol}` : compactBalance}
</span>
</Tooltip>
);
}

Expand Down
32 changes: 12 additions & 20 deletions frontend/src/components/tooltip/tooltip.module.scss
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
@keyframes fadeIn {
from {
opacity: 0;
}

to {
opacity: 1;
}
}

.container,
.skeleton {
flex-shrink: 0;
}

.container {
position: relative;

&:hover {
.tooltip {
opacity: 1;
}

.body {
opacity: 0.75;
}
Expand All @@ -25,6 +29,7 @@

.tooltip {
position: absolute;
z-index: 11;

padding: 10px 16px;

Expand All @@ -42,18 +47,5 @@
border-radius: 4px;
box-shadow: 0px 8px 16px 0px rgba(#000, 0.24);

opacity: 0;
pointer-events: none;
transition: 0.25s opacity;
}

.top {
bottom: calc(100% + 8px);
left: 50%;
transform: translateX(-50%);
}

.bottom-end {
top: calc(100% + 8px);
right: 0;
animation: fadeIn 0.25s;
}
95 changes: 88 additions & 7 deletions frontend/src/components/tooltip/tooltip.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ReactNode } from 'react';
import { CSSProperties, ReactNode, useEffect, useRef, useState } from 'react';
import { createPortal } from 'react-dom';

import { SVGComponent } from '@/types';
import { cx } from '@/utils';

import { Skeleton } from '../layout';

Expand All @@ -10,19 +10,100 @@ import styles from './tooltip.module.scss';

type Props = {
value?: ReactNode;
position?: 'top' | 'bottom-end';
position?: 'top' | 'right' | 'bottom-end';
SVG?: SVGComponent;
children?: ReactNode;
};

function TooltipComponent({ value, style }: { value: ReactNode; style: CSSProperties }) {
const [root, setRoot] = useState<HTMLElement>();

useEffect(() => {
const ID = 'tooltip-root';
const existingRoot = document.getElementById(ID);

if (existingRoot) return setRoot(existingRoot);

const newRoot = document.createElement('div');
newRoot.id = ID;
document.body.appendChild(newRoot);

setRoot(newRoot);

return () => {
if (!newRoot) return;

document.body.removeChild(newRoot);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

if (!root) return null;

return createPortal(
<div className={styles.tooltip} style={style}>
{typeof value === 'string' ? <p className={styles.heading}>{value}</p> : value}
</div>,
root,
);
}

function Tooltip({ value, position = 'top', SVG = QuestionSVG, children }: Props) {
const [style, setStyle] = useState<CSSProperties>();
const containerRef = useRef<HTMLDivElement>(null);

const handleMouseEnter = () => {
const container = containerRef.current;

if (!container) return;

const containerRect = container.getBoundingClientRect();

const GAP = 8;
let top = 0;
let left = 0;
let transform = '';

switch (position) {
case 'top': {
top = containerRect.top + window.scrollY - GAP;
left = containerRect.left + window.scrollX + containerRect.width / 2;
transform = 'translate(-50%, -100%)';

break;
}

case 'right': {
top = containerRect.top + window.scrollY + containerRect.height / 2;
left = containerRect.right + window.scrollX + GAP;
transform = 'translateY(-50%)';

break;
}
case 'bottom-end': {
top = containerRect.bottom + window.scrollY + GAP;
left = containerRect.right - window.scrollX;
transform = 'translate(-100%, 0)';

break;
}

default:
break;
}

setStyle({ top, left, transform });
};

return (
<div className={styles.container}>
<div
className={styles.container}
onMouseEnter={handleMouseEnter}
onMouseLeave={() => setStyle(undefined)}
ref={containerRef}>
<div className={styles.body}>{children || <SVG />}</div>

<div className={cx(styles.tooltip, styles[position])}>
{typeof value === 'string' ? <p className={styles.heading}>{value}</p> : value}
</div>
{style && <TooltipComponent value={value} style={style} />}
</div>
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
.balance {
display: flex;
align-items: flex-start;
flex-direction: column;

white-space: nowrap;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ function BalanceCard({ locked, value, decimals, SVG, symbol, children }: Props)
<div className={cx(styles.card, locked && styles.locked)}>
<span className={styles.balance}>
<SVG />
<FormattedBalance value={value} decimals={decimals} symbol={symbol} />
<FormattedBalance value={value} decimals={decimals} symbol={symbol} tooltipPosition="right" />
</span>

{children}
Expand Down

0 comments on commit 4be9c1d

Please sign in to comment.