(frontend) new Tooltip component

buttons can now easily have tooltip via a new `tooltip` attribute that
generates a Tooltip linked to the button
This commit is contained in:
Emmanuel Pelletier
2024-07-24 17:19:38 +02:00
parent 41ad15e20b
commit c0d490f549
6 changed files with 134 additions and 22 deletions

View File

@@ -47,7 +47,7 @@ const config: Config = {
'2xl': '96em', // 1536px
},
keyframes: {
popoverSlide: {
slide: {
from: {
transform: 'var(--origin)',
opacity: 0,
@@ -57,7 +57,7 @@ const config: Config = {
opacity: 1,
},
},
modalFade: { from: { opacity: 0 }, to: { opacity: 1 } },
fade: { from: { opacity: 0 }, to: { opacity: 1 } },
},
tokens: defineTokens({
/* we take a few things from the panda preset but for now we clear out some stuff.

View File

@@ -2,12 +2,16 @@ import { useTranslation } from 'react-i18next'
import { RiSettings3Line } from '@remixicon/react'
import { Dialog, Button } from '@/primitives'
import { SettingsDialog } from './SettingsDialog'
export const SettingsButton = () => {
const { t } = useTranslation('settings')
return (
<Dialog>
<Button square invisible aria-label={t('settingsButtonLabel')}>
<Button
square
invisible
aria-label={t('settingsButtonLabel')}
tooltip={t('settingsButtonLabel')}
>
<RiSettings3Line />
</Button>
<SettingsDialog />

View File

@@ -1,10 +1,13 @@
import { type ReactNode } from 'react'
import {
Button as RACButton,
type ButtonProps as RACButtonsProps,
TooltipTrigger,
Link,
LinkProps,
} from 'react-aria-components'
import { cva, type RecipeVariantProps } from '@/styled-system/css'
import { Tooltip, TooltipArrow } from './Tooltip'
const button = cva({
base: {
@@ -88,21 +91,52 @@ const button = cva({
},
})
export type ButtonProps = RecipeVariantProps<typeof button> & RACButtonsProps
type Tooltip = {
tooltip?: string
}
export type ButtonProps = RecipeVariantProps<typeof button> &
RACButtonsProps &
Tooltip
type LinkButtonProps = RecipeVariantProps<typeof button> & LinkProps
type LinkButtonProps = RecipeVariantProps<typeof button> & LinkProps & Tooltip
type ButtonOrLinkProps = ButtonProps | LinkButtonProps
export const Button = (props: ButtonOrLinkProps) => {
export const Button = ({ tooltip, ...props }: ButtonOrLinkProps) => {
const [variantProps, componentProps] = button.splitVariantProps(props)
if ((props as LinkButtonProps).href !== undefined) {
return <Link className={button(variantProps)} {...componentProps} />
return (
<TooltipWrapper tooltip={tooltip}>
<Link className={button(variantProps)} {...componentProps} />
</TooltipWrapper>
)
}
return (
<RACButton
className={button(variantProps)}
{...(componentProps as RACButtonsProps)}
/>
<TooltipWrapper tooltip={tooltip}>
<RACButton
className={button(variantProps)}
{...(componentProps as RACButtonsProps)}
/>
</TooltipWrapper>
)
}
const TooltipWrapper = ({
tooltip,
children,
}: {
tooltip?: string
children: ReactNode
}) => {
return tooltip ? (
<TooltipTrigger delay={300}>
{children}
<Tooltip>
<TooltipArrow />
{tooltip}
</Tooltip>
</TooltipTrigger>
) : (
children
)
}

View File

@@ -24,8 +24,8 @@ const StyledModalOverlay = styled(ModalOverlay, {
justifyContent: 'center',
alignItems: 'center',
zIndex: 1000,
'&[data-entering]': { animation: 'modalFade 200ms' },
'&[data-exiting]': { animation: 'modalFade 150ms reverse ease-in' },
'&[data-entering]': { animation: 'fade 200ms' },
'&[data-exiting]': { animation: 'fade 150ms reverse ease-in' },
},
})
@@ -36,7 +36,7 @@ const StyledModal = styled(Modal, {
height: 'full',
pointerEvents: 'none',
'--origin': 'translateY(32px)',
'&[data-entering]': { animation: 'popoverSlide 300ms' },
'&[data-entering]': { animation: 'slide 300ms' },
},
})

View File

@@ -13,10 +13,10 @@ export const StyledPopover = styled(RACPopover, {
base: {
minWidth: 'var(--trigger-width)',
'&[data-entering]': {
animation: 'popoverSlide 200ms',
animation: 'slide 200ms',
},
'&[data-exiting]': {
animation: 'popoverSlide 200ms reverse ease-in',
animation: 'slide 200ms reverse ease-in',
},
'&[data-placement="bottom"]': {
marginTop: 0.25,
@@ -67,11 +67,11 @@ export const Popover = ({
<DialogTrigger>
{trigger}
<StyledPopover>
<StyledOverlayArrow>
<svg width={12} height={12} viewBox="0 0 12 12">
<path d="M0 0 L6 6 L12 0" />
</svg>
</StyledOverlayArrow>
<StyledOverlayArrow>
<svg width={12} height={12} viewBox="0 0 12 12">
<path d="M0 0 L6 6 L12 0" />
</svg>
</StyledOverlayArrow>
<Dialog {...dialogProps}>
{({ close }) => (
<Box size="sm" type="popover">

View File

@@ -0,0 +1,74 @@
import { OverlayArrow, Tooltip as RACTooltip } from 'react-aria-components'
import { styled } from '@/styled-system/jsx'
/**
* Styled react aria Tooltip component.
*
* Note that tooltips are directly handled by Buttons via the `tooltip` prop,
* so you should not need to use this component directly.
*
* Style taken from example at https://react-spectrum.adobe.com/react-aria/Tooltip.html
*/
export const Tooltip = styled(RACTooltip, {
base: {
boxShadow: '0 8px 20px rgba(0 0 0 / 0.1)',
borderRadius: '4px',
backgroundColor: 'gray.800',
color: 'gray.100',
forcedColorAdjust: 'none',
outline: 'none',
padding: '2px 8px',
maxWidth: '150px',
transform: 'translate3d(0, 0, 0)',
'&[data-placement=top]': {
marginBottom: '8px',
'--origin': 'translateY(4px)',
},
'&[data-placement=bottom]': {
marginTop: '8px',
'--origin': 'translateY(-4px)',
},
'&[data-placement=right]': {
marginLeft: '8px',
'--origin': 'translateX(-4px)',
},
'&[data-placement=left]': {
marginRight: '8px',
'--origin': 'translateX(4px)',
},
'& .react-aria-OverlayArrow svg': {
display: 'block',
fill: 'var(--highlight-background)',
},
'&[data-entering]': { animation: 'slide 200ms' },
'&[data-exiting]': { animation: 'slide 200ms reverse ease-in' },
},
})
const StyledOverlayArrow = styled(OverlayArrow, {
base: {
'& svg': {
display: 'block',
fill: 'gray.800',
},
'&[data-placement=bottom] svg': {
transform: 'rotate(180deg)',
},
'&[data-placement=right] svg': {
transform: 'rotate(90deg)',
},
'&[data-placement=left] svg': {
transform: 'rotate(-90deg)',
},
},
})
export const TooltipArrow = () => {
return (
<StyledOverlayArrow>
<svg width={8} height={8} viewBox="0 0 8 8">
<path d="M0 0 L4 4 L8 0" />
</svg>
</StyledOverlayArrow>
)
}