Repository: marigold-ui/marigold
Pull request #2069 (2022-05-13 10:24:37)

refa: `<Tooltip>`
- with arrow pointer yay!
- allows to change placement
- uses `useComponentStyles`

changeset-bot[bot]: ###  ⚠️  No Changeset found

Latest commit: 455104ebb5c0753a9b13c06cac584ce9a98b6584

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. **If these changes should result in a version bump, you need to add a changeset.**

<details><summary>This PR includes no changesets</summary>

  When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

</details>

[Click here to learn what changesets are, and how to add one](https://github.com/changesets/changesets/blob/master/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add a changeset to this PR](https://github.com/marigold-ui/marigold/new/redo-tooltip?filename=.changeset/serious-garlics-pretend.md&value=---%0A%22%40marigold%2Fdocs%22%3A%20patch%0A%22%40marigold%2Fcomponents%22%3A%20patch%0A%22%40marigold%2Ftheme-b2b%22%3A%20patch%0A%22%40marigold%2Ftheme-core%22%3A%20patch%0A%22%40marigold%2Ftheme-unicorn%22%3A%20patch%0A---%0A%0Arefa%3A%20%60%3CTooltip%3E%60%0A)



Patch:
diff --git a/.changeset/serious-garlics-pretend.md b/.changeset/serious-garlics-pretend.md
new file mode 100644
index 000000000..eac015ef2
--- /dev/null
+++ b/.changeset/serious-garlics-pretend.md
@@ -0,0 +1,13 @@
+---
+"@marigold/docs": major
+"@marigold/components": major
+"@marigold/theme-b2b": major
+"@marigold/theme-core": major
+"@marigold/theme-unicorn": major
+---
+
+refa: `<Tooltip>`
+
+- with arrow pointer yay!
+- allows to change placement
+- uses `useComponentStyles`
diff --git a/config/jest/package.json b/config/jest/package.json
index 0f1123f94..ee5a7023f 100644
--- a/config/jest/package.json
+++ b/config/jest/package.json
@@ -22,7 +22,7 @@
   },
   "dependencies": {
     "@babel/core": "7.17.10",
-    "@swc/core": "1.2.120",
+    "@swc/core": "1.2.183",
     "@swc/jest": "0.2.15",
     "@types/jest": "27.4.1",
     "babel-jest": "27.5.1",
diff --git a/config/storybook/package.json b/config/storybook/package.json
index abdafa6d6..726ca9e73 100644
--- a/config/storybook/package.json
+++ b/config/storybook/package.json
@@ -29,7 +29,7 @@
     "@storybook/addon-essentials": "6.4.22",
     "@storybook/addon-interactions": "6.4.22",
     "@storybook/jest": "0.0.10",
-    "@storybook/testing-library": "0.0.9",
+    "@storybook/testing-library": "0.0.11",
     "babel-loader": "8.2.5",
     "find-up": "5.0.0",
     "storybook-addon-themes": "6.1.0",
diff --git a/docs/content/components/tooltip.mdx b/docs/content/components/tooltip.mdx
index aac8dd644..ded79e4b1 100644
--- a/docs/content/components/tooltip.mdx
+++ b/docs/content/components/tooltip.mdx
@@ -2,111 +2,63 @@
 title: Tooltip
 ---
 
-import { DoAndDont } from '../../src/components/DoAndDont';
-import {
-  Box,
-  Button,
-  Stack,
-  Tooltip,
-  TooltipTrigger,
-} from '@marigold/components';
-import { Info } from '@marigold/icons';
-
 # Tooltip
 
-The Tooltip should provide extra information to the user. This information should be
-secondary and not escential to perform a task.
-A tooltip consists of two parts: the **trigger** element and the **tooltip** itself.
-Users may reveal the tooltip by hovering or focusing the trigger.
+The `Tooltip` components allows to provide additional information to that user.
 
-<MarigoldTheme>
-  <TooltipTrigger isOpen>
-    <Button variant="" size="" aria-label="infoIconButton">
-      <Info fill="info" />
-    </Button>
-    <Tooltip>Tooltip</Tooltip>
-  </TooltipTrigger>
-</MarigoldTheme>
+```tsx
+<Tooltip.Trigger>
+  <Button variant="primary">Hover me!</Button>
+  <Tooltip>Look at this cool tip!</Tooltip>
+</Tooltip.Trigger>
+```
 
 ```tsx onlyCode
-import { Tooltip, TooltipTrigger } from '@marigold/components';
+import { Tooltip } from '@marigold/components';
 ```
 
 ## Props
 
-| Property             | Type     | Default     |
-| :------------------- | :------- | :---------- |
-| `variant` (optional) | `string` | `__default` |
+### TooltipTrigger
+
+| Property                 | Type                                     | Default    |
+| :----------------------- | :--------------------------------------- | :--------- |
+| `variant` (optional)     | `string`                                 |            |
+| `size` (optional)        | `string`                                 |            |
+| `disabled` (optional)    | `boolean`                                | `false`    |
+| `offset` (optional)      | `number`                                 |            |
+| `crossOffset` (optional) | `number`                                 |            |
+| `delay` (optional)       | `number`                                 |            |
+| `trogger` (optional)     | `'focus''                                | undefined` |
+| `placement` (optional)   | `'top' \| 'bottom' \| 'left' \| 'right'` | `'top'`    |
 
 ## Examples
 
 ### Default Tooltip
 
 ```tsx expandCode
-<TooltipTrigger>
-  <Button variant="" size="" aria-label="infoIconButton">
+<Tooltip.Trigger>
+  <Button>
     <Info fill="info" />
   </Button>
   <Tooltip>Tooltip</Tooltip>
-</TooltipTrigger>
+</Tooltip.Trigger>
 ```
 
-## Dos & Don'ts
+### Tooltip with custom placement
 
-<Stack space="small">
-  <Box
-    display="grid"
-    css={{
-      gridGap: 'small',
-      gridTemplateColumns: 'repeat(auto-fit, minmax(min(500px, 100%), 1fr))',
-    }}
-  >
-    <DoAndDont
-      preview={
-        <TooltipTrigger isOpen>
-          <Button variant="" size="" aria-label="infoIconButton">
-            <Info fill="info" />
-          </Button>
-          <Box as={Tooltip} css={{ position: 'relative' }}>
-            Tooltip
-          </Box>
-        </TooltipTrigger>
-      }
-    >
-      Point an information icon.
-    </DoAndDont>
-    <DoAndDont
-      dont
-      preview={
-        <TooltipTrigger isOpen>
-          <Button variant="" size="" aria-label="infoIconButton">
-            <Info fill="info" />
-          </Button>
-          <Box as={Tooltip} css={{ position: 'relative' }}>
-            Lorem Ipsum is simply dummy text of the printing and typesetting
-            industry.
-          </Box>
-        </TooltipTrigger>
-      }
-    >
-      Don't make extensive explanations on a tool tip. It is only for short and
-      “nice to have” extra information.
-    </DoAndDont>
-    <DoAndDont
-      dont
-      preview={
-        <TooltipTrigger isOpen>
-          <Button variant="" size="" aria-label="infoIconButton">
-            <Info fill="info" />
-          </Button>
-          <Box as={Tooltip} css={{ position: 'relative' }}>
-            If you do not click the box, all data will be deleted.
-          </Box>
-        </TooltipTrigger>
-      }
-    >
-      Don't put highly important information on a tooltip, this element is
-      hidden and should only contain “nice to have” information.
-    </DoAndDont>
-  </Box>
-</Stack>
+```tsx expandCode
+<Tooltip.Trigger placement="right">
+  <Button>Hover me!</Button>
+  <Tooltip>Tooltip</Tooltip>
+</Tooltip.Trigger>
+```
+
+### Tooltip only shows when focused
+
+```tsx expandCode
+<Tooltip.Trigger trigger="focus">
+  <Button>Hover me!</Button>
+  <Tooltip>Tooltip</Tooltip>
+</Tooltip.Trigger>
+```
diff --git a/packages/components/src/Tooltip/Context.ts b/packages/components/src/Tooltip/Context.ts
new file mode 100644
index 000000000..fc2a6ea0f
--- /dev/null
+++ b/packages/components/src/Tooltip/Context.ts
@@ -0,0 +1,13 @@
+import { createContext, HTMLAttributes, RefObject, useContext } from 'react';
+import { TooltipTriggerState } from '@react-stately/tooltip';
+import { PlacementAxis } from '@react-types/overlays';
+
+export interface TooltipContextProps extends HTMLAttributes<HTMLElement> {
+  state?: TooltipTriggerState;
+  overlayRef?: RefObject<HTMLDivElement>;
+  placement?: PlacementAxis;
+  arrowProps?: HTMLAttributes<HTMLElement>;
+}
+
+export const TooltipContext = createContext<TooltipContextProps>({});
+export const useTooltipContext = () => useContext(TooltipContext);
diff --git a/packages/components/src/Tooltip/Tooltip.stories.tsx b/packages/components/src/Tooltip/Tooltip.stories.tsx
index 51706d81e..4b52c6446 100644
--- a/packages/components/src/Tooltip/Tooltip.stories.tsx
+++ b/packages/components/src/Tooltip/Tooltip.stories.tsx
@@ -1,38 +1,92 @@
 import React from 'react';
 import type { Meta, ComponentStory } from '@storybook/react';
-
-import { Info } from '@marigold/icons';
+import { Box } from '@marigold/system';
 
 import { Tooltip } from './Tooltip';
-import { TooltipTrigger } from './TooltipTrigger';
 import { Button } from '../Button';
 
 export default {
   title: 'Components/Tooltip',
   argTypes: {
-    variant: {
+    disabled: {
+      control: {
+        type: 'boolean',
+      },
+      defaultValue: false,
+      description: 'Disabled state',
+    },
+    offset: {
+      control: {
+        type: 'integer',
+      },
+      description: 'The offset from the trigger element',
+    },
+    crossOffset: {
+      control: {
+        type: 'integer',
+      },
+      description:
+        'The additional offset across the cross acis from the trigger element',
+    },
+    delay: {
+      control: {
+        type: 'number',
+      },
+      description: 'The delay before the tooltip is shown',
+    },
+    trigger: {
+      control: {
+        type: 'select',
+      },
+      options: [undefined, 'focus'],
+      defaultValue: undefined,
+      description: 'The trigger type (default = focus AND hover)',
+    },
+    placement: {
+      control: {
+        type: 'select',
+      },
+      options: ['top', 'bottom', 'left', 'right'],
+      defaultValue: undefined,
+      description: 'The placement of the tooltip',
+    },
+    containerPadding: {
       control: {
-        type: 'text',
+        type: 'number',
       },
-      description: 'variant to style tooltip',
+      description: 'The padding around the tooltip',
     },
-    children: {
+    shouldFlip: {
       control: {
-        type: 'text',
+        type: 'boolean',
       },
-      description: 'Tooltip content',
-      defaultValue: 'Tooltip',
+      description: 'Should the tooltip be automatically be flipped',
     },
   },
 } as Meta;
 
 export const Basic: ComponentStory<typeof Tooltip> = args => {
   return (
-    <TooltipTrigger>
-      <Button variant="" size="" aria-label="infoIconButton">
-        <Info fill="info" />
-      </Button>
-      <Tooltip {...args} />
-    </TooltipTrigger>
+    <Box
+      css={{
+        display: 'flex',
+        gap: 10,
+        pt: 120,
+        width: 'min(100% - 3rem, 60ch)',
+        marginInline: 'auto',
+      }}
+    >
+      <Tooltip.Trigger {...args}>
+        <Button variant="primary">Hover me!</Button>
+        <Tooltip>Look at this tooltip!</Tooltip>
+      </Tooltip.Trigger>
+      <Tooltip.Trigger {...args}>
+        <Button variant="primary">Hover no! Me!</Button>
+        <Tooltip>
+          <div>I am a much more longer tooltip you know!</div>
+          <div>I even have two lines!</div>
+        </Tooltip>
+      </Tooltip.Trigger>
+    </Box>
   );
 };
diff --git a/packages/components/src/Tooltip/Tooltip.tsx b/packages/components/src/Tooltip/Tooltip.tsx
index 455ef3067..8ed926415 100755
--- a/packages/components/src/Tooltip/Tooltip.tsx
+++ b/packages/components/src/Tooltip/Tooltip.tsx
@@ -1,41 +1,66 @@
-import React, { useContext } from 'react';
+import React, { ReactNode } from 'react';
 import { useTooltip } from '@react-aria/tooltip';
-import { mergeProps } from '@react-aria/utils';
-import type { TooltipTriggerProps } from '@react-types/tooltip';
 
-import { Box } from '../Box';
-import { TooltipContext } from './TooltipTrigger';
+import {
+  Box,
+  ThemeExtensionsWithParts,
+  useComponentStyles,
+} from '@marigold/system';
+import { ComponentProps } from '@marigold/types';
+
+import { useTooltipContext } from './Context';
+import { TooltipTrigger } from './TooltipTrigger';
 
 // Theme Extension
 // ---------------
-export interface TooltipThemeExtension<Value> {
-  tooltip?: {
-    [key: string]: Value;
-  };
-}
+export interface TooltipThemeExtension
+  extends ThemeExtensionsWithParts<'Tooltip', ['container', 'arrow']> {}
 
 // Props
 // ---------------
-export interface TooltipProps extends TooltipTriggerProps {
+export interface TooltipProps extends ComponentProps<'div'> {
+  children?: ReactNode;
   variant?: string;
+  size?: string;
 }
 
 // Component
 // ---------------
-export const Tooltip: React.FC<TooltipProps> = ({
-  variant = '',
-  children,
-  ...props
-}) => {
-  const { state, ...tooltipProviderProps } = useContext(TooltipContext);
-  const mergedProps = mergeProps(props, tooltipProviderProps);
-  const { tooltipProps } = useTooltip(mergedProps, state);
+export const Tooltip = ({ children, variant, size }: TooltipProps) => {
+  const { arrowProps, state, overlayRef, placement, ...rest } =
+    useTooltipContext();
+  const { tooltipProps } = useTooltip({}, state);
+
+  const styles = useComponentStyles(
+    'Tooltip',
+    { variant, size },
+    { parts: ['container', 'arrow'] }
+  );
 
   return (
-    <Box position="relative" {...tooltipProps}>
-      <Box position="absolute" variant={`tooltip.${variant}`} {...mergedProps}>
-        {children}
-      </Box>
+    <Box
+      {...tooltipProps}
+      {...rest}
+      ref={overlayRef}
+      css={styles.container}
+      data-placement={placement}
+    >
+      <div>{children}</div>
+      <Box
+        {...arrowProps}
+        __baseCSS={{
+          position: 'absolute',
+          height: 0,
+          width: 0,
+          borderStyle: 'solid',
+          borderLeftColor: 'transparent',
+          borderRightColor: 'transparent',
+          borderBottomColor: 'transparent',
+        }}
+        css={styles.arrow}
+      />
     </Box>
   );
 };
+
+Tooltip.Trigger = TooltipTrigger;
diff --git a/packages/components/src/Tooltip/TooltipTrigger.tsx b/packages/components/src/Tooltip/TooltipTrigger.tsx
index 4b7ac9777..4c65b451a 100644
--- a/packages/components/src/Tooltip/TooltipTrigger.tsx
+++ b/packages/components/src/Tooltip/TooltipTrigger.tsx
@@ -1,45 +1,81 @@
-import React, { Ref, RefObject, useRef } from 'react';
+import React, { ReactElement, useRef } from 'react';
 import { FocusableProvider } from '@react-aria/focus';
+import { useOverlayPosition } from '@react-aria/overlays';
 import { useTooltipTrigger } from '@react-aria/tooltip';
-import {
-  TooltipTriggerState,
-  useTooltipTriggerState,
-} from '@react-stately/tooltip';
+import { useTooltipTriggerState } from '@react-stately/tooltip';
+import { PositionProps } from '@react-types/overlays';
+import { TooltipTriggerProps as AriaTooltipTriggerProps } from '@react-types/tooltip';
+import { TooltipContext } from './Context';
+import { Overlay } from '../Overlay';
 
-// TooltipContext
+// Props
 // ---------------
-export const TooltipContext = React.createContext<{
-  state?: TooltipTriggerState;
-}>({});
+export interface TooltipTriggerProps
+  extends Omit<AriaTooltipTriggerProps, 'isDisabled' | 'isOpen'>,
+    Omit<PositionProps, 'isOpen'> {
+  children: [trigger: ReactElement, menu: ReactElement];
+  disabled?: boolean;
+  open?: boolean;
+}
 
-// TooltipTrigger Component
+// Component
 // ---------------
-export const TooltipTrigger: React.FC = ({ children, ...props }) => {
-  const [trigger, tooltip] = React.Children.toArray(children);
+export const TooltipTrigger = ({
+  disabled,
+  open,
+  delay = 1000,
+  placement = 'top',
+  children,
+  ...rest
+}: TooltipTriggerProps) => {
+  const [tooltipTrigger, tooltip] = React.Children.toArray(children);
+  const props = {
+    ...rest,
+    isDisabled: disabled,
+    isOpen: open,
+    delay,
+    placement,
+  };
+
+  const tooltipTriggerRef = useRef(null);
+  const overlayRef = useRef(null);
+
   const state = useTooltipTriggerState(props);
-  const tooltipTriggerRef = useRef<HTMLElement>();
+
   const { triggerProps, tooltipProps } = useTooltipTrigger(
-    {},
+    props,
     state,
-    tooltipTriggerRef as RefObject<HTMLElement>
+    tooltipTriggerRef
   );
 
+  const {
+    overlayProps: positionProps,
+    placement: overlayPlacement,
+    arrowProps,
+  } = useOverlayPosition({
+    placement: props.placement,
+    targetRef: tooltipTriggerRef,
+    offset: props.offset,
+    crossOffset: props.crossOffset,
+    isOpen: state.isOpen,
+    overlayRef,
+  });
+
   return (
-    <FocusableProvider
-      {...triggerProps}
-      ref={tooltipTriggerRef as Ref<HTMLElement>}
-    >
-      {trigger}
-      {state.isOpen && (
-        <TooltipContext.Provider
-          value={{
-            state,
-            ...tooltipProps,
-          }}
-        >
-          {tooltip}
-        </TooltipContext.Provider>
-      )}
+    <FocusableProvider ref={tooltipTriggerRef} {...triggerProps}>
+      {tooltipTrigger}
+      <TooltipContext.Provider
+        value={{
+          state,
+          overlayRef,
+          arrowProps,
+          placement: overlayPlacement,
+          ...tooltipProps,
+          ...positionProps,
+        }}
+      >
+        <Overlay open={state.isOpen}>{tooltip}</Overlay>
+      </TooltipContext.Provider>
     </FocusableProvider>
   );
 };
diff --git a/packages/components/src/Tooltip/index.ts b/packages/components/src/Tooltip/index.ts
index 8cb0b5104..7594a8f06 100644
--- a/packages/components/src/Tooltip/index.ts
+++ b/packages/components/src/Tooltip/index.ts
@@ -1,2 +1,1 @@
 export * from './Tooltip';
-export * from './TooltipTrigger';
diff --git a/packages/components/src/theme.ts b/packages/components/src/theme.ts
index 6b338f375..24fee963b 100644
--- a/packages/components/src/theme.ts
+++ b/packages/components/src/theme.ts
@@ -36,9 +36,7 @@ import type { SwitchThemeExtension } from './Switch';
 import type { TableThemeExtension } from './Table';
 import type { TextThemeExtension } from './Text';
 import type { TextAreaThemeExtension } from './TextArea';
-
-// Old Styling Solution
-import { type TooltipThemeExtension } from './Tooltip';
+import type { TooltipThemeExtension } from './Tooltip';
 
 interface ComponentStyles
   extends BadgeThemeExtension,
@@ -69,11 +67,9 @@ interface ComponentStyles
     TableThemeExtension,
     TextThemeExtension,
     TextAreaThemeExtension,
+    TooltipThemeExtension,
     UnderlayThemeExtension {}
 
-export interface Theme
-  extends Scales,
-    RootThemeExtension<CSSObject>,
-    TooltipThemeExtension<CSSObject> {
+export interface Theme extends Scales, RootThemeExtension<CSSObject> {
   components: ComponentStyles;
 }
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 0f052e05c..d37ad80b7 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -108,7 +108,7 @@ importers:
   config/jest:
     specifiers:
       '@babel/core': 7.17.10
-      '@swc/core': 1.2.120
+      '@swc/core': 1.2.183
       '@swc/jest': 0.2.15
       '@types/jest': 27.4.1
       babel-jest: 27.5.1
@@ -122,8 +122,8 @@ importers:
       typescript: 4.6.3
     dependencies:
       '@babel/core': 7.17.10
-      '@swc/core': 1.2.120
-      '@swc/jest': 0.2.15_@swc+core@1.2.120
+      '@swc/core': 1.2.183
+      '@swc/jest': 0.2.15_@swc+core@1.2.183
       '@types/jest': 27.4.1
       babel-jest: 27.5.1_@babel+core@7.17.10
       find-up: 5.0.0
@@ -151,7 +151,7 @@ importers:
       '@storybook/addon-essentials': 6.4.22
       '@storybook/addon-interactions': 6.4.22
       '@storybook/jest': 0.0.10
-      '@storybook/testing-library': 0.0.9
+      '@storybook/testing-library': 0.0.11
       babel-loader: 8.2.5
       find-up: 5.0.0
       react: 17.0.2
@@ -171,7 +171,7 @@ importers:
       '@storybook/addon-essentials': 6.4.22_d5ielqfma3aytfbkci42b5ftku
       '@storybook/addon-interactions': 6.4.22_fdtqczkawcrswx4npptvkurkw4
       '@storybook/jest': 0.0.10_sfoxds7t5ydpegc3knd667wn6m
-      '@storybook/testing-library': 0.0.9_sfoxds7t5ydpegc3knd667wn6m
+      '@storybook/testing-library': 0.0.11_sfoxds7t5ydpegc3knd667wn6m
       babel-loader: 8.2.5_usdhdj5awexcm2e5jtwd44bofa
       find-up: 5.0.0
       storybook-addon-themes: 6.1.0_sfoxds7t5ydpegc3knd667wn6m
@@ -4108,16 +4108,6 @@ packages:
       react-is: 17.0.2
     dev: false
 
-  /@napi-rs/triples/1.1.0:
-    resolution: {integrity: sha512-XQr74QaLeMiqhStEhLn1im9EOMnkypp7MZOwQhGzqp2Weu5eQJbpPxWxixxlYRKWPOmJjsk6qYfYH9kq43yc2w==}
-    dev: false
-
-  /@node-rs/helper/1.3.3:
-    resolution: {integrity: sha512-p4OdfQObGN9YFy5WZaGwlPYICQSe7xZYyXB0sxREmvj1HzGKp5bPg2PlfgfMZEfnjIA882B9ZrnagYzZihIwjA==}
-    dependencies:
-      '@napi-rs/triples': 1.1.0
-    dev: false
-
   /@nodelib/fs.scandir/2.1.5:
     resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
     engines: {node: '>= 8'}
@@ -7356,8 +7346,8 @@ packages:
       util-deprecate: 1.0.2
     dev: false
 
-  /@storybook/testing-library/0.0.9_sfoxds7t5ydpegc3knd667wn6m:
-    resolution: {integrity: sha512-X4/Tk7RryB0tZ/9NLUHYTBXW01zRpf6+IhdhqsVl6WOWRyUaIv3zEhr43gQyZhBe4ZZyt5d90FJC9qWmY1oFKg==}
+  /@storybook/testing-library/0.0.11_sfoxds7t5ydpegc3knd667wn6m:
+    resolution: {integrity: sha512-8KbKx3s1e+uF3oWlPdyXRpZa6xtCsCHtXh1nCTisMA6P5YcSDaCg59NXIOVIQCAwKvjRomlqMJH8JL1WyOzeVg==}
     dependencies:
       '@storybook/client-logger': 6.4.22
       '@storybook/instrumenter': 6.4.22_sfoxds7t5ydpegc3knd667wn6m
@@ -7431,8 +7421,17 @@ packages:
       - '@types/react'
     dev: false
 
-  /@swc/core-android-arm64/1.2.174:
-    resolution: {integrity: sha512-gGXBuY8Zs93eNduyrhm+rB+K/smF535uwmamHB1+7BrkuUtt/Lrk3vDTc4D61GMA/vBOqus9IXKHBc+EFmBbog==}
+  /@swc/core-android-arm-eabi/1.2.183:
+    resolution: {integrity: sha512-DzFR8sokjVESnSXWR/4hLDxPUuvFoTpAHbMEQjj6YrwiXeWQneFudATa0ysyGlszGrmlrT+yL5wrK+gp0M8+Kg==}
+    engines: {node: '>=10'}
+    cpu: [arm]
+    os: [android]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@swc/core-android-arm64/1.2.183:
+    resolution: {integrity: sha512-LHDSXrtRLYvSbPU0Canp6kcanGbu5IdApvY/2njyLLlhi9GNg+0VJosc14O5sSid5qdg/mMaq4y6u1X6j8CxUw==}
     engines: {node: '>=10'}
     cpu: [arm64]
     os: [android]
@@ -7440,8 +7439,8 @@ packages:
     dev: false
     optional: true
 
-  /@swc/core-darwin-arm64/1.2.174:
-    resolution: {integrity: sha512-dFxu2o8dR55A+d0Qf/Ai0yKnGjon/UT1hWnBY9tcyvgo7XsiFRB/P83tlbVohqq9N+SDVxdBg9bMAR2i8rDTHg==}
+  /@swc/core-darwin-arm64/1.2.183:
+    resolution: {integrity: sha512-4UZhW/6DzcwFI5MQcGHEsjCDl32Ilfg09TOtr9IO9QK0nADE0Vy6K83ze8iXDIxVnu/jmc2rrX/FwmqRCS7F9w==}
     engines: {node: '>=10'}
     cpu: [arm64]
     os: [darwin]
@@ -7449,8 +7448,8 @@ packages:
     dev: false
     optional: true
 
-  /@swc/core-darwin-x64/1.2.174:
-    resolution: {integrity: sha512-LsANlfBkqfJobPvfojTbUY9xw0ZwTxTicdwaK6BC5TLmYXVfvxvMpmhsvIe3bNlx4jV1z0IrFaJ0YxDSc1s6vw==}
+  /@swc/core-darwin-x64/1.2.183:
+    resolution: {integrity: sha512-3BYS/4jkZAZbmrxXYeDAljjAbE/TAiEXIDKeGiRZ+nRk0sLNqwO30aWZl34U1CbzuK3gPcGHTEVuChUCoTkWKw==}
     engines: {node: '>=10'}
     cpu: [x64]
     os: [darwin]
@@ -7458,8 +7457,8 @@ packages:
     dev: false
     optional: true
 
-  /@swc/core-freebsd-x64/1.2.174:
-    resolution: {integrity: sha512-IG0qc9/qUHmiqC6fqT3yq4iBwNRHN1zYbGpzQm4DU/d7DTwsV7D2A+1McFUKmnUOvBG7zdgg5ee91QxUVOCZTQ==}
+  /@swc/core-freebsd-x64/1.2.183:
+    resolution: {integrity: sha512-9nJqmFZzo2sCcRRZzKxYjEHE9dL3xj0vkELIkfDi7p1C+GpiwZTIh6s39KusnhY89s8igdYWnRyg/3d2FHsuFA==}
     engines: {node: '>=10'}
     cpu: [x64]
     os: [freebsd]
@@ -7467,8 +7466,8 @@ packages:
     dev: false
     optional: true
 
-  /@swc/core-linux-arm-gnueabihf/1.2.174:
-    resolution: {integrity: sha512-nK4U8s9++e5AVEhWz3W6V0tUsPPjYJlMsb4ZzZR2qhYe53YBURCznFIhUDwe0ib/f6T773Tn7sr52uemcQBu8A==}
+  /@swc/core-linux-arm-gnueabihf/1.2.183:
+    resolution: {integrity: sha512-Bd0jwn715oqJTVICZiyWx6OYK7qAXZhJbpJLEekpkMfTyveLCnwyou+bwdldGPdqOHb+3q0Cg2eds1P0pXs9Xw==}
     engines: {node: '>=10'}
     cpu: [arm]
     os: [linux]
@@ -7476,8 +7475,8 @@ packages:
     dev: false
     optional: true
 
-  /@swc/core-linux-arm64-gnu/1.2.174:
-    resolution: {integrity: sha512-Tf6Ths472ul9Z4p8uFP7vMJXLmBS2h4PQL/zDj3g3N+9D8hs6+tnnBNrkEm6jXs09gO4bvaN+706QxmZUEgyPg==}
+  /@swc/core-linux-arm64-gnu/1.2.183:
+    resolution: {integrity: sha512-QdQJyfL9nQPYMXdH2pdN6v0M0G8hdEY1g2Cao07CwNVGD0ZHURFY1GFPoyXLkVPVUqyvUPCRZSfC4fTUrKUFeA==}
     engines: {node: '>=10'}
     cpu: [arm64]
     os: [linux]
@@ -7485,8 +7484,8 @@ packages:
     dev: false
     optional: true
 
-  /@swc/core-linux-arm64-musl/1.2.174:
-    resolution: {integrity: sha512-vsQjxyCe3+qRMQSuDlYwmFxbRILIycRC8OB/tBhTJXYuwIh4zvpxc+zvSuERbd4cmMWwM/JtXIlP5grSNjgH6A==}
+  /@swc/core-linux-arm64-musl/1.2.183:
+    resolution: {integrity: sha512-hkDyhwgsK+MPZ47CSjk3VIrabX8xzuQHRAJoPseR/kTJ782ur19Dsun+7tVD8aSgmWjG7nBHYvsdF96jLLim6g==}
     engines: {node: '>=10'}
     cpu: [arm64]
     os: [linux]
@@ -7494,8 +7493,8 @@ packages:
     dev: false
     optional: true
 
-  /@swc/core-linux-x64-gnu/1.2.174:
-    resolution: {integrity: sha512-CFEL2WMIQ1mZvvDguaVGgwan9X7Ah7ctHG8SOa+mFaWXdAyVXOWp8sdNMJbqvwYAv980CENfYioI8yL0z1heWA==}
+  /@swc/core-linux-x64-gnu/1.2.183:
+    resolution: {integrity: sha512-jR6yjQWBm5q9wY4kXfT+3rKHoE9+ugGycaO/yuSeekxvs+dlEGcup0vu/VjfurnbfpAj7O2dnSKH7TE95wMkQg==}
     engines: {node: '>=10'}
     cpu: [x64]
     os: [linux]
@@ -7503,8 +7502,8 @@ packages:
     dev: false
     optional: true
 
-  /@swc/core-linux-x64-musl/1.2.174:
-    resolution: {integrity: sha512-qulTJ3GYE8gUR0yfIOJVfo8SSl/AgLKAplskIHJu/cP0JtRpHL8B598mfny6SuD+ZabBjUvAz6d8ACL9JdhYTw==}
+  /@swc/core-linux-x64-musl/1.2.183:
+    resolution: {integrity: sha512-4YM3TWishQal3qFsNwWXd47em2zTOShgkxUBm2SOGKtPCMsrfi9MJdesyQH5rCrNWGy1lvo7XpvnN2GSEzYjBQ==}
     engines: {node: '>=10'}
     cpu: [x64]
     os: [linux]
@@ -7512,8 +7511,8 @@ packages:
     dev: false
     optional: true
 
-  /@swc/core-win32-arm64-msvc/1.2.174:
-    resolution: {integrity: sha512-ZogvH4sOVk3pLguYLlc+3lBlGwJ4WOi375VUqDSb92TirBFPjhX98xDn/Xus1fyRKofSGp1zoAs4w80F6hkX6g==}
+  /@swc/core-win32-arm64-msvc/1.2.183:
+    resolution: {integrity: sha512-mIXR1hPkNL2/v3xEcfbgZJclwMuQxJNVIHUyu6+cb8vUp4rbYoJioEi0qh613c36o9MpHccKZftMMd0buNFWXg==}
     engines: {node: '>=10'}
     cpu: [arm64]
     os: [win32]
@@ -7521,8 +7520,8 @@ packages:
     dev: false
     optional: true
 
-  /@swc/core-win32-ia32-msvc/1.2.174:
-    resolution: {integrity: sha512-QcnXBs2L0O5BiV61orDxJqZsmk2wPUscTTY4upfW5++eCI6IpDYVyfQmtQ9OdVCXWfVEPrhQKuUxLZEjoKxdNQ==}
+  /@swc/core-win32-ia32-msvc/1.2.183:
+    resolution: {integrity: sha512-NokriuUcwOBdj6k6fpNrFcprv2FoHfyV6ymBaZuk8UXdoRhDFguoTsFxKxKo1oGyqYreuPmaK7NBU/H9SNRxDA==}
     engines: {node: '>=10'}
     cpu: [ia32]
     os: [win32]
@@ -7530,8 +7529,8 @@ packages:
     dev: false
     optional: true
 
-  /@swc/core-win32-x64-msvc/1.2.174:
-    resolution: {integrity: sha512-9aFcbPTFA5jtiTQbOsq+/QN29F/YNpOrhcwsUa2Haqq0+2h71rMiBn7oluUERFQ+/se9J0C00iSC0QXif4pfuQ==}
+  /@swc/core-win32-x64-msvc/1.2.183:
+    resolution: {integrity: sha512-oWiEDrUb4aRRc7y558KonVoFqmGXGKFvOj8ceiz4UVwoQCcwNX3djvjIf3zwkj+DwjKiHPFVwhENaRgMphthrA==}
     engines: {node: '>=10'}
     cpu: [x64]
     os: [win32]
@@ -7539,38 +7538,38 @@ packages:
     dev: false
     optional: true
 
-  /@swc/core/1.2.120:
-    resolution: {integrity: sha512-9bSN4ZdDQsAiKEdNT0lv/8o0/70xkGFieq/I4cKdA9dQibAtfagzRyLPkCm54XKqd9NwvI9neAfYydbesyY/nw==}
+  /@swc/core/1.2.183:
+    resolution: {integrity: sha512-RwAq9broU0YqoVJqPpTVxXFAj63+Fx1UWAtBsEqWyJQJc7RunuqNb2vMzp9X+5GeVr6yaa8qE7iKB99gp5enJg==}
     engines: {node: '>=10'}
-    dependencies:
-      '@node-rs/helper': 1.3.3
+    hasBin: true
     optionalDependencies:
-      '@swc/core-android-arm64': 1.2.174
-      '@swc/core-darwin-arm64': 1.2.174
-      '@swc/core-darwin-x64': 1.2.174
-      '@swc/core-freebsd-x64': 1.2.174
-      '@swc/core-linux-arm-gnueabihf': 1.2.174
-      '@swc/core-linux-arm64-gnu': 1.2.174
-      '@swc/core-linux-arm64-musl': 1.2.174
-      '@swc/core-linux-x64-gnu': 1.2.174
-      '@swc/core-linux-x64-musl': 1.2.174
-      '@swc/core-win32-arm64-msvc': 1.2.174
-      '@swc/core-win32-ia32-msvc': 1.2.174
-      '@swc/core-win32-x64-msvc': 1.2.174
+      '@swc/core-android-arm-eabi': 1.2.183
+      '@swc/core-android-arm64': 1.2.183
+      '@swc/core-darwin-arm64': 1.2.183
+      '@swc/core-darwin-x64': 1.2.183
+      '@swc/core-freebsd-x64': 1.2.183
+      '@swc/core-linux-arm-gnueabihf': 1.2.183
+      '@swc/core-linux-arm64-gnu': 1.2.183
+      '@swc/core-linux-arm64-musl': 1.2.183
+      '@swc/core-linux-x64-gnu': 1.2.183
+      '@swc/core-linux-x64-musl': 1.2.183
+      '@swc/core-win32-arm64-msvc': 1.2.183
+      '@swc/core-win32-ia32-msvc': 1.2.183
+      '@swc/core-win32-x64-msvc': 1.2.183
     dev: false
 
   /@swc/helpers/0.3.10:
     resolution: {integrity: sha512-ZCioFJz6NcLAA4zQqN6BORE/SFu2tBdwRU3JXQgLBu6JETvFElAOpaP0HYjWpbnCHFEJ5o6c4wWT8QeLbRoe2w==}
     dev: false
 
-  /@swc/jest/0.2.15_@swc+core@1.2.120:
+  /@swc/jest/0.2.15_@swc+core@1.2.183:
     resolution: {integrity: sha512-Ja+YCVPOGPX/nFnPvPKaWpx2HUwrtuvHP1onHOey1gNGhq8CktQ7xQhCFSuyYBQJiW3WOZ+qMsv3aZk1XUBSOg==}
     engines: {npm: '>= 7.0.0'}
     peerDependencies:
       '@swc/core': '*'
     dependencies:
       '@jest/create-cache-key-function': 27.5.1
-      '@swc/core': 1.2.120
+      '@swc/core': 1.2.183
     dev: false
 
   /@szmarczak/http-timer/1.1.2:
diff --git a/themes/theme-b2b/src/component.ts b/themes/theme-b2b/src/component.ts
deleted file mode 100644
index 7fa8d7888..000000000
--- a/themes/theme-b2b/src/component.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-export const component = {
-  dialog: {
-    __default: {
-      bg: 'background',
-      minWidth: 510,
-      minHeight: 240,
-    },
-    backdrop: {
-      bg: '#00000080',
-    },
-  },
-  label: {
-    above: {
-      fontSize: 'xxsmall',
-      color: 'text',
-    },
-    inline: {
-      fontSize: 'xsmall',
-      display: 'inline-flex',
-      alignItems: 'center',
-    },
-    section: {
-      fontSize: 'small',
-      lineHeight: 'large',
-      color: 'gray50',
-    },
-  },
-  tooltip: {
-    __default: {
-      p: 'xsmall',
-      color: 'gray90',
-      fontSize: 14,
-      border: '1px solid',
-      borderColor: 'blue70',
-      borderRadius: 'large',
-      bg: 'blue10',
-    },
-  },
-  validation: {
-    error: {
-      fontSize: 'xxsmall',
-      color: 'error',
-    },
-  },
-} as const;
diff --git a/themes/theme-b2b/src/components/Tooltip.style.ts b/themes/theme-b2b/src/components/Tooltip.style.ts
new file mode 100644
index 000000000..4e8fc12ec
--- /dev/null
+++ b/themes/theme-b2b/src/components/Tooltip.style.ts
@@ -0,0 +1,92 @@
+import { Theme } from '@marigold/components';
+
+const arrowSize = 8;
+
+export const Tooltip: Theme['components']['Tooltip'] = {
+  base: {
+    container: {
+      fontSize: 'xxsmall',
+      lineHeight: 'small',
+
+      border: '1px solid',
+      borderColor: 'blue70',
+      borderRadius: 'large',
+      bg: 'blue10',
+
+      p: 'xsmall',
+
+      '&[data-placement="top"]': {
+        mb: arrowSize,
+      },
+
+      '&[data-placement="right"]': {
+        ml: arrowSize,
+      },
+
+      '&[data-placement="bottom"]': {
+        mt: arrowSize,
+      },
+
+      '&[data-placement="left"]': {
+        mr: arrowSize,
+      },
+    },
+
+    arrow: {
+      borderWidth: arrowSize,
+      borderTopColor: 'blue70',
+
+      '::after': {
+        content: '""',
+        position: 'absolute',
+        height: 0,
+        width: 0,
+
+        borderStyle: 'solid',
+        borderLeftColor: 'transparent',
+        borderRightColor: 'transparent',
+        borderBottomColor: 'transparent',
+
+        borderWidth: arrowSize - 1,
+        borderTopColor: 'blue10',
+
+        left: `-${arrowSize - 1}px`,
+        // weird but looks better
+        top: `-${arrowSize}.5px`,
+      },
+
+      '[data-placement="right"] &, [data-placement="left"] &': {
+        top: '50%',
+        mt: `-${arrowSize}px`,
+
+        '::after': {
+          // weird but looks better
+          top: `-${arrowSize}.5px`,
+        },
+      },
+
+      '[data-placement="top"] &, [data-placement="bottom"] &': {
+        ml: `-${arrowSize}px`,
+      },
+
+      '[data-placement="top"] & ': {
+        top: '100%',
+      },
+
+      '[data-placement="right"] &': {
+        right: '100%',
+        transform: 'rotate(90deg)',
+      },
+
+      '[data-placement="bottom"] & ': {
+        bottom: '100%',
+        transform: 'rotate(180deg)',
+      },
+
+      '[data-placement="left"] &': {
+        left: '100%',
+        transform: 'rotate(270deg)',
+      },
+    },
+  },
+};
diff --git a/themes/theme-b2b/src/components/index.ts b/themes/theme-b2b/src/components/index.ts
index 0ec56e5a1..05264d19e 100644
--- a/themes/theme-b2b/src/components/index.ts
+++ b/themes/theme-b2b/src/components/index.ts
@@ -22,4 +22,5 @@ export * from './Switch.style';
 export * from './Table.style';
 export * from './Text.style';
 export * from './TextArea.style';
+export * from './Tooltip.style';
 export * from './Underlay.style';
diff --git a/themes/theme-b2b/src/index.ts b/themes/theme-b2b/src/index.ts
index ddac194b4..b5b563747 100644
--- a/themes/theme-b2b/src/index.ts
+++ b/themes/theme-b2b/src/index.ts
@@ -1,7 +1,4 @@
 import { colors } from './colors';
-import { component } from './component';
-
-// NEW STYLES!
 import * as components from './components';
 
 export const webFontUrl = [
@@ -81,9 +78,6 @@ const theme: any = {
     xlarge: 2.5,
     xxlarge: 3,
   },
-  ...component,
-
-  // NEW STYLES!!!
   components,
 } as const;
 
diff --git a/themes/theme-core/src/component.ts b/themes/theme-core/src/component.ts
deleted file mode 100644
index 5d458b64b..000000000
--- a/themes/theme-core/src/component.ts
+++ /dev/null
@@ -1,49 +0,0 @@
-import { colors } from './colors';
-
-export const component = {
-  dialog: {
-    __default: {
-      bg: '#ffffff',
-      minWidth: '510px',
-      minHeight: '240px',
-    },
-    backdrop: {
-      bg: '#00000080',
-    },
-  },
-  label: {
-    above: {
-      fontSize: 'xxsmall',
-      lineHeight: 'small',
-      color: 'text',
-    },
-    inline: {
-      fontSize: 'xxsmall',
-      lineHeight: 'small',
-      display: 'inline-flex',
-      alignItems: 'center',
-    },
-    section: {
-      fontSize: 'xsmall',
-      lineHeight: '32px',
-      color: colors.gray50,
-    },
-  },
-  tooltip: {
-    __default: {
-      p: 'xsmall',
-      color: 'gray90',
-      fontSize: 14,
-      border: '1px solid',
-      borderColor: 'info',
-      borderRadius: 'large',
-      bg: 'blue10',
-    },
-  },
-  validation: {
-    error: {
-      fontSize: '0.813rem',
-      color: 'error',
-    },
-  },
-} as const;
diff --git a/themes/theme-core/src/index.ts b/themes/theme-core/src/index.ts
index 475fcf49f..70cbc6a05 100644
--- a/themes/theme-core/src/index.ts
+++ b/themes/theme-core/src/index.ts
@@ -1,8 +1,6 @@
-import { type Theme } from '@marigold/components';
-import { colors } from './colors';
-import { component } from './component';
+import type { Theme } from '@marigold/components';
 
-// NEW STYLES!
+import { colors } from './colors';
 import * as components from './components';
 
 const theme: Theme = {
@@ -79,9 +77,6 @@ const theme: Theme = {
     xlarge: 2.5,
     xxlarge: 3,
   },
-
-  ...component,
-  // NEW STYLES!!!
   components,
 } as const;
 
diff --git a/themes/theme-unicorn/src/component.ts b/themes/theme-unicorn/src/component.ts
deleted file mode 100644
index 1f868f127..000000000
--- a/themes/theme-unicorn/src/component.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-export const component = {
-  dialog: {
-    __default: {
-      bg: 'background',
-      minWidth: '400px',
-      minHeight: '400px',
-    },
-    backdrop: {
-      bg: '#00000080',
-    },
-  },
-  label: {
-    above: {
-      fontSize: 'xsmall',
-    },
-    inline: {
-      fontSize: 'xsmall',
-      display: 'inline-flex',
-      alignItems: 'center',
-    },
-    section: {
-      fontSize: 'small',
-      lineHeight: 'large',
-      color: 'gray50',
-    },
-  },
-  tooltip: {
-    __default: {
-      p: 'xsmall',
-      color: 'gray90',
-      border: '1px solid',
-      borderRadius: 'large',
-      bg: 'info',
-    },
-  },
-  validation: {
-    error: {
-      fontSize: 'xsmall',
-      textTransform: 'uppercase',
-      color: 'error',
-    },
-  },
-} as const;
diff --git a/themes/theme-unicorn/src/components/Tooltip.style.ts b/themes/theme-unicorn/src/components/Tooltip.style.ts
new file mode 100644
index 000000000..c9e3ba933
--- /dev/null
+++ b/themes/theme-unicorn/src/components/Tooltip.style.ts
@@ -0,0 +1,66 @@
+import { Theme } from '@marigold/components';
+
+const arrowSize = 6;
+
+export const Tooltip: Theme['components']['Tooltip'] = {
+  base: {
+    container: {
+      color: 'purple10',
+      fontSize: 'xxsmall',
+
+      borderRadius: 'huge',
+      bg: 'purple80',
+
+      p: 'xsmall',
+
+      '&[data-placement="top"]': {
+        mb: arrowSize,
+      },
+
+      '&[data-placement="right"]': {
+        ml: arrowSize,
+      },
+
+      '&[data-placement="bottom"]': {
+        mt: arrowSize,
+      },
+
+      '&[data-placement="left"]': {
+        mr: arrowSize,
+      },
+    },
+
+    arrow: {
+      borderWidth: arrowSize,
+      borderTopColor: 'purple80',
+
+      '[data-placement="right"] &, [data-placement="left"] &': {
+        top: '50%',
+        mt: `-${arrowSize}px`,
+      },
+
+      '[data-placement="top"] &, [data-placement="bottom"] &': {
+        ml: `-${arrowSize}px`,
+      },
+
+      '[data-placement="top"] & ': {
+        top: '100%',
+      },
+
+      '[data-placement="right"] &': {
+        right: '100%',
+        transform: 'rotate(90deg)',
+      },
+
+      '[data-placement="bottom"] & ': {
+        bottom: '100%',
+        transform: 'rotate(180deg)',
+      },
+
+      '[data-placement="left"] &': {
+        left: '100%',
+        transform: 'rotate(270deg)',
+      },
+    },
+  },
+};
diff --git a/themes/theme-unicorn/src/components/index.ts b/themes/theme-unicorn/src/components/index.ts
index 0ec56e5a1..05264d19e 100644
--- a/themes/theme-unicorn/src/components/index.ts
+++ b/themes/theme-unicorn/src/components/index.ts
@@ -22,4 +22,5 @@ export * from './Switch.style';
 export * from './Table.style';
 export * from './Text.style';
 export * from './TextArea.style';
+export * from './Tooltip.style';
 export * from './Underlay.style';
diff --git a/themes/theme-unicorn/src/index.ts b/themes/theme-unicorn/src/index.ts
index 7476f506d..c85a519b0 100644
--- a/themes/theme-unicorn/src/index.ts
+++ b/themes/theme-unicorn/src/index.ts
@@ -1,8 +1,6 @@
-import { type Theme } from '@marigold/components';
-import { colors } from './colors';
-import { component } from './component';
+import type { Theme } from '@marigold/components';
 
-// NEW STYLES!
+import { colors } from './colors';
 import * as components from './components';
 
 export const webFontUrl = [
@@ -56,6 +54,7 @@ const theme: Theme = {
     small: 2,
     medium: 4,
     large: 8,
+    huge: 16,
   },
   fonts: {
     body: 'Arial',
@@ -82,9 +81,6 @@ const theme: Theme = {
     xlarge: 2.5,
     xxlarge: 3,
   },
-  ...component,
-
-  // NEW STYLES!!!
   components,
 } as const;
 


Test patch:
diff --git a/packages/components/src/Tooltip/Tooltip.test.tsx b/packages/components/src/Tooltip/Tooltip.test.tsx
index fa48c671a..6d88a5fb3 100644
--- a/packages/components/src/Tooltip/Tooltip.test.tsx
+++ b/packages/components/src/Tooltip/Tooltip.test.tsx
@@ -1,74 +1,194 @@
 import React from 'react';
-import { render, screen } from '@testing-library/react';
+import { fireEvent, render, screen, waitFor } from '@testing-library/react';
 import { ThemeProvider } from '@marigold/system';
+
+import { Button } from '../Button';
 import { Tooltip } from './Tooltip';
-import { TooltipTrigger } from '.';
-import { Button } from '..';
 
 const theme = {
+  fontSizes: {
+    'small-1': '12px',
+    'medium-1': '16px',
+  },
   colors: {
-    primary: 'hotpink',
-    secondary: 'green',
+    green: '#69db7c',
+    lime: '#94d82d',
   },
-  tooltip: {
-    __default: {
-      bg: 'primary',
-    },
-    styled: {
-      bg: 'secondary',
+  components: {
+    Tooltip: {
+      base: {
+        container: {
+          fontSize: 'small-1',
+          bg: 'green',
+        },
+        arrow: {
+          borderTopColor: 'green',
+        },
+      },
+      variant: {
+        lime: {
+          container: {
+            bg: 'lime',
+          },
+        },
+      },
+      size: {
+        medium: {
+          container: {
+            fontSize: 'medium-1',
+          },
+        },
+      },
     },
   },
 };
 
-test('supports default variant', async () => {
+beforeEach(() => {
+  // by firing an event at the beginning of each test, we can put ourselves into
+  // keyboard modality for the test
+  fireEvent.keyDown(document.body, { key: 'Tab' });
+  fireEvent.keyUp(document.body, { key: 'Tab' });
+});
+
+test('does not render tooltip by default', () => {
+  render(
+    <Tooltip.Trigger>
+      <Button>Button!</Button>
+      <Tooltip>Look at this tooltip!</Tooltip>
+    </Tooltip.Trigger>
+  );
+
+  expect(screen.queryByText('Look at this tooltip!')).toBeNull();
+});
+
+test('shows tooltip on focus', () => {
+  render(
+    <Tooltip.Trigger>
+      <Button>Button!</Button>
+      <Tooltip>Look at this tooltip!</Tooltip>
+    </Tooltip.Trigger>
+  );
+
+  const button = screen.getByText('Button!');
+  fireEvent.focus(button);
+
+  expect(screen.getByRole('tooltip')).toBeVisible();
+
+  fireEvent.blur(button);
+
+  expect(screen.queryByText('Look at this tooltip!')).toBeNull();
+});
+
+test('shows tooltip on hover', async () => {
+  render(
+    <Tooltip.Trigger delay={0}>
+      <Button>Button!</Button>
+      <Tooltip>Look at this tooltip!</Tooltip>
+    </Tooltip.Trigger>
+  );
+  // Switch to "mouse mode"
+  fireEvent.mouseDown(document.body);
+  fireEvent.mouseUp(document.body);
+
+  const button = screen.getByText('Button!');
+
+  fireEvent.mouseEnter(button);
+  fireEvent.mouseMove(button);
+
+  expect(screen.getByRole('tooltip')).toBeVisible();
+
+  fireEvent.mouseLeave(button);
+
+  await waitFor(() =>
+    expect(screen.queryByText('Look at this tooltip!')).toBeNull()
+  );
+});
+
+test('can be disabled', () => {
+  render(
+    <Tooltip.Trigger disabled>
+      <Button>Button!</Button>
+      <Tooltip>Look at this tooltip!</Tooltip>
+    </Tooltip.Trigger>
+  );
+
+  const button = screen.getByText('Button!');
+  fireEvent.focus(button);
+
+  expect(screen.queryByText('Look at this tooltip!')).toBeNull();
+});
+
+test('can be opened programatically', () => {
+  render(
+    <Tooltip.Trigger open>
+      <Button>Button!</Button>
+      <Tooltip>Look at this tooltip!</Tooltip>
+    </Tooltip.Trigger>
+  );
+  expect(screen.queryByText('Look at this tooltip!')).toBeVisible();
+});
+
+test('allows to change tooltip placement', () => {
+  // Note: There is no real way to test this without actually rendering the tooltip
+  render(
+    <Tooltip.Trigger placement="bottom">
+      <Button>Button!</Button>
+      <Tooltip>Look at this tooltip!</Tooltip>
+    </Tooltip.Trigger>
+  );
+
+  const button = screen.getByText('Button!');
+  fireEvent.focus(button);
+
+  expect(screen.getByRole('tooltip')).toBeVisible();
+});
+
+test('styled via "Tooltip" from theme', () => {
   render(
     <ThemeProvider theme={theme}>
-      <TooltipTrigger>
-        <Button data-testid="button">Info</Button>
-        <Tooltip data-testid="tooltip">Tooltip</Tooltip>
-      </TooltipTrigger>
+      <Tooltip.Trigger open>
+        <Button>Button!</Button>
+        <Tooltip>Look at this tooltip!</Tooltip>
+      </Tooltip.Trigger>
     </ThemeProvider>
   );
-  const button = screen.getByTestId('button');
-  button.focus();
-  expect(button).toHaveFocus();
 
-  const tooltip = screen.getAllByTestId(/tooltip/);
-  expect(tooltip[1]).toHaveStyle(`background-color: hotpink`);
+  const tooltip = screen.getByRole('tooltip');
+  expect(tooltip).toHaveStyle({
+    fontSize: theme.fontSizes['small-1'],
+    backgroundColor: theme.colors.green,
+  });
 });
 
-test('supports other variant than default', async () => {
+test('accepts variant and size', () => {
   render(
     <ThemeProvider theme={theme}>
-      <TooltipTrigger>
-        <Button data-testid="button">Info</Button>
-        <Tooltip variant="styled" data-testid="tooltip">
-          Tooltip
+      <Tooltip.Trigger open>
+        <Button>Button!</Button>
+        <Tooltip variant="lime" size="medium">
+          Look at this tooltip!
         </Tooltip>
-      </TooltipTrigger>
+      </Tooltip.Trigger>
     </ThemeProvider>
   );
-  const button = screen.getByTestId('button');
-  button.focus();
-  expect(button).toHaveFocus();
 
-  const tooltip = screen.getAllByTestId(/tooltip/);
-  expect(tooltip[1]).toHaveStyle(`background-color: green`);
+  const tooltip = screen.getByRole('tooltip');
+  expect(tooltip).toHaveStyle({
+    fontSize: theme.fontSizes['medium-1'],
+    backgroundColor: theme.colors.lime,
+  });
 });
 
-test('shows Tooltip', async () => {
+test('sets placement as data attribute for styling', () => {
   render(
     <ThemeProvider theme={theme}>
-      <TooltipTrigger>
-        <Button data-testid="button">Info</Button>
-        <Tooltip>Tooltip</Tooltip>
-      </TooltipTrigger>
+      <Tooltip.Trigger open placement="left">
+        <Button>Button!</Button>
+        <Tooltip>Look at this tooltip!</Tooltip>
+      </Tooltip.Trigger>
     </ThemeProvider>
   );
-  const button = screen.getByTestId('button');
-  button.focus();
-  expect(button).toHaveFocus();
 
-  const tooltip = screen.getByText(/Tooltip/);
-  expect(tooltip).toBeDefined();
+  const tooltip = screen.getByRole('tooltip');
+  expect(tooltip).toHaveAttribute('data-placement', 'left');
 });