UNPKG

@atlaskit/tooltip

Version:

A tooltip briefly describes an interactive element on mouse hover or keyboard focus.

227 lines (226 loc) • 11.1 kB
import { type ComponentType, type ReactNode } from 'react'; import type UIAnalyticsEvent from '@atlaskit/analytics-next/UIAnalyticsEvent'; import type { Placement } from '@atlaskit/popper/main'; import { type TooltipPrimitiveProps } from './tooltip-primitive'; export type PositionMouseOptions = 'mouse' | 'mouse-x' | 'mouse-y'; export type PositionTypeBase = Placement; export type PositionType = PositionTypeBase | PositionMouseOptions; export interface TriggerProps { onMouseOver: (event: React.MouseEvent<HTMLElement>) => void; onMouseOut: (event: React.MouseEvent<HTMLElement>) => void; onMouseMove: ((event: React.MouseEvent<HTMLElement>) => void) | undefined; /** * Drives the deprecated `hideTooltipOnMouseDown`. With * `platform-dst-top-layer-tooltip` it also records the press, which is what * keeps the tooltip hidden after native light dismiss. Must be spread onto the * trigger, or a pointer move inside the trigger re-shows a tooltip the browser * just dismissed. */ onMouseDown: (event: React.MouseEvent<HTMLElement>) => void; /** * Drives the deprecated `hideTooltipOnClick`. With * `platform-dst-top-layer-tooltip`, a pointer press has already hidden the * tooltip (native light dismiss) by the time `click` fires. It still hides the * tooltip on a click with no pointer press, for example Enter or Space. */ onClick: (event: React.MouseEvent<HTMLElement>) => void; onFocus: (event: React.FocusEvent<HTMLElement>) => void; onBlur: (event: React.FocusEvent<HTMLElement>) => void; ref: (node: HTMLElement | null) => void; 'aria-describedby'?: string | undefined; /** * Set when `Tooltip` is given `testId`. Spread this onto your trigger element so * `data-testid` reaches the rendered DOM. Required because `@atlaskit/button/new` * (and other `Pressable`-backed primitives) overwrite `data-testid` from spread, * so the legacy `(triggerProps as any)['data-testid']` workaround is silently * absorbed by those consumers. A typed `testId` field flows through their own * `testId` destructure instead. */ testId?: string; } export interface TooltipProps { /** * The content of the tooltip. It can be either a: * 1. `ReactNode` * 2. Function which returns a `ReactNode` * The benefit of the second approach is that it allows you to consume the `update` render prop. * This `update` function can be called to manually recalculate the position of the tooltip. * * This content will be rendered into two places: * 1. Into the tooltip * 2. Into a hidden element for screen readers (unless `isScreenReaderAnnouncementDisabled` is set to `true`) * */ content: ReactNode | (({ update }: { update?: () => void; }) => ReactNode); /** * Extend `TooltipPrimitive` to create your own tooltip and pass it as component. * * Build it on `TooltipPrimitive` and forward the `ref`. With `platform-dst-top-layer-tooltip` * on, the tooltip's top-layer host carries `role="tooltip"` and `TooltipPrimitive` defaults to * `role="presentation"`, so: * - don't set `role="tooltip"` on your own element, or the tooltip gets two tooltip roles * - don't hide the content (for example `display: none`), or the host has a tooltip role with * no visible name. To hide the tooltip, don't pass `content`. */ component?: ComponentType<TooltipPrimitiveProps> | React.ForwardRefExoticComponent<React.PropsWithoutRef<TooltipPrimitiveProps> & React.RefAttributes<HTMLDivElement>>; /** * Time in milliseconds to wait before showing and hiding the tooltip. Defaults to 300. */ delay?: number; /** * Adds `pointer-events: none` to the tooltip itself. Setting this to true will also prevent the tooltip from persisting when hovered. */ ignoreTooltipPointerEvents?: boolean; /** * Hide the tooltip when the click event is triggered. Use this when the tooltip should be hidden if `onClick` react synthetic event * is triggered, which happens after `onMouseDown` event. * * @deprecated {@link https://hello.atlassian.net/browse/ENGHEALTH-TODO Internal documentation for deprecation (no external access)} * TODO: replace ENGHEALTH-TODO with the real ticket. * With `platform-dst-top-layer-tooltip`, a pointer press on the * trigger closes the tooltip by default. Native `popover="hint"` light dismiss * closes it on pointerup, before `click` fires. This prop then only has an * effect on a click with no pointer press, for example Enter or Space on a * focused trigger. Without the gate it still works as before. * * To keep the tooltip open on a press, use `hasNewContentOnTriggerClick` * instead. Do not set both: this prop turns `hasNewContentOnTriggerClick` * off. This prop will be removed in a future release. */ hideTooltipOnClick?: boolean; /** * Hide the tooltip when the mousedown event is triggered. This should be * used when tooltip should be hidden if `onMouseDown` react synthetic event * is triggered, which happens before `onClick` event. * * @deprecated {@link https://hello.atlassian.net/browse/ENGHEALTH-TODO Internal documentation for deprecation (no external access)} * TODO: replace ENGHEALTH-TODO with the real ticket. * With `platform-dst-top-layer-tooltip`, a press on the trigger * closes the tooltip by default. Native `popover="hint"` light dismiss closes * it on pointerup. This prop still works with the gate, but it only changes * the timing: the tooltip closes on mousedown, so it does not show while the * press is held. Either way the tooltip stays hidden until the trigger is * re-entered or blurred. Without the gate it still works as before. * * To keep the tooltip open on a press, use `hasNewContentOnTriggerClick` * instead. Do not set both: this prop turns `hasNewContentOnTriggerClick` * off. This prop will be removed in a future release. */ hideTooltipOnMouseDown?: boolean; /** * Set this when a press on the trigger changes `content`, for example "Copy" * to "Copied!". The tooltip then stays open when the trigger is pressed, so * the new content is visible. * * Only has an effect with `platform-dst-top-layer-tooltip`. Without the gate * the tooltip already stays open on a press. With the gate, a press normally * closes the tooltip until the pointer re-enters the trigger. * * The tooltip still closes on pointer leave, blur, scroll and Escape. * * Do not use it with the deprecated `hideTooltipOnClick` or * `hideTooltipOnMouseDown`. If you set one of them, it wins and this prop has * no effect. */ hasNewContentOnTriggerClick?: boolean; /** * Where the tooltip should appear relative to the mouse pointer. * Only use this when the `position` prop is set to `"mouse"`, `"mouse-y"`, or `"mouse-x"`. * When interacting with the target element using a keyboard, it will use this position against the target element instead. */ mousePosition?: PositionTypeBase; /** * Whether or not the tooltip can be displayed. Once a tooltip * is scheduled to be displayed, or is already displayed, it will * continue to be shown. * * @description * * `canAppear()` is called in response to user events, and * not during the rendering of components. * */ canAppear?: () => boolean; /** * By default tooltip content will be duplicated into a hidden element so * it can be read out by a screen reader. Sometimes this is not ideal as * it can result in the same content be announced twice. For those situations, * you can leverage this prop to disable the duplicate hidden text. */ isScreenReaderAnnouncementDisabled?: boolean; /** * Function to be called when the tooltip will be shown. It's called when the * tooltip begins to animate in. */ onShow?: (analyticsEvent: UIAnalyticsEvent) => void; /** * Function to be called when the tooltip will be hidden. It's called after the * delay, when the tooltip begins to animate out. */ onHide?: (analyticsEvent: UIAnalyticsEvent) => void; /** * Where the tooltip should appear relative to its target. * If set to `"mouse"`, the tooltip will display next to the mouse pointer instead. * If set to `"mouse-y"`, the tooltip will use the mouse Y coordinate but the target X coordinate. * If set to `"mouse-x"`, the tooltip will use the mouse X coordinate but the target Y coordinate. * Make sure to utilize the `mousePosition` if you want to customize where the tooltip will show in relation to the mouse. */ position?: PositionType; /** * Replace the wrapping element. This accepts the name of a html tag which will * be used to wrap the element. * If you provide a component, it needs to support a ref prop which is used by popper for positioning. */ tag?: keyof JSX.IntrinsicElements | React.ComponentType<React.AllHTMLAttributes<HTMLElement> & { ref: React.Ref<HTMLElement>; }> | React.ForwardRefExoticComponent<React.PropsWithoutRef<React.AllHTMLAttributes<HTMLElement>> & React.RefAttributes<HTMLElement>>; /** * Use this to show only one line of text, and truncate the text when it's too long. * * We no longer support truncating text in the tooltip as it's inaccessible, and this prop will be removed in a future release. * * @deprecated */ truncate?: boolean; /** * Elements to be wrapped by the tooltip. * It can be either a: * 1. `ReactNode` * 2. Function which returns a `ReactNode` */ children: ReactNode | ((props: TriggerProps) => ReactNode); /** * A `testId` prop is provided for specified elements, which is a unique * string that appears as a data attribute `data-testid` in the rendered code, * serving as a hook for automated tests. */ testId?: string; /** * Analytics context metadata. */ analyticsContext?: Record<string, any>; /** * Use this to define the strategy of popper. */ strategy?: 'absolute' | 'fixed' | undefined; /** * Display a keyboard shortcut in the tooltip. * * Keys will be displayed as individual keyboard key segments after the tooltip content. */ shortcut?: string[]; /** * When set to true, the tooltip will always use the fade-in animation * and never use the show-immediate behavior, even when another tooltip * is already visible. */ shouldAlwaysFadeIn?: boolean; /** * When enabled, the tooltip will be rendered as a sibling to the trigger element instead of being portaled to the document body. * * This is an experimental prop and may be removed at any time. It is not yet intended for public use. */ shouldRenderToParent?: boolean; }