From ff2629252e818f33825ebe56e0155c62a8160f52 Mon Sep 17 00:00:00 2001 From: Julian Roeland Date: Wed, 7 Feb 2024 17:10:22 +0100 Subject: [PATCH] :sparkles: - feat: implemented the tooltip component --- src/components/index.ts | 1 + src/components/tooltip/index.ts | 1 + src/components/tooltip/tooltip.scss | 35 +++++++ src/components/tooltip/tooltip.stories.tsx | 97 ++++++++++++++++++ src/components/tooltip/tooltip.tsx | 114 +++++++++++++++++++++ 5 files changed, 248 insertions(+) create mode 100644 src/components/tooltip/index.ts create mode 100644 src/components/tooltip/tooltip.scss create mode 100644 src/components/tooltip/tooltip.stories.tsx create mode 100644 src/components/tooltip/tooltip.tsx diff --git a/src/components/index.ts b/src/components/index.ts index 40294d1f..cece20ee 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -15,4 +15,5 @@ export * from "./page"; export * from "./paginator"; export * from "./tabs"; export * from "./toolbar"; +export * from "./tooltip"; export * from "./typography"; diff --git a/src/components/tooltip/index.ts b/src/components/tooltip/index.ts new file mode 100644 index 00000000..3c61782a --- /dev/null +++ b/src/components/tooltip/index.ts @@ -0,0 +1 @@ +export * from "./tooltip"; diff --git a/src/components/tooltip/tooltip.scss b/src/components/tooltip/tooltip.scss new file mode 100644 index 00000000..ced8a75b --- /dev/null +++ b/src/components/tooltip/tooltip.scss @@ -0,0 +1,35 @@ +.mykn-tooltip { + transition: opacity 0.5s; + transform: scale(0.8); + opacity: 0; + border-radius: var(--border-radius-m); + + &--open { + opacity: 1; + background-color: var(--theme-color-primary-200); + box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.1); + transform: scale(1); + } + + &__arrow { + fill: var(--theme-color-tertiary-400); + } + + &__content { + padding: var(--spacing-h-m) var(--spacing-v-m); + box-sizing: border-box; + overflow-wrap: break-word; + + &--sm { + max-width: 200px; + } + + &--md { + max-width: 400px; + } + + &--lg { + max-width: 600px; + } + } +} diff --git a/src/components/tooltip/tooltip.stories.tsx b/src/components/tooltip/tooltip.stories.tsx new file mode 100644 index 00000000..6465cc2e --- /dev/null +++ b/src/components/tooltip/tooltip.stories.tsx @@ -0,0 +1,97 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import React from "react"; + +import { Button } from "../button"; +import { Outline } from "../icon"; +import { Tooltip } from "./tooltip"; + +const meta = { + title: "Controls/Tooltip", + component: Tooltip, + decorators: [ + (Story) => ( +
+ +
+ ), + ], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const TooltipComponent: Story = { + args: { + children: ( + + ), + content: + "This tooltip works by hovering over any react element, and it can be placed in any direction.", + }, +}; + +export const TooltipTop: Story = { + args: { + ...TooltipComponent.args, + placement: "top", + }, + decorators: [ + (Story) => ( +
+ +
+ ), + ], +}; + +export const TooltipRight: Story = { + args: { + ...TooltipComponent.args, + placement: "right", + }, +}; + +export const TooltipBottom: Story = { + args: { + ...TooltipComponent.args, + placement: "bottom", + }, +}; + +export const TooltipLeft: Story = { + args: { + ...TooltipComponent.args, + placement: "left", + }, + decorators: [ + (Story) => ( +
+ +
+ ), + ], +}; + +export const TooltipBigText: Story = { + args: { + ...TooltipComponent.args, + content: ( +
+

+ This tooltip works by hovering over any react element, and it can be + placed in any direction. +

+

+ This tooltip works by hovering over any react element, and it can be + placed in any direction. +

+

+ This tooltip works by hovering over any react element, and it can be + placed in any direction. +

+
+ ), + }, +}; diff --git a/src/components/tooltip/tooltip.tsx b/src/components/tooltip/tooltip.tsx new file mode 100644 index 00000000..e3a49890 --- /dev/null +++ b/src/components/tooltip/tooltip.tsx @@ -0,0 +1,114 @@ +import { + FloatingArrow, + Placement, + arrow, + autoUpdate, + flip, + offset, + shift, + useDismiss, + useFloating, + useFocus, + useHover, + useInteractions, + useRole, + useTransitionStyles, +} from "@floating-ui/react"; +import clsx from "clsx"; +import React, { ReactNode, useRef, useState } from "react"; + +import { P } from "../typography"; +import "./tooltip.scss"; + +type TooltipProps = React.PropsWithChildren<{ + /* The content to display in the tooltip */ + content?: ReactNode; + + /* The placement of the tooltip */ + placement?: Placement; + + /* The size of the tooltip, defaults to md */ + size?: "sm" | "md" | "lg"; +}>; + +export const Tooltip = ({ + content, + children, + placement, + size = "md", +}: TooltipProps) => { + const [isOpen, setIsOpen] = useState(false); + const arrowRef = useRef(null); + + const { + refs: { setReference, setFloating }, + floatingStyles, + context, + } = useFloating({ + open: isOpen, + onOpenChange: setIsOpen, + placement, + middleware: [ + offset(12), + flip(), + shift(), + arrow({ + element: arrowRef, + }), + ], + whileElementsMounted: autoUpdate, + }); + + const hover = useHover(context, { move: false }); + const focus = useFocus(context); + const dismiss = useDismiss(context); + const role = useRole(context, { role: "tooltip" }); + + const { getReferenceProps, getFloatingProps } = useInteractions([ + hover, + focus, + dismiss, + role, + ]); + + const { styles: transitionStyles } = useTransitionStyles(context, { + initial: { + opacity: 0, + transform: "scale(0.8)", + }, + }); + + return ( + <> + {React.cloneElement(children as React.ReactElement, { + ...getReferenceProps(), + ref: setReference, + })} +
+
+ +

{content}

+
+
+ + ); +};