diff --git a/.changeset/breezy-geese-hug.md b/.changeset/breezy-geese-hug.md new file mode 100644 index 0000000000..21a71346c7 --- /dev/null +++ b/.changeset/breezy-geese-hug.md @@ -0,0 +1,6 @@ +--- +'@contentful/f36-avatar': minor +'@contentful/f36-tooltip': minor +--- + +Tooltips can now render ReactElement as their Content diff --git a/packages/components/avatar/src/Avatar/Avatar.tsx b/packages/components/avatar/src/Avatar/Avatar.tsx index 77c1f56fcb..2b151737f1 100644 --- a/packages/components/avatar/src/Avatar/Avatar.tsx +++ b/packages/components/avatar/src/Avatar/Avatar.tsx @@ -3,7 +3,11 @@ import { cx } from 'emotion'; import { type CommonProps } from '@contentful/f36-core'; import { Image, type ImageProps } from '@contentful/f36-image'; -import { Tooltip, type TooltipProps } from '@contentful/f36-tooltip'; +import { + Tooltip, + type TooltipInternalProps, + type WithEnhancedContent, +} from '@contentful/f36-tooltip'; import { convertSizeToPixels, getAvatarStyles } from './Avatar.styles'; import type { ColorVariant } from './utils'; @@ -20,7 +24,9 @@ export interface AvatarProps extends CommonProps { /** * A tooltipProps attribute used to conditionally render the tooltip around root element */ - tooltipProps?: Omit; + tooltipProps?: CommonProps & + WithEnhancedContent & + Omit; variant?: Variant; colorVariant?: ColorVariant; icon?: React.ReactElement; diff --git a/packages/components/tooltip/README.mdx b/packages/components/tooltip/README.mdx index 993634be9a..63f9cdb34f 100644 --- a/packages/components/tooltip/README.mdx +++ b/packages/components/tooltip/README.mdx @@ -39,6 +39,8 @@ import { Tooltip } from '@contentful/f36-tooltip'; ## Content guidelines - Use short and clear messages as the `Tooltip`’s content +- The Tooltip component allows you to pass React elements as content. However, this should be used with care. ReactElement as content is best suited for text formatting purposes. It should not be used for interactive elements like links, buttons, or form elements. Additionally, extensive images should be avoided, except for text-decorator icons that give semantic meaning to the text shown in the tooltip (e.g., warning signs or other relevant icons). + - When using `ReactElement` as the content, it's recommended to use the `ScreenReaderOnly` component when displaying critical information. ## Accessibility diff --git a/packages/components/tooltip/src/Tooltip.test.tsx b/packages/components/tooltip/src/Tooltip.test.tsx index 461770ada0..7b3b1b89ff 100644 --- a/packages/components/tooltip/src/Tooltip.test.tsx +++ b/packages/components/tooltip/src/Tooltip.test.tsx @@ -4,6 +4,7 @@ import userEvent from '@testing-library/user-event'; import { axe } from '@/scripts/test/axeHelper'; import { Tooltip } from './Tooltip'; +import { Paragraph } from '@contentful/f36-typography'; jest.mock('@contentful/f36-core', () => { const actual = jest.requireActual('@contentful/f36-core'); @@ -126,4 +127,26 @@ describe('Tooltip', () => { expect(results).toHaveNoViolations(); }); + + it('render a React Element as children', async () => { + const user = userEvent.setup(); + + const { container } = render( + Ich bin ein Paragraph} + > + Hover me + , + ); + await user.hover(screen.getByText('Hover me')); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + expect(screen.getByRole('tooltip').textContent).toBe( + 'Ich bin ein Paragraph', + ); + }); }); diff --git a/packages/components/tooltip/src/Tooltip.tsx b/packages/components/tooltip/src/Tooltip.tsx index 2cfd3704cb..fbc54af410 100644 --- a/packages/components/tooltip/src/Tooltip.tsx +++ b/packages/components/tooltip/src/Tooltip.tsx @@ -5,6 +5,7 @@ import React, { type MouseEvent, type FocusEvent, type CSSProperties, + ReactElement, } from 'react'; import { usePopper } from 'react-popper'; import type { Placement } from '@popperjs/core'; @@ -18,7 +19,29 @@ import { getStyles } from './Tooltip.styles'; export type TooltipPlacement = Placement; -export interface TooltipProps extends CommonProps { +export type WithEnhancedContent = + | { + /** + * Content of the tooltip + */ + content: ReactElement; + /** + * Accesible label property, only required when using ReactElement as content + */ + label: string; + } + | { + /** + * Content of the tooltip + */ + content: string; + /** + * Accesible label property, only required when using ReactElement as content + */ + label?: string; + }; + +export type TooltipInternalProps = { /** * Child nodes to be rendered in the component and that will show the tooltip when they are hovered */ @@ -27,10 +50,6 @@ export interface TooltipProps extends CommonProps { * HTML element used to wrap the target of the tooltip */ as?: React.ElementType; - /** - * Content of the tooltip - */ - content?: string; /** * A unique id of the tooltip */ @@ -94,13 +113,18 @@ export interface TooltipProps extends CommonProps { * @default false */ isDisabled?: boolean; -} +}; + +export type TooltipProps = CommonProps & + TooltipInternalProps & + WithEnhancedContent; export const Tooltip = ({ children, className, as: HtmlTag = 'span', content, + label, id, isVisible = false, hideDelay = 0, @@ -210,7 +234,7 @@ export const Tooltip = ({ }} {...attributes.popper} > - {content} + {content} { ); }; + +export const WithReactElement = () => { + return ( + <> + + With React Elements as children + + + + + + I am a Heading + + + I am a paragraph + + + } + > + Hover me + + + + ); +}; diff --git a/packages/forma-36-codemod/transforms/v4-tooltip.js b/packages/forma-36-codemod/transforms/v4-tooltip.js index 92a3a7feb3..1d12240595 100644 --- a/packages/forma-36-codemod/transforms/v4-tooltip.js +++ b/packages/forma-36-codemod/transforms/v4-tooltip.js @@ -1,19 +1,7 @@ const { modifyPropsCodemod } = require('./common/modify-props-codemod'); -const { hasProperty, getProperty } = require('../utils'); module.exports = modifyPropsCodemod({ componentName: 'Tooltip', - beforeRename: (attributes) => { - if ( - hasProperty(attributes, { propertyName: 'content' }) && - typeof getProperty(attributes, { propertyName: 'content' } !== 'string') - ) { - console.error( - 'Value of property "content" on Tooltip component should be a string.', - ); - } - return attributes; - }, renameMap: { place: 'placement', containerElement: 'as',