diff --git a/packages/eui/src/components/tool_tip/__snapshots__/tool_tip.test.tsx.snap b/packages/eui/src/components/tool_tip/__snapshots__/tool_tip.test.tsx.snap index bb3474fead9..82532a709f8 100644 --- a/packages/eui/src/components/tool_tip/__snapshots__/tool_tip.test.tsx.snap +++ b/packages/eui/src/components/tool_tip/__snapshots__/tool_tip.test.tsx.snap @@ -17,6 +17,84 @@ exports[`EuiToolTip anchor props are rendered 1`] = ` `; +exports[`EuiToolTip delay prop none delay 1`] = ` + +
+ + + +
+
+ + +`; + +exports[`EuiToolTip delay prop short delay 1`] = ` + +
+ + + +
+
+ + +`; + exports[`EuiToolTip display prop renders block 1`] = `
`; +exports[`EuiToolTip transition prop fade transition 1`] = ` + +
+ + + +
+
+ + +`; + +exports[`EuiToolTip transition prop none transition 1`] = ` + +
+ + + +
+
+ + +`; + exports[`EuiToolTip uses custom offset prop value 1`] = ` = { args: { position: 'top', delay: 'regular', + transition: 'default', display: 'inlineBlock', // set up for easier testing/QA anchorClassName: '', @@ -72,6 +73,34 @@ export const Playground: Story = { }), }; +export const ShortDelay: Story = { + args: { + ...Playground.args, + delay: 'short', + }, +}; + +export const NoDelay: Story = { + args: { + ...Playground.args, + delay: 'none', + }, +}; + +export const FadeTransition: Story = { + args: { + ...Playground.args, + transition: 'fade', + }, +}; + +export const NoTransition: Story = { + args: { + ...Playground.args, + transition: 'none', + }, +}; + /** * VRT only stories */ diff --git a/packages/eui/src/components/tool_tip/tool_tip.styles.ts b/packages/eui/src/components/tool_tip/tool_tip.styles.ts index 5842ccb495c..b93f8d838a8 100644 --- a/packages/eui/src/components/tool_tip/tool_tip.styles.ts +++ b/packages/eui/src/components/tool_tip/tool_tip.styles.ts @@ -44,6 +44,16 @@ const euiToolTipAnimationHorizontal = (size: string) => keyframes` } `; +const euiToolTipAnimationFade = keyframes` + 0% { + opacity: 0; + } + + 100% { + opacity: 1; + } +`; + export const euiToolTipStyles = (euiThemeContext: UseEuiTheme) => { const { euiTheme, highContrastMode } = euiThemeContext; @@ -103,6 +113,31 @@ export const euiToolTipStyles = (euiThemeContext: UseEuiTheme) => { ${animationTiming}; } `, + // Fade transition positions (no movement, just opacity) + topFade: css` + ${euiCanAnimate} { + animation: ${euiToolTipAnimationFade} ${animationTiming}; + } + `, + bottomFade: css` + ${euiCanAnimate} { + animation: ${euiToolTipAnimationFade} ${animationTiming}; + } + `, + leftFade: css` + ${euiCanAnimate} { + animation: ${euiToolTipAnimationFade} ${animationTiming}; + } + `, + rightFade: css` + ${euiCanAnimate} { + animation: ${euiToolTipAnimationFade} ${animationTiming}; + } + `, + // No animation + noAnimation: css` + /* No animation - tooltip appears immediately */ + `, // Arrow euiToolTip__arrow: css` ${arrowStyles._arrowStyles} diff --git a/packages/eui/src/components/tool_tip/tool_tip.test.tsx b/packages/eui/src/components/tool_tip/tool_tip.test.tsx index 9b401fa5ead..4ac6d999f66 100644 --- a/packages/eui/src/components/tool_tip/tool_tip.test.tsx +++ b/packages/eui/src/components/tool_tip/tool_tip.test.tsx @@ -111,6 +111,58 @@ describe('EuiToolTip', () => { expect(container).toMatchSnapshot(); }); + describe('delay prop', () => { + test('short delay', async () => { + const { baseElement, getByTestSubject } = render( + + + + ); + + fireEvent.mouseOver(getByTestSubject('trigger')); + await waitForEuiToolTipVisible(); + expect(baseElement).toMatchSnapshot(); + }); + + test('none delay', async () => { + const { baseElement, getByTestSubject } = render( + + + + ); + + fireEvent.mouseOver(getByTestSubject('trigger')); + await waitForEuiToolTipVisible(); + expect(baseElement).toMatchSnapshot(); + }); + }); + + describe('transition prop', () => { + test('fade transition', async () => { + const { baseElement, getByTestSubject } = render( + + + + ); + + fireEvent.mouseOver(getByTestSubject('trigger')); + await waitForEuiToolTipVisible(); + expect(baseElement).toMatchSnapshot(); + }); + + test('none transition', async () => { + const { baseElement, getByTestSubject } = render( + + + + ); + + fireEvent.mouseOver(getByTestSubject('trigger')); + await waitForEuiToolTipVisible(); + expect(baseElement).toMatchSnapshot(); + }); + }); + describe('aria-describedby', () => { it('by default, sets an `aria-describedby` on the anchor when the tooltip is visible', async () => { const { getByTestSubject } = render( diff --git a/packages/eui/src/components/tool_tip/tool_tip.tsx b/packages/eui/src/components/tool_tip/tool_tip.tsx index 6655d9f368d..7b586d05336 100644 --- a/packages/eui/src/components/tool_tip/tool_tip.tsx +++ b/packages/eui/src/components/tool_tip/tool_tip.tsx @@ -36,12 +36,15 @@ import { toolTipManager } from './tool_tip_manager'; export const POSITIONS = ['top', 'right', 'bottom', 'left'] as const; const DISPLAYS = ['inlineBlock', 'block'] as const; -export type ToolTipDelay = 'regular' | 'long'; +export type ToolTipDelay = 'regular' | 'long' | 'short' | 'none'; +export type ToolTipTransition = 'default' | 'fade' | 'none'; export const DEFAULT_TOOLTIP_OFFSET = 16; const delayToMsMap: { [key in ToolTipDelay]: number } = { regular: 250, long: 250 * 5, + short: 100, + none: 0, }; interface ToolTipStyles { @@ -94,6 +97,13 @@ export interface EuiToolTipProps extends CommonProps { * Delay before showing tooltip. Good for repeatable items. */ delay: ToolTipDelay; + /** + * Type of transition for the tooltip appearance. + * - 'default': Uses the current behavior with movement + * - 'fade': Fades in without movement + * - 'none': Appears immediately without animation + */ + transition?: ToolTipTransition; /** * An optional title for your tooltip. */ @@ -175,6 +185,7 @@ export class EuiToolTip extends Component { static defaultProps: Partial = { position: 'top', delay: 'regular', + transition: 'default', display: 'inlineBlock', disableScreenReaderOutput: false, }; @@ -346,6 +357,7 @@ export class EuiToolTip extends Component { content, title, delay, + transition, display, repositionOnScroll, disableScreenReaderOutput = false, @@ -387,6 +399,7 @@ export class EuiToolTip extends Component { id={id} role="tooltip" calculatedPosition={calculatedPosition} + transition={transition} {...rest} > void; calculatedPosition?: ToolTipPositions; + transition?: ToolTipTransition; }; export const EuiToolTipPopover: FunctionComponent = ({ @@ -37,6 +39,7 @@ export const EuiToolTipPopover: FunctionComponent = ({ positionToolTip, popoverRef, calculatedPosition, + transition = 'default', ...rest }) => { const popover = useRef(); @@ -46,6 +49,10 @@ export const EuiToolTipPopover: FunctionComponent = ({ const cssStyles = [ styles.euiToolTip, calculatedPosition && styles[calculatedPosition], + transition === 'fade' && + calculatedPosition && + styles[`${calculatedPosition}Fade`], + transition === 'none' && styles.noAnimation, ]; const updateDimensions = useCallback(() => { diff --git a/packages/website/docs/components/display/tooltip.mdx b/packages/website/docs/components/display/tooltip.mdx index 6c9ac67e16f..aa8d5976454 100644 --- a/packages/website/docs/components/display/tooltip.mdx +++ b/packages/website/docs/components/display/tooltip.mdx @@ -76,6 +76,92 @@ export default () => ( ); ``` +## Delay options + +The `delay` prop controls how long to wait before showing the tooltip. This is useful for reducing visual noise for tooltips that appear in repeating components. + +- **`regular`** (default): 250ms delay +- **`long`**: 1250ms delay (5x regular) +- **`short`**: 100ms delay +- **`none`**: No delay (appears immediately) + +```tsx interactive +import React from 'react'; +import { EuiToolTip, EuiLink, EuiText, EuiSpacer } from '@elastic/eui'; + +export default () => ( + +

+ This tooltip has{' '} + + no delay + + . +

+

+ This tooltip has a{' '} + + short delay + + . +

+

+ This tooltip has a{' '} + + regular delay + + {' '}(default). +

+

+ This tooltip has a{' '} + + long delay + + . +

+
+); +``` + +## Transition options + +The `transition` prop controls how the tooltip animates when it appears. + +- **`default`**: Fades in with a subtle slide from the direction it's positioned +- **`fade`**: Fades in without any movement +- **`none`**: No animation, appears instantly + +```tsx interactive +import React from 'react'; +import { EuiToolTip, EuiLink, EuiText, EuiSpacer } from '@elastic/eui'; + +export default () => ( + +

+ This tooltip uses{' '} + + default transition + + . +

+

+ This tooltip uses{' '} + + fade transition + + . +

+

+ This tooltip has{' '} + + no transition + + . +

+
+); +``` + ## Wrapping components **EuiToolTip** wraps its children in a `` element that is `display: inline-block`. If you are wrapping a block-level child (e.g. a `
`), you may need to change this by passing `display="block"` but the resulting DOM may be in violation of the HTML5 spec.