mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-05-24 01:29:35 +02:00
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:
@@ -2,20 +2,21 @@ import React, { ReactNode } from 'react'
|
||||
import {
|
||||
Dropdown,
|
||||
DropdownMenu,
|
||||
DropdownToggle,
|
||||
} 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 = {
|
||||
id: string
|
||||
children: React.ReactNode
|
||||
isOpened: boolean
|
||||
iconTag: ReactNode
|
||||
toolTipDescription: string
|
||||
tooltipDescription: string
|
||||
setIsOpened: (isOpened: boolean) => void
|
||||
}
|
||||
|
||||
function ActionsDropdown(props: ActionDropdownProps) {
|
||||
const { id, children, isOpened, iconTag, setIsOpened, toolTipDescription } =
|
||||
const { id, children, isOpened, iconTag, setIsOpened, tooltipDescription } =
|
||||
props
|
||||
return (
|
||||
<Dropdown
|
||||
@@ -24,16 +25,23 @@ function ActionsDropdown(props: ActionDropdownProps) {
|
||||
show={isOpened}
|
||||
onToggle={open => setIsOpened(open)}
|
||||
>
|
||||
<DropdownToggleWithTooltip
|
||||
<OLTooltip
|
||||
id={`history-version-dropdown-${id}`}
|
||||
className="history-version-dropdown-menu-btn"
|
||||
aria-label={toolTipDescription}
|
||||
toolTipDescription={toolTipDescription}
|
||||
overlayTriggerProps={{ placement: 'bottom' }}
|
||||
tooltipProps={{ hidden: isOpened }}
|
||||
description={tooltipDescription}
|
||||
overlayProps={{ placement: 'bottom' }}
|
||||
hidden={isOpened}
|
||||
>
|
||||
{iconTag}
|
||||
</DropdownToggleWithTooltip>
|
||||
{/* OverlayTrigger won't fire unless the child is a non-react html element (e.g div, span) */}
|
||||
<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">
|
||||
{children}
|
||||
</DropdownMenu>
|
||||
|
||||
@@ -21,7 +21,7 @@ function CompareVersionDropdown({
|
||||
id={id}
|
||||
isOpened={isOpened}
|
||||
setIsOpened={setIsOpened}
|
||||
toolTipDescription={t('compare')}
|
||||
tooltipDescription={t('compare')}
|
||||
iconTag={
|
||||
<MaterialIcon
|
||||
type="align_space_even"
|
||||
|
||||
@@ -20,7 +20,7 @@ function HistoryDropdown({
|
||||
<ActionsDropdown
|
||||
id={id}
|
||||
isOpened={isOpened}
|
||||
toolTipDescription={t('more_actions')}
|
||||
tooltipDescription={t('more_actions')}
|
||||
setIsOpened={setIsOpened}
|
||||
iconTag={
|
||||
<MaterialIcon type="more_vert" accessibilityLabel={t('more_actions')} />
|
||||
|
||||
@@ -34,7 +34,7 @@ function CompareItems({
|
||||
toVTimestamp: selRange.toVTimestamp,
|
||||
}}
|
||||
closeDropdown={closeDropdown}
|
||||
toolTipDescription={t('history_compare_from_this_version')}
|
||||
tooltipDescription={t('history_compare_from_this_version')}
|
||||
icon={
|
||||
<MaterialIcon
|
||||
type="align_end"
|
||||
@@ -52,7 +52,7 @@ function CompareItems({
|
||||
toVTimestamp: updateRange.toVTimestamp,
|
||||
}}
|
||||
closeDropdown={closeDropdown}
|
||||
toolTipDescription={t('history_compare_up_to_this_version')}
|
||||
tooltipDescription={t('history_compare_up_to_this_version')}
|
||||
icon={
|
||||
<MaterialIcon
|
||||
type="align_start"
|
||||
|
||||
@@ -6,14 +6,14 @@ import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
|
||||
type CompareProps = {
|
||||
comparisonRange: UpdateRange
|
||||
icon: ReactNode
|
||||
toolTipDescription?: string
|
||||
tooltipDescription?: string
|
||||
closeDropdown: () => void
|
||||
}
|
||||
|
||||
function Compare({
|
||||
comparisonRange,
|
||||
closeDropdown,
|
||||
toolTipDescription,
|
||||
tooltipDescription,
|
||||
icon,
|
||||
}: CompareProps) {
|
||||
const { setSelection } = useHistoryContext()
|
||||
@@ -32,12 +32,12 @@ function Compare({
|
||||
|
||||
return (
|
||||
<OLTooltip
|
||||
description={toolTipDescription}
|
||||
description={tooltipDescription}
|
||||
id="compare-btn"
|
||||
overlayProps={{ placement: 'left' }}
|
||||
>
|
||||
<button className="history-compare-btn" onClick={handleCompareVersion}>
|
||||
<span className="visually-hidden">{toolTipDescription}</span>
|
||||
<span className="visually-hidden">{tooltipDescription}</span>
|
||||
{icon}
|
||||
</button>
|
||||
</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 {
|
||||
OverlayTrigger,
|
||||
OverlayTriggerProps,
|
||||
@@ -41,6 +47,30 @@ function Tooltip({
|
||||
overlayProps,
|
||||
hidden,
|
||||
}: 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
|
||||
let delayShow = 300
|
||||
let delayHide = 300
|
||||
@@ -49,12 +79,6 @@ function Tooltip({
|
||||
delayHide = typeof delay === 'number' ? delay : delay.hide
|
||||
}
|
||||
|
||||
const hideTooltip = (e: React.MouseEvent) => {
|
||||
if (e.currentTarget instanceof HTMLElement) {
|
||||
e.currentTarget.blur()
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<OverlayTrigger
|
||||
overlay={
|
||||
@@ -69,6 +93,8 @@ function Tooltip({
|
||||
{...overlayProps}
|
||||
delay={{ show: delayShow, hide: delayHide }}
|
||||
placement={overlayProps?.placement || 'top'}
|
||||
show={show}
|
||||
onToggle={setShow}
|
||||
>
|
||||
{cloneElement(children, {
|
||||
onClick: callFnsInSequence(children.props.onClick, hideTooltip),
|
||||
|
||||
@@ -32,4 +32,33 @@ describe('<OLTooltip />', function () {
|
||||
cy.get('@blurHandler').should('have.been.calledOnce')
|
||||
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')
|
||||
.eq(1)
|
||||
.within(() => {
|
||||
cy.get('[aria-label="Compare"]').click()
|
||||
cy.findByRole('button', { name: /compare/i }).click()
|
||||
cy.findByRole('menu').within(() => {
|
||||
cy.findByRole('menuitem', {
|
||||
name: /compare up to this version/i,
|
||||
|
||||
Reference in New Issue
Block a user