Hide tooltips on the Esc key (#26305)

* Hide the tooltip when Esc key is pressed

* Simplify ActionsDropdown

* Rename to tooltipDescription

* Use OLTooltip instead of Tooltip

GitOrigin-RevId: ee27cde2735ae3a0de5e37bfb8ab1dd99069742c
This commit is contained in:
Rebeka Dekany
2025-06-17 08:05:47 +00:00
committed by Copybot
parent aa4d8f4925
commit 9aa261eaf6
9 changed files with 90 additions and 79 deletions
@@ -2,20 +2,21 @@ import React, { ReactNode } from 'react'
import { import {
Dropdown, Dropdown,
DropdownMenu, DropdownMenu,
DropdownToggle,
} from '@/features/ui/components/bootstrap-5/dropdown-menu' } from '@/features/ui/components/bootstrap-5/dropdown-menu'
import DropdownToggleWithTooltip from '@/features/ui/components/bootstrap-5/dropdown-toggle-with-tooltip' import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
type ActionDropdownProps = { type ActionDropdownProps = {
id: string id: string
children: React.ReactNode children: React.ReactNode
isOpened: boolean isOpened: boolean
iconTag: ReactNode iconTag: ReactNode
toolTipDescription: string tooltipDescription: string
setIsOpened: (isOpened: boolean) => void setIsOpened: (isOpened: boolean) => void
} }
function ActionsDropdown(props: ActionDropdownProps) { function ActionsDropdown(props: ActionDropdownProps) {
const { id, children, isOpened, iconTag, setIsOpened, toolTipDescription } = const { id, children, isOpened, iconTag, setIsOpened, tooltipDescription } =
props props
return ( return (
<Dropdown <Dropdown
@@ -24,16 +25,23 @@ function ActionsDropdown(props: ActionDropdownProps) {
show={isOpened} show={isOpened}
onToggle={open => setIsOpened(open)} onToggle={open => setIsOpened(open)}
> >
<DropdownToggleWithTooltip <OLTooltip
id={`history-version-dropdown-${id}`} id={`history-version-dropdown-${id}`}
className="history-version-dropdown-menu-btn" description={tooltipDescription}
aria-label={toolTipDescription} overlayProps={{ placement: 'bottom' }}
toolTipDescription={toolTipDescription} hidden={isOpened}
overlayTriggerProps={{ placement: 'bottom' }}
tooltipProps={{ hidden: isOpened }}
> >
{iconTag} {/* OverlayTrigger won't fire unless the child is a non-react html element (e.g div, span) */}
</DropdownToggleWithTooltip> <span>
<DropdownToggle
id={`history-version-dropdown-toggle-${id}`}
className="history-version-dropdown-menu-btn"
as="button"
>
{iconTag}
</DropdownToggle>
</span>
</OLTooltip>
<DropdownMenu className="history-version-dropdown-menu"> <DropdownMenu className="history-version-dropdown-menu">
{children} {children}
</DropdownMenu> </DropdownMenu>
@@ -21,7 +21,7 @@ function CompareVersionDropdown({
id={id} id={id}
isOpened={isOpened} isOpened={isOpened}
setIsOpened={setIsOpened} setIsOpened={setIsOpened}
toolTipDescription={t('compare')} tooltipDescription={t('compare')}
iconTag={ iconTag={
<MaterialIcon <MaterialIcon
type="align_space_even" type="align_space_even"
@@ -20,7 +20,7 @@ function HistoryDropdown({
<ActionsDropdown <ActionsDropdown
id={id} id={id}
isOpened={isOpened} isOpened={isOpened}
toolTipDescription={t('more_actions')} tooltipDescription={t('more_actions')}
setIsOpened={setIsOpened} setIsOpened={setIsOpened}
iconTag={ iconTag={
<MaterialIcon type="more_vert" accessibilityLabel={t('more_actions')} /> <MaterialIcon type="more_vert" accessibilityLabel={t('more_actions')} />
@@ -34,7 +34,7 @@ function CompareItems({
toVTimestamp: selRange.toVTimestamp, toVTimestamp: selRange.toVTimestamp,
}} }}
closeDropdown={closeDropdown} closeDropdown={closeDropdown}
toolTipDescription={t('history_compare_from_this_version')} tooltipDescription={t('history_compare_from_this_version')}
icon={ icon={
<MaterialIcon <MaterialIcon
type="align_end" type="align_end"
@@ -52,7 +52,7 @@ function CompareItems({
toVTimestamp: updateRange.toVTimestamp, toVTimestamp: updateRange.toVTimestamp,
}} }}
closeDropdown={closeDropdown} closeDropdown={closeDropdown}
toolTipDescription={t('history_compare_up_to_this_version')} tooltipDescription={t('history_compare_up_to_this_version')}
icon={ icon={
<MaterialIcon <MaterialIcon
type="align_start" type="align_start"
@@ -6,14 +6,14 @@ import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
type CompareProps = { type CompareProps = {
comparisonRange: UpdateRange comparisonRange: UpdateRange
icon: ReactNode icon: ReactNode
toolTipDescription?: string tooltipDescription?: string
closeDropdown: () => void closeDropdown: () => void
} }
function Compare({ function Compare({
comparisonRange, comparisonRange,
closeDropdown, closeDropdown,
toolTipDescription, tooltipDescription,
icon, icon,
}: CompareProps) { }: CompareProps) {
const { setSelection } = useHistoryContext() const { setSelection } = useHistoryContext()
@@ -32,12 +32,12 @@ function Compare({
return ( return (
<OLTooltip <OLTooltip
description={toolTipDescription} description={tooltipDescription}
id="compare-btn" id="compare-btn"
overlayProps={{ placement: 'left' }} overlayProps={{ placement: 'left' }}
> >
<button className="history-compare-btn" onClick={handleCompareVersion}> <button className="history-compare-btn" onClick={handleCompareVersion}>
<span className="visually-hidden">{toolTipDescription}</span> <span className="visually-hidden">{tooltipDescription}</span>
{icon} {icon}
</button> </button>
</OLTooltip> </OLTooltip>
@@ -1,52 +0,0 @@
import { ReactNode, forwardRef } from 'react'
import { BsPrefixRefForwardingComponent } from 'react-bootstrap/helpers'
import type { DropdownToggleProps } from '@/features/ui/components/types/dropdown-menu-props'
import {
DropdownToggle as BS5DropdownToggle,
OverlayTrigger,
OverlayTriggerProps,
Tooltip,
} from 'react-bootstrap'
import type { MergeAndOverride } from '../../../../../../types/utils'
type DropdownToggleWithTooltipProps = MergeAndOverride<
DropdownToggleProps,
{
children: ReactNode
overlayTriggerProps?: Omit<OverlayTriggerProps, 'overlay' | 'children'>
toolTipDescription: string
tooltipProps?: Omit<React.ComponentProps<typeof Tooltip>, 'children'>
'aria-label'?: string
}
>
const DropdownToggleWithTooltip = forwardRef<
BsPrefixRefForwardingComponent<'button', DropdownToggleProps>,
DropdownToggleWithTooltipProps
>(
(
{
children,
toolTipDescription,
overlayTriggerProps,
tooltipProps,
id: _id,
...toggleProps
},
ref
) => {
return (
<OverlayTrigger
overlay={<Tooltip {...tooltipProps}>{toolTipDescription}</Tooltip>}
{...overlayTriggerProps}
>
<BS5DropdownToggle {...toggleProps} ref={ref}>
{children}
</BS5DropdownToggle>
</OverlayTrigger>
)
}
)
DropdownToggleWithTooltip.displayName = 'DropdownToggleWithTooltip'
export default DropdownToggleWithTooltip
@@ -1,4 +1,10 @@
import { cloneElement, useEffect, forwardRef } from 'react' import {
cloneElement,
useEffect,
forwardRef,
useState,
useCallback,
} from 'react'
import { import {
OverlayTrigger, OverlayTrigger,
OverlayTriggerProps, OverlayTriggerProps,
@@ -41,6 +47,30 @@ function Tooltip({
overlayProps, overlayProps,
hidden, hidden,
}: TooltipProps) { }: TooltipProps) {
const [show, setShow] = useState(false)
const handleKeyDown = useCallback(
(e: KeyboardEvent) => {
if (show && e.key === 'Escape') {
setShow(false)
e.stopPropagation()
}
},
[show, setShow]
)
useEffect(() => {
document.addEventListener('keydown', handleKeyDown, true)
return () => document.removeEventListener('keydown', handleKeyDown, true)
}, [handleKeyDown])
const hideTooltip = (e: React.MouseEvent) => {
if (e.currentTarget instanceof HTMLElement) {
e.currentTarget.blur()
}
setShow(false)
}
const delay = overlayProps?.delay const delay = overlayProps?.delay
let delayShow = 300 let delayShow = 300
let delayHide = 300 let delayHide = 300
@@ -49,12 +79,6 @@ function Tooltip({
delayHide = typeof delay === 'number' ? delay : delay.hide delayHide = typeof delay === 'number' ? delay : delay.hide
} }
const hideTooltip = (e: React.MouseEvent) => {
if (e.currentTarget instanceof HTMLElement) {
e.currentTarget.blur()
}
}
return ( return (
<OverlayTrigger <OverlayTrigger
overlay={ overlay={
@@ -69,6 +93,8 @@ function Tooltip({
{...overlayProps} {...overlayProps}
delay={{ show: delayShow, hide: delayHide }} delay={{ show: delayShow, hide: delayHide }}
placement={overlayProps?.placement || 'top'} placement={overlayProps?.placement || 'top'}
show={show}
onToggle={setShow}
> >
{cloneElement(children, { {cloneElement(children, {
onClick: callFnsInSequence(children.props.onClick, hideTooltip), onClick: callFnsInSequence(children.props.onClick, hideTooltip),
@@ -32,4 +32,33 @@ describe('<OLTooltip />', function () {
cy.get('@blurHandler').should('have.been.calledOnce') cy.get('@blurHandler').should('have.been.calledOnce')
cy.findByText(description).should('not.exist') cy.findByText(description).should('not.exist')
}) })
it('hides the tooltip when Escape key is pressed', function () {
const description = 'Press Escape to close'
const btnText = 'Hover me!'
cy.mount(
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
height: '100vh',
}}
>
<OLTooltip id="esc" description={description}>
<button>{btnText}</button>
</OLTooltip>
</div>
)
cy.findByRole('button', { name: btnText }).as('button')
cy.get('@button').trigger('mouseover')
cy.findByText(description)
cy.get('@button').trigger('mouseout')
cy.get('@button').focus()
cy.findByText(description)
cy.get('body').type('{esc}')
cy.findByText(description).should('not.exist')
})
}) })
@@ -372,7 +372,7 @@ describe('change list (Bootstrap 5)', function () {
cy.findAllByTestId('history-version-details') cy.findAllByTestId('history-version-details')
.eq(1) .eq(1)
.within(() => { .within(() => {
cy.get('[aria-label="Compare"]').click() cy.findByRole('button', { name: /compare/i }).click()
cy.findByRole('menu').within(() => { cy.findByRole('menu').within(() => {
cy.findByRole('menuitem', { cy.findByRole('menuitem', {
name: /compare up to this version/i, name: /compare up to this version/i,