From f60642f4f50912be424b2070ab9cbe34e29d832b Mon Sep 17 00:00:00 2001 From: Abel Lopes Date: Fri, 7 Jun 2024 16:55:36 +0100 Subject: [PATCH] feat: implement tooltip base functionality --- .../src/tooltip/index.module.scss | 126 +++++++++++++++++- .../_provisional/src/tooltip/index.tsx | 81 ++++++++--- .../card/src/styles/index.module.scss | 1 - packages/docs/stories/src/tooltip.stories.tsx | 14 +- 4 files changed, 186 insertions(+), 36 deletions(-) diff --git a/packages/components/_provisional/src/tooltip/index.module.scss b/packages/components/_provisional/src/tooltip/index.module.scss index bc3bdee0..743117f7 100644 --- a/packages/components/_provisional/src/tooltip/index.module.scss +++ b/packages/components/_provisional/src/tooltip/index.module.scss @@ -1,17 +1,133 @@ @import "@react-ck/theme"; @import "@react-ck/elevation"; +@import "@react-ck/text"; -$padding-v: #{get-spacing(0.5)}; +$outer-spacing: get-spacing(1); +$inner-spacing-y: get-spacing(1); +$inner-spacing-x: get-spacing(2); +$caret-spacing: get-spacing(1.66); + +@mixin animation-initial { + opacity: 0; +} + +@keyframes appear { + from { + @include animation-initial; + } + to { + opacity: 1; + } +} .container { - padding: #{$padding-v} 0; + @include define-css-var(tooltip, max-height, var(--pe-max-height)); + @include define-css-var(tooltip, max-width, var(--pe-max-width)); + @include define-css-var(tooltip, container-padding-x, 0); + @include define-css-var(tooltip, container-padding-y, 0); + + padding: #{get-css-var(tooltip, container-padding-y)} #{get-css-var(tooltip, container-padding-x)}; box-sizing: border-box; z-index: map-get-strict($elevation, popup); + + @include animation-initial; + + will-change: opacity; + animation: appear 0.2s ease forwards; + + // Apply vertical spacing + &[class*="top"], + &[class*="bottom"] { + @include define-css-var(tooltip, container-padding-y, $outer-spacing); + @include define-css-var( + tooltip, + max-height, + calc(var(--pe-max-height) - #{$outer-spacing} * 2) + ); + + &[class*="start"] .content::before { + left: $caret-spacing; + } + + &[class*="center"] .content::before { + left: 50%; + transform: translateX(-50%); + } + + &[class*="end"] .content::before { + right: $caret-spacing; + } + } + + &[class*="left"], + &[class*="right"] { + @include define-css-var(tooltip, container-padding-x, $outer-spacing); + @include define-css-var(tooltip, max-width, calc(var(--pe-max-width) - #{$outer-spacing} * 2)); + + &[class*="start"] .content::before { + top: $caret-spacing; + } + + &[class*="center"] .content::before { + top: 50%; + transform: translateY(-50%); + } + + &[class*="end"] .content::before { + bottom: $caret-spacing; + } + } + + &[class*="top"] { + .content::before { + top: 100%; + border-color: get-color(neutral-light-1) transparent transparent transparent; + filter: drop-shadow(0 3px 2px get-color(neutral-light-3)); + } + } + + &[class*="bottom"] { + .content::before { + bottom: 100%; + border-color: transparent transparent get-color(neutral-light-1) transparent; + filter: drop-shadow(0 -3px 2px get-color(neutral-light-3)); + } + } + + &[class*="left"] { + .content::before { + left: 100%; + border-color: transparent transparent transparent get-color(neutral-light-1); + filter: drop-shadow(3px 0 2px get-color(neutral-light-3)); + } + } + + &[class*="right"] { + .content::before { + right: 100%; + border-color: transparent get-color(neutral-light-1) transparent transparent; + filter: drop-shadow(-3px 0 2px get-color(neutral-light-3)); + } + } } .content { - padding: get-spacing(2); - max-height: calc(var(--pe-max-height) - #{$padding-v} * 2); - max-width: var(--pe-max-width); + @include text-base; + @include text-variation(extra-small); + + position: relative; + padding: #{$inner-spacing-y} #{$inner-spacing-x}; + max-height: get-css-var(tooltip, max-height); + max-width: get-css-var(tooltip, max-width); box-sizing: border-box; + + &::before { + content: ""; + position: absolute; + display: block; + border-style: solid; + border-width: get-spacing(0.66); + width: 0px; + height: 0px; + } } diff --git a/packages/components/_provisional/src/tooltip/index.tsx b/packages/components/_provisional/src/tooltip/index.tsx index d293ecab..e9854f7c 100644 --- a/packages/components/_provisional/src/tooltip/index.tsx +++ b/packages/components/_provisional/src/tooltip/index.tsx @@ -1,14 +1,19 @@ -import React from "react"; +import styles from "./index.module.scss"; +import React, { useEffect, useState } from "react"; import { Layer } from "@react-ck/layers"; import { PositionEngine, type PositionEngineProps } from "../position-engine"; import { ScrollableContainer } from "../scrollable-container"; import { Card } from "@react-ck/card"; -import styles from "./index.module.scss"; +import classNames from "classnames"; export interface TooltipProps { anchor: PositionEngineProps["anchorRef"]; position?: PositionEngineProps["position"]; children?: React.ReactNode; + /** + * The tooltip will open/close on hover by default, + * if you pass true/false, this behavior will be overridden and should be managed by the consumer + */ open?: boolean; } @@ -17,22 +22,58 @@ export const Tooltip = ({ position = "auto", open, children, -}: Readonly): React.ReactNode => - open && ( - ( - -
- - - {children} - - -
-
- )} - /> +}: Readonly): React.ReactNode => { + const [internalOpen, setInternalOpen] = useState(open); + + useEffect(() => { + const ref = anchor.current; + + if (open !== undefined) { + setInternalOpen(open); + return; + } + + if (!ref) throw new Error("Tooltip anchor ref is required"); + + let to: ReturnType | undefined = undefined; + + function handleMouseEnter(): void { + clearTimeout(to); + to = setTimeout(() => { + setInternalOpen(true); + }, 300); + } + + function handleMouseLeave(): void { + clearTimeout(to); + setInternalOpen(false); + } + + ref.addEventListener("mouseenter", handleMouseEnter); + ref.addEventListener("mouseleave", handleMouseLeave); + + return () => { + clearTimeout(to); + ref.removeEventListener("mouseenter", handleMouseEnter); + ref.removeEventListener("mouseleave", handleMouseLeave); + }; + }, [anchor, open]); + + return ( + internalOpen && ( + ( + +
+ +
{children}
+
+
+
+ )} + /> + ) ); +}; diff --git a/packages/components/card/src/styles/index.module.scss b/packages/components/card/src/styles/index.module.scss index 8694b047..c115de3c 100644 --- a/packages/components/card/src/styles/index.module.scss +++ b/packages/components/card/src/styles/index.module.scss @@ -7,7 +7,6 @@ background-color: get-color(neutral-light-1); border-radius: get-css-var(card, border-radius); - overflow: hidden; // Skins diff --git a/packages/docs/stories/src/tooltip.stories.tsx b/packages/docs/stories/src/tooltip.stories.tsx index 0e897909..796e0c2b 100644 --- a/packages/docs/stories/src/tooltip.stories.tsx +++ b/packages/docs/stories/src/tooltip.stories.tsx @@ -4,6 +4,7 @@ import { Manager } from "@react-ck/manager"; import { configureStory } from "@react-ck/story-config"; import { Tooltip } from "@react-ck/provisional/src"; import { Button } from "@react-ck/button"; +import { faker } from "@faker-js/faker"; type Story = StoryObj; @@ -29,7 +30,8 @@ export default meta; export const Component: Story = { args: { - open: true, + open: undefined, + position: undefined, }, render: ({ open, position }): React.ReactElement => { // eslint-disable-next-line react-hooks/rules-of-hooks -- exception for storybook @@ -39,15 +41,7 @@ export const Component: Story = { <> - Content Lorem ipsum dolor sit amet consectetur adipisicing elit. Ratione nulla est, quidem - enim a molestias accusantium quo officia provident maxime voluptatem, beatae delectus - aliquid ipsa perferendis accusamus! Eius, laborum quisquam. Eius soluta deserunt - aspernatur tenetur, laudantium quod corrupti natus facilis est ab esse sunt dolore magni - cum accusamus nemo. Optio adipisci itaque exercitationem quo nulla, odit eligendi natus - est cupiditate aperiam nemo, vero explicabo. Non eveniet ipsum, dolores suscipit sit - deserunt doloribus. Dolorum aspernatur iusto, aliquid officiis illo modi vitae. - Exercitationem laudantium inventore nemo harum commodi doloribus totam porro aliquam. Quae - aliquam iusto neque ipsam non? Consequuntur saepe inventore aliquam! + {faker.lorem.sentence(5)} );