diff --git a/packages/Overview.mdx b/packages/Overview.mdx index 8ca21c3b60..6a2b93bcac 100644 --- a/packages/Overview.mdx +++ b/packages/Overview.mdx @@ -13,38 +13,38 @@ Noen av de nåværende komponentene i biblioteket er fra Altinn. Vi jobber nå m V1 er klar når følgende komponenter er markert som "✅ Felles": -| Komponent | Status | Design | Specification | -| :-------------------------------------------------- | :--------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------ | :-------------------------------------------------------------------------- | -| [Accordion](/docs/felles-accordion--docs) | ✅ Felles | [Figma - Accordion](https://www.figma.com/file/vpM9dqqQPHqU6ogfKp5tlr/Felles-komponenter?node-id=13134%3A99489&t=q8n4mcOmllKbfppi-1) | [Github - Accordion](https://github.com/digdir/designsystem/issues/75) | -| [Alert](/docs/felles-alert--docs) | ✅ Felles | [Figma - Alert](https://www.figma.com/file/vpM9dqqQPHqU6ogfKp5tlr/Felles-komponenter?node-id=6801%3A29775&t=q8n4mcOmllKbfppi-1) | [Github - Alert](https://github.com/digdir/designsystem/issues/78) | -| Autocomplete | 🚸 Ikke påbegynt | Figma - Autocomplete | [Github - Autocomplete](https://github.com/digdir/designsystem/issues/525) | -| [Button](/docs/altinn-button--docs) | 🔵 Altinn | [Figma - Button](https://www.figma.com/file/vpM9dqqQPHqU6ogfKp5tlr/Felles-komponenter?type=design&node-id=16157%3A48501&mode=design&t=qezsXvNE3xBWJlKj-1) | [Github - Button](https://github.com/digdir/designsystem/issues/315) | -| [Checkbox](/docs/felles-checkbox--docs) | ✅ Felles | [Figma - Checkbox](https://www.figma.com/file/vpM9dqqQPHqU6ogfKp5tlr/Felles-komponenter?node-id=6632%3A21157&t=q8n4mcOmllKbfppi-1) | [Github - Checkbox](https://github.com/digdir/designsystem/issues/316) | -| Card | 🚸 Ikke påbegynt | Figma - Card | [Github - Card](https://github.com/digdir/designsystem/issues/317) | -| [Chip](/docs/felles-chip--docs) | ✅ Felles | [Figma - Chip](https://www.figma.com/file/vpM9dqqQPHqU6ogfKp5tlr/Felles-komponenter?node-id=7882%3A46679&t=q8n4mcOmllKbfppi-1) | [Github - Chip](https://github.com/digdir/designsystem/issues/49) | -| DropdownMenu | 🚸 Ikke påbegynt | Figma - DropdownMenu | [Github - DropdownMenu](https://github.com/digdir/designsystem/issues/219) | -| [Fieldset](/docs/felles-fieldset--docs) | ✅ Felles | [Figma - Fieldset](https://www.figma.com/file/vpM9dqqQPHqU6ogfKp5tlr/Felles-komponenter?type=design&node-id=16120%3A49532&mode=design&t=qezsXvNE3xBWJlKj-1) | [Github - Fieldset](https://github.com/digdir/designsystem/issues/318) | -| Header | 🚸 Ikke påbegynt | Figma - Header | [Github - Header](https://github.com/digdir/designsystem/issues/216) | -| [HelpText](/docs/altinn-helptext--docs) | 🔵 Altinn | [Figma - HelpText](https://www.figma.com/file/vpM9dqqQPHqU6ogfKp5tlr/Felles-komponenter?node-id=10351%3A59384&t=7Q2N4sUdQGhFZrPh-1) | [Github - HelpText](https://github.com/digdir/designsystem/issues/29) | -| [Link](/docs/felles-link--docs) | ✅ Felles | [Figma - Link](https://www.figma.com/file/vpM9dqqQPHqU6ogfKp5tlr/Felles-komponenter?type=design&node-id=12585%3A103505&mode=design&t=qezsXvNE3xBWJlKj-1) | [Github - Link](https://github.com/digdir/designsystem/issues/62) | -| [List](/docs/altinn-list--docs) | 🔵 Altinn | Figma - List | [Github - List](https://github.com/digdir/designsystem/issues/80) | -| Loader ([Spinner](/docs/altinn-spinner--docs)) | 🔵 Altinn | [Figma - Loader](https://www.figma.com/file/vpM9dqqQPHqU6ogfKp5tlr/Felles-komponenter?node-id=9104%3A49626&t=7Q2N4sUdQGhFZrPh-1) | [Github - Loader](https://github.com/digdir/designsystem/issues/81) | -| Modal | 🚸 Ikke påbegynt | Figma - Modal | [Github - Modal](https://github.com/digdir/designsystem/issues/82) | -| [Native Select](/docs/altinn-nativeselect--docs) | 🔵 Altinn | [Figma - Native Select](https://www.figma.com/file/vpM9dqqQPHqU6ogfKp5tlr/Felles-komponenter?node-id=7538%3A45657&t=7Q2N4sUdQGhFZrPh-1) | [Github - Native Select](https://github.com/digdir/designsystem/issues/482) | -| [Pagination](/docs/felles-pagination--docs) | ✅ Felles | [Figma - Pagination](https://www.figma.com/file/vpM9dqqQPHqU6ogfKp5tlr/Felles-komponenter?type=design&node-id=16577%3A54916&mode=design&t=qezsXvNE3xBWJlKj-1) | [Github - Pagination](https://github.com/digdir/designsystem/issues/83) | -| [Popover](/docs/altinn-popover--docs) | 🔵 Altinn | [Figma - Popover](https://www.figma.com/file/vpM9dqqQPHqU6ogfKp5tlr/Felles-komponenter?type=design&node-id=10702%3A68901&t=Rlfq5UyNZBL69dFr-1) | [Github - Popover](https://github.com/digdir/designsystem/issues/60) | -| [Radio](/docs/felles-radio--docs) | ✅ Felles | [Figma - Radio](https://www.figma.com/file/vpM9dqqQPHqU6ogfKp5tlr/Felles-komponenter?type=design&node-id=16241%3A53787&mode=design&t=qezsXvNE3xBWJlKj-1) | [Github - Radio](https://github.com/digdir/designsystem/issues/86) | -| [Select](/docs/altinn-select-flervalgsmeny--docs) | 🔵 Altinn | [Figma - Select](https://www.figma.com/file/vpM9dqqQPHqU6ogfKp5tlr/Felles-komponenter?node-id=7538%3A45657&t=7Q2N4sUdQGhFZrPh-1) | [Github - Select](https://github.com/digdir/designsystem/issues/320) | -| Search | 🚸 Ikke påbegynt | Figma - Search | [Github - Search](https://github.com/digdir/designsystem/issues/88) | -| [Switch](/docs/felles-switch--docs) | ✅ Felles | [Figma - Switch](https://www.figma.com/file/vpM9dqqQPHqU6ogfKp5tlr/Felles-komponenter?type=design&node-id=17755-6024&mode=design&t=ztPUyfbDSQjlpEzv-0) | [Github - Switch](https://github.com/digdir/designsystem/issues/89) | -| [Table](/docs/altinn-table--docs) | 🔵 Altinn | Figma - Table | [Github - Table](https://github.com/digdir/designsystem/issues/90) | -| [Tabs](/docs/felles-tabs--docs) | ✅ Felles | [Figma - Tabs](https://www.figma.com/file/vpM9dqqQPHqU6ogfKp5tlr/Felles-komponenter?type=design&node-id=9551%3A54208&t=Rlfq5UyNZBL69dFr-1) | [Github - Tabs](https://github.com/digdir/designsystem/issues/91) | -| [Tag](/docs/felles-tag--docs) | ✅ Felles | [Figma - Tag](https://www.figma.com/file/vpM9dqqQPHqU6ogfKp5tlr/Felles-komponenter?node-id=10185%3A59053&t=7Q2N4sUdQGhFZrPh-1) | [Github - Tag](https://github.com/digdir/designsystem/issues/322) | -| [Textarea](/docs/felles-textarea--docs) | ✅ Felles | [Figma - Text area](https://www.figma.com/file/vpM9dqqQPHqU6ogfKp5tlr/Felles-komponenter?node-id=6632%3A21873&t=7Q2N4sUdQGhFZrPh-1) | [Github - Textarea](https://github.com/digdir/designsystem/issues/323) | -| [Textfield](/docs/felles-textfield--docs) | ✅ Felles | [Figma - Text Field](https://www.figma.com/file/vpM9dqqQPHqU6ogfKp5tlr/Felles-komponenter?node-id=6632%3A22228&t=7Q2N4sUdQGhFZrPh-1) | [Github - Textfield](https://github.com/digdir/designsystem/issues/92) | -| [ToggleGroup](/docs/felles-togglegroup--docs) | ✅ Felles | Figma - Toggle Group | [Github - ToggleGroup](https://github.com/digdir/designsystem/issues/304) | -| Tooltip | 🚸 Ikke påbegynt | Figma - Tooltip | [Github - Tooltip](https://github.com/digdir/designsystem/issues/93) | -| [Typography](/docs/felles-typography--docs) | ✅ Felles | [Figma - Typography](https://www.figma.com/file/vpM9dqqQPHqU6ogfKp5tlr/Felles-komponenter?node-id=9219%3A49405&t=7Q2N4sUdQGhFZrPh-1) | [Github - Typography](https://github.com/digdir/designsystem/issues/324) | +| Komponent | Status | Design | Specification | +| :------------------------------------------------ | :--------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------ | :-------------------------------------------------------------------------- | +| [Accordion](/docs/felles-accordion--docs) | ✅ Felles | [Figma - Accordion](https://www.figma.com/file/vpM9dqqQPHqU6ogfKp5tlr/Felles-komponenter?node-id=13134%3A99489&t=q8n4mcOmllKbfppi-1) | [Github - Accordion](https://github.com/digdir/designsystem/issues/75) | +| [Alert](/docs/felles-alert--docs) | ✅ Felles | [Figma - Alert](https://www.figma.com/file/vpM9dqqQPHqU6ogfKp5tlr/Felles-komponenter?node-id=6801%3A29775&t=q8n4mcOmllKbfppi-1) | [Github - Alert](https://github.com/digdir/designsystem/issues/78) | +| Autocomplete | 🚸 Ikke påbegynt | Figma - Autocomplete | [Github - Autocomplete](https://github.com/digdir/designsystem/issues/525) | +| [Button](/docs/altinn-button--docs) | 🔵 Altinn | [Figma - Button](https://www.figma.com/file/vpM9dqqQPHqU6ogfKp5tlr/Felles-komponenter?type=design&node-id=16157%3A48501&mode=design&t=qezsXvNE3xBWJlKj-1) | [Github - Button](https://github.com/digdir/designsystem/issues/315) | +| [Checkbox](/docs/felles-checkbox--docs) | ✅ Felles | [Figma - Checkbox](https://www.figma.com/file/vpM9dqqQPHqU6ogfKp5tlr/Felles-komponenter?node-id=6632%3A21157&t=q8n4mcOmllKbfppi-1) | [Github - Checkbox](https://github.com/digdir/designsystem/issues/316) | +| Card | 🚸 Ikke påbegynt | Figma - Card | [Github - Card](https://github.com/digdir/designsystem/issues/317) | +| [Chip](/docs/felles-chip--docs) | ✅ Felles | [Figma - Chip](https://www.figma.com/file/vpM9dqqQPHqU6ogfKp5tlr/Felles-komponenter?node-id=7882%3A46679&t=q8n4mcOmllKbfppi-1) | [Github - Chip](https://github.com/digdir/designsystem/issues/49) | +| DropdownMenu | 🚸 Ikke påbegynt | Figma - DropdownMenu | [Github - DropdownMenu](https://github.com/digdir/designsystem/issues/219) | +| [Fieldset](/docs/felles-fieldset--docs) | ✅ Felles | [Figma - Fieldset](https://www.figma.com/file/vpM9dqqQPHqU6ogfKp5tlr/Felles-komponenter?type=design&node-id=16120%3A49532&mode=design&t=qezsXvNE3xBWJlKj-1) | [Github - Fieldset](https://github.com/digdir/designsystem/issues/318) | +| Header | 🚸 Ikke påbegynt | Figma - Header | [Github - Header](https://github.com/digdir/designsystem/issues/216) | +| [HelpText](/docs/altinn-helptext--docs) | 🔵 Altinn | [Figma - HelpText](https://www.figma.com/file/vpM9dqqQPHqU6ogfKp5tlr/Felles-komponenter?node-id=10351%3A59384&t=7Q2N4sUdQGhFZrPh-1) | [Github - HelpText](https://github.com/digdir/designsystem/issues/29) | +| [Link](/docs/felles-link--docs) | ✅ Felles | [Figma - Link](https://www.figma.com/file/vpM9dqqQPHqU6ogfKp5tlr/Felles-komponenter?type=design&node-id=12585%3A103505&mode=design&t=qezsXvNE3xBWJlKj-1) | [Github - Link](https://github.com/digdir/designsystem/issues/62) | +| [List](/docs/altinn-list--docs) | 🔵 Altinn | Figma - List | [Github - List](https://github.com/digdir/designsystem/issues/80) | +| Loader ([Spinner](/docs/altinn-spinner--docs)) | 🔵 Altinn | [Figma - Loader](https://www.figma.com/file/vpM9dqqQPHqU6ogfKp5tlr/Felles-komponenter?node-id=9104%3A49626&t=7Q2N4sUdQGhFZrPh-1) | [Github - Loader](https://github.com/digdir/designsystem/issues/81) | +| Modal | 🚸 Ikke påbegynt | Figma - Modal | [Github - Modal](https://github.com/digdir/designsystem/issues/82) | +| [Native Select](/docs/altinn-nativeselect--docs) | 🔵 Altinn | [Figma - Native Select](https://www.figma.com/file/vpM9dqqQPHqU6ogfKp5tlr/Felles-komponenter?node-id=7538%3A45657&t=7Q2N4sUdQGhFZrPh-1) | [Github - Native Select](https://github.com/digdir/designsystem/issues/482) | +| [Pagination](/docs/felles-pagination--docs) | ✅ Felles | [Figma - Pagination](https://www.figma.com/file/vpM9dqqQPHqU6ogfKp5tlr/Felles-komponenter?type=design&node-id=16577%3A54916&mode=design&t=qezsXvNE3xBWJlKj-1) | [Github - Pagination](https://github.com/digdir/designsystem/issues/83) | +| [Popover](/docs/altinn-popover--docs) | 🔵 Altinn | [Figma - Popover](https://www.figma.com/file/vpM9dqqQPHqU6ogfKp5tlr/Felles-komponenter?type=design&node-id=10702%3A68901&t=Rlfq5UyNZBL69dFr-1) | [Github - Popover](https://github.com/digdir/designsystem/issues/60) | +| [Radio](/docs/felles-radio--docs) | ✅ Felles | [Figma - Radio](https://www.figma.com/file/vpM9dqqQPHqU6ogfKp5tlr/Felles-komponenter?type=design&node-id=16241%3A53787&mode=design&t=qezsXvNE3xBWJlKj-1) | [Github - Radio](https://github.com/digdir/designsystem/issues/86) | +| [Select](/docs/altinn-select-flervalgsmeny--docs) | 🔵 Altinn | [Figma - Select](https://www.figma.com/file/vpM9dqqQPHqU6ogfKp5tlr/Felles-komponenter?node-id=7538%3A45657&t=7Q2N4sUdQGhFZrPh-1) | [Github - Select](https://github.com/digdir/designsystem/issues/320) | +| Search | 🚸 Ikke påbegynt | Figma - Search | [Github - Search](https://github.com/digdir/designsystem/issues/88) | +| [Switch](/docs/felles-switch--docs) | ✅ Felles | [Figma - Switch](https://www.figma.com/file/vpM9dqqQPHqU6ogfKp5tlr/Felles-komponenter?type=design&node-id=17755-6024&mode=design&t=ztPUyfbDSQjlpEzv-0) | [Github - Switch](https://github.com/digdir/designsystem/issues/89) | +| [Table](/docs/altinn-table--docs) | 🔵 Altinn | Figma - Table | [Github - Table](https://github.com/digdir/designsystem/issues/90) | +| [Tabs](/docs/felles-tabs--docs) | ✅ Felles | [Figma - Tabs](https://www.figma.com/file/vpM9dqqQPHqU6ogfKp5tlr/Felles-komponenter?type=design&node-id=9551%3A54208&t=Rlfq5UyNZBL69dFr-1) | [Github - Tabs](https://github.com/digdir/designsystem/issues/91) | +| [Tag](/docs/felles-tag--docs) | ✅ Felles | [Figma - Tag](https://www.figma.com/file/vpM9dqqQPHqU6ogfKp5tlr/Felles-komponenter?node-id=10185%3A59053&t=7Q2N4sUdQGhFZrPh-1) | [Github - Tag](https://github.com/digdir/designsystem/issues/322) | +| [Textarea](/docs/felles-textarea--docs) | ✅ Felles | [Figma - Text area](https://www.figma.com/file/vpM9dqqQPHqU6ogfKp5tlr/Felles-komponenter?node-id=6632%3A21873&t=7Q2N4sUdQGhFZrPh-1) | [Github - Textarea](https://github.com/digdir/designsystem/issues/323) | +| [Textfield](/docs/felles-textfield--docs) | ✅ Felles | [Figma - Text Field](https://www.figma.com/file/vpM9dqqQPHqU6ogfKp5tlr/Felles-komponenter?node-id=6632%3A22228&t=7Q2N4sUdQGhFZrPh-1) | [Github - Textfield](https://github.com/digdir/designsystem/issues/92) | +| [ToggleGroup](/docs/felles-togglegroup--docs) | ✅ Felles | Figma - Toggle Group | [Github - ToggleGroup](https://github.com/digdir/designsystem/issues/304) | +| [Tooltip](/docs/felles-tooltip--docs) | ✅ Felles | Figma - Tooltip | [Github - Tooltip](https://github.com/digdir/designsystem/issues/93) | +| [Typography](/docs/felles-typography--docs) | ✅ Felles | [Figma - Typography](https://www.figma.com/file/vpM9dqqQPHqU6ogfKp5tlr/Felles-komponenter?node-id=9219%3A49405&t=7Q2N4sUdQGhFZrPh-1) | [Github - Typography](https://github.com/digdir/designsystem/issues/324) | ## Planlagte komponenter i fremtiden diff --git a/packages/react/src/components/Tooltip/Tooltip.mdx b/packages/react/src/components/Tooltip/Tooltip.mdx new file mode 100644 index 0000000000..94b3d1353f --- /dev/null +++ b/packages/react/src/components/Tooltip/Tooltip.mdx @@ -0,0 +1,22 @@ +import { Meta, Canvas, Story, Controls, Primary } from '@storybook/blocks'; + +import * as TooltipStories from './Tooltip.stories.tsx'; + + + +# Tooltip + + + + +## Plassering + + + +## Åpen som standard + + + +## Avansert bruk + + diff --git a/packages/react/src/components/Tooltip/Tooltip.module.css b/packages/react/src/components/Tooltip/Tooltip.module.css new file mode 100644 index 0000000000..459985008e --- /dev/null +++ b/packages/react/src/components/Tooltip/Tooltip.module.css @@ -0,0 +1,7 @@ +.wrapper { + background: var(--fds-semantic-border-neutral-strong); + padding: var(--fds-spacing-1) var(--fds-spacing-2); + color: white; + border-radius: var(--fds-border_radius-medium); + font: var(--fds-typography-paragraph-xsmall); +} diff --git a/packages/react/src/components/Tooltip/Tooltip.stories.tsx b/packages/react/src/components/Tooltip/Tooltip.stories.tsx new file mode 100644 index 0000000000..f4ccd9ac70 --- /dev/null +++ b/packages/react/src/components/Tooltip/Tooltip.stories.tsx @@ -0,0 +1,62 @@ +import type { Meta, StoryObj, StoryFn } from '@storybook/react'; +import React from 'react'; + +import { Paragraph, Button } from '../..'; + +import { Tooltip } from '.'; + +type Story = StoryObj; + +const defaultChildren = ; +const decorators = [ + (Story: StoryFn) => ( +
+ +
+ ), +]; + +export default { + title: 'Felles/Tooltip', + component: Tooltip, + decorators, +} as Meta; + +export const Preview: Story = { + args: { + content: 'Tooltip text', + children: defaultChildren, + }, +}; + +export const Placement: Story = { + args: { + content: 'Tooltip text', + placement: 'bottom', + children: defaultChildren, + }, +}; + +export const DefaultOpen: Story = { + args: { + content: 'Tooltip text', + defaultOpen: true, + children: defaultChildren, + }, +}; + +export const Complex: StoryFn = () => { + return ( + + Du kan ha{' '} + + + tooltip + + {' '} + inne i tekst også + + ); +}; diff --git a/packages/react/src/components/Tooltip/Tooltip.test.tsx b/packages/react/src/components/Tooltip/Tooltip.test.tsx new file mode 100644 index 0000000000..788b0a8079 --- /dev/null +++ b/packages/react/src/components/Tooltip/Tooltip.test.tsx @@ -0,0 +1,96 @@ +import React from 'react'; +import userEvent from '@testing-library/user-event'; +import { act, render as renderRtl, screen } from '@testing-library/react'; + +import type { TooltipProps } from './Tooltip'; +import { Tooltip } from './Tooltip'; + +const render = async (props: Partial = {}) => { + const allProps: TooltipProps = { + children: , + content: 'Tooltip text', + ...props, + }; + /* Flush microtasks */ + await act(async () => {}); + const user = userEvent.setup(); + + return { + user, + ...renderRtl(), + }; +}; + +describe('Tooltip', () => { + describe('should render children', () => { + it('should render child', async () => { + await render(); + const tooltipTrigger = screen.getByRole('button', { name: 'My button' }); + + expect(tooltipTrigger).toBeInTheDocument(); + }); + }); + + describe('should render tooltip', () => { + it('should render tooltip on hover', async () => { + const { user } = await render(); + const tooltipTrigger = screen.getByRole('button', { name: 'My button' }); + + expect(screen.queryByRole('tooltip')).not.toBeInTheDocument(); + await act(async () => await user.hover(tooltipTrigger)); + const tooltip = await screen.findByText('Tooltip text'); + expect(tooltip).toBeInTheDocument(); + expect(screen.queryByRole('tooltip')).toBeInTheDocument(); + }); + + it('should render tooltip on focus', async () => { + await render(); + const tooltipTrigger = screen.getByRole('button', { name: 'My button' }); + + expect(screen.queryByText('Tooltip text')).not.toBeInTheDocument(); + act(() => tooltipTrigger.focus()); + const tooltip = await screen.findByText('Tooltip text'); + expect(tooltip).toBeInTheDocument(); + expect(screen.queryByRole('tooltip')).toBeInTheDocument(); + }); + + it('should close tooltip on escape', async () => { + const { user } = await render(); + const tooltipTrigger = screen.getByRole('button', { name: 'My button' }); + + expect(screen.queryByText('Tooltip text')).not.toBeInTheDocument(); + await act(async () => { + await user.hover(tooltipTrigger); + }); + const tooltip = await screen.findByText('Tooltip text'); + expect(tooltip).toBeInTheDocument(); + await act(async () => { + await user.keyboard('[Escape]'); + }); + expect(screen.queryByText('Tooltip text')).not.toBeInTheDocument(); + }); + }); + + it('should render open when we pass open prop', async () => { + await render({ open: true }); + const tooltipTrigger = screen.getByRole('button', { name: 'My button' }); + + expect(screen.getByRole('tooltip')).toBeInTheDocument(); + expect(tooltipTrigger).toHaveAttribute('aria-describedby'); + }); + + it('delay', async () => { + const user = userEvent.setup(); + + await render({ delay: 300 }); + + await act(async () => { + await user.hover(screen.getByRole('button')); + await new Promise((r) => setTimeout(r, 250)); + expect(screen.queryByRole('tooltip')).toBeNull(); + await new Promise((r) => setTimeout(r, 600)); + }); + + expect(screen.getByRole('tooltip')).toBeVisible(); + }); +}); diff --git a/packages/react/src/components/Tooltip/Tooltip.tsx b/packages/react/src/components/Tooltip/Tooltip.tsx new file mode 100644 index 0000000000..c765dbc097 --- /dev/null +++ b/packages/react/src/components/Tooltip/Tooltip.tsx @@ -0,0 +1,156 @@ +import type { HTMLAttributes } from 'react'; +import React, { cloneElement, forwardRef, useState } from 'react'; +import cn from 'classnames'; +import { + useFloating, + autoUpdate, + offset, + flip, + shift, + arrow, + useHover, + useFocus, + useDismiss, + useRole, + useInteractions, + useTransitionStyles, + useMergeRefs, + FloatingArrow, + FloatingPortal, +} from '@floating-ui/react'; + +import styles from './Tooltip.module.css'; + +const ARROW_HEIGHT = 7; +const ARROW_GAP = 4; + +export type TooltipProps = { + /** + * The element that triggers the tooltip. + * @note Needs to be a single ReactElement and not: | <> + */ + children: React.ReactElement & React.RefAttributes; + /** Content of the tooltip */ + content: string; + /** + * Placement of the tooltip on the trigger. + * @default 'top' + */ + placement?: 'top' | 'right' | 'bottom' | 'left'; + /** Delay in milliseconds before opening. + * @default 150 + */ + delay?: number; + /** Whether the tooltip is open or not. + * This overrides the internal state of the tooltip. + */ + open?: boolean; + /** Whether the tooltip is open by default or not. */ + defaultOpen?: boolean; +} & HTMLAttributes; + +export const Tooltip = forwardRef( + ( + { + children, + content, + placement = 'top', + delay = 150, + open: userOpen, + defaultOpen = false, + className, + ...restHTMLProps + }, + ref, + ) => { + const [isOpen, setIsOpen] = useState(defaultOpen); + + const arrowRef = React.useRef(null); + const internalOpen = userOpen ?? isOpen; + + const { refs, floatingStyles, context } = useFloating({ + open: internalOpen, + onOpenChange: setIsOpen, + placement, + whileElementsMounted: autoUpdate, + middleware: [ + offset(ARROW_HEIGHT + ARROW_GAP), + flip({ + fallbackAxisSideDirection: 'start', + }), + shift(), + arrow({ + element: arrowRef, + }), + ], + }); + + const { styles: animationStyles } = useTransitionStyles(context, { + initial: { + opacity: 0, + }, + }); + + const { getReferenceProps, getFloatingProps } = useInteractions([ + // Event listeners to change the open state + useHover(context, { move: false, delay }), + useFocus(context), + useDismiss(context), + useRole(context, { role: 'tooltip' }), + ]); + + const mergedRef = useMergeRefs([ref, refs.setFloating]); + + const childMergedRef = useMergeRefs([ + (children as React.ReactElement & React.RefAttributes) + .ref as React.MutableRefObject, + refs.setReference, + ]); + + if ( + !children || + children?.type === React.Fragment || + (children as unknown) === React.Fragment + ) { + console.error( + ' children needs to be a single ReactElement and not: | <>', + ); + return null; + } + + return ( + <> + {cloneElement( + children, + getReferenceProps({ + ref: childMergedRef, + }), + )} + + {internalOpen && ( + <> +
+ {content} + +
+ + )} +
+ + ); + }, +); diff --git a/packages/react/src/components/Tooltip/index.ts b/packages/react/src/components/Tooltip/index.ts new file mode 100644 index 0000000000..3c44c04bc4 --- /dev/null +++ b/packages/react/src/components/Tooltip/index.ts @@ -0,0 +1,2 @@ +export type { TooltipProps } from './Tooltip'; +export { Tooltip } from './Tooltip'; diff --git a/packages/react/src/components/index.ts b/packages/react/src/components/index.ts index 3e8ae3678e..fd321a30cf 100644 --- a/packages/react/src/components/index.ts +++ b/packages/react/src/components/index.ts @@ -23,6 +23,7 @@ export * from './Alert'; export * from './Tag'; export * from './Chip'; export * from './Pagination'; +export * from './Tooltip'; export * from './form/Checkbox'; export * from './form/Radio'; export * from './form/Fieldset';