From bfa2597da55d735d3a135a8e36dfe262ca7f47fd Mon Sep 17 00:00:00 2001 From: Maxime Perrault Date: Tue, 21 Jan 2025 18:11:54 +0100 Subject: [PATCH] feat(components): add Tooltip --- src/components/Tooltip/Tooltip.tsx | 85 ++++++++++++++++++++++++++ stories/components/Tooltip.stories.tsx | 35 +++++++++++ 2 files changed, 120 insertions(+) create mode 100644 src/components/Tooltip/Tooltip.tsx create mode 100644 stories/components/Tooltip.stories.tsx diff --git a/src/components/Tooltip/Tooltip.tsx b/src/components/Tooltip/Tooltip.tsx new file mode 100644 index 00000000..6f22b8f5 --- /dev/null +++ b/src/components/Tooltip/Tooltip.tsx @@ -0,0 +1,85 @@ +import { useNewWindow } from '@hooks/useNewWindow' +import { THEME } from '@theme' +import { useId, useRef, useState, type FunctionComponent, type ReactNode } from 'react' +import { createPortal } from 'react-dom' +import styled from 'styled-components' + +import { Icon as IconUi } from '../..' + +import type { IconProps } from '@types_/definitions' + +type TooltipProps = { + Icon?: FunctionComponent + children: ReactNode + className?: string + color?: string + isSideWindow?: boolean +} + +/** + * + * @param isSideWindow set it to `true` when used on SideWindow + */ +export function Tooltip({ + children, + className, + color = THEME.color.slateGray, + Icon = IconUi.Info, + isSideWindow = false +}: TooltipProps) { + // eslint-disable-next-line no-null/no-null + const ref = useRef(null) + const refLeftPosition = ref.current?.getBoundingClientRect().left ?? 0 + const refTopPosition = ref.current?.getBoundingClientRect().top ?? 0 + + const [isVisible, setIsVisible] = useState(false) + const id = useId() + + const { newWindowContainerRef } = useNewWindow() + + return ( + <> + + setIsVisible(false)} + onFocus={() => setIsVisible(true)} + onMouseLeave={() => setIsVisible(false)} + onMouseOver={() => setIsVisible(true)} + style={{ cursor: 'pointer' }} + tabIndex={0} + /> + + + {isVisible && + createPortal( + + {children} + , + isSideWindow ? newWindowContainerRef.current : (document.body as HTMLElement) + )} + + ) +} + +const StyledTooltip = styled.p<{ $left: number; $top: number }>` + background: ${p => p.theme.color.cultured}; + border: ${p => p.theme.color.lightGray} 1px solid; + box-shadow: 0px 3px 6px ${p => p.theme.color.slateGray}; + font-size: 11px; + font-weight: normal; + padding: 4px 8px; + position: fixed; + left: calc(${p => p.$left}px + 24px); + top: ${p => p.$top}px; + max-width: 310px; + pointer-events: none; + z-index: 2; +` + +const Wrapper = styled.div` + > span:hover { + color: ${p => p.theme.color.blueYonder}; + } +` diff --git a/stories/components/Tooltip.stories.tsx b/stories/components/Tooltip.stories.tsx new file mode 100644 index 00000000..365eeb92 --- /dev/null +++ b/stories/components/Tooltip.stories.tsx @@ -0,0 +1,35 @@ +// TODO Migrate this story to the new Storybook structure. Example: stories/components/Banner.stories.tsx. +/* eslint-disable react-hooks/rules-of-hooks */ + +import { Icon } from '@constants' +import { THEME } from '@theme' +import { Tooltip } from 'components/Tooltip/Tooltip' + +import { generateStoryDecorator } from '../../.storybook/utils/generateStoryDecorator' + +import type { Meta } from '@storybook/react' + +/* eslint-disable sort-keys-fix/sort-keys-fix */ +const meta: Meta<{}> = { + title: 'Components/Tooltip', + component: Tooltip, + + argTypes: {}, + + decorators: [generateStoryDecorator()] +} +/* eslint-enable sort-keys-fix/sort-keys-fix */ + +export default meta + +export function _Tooltip() { + return ( +
+ Hi ! I'm a tooltip + Hi ! I'm a tooltip without another icon + + Hi ! I'm a tooltip without another icon and another color + +
+ ) +}