Compare commits

...

2 Commits

Author SHA1 Message Date
Zachery Ng
54540cf8e9 feat: tooltip component 2024-02-22 17:28:38 +08:00
Zachery Ng
2715d0216c fix: button forwardRef 2024-02-22 17:28:18 +08:00
14 changed files with 344 additions and 10 deletions

View File

@ -8,6 +8,7 @@
"@radix-ui/react-avatar": "^1.0.4",
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-tooltip": "^1.0.7",
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",

View File

@ -98,19 +98,29 @@ const Button = forwardRef<
href,
...baseLinkProps,
};
return <a {...externalLinkProps}>{_children}</a>;
return (
// @ts-expect-error - ref
<a ref={ref} {...externalLinkProps}>
{_children}
</a>
);
}
// Internal link
return (
<Link {...baseLinkProps} to={href}>
// @ts-expect-error - ref
<Link ref={ref} {...baseLinkProps} to={href}>
{_children}
</Link>
);
} else {
const { ...buttonProps } = _props;
return (
// @ts-expect-error - as prop is not a valid prop for button elements
return <button {...buttonProps}>{_children}</button>;
<button ref={ref} {...buttonProps}>
{_children}
</button>
);
}
},
[],
@ -161,8 +171,6 @@ const Button = forwardRef<
return (
<Component
{...props}
// @ts-expect-error - ref is not a valid prop for button elements
ref={ref}
className={buttonTheme({ ...styleProps, class: className })}
>
{cloneIcon(leftIcon, { ...iconSize })}

View File

@ -0,0 +1,14 @@
import { tv } from 'tailwind-variants';
export const tooltipTheme = tv({
slots: {
content: [
'z-tooltip',
'rounded-md',
'bg-surface-high-contrast',
'p-2',
'text-elements-on-high-contrast',
],
arrow: ['fill-surface-high-contrast'],
},
});

View File

@ -0,0 +1,47 @@
import React from 'react';
import type {
TooltipContentProps,
TooltipTriggerProps,
} from '@radix-ui/react-tooltip';
import { ReactNode, useState } from 'react';
import { TooltipBase, type TooltipBaseProps } from './TooltipBase';
export interface TooltipProps extends TooltipBaseProps {
triggerProps?: TooltipTriggerProps;
contentProps?: TooltipContentProps;
content?: ReactNode;
}
// https://github.com/radix-ui/primitives/issues/955#issuecomment-1798201143
// Wrap on top of Tooltip base to make tooltip open on mobile via click
export const Tooltip = ({
children,
triggerProps,
contentProps,
content,
...props
}: TooltipProps) => {
const [isTooltipVisible, setIsTooltipVisible] = useState(false);
return (
<TooltipBase
open={isTooltipVisible}
onOpenChange={setIsTooltipVisible}
{...props}
>
<TooltipBase.Trigger
asChild
onBlur={() => setIsTooltipVisible(false)}
onClick={() => setIsTooltipVisible((prevOpen) => !prevOpen)}
onFocus={() => setTimeout(() => setIsTooltipVisible(true), 0)}
{...triggerProps}
>
{triggerProps?.children ?? children}
</TooltipBase.Trigger>
<TooltipBase.Content {...contentProps}>
{content ?? contentProps?.children ?? 'Coming soon'}
</TooltipBase.Content>
</TooltipBase>
);
};

View File

@ -0,0 +1,38 @@
import React from 'react';
import {
Provider,
TooltipProps as RadixTooltipProps,
Root,
type TooltipProviderProps,
} from '@radix-ui/react-tooltip';
import { useState, type PropsWithChildren } from 'react';
import { TooltipContent } from './TooltipContent';
import { TooltipTrigger } from './TooltipTrigger';
export interface TooltipBaseProps extends RadixTooltipProps {
providerProps?: TooltipProviderProps;
}
export const TooltipBase = ({
children,
providerProps,
...props
}: PropsWithChildren<TooltipBaseProps>) => {
const [isTooltipVisible, setIsTooltipVisible] = useState(false);
return (
<Provider {...providerProps}>
<Root
open={isTooltipVisible}
onOpenChange={setIsTooltipVisible}
{...props}
>
{children}
</Root>
</Provider>
);
};
TooltipBase.Trigger = TooltipTrigger;
TooltipBase.Content = TooltipContent;

View File

@ -0,0 +1,46 @@
import React from 'react';
import {
Arrow,
Content,
Portal,
type TooltipArrowProps,
type TooltipContentProps,
} from '@radix-ui/react-tooltip';
import { tooltipTheme } from '../Tooltip.theme';
export interface ContentProps extends TooltipContentProps {
hasArrow?: boolean;
arrowProps?: TooltipArrowProps;
}
export const TooltipContent = ({
children,
arrowProps,
className,
hasArrow = true,
...props
}: ContentProps) => {
const { content, arrow } = tooltipTheme();
return (
<Portal>
<Content
className={content({
className,
})}
sideOffset={5}
{...props}
>
{hasArrow && (
<Arrow
className={arrow({
className: arrowProps?.className,
})}
{...arrowProps}
/>
)}
{children}
</Content>
</Portal>
);
};

View File

@ -0,0 +1 @@
export * from './TooltipContent';

View File

@ -0,0 +1,12 @@
import React from 'react';
import { Trigger, type TooltipTriggerProps } from '@radix-ui/react-tooltip';
export type TriggerProps = TooltipTriggerProps;
export const TooltipTrigger = ({ children, ...props }: TriggerProps) => {
return (
<Trigger asChild {...props}>
{children}
</Trigger>
);
};

View File

@ -0,0 +1 @@
export * from './TooltipTrigger';

View File

@ -0,0 +1,2 @@
export * from './Tooltip';
export * from './TooltipBase';

View File

@ -23,6 +23,7 @@ import {
renderInlineNotifications,
} from './renders/inlineNotifications';
import { renderInputs } from './renders/input';
import { renderTooltips } from './renders/tooltip';
const Page = () => {
const [singleDate, setSingleDate] = useState<Value>();
@ -41,13 +42,21 @@ const Page = () => {
<div className="w-full h border border-gray-200 px-20 my-10" />
{/* Button */}
<div className="flex flex-col gap-10 items-center justify-between">
<h1 className="text-2xl font-bold">Tooltip</h1>
<div className="flex w-full flex-wrap max-w-[680px] justify-center gap-10">
{renderTooltips()}
</div>
<div className="w-full h border border-gray-200 px-20 my-10" />
{/* Input */}
<h1 className="text-2xl font-bold">Input</h1>
<div className="flex w-full flex-col gap-10">{renderInputs()}</div>
<div className="w-full h border border-gray-200 px-20 my-10" />
{/* Button */}
<h1 className="text-2xl font-bold">Button</h1>
<div className="flex flex-col gap-10">
{renderButtons()}

View File

@ -0,0 +1,30 @@
import React from 'react';
import { Button } from 'components/shared/Button';
import { Tooltip } from 'components/shared/Tooltip';
import { ContentProps } from 'components/shared/Tooltip/TooltipContent';
const alignments: ContentProps['align'][] = ['start', 'center', 'end'];
const sides: ContentProps['side'][] = ['left', 'top', 'bottom', 'right'];
export const renderTooltips = () => {
const tooltips = sides.map((side) => {
return alignments.map((align) => {
return (
<Tooltip
key={`${side}-${align}`}
content="tooltip content"
contentProps={{ align, side }}
>
<Button
variant="ghost"
key={`${side}-${align}`}
className="h-16 self-center"
>
Tooltip ({side} - {align})
</Button>
</Tooltip>
);
});
});
return tooltips;
};

View File

@ -8,10 +8,13 @@ export default withMT({
'../../node_modules/@material-tailwind/react/theme/components/**/*.{js,ts,jsx,tsx}',
],
theme: {
extend: {
zIndex: {
tooltip: '52',
},
fontFamily: {
sans: ['Inter', 'sans-serif'],
},
extend: {
fontSize: {
'2xs': '0.625rem',
'3xs': '0.5rem',

122
yarn.lock
View File

@ -2032,6 +2032,13 @@
link-module-alias "^1.2.0"
shx "^0.3.4"
"@floating-ui/core@^1.0.0":
version "1.6.0"
resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.6.0.tgz#fa41b87812a16bf123122bf945946bae3fdf7fc1"
integrity sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==
dependencies:
"@floating-ui/utils" "^0.2.1"
"@floating-ui/core@^1.4.2":
version "1.5.2"
resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.5.2.tgz#53a0f7a98c550e63134d504f26804f6b83dbc071"
@ -2047,6 +2054,14 @@
"@floating-ui/core" "^1.4.2"
"@floating-ui/utils" "^0.1.3"
"@floating-ui/dom@^1.6.1":
version "1.6.3"
resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.3.tgz#954e46c1dd3ad48e49db9ada7218b0985cee75ef"
integrity sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw==
dependencies:
"@floating-ui/core" "^1.0.0"
"@floating-ui/utils" "^0.2.0"
"@floating-ui/react-dom@^1.2.2":
version "1.3.0"
resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-1.3.0.tgz#4d35d416eb19811c2b0e9271100a6aa18c1579b3"
@ -2054,6 +2069,13 @@
dependencies:
"@floating-ui/dom" "^1.2.1"
"@floating-ui/react-dom@^2.0.0":
version "2.0.8"
resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.0.8.tgz#afc24f9756d1b433e1fe0d047c24bd4d9cefaa5d"
integrity sha512-HOdqOt3R3OGeTKidaLvJKcgg75S6tibQ3Tif4eyd91QnIJWr0NLvoXFpJA/j8HqkFSL68GDca9AuyWEHlhyClw==
dependencies:
"@floating-ui/dom" "^1.6.1"
"@floating-ui/react@0.19.0":
version "0.19.0"
resolved "https://registry.yarnpkg.com/@floating-ui/react/-/react-0.19.0.tgz#d8e19a3fcfaa0684d5ec3f335232b4e0ac0c87e1"
@ -2068,6 +2090,11 @@
resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.1.6.tgz#22958c042e10b67463997bd6ea7115fe28cbcaf9"
integrity sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A==
"@floating-ui/utils@^0.2.0", "@floating-ui/utils@^0.2.1":
version "0.2.1"
resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.1.tgz#16308cea045f0fc777b6ff20a9f25474dd8293d2"
integrity sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==
"@fontsource/inter@^5.0.16":
version "5.0.16"
resolved "https://registry.yarnpkg.com/@fontsource/inter/-/inter-5.0.16.tgz#b858508cdb56dcbbf3166903122851e2fbd16b50"
@ -3284,6 +3311,14 @@
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-arrow@1.0.3":
version "1.0.3"
resolved "https://registry.yarnpkg.com/@radix-ui/react-arrow/-/react-arrow-1.0.3.tgz#c24f7968996ed934d57fe6cde5d6ec7266e1d25d"
integrity sha512-wSP+pHsB/jQRaL6voubsQ/ZlrGBHHrOjmBnr19hxYgtS0WvAFwZhK2WP/YY5yF9uKECCEEDGxuLxq1NBK51wFA==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-primitive" "1.0.3"
"@radix-ui/react-avatar@^1.0.4":
version "1.0.4"
resolved "https://registry.yarnpkg.com/@radix-ui/react-avatar/-/react-avatar-1.0.4.tgz#de9a5349d9e3de7bbe990334c4d2011acbbb9623"
@ -3342,6 +3377,18 @@
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-dismissable-layer@1.0.5":
version "1.0.5"
resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.5.tgz#3f98425b82b9068dfbab5db5fff3df6ebf48b9d4"
integrity sha512-aJeDjQhywg9LBu2t/At58hCvr7pEm0o2Ke1x33B+MhjNmmZ17sy4KImo0KPLgsnc/zN7GPdce8Cnn0SWvwZO7g==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/primitive" "1.0.1"
"@radix-ui/react-compose-refs" "1.0.1"
"@radix-ui/react-primitive" "1.0.3"
"@radix-ui/react-use-callback-ref" "1.0.1"
"@radix-ui/react-use-escape-keydown" "1.0.3"
"@radix-ui/react-id@1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@radix-ui/react-id/-/react-id-1.0.1.tgz#73cdc181f650e4df24f0b6a5b7aa426b912c88c0"
@ -3350,6 +3397,31 @@
"@babel/runtime" "^7.13.10"
"@radix-ui/react-use-layout-effect" "1.0.1"
"@radix-ui/react-popper@1.1.3":
version "1.1.3"
resolved "https://registry.yarnpkg.com/@radix-ui/react-popper/-/react-popper-1.1.3.tgz#24c03f527e7ac348fabf18c89795d85d21b00b42"
integrity sha512-cKpopj/5RHZWjrbF2846jBNacjQVwkP068DfmgrNJXpvVWrOvlAmE9xSiy5OqeE+Gi8D9fP+oDhUnPqNMY8/5w==
dependencies:
"@babel/runtime" "^7.13.10"
"@floating-ui/react-dom" "^2.0.0"
"@radix-ui/react-arrow" "1.0.3"
"@radix-ui/react-compose-refs" "1.0.1"
"@radix-ui/react-context" "1.0.1"
"@radix-ui/react-primitive" "1.0.3"
"@radix-ui/react-use-callback-ref" "1.0.1"
"@radix-ui/react-use-layout-effect" "1.0.1"
"@radix-ui/react-use-rect" "1.0.1"
"@radix-ui/react-use-size" "1.0.1"
"@radix-ui/rect" "1.0.1"
"@radix-ui/react-portal@1.0.4":
version "1.0.4"
resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-1.0.4.tgz#df4bfd353db3b1e84e639e9c63a5f2565fb00e15"
integrity sha512-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-primitive" "1.0.3"
"@radix-ui/react-presence@1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@radix-ui/react-presence/-/react-presence-1.0.1.tgz#491990ba913b8e2a5db1b06b203cb24b5cdef9ba"
@ -3406,6 +3478,25 @@
"@radix-ui/react-roving-focus" "1.0.4"
"@radix-ui/react-use-controllable-state" "1.0.1"
"@radix-ui/react-tooltip@^1.0.7":
version "1.0.7"
resolved "https://registry.yarnpkg.com/@radix-ui/react-tooltip/-/react-tooltip-1.0.7.tgz#8f55070f852e7e7450cc1d9210b793d2e5a7686e"
integrity sha512-lPh5iKNFVQ/jav/j6ZrWq3blfDJ0OH9R6FlNUHPMqdLuQ9vwDgFsRxvl8b7Asuy5c8xmoojHUxKHQSOAvMHxyw==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/primitive" "1.0.1"
"@radix-ui/react-compose-refs" "1.0.1"
"@radix-ui/react-context" "1.0.1"
"@radix-ui/react-dismissable-layer" "1.0.5"
"@radix-ui/react-id" "1.0.1"
"@radix-ui/react-popper" "1.1.3"
"@radix-ui/react-portal" "1.0.4"
"@radix-ui/react-presence" "1.0.1"
"@radix-ui/react-primitive" "1.0.3"
"@radix-ui/react-slot" "1.0.2"
"@radix-ui/react-use-controllable-state" "1.0.1"
"@radix-ui/react-visually-hidden" "1.0.3"
"@radix-ui/react-use-callback-ref@1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.1.tgz#f4bb1f27f2023c984e6534317ebc411fc181107a"
@ -3421,6 +3512,14 @@
"@babel/runtime" "^7.13.10"
"@radix-ui/react-use-callback-ref" "1.0.1"
"@radix-ui/react-use-escape-keydown@1.0.3":
version "1.0.3"
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.3.tgz#217b840c250541609c66f67ed7bab2b733620755"
integrity sha512-vyL82j40hcFicA+M4Ex7hVkB9vHgSse1ZWomAqV2Je3RleKGO5iM8KMOEtfoSB0PnIelMd2lATjTGMYqN5ylTg==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-use-callback-ref" "1.0.1"
"@radix-ui/react-use-layout-effect@1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.1.tgz#be8c7bc809b0c8934acf6657b577daf948a75399"
@ -3435,6 +3534,14 @@
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-use-rect@1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-rect/-/react-use-rect-1.0.1.tgz#fde50b3bb9fd08f4a1cd204572e5943c244fcec2"
integrity sha512-Cq5DLuSiuYVKNU8orzJMbl15TXilTnJKUCltMVQg53BQOF1/C5toAaGrowkgksdBQ9H+SRL23g0HDmg9tvmxXw==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/rect" "1.0.1"
"@radix-ui/react-use-size@1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-size/-/react-use-size-1.0.1.tgz#1c5f5fea940a7d7ade77694bb98116fb49f870b2"
@ -3443,6 +3550,21 @@
"@babel/runtime" "^7.13.10"
"@radix-ui/react-use-layout-effect" "1.0.1"
"@radix-ui/react-visually-hidden@1.0.3":
version "1.0.3"
resolved "https://registry.yarnpkg.com/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.0.3.tgz#51aed9dd0fe5abcad7dee2a234ad36106a6984ac"
integrity sha512-D4w41yN5YRKtu464TLnByKzMDG/JlMPHtfZgQAu9v6mNakUqGUI9vUrfQKz8NK41VMm/xbZbh76NUTVtIYqOMA==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-primitive" "1.0.3"
"@radix-ui/rect@1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@radix-ui/rect/-/rect-1.0.1.tgz#bf8e7d947671996da2e30f4904ece343bc4a883f"
integrity sha512-fyrgCaedtvMg9NK3en0pnOYJdtfwxUcNolezkNPUsoX57X8oQk+NkqcvzHXD2uKNij6GXmWU9NDru2IWjrO4BQ==
dependencies:
"@babel/runtime" "^7.13.10"
"@remix-run/router@1.13.1":
version "1.13.1"
resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.13.1.tgz#07e2a8006f23a3bc898b3f317e0a58cc8076b86e"