Skip to content

Commit 0d7147b

Browse files
committed
feat(tooltip): add a tooltip component
1 parent 154c1e0 commit 0d7147b

File tree

6 files changed

+247
-0
lines changed

6 files changed

+247
-0
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const GROUP_NAME = 'tooltip'
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import {
2+
ArgTypes,
3+
Controls,
4+
Description,
5+
Meta,
6+
Primary,
7+
Stories,
8+
Subtitle,
9+
Title,
10+
} from '@storybook/blocks'
11+
12+
import * as TooltipStories from './Tooltip.stories'
13+
14+
<Meta of={TooltipStories} />
15+
16+
<Title />
17+
18+
<Subtitle />
19+
20+
<Description />
21+
22+
---
23+
24+
## Props
25+
26+
On top of all the props, attributes and event handler you can set on a
27+
`<div />` element, component accepts the following props:
28+
29+
<ArgTypes />
30+
31+
<Stories includePrimary={false} title="Tooltips" />
32+
33+
## Playground
34+
35+
<Primary />
36+
37+
<Controls />
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import type { Meta, StoryObj } from '@storybook/react'
2+
3+
import { fn } from '@storybook/test'
4+
import { UilEllipsisV as MoreMenuIcon } from '@tooni/iconscout-unicons-react'
5+
6+
import type { ComponentProps } from 'react'
7+
8+
import { Button } from '@/components/Button'
9+
10+
import { Tooltip } from '.'
11+
12+
type TooltipProps = ComponentProps<typeof Tooltip>
13+
14+
const meta = {
15+
args: {
16+
align: undefined,
17+
children: 'Hover me',
18+
content: 'This is a tooltip',
19+
disabled: false,
20+
side: undefined,
21+
withArrow: true,
22+
wrapInButton: false,
23+
},
24+
argTypes: {
25+
content: { control: 'text' },
26+
trigger: {
27+
control: 'radio',
28+
mapping: {
29+
Button: <Button label="Hover me" />,
30+
'Icon Button': (
31+
<Button
32+
icon={<MoreMenuIcon />}
33+
iconOnly
34+
label="Hover me"
35+
variant="text"
36+
/>
37+
),
38+
Text: 'Hover me',
39+
},
40+
options: ['Text', 'Button', 'Icon Button'],
41+
},
42+
},
43+
component: Tooltip,
44+
parameters: {
45+
docs: {
46+
subtitle: `🎩 A tip of the hat from Dr. Facilier. - Dr. Facilier - The Princess and the Frog`,
47+
},
48+
},
49+
50+
title: 'Molecules/Tooltip',
51+
} satisfies Meta<TooltipProps>
52+
53+
export default meta
54+
type Story = StoryObj<typeof meta>
55+
56+
export const Playground: Story = {
57+
args: {
58+
onDisplayChange: fn(),
59+
onHide: fn(),
60+
onInteractOutside: fn(),
61+
onShow: fn(),
62+
},
63+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import isNil from 'lodash/fp/isNil'
2+
import omit from 'lodash/fp/omit'
3+
4+
import { cn } from '@/styles/helpers'
5+
6+
import type { TooltipProps } from './Tooltip.types'
7+
8+
import { Popover } from '../Popover'
9+
import { Typography } from '../Typography'
10+
11+
/**
12+
* `Tooltip` component allow to wrap anything that should display an small
13+
* tooltip when hovered (or click).
14+
*/
15+
export const Tooltip = ({
16+
align,
17+
children,
18+
content,
19+
disabled = false,
20+
onDisplayChange,
21+
onHide,
22+
onInteractOutside,
23+
onShow,
24+
show,
25+
side,
26+
toggleOnTriggerClick = false,
27+
toggleOnTriggerHover = true,
28+
tooltip = {},
29+
withArrow = true,
30+
wrapInButton = false,
31+
...props
32+
}: TooltipProps) => {
33+
const hasTrigger = Boolean(children)
34+
const hasContent = Boolean(content)
35+
36+
if (!hasTrigger || !hasContent) {
37+
return false
38+
}
39+
40+
return (
41+
<Popover
42+
align={align}
43+
disabled={disabled}
44+
elevation="1"
45+
fullWidth={false}
46+
modal={false}
47+
open={!isNil(show) ? show : undefined}
48+
popover={{
49+
...omit(['className'], tooltip),
50+
className: cn(
51+
'px-1 py-0 rounded-xs bg-dark text-light',
52+
tooltip.className,
53+
),
54+
}}
55+
side={side}
56+
toggleOnTriggerClick={toggleOnTriggerClick}
57+
toggleOnTriggerHover={toggleOnTriggerHover}
58+
trigger={children}
59+
triggerAsButton={wrapInButton}
60+
width="fit"
61+
withArrow={withArrow}
62+
withCloseButton={false}
63+
withScroll={false}
64+
onClose={onHide}
65+
onInteractOutside={onInteractOutside}
66+
onOpen={onShow}
67+
onOpenChange={onDisplayChange}
68+
{...props}
69+
>
70+
<Typography element="div" variant="caption-median">
71+
{content}
72+
</Typography>
73+
</Popover>
74+
)
75+
}
76+
Tooltip.displayName = 'Tooltip'
77+
78+
export default Tooltip
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import type { AllHTMLAttributes, ReactNode } from 'react'
2+
3+
import type { PopoverProps } from '../Popover'
4+
5+
export interface TooltipProps
6+
extends Omit<AllHTMLAttributes<HTMLDivElement>, 'content'> {
7+
/**
8+
* The trigger of the tooltip (i.e. the thing that should display the tooltip
9+
* when hovered/clicked.
10+
*/
11+
children: PopoverProps['trigger']
12+
/** The content of the tooltip (i.e. the thing displayed in it). */
13+
content: ReactNode
14+
/** Indicates where to align the tooltip relative to the trigger. */
15+
align?: PopoverProps['align']
16+
/** Indicates if the tooltip is disabled (i.e. won't be displayed). */
17+
disabled?: PopoverProps['disabled']
18+
/**
19+
* Indicates if the tooltip is shown.
20+
*
21+
* Can be used to force the display of the tooltip or when using a custom
22+
* (non-text) trigger.
23+
*/
24+
show?: PopoverProps['open']
25+
/** The preferred side of the trigger to render the tooltip. */
26+
side?: PopoverProps['side']
27+
/**
28+
* Indicates if you want to toggle the tooltip when clicking on the trigger
29+
* (if provided of course).
30+
*/
31+
toggleOnTriggerClick?: PopoverProps['toggleOnTriggerClick']
32+
/**
33+
* Indicates if you want to toggle the tooltip when hovering on the trigger
34+
* (if provided of course).
35+
*
36+
* If you pass this to `false`, you will have to handle the opening/closing of
37+
* the tooltip on your own or use `toggleOnTriggerClick`.
38+
*/
39+
toggleOnTriggerHover?: PopoverProps['toggleOnTriggerHover']
40+
/**
41+
* Options to tweak the position of the tooltip.
42+
* See https://www.radix-ui.com/primitives/docs/components/popover#content
43+
*
44+
* You can on top of that add the `className` and `style` properties to
45+
* customize the style of the tooltip.
46+
*/
47+
tooltip?: PopoverProps['popover']
48+
/** The trigger of the tooltip. */
49+
trigger?: PopoverProps['trigger']
50+
/** Indicates if the tooltip should have an arrow indicator on the side. */
51+
withArrow?: PopoverProps['withArrow']
52+
/**
53+
* Indicates if you want the trigger to be wrapped in a `button` element.
54+
*/
55+
wrapInButton?: PopoverProps['triggerAsButton']
56+
/** Event handler called when the tooltip is shown or hidden. */
57+
onDisplayChange?: PopoverProps['onOpenChange']
58+
/** Event handler called when the tooltip is hidden. */
59+
onHide?: PopoverProps['onClose']
60+
/**
61+
* Event handler called when an interaction is made outside of the tooltip.
62+
*/
63+
onInteractOutside?: PopoverProps['onInteractOutside']
64+
/** Event handler called when the tooltip is shown. */
65+
onShow?: PopoverProps['onOpen']
66+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { default as Tooltip } from './Tooltip.js'
2+
export type { TooltipProps } from './Tooltip.types.js'

0 commit comments

Comments
 (0)