Skip to content

Commit 7c7b988

Browse files
feat(components): add Tooltip
1 parent 6cdd1d2 commit 7c7b988

File tree

2 files changed

+120
-0
lines changed

2 files changed

+120
-0
lines changed

src/components/Tooltip/Tooltip.tsx

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { useNewWindow } from '@hooks/useNewWindow'
2+
import { THEME } from '@theme'
3+
import { useId, useRef, useState, type FunctionComponent, type ReactNode } from 'react'
4+
import { createPortal } from 'react-dom'
5+
import styled from 'styled-components'
6+
7+
import { Icon as IconUi } from '../..'
8+
9+
import type { IconProps } from '@types_/definitions'
10+
11+
type TooltipProps = {
12+
Icon?: FunctionComponent<IconProps>
13+
children: ReactNode
14+
className?: string
15+
color?: string
16+
isSideWindow?: boolean
17+
}
18+
19+
/**
20+
*
21+
* @param isSideWindow set it to `true` when used on SideWindow
22+
*/
23+
export function Tooltip({
24+
children,
25+
className,
26+
color = THEME.color.slateGray,
27+
Icon = IconUi.Info,
28+
isSideWindow = false
29+
}: TooltipProps) {
30+
// eslint-disable-next-line no-null/no-null
31+
const ref = useRef<HTMLDivElement>(null)
32+
const refLeftPosition = ref.current?.getBoundingClientRect().left ?? 0
33+
const refTopPosition = ref.current?.getBoundingClientRect().top ?? 0
34+
35+
const [isVisible, setIsVisible] = useState<boolean>(false)
36+
const id = useId()
37+
38+
const { newWindowContainerRef } = useNewWindow()
39+
40+
return (
41+
<>
42+
<Wrapper ref={ref} className={className}>
43+
<Icon
44+
aria-describedby={id}
45+
color={color}
46+
onBlur={() => setIsVisible(false)}
47+
onFocus={() => setIsVisible(true)}
48+
onMouseLeave={() => setIsVisible(false)}
49+
onMouseOver={() => setIsVisible(true)}
50+
style={{ cursor: 'pointer' }}
51+
tabIndex={0}
52+
/>
53+
</Wrapper>
54+
55+
{isVisible &&
56+
createPortal(
57+
<StyledTooltip $left={refLeftPosition} $top={refTopPosition} id={id} role="tooltip">
58+
{children}
59+
</StyledTooltip>,
60+
isSideWindow ? newWindowContainerRef.current : (document.body as HTMLElement)
61+
)}
62+
</>
63+
)
64+
}
65+
66+
const StyledTooltip = styled.p<{ $left: number; $top: number }>`
67+
background: ${p => p.theme.color.cultured};
68+
border: ${p => p.theme.color.lightGray} 1px solid;
69+
box-shadow: 0px 3px 6px ${p => p.theme.color.slateGray};
70+
font-size: 11px;
71+
font-weight: normal;
72+
padding: 4px 8px;
73+
position: fixed;
74+
left: calc(${p => p.$left}px + 24px);
75+
top: ${p => p.$top}px;
76+
max-width: 310px;
77+
pointer-events: none;
78+
z-index: 2;
79+
`
80+
81+
const Wrapper = styled.div`
82+
> span:hover {
83+
color: ${p => p.theme.color.blueYonder};
84+
}
85+
`
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// TODO Migrate this story to the new Storybook structure. Example: stories/components/Banner.stories.tsx.
2+
/* eslint-disable react-hooks/rules-of-hooks */
3+
4+
import { Icon } from '@constants'
5+
import { THEME } from '@theme'
6+
import { Tooltip } from 'components/Tooltip/Tooltip'
7+
8+
import { generateStoryDecorator } from '../../.storybook/utils/generateStoryDecorator'
9+
10+
import type { Meta } from '@storybook/react'
11+
12+
/* eslint-disable sort-keys-fix/sort-keys-fix */
13+
const meta: Meta<{}> = {
14+
title: 'Components/Tooltip',
15+
component: Tooltip,
16+
17+
argTypes: {},
18+
19+
decorators: [generateStoryDecorator()]
20+
}
21+
/* eslint-enable sort-keys-fix/sort-keys-fix */
22+
23+
export default meta
24+
25+
export function _Tooltip() {
26+
return (
27+
<div style={{ height: '500px' }}>
28+
<Tooltip>Hi ! I&apos;m a tooltip</Tooltip>
29+
<Tooltip Icon={Icon.AttentionFilled}>Hi ! I&apos;m a tooltip without another icon</Tooltip>
30+
<Tooltip color={THEME.color.maximumRed} Icon={Icon.Fishery}>
31+
Hi ! I&apos;m a tooltip without another icon and another color
32+
</Tooltip>
33+
</div>
34+
)
35+
}

0 commit comments

Comments
 (0)