Skip to content

Commit

Permalink
update tooltip component
Browse files Browse the repository at this point in the history
  • Loading branch information
ap-justin committed Sep 3, 2024
1 parent 68d2d57 commit 232f55f
Show file tree
Hide file tree
Showing 10 changed files with 468 additions and 133 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"@keplr-wallet/wc-qrcode-modal": "0.12.83",
"@paypal/react-paypal-js": "8.3.0",
"@radix-ui/react-slider": "1.1.2",
"@radix-ui/react-tooltip": "1.1.2",
"@reduxjs/toolkit": "2.2.3",
"@sentry/react": "8.22.0",
"@stripe/react-stripe-js": "2.7.1",
Expand Down
16 changes: 12 additions & 4 deletions src/components/BookmarkBtn.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Icon from "components/Icon";
import Tooltip from "components/Tooltip";
import { Arrow, Content, Tooltip } from "components/Tooltip";
import { useErrorContext } from "contexts/ErrorContext";
import { type PropsWithChildren, useRef } from "react";
import {
Expand Down Expand Up @@ -50,8 +50,16 @@ export default function BookmarkBtn({
}

return (
<>
{!isBookmarked && <Tooltip anchorRef={ref} content="Add to favorites" />}
<Tooltip
content={
!isBookmarked ? (
<Content className="px-4 py-2 bg-navy-d4 text-white text-sm rounded-lg shadow-lg">
Add to favorites
<Arrow />
</Content>
) : null
}
>
<button
ref={ref}
type="button"
Expand All @@ -67,6 +75,6 @@ export default function BookmarkBtn({
/>
{children}
</button>
</>
</Tooltip>
);
}
10 changes: 7 additions & 3 deletions src/components/Icon/Icon.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import type { LucideProps } from "lucide-react";
import { forwardRef } from "react";
import { type IconType, icons } from "./icons";

interface IconProps extends LucideProps {
type: IconType;
}

export default function Icon(props: IconProps) {
type El = SVGSVGElement;
const Icon = forwardRef<El, IconProps>((props, ref) => {
const { type, ...rest } = props;
const Icon = icons[type];
return <Icon {...rest} />;
}
return <Icon {...rest} ref={ref} />;
});

export default Icon;
86 changes: 23 additions & 63 deletions src/components/Tooltip.tsx
Original file line number Diff line number Diff line change
@@ -1,65 +1,25 @@
import { Transition } from "@headlessui/react";
import { useEffect, useState } from "react";
import type { RefObject } from "react";
import { createPortal } from "react-dom";

type Props<T extends HTMLElement> = {
content: string;
anchorRef?: RefObject<T>;
};

export default function Tooltip<T extends HTMLElement>({
content,
anchorRef,
}: Props<T>) {
const [top, setTop] = useState(0);
const [left, setLeft] = useState(0);
const [isHovered, setHovered] = useState(false);

useEffect(() => {
const element = anchorRef?.current;
if (!element) return;

function handleMouseEnter(this: HTMLElement) {
const rect = this.getBoundingClientRect();
setTop(rect.top);
setLeft(rect.left);
setHovered(true);
}

function handleMouseLeave(this: HTMLElement) {
setHovered(false);
}

element.addEventListener("mouseenter", handleMouseEnter);
element.addEventListener("mouseleave", handleMouseLeave);

return () => {
element.removeEventListener("mouseenter", handleMouseEnter);
element.removeEventListener("mouseleave", handleMouseLeave);
};
}, [anchorRef]);

return createPortal(
<Transition
as="div"
show={isHovered}
enter="transition-opacity duration-500"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="transition-opacity duration-150"
leaveFrom="opacity-100"
leaveTo="opacity-0"
className="fixed -translate-x-1/2 -translate-y-full -mt-2 ml-2.5 z-50 flex items-center justify-center px-3 py-2 rounded bg-gray-d2 dark:bg-white font-normal text-sm w-max max-w-[200px] text-white dark:text-navy-d4 after:content-[''] after:absolute after:top-full after:left-1/2 after:-ml-1 after:-translate-y-1/2 after:w-2 after:h-2 after:bg-gray-d2 after:dark:bg-white after:rotate-45"
// had to move top+left position calculations to the props.style as the expected behavior wasn't there when setting these values
// in className using tailwind
style={{
top: `${Math.floor(top)}px`,
left: `${Math.floor(left)}px`,
}}
>
{content}
</Transition>,
document.body
import type { ReactNode } from "react";

import {
Portal,
Root,
TooltipProvider,
Trigger,
} from "@radix-ui/react-tooltip";
export { Content, TooltipArrow as Arrow } from "@radix-ui/react-tooltip";

interface Props {
children: ReactNode;
/** must be wrapped by Content */
content: ReactNode;
}
export function Tooltip(props: Props) {
return (
<TooltipProvider>
<Root delayDuration={50}>
<Trigger asChild>{props.children}</Trigger>
<Portal>{props.content}</Portal>
</Root>
</TooltipProvider>
);
}
35 changes: 17 additions & 18 deletions src/components/VerifiedIcon.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@
import { useRef } from "react";
import Icon from "./Icon";
import Tooltip from "./Tooltip";
type Props = {
classes?: string;
size: number;
};
import { Arrow, Content, Tooltip } from "./Tooltip";
type Props = { size: number };

export default function VerifiedIcon({ classes = "", size }: Props) {
const ref = useRef<HTMLDivElement>(null);
export default function VerifiedIcon({ size }: Props) {
return (
<>
<Tooltip anchorRef={ref} content="Verified" />
<div ref={ref} className={classes}>
<Icon
type="Verified"
size={size}
className="text-white inline fill-blue"
/>
</div>
</>
<Tooltip
content={
<Content className="bg-navy-d4 text-white px-4 py-2 rounded text-sm">
Verified
<Arrow className="fill-navy-d4" />
</Content>
}
>
<Icon
type="Verified"
size={size}
className="text-white inline fill-blue"
/>
</Tooltip>
);
}
28 changes: 23 additions & 5 deletions src/pages/Admin/Charity/Dashboard/Dashboard.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { skipToken } from "@reduxjs/toolkit/query";
import ContentLoader from "components/ContentLoader";
import Icon from "components/Icon";
import QueryLoader from "components/QueryLoader";
import { Arrow, Content } from "components/Tooltip";
import { humanize } from "helpers";

import { skipToken } from "@reduxjs/toolkit/query";
import Icon from "components/Icon";
import { useEndowBalanceQuery } from "services/apes";
import type { EndowmentBalances } from "types/aws";
import { useAdminContext } from "../../Context";
Expand Down Expand Up @@ -44,13 +44,31 @@ function Loaded({
<div className="grid gap-4 @lg:grid-cols-2">
<Figure
title="Savings"
tooltip="Funds held in Fidelity Government Money Market (SPAXX)"
tooltip={
<Content className="bg-navy-d4 text-white text-sm max-w-xs p-4 rounded-lg">
Funds held in Fidelity Government Money Market (SPAXX)
<Arrow />
</Content>
}
icon={<Icon size={21} type="PiggyBank" strokeWidth={1.5} />}
amount={`$ ${humanize(props.donationsBal - props.payoutsMade, 2)}`}
/>
<Figure
title="Investments"
tooltip="Funds invested in a diversified portfolio comprising (50%) domestic and international equities, (30%) fixed income, (15%) crypto and (10%) cash."
tooltip={
<Content className="bg-navy-d4 text-white text-sm max-w-xs p-4 rounded-lg shadow-lg">
<span className="block mb-2">
Funds invested in a diversified portfolio comprising
</span>
<div>
<p>50% - domestic and international equities</p>
<p>30% - fixed income</p>
<p>15% - crypto</p>
<p>10% - cash</p>
</div>
<Arrow />
</Content>
}
icon={<Icon type="Stocks" size={16} />}
amount={`$ ${humanize(props.sustainabilityFundBal, 2)}`}
/>
Expand Down
19 changes: 9 additions & 10 deletions src/pages/Admin/Charity/Dashboard/Figure.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,28 @@
import Icon from "components/Icon";
import Tooltip from "components/Tooltip";
import { type ReactNode, useRef } from "react";
import { Tooltip } from "components/Tooltip";
import type { ReactNode } from "react";

type Props = {
title: string;
icon: ReactNode;
/** e.g. $100,000 */
amount: string;
tooltip?: string;
/** must be wrapped by tooltip content */
tooltip?: ReactNode;
};

export default function Figure(props: Props) {
const ref = useRef<HTMLDivElement | null>(null);
return (
<div className="@container rounded border border-gray-l4 p-4">
<div className="flex items-center mb-4">
<h4 className="text-sm">{props.title}</h4>

{props.tooltip && (
<>
<Tooltip anchorRef={ref} content={props.tooltip} />
<div ref={ref}>
<Icon type="Question" size={14} className="text-gray ml-1" />
</div>
</>
<Tooltip content={props.tooltip}>
<Icon type="Question" size={14} className="text-gray ml-1" />
</Tooltip>
)}

<span className="ml-auto">{props.icon}</span>
</div>
<p className="text-2xl font-medium">{props.amount}</p>
Expand Down
23 changes: 13 additions & 10 deletions src/pages/Admin/Charity/Dashboard/Schedule/Schedule.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import leaf from "assets/icons/leaf.png";
import sendMoney from "assets/icons/send-money.png";
import Icon from "components/Icon";
import Tooltip from "components/Tooltip";
import { Arrow, Content, Tooltip } from "components/Tooltip";
import { useModalContext } from "contexts/ModalContext";
import { humanize } from "helpers";
import { useAdminContext } from "pages/Admin/Context";
import { useRef } from "react";
import { useEndowmentQuery } from "services/aws/aws";
import { Edit } from "./Edit";

Expand All @@ -26,8 +25,6 @@ export function Schedule(props: Props) {
const val = (pct?: number) =>
pct || pct === 0 ? `$ ${humanize((pct / 100) * props.amount)}` : "---";

const grantRef = useRef<HTMLDivElement>(null);

return (
<div className="p-4 grid rounded border border-gray-l4 mt-4">
<div className="flex flex-row items-center justify-between space-y-0">
Expand Down Expand Up @@ -58,16 +55,22 @@ export function Schedule(props: Props) {
<div className="grid grid-cols-[auto_auto_auto_1fr] gap-y-2 gap-x-2">
<div className="grid grid-cols-subgrid col-span-full items-center">
<Icon type="ArrowRight" className="h-4 w-4 mr-2" />

<div className="flex items-center">
<span>Grants</span>
<Tooltip
anchorRef={grantRef}
content="Donations received through Better Giving that will distributed to your bank account."
/>
<div ref={grantRef}>
<Icon type="Question" size={14} className="text-navy-l1 ml-0.5" />
</div>
content={
<Content className="max-w-xs bg-navy-d4 p-4 text-white text-sm shadow-lg rounded-lg">
Donations received through Better Giving that will distributed
to your bank account.
<Arrow />
</Content>
}
>
<Icon type="Question" size={14} className="text-navy-l1 ml-1" />
</Tooltip>
</div>

<span className="ml-2 text-navy-l1 text-sm">
{endow?.allocation?.cash ?? 0} %
</span>
Expand Down
36 changes: 16 additions & 20 deletions src/pages/Registration/Steps/Reference.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Icon, { DrawerIcon } from "components/Icon";
import { type ReactElement, cloneElement, useState } from "react";
import { Arrow, Content, Tooltip } from "components/Tooltip";
import { useState } from "react";

type Props = {
id: string;
Expand All @@ -17,11 +18,20 @@ export default function Reference({ id, classes = "" }: Props) {
<span className="font-semibold mr-2">Your registration number:</span>
<span className="block mt-1 md:inline md:mt-0">{id}</span>

<WithTooltip text={tooltip} classes="before:left-0 before:top-0">
<span className="hidden md:inline-block ml-[1.333rem] top-0.5">
<Icon type="Question" size={13} />
</span>
</WithTooltip>
<Tooltip
content={
<Content className="p-3 text-xs bg-navy-d4 text-white max-w-xs rounded">
{tooltip}
<Arrow />
</Content>
}
>
<Icon
type="Question"
size={13}
className="hidden md:inline-block ml-[1.333rem]"
/>
</Tooltip>
<button
onClick={() => {
setIsTooltipOpen((p) => !p);
Expand All @@ -42,17 +52,3 @@ export default function Reference({ id, classes = "" }: Props) {

const tooltip =
"Enter this number on the registration page to continue from where you finished.";

function WithTooltip(props: {
classes?: string;
children: ReactElement; //note: ::before doesn't seem to work on SVG elements;
text: string;
}) {
return cloneElement(props.children, {
"data-before-content": props.text,
className:
props.children.props.className +
` ${props.classes ?? "" /** control position */} relative before:w-48 before:z-10 before:content-[attr(data-before-content)] hover:before:block before:hidden before:absolute
before:text-xs before:bg-gray-l6 before:dark:bg-blue-d6 before:text-navy-l1 before:dark:text-navy-l2 before:border before:border-gray-l4 before:p-2 before:rounded`,
});
}
Loading

0 comments on commit 232f55f

Please sign in to comment.