mirror of
https://github.com/enso-org/enso.git
synced 2025-01-03 18:38:08 +03:00
Don't hide visual tooltip when mouse goes from target to tooltip (#10177)
This commit is contained in:
parent
8332118ff4
commit
4e92d784e2
@ -126,19 +126,8 @@ export const Text = React.forwardRef(function Text(
|
|||||||
balance,
|
balance,
|
||||||
})
|
})
|
||||||
|
|
||||||
const tooltipTextClasses = TEXT_STYLE({
|
|
||||||
variant,
|
|
||||||
weight,
|
|
||||||
transform,
|
|
||||||
monospace,
|
|
||||||
italic,
|
|
||||||
balance,
|
|
||||||
className: 'pointer-events-none',
|
|
||||||
})
|
|
||||||
|
|
||||||
const { tooltip, targetProps } = visualTooltip.useVisualTooltip({
|
const { tooltip, targetProps } = visualTooltip.useVisualTooltip({
|
||||||
isDisabled: !truncate,
|
isDisabled: !truncate,
|
||||||
className: tooltipTextClasses,
|
|
||||||
targetRef: textElementRef,
|
targetRef: textElementRef,
|
||||||
display: 'whenOverflowing',
|
display: 'whenOverflowing',
|
||||||
children,
|
children,
|
||||||
|
@ -6,16 +6,19 @@
|
|||||||
|
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
|
|
||||||
import clsx from 'clsx'
|
import * as eventCallback from '#/hooks/eventCallbackHooks'
|
||||||
|
|
||||||
import * as aria from '#/components/aria'
|
import * as aria from '#/components/aria'
|
||||||
import * as ariaComponents from '#/components/AriaComponents'
|
import * as ariaComponents from '#/components/AriaComponents'
|
||||||
import Portal from '#/components/Portal'
|
import Portal from '#/components/Portal'
|
||||||
|
|
||||||
|
import * as mergeRefs from '#/utilities/mergeRefs'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Props for {@link useVisualTooltip}.
|
* Props for {@link useVisualTooltip}.
|
||||||
*/
|
*/
|
||||||
export interface VisualTooltipProps {
|
export interface VisualTooltipProps
|
||||||
|
extends Pick<ariaComponents.TooltipProps, 'maxWidth' | 'rounded' | 'size' | 'variant'> {
|
||||||
readonly children: React.ReactNode
|
readonly children: React.ReactNode
|
||||||
readonly className?: string
|
readonly className?: string
|
||||||
readonly targetRef: React.RefObject<HTMLElement>
|
readonly targetRef: React.RefObject<HTMLElement>
|
||||||
@ -40,6 +43,7 @@ export interface VisualTooltipProps {
|
|||||||
type DisplayStrategy = 'always' | 'whenOverflowing'
|
type DisplayStrategy = 'always' | 'whenOverflowing'
|
||||||
|
|
||||||
const DEFAULT_OFFSET = 6
|
const DEFAULT_OFFSET = 6
|
||||||
|
const DEFAULT_DELAY = 250
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a tooltip that appears when the target element is hovered over.
|
* Creates a tooltip that appears when the target element is hovered over.
|
||||||
@ -57,6 +61,10 @@ export function useVisualTooltip(props: VisualTooltipProps) {
|
|||||||
overlayPositionProps = {},
|
overlayPositionProps = {},
|
||||||
display = 'always',
|
display = 'always',
|
||||||
testId = 'visual-tooltip',
|
testId = 'visual-tooltip',
|
||||||
|
rounded,
|
||||||
|
variant,
|
||||||
|
size,
|
||||||
|
maxWidth,
|
||||||
} = props
|
} = props
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -68,27 +76,43 @@ export function useVisualTooltip(props: VisualTooltipProps) {
|
|||||||
const popoverRef = React.useRef<HTMLDivElement>(null)
|
const popoverRef = React.useRef<HTMLDivElement>(null)
|
||||||
const id = React.useId()
|
const id = React.useId()
|
||||||
|
|
||||||
const { hoverProps, isHovered } = aria.useHover({
|
const state = aria.useTooltipTriggerState({
|
||||||
onHoverStart: () => {
|
closeDelay: DEFAULT_DELAY,
|
||||||
if (targetRef.current) {
|
delay: DEFAULT_DELAY,
|
||||||
const shouldDisplay =
|
|
||||||
typeof display === 'function'
|
|
||||||
? display(targetRef.current)
|
|
||||||
: DISPLAY_STRATEGIES[display](targetRef.current)
|
|
||||||
|
|
||||||
if (shouldDisplay) {
|
|
||||||
popoverRef.current?.showPopover()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onHoverEnd: () => {
|
|
||||||
popoverRef.current?.hidePopover()
|
|
||||||
},
|
|
||||||
isDisabled,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const { overlayProps } = aria.useOverlayPosition({
|
const handleHoverChange = eventCallback.useEventCallback((isHovered: boolean) => {
|
||||||
isOpen: isHovered,
|
const shouldDisplay = () => {
|
||||||
|
if (isHovered && targetRef.current != null) {
|
||||||
|
return typeof display === 'function'
|
||||||
|
? display(targetRef.current)
|
||||||
|
: DISPLAY_STRATEGIES[display](targetRef.current)
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldDisplay()) {
|
||||||
|
state.open()
|
||||||
|
} else {
|
||||||
|
state.close()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const { hoverProps: targetHoverProps } = aria.useHover({
|
||||||
|
isDisabled,
|
||||||
|
onHoverChange: handleHoverChange,
|
||||||
|
})
|
||||||
|
const { hoverProps: tooltipHoverProps } = aria.useHover({
|
||||||
|
isDisabled,
|
||||||
|
onHoverChange: handleHoverChange,
|
||||||
|
})
|
||||||
|
|
||||||
|
const { tooltipProps } = aria.useTooltipTrigger({}, state, targetRef)
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||||
|
const { overlayProps, updatePosition } = aria.useOverlayPosition({
|
||||||
|
isOpen: state.isOpen,
|
||||||
overlayRef: popoverRef,
|
overlayRef: popoverRef,
|
||||||
targetRef,
|
targetRef,
|
||||||
offset,
|
offset,
|
||||||
@ -96,27 +120,42 @@ export function useVisualTooltip(props: VisualTooltipProps) {
|
|||||||
containerPadding,
|
containerPadding,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const createTooltipElement = () => (
|
||||||
|
<Portal onMount={updatePosition}>
|
||||||
|
<span
|
||||||
|
ref={mergeRefs.mergeRefs(popoverRef, ref => ref?.showPopover())}
|
||||||
|
{...aria.mergeProps<React.HTMLAttributes<HTMLDivElement>>()(
|
||||||
|
overlayProps,
|
||||||
|
tooltipProps,
|
||||||
|
tooltipHoverProps,
|
||||||
|
{
|
||||||
|
id,
|
||||||
|
className: ariaComponents.TOOLTIP_STYLES({
|
||||||
|
className,
|
||||||
|
variant,
|
||||||
|
rounded,
|
||||||
|
size,
|
||||||
|
maxWidth,
|
||||||
|
}),
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
|
'aria-hidden': true,
|
||||||
|
popover: '',
|
||||||
|
role: 'presentation',
|
||||||
|
'data-testid': testId,
|
||||||
|
// Remove z-index from the overlay style
|
||||||
|
// because it's not needed(we show latest element on top) and can cause issues with stacking context
|
||||||
|
style: { zIndex: '' },
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</span>
|
||||||
|
</Portal>
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
targetProps: aria.mergeProps<React.HTMLAttributes<HTMLElement>>()(hoverProps, { id }),
|
targetProps: aria.mergeProps<React.HTMLAttributes<HTMLElement>>()(targetHoverProps, { id }),
|
||||||
tooltip: isDisabled ? null : (
|
tooltip: state.isOpen ? createTooltipElement() : null,
|
||||||
<Portal>
|
|
||||||
<span
|
|
||||||
id={id}
|
|
||||||
ref={popoverRef}
|
|
||||||
className={ariaComponents.TOOLTIP_STYLES({
|
|
||||||
className: clsx(className, 'hidden animate-in fade-in [&:popover-open]:flex'),
|
|
||||||
})}
|
|
||||||
// @ts-expect-error popover attribute does not exist on React.HTMLAttributes yet
|
|
||||||
popover=""
|
|
||||||
aria-hidden="true"
|
|
||||||
role="presentation"
|
|
||||||
data-testid={testId}
|
|
||||||
{...overlayProps}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</span>
|
|
||||||
</Portal>
|
|
||||||
),
|
|
||||||
} as const
|
} as const
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,6 +23,9 @@ export const TOOLTIP_STYLES = twv.tv({
|
|||||||
rounded: {
|
rounded: {
|
||||||
custom: '',
|
custom: '',
|
||||||
full: 'rounded-full',
|
full: 'rounded-full',
|
||||||
|
xxxlarge: 'rounded-3xl',
|
||||||
|
xxlarge: 'rounded-2xl',
|
||||||
|
xlarge: 'rounded-xl',
|
||||||
large: 'rounded-lg',
|
large: 'rounded-lg',
|
||||||
medium: 'rounded-md',
|
medium: 'rounded-md',
|
||||||
small: 'rounded-sm',
|
small: 'rounded-sm',
|
||||||
@ -47,7 +50,7 @@ export const TOOLTIP_STYLES = twv.tv({
|
|||||||
variant: 'primary',
|
variant: 'primary',
|
||||||
size: 'medium',
|
size: 'medium',
|
||||||
maxWidth: 'xsmall',
|
maxWidth: 'xsmall',
|
||||||
rounded: 'full',
|
rounded: 'xxxlarge',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -60,7 +63,8 @@ const DEFAULT_OFFSET = 9
|
|||||||
|
|
||||||
/** Props for a {@link Tooltip}. */
|
/** Props for a {@link Tooltip}. */
|
||||||
export interface TooltipProps
|
export interface TooltipProps
|
||||||
extends Omit<Readonly<aria.TooltipProps>, 'offset' | 'UNSTABLE_portalContainer'> {}
|
extends Omit<Readonly<aria.TooltipProps>, 'offset' | 'UNSTABLE_portalContainer'>,
|
||||||
|
Omit<twv.VariantProps<typeof TOOLTIP_STYLES>, 'isEntering' | 'isExiting'> {}
|
||||||
|
|
||||||
/** Displays the description of an element on hover or focus. */
|
/** Displays the description of an element on hover or focus. */
|
||||||
export function Tooltip(props: TooltipProps) {
|
export function Tooltip(props: TooltipProps) {
|
||||||
|
@ -7,6 +7,7 @@ export * from 'react-aria'
|
|||||||
export * from 'react-aria-components'
|
export * from 'react-aria-components'
|
||||||
// @ts-expect-error The conflicting exports are props types ONLY.
|
// @ts-expect-error The conflicting exports are props types ONLY.
|
||||||
export * from '@react-stately/overlays'
|
export * from '@react-stately/overlays'
|
||||||
|
export * from '@react-stately/tooltip'
|
||||||
|
|
||||||
/** Merges multiple props objects together.
|
/** Merges multiple props objects together.
|
||||||
* Event handlers are chained, classNames are combined, and ids are deduplicated -
|
* Event handlers are chained, classNames are combined, and ids are deduplicated -
|
||||||
|
@ -566,7 +566,14 @@ body[data-debug] [data-navigator2d-neighbor] {
|
|||||||
@apply text-amber-500;
|
@apply text-amber-500;
|
||||||
}
|
}
|
||||||
|
|
||||||
:where([popover]) {
|
/** The default styles for the popover. */
|
||||||
|
:where(
|
||||||
|
:is(
|
||||||
|
.enso-dashboard [popover],
|
||||||
|
.enso-chat [popover],
|
||||||
|
.enso-portal-root [popover]
|
||||||
|
)
|
||||||
|
) {
|
||||||
inset: unset;
|
inset: unset;
|
||||||
margin: unset;
|
margin: unset;
|
||||||
width: unset;
|
width: unset;
|
||||||
|
Loading…
Reference in New Issue
Block a user